@n-uf/motion 26.11.4 → 26.11.6

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/CHANGELOG.md CHANGED
@@ -3,6 +3,62 @@
3
3
  Versions are calendar versions, `YY.M.R` (year, month of the release, counter
4
4
  within the month), the same scheme as `@n-uf/hypr-tiling`.
5
5
 
6
+ ## 26.11.6
7
+
8
+ - `@n-uf/motion/drag`: the README shows a drag on a scaled page (a zoomed
9
+ canvas, a fitted preview): the intent works in the coordinate space of its
10
+ targets and home, so each pointer sample goes in as `(p − origin) / scale`.
11
+ - Docs: `item.retarget` is optional on a `LayoutItem` (`holdItem` falls back
12
+ to `moveTo`); the paged swipe's state diagram shows `cancel` from settling
13
+ back to idle.
14
+ - Released together with `@n-uf/placement` 26.11.6 (keyboard stops, slots,
15
+ `allows`, keep priority, stored arrangements).
16
+
17
+ ## 26.11.5
18
+
19
+ - `@n-uf/motion/drag`: `placeOf(target, point, previous)` is told the place
20
+ it gave that target on the last sample (null if none), so a place can hold
21
+ near its boundary (hysteresis lives in the consumer's `placeOf`). It may
22
+ return null: no drop at that point of the target. There the target is no
23
+ target: it isn't picked or held, a shown one hides at once (a target beneath
24
+ with a place can show instead), no room opens, and a release lands on a
25
+ target beneath, else goes home. A two-argument `placeOf` works as before.
26
+ `@n-uf/placement/targets`' `paneZone` is made for it. Near the current
27
+ target's edge (within the band or the gap grace) answer its nearest place:
28
+ null there would drop the band and the grace.
29
+ - Changed: `placeOf` is now called on every sample for each target under
30
+ the pointer and for the current one; in 26.11.4 it was called only for
31
+ the shown target. Keep it cheap and free of side effects.
32
+ - `@n-uf/motion/layout`: what a tiling host's own leaf motion did, on
33
+ `createLayoutMotion`:
34
+ - `onDraw(batch)`: every item that moved is drawn in one pass per frame and
35
+ handed over once; an item at rest is not drawn;
36
+ - `reducedMotion` (a boolean or the host's query), read on every new target
37
+ and show or hide: items are placed and shown at once, no glide or fade;
38
+ - `flush()` draws a batched change made between frames now, so the paint
39
+ right after a host's commit shows no frame at the new box;
40
+ - `retain(ids)` removes every item not listed;
41
+ - `onActiveChange(active)`, once when the frames start and once when they
42
+ stop;
43
+ - `item.retarget(rect)`: a settle that keeps the content laid out while the
44
+ size is unchanged. The held drag's glide back to the pointer uses it, so
45
+ it no longer lays the item out on every frame. It is optional on
46
+ `LayoutItem`: `holdItem` falls back to `moveTo` for an item without it (a
47
+ 26.11.4 item or a test double).
48
+ - Changed: the `LayoutMotion` interface gains `retain` and `flush`, so an
49
+ implementation of it written outside this package needs both.
50
+ `createLayoutMotion` has them.
51
+ - `@n-uf/motion/gestures`: `touchPressReducer` / `createTouchPress`, a touch
52
+ on something draggable in a scrolling page: travel mostly along the scroll
53
+ direction is never a lift: it is a scroll from 10 px, so a finger scroll
54
+ reported a few px at a time never lifts on the way. `pagedSwipeReducer` /
55
+ `createPagedSwipe`, swiping between pages with a wheel run or a finger: it
56
+ arms only along the swipe, tracks progress over the page size, commits by
57
+ share or speed and locks out the trackpad's momentum after a commit;
58
+ `cancel` while it settles goes back to idle, so a host that stops its
59
+ transition can't leave it stuck. Both from hypr-tiling's public engine, with
60
+ generic names.
61
+
6
62
  ## 26.11.4
7
63
 
8
64
  - New entry `@n-uf/motion/drag`: the drag intent model, pure and DOM-free.
package/README.md CHANGED
@@ -198,8 +198,12 @@ drop targets) is the caller's layout.
198
198
 
199
199
  | | |
200
200
  |---|---|
