mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
7cc3092e1b
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
94 lines
3.6 KiB
TypeScript
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);
|
|
});
|
|
};
|
|
}
|