export const getOffset = (ev: MouseEvent | TouchEvent, dom = ev.target! as HTMLElement, ndx = 0) => { const event = ev instanceof TouchEvent ? ev.changedTouches[ndx] : ev const rect = dom.getBoundingClientRect(); const offsetX = event.clientX - rect.left; const offsetY = event.clientY - rect.top; return { x: offsetX, y: offsetY, }; }; export type Pos = { x: number; y: number }; type DragProps = { move?: (info: Record<'start' | 'prev' | 'end', Pos> & {ev: PointerEvent}) => void, down?: (pos: Pos, ev: PointerEvent) => void, up?: (pos: Pos, ev: PointerEvent) => void, } export const dragListener = (dom: HTMLElement, props: DragProps | DragProps['move'] = {}) => { if (typeof props === 'function') { props = { move: props } } const { move, up, down } = props const mount = document.documentElement if (!move && !up && !down) return () => {} let moveHandler: any, endHandler: any const downHandler = (ev: PointerEvent) => { const start = getOffset(ev, dom) let prev = start down && down(start, ev) // ev.preventDefault(); moveHandler = (ev: PointerEvent) => { const end = getOffset(ev, dom) move!({start, end, prev, ev}) prev = end // ev.preventDefault(); } endHandler = (ev: PointerEvent) => { up && up(getOffset(ev, dom), ev) mount.removeEventListener('pointermove', moveHandler); mount.removeEventListener('pointerup', endHandler); // ev.preventDefault(); } move && mount.addEventListener('pointermove', moveHandler, { passive: false }) mount.addEventListener('pointerup', endHandler, { passive: false }) } dom.addEventListener('pointerdown', downHandler, { passive: false }); return () => { dom.removeEventListener('pointerdown', downHandler); moveHandler && mount.removeEventListener('pointermove', moveHandler); endHandler && mount.removeEventListener('pointerup', endHandler); } } export const clickListener = (dom: HTMLDivElement, callback: (position: Pos, ev: PointerEvent) => void, button = 0) => { let downTime = 0; let move = false return dragListener(dom, { down(_, ev) { if (ev.button !== button) return; downTime = Date.now(); }, up(position, ev) { const prevMove = move move = false if (prevMove || !downTime) return; if (Date.now() - downTime <= 300) { callback(position, ev) } downTime = 0 }, }); };