201
- | `createLayoutMotion({ loop, omega?, phase?, popFrom? })` | The surface: `add(id, drawable, at, { shown? })`, `get`, `remove`, `items`; `task(fn)` runs before the items step each frame, `onFrame(fn)` after; `onIdle`, `idle()`, `running`, `wake()`, `configure({ omega })`, `dispose()`. |
201
+ | `createLayoutMotion({ loop, omega?, phase?, popFrom?, reducedMotion?, onDraw?, onActiveChange? })` | The surface: `add(id, drawable, at, { shown? })`, `get`, `remove`, `retain(ids)` (removes every item not listed), `items`; `task(fn)` runs before the items step each frame, `onFrame(fn)` after; `onIdle`, `idle()`, `running`, `wake()`, `configure({ omega })`, `flush()`, `dispose()`. |
202
+ | `reducedMotion` | A boolean or the host's query (the reduced-motion media query), read on every new target and every show or hide: while true, items are placed and shown at once, with no glide and no fade. |
203
+ | `onDraw(batch)`, `flush()` | Batched drawing: every item that moved is drawn in one pass per frame (its drawable, then `onDraw` once with all their frames); an item at rest is not drawn. A change made between frames waits for the next frame, or `flush()` draws it now, which a host calls right after it commits a layout so the next paint shows no frame at the new box. |
204
+ | `onActiveChange(active)` | Called once when the frames start and once when they stop. |
202
205
  | `item.moveTo(rect)` | Settle there from wherever it is drawn (a retarget mid-flight goes on from the drawn rect). The content is laid out at the new box once, and the item redrawn at once, so no frame shows the old offset from the new box. A size change is revealed, never scaled. |
