@anyknown/axis 0.0.0-stage → 0.2.1
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 +217 -2
- package/dist/axis/constants.d.ts +12 -0
- package/dist/axis/constants.js +27 -0
- package/dist/axis/create-axis.d.ts +10 -0
- package/dist/axis/create-axis.js +212 -0
- package/dist/axis/create-drag-client.d.ts +6 -0
- package/dist/axis/create-drag-client.js +95 -0
- package/dist/axis/create-geometry.d.ts +2 -0
- package/dist/axis/create-geometry.js +11 -0
- package/dist/axis/create-hold.d.ts +2 -0
- package/dist/axis/create-hold.js +38 -0
- package/dist/axis/create-scroll-driver.d.ts +2 -0
- package/dist/axis/create-scroll-driver.js +139 -0
- package/dist/axis/create-snap-suspension.d.ts +2 -0
- package/dist/axis/create-snap-suspension.js +23 -0
- package/dist/axis/create-style-overrides.d.ts +2 -0
- package/dist/axis/create-style-overrides.js +35 -0
- package/dist/axis/create-wheel.d.ts +5 -0
- package/dist/axis/create-wheel.js +57 -0
- package/dist/axis/drags-along.d.ts +2 -0
- package/dist/axis/drags-along.js +1 -0
- package/dist/axis/index.d.ts +2 -0
- package/dist/axis/index.js +1 -0
- package/dist/axis/merge-options.d.ts +2 -0
- package/dist/axis/merge-options.js +9 -0
- package/dist/axis/next-axis-state.d.ts +2 -0
- package/dist/axis/next-axis-state.js +14 -0
- package/dist/axis/resolve-axis.d.ts +2 -0
- package/dist/axis/resolve-axis.js +8 -0
- package/dist/axis/types.d.ts +146 -0
- package/dist/axis/types.js +1 -0
- package/dist/coordinator/constants.d.ts +5 -0
- package/dist/coordinator/constants.js +5 -0
- package/dist/coordinator/create-coordinator.d.ts +2 -0
- package/dist/coordinator/create-coordinator.js +191 -0
- package/dist/coordinator/index.d.ts +2 -0
- package/dist/coordinator/index.js +1 -0
- package/dist/coordinator/pick-owner.d.ts +3 -0
- package/dist/coordinator/pick-owner.js +7 -0
- package/dist/coordinator/register.d.ts +2 -0
- package/dist/coordinator/register.js +11 -0
- package/dist/coordinator/types.d.ts +24 -0
- package/dist/coordinator/types.js +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/react/constants.d.ts +3 -0
- package/dist/react/constants.js +8 -0
- package/dist/react/index.d.ts +3 -0
- package/dist/react/index.js +1 -0
- package/dist/react/types.d.ts +12 -0
- package/dist/react/types.js +1 -0
- package/dist/react/use-applied-options.d.ts +3 -0
- package/dist/react/use-applied-options.js +11 -0
- package/dist/react/use-axis-instance.d.ts +3 -0
- package/dist/react/use-axis-instance.js +19 -0
- package/dist/react/use-axis.d.ts +11 -0
- package/dist/react/use-axis.js +14 -0
- package/dist/react/use-isomorphic-layout-effect.d.ts +2 -0
- package/dist/react/use-isomorphic-layout-effect.js +2 -0
- package/dist/react/use-selected-state.d.ts +4 -0
- package/dist/react/use-selected-state.js +14 -0
- package/dist/react/use-stable-api.d.ts +4 -0
- package/dist/react/use-stable-api.js +7 -0
- package/dist/utils/adjacent-snap.d.ts +1 -0
- package/dist/utils/adjacent-snap.js +8 -0
- package/dist/utils/cap-velocity.d.ts +2 -0
- package/dist/utils/cap-velocity.js +6 -0
- package/dist/utils/clamp.d.ts +1 -0
- package/dist/utils/clamp.js +1 -0
- package/dist/utils/composed-path-matches.d.ts +1 -0
- package/dist/utils/composed-path-matches.js +9 -0
- package/dist/utils/constants.d.ts +9 -0
- package/dist/utils/constants.js +19 -0
- package/dist/utils/css-length-to-px.d.ts +1 -0
- package/dist/utils/css-length-to-px.js +6 -0
- package/dist/utils/dominant-axis.d.ts +2 -0
- package/dist/utils/dominant-axis.js +3 -0
- package/dist/utils/ease-out-cubic.d.ts +1 -0
- package/dist/utils/ease-out-cubic.js +1 -0
- package/dist/utils/glide-duration.d.ts +1 -0
- package/dist/utils/glide-duration.js +9 -0
- package/dist/utils/index.d.ts +28 -0
- package/dist/utils/index.js +27 -0
- package/dist/utils/is-inside-editable.d.ts +1 -0
- package/dist/utils/is-inside-editable.js +2 -0
- package/dist/utils/is-on-scrollbar.d.ts +1 -0
- package/dist/utils/is-on-scrollbar.js +6 -0
- package/dist/utils/is-rtl.d.ts +1 -0
- package/dist/utils/is-rtl.js +1 -0
- package/dist/utils/is-scroll-key.d.ts +1 -0
- package/dist/utils/is-scroll-key.js +7 -0
- package/dist/utils/max-scroll.d.ts +2 -0
- package/dist/utils/max-scroll.js +4 -0
- package/dist/utils/measure-snaps.d.ts +1 -0
- package/dist/utils/measure-snaps.js +31 -0
- package/dist/utils/momentum-distance.d.ts +1 -0
- package/dist/utils/momentum-distance.js +4 -0
- package/dist/utils/momentum-step.d.ts +4 -0
- package/dist/utils/momentum-step.js +4 -0
- package/dist/utils/nearest-snap.d.ts +1 -0
- package/dist/utils/nearest-snap.js +12 -0
- package/dist/utils/parse-snap-align.d.ts +5 -0
- package/dist/utils/parse-snap-align.js +5 -0
- package/dist/utils/parse-snap-type.d.ts +4 -0
- package/dist/utils/parse-snap-type.js +7 -0
- package/dist/utils/pointer-sample.d.ts +2 -0
- package/dist/utils/pointer-sample.js +5 -0
- package/dist/utils/prefers-reduced-motion.d.ts +1 -0
- package/dist/utils/prefers-reduced-motion.js +1 -0
- package/dist/utils/read-scroll.d.ts +2 -0
- package/dist/utils/read-scroll.js +6 -0
- package/dist/utils/snap-items.d.ts +2 -0
- package/dist/utils/snap-items.js +18 -0
- package/dist/utils/snap-positions.d.ts +2 -0
- package/dist/utils/snap-positions.js +28 -0
- package/dist/utils/supported-user-select.d.ts +1 -0
- package/dist/utils/supported-user-select.js +1 -0
- package/dist/utils/to-logical.d.ts +1 -0
- package/dist/utils/to-logical.js +3 -0
- package/dist/utils/to-physical.d.ts +1 -0
- package/dist/utils/to-physical.js +3 -0
- package/dist/utils/try-capture-pointer.d.ts +1 -0
- package/dist/utils/try-capture-pointer.js +6 -0
- package/dist/utils/try-release-pointer.d.ts +1 -0
- package/dist/utils/try-release-pointer.js +7 -0
- package/dist/utils/types.d.ts +22 -0
- package/dist/utils/types.js +1 -0
- package/dist/utils/velocity-from-samples.d.ts +2 -0
- package/dist/utils/velocity-from-samples.js +21 -0
- package/dist/utils/wheel-delta-y-px.d.ts +1 -0
- package/dist/utils/wheel-delta-y-px.js +5 -0
- package/dist/utils/write-scroll.d.ts +2 -0
- package/dist/utils/write-scroll.js +7 -0
- package/package.json +84 -5
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { composedPathMatches, isScrollKey, pointerSample, VELOCITY_WINDOW, velocityFromSamples, } from "../utils/index.js";
|
|
2
|
+
import { CLICK_SUPPRESS_MS, coordinators, DRAG_THRESHOLD, NO_DRAG } from "./constants.js";
|
|
3
|
+
import { pickOwner } from "./pick-owner.js";
|
|
4
|
+
export function createCoordinator(win) {
|
|
5
|
+
const clients = new Map();
|
|
6
|
+
let session = null;
|
|
7
|
+
let suppressClick = false;
|
|
8
|
+
let suppressTimer = 0;
|
|
9
|
+
const capture = { capture: true };
|
|
10
|
+
const passiveCapture = { capture: true, passive: true };
|
|
11
|
+
const stillRegistered = (client) => clients.get(client.el) === client;
|
|
12
|
+
const forgetSuppressedClick = () => {
|
|
13
|
+
suppressClick = false;
|
|
14
|
+
win.clearTimeout(suppressTimer);
|
|
15
|
+
};
|
|
16
|
+
const detach = () => {
|
|
17
|
+
win.removeEventListener("pointermove", onPointerMove);
|
|
18
|
+
win.removeEventListener("pointerup", onPointerUp);
|
|
19
|
+
win.removeEventListener("pointercancel", onPointerCancel);
|
|
20
|
+
win.removeEventListener("wheel", releaseHoldsOnUserScroll, passiveCapture);
|
|
21
|
+
win.removeEventListener("keydown", releaseHoldsOnUserScroll, capture);
|
|
22
|
+
win.removeEventListener("contextmenu", cancelPress, capture);
|
|
23
|
+
win.removeEventListener("blur", cancelPress);
|
|
24
|
+
win.document.removeEventListener("visibilitychange", cancelPressWhenHidden);
|
|
25
|
+
session = null;
|
|
26
|
+
};
|
|
27
|
+
const endSession = (ended, pointerVelocity) => {
|
|
28
|
+
detach();
|
|
29
|
+
if (ended.owner && stillRegistered(ended.owner))
|
|
30
|
+
ended.owner.end(pointerVelocity);
|
|
31
|
+
for (const client of ended.chain) {
|
|
32
|
+
if (client !== ended.owner && stillRegistered(client))
|
|
33
|
+
client.pass();
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const cancelPress = () => {
|
|
37
|
+
if (session)
|
|
38
|
+
endSession(session, null);
|
|
39
|
+
};
|
|
40
|
+
const cancelPressWhenHidden = () => {
|
|
41
|
+
if (win.document.visibilityState === "hidden")
|
|
42
|
+
cancelPress();
|
|
43
|
+
};
|
|
44
|
+
const releaseHoldsOnUserScroll = (event) => {
|
|
45
|
+
if (event.type === "keydown" && !isScrollKey(event))
|
|
46
|
+
return;
|
|
47
|
+
if (session)
|
|
48
|
+
for (const client of session.chain)
|
|
49
|
+
client.interrupt();
|
|
50
|
+
};
|
|
51
|
+
const attach = () => {
|
|
52
|
+
win.addEventListener("pointermove", onPointerMove);
|
|
53
|
+
win.addEventListener("pointerup", onPointerUp);
|
|
54
|
+
win.addEventListener("pointercancel", onPointerCancel);
|
|
55
|
+
win.addEventListener("wheel", releaseHoldsOnUserScroll, passiveCapture);
|
|
56
|
+
win.addEventListener("keydown", releaseHoldsOnUserScroll, capture);
|
|
57
|
+
win.addEventListener("contextmenu", cancelPress, capture);
|
|
58
|
+
win.addEventListener("blur", cancelPress);
|
|
59
|
+
win.document.addEventListener("visibilitychange", cancelPressWhenHidden);
|
|
60
|
+
};
|
|
61
|
+
const onPointerDown = (event) => {
|
|
62
|
+
forgetSuppressedClick();
|
|
63
|
+
if (session)
|
|
64
|
+
return;
|
|
65
|
+
const path = event.composedPath();
|
|
66
|
+
const chain = [];
|
|
67
|
+
for (const node of path) {
|
|
68
|
+
const client = clients.get(node);
|
|
69
|
+
if (client)
|
|
70
|
+
chain.push(client);
|
|
71
|
+
}
|
|
72
|
+
if (chain.length === 0)
|
|
73
|
+
return;
|
|
74
|
+
const draggable = event.pointerType === "mouse" &&
|
|
75
|
+
event.button === 0 &&
|
|
76
|
+
!composedPathMatches(path, NO_DRAG, chain.at(-1).el) &&
|
|
77
|
+
!chain.some((client) => client.onScrollbar(event));
|
|
78
|
+
if (!draggable) {
|
|
79
|
+
for (const client of chain)
|
|
80
|
+
client.interrupt();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
for (const client of chain)
|
|
84
|
+
client.press();
|
|
85
|
+
const pressed = chain.filter(stillRegistered);
|
|
86
|
+
if (pressed.length === 0)
|
|
87
|
+
return;
|
|
88
|
+
session = {
|
|
89
|
+
pointerId: event.pointerId,
|
|
90
|
+
start: { x: event.clientX, y: event.clientY },
|
|
91
|
+
chain: pressed,
|
|
92
|
+
owner: null,
|
|
93
|
+
samples: [pointerSample(event)],
|
|
94
|
+
};
|
|
95
|
+
attach();
|
|
96
|
+
};
|
|
97
|
+
const handOver = (current, delta) => {
|
|
98
|
+
const owner = pickOwner(current.chain, delta);
|
|
99
|
+
if (!owner) {
|
|
100
|
+
endSession(current, null);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
current.owner = owner;
|
|
104
|
+
for (const client of current.chain)
|
|
105
|
+
if (client !== owner)
|
|
106
|
+
client.yield();
|
|
107
|
+
owner.start(current.pointerId);
|
|
108
|
+
};
|
|
109
|
+
const onPointerMove = (event) => {
|
|
110
|
+
const current = session;
|
|
111
|
+
if (!current || event.pointerId !== current.pointerId)
|
|
112
|
+
return;
|
|
113
|
+
const releasedOutsideWindow = !(event.buttons & 1);
|
|
114
|
+
if (releasedOutsideWindow) {
|
|
115
|
+
cancelPress();
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const { samples } = current;
|
|
119
|
+
samples.push(pointerSample(event));
|
|
120
|
+
while (samples.length > 2 && event.timeStamp - samples[0].t > VELOCITY_WINDOW * 2)
|
|
121
|
+
samples.shift();
|
|
122
|
+
const delta = { x: event.clientX - current.start.x, y: event.clientY - current.start.y };
|
|
123
|
+
if (!current.owner) {
|
|
124
|
+
if (Math.hypot(delta.x, delta.y) <= DRAG_THRESHOLD)
|
|
125
|
+
return;
|
|
126
|
+
handOver(current, delta);
|
|
127
|
+
}
|
|
128
|
+
const endedByListener = session !== current;
|
|
129
|
+
if (endedByListener || !current.owner)
|
|
130
|
+
return;
|
|
131
|
+
current.owner.move(delta);
|
|
132
|
+
};
|
|
133
|
+
const onPointerUp = (event) => {
|
|
134
|
+
const current = session;
|
|
135
|
+
if (!current || event.pointerId !== current.pointerId)
|
|
136
|
+
return;
|
|
137
|
+
current.samples.push(pointerSample(event));
|
|
138
|
+
if (current.owner) {
|
|
139
|
+
suppressClick = true;
|
|
140
|
+
win.clearTimeout(suppressTimer);
|
|
141
|
+
suppressTimer = win.setTimeout(() => (suppressClick = false), CLICK_SUPPRESS_MS);
|
|
142
|
+
}
|
|
143
|
+
endSession(current, current.owner ? velocityFromSamples(current.samples) : null);
|
|
144
|
+
};
|
|
145
|
+
const onPointerCancel = (event) => {
|
|
146
|
+
if (session && event.pointerId === session.pointerId)
|
|
147
|
+
cancelPress();
|
|
148
|
+
};
|
|
149
|
+
const swallowClickAfterDrag = (event) => {
|
|
150
|
+
if (!suppressClick)
|
|
151
|
+
return;
|
|
152
|
+
forgetSuppressedClick();
|
|
153
|
+
event.preventDefault();
|
|
154
|
+
event.stopPropagation();
|
|
155
|
+
};
|
|
156
|
+
const preventNativeDragAndDrop = (event) => {
|
|
157
|
+
if (session)
|
|
158
|
+
event.preventDefault();
|
|
159
|
+
};
|
|
160
|
+
const remove = (client) => {
|
|
161
|
+
if (clients.get(client.el) !== client)
|
|
162
|
+
return;
|
|
163
|
+
clients.delete(client.el);
|
|
164
|
+
if (session) {
|
|
165
|
+
const current = session;
|
|
166
|
+
current.chain = current.chain.filter((other) => other !== client);
|
|
167
|
+
if (current.owner === client || current.chain.length === 0) {
|
|
168
|
+
current.owner = null;
|
|
169
|
+
endSession(current, null);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (clients.size === 0) {
|
|
173
|
+
win.removeEventListener("pointerdown", onPointerDown, capture);
|
|
174
|
+
win.removeEventListener("click", swallowClickAfterDrag, capture);
|
|
175
|
+
win.removeEventListener("dragstart", preventNativeDragAndDrop, capture);
|
|
176
|
+
forgetSuppressedClick();
|
|
177
|
+
coordinators.delete(win);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
return {
|
|
181
|
+
add(client) {
|
|
182
|
+
if (clients.size === 0) {
|
|
183
|
+
win.addEventListener("pointerdown", onPointerDown, capture);
|
|
184
|
+
win.addEventListener("click", swallowClickAfterDrag, capture);
|
|
185
|
+
win.addEventListener("dragstart", preventNativeDragAndDrop, capture);
|
|
186
|
+
}
|
|
187
|
+
clients.set(client.el, client);
|
|
188
|
+
return () => remove(client);
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { register } from "./register.js";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { dominantAxis } from "../utils/index.js";
|
|
2
|
+
export function pickOwner(innermostFirst, delta) {
|
|
3
|
+
const axis = dominantAxis(delta);
|
|
4
|
+
const dragsAlongAxis = innermostFirst.filter((client) => client.accepts(axis));
|
|
5
|
+
const canStillScroll = dragsAlongAxis.find((client) => client.canScroll(delta));
|
|
6
|
+
return canStillScroll ?? dragsAlongAxis[0] ?? null;
|
|
7
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { coordinators } from "./constants.js";
|
|
2
|
+
import { createCoordinator } from "./create-coordinator.js";
|
|
3
|
+
export function register(client) {
|
|
4
|
+
const win = client.el.ownerDocument.defaultView ?? window;
|
|
5
|
+
let coordinator = coordinators.get(win);
|
|
6
|
+
if (!coordinator) {
|
|
7
|
+
coordinator = createCoordinator(win);
|
|
8
|
+
coordinators.set(win, coordinator);
|
|
9
|
+
}
|
|
10
|
+
return coordinator.add(client);
|
|
11
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Sample, Vector } from "../utils/index.js";
|
|
2
|
+
export interface DragClient {
|
|
3
|
+
readonly el: HTMLElement;
|
|
4
|
+
onScrollbar(event: PointerEvent): boolean;
|
|
5
|
+
accepts(axis: "x" | "y"): boolean;
|
|
6
|
+
canScroll(delta: Vector): boolean;
|
|
7
|
+
press(): void;
|
|
8
|
+
yield(): void;
|
|
9
|
+
start(pointerId: number): void;
|
|
10
|
+
move(delta: Vector): void;
|
|
11
|
+
pass(): void;
|
|
12
|
+
end(pointerVelocity: Vector | null): void;
|
|
13
|
+
interrupt(): void;
|
|
14
|
+
}
|
|
15
|
+
export interface Session {
|
|
16
|
+
pointerId: number;
|
|
17
|
+
start: Vector;
|
|
18
|
+
chain: DragClient[];
|
|
19
|
+
owner: DragClient | null;
|
|
20
|
+
samples: Sample[];
|
|
21
|
+
}
|
|
22
|
+
export interface Coordinator {
|
|
23
|
+
add(client: DragClient): () => void;
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createAxis } from "./axis/index.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useAxis } from "./use-axis.js";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AxisInstance, AxisScrollState } from "../axis/index.js";
|
|
2
|
+
/** Imperative scroll controls returned by {@link useAxis}. Safe to call before the element mounts (no-op). */
|
|
3
|
+
export type AxisApi = Pick<AxisInstance, "scrollPrev" | "scrollNext" | "scrollTo" | "refresh">;
|
|
4
|
+
/** What {@link useAxis} returns. */
|
|
5
|
+
export interface UseAxisResult<T extends HTMLElement = HTMLElement, S = AxisScrollState> {
|
|
6
|
+
/** Callback ref for the scroll container. Stable. */
|
|
7
|
+
ref: (element: T | null) => void;
|
|
8
|
+
/** The scroll state of both axes, or what the selector picked from it. Re-renders only when it changes. */
|
|
9
|
+
state: S;
|
|
10
|
+
/** Stable scroll controls. */
|
|
11
|
+
api: AxisApi;
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { useRef } from "react";
|
|
2
|
+
import { useIsomorphicLayoutEffect } from "./use-isomorphic-layout-effect.js";
|
|
3
|
+
export function useAppliedOptions(options, instanceRef) {
|
|
4
|
+
const optionsRef = useRef(options);
|
|
5
|
+
const { axis, momentum, wheel, cursor } = options;
|
|
6
|
+
useIsomorphicLayoutEffect(() => {
|
|
7
|
+
optionsRef.current = { axis, momentum, wheel, cursor };
|
|
8
|
+
instanceRef.current?.setOptions(optionsRef.current);
|
|
9
|
+
}, [axis, momentum, wheel, cursor]);
|
|
10
|
+
return optionsRef;
|
|
11
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { type RefObject } from "react";
|
|
2
|
+
import { type AxisInstance, type AxisOptions } from "../axis/index.js";
|
|
3
|
+
export declare function useAxisInstance(element: HTMLElement | null, optionsRef: RefObject<AxisOptions>, instanceRef: RefObject<AxisInstance | null>): AxisInstance | null;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { createAxis } from "../axis/index.js";
|
|
3
|
+
import { useIsomorphicLayoutEffect } from "./use-isomorphic-layout-effect.js";
|
|
4
|
+
export function useAxisInstance(element, optionsRef, instanceRef) {
|
|
5
|
+
const [instance, setInstance] = useState(null);
|
|
6
|
+
useIsomorphicLayoutEffect(() => {
|
|
7
|
+
if (!element)
|
|
8
|
+
return;
|
|
9
|
+
const created = createAxis(element, optionsRef.current);
|
|
10
|
+
instanceRef.current = created;
|
|
11
|
+
setInstance(created);
|
|
12
|
+
return () => {
|
|
13
|
+
created.destroy();
|
|
14
|
+
instanceRef.current = null;
|
|
15
|
+
setInstance(null);
|
|
16
|
+
};
|
|
17
|
+
}, [element]);
|
|
18
|
+
return instance;
|
|
19
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AxisOptions, AxisScrollState } from "../axis/index.js";
|
|
2
|
+
import type { UseAxisResult } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Binds axis to an element through a callback ref. The instance is created when the element mounts and
|
|
5
|
+
* destroyed when it unmounts or changes. Option changes are applied without re-creating the instance.
|
|
6
|
+
*
|
|
7
|
+
* With a `selector`, `state` is `selector(state)` and the component re-renders only when that result changes
|
|
8
|
+
* (compared with `Object.is`).
|
|
9
|
+
*/
|
|
10
|
+
export declare function useAxis<T extends HTMLElement = HTMLElement>(options?: AxisOptions): UseAxisResult<T>;
|
|
11
|
+
export declare function useAxis<S>(options: AxisOptions | undefined, selector: (state: AxisScrollState) => S): UseAxisResult<HTMLElement, S>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useRef, useState } from "react";
|
|
2
|
+
import { useAppliedOptions } from "./use-applied-options.js";
|
|
3
|
+
import { useAxisInstance } from "./use-axis-instance.js";
|
|
4
|
+
import { useSelectedState } from "./use-selected-state.js";
|
|
5
|
+
import { useStableApi } from "./use-stable-api.js";
|
|
6
|
+
export function useAxis(options = {}, selector) {
|
|
7
|
+
const [element, setElement] = useState(null);
|
|
8
|
+
const instanceRef = useRef(null);
|
|
9
|
+
const optionsRef = useAppliedOptions(options, instanceRef);
|
|
10
|
+
const instance = useAxisInstance(element, optionsRef, instanceRef);
|
|
11
|
+
const state = useSelectedState(instance, selector);
|
|
12
|
+
const api = useStableApi(instanceRef);
|
|
13
|
+
return { ref: setElement, state, api };
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useCallback, useRef, useSyncExternalStore } from "react";
|
|
2
|
+
import { EMPTY_STATE, NOOP } from "./constants.js";
|
|
3
|
+
export function useSelectedState(instance, selector) {
|
|
4
|
+
const subscribe = useCallback((listener) => instance?.subscribe(listener) ?? NOOP, [instance]);
|
|
5
|
+
const snapshotMemo = useRef(null);
|
|
6
|
+
const getSnapshot = useCallback(() => {
|
|
7
|
+
const current = instance?.getState() ?? EMPTY_STATE;
|
|
8
|
+
if (snapshotMemo.current?.state !== current || snapshotMemo.current.selector !== selector) {
|
|
9
|
+
snapshotMemo.current = { state: current, selector, selected: selector ? selector(current) : current };
|
|
10
|
+
}
|
|
11
|
+
return snapshotMemo.current.selected;
|
|
12
|
+
}, [instance, selector]);
|
|
13
|
+
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
14
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
export const useStableApi = (instanceRef) => useMemo(() => ({
|
|
3
|
+
scrollPrev: (target) => instanceRef.current?.scrollPrev(target),
|
|
4
|
+
scrollNext: (target) => instanceRef.current?.scrollNext(target),
|
|
5
|
+
scrollTo: (position, scrollOptions) => instanceRef.current?.scrollTo(position, scrollOptions),
|
|
6
|
+
refresh: () => instanceRef.current?.refresh(),
|
|
7
|
+
}), [instanceRef]);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function adjacentSnap(positions: readonly number[], current: number, direction: 1 | -1): number | undefined;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export function adjacentSnap(positions, current, direction) {
|
|
2
|
+
if (direction === 1)
|
|
3
|
+
return positions.find((position) => position > current + 1);
|
|
4
|
+
for (let i = positions.length - 1; i >= 0; i--)
|
|
5
|
+
if (positions[i] < current - 1)
|
|
6
|
+
return positions[i];
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { DECAY, FRAME_MS } from "./constants.js";
|
|
2
|
+
/** Scale a release velocity (px/ms) down so its momentum glide travels at most `maxGlide` px. */
|
|
3
|
+
export function capVelocity(velocity, maxGlide) {
|
|
4
|
+
const limit = (maxGlide * (1 - DECAY)) / FRAME_MS;
|
|
5
|
+
return Math.sign(velocity) * Math.min(Math.abs(velocity), limit);
|
|
6
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const clamp: (value: number, min: number, max: number) => number;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function composedPathMatches(path: readonly EventTarget[], selector: string, boundary: EventTarget): boolean;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const VELOCITY_WINDOW = 100;
|
|
2
|
+
export declare const MAX_VELOCITY = 5;
|
|
3
|
+
export declare const DECAY = 0.95;
|
|
4
|
+
export declare const FRAME_MS: number;
|
|
5
|
+
export declare const MIN_VELOCITY = 0.02;
|
|
6
|
+
export declare const EDITABLE = "input, textarea, select, [contenteditable]:not([contenteditable='false'])";
|
|
7
|
+
export declare const SCROLL_KEYS: Set<string>;
|
|
8
|
+
export declare const EASE_OUT_CUBIC_START_SPEED = 3;
|
|
9
|
+
export declare const LINE_HEIGHT = 16;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const VELOCITY_WINDOW = 100;
|
|
2
|
+
export const MAX_VELOCITY = 5;
|
|
3
|
+
export const DECAY = 0.95;
|
|
4
|
+
export const FRAME_MS = 1000 / 60;
|
|
5
|
+
export const MIN_VELOCITY = 0.02;
|
|
6
|
+
export const EDITABLE = "input, textarea, select, [contenteditable]:not([contenteditable='false'])";
|
|
7
|
+
export const SCROLL_KEYS = new Set([
|
|
8
|
+
"ArrowUp",
|
|
9
|
+
"ArrowDown",
|
|
10
|
+
"ArrowLeft",
|
|
11
|
+
"ArrowRight",
|
|
12
|
+
"PageUp",
|
|
13
|
+
"PageDown",
|
|
14
|
+
"Home",
|
|
15
|
+
"End",
|
|
16
|
+
" ",
|
|
17
|
+
]);
|
|
18
|
+
export const EASE_OUT_CUBIC_START_SPEED = 3;
|
|
19
|
+
export const LINE_HEIGHT = 16;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function cssLengthToPx(value: string, percentBasis: number): number;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const easeOutCubic: (t: number) => number;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const easeOutCubic = (t) => 1 - (1 - t) ** 3;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function glideDuration(distance: number, velocity?: number): number;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { clamp } from "./clamp.js";
|
|
2
|
+
import { EASE_OUT_CUBIC_START_SPEED } from "./constants.js";
|
|
3
|
+
export function glideDuration(distance, velocity = 0) {
|
|
4
|
+
const d = Math.abs(distance);
|
|
5
|
+
const continuesRelease = velocity !== 0 && Math.sign(velocity) === Math.sign(distance);
|
|
6
|
+
if (continuesRelease)
|
|
7
|
+
return clamp((EASE_OUT_CUBIC_START_SPEED * d) / Math.abs(velocity), 180, 900);
|
|
8
|
+
return clamp(200 + d * 0.5, 200, 600);
|
|
9
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export { adjacentSnap } from "./adjacent-snap.js";
|
|
2
|
+
export { capVelocity } from "./cap-velocity.js";
|
|
3
|
+
export { clamp } from "./clamp.js";
|
|
4
|
+
export { composedPathMatches } from "./composed-path-matches.js";
|
|
5
|
+
export { dominantAxis } from "./dominant-axis.js";
|
|
6
|
+
export { easeOutCubic } from "./ease-out-cubic.js";
|
|
7
|
+
export { glideDuration } from "./glide-duration.js";
|
|
8
|
+
export { isOnScrollbar } from "./is-on-scrollbar.js";
|
|
9
|
+
export { isRtl } from "./is-rtl.js";
|
|
10
|
+
export { isScrollKey } from "./is-scroll-key.js";
|
|
11
|
+
export { maxScroll } from "./max-scroll.js";
|
|
12
|
+
export { measureSnaps } from "./measure-snaps.js";
|
|
13
|
+
export { momentumDistance } from "./momentum-distance.js";
|
|
14
|
+
export { momentumStep } from "./momentum-step.js";
|
|
15
|
+
export { nearestSnap } from "./nearest-snap.js";
|
|
16
|
+
export { parseSnapType } from "./parse-snap-type.js";
|
|
17
|
+
export { pointerSample } from "./pointer-sample.js";
|
|
18
|
+
export { prefersReducedMotion } from "./prefers-reduced-motion.js";
|
|
19
|
+
export { readScroll } from "./read-scroll.js";
|
|
20
|
+
export { supportedUserSelect } from "./supported-user-select.js";
|
|
21
|
+
export { toLogical } from "./to-logical.js";
|
|
22
|
+
export { tryCapturePointer } from "./try-capture-pointer.js";
|
|
23
|
+
export { tryReleasePointer } from "./try-release-pointer.js";
|
|
24
|
+
export { velocityFromSamples } from "./velocity-from-samples.js";
|
|
25
|
+
export { wheelDeltaYPx } from "./wheel-delta-y-px.js";
|
|
26
|
+
export { writeScroll } from "./write-scroll.js";
|
|
27
|
+
export { EDITABLE, MIN_VELOCITY, VELOCITY_WINDOW } from "./constants.js";
|
|
28
|
+
export type { Sample, Vector } from "./types.js";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export { adjacentSnap } from "./adjacent-snap.js";
|
|
2
|
+
export { capVelocity } from "./cap-velocity.js";
|
|
3
|
+
export { clamp } from "./clamp.js";
|
|
4
|
+
export { composedPathMatches } from "./composed-path-matches.js";
|
|
5
|
+
export { dominantAxis } from "./dominant-axis.js";
|
|
6
|
+
export { easeOutCubic } from "./ease-out-cubic.js";
|
|
7
|
+
export { glideDuration } from "./glide-duration.js";
|
|
8
|
+
export { isOnScrollbar } from "./is-on-scrollbar.js";
|
|
9
|
+
export { isRtl } from "./is-rtl.js";
|
|
10
|
+
export { isScrollKey } from "./is-scroll-key.js";
|
|
11
|
+
export { maxScroll } from "./max-scroll.js";
|
|
12
|
+
export { measureSnaps } from "./measure-snaps.js";
|
|
13
|
+
export { momentumDistance } from "./momentum-distance.js";
|
|
14
|
+
export { momentumStep } from "./momentum-step.js";
|
|
15
|
+
export { nearestSnap } from "./nearest-snap.js";
|
|
16
|
+
export { parseSnapType } from "./parse-snap-type.js";
|
|
17
|
+
export { pointerSample } from "./pointer-sample.js";
|
|
18
|
+
export { prefersReducedMotion } from "./prefers-reduced-motion.js";
|
|
19
|
+
export { readScroll } from "./read-scroll.js";
|
|
20
|
+
export { supportedUserSelect } from "./supported-user-select.js";
|
|
21
|
+
export { toLogical } from "./to-logical.js";
|
|
22
|
+
export { tryCapturePointer } from "./try-capture-pointer.js";
|
|
23
|
+
export { tryReleasePointer } from "./try-release-pointer.js";
|
|
24
|
+
export { velocityFromSamples } from "./velocity-from-samples.js";
|
|
25
|
+
export { wheelDeltaYPx } from "./wheel-delta-y-px.js";
|
|
26
|
+
export { writeScroll } from "./write-scroll.js";
|
|
27
|
+
export { EDITABLE, MIN_VELOCITY, VELOCITY_WINDOW } from "./constants.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const isInsideEditable: (target: EventTarget | null) => boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isOnScrollbar(el: Element, event: MouseEvent): boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const isRtl: (win: Window, el: Element) => boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const isRtl = (win, el) => win.getComputedStyle(el).direction === "rtl";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isScrollKey(event: KeyboardEvent): boolean;
|