UNPKG

@selenite/commons

Version:

This typescript package provides a set of frequently used utilities, types and svelte actions for building projects with Typescript and Svelte.

153 lines (152 loc) 4.42 kB
import { distance, posFromClient, Rect, stopPropagation } from '../utils'; /** * Action to enable box selection on an element. * @param node Element to attach the action to * @param params Parameters for the action * @returns Svelte action */ export const boxSelection = (node, params = {}) => { function pOver() { node.style.cursor = 'crosshair'; } function pLeave() { node.style.cursor = ''; } function pMove(e) { if (!box) return; stopPropagation(e); setBoxPos(startPos, posFromClient(e)); } let box; let startPos; function pDown(e) { if (e.button !== 0) return; document.body.style.userSelect = 'none'; if (box) { destroyBox(); document.removeEventListener('pointermove', pMove); return; } createBox(posFromClient(e)); document.addEventListener('pointermove', pMove, { passive: true }); document.addEventListener('pointerup', pUp, { once: true, capture: true }); } function pUp(e) { if (startPos) { const pos = posFromClient(e); if (distance(startPos, pos) < 12) { destroyBox(); return; } } document.body.style.userSelect = ''; if (!box) return; const selected = []; const selectionRect = box.getBoundingClientRect(); for (const n of (params.holder ?? node).children) { const r = n.getBoundingClientRect(); const intersection = Rect.intersection(selectionRect, r); const a = Rect.area(r); const ai = Rect.area(intersection); const k = ai / a; if (k > (params.threshold ?? 0.9)) { selected.push(n); } } console.debug('Box selection', selected); params.onselection?.(selected); destroyBox(); } function setBoxPos(a, b) { if (!box) return; let x, y, w, h; if (a.x < b.x) { x = a.x; w = b.x - a.x; } else { x = b.x; w = a.x - b.x; } if (a.y < b.y) { y = a.y; h = b.y - a.y; } else { y = b.y; h = a.y - b.y; } const rect = node.getBoundingClientRect(); if (x < rect.left) { w -= rect.left - x; x = rect.left; } else if (x + w > rect.right) { w = rect.right - x; } if (y < rect.top) { h -= rect.top - y; y = rect.top; } else if (y + h > rect.bottom) { h = rect.bottom - y; } box.style.left = x + 'px'; box.style.top = y + 'px'; box.style.width = w + 'px'; box.style.height = h + 'px'; } function createBox(pos) { if (box) return; box = document.createElement('div'); box.style.position = 'absolute'; box.classList.add('border', 'border-accent-content/50', 'bg-accent/25'); box.style.pointerEvents = 'none'; document.body.appendChild(box); setBoxPos(pos, pos); startPos = pos; } function destroyBox() { if (box) { document.body.removeChild(box); box = undefined; } } function setup() { node.style.cursor = 'crosshair'; node.addEventListener('pointerover', pOver); node.addEventListener('pointerleave', pLeave); node.addEventListener('pointerdown', pDown, { capture: true }); } function destroy() { node.style.cursor = ''; node.removeEventListener('pointerover', pOver); node.removeEventListener('pointerleave', pLeave); node.removeEventListener('pointerdown', pDown, { capture: true }); node.removeEventListener('pointermove', pMove); if (box) { document.body.removeChild(box); box = undefined; } } if (params.enabled ?? true) { setup(); } return { update(newParams) { params = newParams ?? {}; if (params.enabled ?? true) { setup(); } else { destroy(); } }, destroy }; };