import { whichChild, findUpTag, cancelEvent } from "../lib/utils"; import Config, { touchSupport } from "../lib/config"; let rippleClickID = 0; export function ripple(elem: HTMLElement, callback: (id: number) => Promise = () => Promise.resolve(), onEnd: (id: number) => void = null) { //return; if(elem.querySelector('.c-ripple')) return; let r = document.createElement('div'); r.classList.add('c-ripple'); elem.append(r); let handler: () => void; let drawRipple = (clientX: number, clientY: number) => { let startTime = Date.now(); let span = document.createElement('span'); let clickID = rippleClickID++; //console.log('ripple drawRipple'); handler = () => { let elapsedTime = Date.now() - startTime; if(elapsedTime < 700) { let delay = Math.max(700 - elapsedTime, 350); setTimeout(() => span.classList.add('hiding'), Math.max(delay - 350, 0)); setTimeout(() => { //console.log('ripple elapsedTime total pre-remove:', Date.now() - startTime); span.remove(); if(onEnd) onEnd(clickID); }, delay); } else { span.classList.add('hiding'); setTimeout(() => { //console.log('ripple elapsedTime total pre-remove:', Date.now() - startTime); span.remove(); if(onEnd) onEnd(clickID); }, 350); } handler = null; }; callback && callback(clickID); /* callback().then((bad) => { if(bad) { span.remove(); return; } */ //console.log('ripple after promise', Date.now() - startTime); //console.log('ripple tooSlow:', tooSlow); /* if(tooSlow) { span.remove(); return; } */ window.requestAnimationFrame(() => { span.classList.add('c-ripple__circle'); let rect = r.getBoundingClientRect(); let clickX = clientX - rect.left; let clickY = clientY - rect.top; let size: number, clickPos: number; if(rect.width > rect.height) { size = rect.width; clickPos = clickX; } else { size = rect.height; clickPos = clickY; } let offsetFromCenter = clickPos > (size / 2) ? size - clickPos : clickPos; size = size - offsetFromCenter; size *= 1.1; // center of circle let x = clickX - size / 2; let y = clickY - size / 2; //console.log('ripple click', offsetFromCenter, size, clickX, clickY); span.style.width = span.style.height = size + 'px'; span.style.left = x + 'px'; span.style.top = y + 'px'; r.append(span); //r.classList.add('active'); //handler(); }); //}); }; let touchStartFired = false; if(touchSupport) { let touchEnd = () => { handler && handler(); }; elem.addEventListener('touchstart', (e) => { console.log('ripple touchstart', e); if(e.touches.length > 1 || ((e.target as HTMLElement).tagName == 'BUTTON' && e.target != elem)) { return; } console.log('touchstart', e); touchStartFired = true; let {clientX, clientY} = e.touches[0]; drawRipple(clientX, clientY); window.addEventListener('touchend', touchEnd, {once: true}); window.addEventListener('touchmove', (e) => { e.cancelBubble = true; e.stopPropagation(); handler && handler(); window.removeEventListener('touchend', touchEnd); }, {once: true}); }, {passive: true}); } else { elem.addEventListener('mousedown', (e) => { if(elem.dataset.ripple == '0') { return false; } else if(touchStartFired) { touchStartFired = false; return false; } let {clientX, clientY} = e; drawRipple(clientX, clientY); window.addEventListener('mouseup', handler, {once: true}); }); } } const toastEl = document.createElement('div'); toastEl.classList.add('toast'); export function toast(html: string) { toastEl.innerHTML = html; document.body.append(toastEl); if(toastEl.dataset.timeout) clearTimeout(+toastEl.dataset.timeout); toastEl.dataset.timeout = '' + setTimeout(() => { toastEl.remove(); delete toastEl.dataset.timeout; }, 3000); } let loadedURLs: {[url: string]: boolean} = {}; let set = (elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLSourceElement, url: string) => { if(elem instanceof HTMLImageElement || elem instanceof HTMLSourceElement) elem.src = url; else if(elem instanceof SVGImageElement) elem.setAttributeNS(null, 'href', url); else elem.style.backgroundImage = 'url(' + url + ')'; }; export function renderImageFromUrl(elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLSourceElement, url: string): Promise { if(loadedURLs[url]) { set(elem, url); return Promise.resolve(true); } if(elem instanceof HTMLSourceElement) { elem.src = url; return Promise.resolve(false); } else { return new Promise((resolve, reject) => { let loader = new Image(); loader.src = url; //let perf = performance.now(); loader.addEventListener('load', () => { set(elem, url); loadedURLs[url] = true; //console.log('onload:', url, performance.now() - perf); resolve(false); }); loader.addEventListener('error', reject); }); } } export function putPreloader(elem: Element, returnDiv = false) { const html = ` `; if(returnDiv) { let div = document.createElement('div'); div.classList.add('preloader'); div.innerHTML = html; if(elem) { elem.appendChild(div); } return div; } elem.innerHTML += html; } function slideNavigation(tabContent: HTMLElement, prevTabContent: HTMLElement, toRight: boolean) { /* if(toRight) { //prevTabContent.style.filter = `brightness(80%)`; prevTabContent.style.transform = `translateX(-25%)`; tabContent.style.transform = `translateX(20%)`; } else { //tabContent.style.filter = `brightness(80%)`; tabContent.style.transform = `translateX(-25%)`; prevTabContent.style.transform = `translateX(20%)`; } */ if(toRight) { prevTabContent.style.filter = `brightness(80%)`; prevTabContent.style.transform = `translateX(-25%)`; tabContent.style.transform = `translateX(100%)`; } else { tabContent.style.filter = `brightness(80%)`; tabContent.style.transform = `translateX(-25%)`; prevTabContent.style.transform = `translateX(100%)`; } tabContent.classList.add('active'); void tabContent.offsetWidth; // reflow tabContent.style.transform = ''; tabContent.style.filter = ''; } function slideTabs(tabContent: HTMLElement, prevTabContent: HTMLElement, toRight: boolean) { if(toRight) { tabContent.style.transform = `translateX(100%)`; prevTabContent.style.transform = `translateX(-100%)`; } else { tabContent.style.transform = `translateX(-100%)`; prevTabContent.style.transform = `translateX(100%)`; } tabContent.classList.add('active'); void tabContent.offsetWidth; // reflow tabContent.style.transform = ''; } export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick?: (id: number, tabContent: HTMLDivElement) => void, onTransitionEnd?: () => void, transitionTime = 300) { const hideTimeouts: {[id: number]: number} = {}; let prevTabContent: HTMLElement = null; let prevId = -1; const selectTab = async(id: number) => { if(id == prevId) return false; //console.log('selectTab id:', id); const p = prevTabContent; const tabContent = content.children[id] as HTMLElement; const toRight = prevId < id; if(prevId != -1) { if(tabs || content.dataset.slider == 'tabs') { slideTabs(tabContent, prevTabContent, toRight); } else { slideNavigation(tabContent, prevTabContent, toRight); } } else { tabContent.classList.add('active'); } const _prevId = prevId; if(hideTimeouts.hasOwnProperty(id)) clearTimeout(hideTimeouts[id]); if(p/* && false */) { hideTimeouts[_prevId] = setTimeout(() => { p.style.transform = ''; p.style.filter = ''; p.classList.remove('active'); delete hideTimeouts[_prevId]; if(onTransitionEnd) onTransitionEnd(); }, /* 420 */transitionTime); } prevId = id; prevTabContent = tabContent; }; if(tabs) { let activeStripe: HTMLSpanElement; if(!tabs.classList.contains('no-stripe')) { activeStripe = document.createElement('span'); activeStripe.classList.add('menu-horizontal__stripe'); tabs.append(activeStripe); } tabs.addEventListener('click', function(e) { let target = e.target as HTMLLIElement; if(target.tagName != 'LI') { target = findUpTag(target, 'LI'); } //console.log('tabs click:', target); if(!target) return false; let id: number; if(target.dataset.tab) { id = +target.dataset.tab; if(id == -1) { return false; } } else { id = whichChild(target); } const tabContent = content.children[id] as HTMLDivElement; if(onClick) onClick(id, tabContent); if(target.classList.contains('active') || id == prevId) { return false; } const prev = tabs.querySelector('li.active') as HTMLLIElement; prev && prev.classList.remove('active'); if(activeStripe) { const tabsRect = tabs.getBoundingClientRect(); const targetRect = target.getBoundingClientRect(); const width = 50; activeStripe.style.cssText = `width: ${width}px; transform: translateX(${targetRect.left - tabsRect.left + ((targetRect.width - width) / 2)}px);`; /* const textRect = target.firstElementChild.getBoundingClientRect(); activeStripe.style.cssText = `width: ${textRect.width + (2 * 2)}px; transform: translateX(${textRect.left - tabsRect.left}px);`; */ //activeStripe.style.transform = `scaleX(${textRect.width}) translateX(${(textRect.left - tabsRect.left) / textRect.width + 0.5}px)`; //console.log('tabs click:', tabsRect, textRect); } target.classList.add('active'); selectTab(id); }); } return selectTab; } export function formatPhoneNumber(str: string) { str = str.replace(/\D/g, ''); let phoneCode = str.slice(0, 6); ////console.log('str', str, phoneCode); let sortedCountries = Config.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); }); let pattern = country ? country.pattern || country.phoneCode : ''; if(country) { 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); } */ } return {formatted: str, country}; } export function parseMenuButtonsTo(to: {[name: string]: HTMLElement}, elements: HTMLCollection | NodeListOf) { 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); }; let onClick = (/* e: MouseEvent | TouchEvent */) => { //e.preventDefault(); closeBtnMenu(); }; let closeBtnMenu = () => { if(openedMenu) { openedMenu.classList.remove('active'); openedMenu.parentElement.classList.remove('menu-open'); openedMenu = null; } if(openedMenuOnClose) { openedMenuOnClose(); openedMenuOnClose = null; } //document.body.classList.remove('disable-hover'); window.removeEventListener('mousemove', onMouseMove); window.removeEventListener('touchmove', onClick); window.removeEventListener('click', onClick); window.removeEventListener('contextmenu', onClick); }; let openedMenu: HTMLDivElement = null, openedMenuOnClose: () => void = null; export function openBtnMenu(menuElement: HTMLDivElement, onClose?: () => void) { closeBtnMenu(); openedMenu = menuElement; openedMenu.classList.add('active'); openedMenu.parentElement.classList.add('menu-open'); //document.body.classList.add('disable-hover'); openedMenuOnClose = onClose; window.addEventListener('mousemove', onMouseMove); window.addEventListener('touchmove', onClick, {once: true}); window.addEventListener('click', onClick, {once: true}); window.addEventListener('contextmenu', onClick, {once: true}); } export function positionMenu(e: MouseEvent, elem: HTMLElement, side?: 'left' | 'right') { let {clientX, clientY} = e; let {scrollWidth, scrollHeight} = elem; let {innerWidth, innerHeight} = window; if(side === undefined) { if((clientX + scrollWidth) > innerWidth) { if((clientX - scrollWidth) < 0) { elem.style.left = (innerWidth - scrollWidth) + 'px'; } else { side = 'right'; } } } if(!side) { side = 'left'; } elem.classList.remove('bottom-left', 'bottom-right'); if(side !== undefined) { elem.style.left = (side == 'right' ? clientX - scrollWidth : clientX) + 'px'; elem.classList.add(side == 'left' ? 'bottom-right' : 'bottom-left'); } if((clientY + scrollHeight) > innerHeight) { elem.style.top = (innerHeight - scrollHeight) + 'px'; } else { elem.style.top = clientY + 'px'; } }