147 lines
3.6 KiB
TypeScript
147 lines
3.6 KiB
TypeScript
/*
|
|
* https://github.com/morethanwords/tweb
|
|
* Copyright (C) 2019-2021 Eduard Kuzmenko
|
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
|
*/
|
|
|
|
import ButtonMenu, {ButtonMenuItemOptionsVerifiable} from '../../components/buttonMenu';
|
|
import filterAsync from '../array/filterAsync';
|
|
import callbackify from '../callbackify';
|
|
import contextMenuController from '../contextMenuController';
|
|
import ListenerSetter from '../listenerSetter';
|
|
import {getMiddleware, Middleware} from '../middleware';
|
|
import positionMenu from '../positionMenu';
|
|
import {attachContextMenuListener} from './attachContextMenuListener';
|
|
import {attachClickEvent} from './clickEvent';
|
|
|
|
export default function createContextMenu<T extends ButtonMenuItemOptionsVerifiable>({
|
|
buttons,
|
|
findElement,
|
|
listenTo,
|
|
appendTo,
|
|
filterButtons,
|
|
onOpen,
|
|
onClose,
|
|
onBeforeOpen,
|
|
listenerSetter: attachListenerSetter,
|
|
middleware,
|
|
listenForClick
|
|
}: {
|
|
buttons: T[],
|
|
findElement?: (e: MouseEvent | TouchEvent) => HTMLElement,
|
|
listenTo: HTMLElement,
|
|
appendTo?: HTMLElement,
|
|
filterButtons?: (buttons: T[]) => Promise<T[]>,
|
|
onOpen?: (target: HTMLElement) => any,
|
|
onClose?: () => any,
|
|
onBeforeOpen?: () => any,
|
|
listenerSetter?: ListenerSetter,
|
|
middleware?: Middleware,
|
|
listenForClick?: boolean
|
|
}) {
|
|
appendTo ??= document.body;
|
|
|
|
attachListenerSetter ??= new ListenerSetter();
|
|
const listenerSetter = new ListenerSetter();
|
|
const middlewareHelper = middleware ? middleware.create() : getMiddleware();
|
|
let element: HTMLElement;
|
|
|
|
const open = (e: MouseEvent | TouchEvent) => {
|
|
const target = findElement ? findElement(e as any) : listenTo;
|
|
if(!target) {
|
|
return;
|
|
}
|
|
|
|
let _element = element;
|
|
if(e instanceof MouseEvent || e.hasOwnProperty('preventDefault')) (e as any).preventDefault();
|
|
if(_element && _element.classList.contains('active')) {
|
|
return false;
|
|
}
|
|
if(e instanceof MouseEvent || e.hasOwnProperty('cancelBubble')) (e as any).cancelBubble = true;
|
|
|
|
const r = async() => {
|
|
await onOpen?.(target);
|
|
|
|
const initResult = await init();
|
|
if(!initResult) {
|
|
return;
|
|
}
|
|
|
|
_element = initResult.element;
|
|
const {cleanup, destroy} = initResult;
|
|
|
|
positionMenu(e, _element);
|
|
contextMenuController.openBtnMenu(_element, () => {
|
|
onClose?.();
|
|
cleanup();
|
|
|
|
setTimeout(() => {
|
|
destroy();
|
|
}, 300);
|
|
});
|
|
};
|
|
|
|
r();
|
|
};
|
|
|
|
attachContextMenuListener({
|
|
element: listenTo,
|
|
callback: open,
|
|
listenerSetter: attachListenerSetter
|
|
});
|
|
|
|
const cleanup = () => {
|
|
listenerSetter.removeAll();
|
|
middlewareHelper.clean();
|
|
};
|
|
|
|
const destroy = () => {
|
|
cleanup();
|
|
attachListenerSetter.removeAll();
|
|
};
|
|
|
|
const init = async() => {
|
|
cleanup();
|
|
|
|
buttons.forEach((button) => button.element = undefined);
|
|
const f = filterButtons || ((buttons: T[]) => filterAsync(buttons, (button) => {
|
|
return button?.verify ? callbackify(button.verify(), (result) => result ?? false) : true;
|
|
}));
|
|
|
|
const filteredButtons = await f(buttons);
|
|
if(!filteredButtons.length) {
|
|
return;
|
|
}
|
|
|
|
const _element = element = await ButtonMenu({
|
|
buttons: filteredButtons,
|
|
listenerSetter
|
|
});
|
|
_element.classList.add('contextmenu');
|
|
|
|
await onBeforeOpen?.();
|
|
|
|
appendTo.append(_element);
|
|
|
|
return {
|
|
element: _element,
|
|
cleanup,
|
|
destroy: () => {
|
|
_element.remove();
|
|
}
|
|
};
|
|
};
|
|
|
|
if(middleware) {
|
|
middleware.onDestroy(() => {
|
|
destroy();
|
|
});
|
|
}
|
|
|
|
if(listenForClick) {
|
|
attachClickEvent(listenTo, open, {listenerSetter: attachListenerSetter});
|
|
}
|
|
|
|
return {element, destroy, open};
|
|
}
|