tweb/src/components/buttonMenuToggle.ts

130 lines
3.5 KiB
TypeScript

/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import contextMenuController from '../helpers/contextMenuController';
import cancelEvent from '../helpers/dom/cancelEvent';
import {AttachClickOptions, CLICK_EVENT_NAME, hasMouseMovedSinceDown} from '../helpers/dom/clickEvent';
import ListenerSetter from '../helpers/listenerSetter';
import ButtonIcon from './buttonIcon';
import ButtonMenu, {ButtonMenuItemOptionsVerifiable} from './buttonMenu';
import filterAsync from '../helpers/array/filterAsync';
import {doubleRaf} from '../helpers/schedulers';
// TODO: refactor for attachClickEvent, because if move finger after touchstart, it will start anyway
export function ButtonMenuToggleHandler({
el,
onOpen,
options,
onClose
}: {
el: HTMLElement,
onOpen?: (e: Event) => any,
options?: AttachClickOptions,
onClose?: () => void
}) {
const add = options?.listenerSetter ? options.listenerSetter.add(el) : el.addEventListener.bind(el);
add(CLICK_EVENT_NAME, (e: Event) => {
if(!el.classList.contains('btn-menu-toggle') || hasMouseMovedSinceDown(e)) return false;
cancelEvent(e);
if(el.classList.contains('menu-open')) {
contextMenuController.close();
} else {
const result = onOpen?.(e);
const open = () => {
const openedMenu = el.querySelector('.btn-menu') as HTMLDivElement;
contextMenuController.openBtnMenu(openedMenu, onClose);
};
if(result instanceof Promise) {
result.then(open);
} else {
open();
}
}
});
}
export default function ButtonMenuToggle({
buttonOptions,
listenerSetter: attachListenerSetter,
container,
direction,
buttons,
onOpenBefore,
onOpen,
onClose,
onCloseAfter
}: {
buttonOptions?: Parameters<typeof ButtonIcon>[1],
listenerSetter?: ListenerSetter,
container?: HTMLElement
direction: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right',
buttons: ButtonMenuItemOptionsVerifiable[],
onOpenBefore?: (e: Event) => any,
onOpen?: (e: Event, element: HTMLElement) => any,
onClose?: () => void,
onCloseAfter?: () => void
}) {
if(buttonOptions) {
buttonOptions.asDiv = true;
}
const button = container ?? ButtonIcon('more', buttonOptions);
button.classList.add('btn-menu-toggle');
const listenerSetter = new ListenerSetter();
let element: HTMLElement, closeTimeout: number;
ButtonMenuToggleHandler({
el: button,
onOpen: async(e) => {
await onOpenBefore?.(e);
if(closeTimeout) {
clearTimeout(closeTimeout);
closeTimeout = undefined;
return;
}
const f = (b: (typeof buttons[0])[]) => filterAsync(b, (button) => button?.verify?.() ?? true);
const filteredButtons = await f(buttons);
if(!filteredButtons.length) {
return;
}
const _element = element = await ButtonMenu({
buttons: filteredButtons,
listenerSetter
});
_element.classList.add(direction);
await onOpen?.(e, _element);
button.append(_element);
await doubleRaf();
},
options: {
listenerSetter: attachListenerSetter
},
onClose: () => {
onClose?.();
closeTimeout = window.setTimeout(() => {
onCloseAfter?.();
closeTimeout = undefined;
listenerSetter.removeAll();
buttons.forEach((button) => button.element = undefined);
element.remove();
}, 300);
}
});
return button;
}