206
+ | `item.retarget(rect)` | Optional on a `LayoutItem`. Settle there like `moveTo`, but keep the content laid out where it is while the size is unchanged: for chasing a moving point (the held drag's glide back to the pointer uses it), which would otherwise lay the content out on every frame. `holdItem` falls back to `moveTo` for an item without it. |
203
207
  | `item.jumpTo(rect)`, `item.hold(rect)`, `item.glideFrom(from, to)` | At once; held exactly where a pointer puts it (the content moves with it); drawn at `from`, then settle to `to`. |
204
208
  | `item.show(on, { now? })` | Appear or disappear with a uniform-scale fade (`popIn`); folding is a `moveTo` to the folded size, revealed. |
205
209
  | `item.drawn`, `target`, `box`, `settled`, `shown`, `showing`, `shownSettled` | Where it is drawn, where it is heading, where its content is laid out. |
@@ -225,6 +229,27 @@ clock.runUntilIdle();
225
229
  console.log(panel.drawn); // { x: 0, y: 0, width: 200, height: 100 }
226
230
  ```
227
231
 
232
+ A host that draws every item itself, once per frame:
233
+
234
+ ```ts
235
+ import { createFakeClock, createFrameLoop } from "@n-uf/motion";
236
+ import { createLayoutMotion } from "@n-uf/motion/layout";
237
+
238
+ const clock = createFakeClock();
239
+ const loop = createFrameLoop({ clock });
240
+ const batches: string[][] = [];
241
+ const host = createLayoutMotion({ loop, onDraw: (batch) => batches.push(batch.map((e) => e.id)) });
242
+ const noop = { layOut: () => {}, draw: () => {} };
243
+ const a = host.add("a", noop, { x: 0, y: 0, width: 100, height: 100 });
244
+ host.add("b", noop, { x: 0, y: 200, width: 100, height: 100 });
245
+ host.flush(); // both drawn now, in one batch: ["a", "b"]
246
+ a.moveTo({ x: 300, y: 0, width: 100, height: 100 });
247
+ host.flush(); // right after the host's commit: ["a"], at the rect it is drawn at
248
+ clock.runUntilIdle(); // one batch per frame, each ["a"] (b is at rest)
249
+ host.retain(["a"]); // b left the layout
250
+ console.log(host.items.size); // 1
251
+ ```
252
+
228
253
  ## testing/
229
254
 
230
255
  `@n-uf/motion/testing` checks recorded frames against the rules. Record what
@@ -255,6 +280,8 @@ The motion lab in the [n-uf/motion repository](https://github.com/n-uf/motion)
255
280
  | `createFrameCoalescer(onFrame, frames, { merge? })` | `schedule(sample)`, `flush()`, `cancel()`, `pending`. One delivery per frame with the latest sample (or `merge`d ones) and how many were folded. Same shape as hypr-tiling's. |
256
281
  | `dwellReducer`, `DWELL_IDLE`, `dwellDeadline`, `createDwellMachine` | `idle → dwelling → fired`, for spring-loaded targets: hold over the same key long enough and it fires once. |
257
282
  | `createMachine({ reducer, initial, clock, deadline? })` | Runs a pure reducer on a clock: arms the timer the state's deadline asks for and sends its input when it fires. `state` is reactive. |
283
+ | `touchPressReducer`, `TOUCH_PRESS_IDLE`, `touchPressDeadline`, `createTouchPress`, `TOUCH_PRESS_DEFAULTS` | A touch on something draggable in a scrolling page, `idle → pressed → lifted \| scrolled`: before the hold (220 ms), travel mostly along the page's scroll direction (`scroll: "y"` or `"x"`) past 10 px is a scroll, which never lifts (short of 10 px it waits, so a finger scroll reported a few px at a time never lifts on the way); travel past 6 px mostly across it lifts where the finger is; a still finger lifts where it was pressed after the hold. |
284
+ | `pagedSwipeReducer`, `PAGED_SWIPE_IDLE`, `pagedSwipeDeadline`, `createPagedSwipe`, `PAGED_SWIPE_DEFAULTS`, `swipeProgress` | Swiping between pages with a trackpad's wheel run or a finger, `idle → armed → tracking → settling → lockout`. It arms only along the swipe (more than twice the cross movement), without Ctrl, with the configured modifier, when nothing under the pointer scrolls further and a page is there; a run that breaks a rule belongs to scrolling until the wheel rests 120 ms. Past 24 px it tracks: `progress` is the travel over the page size, −1 to 1, held at 0 towards a missing page. At the end it commits at 35 % of the page or a peak of 0.6 px/ms towards it (the peak starts over when the travel crosses zero); the host sends `settled` after its transition, and a 350 ms lockout swallows the trackpad's momentum. On x or y (`along`). |
258
285
 
259
286
  The recogniser takes plain objects the host copies out of DOM events, with
260
287
  their timestamps; nothing in it touches the DOM. Mouse, pen and touch share
@@ -277,6 +304,22 @@ clock.tick(500); // longPress
277
304
  recognizer.feed({ ...finger, type: "pointerup", buttons: 0, time: clock.now() }); // release, click: false
278
305
  ```
279
306
 
307
+ A paged swipe, its timers on a clock:
308
+
309
+ ```ts
310
+ import { createFakeClock, createPagedSwipe, NO_MODIFIERS } from "@n-uf/motion";
311
+
312
+ const clock = createFakeClock();
313
+ const swipe = createPagedSwipe({ clock });
314
+ swipe.send({ type: "context", hasPrev: true, hasNext: true, size: 1000 });
315
+ swipe.send({ type: "wheel", dx: 400, dy: 0, t: clock.now(), modifiers: NO_MODIFIERS });
316
+ console.log(swipe.state.phase, swipe.state.progress); // tracking 0.4
317
+ clock.tick(120); // the wheel rests: past 35 %, it commits
318
+ console.log(swipe.state.phase, swipe.state.target); // settling next
319
+ swipe.send({ type: "settled" }); // the host's page transition is done: momentum is locked out
320
+ console.log(swipe.state.phase); // lockout
321
+ ```
322
+
280
323
  ## drag/
281
324
 
282
325
  `@n-uf/motion/drag` decides, from the pointer's path, when a drop indicator
@@ -305,6 +348,14 @@ a large area 3). The rules:
305
348
  new target has held 150 ms, so no indicator ever shows for less.
306
349
  - Room is made once the shown target, and the place in it (`placeOf`: a tab
307
350
  index, say), have held 160 ms; never for a `free` target.
351
+ - `placeOf(target, point, previous)` gets the place it gave that target on the
352
+ last sample, so a place can hold near its boundary. A null place is no
353
+ target: nothing shows there (a target beneath with a place can), no room
354
+ opens, and a release there lands on a target beneath, else goes home.
355
+ `placeOf` is also asked about the current target just outside its zone
356
+ (within the band or the gap grace): answer the nearest place there, and
357
+ return null only for a part of the target that takes no drop. It runs on
358
+ every sample for each target under the pointer, so keep it cheap.
308
359
  - A release lands on the shown target whenever one shows (drop = shown), else
