@pmndrs/klipp 0.0.0-stage → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +72 -2
- package/dist/_virtual/_rolldown/runtime.js +13 -0
- package/dist/core/CameraState.d.ts +32 -0
- package/dist/core/CameraState.js +97 -0
- package/dist/core/EventDispatcher.d.ts +18 -0
- package/dist/core/EventDispatcher.js +28 -0
- package/dist/core/Klipp.d.ts +109 -0
- package/dist/core/Klipp.js +253 -0
- package/dist/core/TargetExtent.d.ts +11 -0
- package/dist/core/TargetExtent.js +54 -0
- package/dist/core/TargetPose.d.ts +13 -0
- package/dist/core/TargetPose.js +21 -0
- package/dist/core/VirtualCamera.d.ts +102 -0
- package/dist/core/VirtualCamera.js +290 -0
- package/dist/core/aim/HardLookAtAim.d.ts +10 -0
- package/dist/core/aim/HardLookAtAim.js +19 -0
- package/dist/core/aim/PanTiltAim.d.ts +26 -0
- package/dist/core/aim/PanTiltAim.js +38 -0
- package/dist/core/aim/RotateWithFollowTargetAim.d.ts +19 -0
- package/dist/core/aim/RotateWithFollowTargetAim.js +23 -0
- package/dist/core/aim/RotationComposerAim.d.ts +27 -0
- package/dist/core/aim/RotationComposerAim.js +27 -0
- package/dist/core/aim/hardLookAt.d.ts +4 -0
- package/dist/core/aim/hardLookAt.js +13 -0
- package/dist/core/aim/index.d.ts +11 -0
- package/dist/core/aim/lookRotation.d.ts +6 -0
- package/dist/core/aim/lookRotation.js +46 -0
- package/dist/core/aim/panTilt.d.ts +16 -0
- package/dist/core/aim/panTilt.js +121 -0
- package/dist/core/aim/rotateWithFollowTarget.d.ts +20 -0
- package/dist/core/aim/rotateWithFollowTarget.js +35 -0
- package/dist/core/aim/rotationComposer.d.ts +58 -0
- package/dist/core/aim/rotationComposer.js +284 -0
- package/dist/core/blend/BlendCurves.d.ts +19 -0
- package/dist/core/blend/BlendCurves.js +22 -0
- package/dist/core/blend/BlendDefinition.d.ts +21 -0
- package/dist/core/blend/BlendDefinition.js +9 -0
- package/dist/core/blend/BlendDriver.d.ts +23 -0
- package/dist/core/blend/BlendDriver.js +42 -0
- package/dist/core/blend/BlendHints.d.ts +9 -0
- package/dist/core/blend/BlendHints.js +13 -0
- package/dist/core/blend/blend.d.ts +41 -0
- package/dist/core/blend/blend.js +110 -0
- package/dist/core/blend/index.d.ts +6 -0
- package/dist/core/blend/lerpCameraState.d.ts +4 -0
- package/dist/core/blend/lerpCameraState.js +143 -0
- package/dist/core/body/BindingModes.d.ts +9 -0
- package/dist/core/body/BindingModes.js +11 -0
- package/dist/core/body/FollowBody.d.ts +24 -0
- package/dist/core/body/FollowBody.js +26 -0
- package/dist/core/body/HardLockToTargetBody.d.ts +17 -0
- package/dist/core/body/HardLockToTargetBody.js +20 -0
- package/dist/core/body/OrbitFollowBody.d.ts +47 -0
- package/dist/core/body/OrbitFollowBody.js +48 -0
- package/dist/core/body/PositionComposerBody.d.ts +28 -0
- package/dist/core/body/PositionComposerBody.js +27 -0
- package/dist/core/body/follow.d.ts +33 -0
- package/dist/core/body/follow.js +83 -0
- package/dist/core/body/hardLockToTarget.d.ts +17 -0
- package/dist/core/body/hardLockToTarget.js +26 -0
- package/dist/core/body/index.d.ts +17 -0
- package/dist/core/body/orbitFollow.d.ts +76 -0
- package/dist/core/body/orbitFollow.js +336 -0
- package/dist/core/body/positionComposer.d.ts +56 -0
- package/dist/core/body/positionComposer.js +207 -0
- package/dist/core/body/safeFromToRotation.d.ts +3 -0
- package/dist/core/body/safeFromToRotation.js +39 -0
- package/dist/core/body/threeRing.d.ts +28 -0
- package/dist/core/body/threeRing.js +100 -0
- package/dist/core/body/tracker.d.ts +42 -0
- package/dist/core/body/tracker.js +193 -0
- package/dist/core/damping/Damper.d.ts +11 -0
- package/dist/core/damping/Damper.js +22 -0
- package/dist/core/damping/damping.d.ts +30 -0
- package/dist/core/damping/damping.js +119 -0
- package/dist/core/damping/index.d.ts +5 -0
- package/dist/core/damping/predictor.d.ts +16 -0
- package/dist/core/damping/predictor.js +56 -0
- package/dist/core/debug/debugZones.d.ts +16 -0
- package/dist/core/debug/debugZones.js +38 -0
- package/dist/core/debug/index.d.ts +2 -0
- package/dist/core/extension/GroupFramingExtension.d.ts +26 -0
- package/dist/core/extension/GroupFramingExtension.js +24 -0
- package/dist/core/extension/LensExtension.d.ts +20 -0
- package/dist/core/extension/LensExtension.js +12 -0
- package/dist/core/extension/groupFraming.d.ts +62 -0
- package/dist/core/extension/groupFraming.js +271 -0
- package/dist/core/extension/index.d.ts +6 -0
- package/dist/core/extension/lens.d.ts +35 -0
- package/dist/core/extension/lens.js +57 -0
- package/dist/core/groups/ClearShot.d.ts +32 -0
- package/dist/core/groups/MixingCamera.d.ts +20 -0
- package/dist/core/groups/Sequencer.d.ts +24 -0
- package/dist/core/groups/StateDrivenCamera.d.ts +26 -0
- package/dist/core/groups/clearShotState.d.ts +32 -0
- package/dist/core/groups/index.d.ts +11 -0
- package/dist/core/groups/mixCameraStates.d.ts +13 -0
- package/dist/core/groups/sequencerState.d.ts +26 -0
- package/dist/core/groups/stateDrivenState.d.ts +28 -0
- package/dist/core/impulse/ImpulseField.d.ts +15 -0
- package/dist/core/impulse/ImpulseField.js +22 -0
- package/dist/core/impulse/ImpulseListenerNoise.d.ts +19 -0
- package/dist/core/impulse/ImpulseListenerNoise.js +29 -0
- package/dist/core/impulse/impulseListener.d.ts +13 -0
- package/dist/core/impulse/impulseListener.js +14 -0
- package/dist/core/impulse/impulses.d.ts +54 -0
- package/dist/core/impulse/impulses.js +84 -0
- package/dist/core/impulse/index.d.ts +6 -0
- package/dist/core/index.d.ts +20 -0
- package/dist/core/index.js +52 -0
- package/dist/core/input/InputAxis.d.ts +29 -0
- package/dist/core/input/InputAxis.js +28 -0
- package/dist/core/input/InputAxisOwner.d.ts +6 -0
- package/dist/core/input/InputAxisOwner.js +4 -0
- package/dist/core/input/InputController.d.ts +15 -0
- package/dist/core/input/InputController.js +24 -0
- package/dist/core/input/axis.d.ts +52 -0
- package/dist/core/input/axis.js +89 -0
- package/dist/core/input/consumedInput.d.ts +37 -0
- package/dist/core/input/consumedInput.js +34 -0
- package/dist/core/input/index.d.ts +9 -0
- package/dist/core/input/inputMapping.d.ts +46 -0
- package/dist/core/input/inputMapping.js +55 -0
- package/dist/core/input/shortestWrappedDelta.d.ts +2 -0
- package/dist/core/input/shortestWrappedDelta.js +11 -0
- package/dist/core/internal.d.ts +16 -0
- package/dist/core/internal.js +19 -0
- package/dist/core/klippState.d.ts +78 -0
- package/dist/core/klippState.js +124 -0
- package/dist/core/noise/BasicMultiChannelPerlinNoise.d.ts +21 -0
- package/dist/core/noise/BasicMultiChannelPerlinNoise.js +13 -0
- package/dist/core/noise/index.d.ts +3 -0
- package/dist/core/noise/perlinNoise.d.ts +36 -0
- package/dist/core/noise/perlinNoise.js +110 -0
- package/dist/core/params.d.ts +2 -0
- package/dist/core/params.js +12 -0
- package/dist/dom/DebugOverlay.d.ts +11 -0
- package/dist/dom/DebugOverlay.js +96 -0
- package/dist/dom/InputControllerDom.d.ts +15 -0
- package/dist/dom/InputControllerDom.js +31 -0
- package/dist/dom/InputSystem.d.ts +40 -0
- package/dist/dom/InputSystem.js +113 -0
- package/dist/dom/index.d.ts +3 -0
- package/dist/dom/index.js +5 -0
- package/dist/input-sources/buttonInput.d.ts +15 -0
- package/dist/input-sources/buttonInput.js +23 -0
- package/dist/input-sources/isInsideInteractiveArea.d.ts +9 -0
- package/dist/input-sources/isInsideInteractiveArea.js +11 -0
- package/dist/input-sources/mouse.d.ts +47 -0
- package/dist/input-sources/mouse.js +171 -0
- package/dist/input-sources/safariGesture.d.ts +32 -0
- package/dist/input-sources/safariGesture.js +107 -0
- package/dist/input-sources/suppressNativeGestures.d.ts +5 -0
- package/dist/input-sources/suppressNativeGestures.js +32 -0
- package/dist/input-sources/touch.d.ts +47 -0
- package/dist/input-sources/touch.js +173 -0
- package/dist/input-sources/wheel.d.ts +28 -0
- package/dist/input-sources/wheel.js +44 -0
- package/dist/react/CameraFrustumHelper.d.ts +13 -0
- package/dist/react/CameraFrustumHelper.js +32 -0
- package/dist/react/DebugZoneOverlay.d.ts +7 -0
- package/dist/react/DebugZoneOverlay.js +23 -0
- package/dist/react/Klipp.d.ts +20 -0
- package/dist/react/Klipp.js +47 -0
- package/dist/react/KlippContext.d.ts +5 -0
- package/dist/react/KlippContext.js +11 -0
- package/dist/react/VirtualCamera.d.ts +28 -0
- package/dist/react/VirtualCamera.js +81 -0
- package/dist/react/VirtualCameraContext.d.ts +11 -0
- package/dist/react/VirtualCameraContext.js +21 -0
- package/dist/react/aim/Aim.d.ts +11 -0
- package/dist/react/aim/Aim.js +14 -0
- package/dist/react/aim/HardLookAt.d.ts +10 -0
- package/dist/react/aim/HardLookAt.js +15 -0
- package/dist/react/aim/PanTilt.d.ts +31 -0
- package/dist/react/aim/PanTilt.js +38 -0
- package/dist/react/aim/RotateWithFollowTarget.d.ts +10 -0
- package/dist/react/aim/RotateWithFollowTarget.js +18 -0
- package/dist/react/aim/RotationComposer.d.ts +12 -0
- package/dist/react/aim/RotationComposer.js +34 -0
- package/dist/react/body/Body.d.ts +11 -0
- package/dist/react/body/Body.js +14 -0
- package/dist/react/body/CameraControls.d.ts +50 -0
- package/dist/react/body/CameraControls.js +106 -0
- package/dist/react/body/Follow.d.ts +10 -0
- package/dist/react/body/Follow.js +20 -0
- package/dist/react/body/HardLockToTarget.d.ts +10 -0
- package/dist/react/body/HardLockToTarget.js +18 -0
- package/dist/react/body/OrbitFollow.d.ts +25 -0
- package/dist/react/body/OrbitFollow.js +67 -0
- package/dist/react/body/PositionComposer.d.ts +12 -0
- package/dist/react/body/PositionComposer.js +32 -0
- package/dist/react/body/camera-controls.d.ts +2 -0
- package/dist/react/body/camera-controls.js +3 -0
- package/dist/react/extension/Extension.d.ts +7 -0
- package/dist/react/extension/Extension.js +10 -0
- package/dist/react/extension/GroupFraming.d.ts +14 -0
- package/dist/react/extension/GroupFraming.js +52 -0
- package/dist/react/extension/Lens.d.ts +7 -0
- package/dist/react/extension/Lens.js +31 -0
- package/dist/react/impulse/ImpulseListener.d.ts +12 -0
- package/dist/react/impulse/ImpulseListener.js +33 -0
- package/dist/react/index.d.ts +24 -0
- package/dist/react/index.js +24 -0
- package/dist/react/input/InputAxisOwnerContext.d.ts +3 -0
- package/dist/react/input/InputAxisOwnerContext.js +6 -0
- package/dist/react/input/InputController.d.ts +53 -0
- package/dist/react/input/InputController.js +115 -0
- package/dist/react/noise/BasicMultiChannelPerlin.d.ts +7 -0
- package/dist/react/noise/BasicMultiChannelPerlin.js +14 -0
- package/dist/react/noise/Noise.d.ts +5 -0
- package/dist/react/noise/Noise.js +6 -0
- package/dist/react/noise/useBasicMultiChannelPerlinNoise.d.ts +4 -0
- package/dist/react/noise/useBasicMultiChannelPerlinNoise.js +21 -0
- package/dist/react/useCameraTransitionEvent.d.ts +12 -0
- package/dist/react/useCameraTransitionEvent.js +14 -0
- package/dist/three/CameraFrustumHelperThree.d.ts +11 -0
- package/dist/three/CameraFrustumHelperThree.js +44 -0
- package/dist/three/KlippThree.d.ts +18 -0
- package/dist/three/KlippThree.js +50 -0
- package/dist/three/VirtualCameraThree.d.ts +35 -0
- package/dist/three/VirtualCameraThree.js +108 -0
- package/dist/three/aim/HardLookAtAimThree.d.ts +10 -0
- package/dist/three/aim/HardLookAtAimThree.js +14 -0
- package/dist/three/aim/PanTiltAimThree.d.ts +10 -0
- package/dist/three/aim/PanTiltAimThree.js +15 -0
- package/dist/three/aim/RotateWithFollowTargetAimThree.d.ts +11 -0
- package/dist/three/aim/RotateWithFollowTargetAimThree.js +15 -0
- package/dist/three/aim/RotationComposerAimThree.d.ts +26 -0
- package/dist/three/aim/RotationComposerAimThree.js +35 -0
- package/dist/three/body/CameraControlsBodyThree.d.ts +30 -0
- package/dist/three/body/CameraControlsBodyThree.js +54 -0
- package/dist/three/body/FollowBodyThree.d.ts +16 -0
- package/dist/three/body/FollowBodyThree.js +23 -0
- package/dist/three/body/HardLockToTargetBodyThree.d.ts +11 -0
- package/dist/three/body/HardLockToTargetBodyThree.js +14 -0
- package/dist/three/body/OrbitFollowBodyThree.d.ts +16 -0
- package/dist/three/body/OrbitFollowBodyThree.js +23 -0
- package/dist/three/body/OrbitFollowHelperThree.d.ts +23 -0
- package/dist/three/body/OrbitFollowHelperThree.js +85 -0
- package/dist/three/body/PositionComposerBodyThree.d.ts +24 -0
- package/dist/three/body/PositionComposerBodyThree.js +31 -0
- package/dist/three/camera.d.ts +10 -0
- package/dist/three/camera.js +39 -0
- package/dist/three/damping/Predictor.d.ts +12 -0
- package/dist/three/damping/Predictor.js +30 -0
- package/dist/three/damping/QuaternionDamper.d.ts +10 -0
- package/dist/three/damping/QuaternionDamper.js +28 -0
- package/dist/three/damping/Vector3Damper.d.ts +10 -0
- package/dist/three/damping/Vector3Damper.js +26 -0
- package/dist/three/extension/GroupFramingExtensionThree.d.ts +13 -0
- package/dist/three/extension/GroupFramingExtensionThree.js +23 -0
- package/dist/three/extension/TargetGroup.d.ts +32 -0
- package/dist/three/extension/TargetGroup.js +53 -0
- package/dist/three/index.d.ts +24 -0
- package/dist/three/index.js +25 -0
- package/dist/three/noise/BasicMultiChannelPerlinNoiseThree.d.ts +16 -0
- package/dist/three/noise/BasicMultiChannelPerlinNoiseThree.js +17 -0
- package/dist/three/readTargetExtent.d.ts +9 -0
- package/dist/three/readTargetExtent.js +25 -0
- package/dist/three/readTargetPose.d.ts +8 -0
- package/dist/three/readTargetPose.js +21 -0
- package/dist/three/resolve/Target.d.ts +17 -0
- package/dist/three/resolve/Target.js +62 -0
- package/dist/three/resolve/TargetRegistry.d.ts +27 -0
- package/dist/three/resolve/TargetRegistry.js +78 -0
- package/dist/three/resolve/resolveVector3.d.ts +12 -0
- package/dist/three/resolve/resolveVector3.js +29 -0
- package/package.json +120 -4
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ConsumedInput } from '../core/input/consumedInput.js';
|
|
2
|
+
import { InputController } from '../core/input/InputController.js';
|
|
3
|
+
import { InputSystem } from './InputSystem.js';
|
|
4
|
+
/**
|
|
5
|
+
* `InputController` reading mouse, touch, wheel and gesture input from a DOM element. The page keeps its own
|
|
6
|
+
* scrolling and pinch zoom over the element unless `wheel` or `pinch` is mapped.
|
|
7
|
+
*/
|
|
8
|
+
export declare class InputControllerDom extends InputController {
|
|
9
|
+
readonly inputSystem: InputSystem;
|
|
10
|
+
/** Listens to `element`, calling `onInput` for every event that changes the input. */
|
|
11
|
+
connect: (element: HTMLElement, onInput?: () => void) => void;
|
|
12
|
+
disconnect: () => void;
|
|
13
|
+
protected readInput(): ConsumedInput;
|
|
14
|
+
private claimPageGestures;
|
|
15
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { InputController } from "../core/input/InputController.js";
|
|
2
|
+
import { InputSystem } from "./InputSystem.js";
|
|
3
|
+
//#region src/dom/InputControllerDom.ts
|
|
4
|
+
/**
|
|
5
|
+
* `InputController` reading mouse, touch, wheel and gesture input from a DOM element. The page keeps its own
|
|
6
|
+
* scrolling and pinch zoom over the element unless `wheel` or `pinch` is mapped.
|
|
7
|
+
*/
|
|
8
|
+
var InputControllerDom = class extends InputController {
|
|
9
|
+
inputSystem = new InputSystem();
|
|
10
|
+
/** Listens to `element`, calling `onInput` for every event that changes the input. */
|
|
11
|
+
connect = (element, onInput) => {
|
|
12
|
+
this.claimPageGestures();
|
|
13
|
+
this.inputSystem.connect(element, onInput);
|
|
14
|
+
};
|
|
15
|
+
disconnect = () => {
|
|
16
|
+
this.inputSystem.disconnect();
|
|
17
|
+
};
|
|
18
|
+
readInput() {
|
|
19
|
+
this.claimPageGestures();
|
|
20
|
+
return this.inputSystem.consume(this.input);
|
|
21
|
+
}
|
|
22
|
+
claimPageGestures() {
|
|
23
|
+
const { wheel, safariGesture } = this.inputSystem;
|
|
24
|
+
const zoom = !!this.config.pinch;
|
|
25
|
+
wheel.preventPageScroll = !!this.config.wheel;
|
|
26
|
+
wheel.preventPageZoom = zoom;
|
|
27
|
+
safariGesture.preventPageZoom = zoom;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
//#endregion
|
|
31
|
+
export { InputControllerDom };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ConsumedInput } from '../core/input/consumedInput.js';
|
|
2
|
+
import * as mouse from '../input-sources/mouse.js';
|
|
3
|
+
import * as safariGesture from '../input-sources/safariGesture.js';
|
|
4
|
+
import * as touch from '../input-sources/touch.js';
|
|
5
|
+
import * as wheel from '../input-sources/wheel.js';
|
|
6
|
+
import { type InteractiveArea } from '../input-sources/isInsideInteractiveArea.js';
|
|
7
|
+
export type { InteractiveArea } from '../input-sources/isInsideInteractiveArea.js';
|
|
8
|
+
export { MouseButton } from '../input-sources/mouse.js';
|
|
9
|
+
/** Mouse, touch, wheel and Safari gesture input from a DOM element, read once per frame. */
|
|
10
|
+
export declare class InputSystem {
|
|
11
|
+
readonly mouse: mouse.MouseState;
|
|
12
|
+
readonly touch: touch.TouchState;
|
|
13
|
+
readonly wheel: wheel.WheelState;
|
|
14
|
+
readonly safariGesture: safariGesture.SafariGestureState;
|
|
15
|
+
private _interactiveArea;
|
|
16
|
+
private element;
|
|
17
|
+
private disconnectSources;
|
|
18
|
+
/** Suppress the native right-click menu. */
|
|
19
|
+
get suppressContextMenu(): boolean;
|
|
20
|
+
set suppressContextMenu(suppress: boolean);
|
|
21
|
+
/** Restrict gesture starts to a normalized region. */
|
|
22
|
+
get interactiveArea(): InteractiveArea | null;
|
|
23
|
+
set interactiveArea(area: InteractiveArea | null);
|
|
24
|
+
/** Lock diagonal two-finger input to pinch or rotation. */
|
|
25
|
+
get lockTouchAxis(): boolean;
|
|
26
|
+
set lockTouchAxis(lock: boolean);
|
|
27
|
+
/**
|
|
28
|
+
* Attaches listeners to `element`, calling `onInput` for every event that changes the input. Safe to call
|
|
29
|
+
* again with a new element - disconnects the old one first.
|
|
30
|
+
*/
|
|
31
|
+
connect: (element: HTMLElement, onInput?: () => void) => void;
|
|
32
|
+
/** Detaches every listener. A pointer lock stays: it belongs to the document, release it with `exitPointerLock`. */
|
|
33
|
+
disconnect: () => void;
|
|
34
|
+
requestPointerLock: () => void;
|
|
35
|
+
exitPointerLock: () => void;
|
|
36
|
+
/** Starts a new frame in every source and writes it to `out`. */
|
|
37
|
+
consume: (out: ConsumedInput) => ConsumedInput;
|
|
38
|
+
/** Whether `clientX/Y` falls within `interactiveArea`. A locked pointer counts as inside: its position is frozen. */
|
|
39
|
+
isInsideInteractiveArea(clientX: number, clientY: number): boolean;
|
|
40
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { isInsideInteractiveArea } from "../input-sources/isInsideInteractiveArea.js";
|
|
2
|
+
import { MouseButton, connect, create, update } from "../input-sources/mouse.js";
|
|
3
|
+
import { connect as connect$1, create as create$1, update as update$1 } from "../input-sources/touch.js";
|
|
4
|
+
import { connect as connect$2, create as create$2, update as update$2 } from "../input-sources/safariGesture.js";
|
|
5
|
+
import { connect as connect$3, create as create$3, update as update$3 } from "../input-sources/wheel.js";
|
|
6
|
+
//#region src/dom/InputSystem.ts
|
|
7
|
+
/** Mouse, touch, wheel and Safari gesture input from a DOM element, read once per frame. */
|
|
8
|
+
var InputSystem = class {
|
|
9
|
+
mouse = create();
|
|
10
|
+
touch = create$1();
|
|
11
|
+
wheel = create$3();
|
|
12
|
+
safariGesture = create$2();
|
|
13
|
+
_interactiveArea = null;
|
|
14
|
+
element = null;
|
|
15
|
+
disconnectSources = null;
|
|
16
|
+
/** Suppress the native right-click menu. */
|
|
17
|
+
get suppressContextMenu() {
|
|
18
|
+
return this.mouse.suppressContextMenu;
|
|
19
|
+
}
|
|
20
|
+
set suppressContextMenu(suppress) {
|
|
21
|
+
this.mouse.suppressContextMenu = suppress;
|
|
22
|
+
}
|
|
23
|
+
/** Restrict gesture starts to a normalized region. */
|
|
24
|
+
get interactiveArea() {
|
|
25
|
+
return this._interactiveArea;
|
|
26
|
+
}
|
|
27
|
+
set interactiveArea(area) {
|
|
28
|
+
this._interactiveArea = area;
|
|
29
|
+
this.mouse.interactiveArea = area;
|
|
30
|
+
this.touch.interactiveArea = area;
|
|
31
|
+
this.wheel.interactiveArea = area;
|
|
32
|
+
this.safariGesture.interactiveArea = area;
|
|
33
|
+
}
|
|
34
|
+
/** Lock diagonal two-finger input to pinch or rotation. */
|
|
35
|
+
get lockTouchAxis() {
|
|
36
|
+
return this.touch.lockTouchAxis;
|
|
37
|
+
}
|
|
38
|
+
set lockTouchAxis(lock) {
|
|
39
|
+
this.touch.lockTouchAxis = lock;
|
|
40
|
+
this.safariGesture.lockTouchAxis = lock;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Attaches listeners to `element`, calling `onInput` for every event that changes the input. Safe to call
|
|
44
|
+
* again with a new element - disconnects the old one first.
|
|
45
|
+
*/
|
|
46
|
+
connect = (element, onInput) => {
|
|
47
|
+
this.disconnect();
|
|
48
|
+
this.element = element;
|
|
49
|
+
const disconnectMouse = connect(this.mouse, element, onInput);
|
|
50
|
+
const disconnectTouch = connect$1(this.touch, element, onInput);
|
|
51
|
+
const disconnectWheel = connect$3(this.wheel, element, onInput);
|
|
52
|
+
const disconnectSafariGesture = connect$2(this.safariGesture, element, onInput);
|
|
53
|
+
this.disconnectSources = () => {
|
|
54
|
+
disconnectMouse();
|
|
55
|
+
disconnectTouch();
|
|
56
|
+
disconnectWheel();
|
|
57
|
+
disconnectSafariGesture();
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
/** Detaches every listener. A pointer lock stays: it belongs to the document, release it with `exitPointerLock`. */
|
|
61
|
+
disconnect = () => {
|
|
62
|
+
this.disconnectSources?.();
|
|
63
|
+
this.disconnectSources = null;
|
|
64
|
+
this.element = null;
|
|
65
|
+
};
|
|
66
|
+
requestPointerLock = () => {
|
|
67
|
+
this.element?.requestPointerLock();
|
|
68
|
+
};
|
|
69
|
+
exitPointerLock = () => {
|
|
70
|
+
if (document.pointerLockElement === this.element) document.exitPointerLock();
|
|
71
|
+
};
|
|
72
|
+
/** Starts a new frame in every source and writes it to `out`. */
|
|
73
|
+
consume = (out) => {
|
|
74
|
+
update(this.mouse);
|
|
75
|
+
update$1(this.touch);
|
|
76
|
+
update$3(this.wheel);
|
|
77
|
+
update$2(this.safariGesture);
|
|
78
|
+
const { drag, buttons, lockedMovement } = this.mouse;
|
|
79
|
+
out.leftDx = drag.left[0];
|
|
80
|
+
out.leftDy = drag.left[1];
|
|
81
|
+
out.middleDx = drag.middle[0];
|
|
82
|
+
out.middleDy = drag.middle[1];
|
|
83
|
+
out.rightDx = drag.right[0];
|
|
84
|
+
out.rightDy = drag.right[1];
|
|
85
|
+
out.lockedDx = lockedMovement[0];
|
|
86
|
+
out.lockedDy = lockedMovement[1];
|
|
87
|
+
out.leftHeld = buttons.pressed.has("left");
|
|
88
|
+
out.middleHeld = buttons.pressed.has("middle");
|
|
89
|
+
out.rightHeld = buttons.pressed.has("right");
|
|
90
|
+
const touchState = this.touch;
|
|
91
|
+
out.touchOneDx = touchState.drag.one[0];
|
|
92
|
+
out.touchOneDy = touchState.drag.one[1];
|
|
93
|
+
out.touchTwoDx = touchState.drag.two[0];
|
|
94
|
+
out.touchTwoDy = touchState.drag.two[1];
|
|
95
|
+
out.touchThreeDx = touchState.drag.three[0];
|
|
96
|
+
out.touchThreeDy = touchState.drag.three[1];
|
|
97
|
+
out.touchRotateDelta = touchState.twistDelta + this.safariGesture.twistDelta;
|
|
98
|
+
out.touchOneHeld = touchState.fingers === 1;
|
|
99
|
+
out.touchTwoHeld = touchState.fingers === 2;
|
|
100
|
+
out.touchThreeHeld = touchState.fingers === 3;
|
|
101
|
+
out.wheelDeltaX = this.wheel.deltaX;
|
|
102
|
+
out.wheelDeltaY = this.wheel.deltaY;
|
|
103
|
+
out.pinchDelta = touchState.pinchDelta + this.wheel.pinchDelta + this.safariGesture.pinchDelta;
|
|
104
|
+
return out;
|
|
105
|
+
};
|
|
106
|
+
/** Whether `clientX/Y` falls within `interactiveArea`. A locked pointer counts as inside: its position is frozen. */
|
|
107
|
+
isInsideInteractiveArea(clientX, clientY) {
|
|
108
|
+
if (!this.element) return true;
|
|
109
|
+
return isInsideInteractiveArea(this.element, this._interactiveArea, clientX, clientY);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
//#endregion
|
|
113
|
+
export { InputSystem, MouseButton };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { MouseButton } from "../input-sources/mouse.js";
|
|
2
|
+
import { InputSystem } from "./InputSystem.js";
|
|
3
|
+
import { InputControllerDom } from "./InputControllerDom.js";
|
|
4
|
+
import { DebugOverlay } from "./DebugOverlay.js";
|
|
5
|
+
export { DebugOverlay, InputControllerDom, InputSystem, MouseButton };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Which buttons are held, and which went down or up since the last `clear`. */
|
|
2
|
+
export type ButtonInput<T> = {
|
|
3
|
+
readonly pressed: Set<T>;
|
|
4
|
+
readonly justPressed: Set<T>;
|
|
5
|
+
readonly justReleased: Set<T>;
|
|
6
|
+
};
|
|
7
|
+
export declare const create: <T>() => ButtonInput<T>;
|
|
8
|
+
/** Marks `button` as held. Pressing a held button again is not a new press. */
|
|
9
|
+
export declare function press<T>(state: ButtonInput<T>, button: T): void;
|
|
10
|
+
/** Releases `button` if it was held. */
|
|
11
|
+
export declare function release<T>(state: ButtonInput<T>, button: T): void;
|
|
12
|
+
/** Releases every held button, for when the page loses focus mid-press. */
|
|
13
|
+
export declare function releaseAll<T>(state: ButtonInput<T>): void;
|
|
14
|
+
/** Starts a new frame: forgets which buttons just went down or up, keeps what is held. */
|
|
15
|
+
export declare function clear<T>(state: ButtonInput<T>): void;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region src/input-sources/buttonInput.ts
|
|
2
|
+
var create = () => ({
|
|
3
|
+
pressed: /* @__PURE__ */ new Set(),
|
|
4
|
+
justPressed: /* @__PURE__ */ new Set(),
|
|
5
|
+
justReleased: /* @__PURE__ */ new Set()
|
|
6
|
+
});
|
|
7
|
+
/** Marks `button` as held. Pressing a held button again is not a new press. */
|
|
8
|
+
function press(state, button) {
|
|
9
|
+
if (state.pressed.has(button)) return;
|
|
10
|
+
state.pressed.add(button);
|
|
11
|
+
state.justPressed.add(button);
|
|
12
|
+
}
|
|
13
|
+
/** Releases `button` if it was held. */
|
|
14
|
+
function release(state, button) {
|
|
15
|
+
if (state.pressed.delete(button)) state.justReleased.add(button);
|
|
16
|
+
}
|
|
17
|
+
/** Starts a new frame: forgets which buttons just went down or up, keeps what is held. */
|
|
18
|
+
function clear(state) {
|
|
19
|
+
if (state.justPressed.size > 0) state.justPressed.clear();
|
|
20
|
+
if (state.justReleased.size > 0) state.justReleased.clear();
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
export { clear, create, press, release };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Normalized input region within the element. */
|
|
2
|
+
export type InteractiveArea = {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
};
|
|
8
|
+
/** Whether `clientX/Y` falls within `area` of `element`. A locked pointer counts as inside: its position is frozen. */
|
|
9
|
+
export declare function isInsideInteractiveArea(element: HTMLElement, area: InteractiveArea | null, clientX: number, clientY: number): boolean;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region src/input-sources/isInsideInteractiveArea.ts
|
|
2
|
+
/** Whether `clientX/Y` falls within `area` of `element`. A locked pointer counts as inside: its position is frozen. */
|
|
3
|
+
function isInsideInteractiveArea(element, area, clientX, clientY) {
|
|
4
|
+
if (!area || document.pointerLockElement === element) return true;
|
|
5
|
+
const rect = element.getBoundingClientRect();
|
|
6
|
+
const x = (clientX - rect.left) / rect.width;
|
|
7
|
+
const y = (clientY - rect.top) / rect.height;
|
|
8
|
+
return x >= area.x && x <= area.x + area.width && y >= area.y && y <= area.y + area.height;
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
export { isInsideInteractiveArea };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Vec2 } from 'math';
|
|
2
|
+
import type { ButtonInput } from './buttonInput.js';
|
|
3
|
+
import { type InteractiveArea } from './isInsideInteractiveArea.js';
|
|
4
|
+
/** `PointerEvent.buttons` bit of each mouse button. */
|
|
5
|
+
export declare const MouseButton: {
|
|
6
|
+
readonly left: 1;
|
|
7
|
+
readonly right: 2;
|
|
8
|
+
readonly middle: 4;
|
|
9
|
+
};
|
|
10
|
+
export type MouseButtonName = keyof typeof MouseButton;
|
|
11
|
+
/** Mouse buttons and movement, summed per frame. */
|
|
12
|
+
export type MouseState = {
|
|
13
|
+
/** Which buttons are held, and which went down or up this frame. */
|
|
14
|
+
readonly buttons: ButtonInput<MouseButtonName>;
|
|
15
|
+
/** Movement this frame while each button was held, in pixels. */
|
|
16
|
+
readonly drag: Record<MouseButtonName, Vec2>;
|
|
17
|
+
/** Movement this frame while the pointer was locked with no button held. */
|
|
18
|
+
readonly lockedMovement: Vec2;
|
|
19
|
+
/** Whether the pointer is locked to the element. */
|
|
20
|
+
locked: boolean;
|
|
21
|
+
/** Only presses that start inside this normalized region count. */
|
|
22
|
+
interactiveArea: InteractiveArea | null;
|
|
23
|
+
/** Suppress the native right-click menu. */
|
|
24
|
+
suppressContextMenu: boolean;
|
|
25
|
+
/** Internal. */
|
|
26
|
+
pending: MousePending;
|
|
27
|
+
};
|
|
28
|
+
type MousePending = {
|
|
29
|
+
drag: Record<MouseButtonName, Vec2>;
|
|
30
|
+
lockedMovement: Vec2;
|
|
31
|
+
/** Button changes since the last update, in order, as an index into `buttonNames` plus `DOWN` for a press. */
|
|
32
|
+
changes: number[];
|
|
33
|
+
changeCount: number;
|
|
34
|
+
/** `event.buttons` as of the last event. */
|
|
35
|
+
held: number;
|
|
36
|
+
pointerId: number | null;
|
|
37
|
+
x: number;
|
|
38
|
+
y: number;
|
|
39
|
+
/** The next move sets the position instead of measuring from it, since it went stale. */
|
|
40
|
+
reanchor: boolean;
|
|
41
|
+
};
|
|
42
|
+
export declare const create: () => MouseState;
|
|
43
|
+
/** Listens to `element`'s mouse. Returns a function that stops; a pointer lock it holds stays. */
|
|
44
|
+
export declare function connect(state: MouseState, element: HTMLElement, onInput?: () => void): () => void;
|
|
45
|
+
/** Starts a new frame: the input gathered since the last call becomes this frame's. */
|
|
46
|
+
export declare function update(state: MouseState): void;
|
|
47
|
+
export {};
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { clear, create as create$1, press, release } from "./buttonInput.js";
|
|
2
|
+
import { isInsideInteractiveArea } from "./isInsideInteractiveArea.js";
|
|
3
|
+
import { suppressNativeGestures } from "./suppressNativeGestures.js";
|
|
4
|
+
//#region src/input-sources/mouse.ts
|
|
5
|
+
/** `PointerEvent.buttons` bit of each mouse button. */
|
|
6
|
+
var MouseButton = {
|
|
7
|
+
left: 1,
|
|
8
|
+
right: 2,
|
|
9
|
+
middle: 4
|
|
10
|
+
};
|
|
11
|
+
var DOWN = 8;
|
|
12
|
+
var buttonNames = [
|
|
13
|
+
"left",
|
|
14
|
+
"right",
|
|
15
|
+
"middle"
|
|
16
|
+
];
|
|
17
|
+
var create = () => ({
|
|
18
|
+
buttons: create$1(),
|
|
19
|
+
drag: {
|
|
20
|
+
left: [0, 0],
|
|
21
|
+
middle: [0, 0],
|
|
22
|
+
right: [0, 0]
|
|
23
|
+
},
|
|
24
|
+
lockedMovement: [0, 0],
|
|
25
|
+
locked: false,
|
|
26
|
+
interactiveArea: null,
|
|
27
|
+
suppressContextMenu: false,
|
|
28
|
+
pending: {
|
|
29
|
+
drag: {
|
|
30
|
+
left: [0, 0],
|
|
31
|
+
middle: [0, 0],
|
|
32
|
+
right: [0, 0]
|
|
33
|
+
},
|
|
34
|
+
lockedMovement: [0, 0],
|
|
35
|
+
changes: [],
|
|
36
|
+
changeCount: 0,
|
|
37
|
+
held: 0,
|
|
38
|
+
pointerId: null,
|
|
39
|
+
x: 0,
|
|
40
|
+
y: 0,
|
|
41
|
+
reanchor: false
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
/** Queues a press or release for every button that differs between the last event and `held`. */
|
|
45
|
+
function setHeld(pending, held) {
|
|
46
|
+
for (let i = 0; i < buttonNames.length; i++) {
|
|
47
|
+
const bit = MouseButton[buttonNames[i]];
|
|
48
|
+
if ((pending.held & bit) === (held & bit)) continue;
|
|
49
|
+
pending.changes[pending.changeCount++] = i | (held & bit ? DOWN : 0);
|
|
50
|
+
}
|
|
51
|
+
pending.held = held;
|
|
52
|
+
}
|
|
53
|
+
function addDrag(pending, buttons, dx, dy) {
|
|
54
|
+
for (const name of buttonNames) {
|
|
55
|
+
if ((buttons & MouseButton[name]) === 0) continue;
|
|
56
|
+
const drag = pending.drag[name];
|
|
57
|
+
drag[0] += dx;
|
|
58
|
+
drag[1] += dy;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** Listens to `element`'s mouse. Returns a function that stops; a pointer lock it holds stays. */
|
|
62
|
+
function connect(state, element, onInput) {
|
|
63
|
+
const pending = state.pending;
|
|
64
|
+
const isLocked = () => document.pointerLockElement === element;
|
|
65
|
+
const release = () => {
|
|
66
|
+
pending.pointerId = null;
|
|
67
|
+
setHeld(pending, 0);
|
|
68
|
+
};
|
|
69
|
+
const onPointerDown = (event) => {
|
|
70
|
+
if (event.pointerType !== "mouse") return;
|
|
71
|
+
if (!isInsideInteractiveArea(element, state.interactiveArea, event.clientX, event.clientY)) return;
|
|
72
|
+
if (pending.pointerId !== null && pending.pointerId !== event.pointerId) return;
|
|
73
|
+
pending.pointerId = event.pointerId;
|
|
74
|
+
pending.x = event.clientX;
|
|
75
|
+
pending.y = event.clientY;
|
|
76
|
+
setHeld(pending, event.buttons);
|
|
77
|
+
try {
|
|
78
|
+
element.setPointerCapture(event.pointerId);
|
|
79
|
+
} catch {}
|
|
80
|
+
onInput?.();
|
|
81
|
+
};
|
|
82
|
+
const onPointerMove = (event) => {
|
|
83
|
+
if (event.pointerType !== "mouse") return;
|
|
84
|
+
if (pending.pointerId !== event.pointerId) {
|
|
85
|
+
if (!isLocked()) return;
|
|
86
|
+
pending.lockedMovement[0] += event.movementX;
|
|
87
|
+
pending.lockedMovement[1] += event.movementY;
|
|
88
|
+
onInput?.();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
setHeld(pending, event.buttons);
|
|
92
|
+
if (pending.reanchor) pending.reanchor = false;
|
|
93
|
+
else if (isLocked()) addDrag(pending, event.buttons, event.movementX, event.movementY);
|
|
94
|
+
else addDrag(pending, event.buttons, event.clientX - pending.x, event.clientY - pending.y);
|
|
95
|
+
pending.x = event.clientX;
|
|
96
|
+
pending.y = event.clientY;
|
|
97
|
+
onInput?.();
|
|
98
|
+
};
|
|
99
|
+
const onPointerUp = (event) => {
|
|
100
|
+
if (event.pointerType !== "mouse" || pending.pointerId !== event.pointerId) return;
|
|
101
|
+
release();
|
|
102
|
+
onInput?.();
|
|
103
|
+
};
|
|
104
|
+
const onContextMenu = (event) => {
|
|
105
|
+
if (state.suppressContextMenu) event.preventDefault();
|
|
106
|
+
};
|
|
107
|
+
const onPointerLockChange = () => {
|
|
108
|
+
const locked = isLocked();
|
|
109
|
+
if (state.locked && !locked && pending.pointerId !== null) pending.reanchor = true;
|
|
110
|
+
state.locked = locked;
|
|
111
|
+
};
|
|
112
|
+
const onPointerLockError = () => {
|
|
113
|
+
console.warn("requestPointerLock() failed - the browser rejected the request.");
|
|
114
|
+
};
|
|
115
|
+
const onBlur = () => release();
|
|
116
|
+
const onVisibilityChange = () => {
|
|
117
|
+
if (document.hidden) release();
|
|
118
|
+
};
|
|
119
|
+
const restoreStyles = suppressNativeGestures(element);
|
|
120
|
+
element.addEventListener("pointerdown", onPointerDown);
|
|
121
|
+
element.addEventListener("pointermove", onPointerMove);
|
|
122
|
+
element.addEventListener("pointerup", onPointerUp);
|
|
123
|
+
element.addEventListener("pointercancel", onPointerUp);
|
|
124
|
+
element.addEventListener("lostpointercapture", onPointerUp);
|
|
125
|
+
element.addEventListener("contextmenu", onContextMenu);
|
|
126
|
+
document.addEventListener("pointerlockchange", onPointerLockChange);
|
|
127
|
+
document.addEventListener("pointerlockerror", onPointerLockError);
|
|
128
|
+
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
129
|
+
window.addEventListener("blur", onBlur);
|
|
130
|
+
state.locked = isLocked();
|
|
131
|
+
return () => {
|
|
132
|
+
restoreStyles();
|
|
133
|
+
element.removeEventListener("pointerdown", onPointerDown);
|
|
134
|
+
element.removeEventListener("pointermove", onPointerMove);
|
|
135
|
+
element.removeEventListener("pointerup", onPointerUp);
|
|
136
|
+
element.removeEventListener("pointercancel", onPointerUp);
|
|
137
|
+
element.removeEventListener("lostpointercapture", onPointerUp);
|
|
138
|
+
element.removeEventListener("contextmenu", onContextMenu);
|
|
139
|
+
document.removeEventListener("pointerlockchange", onPointerLockChange);
|
|
140
|
+
document.removeEventListener("pointerlockerror", onPointerLockError);
|
|
141
|
+
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
142
|
+
window.removeEventListener("blur", onBlur);
|
|
143
|
+
release();
|
|
144
|
+
pending.reanchor = false;
|
|
145
|
+
state.locked = false;
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
var copyAndZero = (out, source) => {
|
|
149
|
+
out[0] = source[0];
|
|
150
|
+
out[1] = source[1];
|
|
151
|
+
source[0] = 0;
|
|
152
|
+
source[1] = 0;
|
|
153
|
+
};
|
|
154
|
+
/** Starts a new frame: the input gathered since the last call becomes this frame's. */
|
|
155
|
+
function update(state) {
|
|
156
|
+
const pending = state.pending;
|
|
157
|
+
clear(state.buttons);
|
|
158
|
+
for (let i = 0; i < pending.changeCount; i++) {
|
|
159
|
+
const change = pending.changes[i];
|
|
160
|
+
const button = buttonNames[change & -9];
|
|
161
|
+
if (change & DOWN) press(state.buttons, button);
|
|
162
|
+
else release(state.buttons, button);
|
|
163
|
+
}
|
|
164
|
+
pending.changeCount = 0;
|
|
165
|
+
copyAndZero(state.drag.left, pending.drag.left);
|
|
166
|
+
copyAndZero(state.drag.middle, pending.drag.middle);
|
|
167
|
+
copyAndZero(state.drag.right, pending.drag.right);
|
|
168
|
+
copyAndZero(state.lockedMovement, pending.lockedMovement);
|
|
169
|
+
}
|
|
170
|
+
//#endregion
|
|
171
|
+
export { MouseButton, connect, create, update };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type InteractiveArea } from './isInsideInteractiveArea.js';
|
|
2
|
+
/** Safari's trackpad pinch and rotate, summed per frame. Pinches on a touch screen are left to `touch`. */
|
|
3
|
+
export type SafariGestureState = {
|
|
4
|
+
/** Pinch this frame, as the change in `ln(scale)`. Spreading is positive. */
|
|
5
|
+
pinchDelta: number;
|
|
6
|
+
/** Rotation this frame, in radians. */
|
|
7
|
+
twistDelta: number;
|
|
8
|
+
/** Only gestures that start inside this normalized region count. */
|
|
9
|
+
interactiveArea: InteractiveArea | null;
|
|
10
|
+
/** Each gesture keeps only the stronger of pinch and twist. */
|
|
11
|
+
lockTouchAxis: boolean;
|
|
12
|
+
/** Keep the gesture from zooming the page. */
|
|
13
|
+
preventPageZoom: boolean;
|
|
14
|
+
/** Internal. */
|
|
15
|
+
pending: {
|
|
16
|
+
pinchDelta: number;
|
|
17
|
+
twistDelta: number;
|
|
18
|
+
active: boolean;
|
|
19
|
+
scale: number;
|
|
20
|
+
rotationDegrees: number;
|
|
21
|
+
twistTotalDegrees: number;
|
|
22
|
+
axisLock: 'pinch' | 'twist' | null;
|
|
23
|
+
/** Touch pointers down on the element, which iOS also reports as gestures. */
|
|
24
|
+
touchIds: number[];
|
|
25
|
+
touchCount: number;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
export declare const create: () => SafariGestureState;
|
|
29
|
+
/** Listens to `element`'s Safari gesture events. Returns a function that stops. */
|
|
30
|
+
export declare function connect(state: SafariGestureState, element: HTMLElement, onInput?: () => void): () => void;
|
|
31
|
+
/** Starts a new frame: the input gathered since the last call becomes this frame's. */
|
|
32
|
+
export declare function update(state: SafariGestureState): void;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { isInsideInteractiveArea } from "./isInsideInteractiveArea.js";
|
|
2
|
+
import { dominantGesture } from "./touch.js";
|
|
3
|
+
import { degreesToRadians } from "math";
|
|
4
|
+
//#region src/input-sources/safariGesture.ts
|
|
5
|
+
var create = () => ({
|
|
6
|
+
pinchDelta: 0,
|
|
7
|
+
twistDelta: 0,
|
|
8
|
+
interactiveArea: null,
|
|
9
|
+
lockTouchAxis: false,
|
|
10
|
+
preventPageZoom: true,
|
|
11
|
+
pending: {
|
|
12
|
+
pinchDelta: 0,
|
|
13
|
+
twistDelta: 0,
|
|
14
|
+
active: false,
|
|
15
|
+
scale: 1,
|
|
16
|
+
rotationDegrees: 0,
|
|
17
|
+
twistTotalDegrees: 0,
|
|
18
|
+
axisLock: null,
|
|
19
|
+
touchIds: [],
|
|
20
|
+
touchCount: 0
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
/** Listens to `element`'s Safari gesture events. Returns a function that stops. */
|
|
24
|
+
function connect(state, element, onInput) {
|
|
25
|
+
const pending = state.pending;
|
|
26
|
+
const end = () => {
|
|
27
|
+
pending.active = false;
|
|
28
|
+
};
|
|
29
|
+
const liftAll = () => {
|
|
30
|
+
end();
|
|
31
|
+
pending.touchIds.fill(-1);
|
|
32
|
+
pending.touchCount = 0;
|
|
33
|
+
};
|
|
34
|
+
const onPointerDown = (event) => {
|
|
35
|
+
if (event.pointerType !== "touch" || pending.touchIds.indexOf(event.pointerId) !== -1) return;
|
|
36
|
+
pending.touchIds[pending.touchCount++] = event.pointerId;
|
|
37
|
+
};
|
|
38
|
+
const onPointerUp = (event) => {
|
|
39
|
+
if (event.pointerType !== "touch") return;
|
|
40
|
+
const index = pending.touchIds.indexOf(event.pointerId);
|
|
41
|
+
if (index === -1 || index >= pending.touchCount) return;
|
|
42
|
+
pending.touchIds[index] = pending.touchIds[--pending.touchCount];
|
|
43
|
+
pending.touchIds[pending.touchCount] = -1;
|
|
44
|
+
};
|
|
45
|
+
const onGestureStart = (event) => {
|
|
46
|
+
const gesture = event;
|
|
47
|
+
if (!isInsideInteractiveArea(element, state.interactiveArea, gesture.clientX, gesture.clientY)) return;
|
|
48
|
+
if (event.cancelable && state.preventPageZoom) event.preventDefault();
|
|
49
|
+
if (pending.touchCount > 0) return;
|
|
50
|
+
pending.active = true;
|
|
51
|
+
pending.scale = gesture.scale;
|
|
52
|
+
pending.rotationDegrees = gesture.rotation;
|
|
53
|
+
pending.twistTotalDegrees = 0;
|
|
54
|
+
pending.axisLock = null;
|
|
55
|
+
};
|
|
56
|
+
const onGestureChange = (event) => {
|
|
57
|
+
if (!pending.active) return;
|
|
58
|
+
const gesture = event;
|
|
59
|
+
if (event.cancelable && state.preventPageZoom) event.preventDefault();
|
|
60
|
+
if (pending.touchCount > 0) {
|
|
61
|
+
end();
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const pinchStep = Math.log(gesture.scale / pending.scale);
|
|
65
|
+
const rotationStep = gesture.rotation - pending.rotationDegrees;
|
|
66
|
+
pending.scale = gesture.scale;
|
|
67
|
+
pending.rotationDegrees = gesture.rotation;
|
|
68
|
+
pending.twistTotalDegrees += rotationStep;
|
|
69
|
+
if (state.lockTouchAxis && !pending.axisLock) pending.axisLock = dominantGesture(gesture.scale - 1, pending.twistTotalDegrees);
|
|
70
|
+
if (pending.axisLock !== "twist") pending.pinchDelta += pinchStep;
|
|
71
|
+
if (pending.axisLock !== "pinch") pending.twistDelta += degreesToRadians(rotationStep);
|
|
72
|
+
onInput?.();
|
|
73
|
+
};
|
|
74
|
+
const onVisibilityChange = () => {
|
|
75
|
+
if (document.hidden) liftAll();
|
|
76
|
+
};
|
|
77
|
+
element.addEventListener("pointerdown", onPointerDown);
|
|
78
|
+
element.addEventListener("pointerup", onPointerUp);
|
|
79
|
+
element.addEventListener("pointercancel", onPointerUp);
|
|
80
|
+
element.addEventListener("lostpointercapture", onPointerUp);
|
|
81
|
+
element.addEventListener("gesturestart", onGestureStart);
|
|
82
|
+
element.addEventListener("gesturechange", onGestureChange);
|
|
83
|
+
element.addEventListener("gestureend", end);
|
|
84
|
+
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
85
|
+
window.addEventListener("blur", liftAll);
|
|
86
|
+
return () => {
|
|
87
|
+
element.removeEventListener("pointerdown", onPointerDown);
|
|
88
|
+
element.removeEventListener("pointerup", onPointerUp);
|
|
89
|
+
element.removeEventListener("pointercancel", onPointerUp);
|
|
90
|
+
element.removeEventListener("lostpointercapture", onPointerUp);
|
|
91
|
+
element.removeEventListener("gesturestart", onGestureStart);
|
|
92
|
+
element.removeEventListener("gesturechange", onGestureChange);
|
|
93
|
+
element.removeEventListener("gestureend", end);
|
|
94
|
+
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
95
|
+
window.removeEventListener("blur", liftAll);
|
|
96
|
+
liftAll();
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/** Starts a new frame: the input gathered since the last call becomes this frame's. */
|
|
100
|
+
function update(state) {
|
|
101
|
+
state.pinchDelta = state.pending.pinchDelta;
|
|
102
|
+
state.twistDelta = state.pending.twistDelta;
|
|
103
|
+
state.pending.pinchDelta = 0;
|
|
104
|
+
state.pending.twistDelta = 0;
|
|
105
|
+
}
|
|
106
|
+
//#endregion
|
|
107
|
+
export { connect, create, update };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turns off native touch scrolling and text selection on `element`, which would fight a drag. Returns a
|
|
3
|
+
* function that restores the inline styles from before the first of any overlapping calls.
|
|
4
|
+
*/
|
|
5
|
+
export declare function suppressNativeGestures(element: HTMLElement): () => void;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//#region src/input-sources/suppressNativeGestures.ts
|
|
2
|
+
var suppressed = /* @__PURE__ */ new WeakMap();
|
|
3
|
+
/**
|
|
4
|
+
* Turns off native touch scrolling and text selection on `element`, which would fight a drag. Returns a
|
|
5
|
+
* function that restores the inline styles from before the first of any overlapping calls.
|
|
6
|
+
*/
|
|
7
|
+
function suppressNativeGestures(element) {
|
|
8
|
+
let entry = suppressed.get(element);
|
|
9
|
+
if (!entry) {
|
|
10
|
+
entry = {
|
|
11
|
+
count: 0,
|
|
12
|
+
touchAction: element.style.touchAction,
|
|
13
|
+
userSelect: element.style.userSelect
|
|
14
|
+
};
|
|
15
|
+
suppressed.set(element, entry);
|
|
16
|
+
element.style.touchAction = "none";
|
|
17
|
+
element.style.userSelect = "none";
|
|
18
|
+
}
|
|
19
|
+
entry.count++;
|
|
20
|
+
let restored = false;
|
|
21
|
+
return () => {
|
|
22
|
+
if (restored) return;
|
|
23
|
+
restored = true;
|
|
24
|
+
const current = suppressed.get(element);
|
|
25
|
+
if (--current.count > 0) return;
|
|
26
|
+
element.style.touchAction = current.touchAction;
|
|
27
|
+
element.style.userSelect = current.userSelect;
|
|
28
|
+
suppressed.delete(element);
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
export { suppressNativeGestures };
|