Skip to content

Commit

Permalink
Merge pull request #206 from fcollonval/ft/contextmenu-global-sort
Browse files Browse the repository at this point in the history
Add option to not group context menu item by target
  • Loading branch information
blink1073 authored Jul 22, 2021
2 parents a105b6f + 8a96cee commit 28c3826
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 42 deletions.
47 changes: 36 additions & 11 deletions packages/widgets/src/contextmenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
import { ArrayExt, each } from "@lumino/algorithm";
import { ArrayExt, each } from '@lumino/algorithm';

import { CommandRegistry } from "@lumino/commands";
import { CommandRegistry } from '@lumino/commands';

import { DisposableDelegate, IDisposable } from "@lumino/disposable";
import { DisposableDelegate, IDisposable } from '@lumino/disposable';

import { Selector } from "@lumino/domutils";
import { Selector } from '@lumino/domutils';

import { Menu } from "./menu";
import { Menu } from './menu';

/**
* An object which implements a universal context menu.
Expand All @@ -33,8 +33,9 @@ export class ContextMenu {
* @param options - The options for initializing the menu.
*/
constructor(options: ContextMenu.IOptions) {
const { sortBySelector, ...others } = options;
const { groupByTarget, sortBySelector, ...others } = options;
this.menu = new Menu(others);
this._groupByTarget = groupByTarget !== false;
this._sortBySelector = sortBySelector !== false;
}

Expand Down Expand Up @@ -86,7 +87,12 @@ export class ContextMenu {
}

// Find the matching items for the event.
let items = Private.matchItems(this._items, event, this._sortBySelector);
let items = Private.matchItems(
this._items,
event,
this._groupByTarget,
this._sortBySelector
);

