2021-08-26 02:54:42 +03:00

396 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import { MOUNT_CLASS_TO } from "../config/debug";
import Countries, { Country, PhoneCodesMain } from "../countries";
import { cancelEvent } from "../helpers/dom/cancelEvent";
import { CLICK_EVENT_NAME } from "../helpers/dom/clickEvent";
import ListenerSetter from "../helpers/listenerSetter";
import mediaSizes from "../helpers/mediaSizes";
import { isTouchSupported } from "../helpers/touchSupport";
import { isApple, isMobileSafari } from "../helpers/userAgent";
import rootScope from "../lib/rootScope";
import appNavigationController from "./appNavigationController";
export function putPreloader(elem: Element, returnDiv = false): HTMLElement {
const html = `
<svg xmlns="http://www.w3.org/2000/svg" class="preloader-circular" viewBox="25 25 50 50">
<circle class="preloader-path" cx="50" cy="50" r="20" fill="none" stroke-miterlimit="10"/>
</svg>`;
if(returnDiv) {
const div = document.createElement('div');
div.classList.add('preloader');
div.innerHTML = html;
if(elem) {
elem.appendChild(div);
}
return div;
}
elem.insertAdjacentHTML('beforeend', html);
return elem.lastElementChild as HTMLElement;
}
MOUNT_CLASS_TO.putPreloader = putPreloader;
export function setButtonLoader(elem: HTMLButtonElement, icon = 'check') {
elem.classList.remove('tgico-' + icon);
elem.disabled = true;
putPreloader(elem);
return () => {
elem.innerHTML = '';
elem.classList.add('tgico-' + icon);
elem.removeAttribute('disabled');
};
}
let sortedCountries: Country[];
export function formatPhoneNumber(originalStr: string) {
let str = originalStr.replace(/\D/g, '');
let phoneCode = str.slice(0, 6);
////console.log('str', str, phoneCode);
if(!sortedCountries) {
sortedCountries = Countries.slice().sort((a, b) => b.phoneCode.length - a.phoneCode.length);
}
let country = sortedCountries.find((c) => {
return c.phoneCode.split(' and ').find((c) => phoneCode.indexOf(c.replace(/\D/g, '')) === 0);
});
if(!country) return {formatted: str, country, leftPattern: ''};
country = PhoneCodesMain[country.phoneCode] || country;
let pattern = country.pattern || country.phoneCode;
pattern.split('').forEach((symbol, idx) => {
if(symbol === ' ' && str[idx] !== ' ' && str.length > idx) {
str = str.slice(0, idx) + ' ' + str.slice(idx);
}
});
/* if(country.pattern) {
str = str.slice(0, country.pattern.length);
} */
let leftPattern = pattern && pattern.length > str.length ? pattern.slice(str.length) : '';
if(leftPattern) {
/* const length = str.length;
leftPattern = leftPattern.split('').map((_, idx) => (length + idx).toString().slice(-1)).join(''); */
leftPattern = leftPattern.replace(/X/g, '');
// leftPattern = leftPattern.replace(/X/g, '0');
}
return {formatted: str, country, leftPattern};
}
/* export function parseMenuButtonsTo(to: {[name: string]: HTMLElement}, elements: HTMLCollection | NodeListOf<HTMLElement>) {
Array.from(elements).forEach(el => {
const match = el.className.match(/(?:^|\s)menu-(.+?)(?:$|\s)/);
if(!match) return;
to[match[1]] = el as HTMLElement;
});
} */
let onMouseMove = (e: MouseEvent) => {
let rect = openedMenu.getBoundingClientRect();
let {clientX, clientY} = e;
let diffX = clientX >= rect.right ? clientX - rect.right : rect.left - clientX;
let diffY = clientY >= rect.bottom ? clientY - rect.bottom : rect.top - clientY;
if(diffX >= 100 || diffY >= 100) {
closeBtnMenu();
//openedMenu.parentElement.click();
}
//console.log('mousemove', diffX, diffY);
};
const onClick = (e: MouseEvent | TouchEvent) => {
//cancelEvent(e);
closeBtnMenu();
};
// ! no need in this due to the same handler in appNavigationController
/* const onKeyDown = (e: KeyboardEvent) => {
if(e.key === 'Escape') {
closeBtnMenu();
cancelEvent(e);
}
}; */
export const closeBtnMenu = () => {
if(openedMenu) {
openedMenu.classList.remove('active');
openedMenu.parentElement.classList.remove('menu-open');
//openedMenu.previousElementSibling.remove(); // remove overlay
if(menuOverlay) menuOverlay.remove();
openedMenu = null;
rootScope.dispatchEvent('context_menu_toggle', false);
}
if(openedMenuOnClose) {
openedMenuOnClose();
openedMenuOnClose = null;
}
if(!isTouchSupported) {
window.removeEventListener('mousemove', onMouseMove);
//window.removeEventListener('keydown', onKeyDown, {capture: true});
window.removeEventListener('contextmenu', onClick);
}
document.removeEventListener(CLICK_EVENT_NAME, onClick);
if(!isMobileSafari) {
appNavigationController.removeByType('menu');
}
};
window.addEventListener('resize', () => {
if(openedMenu) {
closeBtnMenu();
}
/* if(openedMenu && (openedMenu.style.top || openedMenu.style.left)) {
const rect = openedMenu.getBoundingClientRect();
const {innerWidth, innerHeight} = window;
console.log(innerWidth, innerHeight, rect);
} */
});
let openedMenu: HTMLElement = null, openedMenuOnClose: () => void = null, menuOverlay: HTMLElement = null;
export function openBtnMenu(menuElement: HTMLElement, onClose?: () => void) {
closeBtnMenu();
if(!isMobileSafari) {
appNavigationController.pushItem({
type: 'menu',
onPop: (canAnimate) => {
closeBtnMenu();
}
});
}
openedMenu = menuElement;
openedMenu.classList.add('active');
openedMenu.parentElement.classList.add('menu-open');
if(!menuOverlay) {
menuOverlay = document.createElement('div');
menuOverlay.classList.add('btn-menu-overlay');
// ! because this event must be canceled, and can't cancel on menu click (below)
menuOverlay.addEventListener(CLICK_EVENT_NAME, (e) => {
cancelEvent(e);
onClick(e);
});
}
openedMenu.parentElement.insertBefore(menuOverlay, openedMenu);
//document.body.classList.add('disable-hover');
openedMenuOnClose = onClose;
if(!isTouchSupported) {
window.addEventListener('mousemove', onMouseMove);
//window.addEventListener('keydown', onKeyDown, {capture: true});
window.addEventListener('contextmenu', onClick, {once: true});
}
/* // ! because this event must be canceled, and can't cancel on menu click (below)
overlay.addEventListener(CLICK_EVENT_NAME, (e) => {
cancelEvent(e);
onClick(e);
}); */
// ! safari iOS doesn't handle window click event on overlay, idk why
document.addEventListener(CLICK_EVENT_NAME, onClick);
rootScope.dispatchEvent('context_menu_toggle', true);
}
const PADDING_TOP = 8;
const PADDING_LEFT = 8;
export function positionMenu({pageX, pageY}: MouseEvent | Touch, elem: HTMLElement, side?: 'left' | 'right' | 'center') {
//let {clientX, clientY} = e;
// * side mean the OPEN side
let {scrollWidth: menuWidth, scrollHeight: menuHeight} = elem;
//let {innerWidth: windowWidth, innerHeight: windowHeight} = window;
const rect = document.body.getBoundingClientRect();
const windowWidth = rect.width;
const windowHeight = rect.height;
side = mediaSizes.isMobile ? 'right' : 'left';
let verticalSide: 'top' /* | 'bottom' */ | 'center' = 'top';
const getSides = () => {
return {
x: {
left: pageX,
right: pageX - menuWidth
},
intermediateX: side === 'right' ? PADDING_LEFT : windowWidth - menuWidth - PADDING_LEFT,
//intermediateX: clientX < windowWidth / 2 ? PADDING_LEFT : windowWidth - menuWidth - PADDING_LEFT,
y: {
top: pageY,
bottom: pageY - menuHeight
},
//intermediateY: verticalSide === 'top' ? PADDING_TOP : windowHeight - menuHeight - PADDING_TOP,
intermediateY: pageY < windowHeight / 2 ? PADDING_TOP : windowHeight - menuHeight - PADDING_TOP,
};
};
const sides = getSides();
const possibleSides = {
x: {
left: sides.x.left + menuWidth + PADDING_LEFT <= windowWidth,
right: sides.x.right >= PADDING_LEFT
},
y: {
top: sides.y.top + menuHeight + PADDING_TOP <= windowHeight,
bottom: sides.y.bottom - PADDING_TOP >= PADDING_TOP
}
};
/* if(side === undefined) {
if((clientX + menuWidth + PADDING_LEFT) > windowWidth) {
side = 'right';
}
} */
{
/* const x = sides.x;
const s = Object.keys(x) as (keyof typeof possibleSides.x)[];
if(side) {
s.findAndSplice(s => s === side);
s.unshift(side);
}
const possibleSide = s.find(s => possibleSides.x[s]); */
let left: number;
/* if(possibleSide) {
left = x[possibleSide];
side = possibleSide;
} else {
left = sides.intermediateX;
side = undefined;
} */
left = possibleSides.x[side] ? sides.x[side] : (side = 'center', sides.intermediateX);
elem.style.left = left + 'px';
}
/* if((clientY + menuHeight + PADDING_TOP) > windowHeight) {
elem.style.top = clamp(clientY - menuHeight, PADDING_TOP, windowHeight - menuHeight - PADDING_TOP) + 'px';
// elem.style.top = (innerHeight - scrollHeight - PADDING_TOP) + 'px';
verticalSide = 'bottom';
} else {
elem.style.top = Math.max(PADDING_TOP, clientY) + 'px';
verticalSide = 'top';
} */
{
let top: number;
top = possibleSides.y[verticalSide] ? sides.y[verticalSide] : (verticalSide = 'center', sides.intermediateY);
elem.style.top = top + 'px';
}
elem.className = elem.className.replace(/(top|center|bottom)-(left|center|right)/g, '');
elem.classList.add(
//(verticalSide === 'center' ? verticalSide : (verticalSide === 'bottom' ? 'top' : 'bottom')) +
(verticalSide === 'center' ? verticalSide : 'bottom') +
'-' +
(side === 'center' ? side : (side === 'left' ? 'right' : 'left')));
}
let _cancelContextMenuOpening = false, _cancelContextMenuOpeningTimeout = 0;
export function cancelContextMenuOpening() {
if(_cancelContextMenuOpeningTimeout) {
clearTimeout(_cancelContextMenuOpeningTimeout);
}
_cancelContextMenuOpeningTimeout = window.setTimeout(() => {
_cancelContextMenuOpeningTimeout = 0;
_cancelContextMenuOpening = false;
}, .4e3);
_cancelContextMenuOpening = true;
}
export function attachContextMenuListener(element: HTMLElement, callback: (e: Touch | MouseEvent) => void, listenerSetter?: ListenerSetter) {
const add = listenerSetter ? listenerSetter.add(element) : element.addEventListener.bind(element);
const remove = listenerSetter ? listenerSetter.removeManual.bind(listenerSetter, element) : element.removeEventListener.bind(element);
if(isApple && isTouchSupported) {
let timeout: number;
const options: EventListenerOptions = {capture: true};
const onCancel = () => {
clearTimeout(timeout);
// @ts-ignore
remove('touchmove', onCancel, options);
// @ts-ignore
remove('touchend', onCancel, options);
// @ts-ignore
remove('touchcancel', onCancel, options);
};
add('touchstart', (e: TouchEvent) => {
if(e.touches.length > 1) {
onCancel();
return;
}
add('touchmove', onCancel, options);
add('touchend', onCancel, options);
add('touchcancel', onCancel, options);
timeout = window.setTimeout(() => {
if(_cancelContextMenuOpening) {
onCancel();
return;
}
callback(e.touches[0]);
onCancel();
if(openedMenu) {
element.addEventListener('touchend', cancelEvent, {once: true}); // * fix instant closing
}
}, .4e3);
});
/* if(!isSafari) {
add('contextmenu', (e: any) => {
cancelEvent(e);
}, {passive: false, capture: true});
} */
} else {
add('contextmenu', isTouchSupported ? (e: any) => {
callback(e);
if(openedMenu) {
element.addEventListener('touchend', cancelEvent, {once: true}); // * fix instant closing
}
} : callback);
}
};