309
360
  on the target under the pointer, else nowhere. A release over home does
310
361
  nothing.
@@ -319,6 +370,30 @@ const intent = createDragIntent({ home: { x: 300, y: 300, width: 100, height: 50
319
370
  for (let t = 0; t <= 300; t += 16) intent.update({ x: 100, y: 50 }, t, [slot]); // held still over the slot
320
371
  console.log(intent.state.shown?.id); // "slot"
321
372
  console.log(intent.release({ x: 100, y: 50 }, [slot])?.id); // "slot"
373
+
374
+ // A pane that takes drops on its right half only; that half holds 6 px past the line.
375
+ const pane: DragTarget = { id: "pane", priority: 2, hit: { x: 0, y: 0, width: 200, height: 100 } };
376
+ const zones = createDragIntent<DragTarget>({
377
+ home: { x: 300, y: 300, width: 100, height: 50 },
378
+ placeOf: (_t, p, previous) => (p.x >= (previous === "right" ? 94 : 100) ? "right" : null),
379
+ });
380
+ for (let t = 0; t <= 300; t += 16) zones.update({ x: 50, y: 50 }, t, [pane]); // held over its left half
381
+ console.log(zones.state.shown, zones.release({ x: 50, y: 50 }, [pane])); // null null: nothing shows, the item goes home
382
+ ```
383
+
384
+ The intent works in one coordinate space: the one its targets and home are in. On a page drawn scaled (a zoomed
385
+ canvas, a fitted preview), convert each pointer sample into page coordinates first, `(p − origin) / scale`, and the
386
+ band, breakthrough and stillness distances stay page px:
387
+
388
+ ```ts
389
+ // The page is drawn at half size, its top-left corner at (300, 200) on screen; targets are in page px.
390
+ const origin = { x: 300, y: 200 };
391
+ const scale = 0.5;
392
+ const toPage = (p: { x: number; y: number }) => ({ x: (p.x - origin.x) / scale, y: (p.y - origin.y) / scale });
393
+ const onPage = createDragIntent({ home: { x: 600, y: 600, width: 100, height: 50 } });
394
+ const screen = { x: 350, y: 225 }; // the pointer, as the browser reports it
395
+ for (let t = 0; t <= 300; t += 16) onPage.update(toPage(screen), t, [slot]); // page (100, 50): over the slot
396
+ console.log(onPage.state.shown?.id, onPage.release(toPage(screen), [slot])?.id); // "slot" "slot"
322
397
  ```
323
398
 
324
399
  ## @n-uf/motion/browser
@@ -571,6 +571,218 @@ function createDwellMachine(options) {
571
571
  });
572
572
  }
573
573
 
574
+ // src/gestures/touch-press.ts
575
+ var TOUCH_PRESS_DEFAULTS = { lift: 6, scrollEscape: 10, scroll: "y", holdMs: 220 };
576
+ var TOUCH_PRESS_IDLE = { phase: "idle" };
577
+ function touchPressReducer(state, event, config = TOUCH_PRESS_DEFAULTS) {
578
+ switch (event.type) {
579
+ case "release":
580
+ return state.phase === "idle" ? state : TOUCH_PRESS_IDLE;
581
+ case "press":
582
+ return state.phase === "idle" ? { phase: "pressed", from: { x: event.point.x, y: event.point.y }, since: event.t } : state;
583
+ case "tick":
584
+ if (state.phase !== "pressed" || event.t - state.since < config.holdMs) return state;
585
+ return { phase: "lifted", from: state.from, at: state.from };
586
+ case "move": {
587
+ if (state.phase !== "pressed") return state;
588
+ const dx = event.point.x - state.from.x;
589
+ const dy = event.point.y - state.from.y;
590
+ const along = Math.abs(config.scroll === "y" ? dy : dx);
591
+ const across = Math.abs(config.scroll === "y" ? dx : dy);
592
+ if (along > across) return along >= config.scrollEscape ? { phase: "scrolled", from: state.from } : state;
593
+ if (Math.hypot(dx, dy) >= config.lift) return { phase: "lifted", from: state.from, at: { x: event.point.x, y: event.point.y } };
594
+ return state;
595
+ }
596
+ default:
597
+ return state;
598
+ }
599
+ }
600
+ function touchPressDeadline(state, config = TOUCH_PRESS_DEFAULTS) {
601
+ if (state.phase !== "pressed") return null;
602
+ return { at: state.since + config.holdMs, event: (t) => ({ type: "tick", t }) };
603
+ }
604
+ function createTouchPress(options) {
605
+ const config = { ...TOUCH_PRESS_DEFAULTS, ...options.config };
606
+ return createMachine({
607
+ reducer: (state, event) => touchPressReducer(state, event, config),
608
+ initial: TOUCH_PRESS_IDLE,
609
+ clock: options.clock,
610
+ deadline: (state) => touchPressDeadline(state, config)
611
+ });
612
+ }
613
+
614
+ // src/gestures/paged-swipe.ts
615
+ var PAGED_SWIPE_DEFAULTS = {
616
+ thresholdPx: 24,
617
+ commitFraction: 0.35,
618
+ commitVelocity: 0.6,
619
+ idleMs: 120,
620
+ lockoutMs: 350,
621
+ wrap: false,
622
+ size: 800,
623
+ modifier: null,
624
+ along: "x"
625
+ };
626
+ var PAGED_SWIPE_EMPTY_CONTEXT = { hasPrev: false, hasNext: false, size: null, blocked: false };
627
+ var PAGED_SWIPE_IDLE = { phase: "idle", context: PAGED_SWIPE_EMPTY_CONTEXT, progress: 0, target: null, lastWheel: null, claimed: false };
628
+ var idle = (context, lastWheel = null, claimed = false) => ({ phase: "idle", context, progress: 0, target: null, lastWheel, claimed });
629
+ var sideOf = (travel) => travel > 0 ? "next" : travel < 0 ? "prev" : null;
630
+ var hasPage = (c, side, wrap) => wrap ? c.hasPrev || c.hasNext : side === "next" ? c.hasNext : c.hasPrev;
631
+ var withSize = (c, size) => size === void 0 || size === c.size ? c : { ...c, size };
632
+ var sizeOf = (c, size, config) => size ?? c.size ?? config.size;
633
+ function arming(c, main, cross, config, gates) {
634
+ if (c.blocked || gates.ctrl || gates.canScrollFurther || !gates.modifierHeld) return null;
635
+ if (Math.abs(main) <= 2 * Math.abs(cross)) return null;
636
+ const side = sideOf(main);
637
+ return side && hasPage(c, side, config.wrap) ? side : null;
638
+ }
639
+ function swipeProgress(travel, size, c, wrap) {
640
+ const p = Math.max(-1, Math.min(1, travel / Math.max(1, size)));
641
+ if (wrap) return p;
642
+ if (p < 0 && !c.hasPrev || p > 0 && !c.hasNext) return 0;
643
+ return p;
644
+ }
645
+ var towards = (delta, dt, side) => {
646
+ if (dt <= 0 || side === null) return 0;
647
+ const v = (side === "next" ? delta : -delta) / dt;
648
+ return v > 0 ? v : 0;
649
+ };
650
+ function track(c, input, origin, travel, t, peak, size, config) {
651
+ const progress = swipeProgress(travel, size, c, config.wrap);
652
+ return { phase: "tracking", context: c, input, origin, travel, last: t, peak, progress, target: sideOf(progress) };
653
+ }
654
+ function end(s, t, config) {
655
+ const commit = s.target !== null && (Math.abs(s.progress) >= config.commitFraction || s.peak >= config.commitVelocity);
656
+ return { phase: "settling", context: s.context, input: s.input, outcome: commit ? "commit" : "cancel", ended: t, lastWheel: t, progress: s.progress, target: commit ? s.target : null };
657
+ }
658
+ var giveUp = (s, c) => ({
659
+ phase: "settling",
660
+ context: c,
661
+ input: s.input,
662
+ outcome: "cancel",
663
+ ended: s.last,
664
+ lastWheel: s.last,
665
+ progress: s.progress,
666
+ target: null
667
+ });
668
+ function pagedSwipeReducer(state, event, config = PAGED_SWIPE_DEFAULTS) {
669
+ const y = config.along === "y";
670
+ if (event.type === "context") {
671
+ return { ...state, context: { ...state.context, hasPrev: event.hasPrev, hasNext: event.hasNext, size: event.size ?? state.context.size } };
672
+ }
673
+ if (event.type === "block") {
674
+ const c = state.context.blocked === event.on ? state.context : { ...state.context, blocked: event.on };
675
+ if (event.on && state.phase === "armed") return idle(c);
676
+ if (event.on && state.phase === "tracking") return giveUp(state, c);
677
+ return c === state.context ? state : { ...state, context: c };
678
+ }
679
+ if (event.type === "cancel") {
680
+ if (state.phase === "armed" || state.phase === "lockout" || state.phase === "settling") return idle(state.context);
681
+ if (state.phase === "tracking") return giveUp(state, state.context);
682
+ return state;
683
+ }
684
+ switch (state.phase) {
685
+ case "idle": {
686
+ if (event.type === "wheel") {
687
+ if (state.context.blocked) return state;
688
+ const fresh = state.lastWheel === null || event.t - state.lastWheel >= config.idleMs;
689
+ if (!fresh && state.claimed) return { ...state, lastWheel: event.t };
690
+ const main = y ? event.dy : event.dx;
691
+ const cross = y ? event.dx : event.dy;
692
+ const side = arming(state.context, main, cross, config, wheelGates(event, config));
693
+ const c = withSize(state.context, event.size);
694
+ if (side === null) return idle(c, event.t, true);
695
+ if (Math.abs(main) >= config.thresholdPx) return track(c, "wheel", { x: 0, y: 0 }, main, event.t, 0, sizeOf(c, event.size, config), config);
696
+ return { phase: "armed", context: c, input: "wheel", origin: { x: 0, y: 0 }, travel: main, cross, last: event.t, progress: 0, target: null };
697
+ }
698
+ if (event.type === "touch-start") {
699
+ if (state.context.blocked) return state;
700
+ return { phase: "armed", context: withSize(state.context, event.size), input: "touch", origin: { x: event.x, y: event.y }, travel: 0, cross: 0, last: event.t, progress: 0, target: null };
701
+ }
702
+ return state;
703
+ }
704
+ case "armed": {
705
+ if (event.type === "wheel") {
706
+ if (state.input !== "wheel") return state;
707
+ const main = y ? event.dy : event.dx;
708
+ const cross = y ? event.dx : event.dy;
709
+ if (arming(state.context, main, cross, config, wheelGates(event, config)) === null) return idle(state.context, event.t, true);
710
+ const c = withSize(state.context, event.size);
711
+ const travel = state.travel + main;
712
+ const crossTravel = state.cross + cross;
713
+ if (Math.abs(crossTravel) > Math.abs(travel) / 2) return idle(c, event.t, true);
714
+ if (Math.abs(travel) < config.thresholdPx) return { ...state, context: c, travel, cross: crossTravel, last: event.t };
715
+ return track(c, "wheel", state.origin, travel, event.t, towards(main, event.t - state.last, sideOf(travel)), sizeOf(c, event.size, config), config);
716
+ }
717
+ if (event.type === "touch-move") {
718
+ if (state.input !== "touch") return state;
719
+ const main = y ? state.origin.y - event.y : state.origin.x - event.x;
720
+ const cross = y ? event.x - state.origin.x : event.y - state.origin.y;
721
+ if (Math.hypot(main, cross) < config.thresholdPx) return { ...state, last: event.t };
722
+ const side = arming(state.context, main, cross, config, { ctrl: false, modifierHeld: true, canScrollFurther: event.canScrollFurther ?? false });
723
+ if (side === null) return idle(state.context);
724
+ const c = withSize(state.context, event.size);
725
+ return track(c, "touch", state.origin, main, event.t, towards(main, event.t - state.last, side), sizeOf(c, event.size, config), config);
726
+ }
727
+ if (event.type === "tick") return state.input === "wheel" && event.t - state.last >= config.idleMs ? idle(state.context) : state;
728
+ if (event.type === "touch-end") return state.input === "touch" ? idle(state.context) : state;
729
+ if (event.type === "touch-start") return { ...state, context: withSize(state.context, event.size), input: "touch", origin: { x: event.x, y: event.y }, travel: 0, cross: 0, last: event.t };
730
+ return state;
731
+ }
732
+ case "tracking": {
733
+ if (event.type === "wheel" || event.type === "touch-move") {
734
+ if (state.input !== (event.type === "wheel" ? "wheel" : "touch")) return state;
735
+ const travel = event.type === "wheel" ? state.travel + (y ? event.dy : event.dx) : y ? state.origin.y - event.y : state.origin.x - event.x;
736
+ const side = sideOf(travel);
737
+ const v = towards(travel - state.travel, event.t - state.last, side);
738
+ const peak = side === sideOf(state.travel) ? Math.max(state.peak, v) : v;
739
+ const c = withSize(state.context, event.size);
740
+ return track(c, state.input, state.origin, travel, event.t, peak, sizeOf(c, event.size, config), config);
741
+ }
742
+ if (event.type === "tick") return state.input === "wheel" && event.t - state.last >= config.idleMs ? end(state, event.t, config) : state;
743
+ if (event.type === "touch-end") return state.input === "touch" ? end(state, event.t, config) : state;
744
+ return state;
745
+ }
746
+ case "settling": {
747
+ if (event.type === "settled") {
748
+ if (state.outcome === "commit") return { phase: "lockout", context: state.context, until: state.ended + config.lockoutMs, lastWheel: state.lastWheel, progress: 0, target: null };
749
+ return idle(state.context, state.lastWheel, false);
750
+ }
751
+ if (event.type === "wheel") return { ...state, lastWheel: event.t };
752
+ return state;
753
+ }
754
+ case "lockout": {
755
+ if (event.type === "wheel") return event.t >= state.until ? idle(state.context, event.t, true) : { ...state, lastWheel: event.t };
756
+ if (event.type === "tick") {
757
+ if (event.t < state.until) return state;
758
+ return event.t - state.lastWheel < config.idleMs ? idle(state.context, state.lastWheel, true) : idle(state.context);
759
+ }
760
+ if (event.type === "touch-start") return pagedSwipeReducer(idle(state.context), event, config);
761
+ return state;
762
+ }
763
+ default:
764
+ return state;
765
+ }
766
+ }
767
+ function wheelGates(event, config) {
768
+ return { ctrl: event.modifiers.ctrl, modifierHeld: config.modifier === null || event.modifiers[config.modifier], canScrollFurther: event.canScrollFurther ?? false };
769
+ }
770
+ function pagedSwipeDeadline(state, config = PAGED_SWIPE_DEFAULTS) {
771
+ const tick = (t) => ({ type: "tick", t });
772
+ if ((state.phase === "armed" || state.phase === "tracking") && state.input === "wheel") return { at: state.last + config.idleMs, event: tick };
773
+ if (state.phase === "lockout") return { at: state.until, event: tick };
774
+ return null;
775
+ }
776
+ function createPagedSwipe(options) {
777
+ const config = { ...PAGED_SWIPE_DEFAULTS, ...options.config };
778
+ return createMachine({
779
+ reducer: (state, event) => pagedSwipeReducer(state, event, config),
780
+ initial: PAGED_SWIPE_IDLE,
781
+ clock: options.clock,
782
+ deadline: (state) => pagedSwipeDeadline(state, config)
783
+ });
784
+ }
785
+
574
786
  export {
575
787
  NO_MODIFIERS,
576
788
  toModifiers,
@@ -592,6 +804,18 @@ export {
592
804
  DWELL_IDLE,
593
805
  dwellReducer,
594
806
  dwellDeadline,
595
- createDwellMachine
807
+ createDwellMachine,
808
+ TOUCH_PRESS_DEFAULTS,
809
+ TOUCH_PRESS_IDLE,
810
+ touchPressReducer,
811
+ touchPressDeadline,
812
+ createTouchPress,
813
+ PAGED_SWIPE_DEFAULTS,
814
+ PAGED_SWIPE_EMPTY_CONTEXT,
815
+ PAGED_SWIPE_IDLE,
816
+ swipeProgress,
817
+ pagedSwipeReducer,
818
+ pagedSwipeDeadline,
819
+ createPagedSwipe
596
820
  };
597
- //# sourceMappingURL=chunk-C4M62FUT.js.map
821
+ //# sourceMappingURL=chunk-KVFX5V4Y.js.map