@anyknown/axis 0.1.0 → 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.
Files changed (141) hide show
  1. package/README.md +1 -0
  2. package/dist/axis/constants.d.ts +12 -0
  3. package/dist/axis/constants.js +27 -0
  4. package/dist/axis/create-axis.d.ts +10 -0
  5. package/dist/axis/create-axis.js +212 -0
  6. package/dist/axis/create-drag-client.d.ts +6 -0
  7. package/dist/axis/create-drag-client.js +95 -0
  8. package/dist/axis/create-geometry.d.ts +2 -0
  9. package/dist/axis/create-geometry.js +11 -0
  10. package/dist/axis/create-hold.d.ts +2 -0
  11. package/dist/axis/create-hold.js +38 -0
  12. package/dist/axis/create-scroll-driver.d.ts +2 -0
  13. package/dist/axis/create-scroll-driver.js +139 -0
  14. package/dist/axis/create-snap-suspension.d.ts +2 -0
  15. package/dist/axis/create-snap-suspension.js +23 -0
  16. package/dist/axis/create-style-overrides.d.ts +2 -0
  17. package/dist/axis/create-style-overrides.js +35 -0
  18. package/dist/axis/create-wheel.d.ts +5 -0
  19. package/dist/axis/create-wheel.js +57 -0
  20. package/dist/axis/drags-along.d.ts +2 -0
  21. package/dist/axis/drags-along.js +1 -0
  22. package/dist/axis/index.d.ts +2 -0
  23. package/dist/axis/index.js +1 -0
  24. package/dist/axis/merge-options.d.ts +2 -0
  25. package/dist/axis/merge-options.js +9 -0
  26. package/dist/axis/next-axis-state.d.ts +2 -0
  27. package/dist/axis/next-axis-state.js +14 -0
  28. package/dist/axis/resolve-axis.d.ts +2 -0
  29. package/dist/axis/resolve-axis.js +8 -0
  30. package/dist/{axis.d.ts → axis/types.d.ts} +57 -9
  31. package/dist/axis/types.js +1 -0
  32. package/dist/coordinator/constants.d.ts +5 -0
  33. package/dist/coordinator/constants.js +5 -0
  34. package/dist/coordinator/create-coordinator.d.ts +2 -0
  35. package/dist/coordinator/create-coordinator.js +191 -0
  36. package/dist/coordinator/index.d.ts +2 -0
  37. package/dist/coordinator/index.js +1 -0
  38. package/dist/coordinator/pick-owner.d.ts +3 -0
  39. package/dist/coordinator/pick-owner.js +7 -0
  40. package/dist/coordinator/register.d.ts +2 -0
  41. package/dist/coordinator/register.js +11 -0
  42. package/dist/coordinator/types.d.ts +24 -0
  43. package/dist/coordinator/types.js +1 -0
  44. package/dist/index.d.ts +2 -2
  45. package/dist/index.js +1 -1
  46. package/dist/react/constants.d.ts +3 -0
  47. package/dist/react/constants.js +8 -0
  48. package/dist/react/index.d.ts +3 -0
  49. package/dist/react/index.js +1 -0
  50. package/dist/react/types.d.ts +12 -0
  51. package/dist/react/types.js +1 -0
  52. package/dist/react/use-applied-options.d.ts +3 -0
  53. package/dist/react/use-applied-options.js +11 -0
  54. package/dist/react/use-axis-instance.d.ts +3 -0
  55. package/dist/react/use-axis-instance.js +19 -0
  56. package/dist/react/use-axis.d.ts +11 -0
  57. package/dist/react/use-axis.js +14 -0
  58. package/dist/react/use-isomorphic-layout-effect.d.ts +2 -0
  59. package/dist/react/use-isomorphic-layout-effect.js +2 -0
  60. package/dist/react/use-selected-state.d.ts +4 -0
  61. package/dist/react/use-selected-state.js +14 -0
  62. package/dist/react/use-stable-api.d.ts +4 -0
  63. package/dist/react/use-stable-api.js +7 -0
  64. package/dist/utils/adjacent-snap.d.ts +1 -0
  65. package/dist/utils/adjacent-snap.js +8 -0
  66. package/dist/utils/cap-velocity.d.ts +2 -0
  67. package/dist/utils/cap-velocity.js +6 -0
  68. package/dist/utils/clamp.d.ts +1 -0
  69. package/dist/utils/clamp.js +1 -0
  70. package/dist/utils/composed-path-matches.d.ts +1 -0
  71. package/dist/utils/composed-path-matches.js +9 -0
  72. package/dist/utils/constants.d.ts +9 -0
  73. package/dist/utils/constants.js +19 -0
  74. package/dist/utils/css-length-to-px.d.ts +1 -0
  75. package/dist/utils/css-length-to-px.js +6 -0
  76. package/dist/utils/dominant-axis.d.ts +2 -0
  77. package/dist/utils/dominant-axis.js +3 -0
  78. package/dist/utils/ease-out-cubic.d.ts +1 -0
  79. package/dist/utils/ease-out-cubic.js +1 -0
  80. package/dist/utils/glide-duration.d.ts +1 -0
  81. package/dist/utils/glide-duration.js +9 -0
  82. package/dist/utils/index.d.ts +28 -0
  83. package/dist/utils/index.js +27 -0
  84. package/dist/utils/is-inside-editable.d.ts +1 -0
  85. package/dist/utils/is-inside-editable.js +2 -0
  86. package/dist/utils/is-on-scrollbar.d.ts +1 -0
  87. package/dist/utils/is-on-scrollbar.js +6 -0
  88. package/dist/utils/is-rtl.d.ts +1 -0
  89. package/dist/utils/is-rtl.js +1 -0
  90. package/dist/utils/is-scroll-key.d.ts +1 -0
  91. package/dist/utils/is-scroll-key.js +7 -0
  92. package/dist/utils/max-scroll.d.ts +2 -0
  93. package/dist/utils/max-scroll.js +4 -0
  94. package/dist/utils/measure-snaps.d.ts +1 -0
  95. package/dist/utils/measure-snaps.js +31 -0
  96. package/dist/utils/momentum-distance.d.ts +1 -0
  97. package/dist/utils/momentum-distance.js +4 -0
  98. package/dist/utils/momentum-step.d.ts +4 -0
  99. package/dist/utils/momentum-step.js +4 -0
  100. package/dist/utils/nearest-snap.d.ts +1 -0
  101. package/dist/utils/nearest-snap.js +12 -0
  102. package/dist/utils/parse-snap-align.d.ts +5 -0
  103. package/dist/utils/parse-snap-align.js +5 -0
  104. package/dist/utils/parse-snap-type.d.ts +4 -0
  105. package/dist/utils/parse-snap-type.js +7 -0
  106. package/dist/utils/pointer-sample.d.ts +2 -0
  107. package/dist/utils/pointer-sample.js +5 -0
  108. package/dist/utils/prefers-reduced-motion.d.ts +1 -0
  109. package/dist/utils/prefers-reduced-motion.js +1 -0
  110. package/dist/utils/read-scroll.d.ts +2 -0
  111. package/dist/utils/read-scroll.js +6 -0
  112. package/dist/utils/snap-items.d.ts +2 -0
  113. package/dist/utils/snap-items.js +18 -0
  114. package/dist/utils/snap-positions.d.ts +2 -0
  115. package/dist/utils/snap-positions.js +28 -0
  116. package/dist/utils/supported-user-select.d.ts +1 -0
  117. package/dist/utils/supported-user-select.js +1 -0
  118. package/dist/utils/to-logical.d.ts +1 -0
  119. package/dist/utils/to-logical.js +3 -0
  120. package/dist/utils/to-physical.d.ts +1 -0
  121. package/dist/utils/to-physical.js +3 -0
  122. package/dist/utils/try-capture-pointer.d.ts +1 -0
  123. package/dist/utils/try-capture-pointer.js +6 -0
  124. package/dist/utils/try-release-pointer.d.ts +1 -0
  125. package/dist/utils/try-release-pointer.js +7 -0
  126. package/dist/utils/types.d.ts +22 -0
  127. package/dist/utils/types.js +1 -0
  128. package/dist/utils/velocity-from-samples.d.ts +2 -0
  129. package/dist/utils/velocity-from-samples.js +21 -0
  130. package/dist/utils/wheel-delta-y-px.d.ts +1 -0
  131. package/dist/utils/wheel-delta-y-px.js +5 -0
  132. package/dist/utils/write-scroll.d.ts +2 -0
  133. package/dist/utils/write-scroll.js +7 -0
  134. package/package.json +6 -5
  135. package/dist/axis.js +0 -655
  136. package/dist/coordinator.d.ts +0 -36
  137. package/dist/coordinator.js +0 -247
  138. package/dist/math.d.ts +0 -90
  139. package/dist/math.js +0 -139
  140. package/dist/react.d.ts +0 -22
  141. package/dist/react.js +0 -49
