tweb/src/components/buttonMenu.ts

180 lines
5.5 KiB
TypeScript
Raw Normal View History

2021-04-08 15:52:31 +02:00
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
2023-01-06 20:27:29 +01:00
import flatten from '../helpers/array/flatten';
2022-08-04 08:49:54 +02:00
import contextMenuController from '../helpers/contextMenuController';
import cancelEvent from '../helpers/dom/cancelEvent';
import {AttachClickOptions, attachClickEvent} from '../helpers/dom/clickEvent';
import findUpClassName from '../helpers/dom/findUpClassName';
2023-01-06 20:27:29 +01:00
import setInnerHTML from '../helpers/dom/setInnerHTML';
2022-08-04 08:49:54 +02:00
import ListenerSetter from '../helpers/listenerSetter';
import {FormatterArguments, i18n, LangPackKey} from '../lib/langPack';
import CheckboxField from './checkboxField';
2023-01-06 20:27:29 +01:00
import {Document} from '../layer';
import wrapPhoto from './wrappers/photo';
import textToSvgURL from '../helpers/textToSvgURL';
import customProperties from '../helpers/dom/customProperties';
import {IS_MOBILE} from '../environment/userAgent';
import ripple from './ripple';
export type ButtonMenuItemOptions = {
2022-08-04 08:49:54 +02:00
icon?: string,
2023-01-06 20:27:29 +01:00
iconDoc?: Document.document,
2022-08-04 08:49:54 +02:00
text?: LangPackKey,
2022-02-08 20:18:01 +01:00
textArgs?: FormatterArguments,
2023-01-06 20:27:29 +01:00
regularText?: Parameters<typeof setInnerHTML>[1],
onClick: (e: MouseEvent | TouchEvent) => any,
checkForClose?: () => boolean,
element?: HTMLElement,
textElement?: HTMLElement,
options?: AttachClickOptions,
checkboxField?: CheckboxField,
noCheckboxClickListener?: boolean,
2023-01-06 20:27:29 +01:00
keepOpen?: boolean,
separator?: boolean | HTMLElement,
multiline?: boolean,
loadPromise?: Promise<any>,
waitForAnimation?: boolean
/* , cancelEvent?: true */
};
2023-01-06 20:27:29 +01:00
export type ButtonMenuItemOptionsVerifiable = ButtonMenuItemOptions & {
verify?: () => boolean | Promise<boolean>
};
function ButtonMenuItem(options: ButtonMenuItemOptions) {
if(options.element) return [options.separator as HTMLElement, options.element].filter(Boolean);
2023-01-06 20:27:29 +01:00
const {icon, iconDoc, text, onClick, checkboxField, noCheckboxClickListener} = options;
const el = document.createElement('div');
2022-02-08 20:18:01 +01:00
el.className = 'btn-menu-item rp-overflow' + (icon ? ' tgico-' + icon : '');
if(IS_MOBILE) {
ripple(el);
}
2021-07-19 21:53:52 +02:00
let textElement = options.textElement;
if(!textElement) {
2022-02-08 20:18:01 +01:00
textElement = options.textElement = text ? i18n(text, options.textArgs) : document.createElement('span');
2023-01-06 20:27:29 +01:00
if(options.regularText) {
setInnerHTML(textElement, options.regularText);
}
}
if(iconDoc) {
const iconElement = document.createElement('span');
iconElement.classList.add('btn-menu-item-icon');
el.append(iconElement);
options.loadPromise = wrapPhoto({
container: iconElement,
photo: iconDoc,
boxWidth: 24,
boxHeight: 24,
withoutPreloader: true,
noFadeIn: true,
noBlur: true,
processUrl: (url) => {
return fetch(url)
.then((response) => response.text())
.then((text) => {
const color = customProperties.getProperty('primary-text-color');
const doc = new DOMParser().parseFromString(text, 'image/svg+xml');
const svg = doc.firstElementChild as HTMLElement;
svg.querySelectorAll('path').forEach((path) => {
path.setAttributeNS(null, 'fill', color);
path.style.stroke = color;
path.style.strokeWidth = '.25px';
});
return textToSvgURL(svg.outerHTML);
});
}
}).then((ret) => ret.loadPromises.thumb);
}
2022-08-04 08:49:54 +02:00
textElement.classList.add('btn-menu-item-text');
el.append(textElement);
const keepOpen = !!checkboxField || !!options.keepOpen;
2021-06-28 00:32:50 +02:00
// * cancel mobile keyboard close
onClick && attachClickEvent(el, /* CLICK_EVENT_NAME !== 'click' || keepOpen ? */ /* async */(e) => {
cancelEvent(e);
2022-06-07 14:28:50 +02:00
const menu = findUpClassName(e.target, 'btn-menu');
if(menu && !menu.classList.contains('active')) {
return;
}
2022-08-04 08:49:54 +02:00
// let closed = false;
// if(!keepOpen && !options.checkForClose) {
// closed = true;
// contextMenuController.close();
// }
// wait for closing animation
// if(options.waitForAnimation && rootScope.settings.animationsEnabled && !options.checkForClose) {
// await pause(125);
// }
onClick(e);
if(options.checkForClose?.() === false) {
return;
}
2021-06-28 00:32:50 +02:00
if(!keepOpen/* && !closed */) {
2022-08-10 21:50:25 +02:00
contextMenuController.close();
2021-06-28 00:32:50 +02:00
}
if(checkboxField && !noCheckboxClickListener/* && result !== false */) {
checkboxField.checked = checkboxField.input.type === 'radio' ? true : !checkboxField.checked;
}
}/* : onClick */, options.options);
if(checkboxField) {
el.append(checkboxField.label);
}
2023-01-06 20:27:29 +01:00
if(options.separator === true) {
options.separator = document.createElement('hr');
}
2023-01-06 20:27:29 +01:00
if(options.multiline) {
el.classList.add('is-multiline');
}
return [options.separator as HTMLElement, options.element = el].filter(Boolean);
}
export function ButtonMenuSync({listenerSetter, buttons}: {
buttons: ButtonMenuItemOptions[],
listenerSetter?: ListenerSetter
}) {
const el: HTMLElement = document.createElement('div');
el.classList.add('btn-menu');
if(listenerSetter) {
buttons.forEach((b) => {
if(b.options) {
b.options.listenerSetter = listenerSetter;
} else {
b.options = {listenerSetter};
}
});
}
const items = buttons.map(ButtonMenuItem);
2023-01-06 20:27:29 +01:00
el.append(...flatten(items));
return el;
2023-01-06 20:27:29 +01:00
}
2023-01-06 20:27:29 +01:00
export default async function ButtonMenu(options: Parameters<typeof ButtonMenuSync>[0]) {
const el = ButtonMenuSync(options);
await Promise.all(options.buttons.map(({loadPromise}) => loadPromise));
return el;
}