// Bail if there are no matching items.
if (!items || items.length === 0) {
Expand All @@ -105,6 +111,7 @@ export class ContextMenu {
return true;
}

private _groupByTarget: boolean = true;
private _idTick = 0;
private _items: Private.IItem[] = [];
private _sortBySelector: boolean = true;
Expand Down Expand Up @@ -134,6 +141,17 @@ export namespace ContextMenu {
* Default true.
*/
sortBySelector?: boolean;

/**
* Whether to group items following the DOM hierarchy.
*
* Default true.
*
* #### Note
* If true, when the mouse event occurs on element `span` within `div.top`,
* the items matching `div.top` will be shown before the ones matching `body`.
*/
groupByTarget?: boolean;
}

/**
Expand Down Expand Up @@ -212,7 +230,8 @@ namespace Private {
export function matchItems(
items: IItem[],
event: MouseEvent,
sortBySelector: boolean,
groupByTarget: boolean,
sortBySelector: boolean
): IItem[] | null {
// Look up the target of the event.
let target = event.target as Element | null;
Expand Down Expand Up @@ -276,7 +295,9 @@ namespace Private {

// Sort the matches for this level and add them to the results.
if (matches.length !== 0) {
matches.sort(sortBySelector ? itemCmp : itemCmpRank);
if (groupByTarget) {
matches.sort(sortBySelector ? itemCmp : itemCmpRank);
}
result.push(...matches);
}

Expand All @@ -289,6 +310,10 @@ namespace Private {
target = target.parentElement;
}

if (!groupByTarget) {
result.sort(sortBySelector ? itemCmp : itemCmpRank);
}

// Return the matched and sorted results.
return result;
}
Expand All @@ -300,7 +325,7 @@ namespace Private {
* invalid or contains commas.
*/
function validateSelector(selector: string): string {
if (selector.indexOf(",") !== -1) {
if (selector.indexOf(',') !== -1) {
throw new Error(`Selector cannot contain commas: ${selector}`);
}
if (!Selector.isValid(selector)) {
Expand Down Expand Up @@ -334,7 +359,7 @@ namespace Private {
if (s1 !== s2) {
return s2 - s1;
}

// If specificities are equal
return itemCmpRank(a, b);
}
Expand Down
125 changes: 94 additions & 31 deletions packages/widgets/tests/src/contextmenu.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,72 +7,77 @@
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
import { expect } from "chai";
import { expect } from 'chai';

// import { simulate } from "simulate-event";
// import { simulate } from 'simulate-event';

import { CommandRegistry } from "@lumino/commands";
import { CommandRegistry } from '@lumino/commands';

import { JSONObject } from "@lumino/coreutils";
import { JSONObject } from '@lumino/coreutils';

import { ContextMenu } from "@lumino/widgets";
import { ContextMenu } from '@lumino/widgets';

describe("@lumino/widgets", () => {
describe('@lumino/widgets', () => {
let commands = new CommandRegistry();

before(() => {
commands.addCommand("test-1", {
commands.addCommand('test-1', {
execute: (args: JSONObject) => {},
label: "Test 1 Label",
label: 'Test 1 Label',
});
commands.addCommand("test-2", {
commands.addCommand('test-2', {
execute: (args: JSONObject) => {},
label: "Test 2 Label",
label: 'Test 2 Label',
});
commands.addCommand("test-3", {
commands.addCommand('test-3', {
execute: (args: JSONObject) => {},
label: "Test 3 Label",
label: 'Test 3 Label',
});
commands.addCommand("test-4", {
commands.addCommand('test-4', {
execute: (args: JSONObject) => {},
label: "Test 4 Label",
label: 'Test 4 Label',
});
});

describe("ContextMenu", () => {
describe("#open", () => {
describe('ContextMenu', () => {
describe('#open', () => {
let menu: ContextMenu;
const CLASSNAME = "menu-1";
const CLASSNAME = 'menu-1';

function addItems(menu: ContextMenu) {
menu.addItem({
command: "test-1",
command: 'test-1',
selector: `.${CLASSNAME}`,
rank: 20,
});
menu.addItem({
command: "test-2",
command: 'test-2',
selector: `.${CLASSNAME}`,
rank: 10,
});
menu.addItem({
command: "test-3",
command: 'test-3',
selector: `div.${CLASSNAME}`,
rank: 30,
});
menu.addItem({
command: "test-4",
selector: ".menu-2",
command: 'test-4',
selector: '.menu-2',
rank: 1,
});
menu.addItem({
command: 'test-5',
selector: 'body',
rank: 15,
});
}

afterEach(() => {
menu && menu.menu.dispose();
});

it("should show items matching selector and order by selector and rank", () => {
const target = document.createElement("div");
it('should show items matching selector, grouped and ordered by selector and rank', () => {
const target = document.createElement('div');
target.className = CLASSNAME;
document.body.appendChild(target);

Expand All @@ -87,15 +92,16 @@ describe("@lumino/widgets", () => {
clientX: bb.x,
clientY: bb.y,
} as any);
expect(menu.menu.items).to.have.length(3);

expect(menu.menu.items).to.have.length(4);
expect(menu.menu.items[0].command).to.equal('test-3');
expect(menu.menu.items[1].command).to.equal('test-2');
expect(menu.menu.items[2].command).to.equal('test-1');
expect(menu.menu.items[3].command).to.equal('test-5');
});

it("should show items matching selector and order only by rank", () => {
const target = document.createElement("div");
it('should show items matching selector, grouped and ordered only by rank', () => {
const target = document.createElement('div');
target.className = CLASSNAME;
document.body.appendChild(target);

Expand All @@ -110,11 +116,68 @@ describe("@lumino/widgets", () => {
clientX: bb.x,
clientY: bb.y,
} as any);
expect(menu.menu.items).to.have.length(3);

expect(menu.menu.items).to.have.length(4);
expect(menu.menu.items[0].command).to.equal('test-2');
expect(menu.menu.items[1].command).to.equal('test-1');
expect(menu.menu.items[2].command).to.equal('test-3');
expect(menu.menu.items[3].command).to.equal('test-5');
});

it('should show items matching selector, ungrouped and ordered by selector and rank', () => {
const target = document.createElement('div');
target.className = CLASSNAME;
document.body.appendChild(target);

menu = new ContextMenu({
commands,
groupByTarget: false,
sortBySelector: false,
});
addItems(menu);

const bb = target.getBoundingClientRect() as DOMRect;

menu.open({
target,
currentTarget: document.body,
clientX: bb.x,
clientY: bb.y,
} as any);

expect(menu.menu.items).to.have.length(4);
expect(menu.menu.items[1].command).to.equal('test-5');
expect(menu.menu.items[0].command).to.equal('test-2');
expect(menu.menu.items[2].command).to.equal('test-1');
expect(menu.menu.items[3].command).to.equal('test-3');
});

it('should show items matching selector, ungrouped and ordered only by rank', () => {
const target = document.createElement('div');
target.className = CLASSNAME;
document.body.appendChild(target);

menu = new ContextMenu({
commands,
groupByTarget: false,
sortBySelector: false,
});
addItems(menu);

const bb = target.getBoundingClientRect() as DOMRect;

menu.open({
target,
currentTarget: document.body,
clientX: bb.x,
clientY: bb.y,
} as any);

expect(menu.menu.items).to.have.length(4);
expect(menu.menu.items[0].command).to.equal('test-2');
expect(menu.menu.items[1].command).to.equal('test-5');
expect(menu.menu.items[2].command).to.equal('test-1');
expect(menu.menu.items[3].command).to.equal('test-3');
});
});
});
Expand Down

0 comments on commit 28c3826

Please sign in to comment.