From c70cf39f79aaeb2886ee300f08e4c528ddd76050 Mon Sep 17 00:00:00 2001 From: Sultan <105234327+natlusyht@users.noreply.github.com> Date: Sat, 2 Jul 2022 00:23:30 +0300 Subject: [PATCH] abstract requestPointerLock API within the abstraction decide whether to use requestPointerLock or setPointerCapture based on browser type. --- .../primitives/numeric-gesture-control.ts | 141 +++++++++++------- 1 file changed, 89 insertions(+), 52 deletions(-) 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; +};