tweb/src/helpers/dom/createContextMenu.ts

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};
}