package/README.md CHANGED
@@ -140,6 +140,7 @@ Do not pass type arguments together with a selector: `useAxis<HTMLDivElement>(un
140
140
  | ---------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------- |
141
141
  | `axis` | `"x"` | `"x"` drags horizontally and ignores vertical mouse movement, `"y"` drags vertically, `"both"` pans freely in 2D. |
142
142
  | `momentum` | `true` | Keep gliding after a fast mouse drag is released, along the release velocity vector. Never applied under `prefers-reduced-motion`. |
143
+ | `maxGlide` | `Infinity` | The farthest a momentum glide may travel after one release, in px. A faster fling is slowed to land within it, so one gesture never moves more than this. |
143
144
  | `wheel` | `false` | Translate vertical mouse-wheel scrolling into horizontal scrolling. **Only used with `axis: "x"`**; see below. |
144
145
  | `cursor` | `true` | Set `cursor: grab` while the container can be dragged and `grabbing` while dragging. |
145
146
 
@@ -0,0 +1,12 @@
1
+ import type { Vector } from "../utils/index.js";
2
+ import type { AxisOptions, AxisScrollState, AxisState } from "./types.js";
3
+ export declare const AXES: readonly ["x", "y"];
4
+ export declare const EDGE = 1;
5
+ export declare const WHEEL_SETTLE_MS = 150;
6
+ export declare const DRAGGING_ATTR = "data-axis-dragging";
7
+ export declare const ZERO: Vector;
8
+ export declare const MAX_FRAME_MS = 64;
9
+ export declare const PAGE_FRACTION = 0.9;
10
+ export declare const EMPTY_AXIS: AxisState;
11
+ export declare const INITIAL_STATE: AxisScrollState;
12
+ export declare const DEFAULTS: Required<AxisOptions>;
@@ -0,0 +1,27 @@
1
+ export const AXES = ["x", "y"];
2
+ export const EDGE = 1;
3
+ export const WHEEL_SETTLE_MS = 150;
4
+ export const DRAGGING_ATTR = "data-axis-dragging";
5
+ export const ZERO = { x: 0, y: 0 };
6
+ export const MAX_FRAME_MS = 64;
7
+ export const PAGE_FRACTION = 0.9;
8
+ export const EMPTY_AXIS = {
9
+ scroll: 0,
10
+ maxScroll: 0,
11
+ progress: 0,
12
+ canScrollPrev: false,
13
+ canScrollNext: false,
14
+ };
15
+ export const INITIAL_STATE = {
16
+ x: EMPTY_AXIS,
17
+ y: EMPTY_AXIS,
18
+ isDragging: false,
19
+ isMomentum: false,
20
+ };
21
+ export const DEFAULTS = {
22
+ axis: "x",
23
+ momentum: true,
24
+ maxGlide: Infinity,
25
+ wheel: false,
26
+ cursor: true,
27
+ };
@@ -0,0 +1,10 @@
1
+ import type { AxisInstance, AxisOptions } from "./types.js";
2
+ /**
3
+ * Enhances a scroll container (`overflow: auto`) with mouse drag-scrolling, momentum, snap-aware release,
4
+ * optional wheel translation and an observable scroll state.
5
+ *
6
+ * Native scrolling stays the source of truth: touch, trackpad, keyboard and scrollbar input are untouched,
7
+ * and only a primary-button mouse drag is taken over (by writing `scrollLeft` / `scrollTop`). Nested instances
8
+ * share the drag: the innermost instance that can scroll in the drag's direction takes it.
9
+ */
10
+ export declare function createAxis(el: HTMLElement, options?: AxisOptions): AxisInstance;
@@ -0,0 +1,212 @@
1
+ import { register } from "../coordinator/index.js";
2
+ import { adjacentSnap, clamp, isScrollKey, measureSnaps } from "../utils/index.js";
3
+ import { AXES, DEFAULTS, DRAGGING_ATTR, INITIAL_STATE, PAGE_FRACTION, ZERO } from "./constants.js";
4
+ import { createDragClient } from "./create-drag-client.js";
5
+ import { createGeometry } from "./create-geometry.js";
6
+ import { createHold } from "./create-hold.js";
7
+ import { createScrollDriver } from "./create-scroll-driver.js";
8
+ import { createSnapSuspension } from "./create-snap-suspension.js";
9
+ import { createStyleOverrides } from "./create-style-overrides.js";
10
+ import { createWheel } from "./create-wheel.js";
11
+ import { dragsAlong } from "./drags-along.js";
12
+ import { mergeOptions } from "./merge-options.js";
13
+ import { nextAxisState } from "./next-axis-state.js";
14
+ import { resolveAxis } from "./resolve-axis.js";
15
+ /**
16
+ * Enhances a scroll container (`overflow: auto`) with mouse drag-scrolling, momentum, snap-aware release,
17
+ * optional wheel translation and an observable scroll state.
18
+ *
19
+ * Native scrolling stays the source of truth: touch, trackpad, keyboard and scrollbar input are untouched,
20
+ * and only a primary-button mouse drag is taken over (by writing `scrollLeft` / `scrollTop`). Nested instances
21
+ * share the drag: the innermost instance that can scroll in the drag's direction takes it.
22
+ */
23
+ export function createAxis(el, options = {}) {
24
+ const win = el.ownerDocument.defaultView ?? window;
25
+ let opts = mergeOptions(DEFAULTS, options);
26
+ let motion = "idle";
27
+ let destroyed = false;
28
+ let state = INITIAL_STATE;
29
+ const listeners = new Set();
30
+ const hadStyleAttr = el.hasAttribute("style");
31
+ const mutationObserver = new MutationObserver((records) => onLayoutMutations(records));
32
+ const styles = createStyleOverrides(el, mutationObserver, (records) => onLayoutMutations(records));
33
+ const { override, restore } = styles;
34
+ const geometry = createGeometry(win, el);
35
+ const { rtl, scrollRange, position } = geometry;
36
+ let snapCache = null;
37
+ const invalidate = () => {
38
+ snapCache = null;
39
+ };
40
+ const snapPoints = () => (snapCache ??= measureSnaps(win, el));
41
+ const allows = (axis) => dragsAlong(opts.axis, axis);
42
+ const syncCursor = (max) => {
43
+ if (!opts.cursor)
44
+ restore("cursor");
45
+ else if (motion === "dragging")
46
+ override("cursor", "grabbing");
47
+ else if ((allows("x") && max.x > 0) || (allows("y") && max.y > 0))
48
+ override("cursor", "grab");
49
+ else
50
+ restore("cursor");
51
+ };
52
+ const update = () => {
53
+ if (destroyed)
54
+ return;
55
+ const max = scrollRange();
56
+ const current = position(rtl(), max);
57
+ const x = nextAxisState(current.x, max.x, state.x);
58
+ const y = nextAxisState(current.y, max.y, state.y);
59
+ const isDragging = motion === "dragging";
60
+ const isMomentum = motion === "momentum";
61
+ syncCursor(max);
62
+ const unchanged = x === state.x && y === state.y && isDragging === state.isDragging;
63
+ if (unchanged && isMomentum === state.isMomentum)
64
+ return;
65
+ state = { x, y, isDragging, isMomentum };
66
+ for (const listener of listeners)
67
+ listener(state);
68
+ };
69
+ const setMotion = (next) => {
70
+ motion = next;
71
+ update();
72
+ };
73
+ const core = {
74
+ ...geometry,
75
+ el,
76
+ win,
77
+ options: () => opts,
78
+ motion: () => motion,
79
+ setMotion,
80
+ isDestroyed: () => destroyed,
81
+ snapPoints,
82
+ };
83
+ const snap = createSnapSuspension(win, el, styles);
84
+ const hold = createHold(el);
85
+ const driver = createScrollDriver(core, snap, hold);
86
+ const { stop, interrupt, glideTo } = driver;
87
+ const drag = createDragClient(core, snap, hold, driver, styles);
88
+ const wheel = createWheel(core, snap, driver);
89
+ const resizeObserver = new ResizeObserver(() => {
90
+ invalidate();
91
+ update();
92
+ });
93
+ resizeObserver.observe(el);
94
+ for (const child of el.children)
95
+ resizeObserver.observe(child);
96
+ const observeCurrentChildren = (record) => {
97
+ for (const node of [...record.addedNodes, ...record.removedNodes]) {
98
+ if (node.nodeType !== 1)
99
+ continue;
100
+ if (node.parentElement === el)
101
+ resizeObserver.observe(node);
102
+ else
103
+ resizeObserver.unobserve(node);
104
+ }
105
+ };
106
+ const onLayoutMutations = (records) => {
107
+ if (destroyed)
108
+ return;
109
+ invalidate();
110
+ for (const record of records)
111
+ if (record.target === el)
112
+ observeCurrentChildren(record);
113
+ update();
114
+ };
115
+ mutationObserver.observe(el, {
116
+ childList: true,
117
+ attributes: true,
118
+ attributeFilter: ["class", "style", "dir"],
119
+ subtree: true,
120
+ });
121
+ const root = el.ownerDocument.documentElement;
122
+ const watchAncestorDirection = () => mutationObserver.observe(root, { attributes: true, attributeFilter: ["dir"], subtree: true });
123
+ if (el !== root)
124
+ watchAncestorDirection();
125
+ const onKeyDown = (event) => {
126
+ if (isScrollKey(event))
127
+ interrupt();
128
+ };
129
+ const holdAgainstSafariAutoscroll = () => {
130
+ hold.undoAutoscroll();
131
+ update();
132
+ };
133
+ const passive = { passive: true };
134
+ el.addEventListener("scroll", holdAgainstSafariAutoscroll, passive);
135
+ el.addEventListener("wheel", interrupt, passive);
136
+ el.addEventListener("touchstart", interrupt, passive);
137
+ el.addEventListener("keydown", onKeyDown);
138
+ const unregister = register(drag.client);
139
+ wheel.sync();
140
+ update();
141
+ const step = (axis, direction) => {
142
+ if (destroyed || motion === "dragging")
143
+ return;
144
+ const visible = axis === "x" ? el.clientWidth : el.clientHeight;
145
+ const max = scrollRange();
146
+ const heading = motion === "glide" ? driver.glideTarget()[axis] : position(rtl(), max)[axis];
147
+ const target = snap.snapAxes()[axis]
148
+ ? (adjacentSnap(snapPoints()[axis], heading, direction) ?? (direction === 1 ? max[axis] : 0))
149
+ : heading + direction * visible * PAGE_FRACTION;
150
+ glideTo({ [axis]: clamp(target, 0, max[axis]) }, ZERO, "glide");
151
+ };
152
+ return {
153
+ getState: () => state,
154
+ subscribe(listener) {
155
+ listeners.add(listener);
156
+ return () => {
157
+ listeners.delete(listener);
158
+ };
159
+ },
160
+ scrollPrev: (axis) => step(resolveAxis("scrollPrev", axis, opts.axis), -1),
161
+ scrollNext: (axis) => step(resolveAxis("scrollNext", axis, opts.axis), 1),
162
+ scrollTo(target, { animate = true } = {}) {
163
+ if (destroyed || motion === "dragging")
164
+ return;
165
+ const max = scrollRange();
166
+ const clamped = {};
167
+ for (const axis of AXES) {
168
+ const value = target[axis];
169
+ if (value !== undefined)
170
+ clamped[axis] = clamp(value, 0, max[axis]);
171
+ }
172
+ if (animate)
173
+ glideTo(clamped, ZERO, "glide");
174
+ else
175
+ driver.jump(clamped);
176
+ },
177
+ setOptions(patch) {
178
+ if (destroyed)
179
+ return;
180
+ opts = mergeOptions(opts, patch);
181
+ invalidate();
182
+ wheel.sync();
183
+ update();
184
+ },
185
+ refresh() {
186
+ if (destroyed)
187
+ return;
188
+ invalidate();
189
+ update();
190
+ },
191
+ destroy() {
192
+ if (destroyed)
193
+ return;
194
+ stop();
195
+ unregister();
196
+ drag.endDragIfActive();
197
+ resizeObserver.disconnect();
198
+ mutationObserver.disconnect();
199
+ el.removeEventListener("scroll", holdAgainstSafariAutoscroll);
200
+ el.removeEventListener("wheel", interrupt);
201
+ el.removeEventListener("touchstart", interrupt);
202
+ el.removeEventListener("keydown", onKeyDown);
203
+ wheel.remove();
204
+ el.removeAttribute(DRAGGING_ATTR);
205
+ styles.restoreAll();
206
+ if (!hadStyleAttr && !el.getAttribute("style"))
207
+ el.removeAttribute("style");
208
+ destroyed = true;
209
+ listeners.clear();
210
+ },
211
+ };
212
+ }
@@ -0,0 +1,6 @@
1
+ import type { DragClient } from "../coordinator/index.js";
2
+ import type { AxisCore, Hold, ScrollDriver, SnapSuspension, StyleOverrides } from "./types.js";
3
+ export declare function createDragClient(core: AxisCore, snap: SnapSuspension, hold: Hold, driver: ScrollDriver, styles: StyleOverrides): {
4
+ client: DragClient;
5
+ endDragIfActive: () => void;
6
+ };
@@ -0,0 +1,95 @@
1
+ import { isOnScrollbar, supportedUserSelect, toLogical, tryCapturePointer, tryReleasePointer, } from "../utils/index.js";
2
+ import { AXES, DRAGGING_ATTR, EDGE, ZERO } from "./constants.js";
3
+ import { dragsAlong } from "./drags-along.js";
4
+ export function createDragClient(core, snap, hold, driver, styles) {
5
+ const { el, win, rtl, scrollRange, position, options, motion, setMotion, isDestroyed } = core;
6
+ const { stop, fling } = driver;
7
+ const userSelect = supportedUserSelect(el.style);
8
+ let pointerId = -1;
9
+ let pressScroll = { x: 0, y: 0 };
10
+ const allows = (axis) => dragsAlong(options().axis, axis);
11
+ const endDrag = () => {
12
+ tryReleasePointer(el, pointerId);
13
+ pointerId = -1;
14
+ el.removeAttribute(DRAGGING_ATTR);
15
+ styles.restore(userSelect);
16
+ };
17
+ const freezeMomentum = () => {
18
+ stop();
19
+ setMotion("pressed");
20
+ };
21
+ const client = {
22
+ el,
23
+ onScrollbar: (event) => isOnScrollbar(el, event),
24
+ accepts(axis) {
25
+ if (!allows(axis))
26
+ return false;
27
+ const max = scrollRange();
28
+ return options().axis === "both" ? max.x > 0 || max.y > 0 : max[axis] > 0;
29
+ },
30
+ canScroll(delta) {
31
+ const isRtlNow = rtl();
32
+ const max = scrollRange();
33
+ const current = position(isRtlNow, max);
34
+ const againstPointer = { x: toLogical(-delta.x, isRtlNow), y: -delta.y };
35
+ return AXES.some((axis) => allows(axis) &&
36
+ ((againstPointer[axis] > 0 && current[axis] < max[axis] - EDGE) ||
37
+ (againstPointer[axis] < 0 && current[axis] > EDGE)));
38
+ },
39
+ press() {
40
+ if (motion() === "momentum")
41
+ freezeMomentum();
42
+ else if (motion() === "idle")
43
+ setMotion("pressed");
44
+ },
45
+ yield() {
46
+ hold.yieldDrag(motion() === "glide" || motion() === "wheel");
47
+ },
48
+ start(id) {
49
+ stop();
50
+ pressScroll = { x: el.scrollLeft, y: el.scrollTop };
51
+ hold.holdCurrent();
52
+ pointerId = id;
53
+ snap.suspend();
54
+ tryCapturePointer(el, id);
55
+ el.setAttribute(DRAGGING_ATTR, "");
56
+ styles.override(userSelect, "none");
57
+ win.getSelection()?.removeAllRanges();
58
+ setMotion("dragging");
59
+ },
60
+ move(delta) {
61
+ if (isDestroyed())
62
+ return;
63
+ if (allows("x")) {
64
+ el.scrollLeft = pressScroll.x - delta.x;
65
+ hold.follow("x");
66
+ }
67
+ if (allows("y")) {
68
+ el.scrollTop = pressScroll.y - delta.y;
69
+ hold.follow("y");
70
+ }
71
+ },
72
+ pass() {
73
+ hold.undoAutoscroll();
74
+ hold.reset();
75
+ const pressFroze = motion() === "pressed";
76
+ if (!pressFroze)
77
+ return;
78
+ if (snap.isSuspended())
79
+ fling(ZERO);
80
+ else
81
+ setMotion("idle");
82
+ },
83
+ end(pointerVelocity) {
84
+ endDrag();
85
+ hold.undoAutoscroll();
86
+ fling(pointerVelocity ? { x: toLogical(-pointerVelocity.x, rtl()), y: -pointerVelocity.y } : ZERO);
87
+ },
88
+ interrupt: driver.interrupt,
89
+ };
90
+ const endDragIfActive = () => {
91
+ if (pointerId !== -1)
92
+ endDrag();
93
+ };
94
+ return { client, endDragIfActive };
95
+ }
@@ -0,0 +1,2 @@
1
+ import type { Geometry } from "./types.js";
2
+ export declare function createGeometry(win: Window, el: HTMLElement): Geometry;
@@ -0,0 +1,11 @@
1
+ import { isRtl, maxScroll, readScroll, writeScroll } from "../utils/index.js";
2
+ export function createGeometry(win, el) {
3
+ const rtl = () => isRtl(win, el);
4
+ const scrollRange = () => maxScroll(el);
5
+ return {
6
+ rtl,
7
+ scrollRange,
8
+ position: (isRtlNow = rtl(), range = scrollRange()) => readScroll(el, isRtlNow, range),
9
+ writePosition: (position, isRtlNow = rtl()) => writeScroll(el, position, isRtlNow),
10
+ };
11
+ }
@@ -0,0 +1,2 @@
1
+ import type { Hold } from "./types.js";
2
+ export declare function createHold(el: HTMLElement): Hold;
@@ -0,0 +1,38 @@
1
+ export function createHold(el) {
2
+ let held = null;
3
+ let yielded = false;
4
+ const holdCurrent = () => {
5
+ held = { x: el.scrollLeft, y: el.scrollTop };
6
+ };
7
+ return {
8
+ holdCurrent,
9
+ release() {
10
+ held = null;
11
+ },
12
+ reset() {
13
+ held = null;
14
+ yielded = false;
15
+ },
16
+ yieldDrag(glideHoldsAtItsEnd) {
17
+ yielded = true;
18
+ if (!glideHoldsAtItsEnd)
19
+ holdCurrent();
20
+ },
21
+ holdIfYielded() {
22
+ if (yielded)
23
+ holdCurrent();
24
+ },
25
+ follow(axis) {
26
+ if (held)
27
+ held[axis] = axis === "x" ? el.scrollLeft : el.scrollTop;
28
+ },
29
+ undoAutoscroll() {
30
+ if (!held)
31
+ return;
32
+ if (Math.abs(el.scrollLeft - held.x) >= 1)
33
+ el.scrollLeft = held.x;
34
+ if (Math.abs(el.scrollTop - held.y) >= 1)
35
+ el.scrollTop = held.y;
36
+ },
37
+ };
38
+ }
@@ -0,0 +1,2 @@
1
+ import type { AxisCore, Hold, ScrollDriver, SnapSuspension } from "./types.js";
2
+ export declare function createScrollDriver(core: AxisCore, snap: SnapSuspension, hold: Hold): ScrollDriver;
@@ -0,0 +1,139 @@
1
+ import { clamp, easeOutCubic, glideDuration, MIN_VELOCITY, capVelocity, momentumDistance, momentumStep, nearestSnap, prefersReducedMotion, } from "../utils/index.js";
2
+ import { AXES, MAX_FRAME_MS, WHEEL_SETTLE_MS } from "./constants.js";
3
+ import { dragsAlong } from "./drags-along.js";
4
+ export function createScrollDriver(core, snap, hold) {
5
+ const { win, rtl, scrollRange, position, writePosition, options, motion, setMotion, isDestroyed } = core;
6
+ const { suspend, resume, snapAxes } = snap;
7
+ let frame = 0;
8
+ let wheelTimer = 0;
9
+ let glideTarget = { x: 0, y: 0 };
10
+ const stop = () => {
11
+ hold.release();
12
+ if (frame)
13
+ win.cancelAnimationFrame(frame);
14
+ frame = 0;
15
+ win.clearTimeout(wheelTimer);
16
+ wheelTimer = 0;
17
+ };
18
+ const settle = () => {
19
+ stop();
20
+ hold.holdIfYielded();
21
+ resume();
22
+ setMotion("idle");
23
+ };
24
+ const jump = (target) => {
25
+ stop();
26
+ suspend();
27
+ writePosition(target);
28
+ settle();
29
+ };
30
+ const glideTo = (target, velocity, kind) => {
31
+ stop();
32
+ const isRtlNow = rtl();
33
+ const from = position(isRtlNow);
34
+ const moving = AXES.filter((axis) => target[axis] !== undefined && Math.abs(target[axis] - from[axis]) >= 0.5);
35
+ if (moving.length === 0 || prefersReducedMotion(win)) {
36
+ jump(target);
37
+ return;
38
+ }
39
+ suspend();
40
+ glideTarget = { x: target.x ?? from.x, y: target.y ?? from.y };
41
+ const distance = { x: glideTarget.x - from.x, y: glideTarget.y - from.y };
42
+ const duration = Math.max(...moving.map((axis) => glideDuration(distance[axis], velocity[axis])));
43
+ const start = win.performance.now();
44
+ setMotion(kind);
45
+ if (isDestroyed())
46
+ return;
47
+ const tick = (now) => {
48
+ if (isDestroyed())
49
+ return;
50
+ const eased = easeOutCubic(clamp((now - start) / duration, 0, 1));
51
+ const next = {};
52
+ for (const axis of moving)
53
+ next[axis] = from[axis] + distance[axis] * eased;
54
+ writePosition(next, isRtlNow);
55
+ if (eased < 1)
56
+ frame = win.requestAnimationFrame(tick);
57
+ else
58
+ settle();
59
+ };
60
+ frame = win.requestAnimationFrame(tick);
61
+ };
62
+ const runMomentum = (velocity) => {
63
+ stop();
64
+ suspend();
65
+ setMotion("momentum");
66
+ if (isDestroyed())
67
+ return;
68
+ const isRtlNow = rtl();
69
+ const max = scrollRange();
70
+ const current = position(isRtlNow, max);
71
+ const speed = { ...velocity };
72
+ let last = win.performance.now();
73
+ const tick = (now) => {
74
+ if (isDestroyed())
75
+ return;
76
+ const dt = clamp(now - last, 0, MAX_FRAME_MS);
77
+ last = now;
78
+ const next = {};
79
+ for (const axis of AXES) {
80
+ if (speed[axis] === 0)
81
+ continue;
82
+ const advance = momentumStep(speed[axis], dt);
83
+ current[axis] = clamp(current[axis] + advance.distance, 0, max[axis]);
84
+ speed[axis] = current[axis] <= 0 || current[axis] >= max[axis] ? 0 : advance.velocity;
85
+ next[axis] = current[axis];
86
+ }
87
+ writePosition(next, isRtlNow);
88
+ if (Math.hypot(speed.x, speed.y) < MIN_VELOCITY)
89
+ settle();
90
+ else
91
+ frame = win.requestAnimationFrame(tick);
92
+ };
93
+ frame = win.requestAnimationFrame(tick);
94
+ };
95
+ const nearestSnapToLanding = (speed, snaps) => {
96
+ const max = scrollRange();
97
+ const from = position(rtl(), max);
98
+ const positions = core.snapPoints();
99
+ const target = {};
100
+ for (const axis of AXES) {
101
+ const landing = clamp(from[axis] + momentumDistance(speed[axis]), 0, max[axis]);
102
+ target[axis] = snaps[axis] ? nearestSnap(positions[axis], landing) : landing;
103
+ }
104
+ return target;
105
+ };
106
+ return {
107
+ stop,
108
+ settle,
109
+ jump,
110
+ glideTo,
111
+ interrupt() {
112
+ hold.reset();
113
+ if (motion() === "momentum" || motion() === "glide")
114
+ settle();
115
+ },
116
+ fling(scrollVelocity) {
117
+ const { momentum, axis, maxGlide } = options();
118
+ const allowed = momentum && !prefersReducedMotion(win);
119
+ const speed = {
120
+ x: allowed && dragsAlong(axis, "x") ? capVelocity(scrollVelocity.x, maxGlide) : 0,
121
+ y: allowed && dragsAlong(axis, "y") ? capVelocity(scrollVelocity.y, maxGlide) : 0,
122
+ };
123
+ const snaps = snapAxes();
124
+ if (snaps.x || snaps.y)
125
+ glideTo(nearestSnapToLanding(speed, snaps), speed, "momentum");
126
+ else if (Math.hypot(speed.x, speed.y) > MIN_VELOCITY)
127
+ runMomentum(speed);
128
+ else
129
+ settle();
130
+ },
131
+ afterWheelPause(settleWheel) {
132
+ wheelTimer = win.setTimeout(() => {
133
+ wheelTimer = 0;
134
+ settleWheel();
135
+ }, WHEEL_SETTLE_MS);
136
+ },
137
+ glideTarget: () => glideTarget,
138
+ };
139
+ }
@@ -0,0 +1,2 @@
1
+ import type { SnapSuspension, StyleOverrides } from "./types.js";
2
+ export declare function createSnapSuspension(win: Window, el: HTMLElement, styles: StyleOverrides): SnapSuspension;
@@ -0,0 +1,23 @@
1
+ import { parseSnapType } from "../utils/index.js";
2
+ export function createSnapSuspension(win, el, styles) {
3
+ const { override, restore } = styles;
4
+ let suspendedSnapType = null;
5
+ return {
6
+ suspend() {
7
+ if (suspendedSnapType !== null)
8
+ return;
9
+ suspendedSnapType = win.getComputedStyle(el).scrollSnapType;
10
+ override("scroll-snap-type", "none", true);
11
+ override("scroll-behavior", "auto", true);
12
+ },
13
+ resume() {
14
+ if (suspendedSnapType === null)
15
+ return;
16
+ suspendedSnapType = null;
17
+ restore("scroll-snap-type");
18
+ restore("scroll-behavior");
19
+ },
20
+ snapAxes: () => parseSnapType(suspendedSnapType ?? win.getComputedStyle(el).scrollSnapType),
21
+ isSuspended: () => suspendedSnapType !== null,
22
+ };
23
+ }
@@ -0,0 +1,2 @@
1
+ import type { StyleOverrides } from "./types.js";
2
+ export declare function createStyleOverrides(el: HTMLElement, observer: MutationObserver, onMutations: (records: MutationRecord[]) => void): StyleOverrides;
@@ -0,0 +1,35 @@
1
+ export function createStyleOverrides(el, observer, onMutations) {
2
+ const saved = new Map();
3
+ const writeUnobserved = (write) => {
4
+ const pendingRecords = observer.takeRecords();
5
+ write();
6
+ observer.takeRecords();
7
+ if (pendingRecords.length > 0)
8
+ queueMicrotask(() => onMutations(pendingRecords));
9
+ };
10
+ const override = (prop, value, important = false) => {
11
+ if (!saved.has(prop)) {
12
+ saved.set(prop, [el.style.getPropertyValue(prop), el.style.getPropertyPriority(prop)]);
13
+ }
14
+ else if (el.style.getPropertyValue(prop) === value)
15
+ return;
16
+ writeUnobserved(() => el.style.setProperty(prop, value, important ? "important" : ""));
17
+ };
18
+ const restore = (prop) => {
19
+ const original = saved.get(prop);
20
+ if (!original)
21
+ return;
22
+ saved.delete(prop);
23
+ writeUnobserved(() => {
24
+ if (original[0])
25
+ el.style.setProperty(prop, original[0], original[1]);
26
+ else
27
+ el.style.removeProperty(prop);
28
+ });
29
+ };
30
+ const restoreAll = () => {
31
+ for (const prop of saved.keys())
32
+ restore(prop);
33
+ };
34
+ return { override, restore, restoreAll };
35
+ }
@@ -0,0 +1,5 @@
1
+ import type { AxisCore, ScrollDriver, SnapSuspension } from "./types.js";
2
+ export declare function createWheel(core: AxisCore, snap: SnapSuspension, driver: ScrollDriver): {
3
+ sync: () => void;
4
+ remove: () => void;
5
+ };