Skip to content

Commit

Permalink
Support host and ref options in Menu.open (#700)
Browse files Browse the repository at this point in the history
* Support host and ref options in Menu.open

* Address review comments
  • Loading branch information
ianthomas23 authored May 19, 2024
1 parent 43ae67c commit f26c85a
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 4 deletions.
33 changes: 29 additions & 4 deletions packages/widgets/src/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,12 @@ export class Menu extends Widget {
* fully fit on the screen. If it will not fit, it will be adjusted
* to fit naturally on the screen.
*
* The menu will be attached under the `host` element in the DOM
* (or `document.body` if `host` is `null`) and before the `ref`
* element (or as the last child of `host` if `ref` is `null`).
* The menu may be displayed outside of the `host` element
* following the rules of CSS absolute positioning.
*
* This is a no-op if the menu is already attached to the DOM.
*/
open(x: number, y: number, options: Menu.IOpenOptions = {}): void {
Expand All @@ -454,12 +460,14 @@ export class Menu extends Widget {
return;
}

// Extract the position options.
// Extract the menu options.
let forceX = options.forceX || false;
let forceY = options.forceY || false;
const host = options.host ?? null;
const ref = options.ref ?? null;

// Open the menu as a root menu.
Private.openRootMenu(this, x, y, forceX, forceY);
Private.openRootMenu(this, x, y, forceX, forceY, host, ref);

// Activate the menu to accept keyboard input.
this.activate();
Expand Down Expand Up @@ -986,6 +994,21 @@ export namespace Menu {
* The default is `false`.
*/
forceY?: boolean;

/**
* The DOM node to use as the menu's host.
*
* If not specified then uses `document.body`.
*/
host?: HTMLElement;

/**
* The child of `host` to use as the reference element.
* If this is provided, the menu will be inserted before this
* node in the host. The default is `null`, which will cause the
* menu to be added as the last child of the host.
*/
ref?: HTMLElement;
}

/**
Expand Down Expand Up @@ -1535,7 +1558,9 @@ namespace Private {
x: number,
y: number,
forceX: boolean,
forceY: boolean
forceY: boolean,
host: HTMLElement | null,
ref: HTMLElement | null
): void {
// Get the current position and size of the main viewport.
const windowData = getWindowData();
Expand All @@ -1559,7 +1584,7 @@ namespace Private {
style.maxHeight = `${maxHeight}px`;

// Attach the menu to the document.
Widget.attach(menu, document.body);
Widget.attach(menu, host || document.body, ref);

// Measure the size of the menu.
let { width, height } = node.getBoundingClientRect();
Expand Down
39 changes: 39 additions & 0 deletions packages/widgets/tests/src/menu.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,45 @@ describe('@lumino/widgets', () => {
menu.open(100, 100);
expect(menu.node.style.transform).to.equal('translate(10px, 10px)');
});

it('should insert as last child under document.body by default', () => {
const div = document.body.appendChild(document.createElement('div'));
menu.addItem({ command: 'test' });
menu.open(10, 10);
expect(menu.node.parentElement).to.equal(document.body);
expect(menu.node.previousElementSibling).to.equal(div);
expect(menu.node.nextElementSibling).to.be.null;
});

it('should insert as last child under specified host element', () => {
const div = document.body.appendChild(document.createElement('div'));
const child = div.appendChild(document.createElement('div'));
menu.addItem({ command: 'test' });
menu.open(10, 10, { host: div });
expect(menu.node.parentElement).to.equal(div);
expect(menu.node.previousElementSibling).to.equal(child);
expect(menu.node.nextElementSibling).to.be.null;
});

it('should insert before reference element under document.body', () => {
const div1 = document.body.appendChild(document.createElement('div'));
const div2 = document.body.appendChild(document.createElement('div'));
menu.addItem({ command: 'test' });
menu.open(10, 10, { ref: div2 });
expect(menu.node.parentElement).to.equal(document.body);
expect(menu.node.previousElementSibling).to.equal(div1);
expect(menu.node.nextElementSibling).to.equal(div2);
});

it('should insert before reference element under specified host element', () => {
const div = document.body.appendChild(document.createElement('div'));
const child1 = div.appendChild(document.createElement('div'));
const child2 = div.appendChild(document.createElement('div'));
menu.open(10, 10, { host: div, ref: child2 });
expect(menu.node.parentElement).to.equal(div);
expect(menu.node.previousElementSibling).to.equal(child1);
expect(menu.node.nextElementSibling).to.equal(child2);
});
});

describe('#handleEvent()', () => {
Expand Down
2 changes: 2 additions & 0 deletions review/api/widgets.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,8 @@ export namespace Menu {
export interface IOpenOptions {
forceX?: boolean;
forceY?: boolean;
host?: HTMLElement;
ref?: HTMLElement;
}
export interface IOptions {
commands: CommandRegistry;
Expand Down

0 comments on commit f26c85a

Please sign in to comment.