@pmndrs/uikit
Version:
Build performant 3D user interfaces with Three.js and yoga.
108 lines (107 loc) • 4.49 kB
JavaScript
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);
}