diff --git a/app/shared/design-system/components/primitives/numeric-gesture-control.ts b/app/shared/design-system/components/primitives/numeric-gesture-control.ts
index 19c38312e9..b4c3f75fd6 100644
--- a/app/shared/design-system/components/primitives/numeric-gesture-control.ts
+++ b/app/shared/design-system/components/primitives/numeric-gesture-control.ts
@@ -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(`
-
-`).firstElementChild as HTMLElement;
+const requestPointerLock = (
+ state: State,
+ event: PointerEvent,
+ targetNode: HTMLElement
+) => {
+ targetNode.setPointerCapture(event.pointerId);
+
+ if (shouldUsePointerLock()) {
+ targetNode.requestPointerLock();
+ const cursorNode = new Range().createContextualFragment(`
+ `).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;
+};