@humanspeak/svelte-motion 0.5.1 → 0.5.3
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/dist/components/projection.context.d.ts +22 -0
- package/dist/components/projection.context.js +56 -0
- package/dist/html/_MotionContainer.svelte +328 -24
- package/dist/index.d.ts +1 -1
- package/dist/types.d.ts +78 -0
- package/dist/utils/drag.d.ts +42 -11
- package/dist/utils/drag.js +103 -12
- package/dist/utils/layout.d.ts +26 -2
- package/dist/utils/layout.js +13 -2
- package/dist/utils/pan.d.ts +135 -0
- package/dist/utils/pan.js +426 -0
- package/dist/utils/projection.d.ts +287 -0
- package/dist/utils/projection.js +392 -0
- package/dist/utils/style.d.ts +23 -0
- package/dist/utils/style.js +27 -0
- package/dist/utils/variants.d.ts +26 -0
- package/dist/utils/variants.js +42 -0
- package/package.json +5 -5
package/dist/utils/drag.d.ts
CHANGED
|
@@ -66,24 +66,55 @@ export declare const resolveConstraints: (el: HTMLElement | null, constraints: D
|
|
|
66
66
|
*/
|
|
67
67
|
export declare const applyElastic: (value: number, min: number, max: number, elastic: number) => number;
|
|
68
68
|
/**
|
|
69
|
-
*
|
|
69
|
+
* Cleanup handle returned by {@link attachDrag}. Invoke it to detach the
|
|
70
|
+
* gesture's pointer/resize listeners. It does NOT cancel an in-flight
|
|
71
|
+
* momentum/settle animation — matching framer-motion, whose drag teardown
|
|
72
|
+
* removes listeners only and deliberately lets motion continue across an
|
|
73
|
+
* unmount/remount (e.g. reorder reconciliation); the animation is cleaned
|
|
74
|
+
* up by the element/motion-value lifecycle.
|
|
70
75
|
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
76
|
+
* The handle also carries `adjustOrigin(dx, dy)`, which shifts the LIVE
|
|
77
|
+
* gesture's origin + visual offset by a layout-shift delta mid-drag — used
|
|
78
|
+
* for projection-driven cursor pinning when a layout slot moves under the
|
|
79
|
+
* dragged element (#379 / #310). It is a no-op when not currently dragging
|
|
80
|
+
* and compensates on both axes (mirroring upstream's per-axis `eachAxis`
|
|
81
|
+
* compensation).
|
|
74
82
|
*/
|
|
83
|
+
export type AttachDragCleanup = (() => void) & {
|
|
84
|
+
adjustOrigin: (dx: number, dy: number) => void;
|
|
85
|
+
};
|
|
75
86
|
/**
|
|
76
|
-
* Attach a drag gesture to an
|
|
87
|
+
* Attach a drag gesture to an element.
|
|
88
|
+
*
|
|
89
|
+
* Captures the pointer and updates x/y transforms with axis and optional
|
|
90
|
+
* direction lock, applies elastic overflow against constraints, emits
|
|
91
|
+
* lifecycle callbacks with `DragInfo`, and runs a momentum animation on
|
|
92
|
+
* release when enabled.
|
|
77
93
|
*
|
|
78
94
|
* Lifecycle:
|
|
79
95
|
* - pointerdown → capture pointer, snapshot origin, start velocity history, enter whileDrag
|
|
80
96
|
* - pointermove → compute deltas, direction lock, apply constraints + elastic, write x/y
|
|
81
97
|
* - pointerup/cancel → either run momentum decay to a target or settle/clamp instantly
|
|
82
98
|
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
99
|
+
* Invariant: `applied` tracks the currently applied x/y transform — it
|
|
100
|
+
* must stay in sync when writing transforms or finishing animations, or a
|
|
101
|
+
* second drag "jumps" from a stale origin (commonly a missed update after
|
|
102
|
+
* a non-zero-duration settle animation).
|
|
103
|
+
*
|
|
104
|
+
* @param el The element to make draggable.
|
|
105
|
+
* @param opts Drag options — `axis`, `constraints`, `elastic`,
|
|
106
|
+
* `momentum`, `whileDrag`, and the `onDrag*` lifecycle callbacks.
|
|
107
|
+
* @returns A callable cleanup handle ({@link AttachDragCleanup}): call it
|
|
108
|
+
* to detach the gesture's listeners (in-flight momentum is not
|
|
109
|
+
* cancelled — see the type docs), or call its `adjustOrigin(dx, dy)` to
|
|
110
|
+
* reposition the live gesture mid-drag.
|
|
111
|
+
* @example
|
|
112
|
+
* ```ts
|
|
113
|
+
* const cleanup = attachDrag(el, { axis: 'x', momentum: true })
|
|
114
|
+
* // …when a layout swap shifts the slot under the cursor mid-drag:
|
|
115
|
+
* cleanup.adjustOrigin(10, -5)
|
|
116
|
+
* // on teardown:
|
|
117
|
+
* cleanup()
|
|
118
|
+
* ```
|
|
88
119
|
*/
|
|
89
|
-
export declare const attachDrag: (el: HTMLElement, opts: AttachDragOptions) =>
|
|
120
|
+
export declare const attachDrag: (el: HTMLElement, opts: AttachDragOptions) => AttachDragCleanup;
|
package/dist/utils/drag.js
CHANGED
|
@@ -137,23 +137,36 @@ const computeReleaseVelocity = (history, nowMs) => {
|
|
|
137
137
|
/**
|
|
138
138
|
* Attach a drag gesture to an element.
|
|
139
139
|
*
|
|
140
|
-
* Captures the pointer
|
|
141
|
-
* applies elastic overflow against constraints, emits
|
|
142
|
-
* and runs a momentum animation on
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Attach a drag gesture to an HTMLElement.
|
|
140
|
+
* Captures the pointer and updates x/y transforms with axis and optional
|
|
141
|
+
* direction lock, applies elastic overflow against constraints, emits
|
|
142
|
+
* lifecycle callbacks with `DragInfo`, and runs a momentum animation on
|
|
143
|
+
* release when enabled.
|
|
146
144
|
*
|
|
147
145
|
* Lifecycle:
|
|
148
146
|
* - pointerdown → capture pointer, snapshot origin, start velocity history, enter whileDrag
|
|
149
147
|
* - pointermove → compute deltas, direction lock, apply constraints + elastic, write x/y
|
|
150
148
|
* - pointerup/cancel → either run momentum decay to a target or settle/clamp instantly
|
|
151
149
|
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
150
|
+
* Invariant: `applied` tracks the currently applied x/y transform — it
|
|
151
|
+
* must stay in sync when writing transforms or finishing animations, or a
|
|
152
|
+
* second drag "jumps" from a stale origin (commonly a missed update after
|
|
153
|
+
* a non-zero-duration settle animation).
|
|
154
|
+
*
|
|
155
|
+
* @param el The element to make draggable.
|
|
156
|
+
* @param opts Drag options — `axis`, `constraints`, `elastic`,
|
|
157
|
+
* `momentum`, `whileDrag`, and the `onDrag*` lifecycle callbacks.
|
|
158
|
+
* @returns A callable cleanup handle ({@link AttachDragCleanup}): call it
|
|
159
|
+
* to detach the gesture's listeners (in-flight momentum is not
|
|
160
|
+
* cancelled — see the type docs), or call its `adjustOrigin(dx, dy)` to
|
|
161
|
+
* reposition the live gesture mid-drag.
|
|
162
|
+
* @example
|
|
163
|
+
* ```ts
|
|
164
|
+
* const cleanup = attachDrag(el, { axis: 'x', momentum: true })
|
|
165
|
+
* // …when a layout swap shifts the slot under the cursor mid-drag:
|
|
166
|
+
* cleanup.adjustOrigin(10, -5)
|
|
167
|
+
* // on teardown:
|
|
168
|
+
* cleanup()
|
|
169
|
+
* ```
|
|
157
170
|
*/
|
|
158
171
|
export const attachDrag = (el, opts) => {
|
|
159
172
|
const EL_ID = el.getAttribute('data-testid') || el.id || el.tagName;
|
|
@@ -249,6 +262,83 @@ export const attachDrag = (el, opts) => {
|
|
|
249
262
|
}
|
|
250
263
|
}
|
|
251
264
|
};
|
|
265
|
+
/**
|
|
266
|
+
* Write absolute element-space translation by mutating
|
|
267
|
+
* `el.style.transform` DIRECTLY — no `animate()`, no epsilon skip,
|
|
268
|
+
* no Playwright retry. The translate channel is rewritten while any
|
|
269
|
+
* non-translate transform the element already carries (e.g. a
|
|
270
|
+
* `whileDrag` scale) is preserved as a suffix.
|
|
271
|
+
*
|
|
272
|
+
* Why this exists separately from `setXY`: routing through
|
|
273
|
+
* `animate(el, _, { duration: 0 })` defers the write to Motion's
|
|
274
|
+
* scheduler, costing ~1 frame before the new position paints. For
|
|
275
|
+
* the projection-driven origin compensation (where a layout swap
|
|
276
|
+
* must keep the dragged element under the cursor in the SAME frame
|
|
277
|
+
* the swap commits), that frame of lag manifests as a visible
|
|
278
|
+
* wobble. This path lands synchronously. See #379 / the
|
|
279
|
+
* `adjustOrigin` hook below.
|
|
280
|
+
*/
|
|
281
|
+
const setXYImmediate = (x, y) => {
|
|
282
|
+
const parts = [];
|
|
283
|
+
if (axis === true || axis === 'x')
|
|
284
|
+
parts.push(`translateX(${x}px)`);
|
|
285
|
+
if (axis === true || axis === 'y')
|
|
286
|
+
parts.push(`translateY(${y}px)`);
|
|
287
|
+
// Strip existing translate channels, keep the rest (scale/rotate/etc.).
|
|
288
|
+
const nonTranslate = el.style.transform.replace(/translate[XYZ3d]*\([^)]*\)/g, '').trim();
|
|
289
|
+
el.style.transform = [...parts, nonTranslate].filter(Boolean).join(' ');
|
|
290
|
+
if (axis === true || axis === 'x')
|
|
291
|
+
applied.x = x;
|
|
292
|
+
if (axis === true || axis === 'y')
|
|
293
|
+
applied.y = y;
|
|
294
|
+
};
|
|
295
|
+
/**
|
|
296
|
+
* Adjust the drag origin + visual offset by a layout-shift delta,
|
|
297
|
+
* mid-gesture, keeping the dragged element pinned under the cursor
|
|
298
|
+
* while its underlying layout slot moves.
|
|
299
|
+
*
|
|
300
|
+
* Direct port of framer-motion's projection `didUpdate` handler in
|
|
301
|
+
* `VisualElementDragControls.ts:742-758`:
|
|
302
|
+
*
|
|
303
|
+
* ```ts
|
|
304
|
+
* this.originPoint[axis] += delta[axis].translate
|
|
305
|
+
* motionValue.set(motionValue.get() + delta[axis].translate)
|
|
306
|
+
* ```
|
|
307
|
+
*
|
|
308
|
+
* We do the same two-write dance: shift `origin` (the gesture's
|
|
309
|
+
* reference zero) AND the applied visual transform by the same
|
|
310
|
+
* delta, so `lastPoint - startPoint + origin` continues to resolve
|
|
311
|
+
* to the correct on-screen position after the layout slot moved.
|
|
312
|
+
* Uses `setXYImmediate` so the compensation is visible the same
|
|
313
|
+
* frame as the layout change.
|
|
314
|
+
*
|
|
315
|
+
* Not wired to any projection node in this PR — exposed on the
|
|
316
|
+
* `attachDrag` return handle for the Reorder PR (#310) to call from
|
|
317
|
+
* its `ProjectionNode.didUpdate` listener.
|
|
318
|
+
*
|
|
319
|
+
* @param dx Layout delta on the x axis (px).
|
|
320
|
+
* @param dy Layout delta on the y axis (px).
|
|
321
|
+
*/
|
|
322
|
+
const adjustOrigin = (dx, dy) => {
|
|
323
|
+
if (!dragging)
|
|
324
|
+
return;
|
|
325
|
+
// Compensate the origin on BOTH axes unconditionally — upstream's
|
|
326
|
+
// didUpdate handler applies the delta per-axis via `eachAxis`
|
|
327
|
+
// regardless of the drag axis or direction lock, because a layout
|
|
328
|
+
// slot can shift on either axis.
|
|
329
|
+
origin.x += dx;
|
|
330
|
+
origin.y += dy;
|
|
331
|
+
// The VISUAL write is `setXYImmediate`, which only writes the axis
|
|
332
|
+
// this drag manages (`opts.axis`). For the dragged axis that pins
|
|
333
|
+
// the element same-frame; the cross-axis case (e.g. drag="x" + a
|
|
334
|
+
// y-shift) only updates `origin`, not the transform. Fully
|
|
335
|
+
// rendering cross-axis compensation needs to route through the
|
|
336
|
+
// Motion value the move path uses (a direct write here would be
|
|
337
|
+
// wiped by the next `setXY`), so it's finalized when this hook is
|
|
338
|
+
// wired in #310. The common Reorder case (drag axis === the shift
|
|
339
|
+
// axis) is fully compensated today.
|
|
340
|
+
setXYImmediate(applied.x + dx, applied.y + dy);
|
|
341
|
+
};
|
|
252
342
|
const startWhileDrag = () => {
|
|
253
343
|
if (!opts.whileDrag)
|
|
254
344
|
return;
|
|
@@ -758,7 +848,7 @@ export const attachDrag = (el, opts) => {
|
|
|
758
848
|
}
|
|
759
849
|
el.addEventListener('pointerdown', onPointerDown);
|
|
760
850
|
pwLog('[drag] pointerdown listener attached', { el: EL_ID });
|
|
761
|
-
|
|
851
|
+
const teardown = () => {
|
|
762
852
|
pwLog('[drag] detach', { el: EL_ID });
|
|
763
853
|
el.removeEventListener('pointerdown', onPointerDown);
|
|
764
854
|
el.removeEventListener('pointermove', onPointerMove);
|
|
@@ -768,4 +858,5 @@ export const attachDrag = (el, opts) => {
|
|
|
768
858
|
window.removeEventListener('pointerup', onPointerUp);
|
|
769
859
|
window.removeEventListener('pointercancel', onPointerCancel);
|
|
770
860
|
};
|
|
861
|
+
return Object.assign(teardown, { adjustOrigin });
|
|
771
862
|
};
|
package/dist/utils/layout.d.ts
CHANGED
|
@@ -14,8 +14,19 @@ import { type AnimationOptions } from 'motion';
|
|
|
14
14
|
*
|
|
15
15
|
* Pass an empty array (or omit) for viewport-relative behaviour.
|
|
16
16
|
*
|
|
17
|
+
* `baseTransform` is the value the element's `transform` is set to while
|
|
18
|
+
* measuring (default `'none'`, i.e. all transforms removed). The
|
|
19
|
+
* projection system passes the element's mount-time transform here so
|
|
20
|
+
* that a user-authored static `transform` is preserved in the
|
|
21
|
+
* measurement while only the motion-applied portion (written after
|
|
22
|
+
* mount) is removed — mirroring framer-motion's `removeBoxTransforms`,
|
|
23
|
+
* which only subtracts motion-tracked `latestValues` and leaves
|
|
24
|
+
* user-authored transforms intact. Existing FLIP callers omit it and
|
|
25
|
+
* get the original strip-everything behaviour.
|
|
26
|
+
*
|
|
17
27
|
* @param el Element to measure.
|
|
18
28
|
* @param scrollContainers Optional ancestor chain with `layoutScroll` enabled.
|
|
29
|
+
* @param baseTransform Transform string applied during measurement. Defaults to `'none'`.
|
|
19
30
|
* @returns DOMRect snapshot of the element.
|
|
20
31
|
*
|
|
21
32
|
* @example
|
|
@@ -30,7 +41,20 @@ import { type AnimationOptions } from 'motion';
|
|
|
30
41
|
* const rect = measureRect(node, [innerScroll, outerScroll])
|
|
31
42
|
* ```
|
|
32
43
|
*/
|
|
33
|
-
export declare const measureRect: (el: HTMLElement, scrollContainers?: HTMLElement[]) => DOMRect;
|
|
44
|
+
export declare const measureRect: (el: HTMLElement, scrollContainers?: HTMLElement[], baseTransform?: string) => DOMRect;
|
|
45
|
+
/**
|
|
46
|
+
* Minimal rectangle shape `computeFlipTransforms` reads. A `DOMRect`
|
|
47
|
+
* satisfies it structurally, and so does a projection `Box` converted to
|
|
48
|
+
* `{ left, top, width, height }`. Declared here (rather than importing
|
|
49
|
+
* the projection `Box`) so `layout.ts` stays free of a circular
|
|
50
|
+
* dependency on `projection.ts`, which imports `measureRect` from here.
|
|
51
|
+
*/
|
|
52
|
+
export interface RectLike {
|
|
53
|
+
left: number;
|
|
54
|
+
top: number;
|
|
55
|
+
width: number;
|
|
56
|
+
height: number;
|
|
57
|
+
}
|
|
34
58
|
/**
|
|
35
59
|
* Compute FLIP transform deltas between two rects.
|
|
36
60
|
*
|
|
@@ -39,7 +63,7 @@ export declare const measureRect: (el: HTMLElement, scrollContainers?: HTMLEleme
|
|
|
39
63
|
* @param mode `true` for translate+scale, `'position'` for translate only.
|
|
40
64
|
* @return Deltas and flags indicating which transforms to apply.
|
|
41
65
|
*/
|
|
42
|
-
export declare const computeFlipTransforms: (prev:
|
|
66
|
+
export declare const computeFlipTransforms: (prev: RectLike, next: RectLike, mode: boolean | "position") => {
|
|
43
67
|
dx: number;
|
|
44
68
|
dy: number;
|
|
45
69
|
sx: number;
|
package/dist/utils/layout.js
CHANGED
|
@@ -14,8 +14,19 @@ import { animate } from 'motion';
|
|
|
14
14
|
*
|
|
15
15
|
* Pass an empty array (or omit) for viewport-relative behaviour.
|
|
16
16
|
*
|
|
17
|
+
* `baseTransform` is the value the element's `transform` is set to while
|
|
18
|
+
* measuring (default `'none'`, i.e. all transforms removed). The
|
|
19
|
+
* projection system passes the element's mount-time transform here so
|
|
20
|
+
* that a user-authored static `transform` is preserved in the
|
|
21
|
+
* measurement while only the motion-applied portion (written after
|
|
22
|
+
* mount) is removed — mirroring framer-motion's `removeBoxTransforms`,
|
|
23
|
+
* which only subtracts motion-tracked `latestValues` and leaves
|
|
24
|
+
* user-authored transforms intact. Existing FLIP callers omit it and
|
|
25
|
+
* get the original strip-everything behaviour.
|
|
26
|
+
*
|
|
17
27
|
* @param el Element to measure.
|
|
18
28
|
* @param scrollContainers Optional ancestor chain with `layoutScroll` enabled.
|
|
29
|
+
* @param baseTransform Transform string applied during measurement. Defaults to `'none'`.
|
|
19
30
|
* @returns DOMRect snapshot of the element.
|
|
20
31
|
*
|
|
21
32
|
* @example
|
|
@@ -30,10 +41,10 @@ import { animate } from 'motion';
|
|
|
30
41
|
* const rect = measureRect(node, [innerScroll, outerScroll])
|
|
31
42
|
* ```
|
|
32
43
|
*/
|
|
33
|
-
export const measureRect = (el, scrollContainers) => {
|
|
44
|
+
export const measureRect = (el, scrollContainers, baseTransform = 'none') => {
|
|
34
45
|
const prev = el.style.transform;
|
|
35
46
|
try {
|
|
36
|
-
el.style.transform =
|
|
47
|
+
el.style.transform = baseTransform;
|
|
37
48
|
const rect = el.getBoundingClientRect();
|
|
38
49
|
if (!scrollContainers || scrollContainers.length === 0)
|
|
39
50
|
return rect;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pan gesture session.
|
|
3
|
+
*
|
|
4
|
+
* Direct port of framer-motion's `PanSession`
|
|
5
|
+
* (`packages/framer-motion/src/gestures/pan/PanSession.ts`) and `PanGesture`
|
|
6
|
+
* (`packages/framer-motion/src/gestures/pan/index.ts`). Pan is the
|
|
7
|
+
* primitive that powers swipe-to-dismiss drawers, swipe-to-delete rows,
|
|
8
|
+
* carousels, and any gesture that tracks pointer offset/velocity without
|
|
9
|
+
* the constraint/momentum/snap-to-origin baggage of `drag`.
|
|
10
|
+
*
|
|
11
|
+
* Critical design notes (mirrors upstream):
|
|
12
|
+
*
|
|
13
|
+
* - `pointermove`, `pointerup`, `pointercancel` subscribe on the
|
|
14
|
+
* `contextWindow` (defaults to `window`), NOT the source element. This
|
|
15
|
+
* keeps the gesture alive even when the pointer leaves the element's
|
|
16
|
+
* bounds during a fast swipe — the original element is only used for
|
|
17
|
+
* the initial `pointerdown` and for scroll-compensation tracking.
|
|
18
|
+
*
|
|
19
|
+
* - `distanceThreshold` (default `3`px) gates the `onStart` callback so
|
|
20
|
+
* a steady press without movement doesn't fire a pan. `onSessionStart`
|
|
21
|
+
* fires immediately on pointerdown for setup work.
|
|
22
|
+
*
|
|
23
|
+
* - Per-frame throttling via `frame.update(updatePoint, true)` so a flood
|
|
24
|
+
* of pointermove events doesn't run handlers more than once per render
|
|
25
|
+
* frame. On top of that, individual handlers are routed onto motion-dom's
|
|
26
|
+
* step lanes (see `wrapUpdate` / `wrapPostRender` above):
|
|
27
|
+
* `onSessionStart` / `onStart` / `onMove` land on `update`,
|
|
28
|
+
* `onEnd` / `onSessionEnd` on `postRender`. Matches upstream's
|
|
29
|
+
* `asyncHandler` + `frame.postRender` split byte-for-byte.
|
|
30
|
+
*
|
|
31
|
+
* - `getPanInfo` returns `{ point, delta, offset, velocity }` — identical
|
|
32
|
+
* shape to motion-dom's `DragInfo` / framer-motion's `PanInfo`.
|
|
33
|
+
*
|
|
34
|
+
* - Velocity uses a 100ms history window with the "skip the pointer-down
|
|
35
|
+
* origin if it's too stale" tweak upstream added for hold-then-flick
|
|
36
|
+
* gestures.
|
|
37
|
+
*/
|
|
38
|
+
import type { DragInfo } from '../types';
|
|
39
|
+
export interface PanHandlers {
|
|
40
|
+
/** Fires on `pointerdown` regardless of whether movement follows. */
|
|
41
|
+
onSessionStart?: (event: PointerEvent, info: DragInfo) => void;
|
|
42
|
+
/** Fires the first time pointer offset crosses `distanceThreshold`. */
|
|
43
|
+
onStart?: (event: PointerEvent, info: DragInfo) => void;
|
|
44
|
+
/** Fires on every per-frame-throttled pointermove past threshold. */
|
|
45
|
+
onMove?: (event: PointerEvent, info: DragInfo) => void;
|
|
46
|
+
/** Fires on `pointerup` / `pointercancel` if `onStart` ever fired. */
|
|
47
|
+
onEnd?: (event: PointerEvent, info: DragInfo) => void;
|
|
48
|
+
/** Fires on `pointerup` / `pointercancel` always (paired with `onSessionStart`). */
|
|
49
|
+
onSessionEnd?: (event: PointerEvent, info: DragInfo) => void;
|
|
50
|
+
}
|
|
51
|
+
export interface AttachPanOptions {
|
|
52
|
+
/**
|
|
53
|
+
* Movement distance (in pixels) required before `onStart`/`onMove`
|
|
54
|
+
* fire. Default `3` — same as framer-motion. A steady press with
|
|
55
|
+
* sub-threshold drift is reported via `onSessionStart` / `onSessionEnd`
|
|
56
|
+
* only.
|
|
57
|
+
*/
|
|
58
|
+
distanceThreshold?: number;
|
|
59
|
+
/**
|
|
60
|
+
* Window to attach the move/up/cancel listeners to. Defaults to the
|
|
61
|
+
* source element's owner window. Override for iframe / shadow-root
|
|
62
|
+
* scenarios.
|
|
63
|
+
*/
|
|
64
|
+
contextWindow?: Window | null;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Cleanup function returned by `attachPan`. Carries an `update` method
|
|
68
|
+
* that hot-swaps the live handler set without tearing down the active
|
|
69
|
+
* `PanSession` — call this when a consumer's `onPan` reference changes
|
|
70
|
+
* mid-gesture (the canonical Svelte 5 pattern of inline arrow handlers
|
|
71
|
+
* passes a fresh closure every render). Without this, the host
|
|
72
|
+
* `$effect` would have to teardown + re-attach and the user's in-flight
|
|
73
|
+
* pan would silently die.
|
|
74
|
+
*/
|
|
75
|
+
export type AttachPanCleanup = (() => void) & {
|
|
76
|
+
update: (next: PanHandlers) => void;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Attach a pan gesture session to `el`. Returns a cleanup function that
|
|
80
|
+
* tears down the pointerdown listener and ends any in-flight session,
|
|
81
|
+
* with a `.update(next)` method for hot-swapping handlers mid-gesture.
|
|
82
|
+
*
|
|
83
|
+
* Internally a fresh `PanSession` spawns on each pointerdown — the
|
|
84
|
+
* outer attachment just keeps the pointerdown listener alive across the
|
|
85
|
+
* element's lifetime.
|
|
86
|
+
*
|
|
87
|
+
* SSR-safe: returns a no-op cleanup if `window` is undefined. The Svelte
|
|
88
|
+
* `$effect` consumer never fires on the server anyway, but defending the
|
|
89
|
+
* boundary lets the module load cleanly in node-only test runners.
|
|
90
|
+
*
|
|
91
|
+
* Lifecycle guarantee: when the returned cleanup runs mid-gesture, the
|
|
92
|
+
* session synthesizes `onEnd` + `onSessionEnd` against the raw handlers
|
|
93
|
+
* BEFORE removing listeners (see `PanSession.dispatchTerminal`). Hosts
|
|
94
|
+
* (e.g. `_MotionContainer`'s pan `$effect`) can put their `whilePan`
|
|
95
|
+
* revert logic inside the user-supplied `onEnd` and rely on it firing
|
|
96
|
+
* exactly once per gesture — whether the user released or the host
|
|
97
|
+
* forced teardown.
|
|
98
|
+
*
|
|
99
|
+
* @param el Target element to bind `pointerdown` on. Move/up/cancel
|
|
100
|
+
* events are listened for on the element's owning window so a fast
|
|
101
|
+
* swipe past the element's bounds keeps the gesture alive.
|
|
102
|
+
* @param handlers Pan lifecycle handlers. Any subset of
|
|
103
|
+
* `onSessionStart` (fires on pointerdown), `onStart` (fires the first
|
|
104
|
+
* time the cumulative offset crosses `distanceThreshold`), `onMove`
|
|
105
|
+
* (per-frame-throttled on every pointermove past threshold), `onEnd`
|
|
106
|
+
* (fires on pointerup/cancel if `onStart` ever fired), `onSessionEnd`
|
|
107
|
+
* (fires on every pointerup/cancel where a pointermove occurred).
|
|
108
|
+
* @param options Per-session config. `distanceThreshold` (default 3px)
|
|
109
|
+
* gates the start callback; `contextWindow` overrides the owning
|
|
110
|
+
* window (use for shadow-root / iframe scenarios).
|
|
111
|
+
* @returns A cleanup function with an attached `.update(next)` method.
|
|
112
|
+
* Calling the cleanup ends the session + removes the pointerdown
|
|
113
|
+
* listener. Calling `.update(next)` swaps handlers in place on the
|
|
114
|
+
* live session without rebuilding it — the canonical Svelte pattern
|
|
115
|
+
* for inline arrow handlers that change identity each render.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```ts
|
|
119
|
+
* const cleanup = attachPan(node, {
|
|
120
|
+
* onStart: (_event, info) => console.log('start', info.offset),
|
|
121
|
+
* onMove: (_event, info) => x.set(info.offset.x),
|
|
122
|
+
* onEnd: (_event, info) => {
|
|
123
|
+
* if (Math.abs(info.velocity.x) > 600) commit()
|
|
124
|
+
* else animate(x, 0, { type: 'spring' })
|
|
125
|
+
* }
|
|
126
|
+
* })
|
|
127
|
+
*
|
|
128
|
+
* // Later, swap handlers without ending the live gesture:
|
|
129
|
+
* cleanup.update({ onMove: (_e, info) => x.set(info.offset.x * 2) })
|
|
130
|
+
*
|
|
131
|
+
* // On unmount:
|
|
132
|
+
* cleanup()
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
export declare const attachPan: (el: HTMLElement, handlers: PanHandlers, options?: AttachPanOptions) => AttachPanCleanup;
|