morethanwords
4 years ago
28 changed files with 740 additions and 606 deletions
@ -0,0 +1,159 @@
@@ -0,0 +1,159 @@
|
||||
import { findUpTag, whichChild } from "../lib/utils"; |
||||
|
||||
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 = (id: number) => { |
||||
if(id == prevId) return false; |
||||
|
||||
//console.log('selectTab id:', id);
|
||||
|
||||
const p = prevTabContent; |
||||
const tabContent = content.children[id] as HTMLElement; |
||||
|
||||
if(content.dataset.slider == 'none') { |
||||
if(p) { |
||||
p.classList.remove('active'); |
||||
} |
||||
|
||||
tabContent.classList.add('active'); |
||||
|
||||
prevId = id; |
||||
prevTabContent = tabContent; |
||||
|
||||
if(onTransitionEnd) onTransitionEnd(); |
||||
return; |
||||
} |
||||
|
||||
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); |
||||
} |
||||
|
||||
const tagName = 'LI';//tabs.firstElementChild.tagName;
|
||||
tabs.addEventListener('click', function(e) { |
||||
let target = e.target as HTMLElement; |
||||
|
||||
if(target.tagName != tagName) { |
||||
target = findUpTag(target, tagName); |
||||
} |
||||
|
||||
//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(tagName.toLowerCase() + '.active') as HTMLElement; |
||||
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; |
||||
} |
@ -0,0 +1,145 @@
@@ -0,0 +1,145 @@
|
||||
import { touchSupport } from "../lib/config"; |
||||
|
||||
let rippleClickID = 0; |
||||
export function ripple(elem: HTMLElement, callback: (id: number) => Promise<boolean | void> = () => Promise.resolve(), onEnd: (id: number) => void = null) { |
||||
//return;
|
||||
if(elem.querySelector('.c-ripple')) return; |
||||
elem.classList.add('rp'); |
||||
|
||||
let r = document.createElement('div'); |
||||
r.classList.add('c-ripple'); |
||||
|
||||
const isSquare = elem.classList.contains('rp-square'); |
||||
if(isSquare) { |
||||
r.classList.add('is-square'); |
||||
} |
||||
|
||||
const duration = isSquare ? 200 : 700; |
||||
|
||||
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 < duration) { |
||||
let delay = Math.max(duration - elapsedTime, duration / 2); |
||||
setTimeout(() => span.classList.add('hiding'), Math.max(delay - duration / 2, 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); |
||||
}, duration / 2); |
||||
} |
||||
|
||||
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}); |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,12 @@
@@ -0,0 +1,12 @@
|
||||
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); |
||||
} |
Loading…
Reference in new issue