UNPKG

@pmndrs/uikit

Version:

Build performant 3D user interfaces with Three.js and yoga.

108 lines (107 loc) 4.49 kB
import { abortableEffect } from '../../utils.js'; import { getCharIndex } from '../layout/index.js'; const cancelSet = new Set(); function cancelBlur(event) { cancelSet.add(event); } export const canvasInputProps = { onPointerDown: (e) => { if (!(document.activeElement instanceof HTMLElement)) { return; } if (!cancelSet.has(e.nativeEvent)) { return; } cancelSet.delete(e.nativeEvent); e.preventDefault(); }, }; const segmenter = typeof Intl === 'undefined' ? undefined : new Intl.Segmenter(undefined, { granularity: 'word' }); export function setupSelectionHandlers(target, properties, text, component, textLayout, focus, abortSignal) { abortableEffect(() => { if (properties.value.disabled) { target.value = undefined; return; } let dragState; const onPointerFinish = (e) => { if (dragState == null || dragState.pointerId != e.pointerId) { return; } e.stopImmediatePropagation?.(); dragState = undefined; }; target.value = { onPointerDown: (e) => { const layout = textLayout.peek(); if (dragState != null || e.uv == null || layout == null) { return; } cancelBlur(e.nativeEvent); e.stopImmediatePropagation?.(); if ('setPointerCapture' in e.object && typeof e.object.setPointerCapture === 'function' && e.pointerId != null) { e.object.setPointerCapture(e.pointerId); } const startCharIndex = uvToCharIndex(component, e.uv, layout, 'between'); dragState = { pointerId: e.pointerId, startCharIndex, }; setTimeout(() => focus(startCharIndex, startCharIndex)); }, onDblClick: (e) => { const layout = textLayout.peek(); if (segmenter == null || e.uv == null || layout == null) { return; } e.stopImmediatePropagation?.(); if (properties.peek().type === 'password') { setTimeout(() => focus(0, text.peek().length, 'none')); return; } const charIndex = uvToCharIndex(component, e.uv, layout, 'on'); const segments = segmenter.segment(text.peek()); let segmentLengthSum = 0; for (const { segment } of segments) { const segmentLength = segment.length; if (charIndex < segmentLengthSum + segmentLength) { setTimeout(() => focus(segmentLengthSum, segmentLengthSum + segmentLength, 'none')); break; } segmentLengthSum += segmentLength; } }, onPointerUp: onPointerFinish, onPointerLeave: onPointerFinish, onPointerCancel: onPointerFinish, onPointerMove: (e) => { const layout = textLayout.peek(); if (dragState == null || dragState?.pointerId != e.pointerId || e.uv == null || layout == null) { return; } e.stopImmediatePropagation?.(); const charIndex = uvToCharIndex(component, e.uv, layout, 'between'); const start = Math.min(dragState.startCharIndex, charIndex); const end = Math.max(dragState.startCharIndex, charIndex); const direction = dragState.startCharIndex < charIndex ? 'forward' : 'backward'; setTimeout(() => focus(start, end, direction)); }, }; }, abortSignal); } function uvToCharIndex({ size: s, borderInset: b, paddingInset: p }, uv, layout, position) { const size = s.peek(); const borderInset = b.peek(); const paddingInset = p.peek(); if (size == null || borderInset == null || paddingInset == null) { return 0; } const [width, height] = size; const [bTop, , , bLeft] = borderInset; const [pTop, , , pLeft] = paddingInset; const x = uv.x * width - bLeft - pLeft; const y = (uv.y - 1) * height + bTop + pTop; return getCharIndex(layout, x, y, position); }