mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-11-08 13:05:08 +09:00
abstract requestPointerLock API
within the abstraction decide whether to use requestPointerLock or setPointerCapture based on browser type.
This commit is contained in:
1 parent
370817fa85
commit
c70cf39f79
1 file changed
+89
-52
@@ -28,6 +28,14 @@ type Options = {
|
||||
}) => void;
|
||||
};
|
||||
|
||||
type State = {
|
||||
value: number;
|
||||
cursor: SVGElement | null;
|
||||
offset: number;
|
||||
velocity: number;
|
||||
direction: string;
|
||||
};
|
||||
|
||||
export const numericGestureControl = (
|
||||
targetNode: HTMLInputElement,
|
||||
{
|
||||
@@ -36,68 +44,50 @@ export const numericGestureControl = (
|
||||
onValueChange = () => null,
|
||||
}: Options
|
||||
) => {
|
||||
const { ownerDocument } = targetNode;
|
||||
const eventNames = ["pointerup", "pointerdown", "pointermove"] as const;
|
||||
const state = {
|
||||
const state: State = {
|
||||
value: initialValue,
|
||||
cursor: cursorNode.cloneNode(true) as HTMLElement,
|
||||
cursor: null,
|
||||
offset: 0,
|
||||
velocity: direction === "horizontal" ? 1 : -1,
|
||||
position: direction === "horizontal" ? "left" : "top",
|
||||
rotation: direction === "horizontal" ? "rotate(0deg)" : "rotate(90deg)",
|
||||
direction: direction === "horizontal" ? "ew-resize" : "ns-resize",
|
||||
dropShadow: `drop-shadow(${
|
||||
direction === "horizontal" ? "0 1px" : "1px 0"
|
||||
} 1.1px rgba(0,0,0,.4))`,
|
||||
direction: direction,
|
||||
};
|
||||
const handleCursor = (
|
||||
targetNode: HTMLElement | HTMLInputElement,
|
||||
shouldSetCursor: boolean
|
||||
) => {
|
||||
if (shouldSetCursor) {
|
||||
targetNode.style.setProperty(
|
||||
"cursor",
|
||||
direction === "horizontal" ? "ew-resize" : "ns-resize"
|
||||
);
|
||||
} else {
|
||||
targetNode.style.removeProperty("cursor");
|
||||
}
|
||||
};
|
||||
const handleEvent = (event: PointerEvent) => {
|
||||
const { type, pressure, offsetX, offsetY, movementY, movementX } = event;
|
||||
const { type, offsetX, offsetY, movementY, movementX } = event;
|
||||
const offset = direction === "horizontal" ? offsetX : offsetY;
|
||||
const movement = direction === "horizontal" ? movementX : -movementY;
|
||||
switch (type) {
|
||||
case "pointerup": {
|
||||
ownerDocument.exitPointerLock();
|
||||
state.offset = 0;
|
||||
handleCursor(targetNode.ownerDocument.documentElement, false);
|
||||
exitPointerLock(state, event, targetNode);
|
||||
break;
|
||||
}
|
||||
case "pointerdown": {
|
||||
const handlePointerLockEvent = () => {
|
||||
switch (ownerDocument.pointerLockElement) {
|
||||
case targetNode: {
|
||||
state.offset = offset;
|
||||
state.cursor.style.position = "absolute";
|
||||
state.cursor.style.top = `${offsetY}px`;
|
||||
state.cursor.style.left = `${offsetX}px`;
|
||||
state.cursor.style.transform = state.rotation;
|
||||
state.cursor.style.setProperty("--drop-shadow", state.dropShadow);
|
||||
ownerDocument.documentElement.append(state.cursor);
|
||||
break;
|
||||
}
|
||||
case null: {
|
||||
ownerDocument.removeEventListener(
|
||||
"pointerlockchange",
|
||||
handlePointerLockEvent
|
||||
);
|
||||
state.cursor.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
ownerDocument.addEventListener(
|
||||
"pointerlockchange",
|
||||
handlePointerLockEvent
|
||||
);
|
||||
targetNode.requestPointerLock();
|
||||
state.offset = offset;
|
||||
handleCursor(targetNode.ownerDocument.documentElement, true);
|
||||
requestPointerLock(state, event, targetNode);
|
||||
break;
|
||||
}
|
||||
case "pointermove": {
|
||||
if (pressure) {
|
||||
if (state.offset) {
|
||||
if (state.offset < 0) state.offset = globalThis.innerWidth + 1;
|
||||
else if (state.offset > globalThis.innerWidth) state.offset = 1;
|
||||
state.value += movement;
|
||||
state.offset += movement * state.velocity;
|
||||
state.cursor.style[
|
||||
state.position as "top" | "left"
|
||||
] = `${state.offset}px`;
|
||||
onValueChange({
|
||||
target: targetNode,
|
||||
value: state.value,
|
||||
@@ -108,13 +98,14 @@ export const numericGestureControl = (
|
||||
}
|
||||
}
|
||||
};
|
||||
targetNode.style.setProperty("cursor", state.direction);
|
||||
|
||||
handleCursor(targetNode, true);
|
||||
eventNames.forEach((eventName) =>
|
||||
targetNode.addEventListener(eventName, handleEvent)
|
||||
);
|
||||
return {
|
||||
disconnectedCallback: () => {
|
||||
targetNode.style.removeProperty("cursor");
|
||||
handleCursor(targetNode, false);
|
||||
eventNames.forEach((eventName) =>
|
||||
targetNode.removeEventListener(eventName, handleEvent)
|
||||
);
|
||||
@@ -122,11 +113,57 @@ export const numericGestureControl = (
|
||||
};
|
||||
};
|
||||
|
||||
const cursorNode = new Range().createContextualFragment(`
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="46" height="15" style="filter:var(--drop-shadow);">
|
||||
<g transform="translate(2 3)">
|
||||
<path d="M 15 4.5L 15 2L 11.5 5.5L 15 9L 15 6.5L 31 6.5L 31 9L 34.5 5.5L 31 2L 31 4.5Z" fill="#111" fill-rule="evenodd" stroke="#FFF" stroke-width="2"></path>
|
||||
<path d="M 15 4.5L 15 2L 11.5 5.5L 15 9L 15 6.5L 31 6.5L 31 9L 34.5 5.5L 31 2L 31 4.5Z" fill="#111" fill-rule="evenodd"></path>
|
||||
</g>
|
||||
</svg>
|
||||
`).firstElementChild as HTMLElement;
|
||||
const requestPointerLock = (
|
||||
state: State,
|
||||
event: PointerEvent,
|
||||
targetNode: HTMLElement
|
||||
) => {
|
||||
targetNode.setPointerCapture(event.pointerId);
|
||||
|
||||
if (shouldUsePointerLock()) {
|
||||
targetNode.requestPointerLock();
|
||||
const cursorNode = new Range().createContextualFragment(`
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="46" height="15" style="filter:var(--drop-shadow);">
|
||||
<g transform="translate(2 3)">
|
||||
<path d="M 15 4.5L 15 2L 11.5 5.5L 15 9L 15 6.5L 31 6.5L 31 9L 34.5 5.5L 31 2L 31 4.5Z" fill="#111" fill-rule="evenodd" stroke="#FFF" stroke-width="2"></path>
|
||||
<path d="M 15 4.5L 15 2L 11.5 5.5L 15 9L 15 6.5L 31 6.5L 31 9L 34.5 5.5L 31 2L 31 4.5Z" fill="#111" fill-rule="evenodd"></path>
|
||||
</g>
|
||||
</svg>`).firstElementChild as SVGElement;
|
||||
cursorNode.style.position = "absolute";
|
||||
cursorNode.style.left = `${event.offsetX}px`;
|
||||
cursorNode.style.top = `${event.offsetY}px`;
|
||||
cursorNode.style.transform =
|
||||
state.direction === "horizontal" ? "rotate(0deg)" : "rotate(90deg)";
|
||||
cursorNode.style.setProperty(
|
||||
"--drop-shadow",
|
||||
`drop-shadow(${
|
||||
state.direction === "horizontal" ? "0 1px" : "1px 0"
|
||||
} 1.1px rgba(0,0,0,.4))`
|
||||
);
|
||||
state.cursor = cursorNode;
|
||||
targetNode.ownerDocument.documentElement.append(state.cursor as Node);
|
||||
targetNode.onpointermove = () => {
|
||||
(state.cursor as SVGElement).style[
|
||||
state.direction === "horizontal" ? "left" : "top"
|
||||
] = `${state.offset}px`;
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const exitPointerLock = (
|
||||
state: State,
|
||||
event: PointerEvent,
|
||||
targetNode: HTMLElement
|
||||
) => {
|
||||
targetNode.releasePointerCapture(event.pointerId);
|
||||
|
||||
if (shouldUsePointerLock()) {
|
||||
(state.cursor as SVGElement).remove();
|
||||
targetNode.onpointermove = null;
|
||||
targetNode.ownerDocument.exitPointerLock();
|
||||
}
|
||||
};
|
||||
|
||||
const shouldUsePointerLock = () => {
|
||||
return "chrome" in globalThis;
|
||||
};
|
||||
Reference in new issue
Block a user