gristlabs_grist-core/app/client/ui/contextMenu.ts
Cyprien P 7cc3092e1b (core) Restore default context menu out of std views
Summary:
Also fix few clode glitches
- Attach events handler to the scrollPane using `onMatch` instead of
  the cell itself. This streamline cell dom creation a bit while
  scrolling.
- Fix memory leaks on contextMenu.
- Also fix Importer and ColumnOps nbrowser test

see:
https://phab.getgrist.com/D3237#inline-36376
https://phab.getgrist.com/D3237#inline-36375

Restore default context menu for where there's no custom one

It appears that default context menu adds some value, in particular
for links and for editing text. This diff restores it.

Test Plan: Should not break anything.

Reviewers: paulfitz

Reviewed By: paulfitz

Differential Revision: https://phab.getgrist.com/D3256
2022-02-11 09:05:54 +01:00

94 lines
3.6 KiB
TypeScript

/**
* This module implements context menu to be shown on contextmenu event (most commonly associated
* with right+click, but could varies slightly depending on platform, ie: mac support ctrl+click as
* well).
*
* To prevent the default context menu to show everywhere else (including on the top of your custom
* context menu) dont forget to prevent it by including below line at the root of the dom:
* `dom.on('contextmenu', ev => ev.preventDefault())`
*/
import { Disposable, dom, DomArg, DomContents, Holder } from "grainjs";
import { cssMenuElem } from 'app/client/ui2018/menus';
import { IOpenController, Menu } from 'popweasel';
export type IContextMenuContentFunc = (ctx: ContextMenuController) => DomContents;
class ContextMenuController extends Disposable implements IOpenController {
private _content: HTMLElement;
constructor(private _event: MouseEvent, contentFunc: IContextMenuContentFunc) {
super();
setTimeout(() => this._updatePosition(), 0);
// Create content and add to the dom but keep hidden until menu gets positioned
const menu = Menu.create(null, this, [contentFunc(this)], {
menuCssClass: cssMenuElem.className + ' grist-floating-menu'
});
const content = this._content = menu.content;
content.style.visibility = 'hidden';
document.body.appendChild(content);
// Prevents arrow to move the cursor while menu is open.
dom.onKeyElem(content, 'keydown', {
ArrowLeft: (ev) => ev.stopPropagation(),
ArrowRight: (ev) => ev.stopPropagation()
// UP and DOWN are already handle by the menu to navigate the menu)
});
// On click anywhere on the page (outside popup content), close it.
const onClick = (evt: MouseEvent) => {
const target: Node|null = evt.target as Node;
if (target && !content.contains(target)) {
this.close();
}
};
this.autoDispose(dom.onElem(document, 'contextmenu', onClick, {useCapture: true}));
this.autoDispose(dom.onElem(document, 'click', onClick, {useCapture: true}));
// Cleanup involves removing the element.
this.onDispose(() => {
dom.domDispose(content);
content.remove();
});
}
public close() {
this.dispose();
}
public setOpenClass() {}
// IOpenController expects a trigger elem but context menu has no trigger. Let's return body for
// now. As of time of writing the trigger elem is only used by popweasel when certain options are
// enabled, ie: strectToSelector, parentSelectoToMark.
// TODO: make a PR on popweasel to support using Menu with no trigger element.
public getTriggerElem() { return document.body; }
public update() {}
private _updatePosition() {
const content = this._content;
const ev = this._event;
const rect = content.getBoundingClientRect();
// position menu on the right of the cursor if it can fit, on the left otherwise
content.style.left = ((ev.pageX + rect.width < window.innerWidth) ? ev.pageX : ev.pageX - rect.width) + 'px';
// position menu below the cursor if it can fit, otherwise fit at the bottom of the screen
content.style.bottom = Math.max(window.innerHeight - (ev.pageY + rect.height), 0) + 'px';
// show content
content.style.visibility = '';
}
}
/**
* Show a context menu on contextmenu.
*/
export function contextMenu(contentFunc: IContextMenuContentFunc): DomArg {
return (elem) => {
const holder = Holder.create(null);
dom.autoDisposeElem(elem, holder);
dom.onElem(elem, 'contextmenu', (ev) => {
ev.preventDefault();
ev.stopPropagation();
ContextMenuController.create(holder, ev, contentFunc);
});
};
}