@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 +56 -0
- package/README.md +76 -1
- package/dist/{chunk-C4M62FUT.js → chunk-KVFX5V4Y.js} +226 -2
- package/dist/chunk-KVFX5V4Y.js.map +1 -0
- package/dist/drag.d.ts +20 -4
- package/dist/drag.js +17 -3
- package/dist/drag.js.map +1 -1
- package/dist/gestures.d.ts +245 -1
- package/dist/gestures.js +25 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +25 -1
- package/dist/layout.d.ts +36 -1
- package/dist/layout.js +84 -14
- package/dist/layout.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-C4M62FUT.js.map +0 -1
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-
|
|
821
|
+
//# sourceMappingURL=chunk-KVFX5V4Y.js.map
|