@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,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,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,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,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,57 @@
|
|
|
1
|
+
import { adjacentSnap, clamp, nearestSnap, wheelDeltaYPx } from "../utils/index.js";
|
|
2
|
+
import { EDGE, ZERO } from "./constants.js";
|
|
3
|
+
export function createWheel(core, snap, driver) {
|
|
4
|
+
const { el, rtl, scrollRange, position, writePosition, options, motion, setMotion, isDestroyed } = core;
|
|
5
|
+
let direction = 1;
|
|
6
|
+
let attached = false;
|
|
7
|
+
const settleOnNextSnapInWheelDirection = () => {
|
|
8
|
+
if (isDestroyed())
|
|
9
|
+
return;
|
|
10
|
+
if (!snap.snapAxes().x) {
|
|
11
|
+
driver.settle();
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const current = position().x;
|
|
15
|
+
const positions = core.snapPoints().x;
|
|
16
|
+
const target = adjacentSnap(positions, current - 2 * direction, direction) ?? nearestSnap(positions, current);
|
|
17
|
+
driver.glideTo({ x: target }, ZERO, "glide");
|
|
18
|
+
};
|
|
19
|
+
const onWheel = (event) => {
|
|
20
|
+
if (options().axis !== "x" || event.ctrlKey || event.shiftKey)
|
|
21
|
+
return;
|
|
22
|
+
if (Math.abs(event.deltaY) <= Math.abs(event.deltaX))
|
|
23
|
+
return;
|
|
24
|
+
if (motion() === "pressed" || motion() === "dragging")
|
|
25
|
+
return;
|
|
26
|
+
const delta = wheelDeltaYPx(event, el.clientWidth);
|
|
27
|
+
const isRtlNow = rtl();
|
|
28
|
+
const max = scrollRange();
|
|
29
|
+
const current = position(isRtlNow, max).x;
|
|
30
|
+
const leavesPageScrollingAtEdge = delta > 0 ? current >= max.x - EDGE : current <= EDGE;
|
|
31
|
+
if (leavesPageScrollingAtEdge)
|
|
32
|
+
return;
|
|
33
|
+
event.preventDefault();
|
|
34
|
+
driver.stop();
|
|
35
|
+
snap.suspend();
|
|
36
|
+
direction = delta > 0 ? 1 : -1;
|
|
37
|
+
writePosition({ x: clamp(current + delta, 0, max.x) }, isRtlNow);
|
|
38
|
+
if (motion() !== "wheel")
|
|
39
|
+
setMotion("wheel");
|
|
40
|
+
if (isDestroyed())
|
|
41
|
+
return;
|
|
42
|
+
driver.afterWheelPause(settleOnNextSnapInWheelDirection);
|
|
43
|
+
};
|
|
44
|
+
const sync = () => {
|
|
45
|
+
const { wheel, axis } = options();
|
|
46
|
+
const wanted = wheel && axis === "x";
|
|
47
|
+
if (wanted === attached)
|
|
48
|
+
return;
|
|
49
|
+
attached = wanted;
|
|
50
|
+
if (wanted)
|
|
51
|
+
el.addEventListener("wheel", onWheel, { passive: false });
|
|
52
|
+
else
|
|
53
|
+
el.removeEventListener("wheel", onWheel);
|
|
54
|
+
};
|
|
55
|
+
const remove = () => el.removeEventListener("wheel", onWheel);
|
|
56
|
+
return { sync, remove };
|
|
57
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const dragsAlong = (mode, axis) => mode === "both" || mode === axis;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createAxis } from "./create-axis.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { EDGE } from "./constants.js";
|
|
2
|
+
export function nextAxisState(scroll, max, previous) {
|
|
3
|
+
const next = {
|
|
4
|
+
scroll,
|
|
5
|
+
maxScroll: max,
|
|
6
|
+
progress: max > 0 ? scroll / max : 0,
|
|
7
|
+
canScrollPrev: scroll > EDGE,
|
|
8
|
+
canScrollNext: scroll < max - EDGE,
|
|
9
|
+
};
|
|
10
|
+
for (const key of Object.keys(next))
|
|
11
|
+
if (next[key] !== previous[key])
|
|
12
|
+
return next;
|
|
13
|
+
return previous;
|
|
14
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type { Vector } from "../utils/index.js";
|
|
2
|
+
/** The direction(s) a mouse drag scrolls: horizontal, vertical, or free 2D panning. */
|
|
3
|
+
export type AxisMode = "x" | "y" | "both";
|
|
4
|
+
/** A single scroll axis. */
|
|
5
|
+
export type AxisName = "x" | "y";
|
|
6
|
+
/** Options for {@link createAxis}. Every option can be changed later with {@link AxisInstance.setOptions}. */
|
|
7
|
+
export interface AxisOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Which direction a mouse drag scrolls. `"x"` ignores vertical mouse movement, `"y"` ignores horizontal
|
|
10
|
+
* movement, and `"both"` pans freely in 2D.
|
|
11
|
+
* @default "x"
|
|
12
|
+
*/
|
|
13
|
+
axis?: AxisMode;
|
|
14
|
+
/**
|
|
15
|
+
* Keep gliding after a fast mouse drag is released. Never applied under `prefers-reduced-motion: reduce`.
|
|
16
|
+
* @default true
|
|
17
|
+
*/
|
|
18
|
+
momentum?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* The farthest a momentum glide may travel after one release, in px. A faster fling is slowed down to land
|
|
21
|
+
* within it, so one gesture never moves more than this.
|
|
22
|
+
* @default Infinity
|
|
23
|
+
*/
|
|
24
|
+
maxGlide?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Translate vertical mouse-wheel scrolling into horizontal scrolling. Only used with `axis: "x"`; at either
|
|
27
|
+
* end the wheel is left alone, so the page keeps scrolling.
|
|
28
|
+
* @default false
|
|
29
|
+
*/
|
|
30
|
+
wheel?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Set `cursor: grab` on the container while it can be dragged, and `grabbing` while dragging.
|
|
33
|
+
* @default true
|
|
34
|
+
*/
|
|
35
|
+
cursor?: boolean;
|
|
36
|
+
}
|
|
37
|
+
/** Scroll state of one axis. Positions are logical: measured from the start edge, so positive in RTL too. */
|
|
38
|
+
export interface AxisState {
|
|
39
|
+
/** Distance scrolled from the start edge, in px (0 to `maxScroll`). */
|
|
40
|
+
scroll: number;
|
|
41
|
+
/** Largest possible `scroll`, in px. 0 when the content fits. */
|
|
42
|
+
maxScroll: number;
|
|
43
|
+
/** `scroll / maxScroll`, from 0 to 1. 0 when the content fits. */
|
|
44
|
+
progress: number;
|
|
45
|
+
/** True when the container is more than 1px away from the start edge. */
|
|
46
|
+
canScrollPrev: boolean;
|
|
47
|
+
/** True when the container is more than 1px away from the end edge. */
|
|
48
|
+
canScrollNext: boolean;
|
|
49
|
+
}
|
|
50
|
+
/** Scroll state of both axes, whatever `axis` is set to. */
|
|
51
|
+
export interface AxisScrollState {
|
|
52
|
+
x: AxisState;
|
|
53
|
+
y: AxisState;
|
|
54
|
+
/** True while a mouse drag is scrolling the container. */
|
|
55
|
+
isDragging: boolean;
|
|
56
|
+
/** True while the container glides after a drag is released (momentum or settling on a snap position). */
|
|
57
|
+
isMomentum: boolean;
|
|
58
|
+
}
|
|
59
|
+
/** A logical scroll position. Axes that are left out keep their current position. */
|
|
60
|
+
export interface AxisPosition {
|
|
61
|
+
x?: number;
|
|
62
|
+
y?: number;
|
|
63
|
+
}
|
|
64
|
+
/** Options for {@link AxisInstance.scrollTo}. */
|
|
65
|
+
export interface AxisScrollToOptions {
|
|
66
|
+
/** Animate the scroll. Ignored (jumps) under `prefers-reduced-motion: reduce`. @default true */
|
|
67
|
+
animate?: boolean;
|
|
68
|
+
}
|
|
69
|
+
/** An axis instance bound to one scroll container. */
|
|
70
|
+
export interface AxisInstance {
|
|
71
|
+
/** The current state. The object is replaced, never mutated, when a value changes. */
|
|
72
|
+
getState(): AxisScrollState;
|
|
73
|
+
/** Calls `listener` whenever the state changes. Returns a function that removes the listener. */
|
|
74
|
+
subscribe(listener: (state: AxisScrollState) => void): () => void;
|
|
75
|
+
/**
|
|
76
|
+
* Scrolls `axis` to the previous snap position, or back by 90% of the visible size when it does not snap.
|
|
77
|
+
* `axis` defaults to the configured axis and is required with `axis: "both"`.
|
|
78
|
+
*/
|
|
79
|
+
scrollPrev(axis?: AxisName): void;
|
|
80
|
+
/**
|
|
81
|
+
* Scrolls `axis` to the next snap position, or forward by 90% of the visible size when it does not snap.
|
|
82
|
+
* `axis` defaults to the configured axis and is required with `axis: "both"`.
|
|
83
|
+
*/
|
|
84
|
+
scrollNext(axis?: AxisName): void;
|
|
85
|
+
/** Scrolls to a logical position (px from the start edge of each axis). */
|
|
86
|
+
scrollTo(position: AxisPosition, options?: AxisScrollToOptions): void;
|
|
87
|
+
/** Changes some options. Keys that are left out or `undefined` keep their current value. */
|
|
88
|
+
setOptions(options: AxisOptions): void;
|
|
89
|
+
/**
|
|
90
|
+
* Re-reads the layout: clears the cached snap positions and updates the state. Only needed after a change
|
|
91
|
+
* axis cannot observe, such as a stylesheet or media query changing snap properties without a resize.
|
|
92
|
+
*/
|
|
93
|
+
refresh(): void;
|
|
94
|
+
/** Removes every listener and observer and restores every inline style that axis changed. */
|
|
95
|
+
destroy(): void;
|
|
96
|
+
}
|
|
97
|
+
export type Motion = "idle" | "pressed" | "dragging" | "momentum" | "glide" | "wheel";
|
|
98
|
+
export type Glide = "momentum" | "glide";
|
|
99
|
+
export interface Geometry {
|
|
100
|
+
rtl(): boolean;
|
|
101
|
+
scrollRange(): Vector;
|
|
102
|
+
position(rtl?: boolean, range?: Vector): Vector;
|
|
103
|
+
writePosition(position: AxisPosition, rtl?: boolean): void;
|
|
104
|
+
}
|
|
105
|
+
export interface AxisCore extends Geometry {
|
|
106
|
+
el: HTMLElement;
|
|
107
|
+
win: Window;
|
|
108
|
+
options(): Required<AxisOptions>;
|
|
109
|
+
motion(): Motion;
|
|
110
|
+
setMotion(next: Motion): void;
|
|
111
|
+
isDestroyed(): boolean;
|
|
112
|
+
snapPoints(): Record<AxisName, number[]>;
|
|
113
|
+
}
|
|
114
|
+
export interface StyleOverrides {
|
|
115
|
+
override(prop: string, value: string, important?: boolean): void;
|
|
116
|
+
restore(prop: string): void;
|
|
117
|
+
restoreAll(): void;
|
|
118
|
+
}
|
|
119
|
+
export interface SnapSuspension {
|
|
120
|
+
suspend(): void;
|
|
121
|
+
resume(): void;
|
|
122
|
+
snapAxes(): {
|
|
123
|
+
x: boolean;
|
|
124
|
+
y: boolean;
|
|
125
|
+
};
|
|
126
|
+
isSuspended(): boolean;
|
|
127
|
+
}
|
|
128
|
+
export interface Hold {
|
|
129
|
+
holdCurrent(): void;
|
|
130
|
+
release(): void;
|
|
131
|
+
reset(): void;
|
|
132
|
+
yieldDrag(glideHoldsAtItsEnd: boolean): void;
|
|
133
|
+
holdIfYielded(): void;
|
|
134
|
+
follow(axis: AxisName): void;
|
|
135
|
+
undoAutoscroll(): void;
|
|
136
|
+
}
|
|
137
|
+
export interface ScrollDriver {
|
|
138
|
+
stop(): void;
|
|
139
|
+
settle(): void;
|
|
140
|
+
interrupt(): void;
|
|
141
|
+
jump(target: AxisPosition): void;
|
|
142
|
+
glideTo(target: AxisPosition, velocity: Vector, kind: Glide): void;
|
|
143
|
+
fling(scrollVelocity: Vector): void;
|
|
144
|
+
afterWheelPause(settle: () => void): void;
|
|
145
|
+
glideTarget(): Vector;
|
|
146
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Coordinator } from "./types.js";
|
|
2
|
+
export declare const DRAG_THRESHOLD = 5;
|
|
3
|
+
export declare const CLICK_SUPPRESS_MS = 100;
|
|
4
|
+
export declare const NO_DRAG = "input, textarea, select, [contenteditable]:not([contenteditable='false']), [data-axis-no-drag]";
|
|
5
|
+
export declare const coordinators: WeakMap<Window, Coordinator>;
|