solidjs-motion 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CHANGELOG.md +117 -0
  2. package/LICENSE +21 -0
  3. package/README.md +140 -0
  4. package/dist/index.d.ts +2 -0
  5. package/dist/index.js +2627 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/src/default-values.d.ts +6 -0
  8. package/dist/src/index.d.ts +16 -0
  9. package/dist/src/motion-config.d.ts +14 -0
  10. package/dist/src/motion-proxy.d.ts +103 -0
  11. package/dist/src/presence-context.d.ts +4 -0
  12. package/dist/src/presence.d.ts +95 -0
  13. package/dist/src/primitives/createDrag.d.ts +16 -0
  14. package/dist/src/primitives/createDragControls.d.ts +30 -0
  15. package/dist/src/primitives/createGestures.d.ts +8 -0
  16. package/dist/src/primitives/createInView.d.ts +51 -0
  17. package/dist/src/primitives/createMotion.d.ts +82 -0
  18. package/dist/src/primitives/createPan.d.ts +83 -0
  19. package/dist/src/primitives/createScroll.d.ts +40 -0
  20. package/dist/src/primitives/gesture-state.d.ts +108 -0
  21. package/dist/src/primitives/motion-value.d.ts +111 -0
  22. package/dist/src/primitives/value-registry.d.ts +34 -0
  23. package/dist/src/reduced-motion.d.ts +29 -0
  24. package/dist/src/style.d.ts +79 -0
  25. package/dist/src/types.d.ts +374 -0
  26. package/dist/src/use-motion.d.ts +35 -0
  27. package/dist/src/variants.d.ts +64 -0
  28. package/package.json +78 -0
  29. package/src/default-values.ts +52 -0
  30. package/src/index.ts +60 -0
  31. package/src/motion-config.tsx +37 -0
  32. package/src/motion-proxy.tsx +377 -0
  33. package/src/presence-context.ts +32 -0
  34. package/src/presence.tsx +466 -0
  35. package/src/primitives/createDrag.ts +670 -0
  36. package/src/primitives/createDragControls.ts +101 -0
  37. package/src/primitives/createGestures.ts +145 -0
  38. package/src/primitives/createInView.ts +124 -0
  39. package/src/primitives/createMotion.ts +638 -0
  40. package/src/primitives/createPan.ts +338 -0
  41. package/src/primitives/createScroll.ts +101 -0
  42. package/src/primitives/gesture-state.ts +772 -0
  43. package/src/primitives/motion-value.ts +328 -0
  44. package/src/primitives/value-registry.ts +114 -0
  45. package/src/reduced-motion.ts +51 -0
  46. package/src/style.ts +266 -0
  47. package/src/types.ts +538 -0
  48. package/src/use-motion.tsx +412 -0
  49. package/src/variants.ts +134 -0
package/dist/index.js ADDED
@@ -0,0 +1,2627 @@
1
+ import { animate, animate as animate$1, cancelFrame, frame, inView, isMotionValue, isMotionValue as isMotionValue$1, motionValue, motionValue as motionValue$1, scroll, scroll as scroll$1, spring, springValue, transform } from "motion";
2
+ import { Dynamic, createComponent, mergeProps } from "solid-js/web";
3
+ import { Match, Switch, createComputed, createContext, createEffect, createMemo, createSignal, from, mergeProps as mergeProps$1, onCleanup, onMount, splitProps, untrack, useContext } from "solid-js";
4
+ import { mergeRefs, resolveElements } from "@solid-primitives/refs";
5
+ import { createStore } from "solid-js/store";
6
+ import { HTMLVisualElement, addDomEvent, hover, isPrimaryPointer, press, time, visualElementStore } from "motion-dom";
7
+ import { createListTransition, createSwitchTransition } from "@solid-primitives/transition-group";
8
+ var MotionConfigContext = createContext({
9
+ reducedMotion: () => "never",
10
+ transition: () => void 0,
11
+ nonce: () => void 0
12
+ });
13
+ function useMotionConfig() {
14
+ return useContext(MotionConfigContext);
15
+ }
16
+ /**
17
+ * Provider that flows reduced-motion mode, default transition, and CSP nonce
18
+ * to every descendant motion element.
19
+ *
20
+ * @example
21
+ * <MotionConfig reducedMotion="user" transition={{ duration: 0.4 }}>
22
+ * <App />
23
+ * </MotionConfig>
24
+ */
25
+ function MotionConfig(props) {
26
+ const value = {
27
+ reducedMotion: createMemo(() => props.reducedMotion ?? "never"),
28
+ transition: createMemo(() => props.transition),
29
+ nonce: createMemo(() => props.nonce)
30
+ };
31
+ return createComponent(MotionConfigContext.Provider, {
32
+ value,
33
+ get children() {
34
+ return props.children;
35
+ }
36
+ });
37
+ }
38
+ var PresenceContext = createContext({
39
+ register: () => {},
40
+ unregister: () => {},
41
+ beforeUnmount: () => Promise.resolve()
42
+ });
43
+ function usePresenceContext() {
44
+ return useContext(PresenceContext);
45
+ }
46
+ //#endregion
47
+ //#region src/reduced-motion.ts
48
+ /**
49
+ * A reactive `Accessor<boolean>` tracking the user's
50
+ * `prefers-reduced-motion: reduce` media query.
51
+ *
52
+ * Returns `false` server-side (no `window.matchMedia`). On the client, it seeds
53
+ * with the current match state and updates as the system preference toggles.
54
+ * The matchMedia listener is removed automatically on owner disposal via
55
+ * `from`'s teardown callback.
56
+ *
57
+ * @example
58
+ * const reduced = createReducedMotion()
59
+ * createEffect(() => {
60
+ * if (reduced()) console.log("user prefers reduced motion")
61
+ * })
62
+ */
63
+ function createReducedMotion() {
64
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return () => false;
65
+ const mql = window.matchMedia("(prefers-reduced-motion: reduce)");
66
+ return from((set) => {
67
+ set(mql.matches);
68
+ const handler = (e) => set(e.matches);
69
+ mql.addEventListener("change", handler);
70
+ return () => mql.removeEventListener("change", handler);
71
+ });
72
+ }
73
+ /**
74
+ * Compute the effective reduced-motion state by combining a {@link MotionConfig}
75
+ * `reducedMotion` setting with the system preference.
76
+ *
77
+ * - `"always"` — forced reduced, regardless of system pref
78
+ * - `"never"` — never reduced, regardless of system pref
79
+ * - `"user"` — respect system pref
80
+ *
81
+ * @example
82
+ * const reduced = shouldReduceMotion("user", createReducedMotion()())
83
+ */
84
+ function shouldReduceMotion(configValue, systemReduced) {
85
+ if (configValue === "always") return true;
86
+ if (configValue === "never") return false;
87
+ return systemReduced;
88
+ }
89
+ //#endregion
90
+ //#region src/style.ts
91
+ /**
92
+ * Set of CSS shortcut keys motion treats as transform components. Re-used by
93
+ * `createMotion`'s Stage 3 animate bridge to decide whether an animate-target
94
+ * key should be routed through the value registry (composed via the writer's
95
+ * `el.style.transform =`) or sent down the existing WAA path.
96
+ */
97
+ var TRANSFORM_KEYS = /* @__PURE__ */ new Set([
98
+ "x",
99
+ "y",
100
+ "z",
101
+ "scale",
102
+ "scaleX",
103
+ "scaleY",
104
+ "scaleZ",
105
+ "rotate",
106
+ "rotateX",
107
+ "rotateY",
108
+ "rotateZ",
109
+ "skew",
110
+ "skewX",
111
+ "skewY",
112
+ "transformPerspective"
113
+ ]);
114
+ /** Order matters — motion composes transforms in this sequence (Q5 sub-1). */
115
+ var TRANSFORM_ORDER = [
116
+ "x",
117
+ "y",
118
+ "z",
119
+ "scale",
120
+ "scaleX",
121
+ "scaleY",
122
+ "scaleZ",
123
+ "rotate",
124
+ "rotateX",
125
+ "rotateY",
126
+ "rotateZ",
127
+ "skew",
128
+ "skewX",
129
+ "skewY",
130
+ "transformPerspective"
131
+ ];
132
+ var PX_PROPERTIES = /* @__PURE__ */ new Set([
133
+ "width",
134
+ "minWidth",
135
+ "maxWidth",
136
+ "height",
137
+ "minHeight",
138
+ "maxHeight",
139
+ "top",
140
+ "right",
141
+ "bottom",
142
+ "left",
143
+ "padding",
144
+ "paddingTop",
145
+ "paddingRight",
146
+ "paddingBottom",
147
+ "paddingLeft",
148
+ "paddingInline",
149
+ "paddingBlock",
150
+ "paddingInlineStart",
151
+ "paddingInlineEnd",
152
+ "paddingBlockStart",
153
+ "paddingBlockEnd",
154
+ "margin",
155
+ "marginTop",
156
+ "marginRight",
157
+ "marginBottom",
158
+ "marginLeft",
159
+ "marginInline",
160
+ "marginBlock",
161
+ "marginInlineStart",
162
+ "marginInlineEnd",
163
+ "marginBlockStart",
164
+ "marginBlockEnd",
165
+ "borderWidth",
166
+ "borderTopWidth",
167
+ "borderRightWidth",
168
+ "borderBottomWidth",
169
+ "borderLeftWidth",
170
+ "borderRadius",
171
+ "borderTopLeftRadius",
172
+ "borderTopRightRadius",
173
+ "borderBottomLeftRadius",
174
+ "borderBottomRightRadius",
175
+ "gap",
176
+ "rowGap",
177
+ "columnGap",
178
+ "fontSize",
179
+ "outlineWidth",
180
+ "outlineOffset"
181
+ ]);
182
+ /**
183
+ * Reduce a target-value (which may be raw, a MotionValue, an Accessor, or a
184
+ * keyframe array) to a concrete leaf value the writer can apply to the DOM
185
+ * or use to initialize a transient MotionValue. The cascade follows motion's
186
+ * own semantics:
187
+ *
188
+ * - `null` / `undefined` → `undefined` (caller drops the key)
189
+ * - keyframe array → first frame (consistent with motion-vanilla's
190
+ * initial-style snapshot)
191
+ * - `MotionValue` → its current `.get()`
192
+ * - `Accessor` (a bare zero-arg function) → its invocation result
193
+ * - primitive (string / number) → returned as-is
194
+ *
195
+ * Exported for the MV-in-style Stage 4 work: createMotion uses it to
196
+ * snapshot initial-target entries when registering them into the value
197
+ * registry as transient MVs.
198
+ */
199
+ function snapshotValue(value) {
200
+ if (value === null || value === void 0) return void 0;
201
+ if (Array.isArray(value)) return snapshotValue(value[0]);
202
+ if (isMotionValue$1(value)) return snapshotValue(value.get());
203
+ if (typeof value === "function") return snapshotValue(value());
204
+ if (typeof value === "string" || typeof value === "number") return value;
205
+ }
206
+ var TRANSFORM_FORMATTERS = {
207
+ x: (v) => `translateX(${typeof v === "string" ? v : `${v}px`})`,
208
+ y: (v) => `translateY(${typeof v === "string" ? v : `${v}px`})`,
209
+ z: (v) => `translateZ(${typeof v === "string" ? v : `${v}px`})`,
210
+ scale: (v) => `scale(${v})`,
211
+ scaleX: (v) => `scaleX(${v})`,
212
+ scaleY: (v) => `scaleY(${v})`,
213
+ scaleZ: (v) => `scaleZ(${v})`,
214
+ rotate: (v) => `rotate(${typeof v === "string" ? v : `${v}deg`})`,
215
+ rotateX: (v) => `rotateX(${typeof v === "string" ? v : `${v}deg`})`,
216
+ rotateY: (v) => `rotateY(${typeof v === "string" ? v : `${v}deg`})`,
217
+ rotateZ: (v) => `rotateZ(${typeof v === "string" ? v : `${v}deg`})`,
218
+ skew: (v) => `skew(${typeof v === "string" ? v : `${v}deg`})`,
219
+ skewX: (v) => `skewX(${typeof v === "string" ? v : `${v}deg`})`,
220
+ skewY: (v) => `skewY(${typeof v === "string" ? v : `${v}deg`})`,
221
+ transformPerspective: (v) => `perspective(${typeof v === "string" ? v : `${v}px`})`
222
+ };
223
+ /**
224
+ * Look up the formatter for a transform-shortcut key. Returns `undefined`
225
+ * for non-transform keys; callers should check `TRANSFORM_KEYS.has(key)`
226
+ * before assuming a formatter exists.
227
+ */
228
+ function pickTransformFormatter(key) {
229
+ return TRANSFORM_FORMATTERS[key];
230
+ }
231
+ /**
232
+ * Format a motion transform-shortcut key + value as the corresponding CSS
233
+ * transform function string (e.g. `transformFunctionFor("scale", 1.05)`
234
+ * → `"scale(1.05)"`). One-shot variant — for hot paths, use
235
+ * `pickTransformFormatter(key)` once at compile time and reuse.
236
+ */
237
+ function transformFunctionFor(key, value) {
238
+ return TRANSFORM_FORMATTERS[key]?.(value) ?? "";
239
+ }
240
+ /**
241
+ * Format a non-transform CSS property's value (e.g. `formatProperty("width", 100)`
242
+ * → `"100px"`, `formatProperty("opacity", 0.5)` → `0.5`). Applies motion's
243
+ * default-unit table (PX for dimensional CSS, dimensionless otherwise);
244
+ * leaves CSS variables alone. Exported for `createMotion`'s writer fast path.
245
+ */
246
+ function formatProperty(key, value) {
247
+ if (typeof value === "string") return value;
248
+ if (key.startsWith("--")) return String(value);
249
+ if (PX_PROPERTIES.has(key)) return `${value}px`;
250
+ return value;
251
+ }
252
+ /**
253
+ * Convert a {@link Target} to a Solid {@link JSX.CSSProperties} object.
254
+ *
255
+ * - Composes transform shorthand (`x`, `y`, `scale`, `rotate`, etc.) into a
256
+ * single `transform: "..."` string in motion's canonical order.
257
+ * - Applies the default-unit table (px for dimensional CSS, deg for rotate/
258
+ * skew, dimensionless for scale/opacity/etc.).
259
+ * - For keyframe arrays, uses the first frame; a leading `null`/`undefined`
260
+ * keyframe omits the property entirely.
261
+ * - MotionValues and Solid Accessors are snapshotted at call time. Callers
262
+ * wrap in `untrack` if they don't want the read to subscribe.
263
+ * - Skips the `transition` key (animation config, not style).
264
+ * - CSS variables (`--foo`) emit raw values, no unit guess.
265
+ *
266
+ * @example
267
+ * targetToStyle({ x: 100, scale: 0.9, opacity: 0.5 })
268
+ * // { transform: "translateX(100px) scale(0.9)", opacity: 0.5 }
269
+ */
270
+ function targetToStyle(target) {
271
+ const out = {};
272
+ const transforms = {};
273
+ for (const key in target) {
274
+ if (key === "transition") continue;
275
+ const raw = target[key];
276
+ const snapshot = snapshotValue(raw);
277
+ if (snapshot === void 0) continue;
278
+ if (TRANSFORM_KEYS.has(key)) transforms[key] = snapshot;
279
+ else out[key] = formatProperty(key, snapshot);
280
+ }
281
+ const parts = [];
282
+ for (const key of TRANSFORM_ORDER) if (key in transforms) parts.push(transformFunctionFor(key, transforms[key]));
283
+ if (parts.length > 0) out.transform = parts.join(" ");
284
+ return out;
285
+ }
286
+ /**
287
+ * Solid context propagating variant state from a motion ancestor to its
288
+ * descendants. Only the wrapper components (`<motion.div>`, `motion(...)`)
289
+ * provide a value. Bare `useMotion` consumers can opt in via the `Provider`
290
+ * returned alongside the getter.
291
+ */
292
+ var VariantContext = createContext({});
293
+ function useVariantContext() {
294
+ return useContext(VariantContext);
295
+ }
296
+ /**
297
+ * Resolve a variant label (or array of labels) against a variants map and the
298
+ * current `custom` value. Returns a {@link Target} object, or `null` if nothing
299
+ * resolves.
300
+ *
301
+ * Resolution rules locked in Phase 1 Q4:
302
+ *
303
+ * - Child's own `variants` always wins for a given name (sub-1A).
304
+ * - No cascade: if a child has no `variants` of its own, parent's are NOT
305
+ * consulted (sub-1B / Pattern X). Callers pass `variants = undefined` in
306
+ * that case and this returns `null`.
307
+ * - String + array forms both supported; array variants merge in order
308
+ * (later wins on conflicting keys).
309
+ * - Function variants are invoked with `custom`; the value can be any type.
310
+ *
311
+ * @example
312
+ * const variants = { visible: { opacity: 1 }, hidden: { opacity: 0 } }
313
+ * resolveVariant("visible", variants, undefined)
314
+ * // { opacity: 1 }
315
+ *
316
+ * resolveVariant(["visible", "highlighted"], variants, undefined)
317
+ * // merged in order, last variant's keys override
318
+ *
319
+ * resolveVariant("visible", { visible: (i: number) => ({ x: i * 10 }) }, 3)
320
+ * // { x: 30 }
321
+ */
322
+ function resolveVariant(names, variants, custom) {
323
+ if (!names || !variants) return null;
324
+ const list = Array.isArray(names) ? names : [names];
325
+ let merged = null;
326
+ for (const name of list) {
327
+ const variant = variants[name];
328
+ if (!variant) continue;
329
+ const resolved = typeof variant === "function" ? variant(custom) : variant;
330
+ merged = merged ? Object.assign({}, merged, resolved) : Object.assign({}, resolved);
331
+ }
332
+ return merged;
333
+ }
334
+ /**
335
+ * Determine the effective variant name for a given motion state. If the caller
336
+ * provided an explicit value (string, array, or Target object), that wins.
337
+ * Otherwise the parent context's value (per gesture/state) is used as a fall-
338
+ * back.
339
+ *
340
+ * Returns the explicit value as-is when it's a Target object (used by callers
341
+ * to detect "explicit target — skip variant lookup entirely").
342
+ */
343
+ function effectiveLabels(own, parent) {
344
+ if (own !== void 0) return own;
345
+ return parent;
346
+ }
347
+ /**
348
+ * Determine whether an `AnimateValue` is a variant *label* (string or array
349
+ * of strings) — as opposed to a `Target` object or `false` / `undefined`.
350
+ *
351
+ * Mirrors motion-dom's `isVariantLabel`. Used by `isControllingVariants`
352
+ * to decide whether a prop opts the node into the "controlling" role.
353
+ */
354
+ function isVariantLabelValue(v) {
355
+ if (typeof v === "string") return true;
356
+ if (Array.isArray(v)) return true;
357
+ return false;
358
+ }
359
+ /**
360
+ * A motion node is "controlling variants" when any of its variant slots
361
+ * (`initial`, `animate`, `hover`, `press`, `focus`, `inView`, `exit`) carries
362
+ * a variant *label* (string or array of strings).
363
+ *
364
+ * Mirrors motion-dom's same-named check. A controlling node opts OUT of
365
+ * inheriting its parent's variant cascade — it provides its own. Descendants
366
+ * with no controlling props of their own DO inherit from the nearest
367
+ * controlling ancestor.
368
+ *
369
+ * Behavior is binary (any single controlling-slot label flips the node into
370
+ * controlling mode); not per-slot.
371
+ *
372
+ * Object-shaped values (`animate: \{ x: 100 \}`) do NOT make a node
373
+ * controlling — they're treated as targets, not as variant references.
374
+ */
375
+ function isControllingVariants(opts) {
376
+ return isVariantLabelValue(opts.initial) || isVariantLabelValue(opts.animate) || isVariantLabelValue(opts.hover) || isVariantLabelValue(opts.press) || isVariantLabelValue(opts.focus) || isVariantLabelValue(opts.inView) || isVariantLabelValue(opts.exit);
377
+ }
378
+ //#endregion
379
+ //#region src/primitives/createDragControls.ts
380
+ /**
381
+ * Symbol used to attach the registration method on the controls object.
382
+ * `createDrag` imports this symbol to find/register its handler. Userland
383
+ * code never touches it — exported only for library-internal use.
384
+ */
385
+ var DRAG_CONTROLS_REGISTER = Symbol("solidjs-motion.dragControls.register");
386
+ /**
387
+ * Create a controls instance for imperatively starting a drag on a motion
388
+ * element from a different element (e.g., a drag-handle button).
389
+ *
390
+ * Pattern (Q9):
391
+ *
392
+ * @example
393
+ * function Card() {
394
+ * const controls = createDragControls()
395
+ * const m = useMotion({ drag: "y", dragControls: controls })
396
+ * return (
397
+ * <div {...m()}>
398
+ * <button onPointerDown={(e) => controls.start(e)}>handle</button>
399
+ * Card body
400
+ * </div>
401
+ * )
402
+ * }
403
+ *
404
+ * The handle's pointerdown fires `controls.start(event)`. createDrag is
405
+ * registered with the controls and translates the call into a pan-session
406
+ * synthesized from the handle's event, bypassing the threshold gate.
407
+ */
408
+ function createDragControls() {
409
+ let handler = null;
410
+ const controls = {
411
+ start(event, options = {}) {
412
+ handler?.(event, options);
413
+ },
414
+ [DRAG_CONTROLS_REGISTER]: void 0
415
+ };
416
+ Object.defineProperty(controls, DRAG_CONTROLS_REGISTER, {
417
+ value: ((newHandler) => {
418
+ handler = newHandler;
419
+ return () => {
420
+ if (handler === newHandler) handler = null;
421
+ };
422
+ }),
423
+ enumerable: false,
424
+ writable: false,
425
+ configurable: false
426
+ });
427
+ return controls;
428
+ }
429
+ //#endregion
430
+ //#region src/primitives/motion-value.ts
431
+ function makeAccessor(mv) {
432
+ const [signal, setSignal] = createSignal(mv.get());
433
+ onCleanup(mv.on("change", (v) => setSignal(() => v)));
434
+ const fn = (() => signal());
435
+ return new Proxy(fn, {
436
+ get(target, prop, receiver) {
437
+ if (prop === "call" || prop === "apply" || prop === "bind") return Reflect.get(target, prop, receiver);
438
+ if (Reflect.has(target, prop)) return Reflect.get(target, prop, receiver);
439
+ const value = Reflect.get(mv, prop, mv);
440
+ return typeof value === "function" ? value.bind(mv) : value;
441
+ },
442
+ has(target, prop) {
443
+ return Reflect.has(target, prop) || prop in mv;
444
+ }
445
+ });
446
+ }
447
+ /**
448
+ * Create a {@link MotionValueAccessor} bound to the current reactive scope.
449
+ *
450
+ * The returned value has two access patterns:
451
+ *
452
+ * - `mv()` — invoke as a Solid Accessor. Tracks in JSX, `createEffect`,
453
+ * `createMemo`, etc.
454
+ * - `mv.get()` / `mv.set(v)` / `mv.jump(v)` / `mv.on(...)` — the full upstream
455
+ * {@link MotionValue} surface. Matches motion/react idioms.
456
+ *
457
+ * The same value can be passed as a target in
458
+ * `useMotion({ animate: { x: mv } })` (motion engine sees `.getVelocity` via
459
+ * the Proxy and treats it as a motion value) or directly as the target of
460
+ * `animate(mv, 100)`.
461
+ *
462
+ * Auto-destroyed via `onCleanup` when the owner is disposed.
463
+ *
464
+ * @example
465
+ * const x = createMotionValue(0)
466
+ * x.set(100)
467
+ * animate(x, 200, { duration: 0.5 })
468
+ * <p>{x()}</p> // reactive read in JSX
469
+ */
470
+ function createMotionValue(initial) {
471
+ const accessor = makeAccessor(motionValue$1(initial));
472
+ onCleanup(() => accessor.destroy());
473
+ return accessor;
474
+ }
475
+ /**
476
+ * Bridge a raw {@link MotionValue} (from motion's `motionValue()` factory or
477
+ * any other motion API that doesn't return our hybrid) to a Solid
478
+ * {@link Accessor}. Seeds with the current value and updates on every
479
+ * `change` event.
480
+ *
481
+ * **You usually don't need this.** Values returned by `createMotionValue`,
482
+ * `createTransform`, `createSpring`, `createTime`, `createVelocity`, and
483
+ * `createTemplate` are already callable — you can do `mv()` directly. Reach
484
+ * for `toSignal` only when you receive a raw MotionValue from an external API.
485
+ *
486
+ * @example
487
+ * import { motionValue } from "motion"
488
+ * const rawMv = motionValue(0)
489
+ * const xSignal = toSignal(rawMv)
490
+ */
491
+ function toSignal(mv) {
492
+ const [value, setValue] = createSignal(mv.get());
493
+ onCleanup(mv.on("change", (v) => setValue(() => v)));
494
+ return value;
495
+ }
496
+ /**
497
+ * Subscribe to a {@link MotionValue} event with automatic cleanup. Convenience
498
+ * wrapper around `mv.on(event, cb)` for parity with motion/react's
499
+ * `useMotionValueEvent`. For per-change reactivity, prefer
500
+ * `createComputed(() => fn(mv()))` since hybrids are directly callable.
501
+ *
502
+ * @example
503
+ * const x = createMotionValue(0)
504
+ * createMotionValueEvent(x, "animationComplete", () => console.log("done"))
505
+ */
506
+ function createMotionValueEvent(mv, event, callback) {
507
+ onCleanup(mv.on(event, callback));
508
+ }
509
+ function readInputValue(input) {
510
+ if (isMotionValue$1(input)) return input.get();
511
+ return input();
512
+ }
513
+ function subscribeInput(input, onChange) {
514
+ if (isMotionValue$1(input)) onCleanup(input.on("change", onChange));
515
+ else createComputed(() => onChange(input()));
516
+ }
517
+ /**
518
+ * Create a {@link MotionValueAccessor} that maps an input through a range/
519
+ * output pair. Mirrors motion/react's `useTransform`. The input can be a
520
+ * MotionValue, our hybrid, or any Solid Accessor; the output composes with
521
+ * `animate()`, `useMotion`'s targets, and JSX reactivity.
522
+ *
523
+ * @example
524
+ * const { scrollY } = createScroll()
525
+ * const opacity = createTransform(scrollY, [0, 200], [1, 0])
526
+ * <div style={{ opacity: opacity() }}>...</div>
527
+ */
528
+ function createTransform(input, inputRange, outputRange, options) {
529
+ const mapper = transform(inputRange, outputRange, options);
530
+ const mv = motionValue$1(mapper(readInputValue(input)));
531
+ onCleanup(() => mv.destroy());
532
+ subscribeInput(input, (v) => mv.set(mapper(v)));
533
+ return makeAccessor(mv);
534
+ }
535
+ /**
536
+ * Spring-smoothed mirror of a numeric input. Returns a
537
+ * {@link MotionValueAccessor} that tracks the source with physics-based easing.
538
+ *
539
+ * @example
540
+ * const x = createMotionValue(0)
541
+ * const smoothX = createSpring(x, { stiffness: 100, damping: 20 })
542
+ */
543
+ function createSpring(source, options) {
544
+ if (isMotionValue$1(source)) {
545
+ const mv = springValue(source, options);
546
+ onCleanup(() => mv.destroy());
547
+ return makeAccessor(mv);
548
+ }
549
+ const bridge = motionValue$1(source());
550
+ onCleanup(() => bridge.destroy());
551
+ createComputed(() => bridge.set(source()));
552
+ const mv = springValue(bridge, options);
553
+ onCleanup(() => mv.destroy());
554
+ return makeAccessor(mv);
555
+ }
556
+ /**
557
+ * {@link MotionValueAccessor} that advances every animation frame, holding
558
+ * the milliseconds elapsed since this primitive was called. Driver for
559
+ * time-based animations and {@link createTransform}-derived values.
560
+ *
561
+ * @example
562
+ * const t = createTime()
563
+ * const wobble = createTransform(t, [0, 1000, 2000], [0, 10, 0])
564
+ */
565
+ function createTime() {
566
+ const mv = motionValue$1(0);
567
+ onCleanup(() => mv.destroy());
568
+ const startedAt = performance.now();
569
+ const tick = () => mv.set(performance.now() - startedAt);
570
+ frame.update(tick, true);
571
+ onCleanup(() => cancelFrame(tick));
572
+ return makeAccessor(mv);
573
+ }
574
+ /**
575
+ * {@link MotionValueAccessor} reporting the velocity of a source motion value.
576
+ * Updated whenever the source changes.
577
+ *
578
+ * @example
579
+ * const x = createMotionValue(0)
580
+ * const xVelocity = createVelocity(x)
581
+ */
582
+ function createVelocity(source) {
583
+ const mv = motionValue$1(source.getVelocity());
584
+ onCleanup(() => mv.destroy());
585
+ onCleanup(source.on("change", () => mv.set(source.getVelocity())));
586
+ return makeAccessor(mv);
587
+ }
588
+ /**
589
+ * Tagged template producing a {@link MotionValueAccessor}\<string\>.
590
+ * Interpolated {@link MotionValue}s, hybrids, and Solid Accessors recompute
591
+ * the output string on change; primitives and static strings are baked in.
592
+ *
593
+ * @example
594
+ * const x = createMotionValue(0)
595
+ * const y = createMotionValue(0)
596
+ * const transform = createTemplate`translate(${x}px, ${y}px) scale(1.1)`
597
+ * <div style={{ transform: transform() }} />
598
+ */
599
+ function createTemplate(strings, ...values) {
600
+ const compute = () => {
601
+ let out = "";
602
+ for (let i = 0; i < strings.length; i++) {
603
+ out += strings[i];
604
+ if (i < values.length) {
605
+ const v = values[i];
606
+ if (isMotionValue$1(v)) out += String(v.get());
607
+ else if (typeof v === "function") out += String(v());
608
+ else out += String(v);
609
+ }
610
+ }
611
+ return out;
612
+ };
613
+ const mv = motionValue$1(compute());
614
+ onCleanup(() => mv.destroy());
615
+ for (const v of values) if (isMotionValue$1(v)) onCleanup(v.on("change", () => mv.set(compute())));
616
+ if (values.some((v) => typeof v === "function" && !isMotionValue$1(v))) createComputed(() => {
617
+ for (const v of values) if (typeof v === "function" && !isMotionValue$1(v)) v();
618
+ mv.set(compute());
619
+ });
620
+ return makeAccessor(mv);
621
+ }
622
+ //#endregion
623
+ //#region src/primitives/createPan.ts
624
+ /** Sliding-window width for velocity computation (Q15a). */
625
+ var VELOCITY_WINDOW_MS = 200;
626
+ /** Default movement threshold before onPanStart fires (Q11a, matches motion). */
627
+ var DEFAULT_THRESHOLD = 3;
628
+ /**
629
+ * Observe pointer-driven pan gestures on an element.
630
+ *
631
+ * Returns `{ isPanning, point, delta, offset, velocity }`:
632
+ *
633
+ * - `pan.isPanning()` — Solid Accessor; `true` between onPanStart and onPanEnd.
634
+ * - `pan.point.x`, `pan.point.y` — current pointer position in client coords.
635
+ * Each is a {@link MotionValueAccessor}: call `pan.point.x()` for a tracked
636
+ * read, `pan.point.x.get()` for an untracked snapshot, and pass it directly
637
+ * to `animate()`, `createTransform`, or `useMotion` targets.
638
+ * - `pan.delta.x/y` — delta since last pointermove.
639
+ * - `pan.offset.x/y` — cumulative offset since the current pointerdown.
640
+ * - `pan.velocity.x/y` — sliding-window velocity in px/sec.
641
+ *
642
+ * Fields update from `pointerdown` forward (including pre-threshold moves)
643
+ * — gate reads on `pan.isPanning()` if you only care about real pans.
644
+ *
645
+ * The `options` argument accepts either a static object or a function form
646
+ * (matching `useMotion`'s convention). The function form is read INSIDE
647
+ * each pointer-event handler, so reactive option changes apply on the next
648
+ * relevant event without re-attaching listeners.
649
+ *
650
+ * @example Static options
651
+ * const pan = createPan(el, {
652
+ * onPanStart: (e, info) => console.log("start", info.point),
653
+ * threshold: 3,
654
+ * })
655
+ *
656
+ * @example Reactive options (function form — signals tracked)
657
+ * const [threshold, setThreshold] = createSignal(3)
658
+ * const pan = createPan(el, () => ({
659
+ * threshold: threshold(),
660
+ * onPanStart: (e, info) => console.log(info),
661
+ * }))
662
+ *
663
+ * @example Composing pan.point.x with createTransform
664
+ * const pan = createPan(el)
665
+ * const rotation = createTransform(pan.point.x, [0, 300], [0, 90])
666
+ * <div ref={setEl} style={{ transform: `rotate(${rotation()}deg)` }} />
667
+ *
668
+ * @example Reading reactively in JSX
669
+ * const pan = createPan(el)
670
+ * <Show when={pan.isPanning()}>
671
+ * Position: {pan.point.x()}, {pan.point.y()}
672
+ * </Show>
673
+ */
674
+ function createPan(ref, options = {}) {
675
+ const getOpts = typeof options === "function" ? options : () => options;
676
+ const [isPanning, setIsPanning] = createSignal(false);
677
+ const pointX = createMotionValue(0);
678
+ const pointY = createMotionValue(0);
679
+ const deltaX = createMotionValue(0);
680
+ const deltaY = createMotionValue(0);
681
+ const offsetX = createMotionValue(0);
682
+ const offsetY = createMotionValue(0);
683
+ const velocityX = createMotionValue(0);
684
+ const velocityY = createMotionValue(0);
685
+ createEffect(() => {
686
+ const el = ref();
687
+ if (!el) return;
688
+ let startPoint = null;
689
+ let lastPoint = null;
690
+ let pointerId = null;
691
+ let panning = false;
692
+ let samples = [];
693
+ function pointOf(event) {
694
+ return {
695
+ x: event.clientX,
696
+ y: event.clientY
697
+ };
698
+ }
699
+ function computeVelocity() {
700
+ if (samples.length < 2) return {
701
+ x: 0,
702
+ y: 0
703
+ };
704
+ const first = samples[0];
705
+ const last = samples[samples.length - 1];
706
+ const dt = last.t - first.t;
707
+ if (dt <= 0) return {
708
+ x: 0,
709
+ y: 0
710
+ };
711
+ return {
712
+ x: (last.point.x - first.point.x) / dt * 1e3,
713
+ y: (last.point.y - first.point.y) / dt * 1e3
714
+ };
715
+ }
716
+ function buildInfo(event) {
717
+ const point = pointOf(event);
718
+ return {
719
+ point,
720
+ delta: lastPoint ? {
721
+ x: point.x - lastPoint.x,
722
+ y: point.y - lastPoint.y
723
+ } : {
724
+ x: 0,
725
+ y: 0
726
+ },
727
+ offset: startPoint ? {
728
+ x: point.x - startPoint.x,
729
+ y: point.y - startPoint.y
730
+ } : {
731
+ x: 0,
732
+ y: 0
733
+ },
734
+ velocity: computeVelocity()
735
+ };
736
+ }
737
+ /** Push a freshly-computed info snapshot into the MVs. Each `.set` fires
738
+ * the MV's change subscription, which the callable-hybrid bridge
739
+ * forwards to Solid; consumers reading e.g. only `pan.velocity.x()` only
740
+ * re-run when velocity.x actually changes — pre-existing MotionValue
741
+ * granularity, not Store path-tracking. */
742
+ function writeInfo(info) {
743
+ pointX.set(info.point.x);
744
+ pointY.set(info.point.y);
745
+ deltaX.set(info.delta.x);
746
+ deltaY.set(info.delta.y);
747
+ offsetX.set(info.offset.x);
748
+ offsetY.set(info.offset.y);
749
+ velocityX.set(info.velocity.x);
750
+ velocityY.set(info.velocity.y);
751
+ }
752
+ function onPointerDown(event) {
753
+ if (!isPrimaryPointer(event)) return;
754
+ startPoint = pointOf(event);
755
+ lastPoint = startPoint;
756
+ pointerId = event.pointerId;
757
+ panning = false;
758
+ samples = [{
759
+ t: time.now(),
760
+ point: startPoint
761
+ }];
762
+ setIsPanning(false);
763
+ pointX.set(startPoint.x);
764
+ pointY.set(startPoint.y);
765
+ deltaX.set(0);
766
+ deltaY.set(0);
767
+ offsetX.set(0);
768
+ offsetY.set(0);
769
+ velocityX.set(0);
770
+ velocityY.set(0);
771
+ window.addEventListener("pointermove", onPointerMove);
772
+ window.addEventListener("pointerup", onPointerEnd);
773
+ window.addEventListener("pointercancel", onPointerEnd);
774
+ }
775
+ function onPointerMove(event) {
776
+ if (event.pointerId !== pointerId) return;
777
+ const point = pointOf(event);
778
+ const now = time.now();
779
+ samples.push({
780
+ t: now,
781
+ point
782
+ });
783
+ const cutoff = now - VELOCITY_WINDOW_MS;
784
+ while (samples.length > 1 && (samples[0]?.t ?? 0) < cutoff) samples.shift();
785
+ const info = buildInfo(event);
786
+ writeInfo(info);
787
+ if (!panning) {
788
+ const threshold = getOpts().threshold ?? DEFAULT_THRESHOLD;
789
+ if (Math.hypot(info.offset.x, info.offset.y) >= threshold) {
790
+ panning = true;
791
+ setIsPanning(true);
792
+ getOpts().onPanStart?.(event, info);
793
+ }
794
+ } else getOpts().onPan?.(event, info);
795
+ lastPoint = point;
796
+ }
797
+ function onPointerEnd(event) {
798
+ if (event.pointerId !== pointerId) return;
799
+ if (panning) getOpts().onPanEnd?.(event, buildInfo(event));
800
+ panning = false;
801
+ setIsPanning(false);
802
+ startPoint = null;
803
+ lastPoint = null;
804
+ pointerId = null;
805
+ samples = [];
806
+ window.removeEventListener("pointermove", onPointerMove);
807
+ window.removeEventListener("pointerup", onPointerEnd);
808
+ window.removeEventListener("pointercancel", onPointerEnd);
809
+ }
810
+ el.addEventListener("pointerdown", onPointerDown);
811
+ onCleanup(() => {
812
+ el.removeEventListener("pointerdown", onPointerDown);
813
+ window.removeEventListener("pointermove", onPointerMove);
814
+ window.removeEventListener("pointerup", onPointerEnd);
815
+ window.removeEventListener("pointercancel", onPointerEnd);
816
+ });
817
+ });
818
+ return {
819
+ isPanning,
820
+ point: {
821
+ x: pointX,
822
+ y: pointY
823
+ },
824
+ delta: {
825
+ x: deltaX,
826
+ y: deltaY
827
+ },
828
+ offset: {
829
+ x: offsetX,
830
+ y: offsetY
831
+ },
832
+ velocity: {
833
+ x: velocityX,
834
+ y: velocityY
835
+ }
836
+ };
837
+ }
838
+ //#endregion
839
+ //#region src/primitives/createDrag.ts
840
+ /**
841
+ * Get or create a motion-dom VisualElement for an HTMLElement. Required
842
+ * because we write to the VE's `x`/`y` MotionValues during drag, and motion
843
+ * only auto-creates the VE inside `animate(el, target)` calls — if a user
844
+ * configures drag without any animate target, no VE would exist.
845
+ *
846
+ * Mirrors framer-motion's `createDOMVisualElement` (which isn't reachable
847
+ * from a non-React context — framer-motion's main entry requires React).
848
+ * The options shape and `mount` + `visualElementStore.set` calls match the
849
+ * upstream implementation. SVG support is omitted for v0.1 — drag on SVG
850
+ * is an unusual case.
851
+ */
852
+ function ensureVisualElement(el) {
853
+ const existing = visualElementStore.get(el);
854
+ if (existing) return existing;
855
+ const ve = new HTMLVisualElement({
856
+ presenceContext: null,
857
+ props: {},
858
+ visualState: {
859
+ renderState: {
860
+ transform: {},
861
+ transformOrigin: {},
862
+ style: {},
863
+ vars: {},
864
+ attrs: {}
865
+ },
866
+ latestValues: {}
867
+ }
868
+ });
869
+ ve.mount(el);
870
+ visualElementStore.set(el, ve);
871
+ return ve;
872
+ }
873
+ /**
874
+ * Compute the `touch-action` CSS value for an element being dragged.
875
+ * Disabling touch-action prevents the browser from interpreting the gesture
876
+ * as a scroll. Axis-locked drags leave the unused axis available for scroll
877
+ * (so a horizontally-draggable card can still be scrolled vertically by the
878
+ * surrounding page).
879
+ */
880
+ function touchActionFor(drag) {
881
+ if (drag === "x") return "pan-y";
882
+ if (drag === "y") return "pan-x";
883
+ return "none";
884
+ }
885
+ /**
886
+ * Resolve a {@link DragConstraints} value into absolute MotionValue bounds at
887
+ * drag-start. Two input shapes (Q8):
888
+ *
889
+ * - **Numeric** (`{ top, left, right, bottom }`): bounds are absolute MV
890
+ * values. `left: -100` means x cannot go below -100.
891
+ * - **HTMLElement or `() => HTMLElement | null`**: container that the
892
+ * dragged element must stay inside. Bounds are computed from the
893
+ * container's bounding rect vs the dragged element's current rect, then
894
+ * re-centered around the current MV values.
895
+ *
896
+ * The element form is resolved ONCE at drag-start (current viewport rects).
897
+ * Reactive constraint changes mid-drag aren't honored in v0.1 — they'd
898
+ * require re-measuring on each pointermove. Acceptable corner case.
899
+ *
900
+ * Returns `null` when constraints are unset or the accessor returns null —
901
+ * caller treats as "no clamping, no elastic resistance."
902
+ */
903
+ function resolveConstraints(constraints, el, dragStartX, dragStartY) {
904
+ if (!constraints) return null;
905
+ let container = null;
906
+ if (constraints instanceof HTMLElement) container = constraints;
907
+ else if (typeof constraints === "function") container = constraints();
908
+ if (container) {
909
+ const containerRect = container.getBoundingClientRect();
910
+ const elementRect = el.getBoundingClientRect();
911
+ return {
912
+ minX: dragStartX + (containerRect.left - elementRect.left),
913
+ maxX: dragStartX + (containerRect.right - elementRect.right),
914
+ minY: dragStartY + (containerRect.top - elementRect.top),
915
+ maxY: dragStartY + (containerRect.bottom - elementRect.bottom)
916
+ };
917
+ }
918
+ const numeric = constraints;
919
+ return {
920
+ minX: numeric.left ?? -Infinity,
921
+ maxX: numeric.right ?? Infinity,
922
+ minY: numeric.top ?? -Infinity,
923
+ maxY: numeric.bottom ?? Infinity
924
+ };
925
+ }
926
+ /**
927
+ * Apply elastic resistance past a boundary (Q15c — linear).
928
+ *
929
+ * Within bounds: `value` passes through unchanged. Past a bound by `Δ`: the
930
+ * displayed value is `boundary + elastic × Δ`. With `elastic: 0` the value
931
+ * clamps hard at the boundary; with `elastic: 1` resistance vanishes
932
+ * (motion's default is `0.5`, halving the overflow).
933
+ *
934
+ * The function is symmetric — overflow on either side resists with the
935
+ * same coefficient.
936
+ */
937
+ function applyElastic(value, min, max, elastic) {
938
+ if (value < min) return min + (value - min) * elastic;
939
+ if (value > max) return max + (value - max) * elastic;
940
+ return value;
941
+ }
942
+ var DEFAULT_ELASTIC = .5;
943
+ /**
944
+ * Default `dragTransition` (Q15d — matches motion's inertia preset).
945
+ *
946
+ * `type: "inertia"` decays from the release point using `velocity`, with
947
+ * spring physics at `min`/`max` boundaries. The defaults are the values
948
+ * the user signed off on during Phase 2 grilling; passing a custom
949
+ * `dragTransition` shallow-merges over these.
950
+ */
951
+ var DEFAULT_DRAG_TRANSITION = {
952
+ type: "inertia",
953
+ power: .8,
954
+ timeConstant: 750,
955
+ bounceStiffness: 500,
956
+ bounceDamping: 10,
957
+ restDelta: 1,
958
+ restSpeed: 10
959
+ };
960
+ /**
961
+ * Bind pointer-driven drag to an element. Layers on top of createPan for the
962
+ * pointer session; adds transform writes, body styles, pointer capture, and
963
+ * state-machine activation.
964
+ *
965
+ * Drag is enabled when `opts.drag` is truthy (`true`, `"x"`, or `"y"`).
966
+ * createDrag always wires the pointer session — the enable check is per-
967
+ * gesture-start, so toggling `opts.drag` on/off doesn't churn listeners.
968
+ *
969
+ * Phase 2 Commit 6 — Stage 2 scope: VE bootstrap, translation, axis lock,
970
+ * body/pointer styles, callbacks, cleanup. Constraints + elastic resistance
971
+ * land in Stage 3; momentum + dragSnapToOrigin in Stage 4.
972
+ */
973
+ function createDrag(el, getOpts, setActive) {
974
+ let xMV = null;
975
+ let yMV = null;
976
+ /** Drag-start position of the x/y MotionValues (the values that existed
977
+ * before the user grabbed). offsets from PanInfo accumulate from this. */
978
+ let dragStartX = 0;
979
+ let dragStartY = 0;
980
+ /** Resolved bounds for this session — computed once at drag-start and
981
+ * reused across all pointermoves to avoid repeat layout reads. `null`
982
+ * means no constraints. */
983
+ let sessionBounds = null;
984
+ /** Saved before applying drag's `user-select` / `touch-action` overrides
985
+ * so we can restore them exactly on session end. */
986
+ let savedUserSelect = "";
987
+ let savedTouchAction = "";
988
+ let capturedPointerId = null;
989
+ /** In-flight momentum animations (one per axis when active). Stopped on
990
+ * owner disposal AND on a fresh pointerdown (to interrupt a settling
991
+ * momentum if the user grabs again mid-decay). */
992
+ let momentumControls = [];
993
+ function isDragEnabled() {
994
+ return Boolean(getOpts().drag);
995
+ }
996
+ function restoreBodyAndElementStyles() {
997
+ document.body.style.userSelect = savedUserSelect;
998
+ el.style.touchAction = savedTouchAction;
999
+ }
1000
+ function releasePointerCaptureSafely() {
1001
+ if (capturedPointerId === null) return;
1002
+ try {
1003
+ el.releasePointerCapture(capturedPointerId);
1004
+ } catch {}
1005
+ capturedPointerId = null;
1006
+ }
1007
+ function stopMomentum() {
1008
+ for (const ctrl of momentumControls) ctrl.stop();
1009
+ momentumControls = [];
1010
+ }
1011
+ const handlePanStart = (event, info) => {
1012
+ if (!isDragEnabled()) return;
1013
+ stopMomentum();
1014
+ const ve = ensureVisualElement(el);
1015
+ xMV = ve.getValue("x", 0);
1016
+ yMV = ve.getValue("y", 0);
1017
+ dragStartX = xMV.get();
1018
+ dragStartY = yMV.get();
1019
+ sessionBounds = resolveConstraints(getOpts().dragConstraints, el, dragStartX, dragStartY);
1020
+ savedUserSelect = document.body.style.userSelect;
1021
+ savedTouchAction = el.style.touchAction;
1022
+ document.body.style.userSelect = "none";
1023
+ el.style.touchAction = touchActionFor(getOpts().drag);
1024
+ try {
1025
+ el.setPointerCapture(event.pointerId);
1026
+ capturedPointerId = event.pointerId;
1027
+ } catch {}
1028
+ setActive("whileDrag", true);
1029
+ getOpts().onDragStart?.(event, info);
1030
+ };
1031
+ const handlePan = (event, info) => {
1032
+ if (!isDragEnabled() || !xMV || !yMV) return;
1033
+ const axis = getOpts().drag;
1034
+ const writeX = axis !== "y";
1035
+ const writeY = axis !== "x";
1036
+ const elastic = getOpts().dragElastic ?? DEFAULT_ELASTIC;
1037
+ if (writeX) {
1038
+ const candidateX = dragStartX + info.offset.x;
1039
+ const finalX = sessionBounds ? applyElastic(candidateX, sessionBounds.minX, sessionBounds.maxX, elastic) : candidateX;
1040
+ xMV.set(finalX);
1041
+ }
1042
+ if (writeY) {
1043
+ const candidateY = dragStartY + info.offset.y;
1044
+ const finalY = sessionBounds ? applyElastic(candidateY, sessionBounds.minY, sessionBounds.maxY, elastic) : candidateY;
1045
+ yMV.set(finalY);
1046
+ }
1047
+ getOpts().onDrag?.(event, info);
1048
+ };
1049
+ const handlePanEnd = (event, info) => {
1050
+ if (!isDragEnabled() || !xMV || !yMV) return;
1051
+ setActive("whileDrag", false);
1052
+ restoreBodyAndElementStyles();
1053
+ releasePointerCaptureSafely();
1054
+ getOpts().onDragEnd?.(event, info);
1055
+ const xRef = xMV;
1056
+ const yRef = yMV;
1057
+ const boundsRef = sessionBounds;
1058
+ const opts = getOpts();
1059
+ const snapToOrigin = opts.dragSnapToOrigin ?? false;
1060
+ const momentum = opts.dragMomentum ?? true;
1061
+ const userTransition = opts.dragTransition ?? {};
1062
+ const dragAxis = opts.drag;
1063
+ const releaseX = dragAxis !== "y";
1064
+ const releaseY = dragAxis !== "x";
1065
+ momentumControls = [];
1066
+ const elastic = opts.dragElastic ?? DEFAULT_ELASTIC;
1067
+ const bounceParams = elastic ? {
1068
+ bounceStiffness: 200,
1069
+ bounceDamping: 40
1070
+ } : {
1071
+ bounceStiffness: 1e6,
1072
+ bounceDamping: 1e7
1073
+ };
1074
+ const xAtMax = boundsRef !== null && boundsRef.maxX !== Infinity && xRef.get() >= boundsRef.maxX;
1075
+ const xAtMin = boundsRef !== null && boundsRef.minX !== -Infinity && xRef.get() <= boundsRef.minX;
1076
+ const yAtMax = boundsRef !== null && boundsRef.maxY !== Infinity && yRef.get() >= boundsRef.maxY;
1077
+ const yAtMin = boundsRef !== null && boundsRef.minY !== -Infinity && yRef.get() <= boundsRef.minY;
1078
+ const xVelocity = !elastic && (xAtMax && info.velocity.x > 0 || xAtMin && info.velocity.x < 0) ? 0 : info.velocity.x;
1079
+ const yVelocity = !elastic && (yAtMax && info.velocity.y > 0 || yAtMin && info.velocity.y < 0) ? 0 : info.velocity.y;
1080
+ /** Fire onDragTransitionEnd via getOpts so reactive callback swaps see
1081
+ * the latest value (the user may have swapped handlers between pan-end
1082
+ * and momentum-settle). */
1083
+ const fireTransitionEnd = () => getOpts().onDragTransitionEnd?.();
1084
+ if (snapToOrigin) {
1085
+ const transitionX = {
1086
+ ...DEFAULT_DRAG_TRANSITION,
1087
+ ...bounceParams,
1088
+ ...userTransition,
1089
+ velocity: xVelocity,
1090
+ min: 0,
1091
+ max: 0
1092
+ };
1093
+ const transitionY = {
1094
+ ...DEFAULT_DRAG_TRANSITION,
1095
+ ...bounceParams,
1096
+ ...userTransition,
1097
+ velocity: yVelocity,
1098
+ min: 0,
1099
+ max: 0
1100
+ };
1101
+ const settles = [];
1102
+ if (releaseX) {
1103
+ const ctrlX = animate$1(xRef, 0, transitionX);
1104
+ momentumControls.push(ctrlX);
1105
+ settles.push(ctrlX);
1106
+ }
1107
+ if (releaseY) {
1108
+ const ctrlY = animate$1(yRef, 0, transitionY);
1109
+ momentumControls.push(ctrlY);
1110
+ settles.push(ctrlY);
1111
+ }
1112
+ if (settles.length > 0) Promise.all(settles).then(fireTransitionEnd);
1113
+ else fireTransitionEnd();
1114
+ } else {
1115
+ const releaseVelocityX = momentum ? xVelocity : 0;
1116
+ const releaseVelocityY = momentum ? yVelocity : 0;
1117
+ const transitionX = {
1118
+ ...DEFAULT_DRAG_TRANSITION,
1119
+ ...bounceParams,
1120
+ ...userTransition,
1121
+ velocity: releaseVelocityX,
1122
+ min: boundsRef?.minX,
1123
+ max: boundsRef?.maxX
1124
+ };
1125
+ const transitionY = {
1126
+ ...DEFAULT_DRAG_TRANSITION,
1127
+ ...bounceParams,
1128
+ ...userTransition,
1129
+ velocity: releaseVelocityY,
1130
+ min: boundsRef?.minY,
1131
+ max: boundsRef?.maxY
1132
+ };
1133
+ const settles = [];
1134
+ if (releaseX) {
1135
+ const ctrlX = animate$1(xRef, 0, transitionX);
1136
+ momentumControls.push(ctrlX);
1137
+ settles.push(ctrlX);
1138
+ }
1139
+ if (releaseY) {
1140
+ const ctrlY = animate$1(yRef, 0, transitionY);
1141
+ momentumControls.push(ctrlY);
1142
+ settles.push(ctrlY);
1143
+ }
1144
+ if (settles.length > 0) Promise.all(settles).then(fireTransitionEnd);
1145
+ else fireTransitionEnd();
1146
+ }
1147
+ xMV = null;
1148
+ yMV = null;
1149
+ sessionBounds = null;
1150
+ };
1151
+ createPan(() => el, () => ({
1152
+ threshold: getOpts().panThreshold,
1153
+ onPanStart: handlePanStart,
1154
+ onPan: handlePan,
1155
+ onPanEnd: handlePanEnd
1156
+ }));
1157
+ function startExternalDrag(event, options) {
1158
+ if (!isDragEnabled()) return;
1159
+ if (options.snapToCursor) {
1160
+ const ve = ensureVisualElement(el);
1161
+ const snapXMV = ve.getValue("x", 0);
1162
+ const snapYMV = ve.getValue("y", 0);
1163
+ const elRect = el.getBoundingClientRect();
1164
+ const centerX = elRect.left + elRect.width / 2;
1165
+ const centerY = elRect.top + elRect.height / 2;
1166
+ const axis = getOpts().drag;
1167
+ if (axis !== "y") snapXMV.set(snapXMV.get() + (event.clientX - centerX));
1168
+ if (axis !== "x") snapYMV.set(snapYMV.get() + (event.clientY - centerY));
1169
+ }
1170
+ handlePanStart(event, {
1171
+ point: {
1172
+ x: event.clientX,
1173
+ y: event.clientY
1174
+ },
1175
+ delta: {
1176
+ x: 0,
1177
+ y: 0
1178
+ },
1179
+ offset: {
1180
+ x: 0,
1181
+ y: 0
1182
+ },
1183
+ velocity: {
1184
+ x: 0,
1185
+ y: 0
1186
+ }
1187
+ });
1188
+ const sessionStartPoint = {
1189
+ x: event.clientX,
1190
+ y: event.clientY
1191
+ };
1192
+ let sessionLastPoint = { ...sessionStartPoint };
1193
+ const sessionPointerId = event.pointerId;
1194
+ const sessionSamples = [{
1195
+ t: time.now(),
1196
+ point: { ...sessionStartPoint }
1197
+ }];
1198
+ function computeSessionVelocity() {
1199
+ if (sessionSamples.length < 2) return {
1200
+ x: 0,
1201
+ y: 0
1202
+ };
1203
+ const first = sessionSamples[0];
1204
+ const last = sessionSamples[sessionSamples.length - 1];
1205
+ if (!first || !last) return {
1206
+ x: 0,
1207
+ y: 0
1208
+ };
1209
+ const dt = last.t - first.t;
1210
+ if (dt <= 0) return {
1211
+ x: 0,
1212
+ y: 0
1213
+ };
1214
+ return {
1215
+ x: (last.point.x - first.point.x) / dt * 1e3,
1216
+ y: (last.point.y - first.point.y) / dt * 1e3
1217
+ };
1218
+ }
1219
+ function buildSessionInfo(e) {
1220
+ const point = {
1221
+ x: e.clientX,
1222
+ y: e.clientY
1223
+ };
1224
+ return {
1225
+ point,
1226
+ delta: {
1227
+ x: point.x - sessionLastPoint.x,
1228
+ y: point.y - sessionLastPoint.y
1229
+ },
1230
+ offset: {
1231
+ x: point.x - sessionStartPoint.x,
1232
+ y: point.y - sessionStartPoint.y
1233
+ },
1234
+ velocity: computeSessionVelocity()
1235
+ };
1236
+ }
1237
+ function onSessionMove(e) {
1238
+ if (e.pointerId !== sessionPointerId) return;
1239
+ const point = {
1240
+ x: e.clientX,
1241
+ y: e.clientY
1242
+ };
1243
+ const now = time.now();
1244
+ sessionSamples.push({
1245
+ t: now,
1246
+ point
1247
+ });
1248
+ const cutoff = now - 200;
1249
+ while (sessionSamples.length > 1 && (sessionSamples[0]?.t ?? 0) < cutoff) sessionSamples.shift();
1250
+ const info = buildSessionInfo(e);
1251
+ sessionLastPoint = point;
1252
+ handlePan(e, info);
1253
+ }
1254
+ function onSessionEnd(e) {
1255
+ if (e.pointerId !== sessionPointerId) return;
1256
+ handlePanEnd(e, buildSessionInfo(e));
1257
+ window.removeEventListener("pointermove", onSessionMove);
1258
+ window.removeEventListener("pointerup", onSessionEnd);
1259
+ window.removeEventListener("pointercancel", onSessionEnd);
1260
+ }
1261
+ window.addEventListener("pointermove", onSessionMove);
1262
+ window.addEventListener("pointerup", onSessionEnd);
1263
+ window.addEventListener("pointercancel", onSessionEnd);
1264
+ }
1265
+ createEffect(() => {
1266
+ const controls = getOpts().dragControls;
1267
+ if (!controls) return;
1268
+ const register = controls[DRAG_CONTROLS_REGISTER];
1269
+ if (!register) return;
1270
+ onCleanup(register(startExternalDrag));
1271
+ });
1272
+ onCleanup(() => {
1273
+ stopMomentum();
1274
+ if (xMV || yMV) {
1275
+ restoreBodyAndElementStyles();
1276
+ releasePointerCaptureSafely();
1277
+ }
1278
+ });
1279
+ }
1280
+ //#endregion
1281
+ //#region src/primitives/createInView.ts
1282
+ /**
1283
+ * Observe an element via {@link IntersectionObserver} and expose its
1284
+ * in-view state as a pair of Solid Accessors.
1285
+ *
1286
+ * Pass a ref-style accessor that returns the element. The observer
1287
+ * attaches once the accessor returns a non-null element and re-attaches
1288
+ * if it changes. The observer is disconnected on owner disposal.
1289
+ *
1290
+ * Options can be a static object or a function form (matching `useMotion`
1291
+ * and `createPan`'s convention). The function form is tracked inside the
1292
+ * effect — option changes (e.g., switching `root`) re-attach the observer.
1293
+ *
1294
+ * @example Static options
1295
+ * const [el, setEl] = createSignal<HTMLElement>()
1296
+ * const view = createInView(el, { once: true })
1297
+ * createEffect(() => {
1298
+ * if (view.isInView()) console.log("now in view")
1299
+ * })
1300
+ *
1301
+ * @example Function-form options (reactive)
1302
+ * const [root, setRoot] = createSignal<HTMLElement>()
1303
+ * const view = createInView(el, () => ({ root, margin: "100px" }))
1304
+ *
1305
+ * @example Reading the raw entry reactively
1306
+ * const view = createInView(el)
1307
+ * createEffect(() => {
1308
+ * const e = view.entry()
1309
+ * if (e) console.log("ratio:", e.intersectionRatio)
1310
+ * })
1311
+ *
1312
+ * <div ref={setEl}>watch me</div>
1313
+ */
1314
+ function createInView(ref, options = {}) {
1315
+ const [isInView, setIsInView] = createSignal(false);
1316
+ const [entry, setEntry] = createSignal(null);
1317
+ createEffect(() => {
1318
+ const el = ref();
1319
+ if (!el) return;
1320
+ const opts = typeof options === "function" ? options() : options;
1321
+ const threshold = resolveThreshold(opts.amount);
1322
+ const observer = new IntersectionObserver((entries) => {
1323
+ for (const e of entries) {
1324
+ opts.onChange?.(e);
1325
+ setEntry(e);
1326
+ if (e.isIntersecting) {
1327
+ setIsInView(true);
1328
+ if (opts.once) observer.disconnect();
1329
+ } else if (!opts.once) setIsInView(false);
1330
+ }
1331
+ }, {
1332
+ root: opts.root?.() ?? null,
1333
+ rootMargin: opts.margin ?? "0px",
1334
+ threshold
1335
+ });
1336
+ observer.observe(el);
1337
+ onCleanup(() => observer.disconnect());
1338
+ });
1339
+ return {
1340
+ isInView,
1341
+ entry
1342
+ };
1343
+ }
1344
+ function resolveThreshold(amount) {
1345
+ if (Array.isArray(amount)) return amount;
1346
+ if (typeof amount === "number") return amount;
1347
+ if (amount === "all") return 1;
1348
+ return 0;
1349
+ }
1350
+ //#endregion
1351
+ //#region src/primitives/createGestures.ts
1352
+ /**
1353
+ * Bind pointer-event-driven gestures (hover, press) to the motion element.
1354
+ * Toggles the state machine's `whileHover` / `whilePress` flags and forwards
1355
+ * events to the user's `MotionCallbacks`.
1356
+ */
1357
+ function createGestures(el, getOpts, setActive) {
1358
+ onCleanup(hover(el, (_element, event) => {
1359
+ setActive("whileHover", true);
1360
+ getOpts().onHoverStart?.(event);
1361
+ return (event) => {
1362
+ setActive("whileHover", false);
1363
+ getOpts().onHoverEnd?.(event);
1364
+ };
1365
+ }));
1366
+ onCleanup(press(el, (_element, event) => {
1367
+ setActive("whilePress", true);
1368
+ getOpts().onPressStart?.(event);
1369
+ return (event, info) => {
1370
+ setActive("whilePress", false);
1371
+ if (info.success) getOpts().onPress?.(event, info);
1372
+ else getOpts().onPressCancel?.(event, info);
1373
+ };
1374
+ }));
1375
+ let focusActiveByVisible = false;
1376
+ const stopFocus = addDomEvent(el, "focus", (event) => {
1377
+ let isFocusVisible = false;
1378
+ try {
1379
+ isFocusVisible = el.matches(":focus-visible");
1380
+ } catch {
1381
+ isFocusVisible = true;
1382
+ }
1383
+ if (isFocusVisible) {
1384
+ setActive("whileFocus", true);
1385
+ focusActiveByVisible = true;
1386
+ }
1387
+ getOpts().onFocus?.(event);
1388
+ });
1389
+ const stopBlur = addDomEvent(el, "blur", (event) => {
1390
+ if (focusActiveByVisible) {
1391
+ setActive("whileFocus", false);
1392
+ focusActiveByVisible = false;
1393
+ }
1394
+ getOpts().onBlur?.(event);
1395
+ });
1396
+ onCleanup(stopFocus);
1397
+ onCleanup(stopBlur);
1398
+ const view = createInView(() => el, () => ({
1399
+ ...getOpts().inViewOptions,
1400
+ onChange: (entry) => {
1401
+ if (entry.isIntersecting) getOpts().onViewportEnter?.(entry);
1402
+ else getOpts().onViewportLeave?.(entry);
1403
+ }
1404
+ }));
1405
+ createEffect(() => {
1406
+ setActive("whileInView", view.isInView());
1407
+ });
1408
+ }
1409
+ //#endregion
1410
+ //#region src/default-values.ts
1411
+ var TRANSFORM_DEFAULTS = {
1412
+ x: 0,
1413
+ y: 0,
1414
+ z: 0,
1415
+ translateX: 0,
1416
+ translateY: 0,
1417
+ translateZ: 0,
1418
+ scale: 1,
1419
+ scaleX: 1,
1420
+ scaleY: 1,
1421
+ scaleZ: 1,
1422
+ rotate: 0,
1423
+ rotateX: 0,
1424
+ rotateY: 0,
1425
+ rotateZ: 0,
1426
+ skew: 0,
1427
+ skewX: 0,
1428
+ skewY: 0,
1429
+ perspective: 0,
1430
+ transformPerspective: 0,
1431
+ opacity: 1
1432
+ };
1433
+ /**
1434
+ * Look up the canonical fallback value for a property key. Returns the table
1435
+ * value if known, else `null` — which motion's `animate()` interprets as
1436
+ * "read from computed style at animation start."
1437
+ */
1438
+ function getMotionDefault(key) {
1439
+ return TRANSFORM_DEFAULTS[key] ?? null;
1440
+ }
1441
+ //#endregion
1442
+ //#region src/primitives/gesture-state.ts
1443
+ /** High → low priority for the winners walk. Materialized once. */
1444
+ var PRIORITY_HIGH_TO_LOW = [...[
1445
+ "animate",
1446
+ "whileInView",
1447
+ "whileHover",
1448
+ "whilePress",
1449
+ "whileFocus",
1450
+ "whileDrag",
1451
+ "exit"
1452
+ ]].reverse();
1453
+ /**
1454
+ * Construct the per-element gesture state machine.
1455
+ *
1456
+ * Wired primitives:
1457
+ * - `createStore` for the seven active flags — Solid tracks per-path, so
1458
+ * toggling `whileHover` doesn't dirty memos reading `whilePress`.
1459
+ * - `createMemo` for `stateTargets` — cached, re-runs only when opts/parent
1460
+ * context change.
1461
+ * - `createMemo` for `winners` — same caching, re-runs when `active` flags or
1462
+ * `stateTargets` change.
1463
+ * - `createEffect` for the diff-and-animate loop — fires on `winners` change;
1464
+ * compares against `lastApplied` to compute changed/removed keys.
1465
+ * - `onCleanup` inside the effect for per-iteration MV subscriptions — scoped
1466
+ * to each effect run (fires on re-run AND owner disposal). Same iteration-
1467
+ * scoped cleanup pattern Phase 1 established.
1468
+ */
1469
+ function createGestureStateMachine(deps) {
1470
+ const { el, getOpts, parentVariantCtx, motionConfig, systemReducedMotion, initialTarget, externalActiveStore, suppressFirstMount, enterReady, getValueForAnimate } = deps;
1471
+ const [active, setActiveStore] = externalActiveStore ?? createStore({
1472
+ animate: true,
1473
+ whileInView: false,
1474
+ whileHover: false,
1475
+ whilePress: false,
1476
+ whileFocus: false,
1477
+ whileDrag: false,
1478
+ exit: false
1479
+ });
1480
+ const stateTargets = createMemo(() => {
1481
+ const opts = getOpts();
1482
+ const variants = opts.variants;
1483
+ const custom = opts.custom ?? parentVariantCtx.custom?.();
1484
+ return {
1485
+ animate: resolveTarget(opts.animate, variants, asVariantLabels(parentVariantCtx.animate?.()), custom),
1486
+ whileInView: resolveTarget(opts.inView, variants, asVariantLabels(parentVariantCtx.inView?.()), custom),
1487
+ whileHover: resolveTarget(opts.hover, variants, asVariantLabels(parentVariantCtx.hover?.()), custom),
1488
+ whilePress: resolveTarget(opts.press, variants, asVariantLabels(parentVariantCtx.press?.()), custom),
1489
+ whileFocus: resolveTarget(opts.focus, variants, asVariantLabels(parentVariantCtx.focus?.()), custom),
1490
+ whileDrag: resolveTarget(opts.whileDrag, variants, void 0, custom),
1491
+ exit: resolveTarget(opts.exit, variants, asVariantLabels(parentVariantCtx.exit?.()), custom)
1492
+ };
1493
+ });
1494
+ const winners = createMemo(() => {
1495
+ const targets = stateTargets();
1496
+ const dragEnabled = Boolean(getOpts().drag);
1497
+ const out = {};
1498
+ for (const stateName of PRIORITY_HIGH_TO_LOW) {
1499
+ if (!isStateActive(stateName, active, parentVariantCtx)) continue;
1500
+ const target = targets[stateName];
1501
+ if (!target) continue;
1502
+ for (const key in target) {
1503
+ if (key === "transition") continue;
1504
+ if (key in out) continue;
1505
+ if (!active.exit && dragEnabled && (key === "x" || key === "y")) continue;
1506
+ out[key] = {
1507
+ value: target[key],
1508
+ transition: target.transition,
1509
+ stateName
1510
+ };
1511
+ }
1512
+ }
1513
+ return out;
1514
+ });
1515
+ let prevControls = null;
1516
+ let lastApplied = {};
1517
+ let isFirstRun = true;
1518
+ let pendingExitResolvers = [];
1519
+ function drainPendingExitResolvers() {
1520
+ const resolvers = pendingExitResolvers;
1521
+ pendingExitResolvers = [];
1522
+ for (const r of resolvers) r();
1523
+ }
1524
+ createEffect(() => {
1525
+ const next = winners();
1526
+ const opts = getOpts();
1527
+ if (isFirstRun && enterReady && !enterReady()) return;
1528
+ let skipAnimate = false;
1529
+ if (isFirstRun && (untrack(() => opts.initial) === false || suppressFirstMount)) {
1530
+ lastApplied = snapshotValues(next);
1531
+ skipAnimate = true;
1532
+ }
1533
+ isFirstRun = false;
1534
+ if (!skipAnimate && Object.keys(next).length === 0 && Object.keys(lastApplied).length === 0 && opts.animate === void 0 && parentVariantCtx.animate?.() === void 0) return;
1535
+ const changes = {};
1536
+ let mergedPerTargetTransition;
1537
+ if (!skipAnimate) {
1538
+ for (const key in next) {
1539
+ const entry = next[key];
1540
+ if (!entry) continue;
1541
+ if (lastApplied[key] !== entry.value) {
1542
+ changes[key] = entry.value;
1543
+ mergedPerTargetTransition ??= entry.transition;
1544
+ }
1545
+ }
1546
+ for (const key in lastApplied) {
1547
+ if (key in next) continue;
1548
+ const initialValue = initialTarget && key in initialTarget ? initialTarget[key] : void 0;
1549
+ changes[key] = initialValue !== void 0 ? initialValue : getMotionDefault(key);
1550
+ }
1551
+ }
1552
+ const reduced = shouldReduceMotion(motionConfig.reducedMotion(), systemReducedMotion());
1553
+ const transition = mergeTransition(motionConfig.transition(), opts.transition, mergedPerTargetTransition, reduced);
1554
+ const driverState = highestActiveDriverState(next);
1555
+ const effectiveAnimateValue = animateValueForState(driverState, opts, parentVariantCtx);
1556
+ const buildAnimateOptions = () => ({
1557
+ ...transition,
1558
+ onPlay: opts.onAnimationStart ? () => untrack(() => opts.onAnimationStart?.()) : void 0,
1559
+ onComplete: opts.onAnimationComplete ? () => untrack(() => {
1560
+ if (effectiveAnimateValue != null) opts.onAnimationComplete?.(effectiveAnimateValue);
1561
+ }) : void 0,
1562
+ onStop: opts.onAnimationCancel ? () => untrack(() => opts.onAnimationCancel?.()) : void 0,
1563
+ onUpdate: opts.onUpdate ? (latest) => untrack(() => opts.onUpdate?.(latest)) : void 0
1564
+ });
1565
+ if (!skipAnimate && Object.keys(changes).length > 0) {
1566
+ lastApplied = {
1567
+ ...lastApplied,
1568
+ ...changes
1569
+ };
1570
+ for (const key in lastApplied) if (!(key in next) && !(key in changes)) delete lastApplied[key];
1571
+ const { plain } = splitTarget(changes);
1572
+ const routed = [];
1573
+ const waaPlain = {};
1574
+ for (const key in plain) {
1575
+ const value = plain[key];
1576
+ const fallback = initialTarget && key in initialTarget ? initialTarget[key] : getMotionDefault(key);
1577
+ const routedMV = getValueForAnimate?.(key, fallback);
1578
+ if (routedMV) routed.push({
1579
+ mv: routedMV,
1580
+ value
1581
+ });
1582
+ else waaPlain[key] = value;
1583
+ }
1584
+ prevControls?.stop();
1585
+ const animOpts = buildAnimateOptions();
1586
+ if (routed.length === 0) prevControls = animate$1(el, waaPlain, animOpts);
1587
+ else {
1588
+ const controls = [];
1589
+ for (const { mv, value } of routed) controls.push(animate$1(mv, value, animOpts));
1590
+ if (Object.keys(waaPlain).length > 0) controls.push(animate$1(el, waaPlain, animOpts));
1591
+ prevControls = aggregateControls(controls);
1592
+ }
1593
+ if (driverState === "exit") {
1594
+ const dispatched = prevControls;
1595
+ dispatched.then(() => {
1596
+ if (prevControls === dispatched) drainPendingExitResolvers();
1597
+ });
1598
+ }
1599
+ } else if (driverState === "exit") drainPendingExitResolvers();
1600
+ for (const key in next) {
1601
+ const entry = next[key];
1602
+ if (!entry) continue;
1603
+ if (isMotionValue$1(entry.value)) {
1604
+ const targetMV = entry.value;
1605
+ onCleanup(targetMV.on("change", (v) => {
1606
+ const fallback = initialTarget && key in initialTarget ? initialTarget[key] : getMotionDefault(key);
1607
+ const routedMV = getValueForAnimate?.(key, fallback);
1608
+ if (routedMV && routedMV !== targetMV) animate$1(routedMV, v, buildAnimateOptions());
1609
+ else animate$1(el, { [key]: v }, buildAnimateOptions());
1610
+ }));
1611
+ }
1612
+ }
1613
+ });
1614
+ onCleanup(() => prevControls?.stop());
1615
+ function setActive(state, isActive) {
1616
+ setActiveStore(state, isActive);
1617
+ }
1618
+ /**
1619
+ * Phase 3 — Presence integration. Returns a Promise that resolves when the
1620
+ * NEXT exit-driven animate dispatched by the diff effect completes, OR
1621
+ * immediately if no exit target is configured (nothing to wait for).
1622
+ *
1623
+ * The typical caller is `createMotion`'s presence-registered `runExit`:
1624
+ * it flips `setActive("exit", true)` then awaits this. The diff effect
1625
+ * runs in the next microtask, dispatches the exit animation, and on its
1626
+ * completion drains the pending resolvers.
1627
+ *
1628
+ * Multiple concurrent waiters are supported — they all resolve from the
1629
+ * same animation's completion.
1630
+ *
1631
+ * Edge case: if the user reactively removes `opts.exit` AFTER this call
1632
+ * but before the effect runs, the resolver will still be drained the
1633
+ * next time exit drives a dispatch (or by the "no-animate but exit-
1634
+ * driven" branch in the effect).
1635
+ */
1636
+ function onceExitComplete() {
1637
+ if (untrack(() => stateTargets().exit) === null) return Promise.resolve();
1638
+ return new Promise((resolve) => {
1639
+ pendingExitResolvers.push(resolve);
1640
+ });
1641
+ }
1642
+ return {
1643
+ setActive,
1644
+ onceExitComplete
1645
+ };
1646
+ }
1647
+ /**
1648
+ * Q4 — a state is considered active if EITHER its own flag is set OR the
1649
+ * parent's VariantContext carries a label for it (the parent's gesture is
1650
+ * active and propagating). The parent slots are themselves active-gated in
1651
+ * `useMotion`'s `myVariantCtx`, so a defined return value here means the
1652
+ * parent's gesture really is firing right now.
1653
+ *
1654
+ * `animate` and `exit` are special — their inheritance happens through the
1655
+ * normal label-resolution path in `resolveTarget`, not through the active
1656
+ * flag. We treat `animate` as always-active (matches motion's
1657
+ * createTypeState(true)). `exit` is driven by the Presence context; the
1658
+ * flag-based check is fine.
1659
+ */
1660
+ function isStateActive(state, active, parent) {
1661
+ if (active[state]) return true;
1662
+ switch (state) {
1663
+ case "whileHover": return parent.hover?.() !== void 0;
1664
+ case "whilePress": return parent.press?.() !== void 0;
1665
+ case "whileFocus": return parent.focus?.() !== void 0;
1666
+ case "whileInView": return parent.inView?.() !== void 0;
1667
+ case "whileDrag": return false;
1668
+ case "animate":
1669
+ case "exit": return false;
1670
+ }
1671
+ }
1672
+ /** Convert a winners map into the flat value snapshot used by `lastApplied`. */
1673
+ function snapshotValues(winners) {
1674
+ const out = {};
1675
+ for (const key in winners) {
1676
+ const entry = winners[key];
1677
+ if (entry) out[key] = entry.value;
1678
+ }
1679
+ return out;
1680
+ }
1681
+ /**
1682
+ * Phase 1's splitTarget: separate MotionValue refs in a target from plain
1683
+ * values. Motion-vanilla `animate(el, target)` doesn't subscribe to MV refs
1684
+ * passed in target — we handle that bridge ourselves.
1685
+ */
1686
+ function splitTarget(target) {
1687
+ const plain = {};
1688
+ const motionValues = [];
1689
+ for (const key in target) {
1690
+ const value = target[key];
1691
+ if (value === void 0 || value === null) {
1692
+ plain[key] = value;
1693
+ continue;
1694
+ }
1695
+ if (isMotionValue$1(value)) {
1696
+ motionValues.push({
1697
+ key,
1698
+ mv: value
1699
+ });
1700
+ plain[key] = value.get();
1701
+ } else if (typeof value === "function") plain[key] = value();
1702
+ else if (Array.isArray(value)) plain[key] = value.map((v) => {
1703
+ if (isMotionValue$1(v)) return v.get();
1704
+ if (typeof v === "function") return v();
1705
+ return v;
1706
+ });
1707
+ else plain[key] = value;
1708
+ }
1709
+ return {
1710
+ plain,
1711
+ motionValues
1712
+ };
1713
+ }
1714
+ /**
1715
+ * Return the highest-priority active state that contributed any key in the
1716
+ * current winners map. Used to identify the "driver" for onAnimationComplete
1717
+ * (which receives the AnimateValue that drove the animation).
1718
+ *
1719
+ * `animate` is the fallback when no gesture is contributing — matches Phase 1's
1720
+ * effectiveAnimateValue semantic.
1721
+ */
1722
+ function highestActiveDriverState(winners) {
1723
+ for (const stateName of PRIORITY_HIGH_TO_LOW) for (const key in winners) {
1724
+ const entry = winners[key];
1725
+ if (entry && entry.stateName === stateName) return stateName;
1726
+ }
1727
+ return "animate";
1728
+ }
1729
+ /**
1730
+ * Look up the AnimateValue (Target | string | string[]) that corresponds to a
1731
+ * given state — for onAnimationComplete's argument.
1732
+ */
1733
+ function animateValueForState(state, opts, parentVariantCtx) {
1734
+ switch (state) {
1735
+ case "animate": return opts.animate ?? parentVariantCtx.animate?.();
1736
+ case "whileHover": return opts.hover ?? parentVariantCtx.hover?.();
1737
+ case "whilePress": return opts.press ?? parentVariantCtx.press?.();
1738
+ case "whileFocus": return opts.focus ?? parentVariantCtx.focus?.();
1739
+ case "whileInView": return opts.inView ?? parentVariantCtx.inView?.();
1740
+ case "exit": return opts.exit ?? parentVariantCtx.exit?.();
1741
+ case "whileDrag": return opts.whileDrag;
1742
+ }
1743
+ }
1744
+ /**
1745
+ * Combine N AnimationPlaybackControls into a single Thenable+stoppable handle.
1746
+ *
1747
+ * Used by the Stage 3 bridge when an animate dispatch fans out across per-MV
1748
+ * `animate(mv, value, opts)` calls (one per routed key) plus an optional
1749
+ * single `animate(el, target, opts)` for keys still on the WAA path. The
1750
+ * gesture state machine treats `prevControls` as one handle: subsequent diff
1751
+ * runs call `.stop()` on it to cancel the in-flight animation, and the exit
1752
+ * drain awaits `.then(...)` to settle Presence's `onceExitComplete()` waiters.
1753
+ * Aggregating lets both code paths stay uniform whether bridging fired one
1754
+ * underlying motion call or six.
1755
+ *
1756
+ * The other AnimationPlaybackControls methods (pause/play/cancel/complete)
1757
+ * fan out unchanged. `time`/`speed`/`duration` aren't aggregated — they're
1758
+ * read-rare in our codebase and a meaningful aggregate isn't well-defined
1759
+ * across heterogeneous animations.
1760
+ */
1761
+ function aggregateControls(controls) {
1762
+ let settled = null;
1763
+ const settle = () => {
1764
+ if (!settled) settled = Promise.all(controls.map((c) => c));
1765
+ return settled;
1766
+ };
1767
+ const forAll = (fn) => {
1768
+ for (const c of controls) fn(c);
1769
+ };
1770
+ return {
1771
+ stop: () => {
1772
+ forAll((c) => c.stop());
1773
+ },
1774
+ pause: () => {
1775
+ forAll((c) => c.pause());
1776
+ },
1777
+ play: () => {
1778
+ forAll((c) => c.play());
1779
+ },
1780
+ cancel: () => {
1781
+ forAll((c) => c.cancel());
1782
+ },
1783
+ complete: () => {
1784
+ forAll((c) => c.complete());
1785
+ },
1786
+ speed: 1,
1787
+ time: 0,
1788
+ duration: controls.reduce((acc, c) => Math.max(acc, c.duration ?? 0), 0),
1789
+ then: (onFulfilled, onRejected) => settle().then(onFulfilled, onRejected)
1790
+ };
1791
+ }
1792
+ //#endregion
1793
+ //#region src/primitives/value-registry.ts
1794
+ function createValueRegistry() {
1795
+ const values = /* @__PURE__ */ new Map();
1796
+ const transient = /* @__PURE__ */ new Set();
1797
+ return {
1798
+ get(key) {
1799
+ return values.get(key);
1800
+ },
1801
+ has(key) {
1802
+ return values.has(key);
1803
+ },
1804
+ setExternal(key, mv) {
1805
+ const existing = values.get(key);
1806
+ if (existing && transient.has(existing)) transient.delete(existing);
1807
+ values.set(key, mv);
1808
+ },
1809
+ getOrCreateTransient(key, fallback) {
1810
+ const existing = values.get(key);
1811
+ if (existing) return existing;
1812
+ const mv = motionValue$1(fallback);
1813
+ values.set(key, mv);
1814
+ transient.add(mv);
1815
+ return mv;
1816
+ },
1817
+ entries() {
1818
+ return values.entries();
1819
+ },
1820
+ get size() {
1821
+ return values.size;
1822
+ },
1823
+ dispose() {
1824
+ transient.clear();
1825
+ values.clear();
1826
+ }
1827
+ };
1828
+ }
1829
+ //#endregion
1830
+ //#region src/primitives/createMotion.ts
1831
+ /**
1832
+ * Detect whether an animate-value is a variant name (string or string[]) vs.
1833
+ * an explicit target object. Returns the labels or undefined.
1834
+ */
1835
+ function asVariantLabels(value) {
1836
+ if (value === void 0) return void 0;
1837
+ if (typeof value === "string") return value;
1838
+ if (Array.isArray(value)) return value;
1839
+ }
1840
+ /**
1841
+ * Resolve a per-state animate value into a {@link Target}. Implements the
1842
+ * Q4 sub-2 priority table:
1843
+ *
1844
+ * - explicit Target object → use as-is (parent context ignored)
1845
+ * - variant name → look up in own variants only (no cascade)
1846
+ * - undefined → fall back to parent context's variant name, then look up in
1847
+ * own variants
1848
+ */
1849
+ function resolveTarget(ownValue, ownVariants, parentLabel, custom) {
1850
+ if (ownValue !== void 0 && typeof ownValue !== "string" && !Array.isArray(ownValue)) return ownValue;
1851
+ const labels = effectiveLabels(ownValue, parentLabel);
1852
+ if (labels === void 0) return null;
1853
+ return resolveVariant(labels, ownVariants, custom);
1854
+ }
1855
+ /**
1856
+ * Merge transition specs in priority order: MotionConfig default <
1857
+ * user's `transition` < per-target `transition`. When reduced motion is
1858
+ * active, returns `{ duration: 0 }` and drops everything else (Q11 sub-4).
1859
+ */
1860
+ function mergeTransition(configDefault, ownTransition, perTargetTransition, reduced) {
1861
+ if (reduced) return { duration: 0 };
1862
+ return {
1863
+ ...configDefault ?? {},
1864
+ ...ownTransition ?? {},
1865
+ ...perTargetTransition ?? {}
1866
+ };
1867
+ }
1868
+ /**
1869
+ * Apply a static target to an element's inline style before paint. Used on
1870
+ * mount when no SSR style was emitted. The ref callback fires before the
1871
+ * browser yields, so this avoids a frame of flicker.
1872
+ */
1873
+ function applyStaticStyle(el, target) {
1874
+ const style = targetToStyle(target);
1875
+ for (const key in style) {
1876
+ const value = style[key];
1877
+ if (value === void 0) continue;
1878
+ if (key.startsWith("--")) el.style.setProperty(key, String(value));
1879
+ else el.style[key] = value;
1880
+ }
1881
+ }
1882
+ /**
1883
+ * The imperative primitive: bind an element to a reactive motion-options
1884
+ * source. Caller is responsible for keeping the element alive (refs in a
1885
+ * component, drag controls, etc.).
1886
+ *
1887
+ * Phase 1 scope: animate + initial + transition + lifecycle hooks +
1888
+ * reduced-motion override + presence registration. Phase 2 layers gesture
1889
+ * states (hover/press/focus/inView) and drag on top.
1890
+ */
1891
+ function createMotion(el, getOpts, config) {
1892
+ const parentVariantCtx = config?.parentContext ?? useVariantContext();
1893
+ const presence = usePresenceContext();
1894
+ const motionConfig = useMotionConfig();
1895
+ const systemReducedMotion = createReducedMotion();
1896
+ let valueRegistry;
1897
+ const ensureRegistry = () => {
1898
+ if (!valueRegistry) valueRegistry = createValueRegistry();
1899
+ return valueRegistry;
1900
+ };
1901
+ const initialOpts = untrack(getOpts);
1902
+ let capturedInitialTarget = null;
1903
+ if (initialOpts.initial !== false) {
1904
+ const inheritedInitial = parentVariantCtx.initial?.();
1905
+ const inheritedAnimate = parentVariantCtx.animate?.();
1906
+ const effective = initialOpts.initial !== void 0 ? initialOpts.initial : inheritedInitial !== void 0 ? inheritedInitial : initialOpts.animate !== void 0 ? initialOpts.animate : inheritedAnimate;
1907
+ if (effective !== void 0) capturedInitialTarget = resolveTarget(effective, initialOpts.variants, void 0, initialOpts.custom ?? parentVariantCtx.custom?.());
1908
+ }
1909
+ const suppressFirstMount = untrack(() => presence.initial?.()) === false;
1910
+ if (!config?.initialAppliedBySSR && !suppressFirstMount && capturedInitialTarget) applyStaticStyle(el, capturedInitialTarget);
1911
+ const noop = () => {};
1912
+ let writer = noop;
1913
+ const writeFromRegistry = () => writer();
1914
+ const multiKeyWriter = () => {
1915
+ if (!valueRegistry) return;
1916
+ const target = {};
1917
+ for (const [k, mv] of valueRegistry.entries()) target[k] = mv.get();
1918
+ if (Object.keys(target).length === 0) return;
1919
+ applyStaticStyle(el, target);
1920
+ };
1921
+ const compileSingleKeyWriter = () => {
1922
+ const [key, mv] = valueRegistry.entries().next().value;
1923
+ if (TRANSFORM_KEYS.has(key)) {
1924
+ const formatter = pickTransformFormatter(key);
1925
+ if (formatter !== void 0) return () => {
1926
+ const v = snapshotValue(mv.get());
1927
+ if (v !== void 0) el.style.transform = formatter(v);
1928
+ };
1929
+ }
1930
+ if (key.startsWith("--")) return () => {
1931
+ const v = snapshotValue(mv.get());
1932
+ if (v !== void 0) el.style.setProperty(key, String(v));
1933
+ };
1934
+ return () => {
1935
+ const v = snapshotValue(mv.get());
1936
+ if (v === void 0) return;
1937
+ const formatted = formatProperty(key, v);
1938
+ el.style[key] = formatted;
1939
+ };
1940
+ };
1941
+ const refreshWriter = () => {
1942
+ const size = valueRegistry?.size ?? 0;
1943
+ if (size === 0) {
1944
+ writer = noop;
1945
+ return;
1946
+ }
1947
+ if (size === 1) {
1948
+ writer = compileSingleKeyWriter();
1949
+ return;
1950
+ }
1951
+ writer = multiKeyWriter;
1952
+ };
1953
+ let bridgeActive = false;
1954
+ if (config?.styleMotionValues && config.styleMotionValues.size > 0) {
1955
+ const registry = ensureRegistry();
1956
+ for (const [key, mv] of config.styleMotionValues) {
1957
+ registry.setExternal(key, mv);
1958
+ onCleanup(mv.on("change", writeFromRegistry));
1959
+ }
1960
+ bridgeActive = true;
1961
+ }
1962
+ if (config?.styleStaticTransforms && config.styleStaticTransforms.size > 0) bridgeActive = true;
1963
+ if (bridgeActive && capturedInitialTarget) {
1964
+ const registry = ensureRegistry();
1965
+ for (const key in capturedInitialTarget) {
1966
+ if (key === "transition") continue;
1967
+ if (!TRANSFORM_KEYS.has(key)) continue;
1968
+ if (registry.has(key)) continue;
1969
+ const raw = capturedInitialTarget[key];
1970
+ const snapshot = snapshotValue(raw);
1971
+ if (snapshot === void 0) continue;
1972
+ onCleanup(registry.getOrCreateTransient(key, snapshot).on("change", writeFromRegistry));
1973
+ }
1974
+ }
1975
+ if (bridgeActive && config?.styleStaticTransforms) {
1976
+ const registry = ensureRegistry();
1977
+ for (const [key, value] of config.styleStaticTransforms) {
1978
+ const existing = registry.get(key);
1979
+ if (existing) existing.set(value);
1980
+ else onCleanup(registry.getOrCreateTransient(key, value).on("change", writeFromRegistry));
1981
+ }
1982
+ }
1983
+ if (bridgeActive) {
1984
+ refreshWriter();
1985
+ writeFromRegistry();
1986
+ }
1987
+ const getValueForAnimate = (key, fallback) => {
1988
+ if (!bridgeActive) return void 0;
1989
+ const registry = ensureRegistry();
1990
+ const existing = registry.get(key);
1991
+ if (existing) return existing;
1992
+ if (!TRANSFORM_KEYS.has(key)) return void 0;
1993
+ const mv = registry.getOrCreateTransient(key, fallback);
1994
+ onCleanup(mv.on("change", writeFromRegistry));
1995
+ refreshWriter();
1996
+ return mv;
1997
+ };
1998
+ const inPresence = presence.registerEnter !== void 0;
1999
+ const [enterReady, setEnterReady] = createSignal(!inPresence);
2000
+ if (inPresence && presence.registerEnter) {
2001
+ presence.registerEnter(el, () => setEnterReady(true));
2002
+ queueMicrotask(() => {
2003
+ if (el.isConnected) setEnterReady(true);
2004
+ });
2005
+ }
2006
+ const { setActive, onceExitComplete } = createGestureStateMachine({
2007
+ el,
2008
+ getOpts,
2009
+ parentVariantCtx,
2010
+ motionConfig,
2011
+ systemReducedMotion,
2012
+ initialTarget: capturedInitialTarget,
2013
+ externalActiveStore: config?.activeStore,
2014
+ suppressFirstMount,
2015
+ enterReady,
2016
+ getValueForAnimate
2017
+ });
2018
+ const inheritedExitLabel = untrack(() => parentVariantCtx.exit?.());
2019
+ const hasOwnExit = initialOpts.exit !== void 0;
2020
+ const hasCascadedExit = inheritedExitLabel !== void 0 && initialOpts.variants !== void 0;
2021
+ if (hasOwnExit || hasCascadedExit) {
2022
+ const runExit = async () => {
2023
+ const opts = untrack(getOpts);
2024
+ const exitTarget = resolveTarget(opts.exit, opts.variants, asVariantLabels(untrack(() => parentVariantCtx.exit?.())), opts.custom ?? parentVariantCtx.custom?.());
2025
+ if (!exitTarget) {
2026
+ setActive("exit", true);
2027
+ await onceExitComplete();
2028
+ return;
2029
+ }
2030
+ const reduced = shouldReduceMotion(motionConfig.reducedMotion(), systemReducedMotion());
2031
+ const transition = mergeTransition(motionConfig.transition(), opts.transition, exitTarget.transition, reduced);
2032
+ const animTarget = {};
2033
+ for (const k in exitTarget) if (k !== "transition") animTarget[k] = exitTarget[k];
2034
+ await animate$1(el, animTarget, transition);
2035
+ };
2036
+ presence.register(el, runExit);
2037
+ }
2038
+ createGestures(el, getOpts, setActive);
2039
+ if (el instanceof HTMLElement) createDrag(el, getOpts, setActive);
2040
+ }
2041
+ //#endregion
2042
+ //#region src/use-motion.tsx
2043
+ /**
2044
+ * Wire motion to an element via a getter function.
2045
+ *
2046
+ * ```tsx
2047
+ * const motion = useMotion({
2048
+ * initial: { opacity: 0, y: 20 },
2049
+ * animate: { opacity: 1, y: 0 },
2050
+ * transition: { duration: 0.6 },
2051
+ * })
2052
+ *
2053
+ * <div {...motion({ class: "card" })}>Hello</div>
2054
+ * ```
2055
+ *
2056
+ * **Reactive form**: pass a function to track signals.
2057
+ * ```tsx
2058
+ * useMotion(() => ({ animate: { x: x() } }))
2059
+ * ```
2060
+ *
2061
+ * **Variant context propagation**: `useMotion` only *consumes* the parent
2062
+ * variant context. To propagate to descendants, wrap them in `motion.Provider`:
2063
+ * ```tsx
2064
+ * const m = useMotion({ animate: "visible", variants })
2065
+ * <div {...m()}>
2066
+ * <m.Provider>
2067
+ * <ChildMotion />
2068
+ * </m.Provider>
2069
+ * </div>
2070
+ * ```
2071
+ *
2072
+ * For the common "JSX wrapper does propagation automatically" pattern, use
2073
+ * `<motion.div>` (Phase 4).
2074
+ */
2075
+ function useMotion(opts) {
2076
+ const getOpts = typeof opts === "function" ? opts : () => opts;
2077
+ const actualParentCtx = useVariantContext();
2078
+ const isControlling = () => isControllingVariants(getOpts());
2079
+ const parentVariantCtx = {
2080
+ variants: () => isControlling() ? void 0 : actualParentCtx.variants?.(),
2081
+ initial: () => isControlling() ? void 0 : actualParentCtx.initial?.(),
2082
+ animate: () => isControlling() ? void 0 : actualParentCtx.animate?.(),
2083
+ hover: () => isControlling() ? void 0 : actualParentCtx.hover?.(),
2084
+ press: () => isControlling() ? void 0 : actualParentCtx.press?.(),
2085
+ focus: () => isControlling() ? void 0 : actualParentCtx.focus?.(),
2086
+ inView: () => isControlling() ? void 0 : actualParentCtx.inView?.(),
2087
+ exit: () => isControlling() ? void 0 : actualParentCtx.exit?.(),
2088
+ custom: () => isControlling() ? void 0 : actualParentCtx.custom?.(),
2089
+ transition: () => isControlling() ? void 0 : actualParentCtx.transition?.()
2090
+ };
2091
+ const presenceCtx = usePresenceContext();
2092
+ const initialTarget = computeInitialTarget(untrack(getOpts), parentVariantCtx, presenceCtx.initial);
2093
+ const activeStore = createStore({
2094
+ animate: true,
2095
+ whileInView: false,
2096
+ whileHover: false,
2097
+ whilePress: false,
2098
+ whileFocus: false,
2099
+ whileDrag: false,
2100
+ exit: false
2101
+ });
2102
+ const [active] = activeStore;
2103
+ let styleMotionValues;
2104
+ let styleStaticTransforms;
2105
+ let styleCaptured = false;
2106
+ const motionRef = (el) => {
2107
+ createMotion(el, getOpts, {
2108
+ initialAppliedBySSR: initialTarget !== null || styleMotionValues !== void 0 || styleStaticTransforms !== void 0,
2109
+ activeStore,
2110
+ parentContext: parentVariantCtx,
2111
+ styleMotionValues,
2112
+ styleStaticTransforms
2113
+ });
2114
+ };
2115
+ let renderedOnce = false;
2116
+ onMount(() => {
2117
+ renderedOnce = true;
2118
+ });
2119
+ /**
2120
+ * Walk `style` once and pull `MotionValue` refs into `styleMotionValues`.
2121
+ * Idempotent across re-renders — Stage 2's contract is "MV refs in style
2122
+ * are captured on first call and never re-scraped." Subsequent m()
2123
+ * invocations that pass a different style with new MVs won't pick them
2124
+ * up; that pattern wasn't in scope for v0.1.
2125
+ *
2126
+ * The read is `untrack`ed because m() is typically called from inside a
2127
+ * JSX spread, which Solid evaluates within a tracked owner. Without
2128
+ * untrack we'd subscribe to whatever signals the user's `style` object
2129
+ * references and re-fire this useMotion's owner-level effects on every
2130
+ * change.
2131
+ */
2132
+ const captureStyleEntries = (style) => {
2133
+ if (styleCaptured) return;
2134
+ styleCaptured = true;
2135
+ if (!style || typeof style !== "object") return;
2136
+ for (const key in style) {
2137
+ const value = style[key];
2138
+ if (isMotionValue$1(value)) {
2139
+ if (!styleMotionValues) styleMotionValues = /* @__PURE__ */ new Map();
2140
+ styleMotionValues.set(key, value);
2141
+ } else if (TRANSFORM_KEYS.has(key)) {
2142
+ const snap = snapshotValue(value);
2143
+ if (snap !== void 0) {
2144
+ if (!styleStaticTransforms) styleStaticTransforms = /* @__PURE__ */ new Map();
2145
+ styleStaticTransforms.set(key, snap);
2146
+ }
2147
+ }
2148
+ }
2149
+ };
2150
+ /**
2151
+ * Produce a style object with MV-valued keys (and transform-shortcut keys —
2152
+ * see below) removed. Solid's style binding would otherwise either write the
2153
+ * MotionValue instance as a literal (coercing it via String() to
2154
+ * "[object Object]") for MV-valued entries, or apply transform shortcuts
2155
+ * directly as bogus CSS properties for static-shortcut entries. createMotion
2156
+ * handles both via the registry-write path; we strip them here so the
2157
+ * Solid-bound `cleaned` style only contains regular CSS keys.
2158
+ */
2159
+ const stripStyleEntriesOwnedByRegistry = (style) => {
2160
+ if (!style) return {};
2161
+ const out = {};
2162
+ for (const key in style) {
2163
+ if (styleMotionValues?.has(key)) continue;
2164
+ if (TRANSFORM_KEYS.has(key)) continue;
2165
+ out[key] = style[key];
2166
+ }
2167
+ return out;
2168
+ };
2169
+ /**
2170
+ * Stage 4 — compose the first-paint inline style from:
2171
+ * 1. `initialTarget` (resolved via the priority chain at construction)
2172
+ * 2. MotionValue snapshots from `style: { key: mv }`
2173
+ * 3. Static transform shortcuts in `style: { x: 10, scale: 0.5 }`
2174
+ *
2175
+ * Style entries (2, 3) override `initialTarget` (1) on the same key because
2176
+ * `style` is the runtime source-of-truth for those keys. Returns the composed
2177
+ * `JSX.CSSProperties` or null when nothing applies (no initial + no style
2178
+ * registry contributions).
2179
+ *
2180
+ * Called only before `onMount` flips `renderedOnce`. After mount, the
2181
+ * registry's writer (in createMotion) owns el.style directly and this
2182
+ * function isn't consulted.
2183
+ */
2184
+ const composeFirstPaintStyle = (userStyle) => {
2185
+ const merged = {};
2186
+ let hasAny = false;
2187
+ if (initialTarget) {
2188
+ Object.assign(merged, initialTarget);
2189
+ hasAny = true;
2190
+ }
2191
+ if (styleMotionValues) for (const [key, mv] of styleMotionValues) {
2192
+ merged[key] = mv.get();
2193
+ hasAny = true;
2194
+ }
2195
+ if (userStyle) for (const key in userStyle) {
2196
+ if (styleMotionValues?.has(key)) continue;
2197
+ if (!TRANSFORM_KEYS.has(key)) continue;
2198
+ const v = userStyle[key];
2199
+ if (typeof v === "number" || typeof v === "string") {
2200
+ merged[key] = v;
2201
+ hasAny = true;
2202
+ }
2203
+ }
2204
+ return hasAny ? targetToStyle(merged) : null;
2205
+ };
2206
+ function getProps(userProps) {
2207
+ untrack(() => captureStyleEntries(userProps?.style));
2208
+ const wroteFirstPaintStyle = initialTarget !== null || styleMotionValues !== void 0 || styleStaticTransforms !== void 0;
2209
+ return mergeProps$1(userProps ?? {}, {
2210
+ get style() {
2211
+ const cleaned = stripStyleEntriesOwnedByRegistry(userProps?.style);
2212
+ if (renderedOnce) return cleaned;
2213
+ const composed = composeFirstPaintStyle(userProps?.style);
2214
+ return composed ? {
2215
+ ...cleaned,
2216
+ ...composed
2217
+ } : cleaned;
2218
+ },
2219
+ ref: mergeRefs(userProps?.ref, motionRef),
2220
+ ...wroteFirstPaintStyle ? { "data-motion-hydrated": "" } : {}
2221
+ });
2222
+ }
2223
+ const myVariantCtx = {
2224
+ variants: () => getOpts().variants,
2225
+ initial: () => {
2226
+ const v = getOpts().initial;
2227
+ return v === false ? void 0 : asVariantLabels(v);
2228
+ },
2229
+ animate: () => asVariantLabels(getOpts().animate),
2230
+ hover: () => active.whileHover ? asVariantLabels(getOpts().hover) : void 0,
2231
+ press: () => active.whilePress ? asVariantLabels(getOpts().press) : void 0,
2232
+ focus: () => active.whileFocus ? asVariantLabels(getOpts().focus) : void 0,
2233
+ inView: () => active.whileInView ? asVariantLabels(getOpts().inView) : void 0,
2234
+ exit: () => asVariantLabels(getOpts().exit),
2235
+ custom: () => getOpts().custom,
2236
+ transition: () => getOpts().transition
2237
+ };
2238
+ const Provider = (props) => createComponent(VariantContext.Provider, {
2239
+ value: myVariantCtx,
2240
+ get children() {
2241
+ return props.children;
2242
+ }
2243
+ });
2244
+ return Object.assign(getProps, { Provider });
2245
+ }
2246
+ function computeInitialTarget(opts, parentVariantCtx, presenceInitial) {
2247
+ if (presenceInitial?.() === false) {
2248
+ const animateValue = opts.animate !== void 0 ? opts.animate : parentVariantCtx.animate?.();
2249
+ if (animateValue === void 0) return null;
2250
+ return resolveTarget(animateValue, opts.variants, void 0, opts.custom ?? parentVariantCtx.custom?.());
2251
+ }
2252
+ if (opts.initial === false) return null;
2253
+ const inheritedInitial = parentVariantCtx.initial?.();
2254
+ const inheritedAnimate = parentVariantCtx.animate?.();
2255
+ const effective = opts.initial !== void 0 ? opts.initial : inheritedInitial !== void 0 ? inheritedInitial : opts.animate !== void 0 ? opts.animate : inheritedAnimate;
2256
+ if (effective === void 0) return null;
2257
+ return resolveTarget(effective, opts.variants, void 0, opts.custom ?? parentVariantCtx.custom?.());
2258
+ }
2259
+ //#endregion
2260
+ //#region src/motion-proxy.tsx
2261
+ /**
2262
+ * Frozen list of `MotionOptKey`s — fed to `splitProps` at every
2263
+ * tag-component render to separate motion options from element
2264
+ * attributes. The `satisfies` clause checks every entry against
2265
+ * `MotionOptKey` at compile time so typos / drift between the union
2266
+ * and the array surface as errors.
2267
+ */
2268
+ var MOTION_OPT_KEYS = [
2269
+ "initial",
2270
+ "animate",
2271
+ "exit",
2272
+ "hover",
2273
+ "press",
2274
+ "focus",
2275
+ "inView",
2276
+ "inViewOptions",
2277
+ "drag",
2278
+ "dragConstraints",
2279
+ "dragElastic",
2280
+ "dragMomentum",
2281
+ "dragTransition",
2282
+ "dragSnapToOrigin",
2283
+ "dragControls",
2284
+ "whileDrag",
2285
+ "panThreshold",
2286
+ "variants",
2287
+ "custom",
2288
+ "transition",
2289
+ "onAnimationStart",
2290
+ "onAnimationComplete",
2291
+ "onAnimationCancel",
2292
+ "onUpdate",
2293
+ "onHoverStart",
2294
+ "onHoverEnd",
2295
+ "onPressStart",
2296
+ "onPress",
2297
+ "onPressCancel",
2298
+ "onFocus",
2299
+ "onBlur",
2300
+ "onPanStart",
2301
+ "onPan",
2302
+ "onPanEnd",
2303
+ "onViewportEnter",
2304
+ "onViewportLeave",
2305
+ "onDragStart",
2306
+ "onDrag",
2307
+ "onDragEnd",
2308
+ "onDragTransitionEnd"
2309
+ ];
2310
+ var tagComponentCache = /* @__PURE__ */ new Map();
2311
+ var motionComponents = /* @__PURE__ */ new WeakSet();
2312
+ function makeMotionTag(tag) {
2313
+ const cached = tagComponentCache.get(tag);
2314
+ if (cached) return cached;
2315
+ const Tag = (props) => {
2316
+ const [motionOpts, rest] = splitProps(props, MOTION_OPT_KEYS);
2317
+ const m = useMotion(() => motionOpts);
2318
+ return createComponent(m.Provider, { get children() {
2319
+ return createComponent(Dynamic, mergeProps({ component: tag }, () => m(rest)));
2320
+ } });
2321
+ };
2322
+ const stored = Tag;
2323
+ tagComponentCache.set(tag, stored);
2324
+ motionComponents.add(stored);
2325
+ return stored;
2326
+ }
2327
+ /**
2328
+ * `motion.create(Component)` — wraps a custom Component with motion's
2329
+ * behavior. The wrapped Component must forward props to a single DOM
2330
+ * element root; the contract is documented in the {@link Motion.create}
2331
+ * JSDoc above and enforced at runtime (in dev mode) by detecting whether
2332
+ * motion's ref ever reaches the DOM after mount.
2333
+ */
2334
+ function motionCreate(Component) {
2335
+ if (process.env.NODE_ENV !== "production" && motionComponents.has(Component)) console.warn("[solidjs-motion] motion.create(motion.X) double-wraps the same element with two motion state machines. Compose options on a single layer instead.");
2336
+ const Wrapped = (props) => {
2337
+ const [motionOpts, rest] = splitProps(props, MOTION_OPT_KEYS);
2338
+ const m = useMotion(() => motionOpts);
2339
+ let refFired = false;
2340
+ const detector = (_el) => {
2341
+ refFired = true;
2342
+ };
2343
+ const userRef = rest.ref;
2344
+ const mergedUserRef = process.env.NODE_ENV !== "production" ? mergeRefs(userRef, detector) : userRef;
2345
+ const restWithDetector = process.env.NODE_ENV !== "production" ? mergeProps$1(rest, { ref: mergedUserRef }) : rest;
2346
+ if (process.env.NODE_ENV !== "production") onMount(() => {
2347
+ queueMicrotask(() => {
2348
+ if (!refFired) console.warn("[solidjs-motion] motion.create wrapped a Component whose root didn't receive motion's ref. The wrapped Component must either spread {...props} on a single DOM element OR explicitly forward `props.ref` to its root. Motion's animations and exit registration won't run until this is fixed.");
2349
+ });
2350
+ });
2351
+ return createComponent(m.Provider, { get children() {
2352
+ return createComponent(Component, mergeProps(() => m(restWithDetector)));
2353
+ } });
2354
+ };
2355
+ motionComponents.add(Wrapped);
2356
+ return Wrapped;
2357
+ }
2358
+ /**
2359
+ * `motion` — the indexable proxy. Every property access returns a cached
2360
+ * motion-aware component for the given HTML/SVG tag. The reserved
2361
+ * `motion.create` key returns the HOC entry point.
2362
+ *
2363
+ * @example HTML element
2364
+ * ```tsx
2365
+ * <motion.div animate={{ x: 100 }} hover={{ scale: 1.05 }}>
2366
+ * draggable card
2367
+ * </motion.div>
2368
+ * ```
2369
+ *
2370
+ * @example SVG element (handled transparently via <Dynamic>)
2371
+ * ```tsx
2372
+ * <motion.path d="M0 0 L100 100" animate={{ pathLength: 1 }} />
2373
+ * ```
2374
+ *
2375
+ * @example Wrapping a custom Component via the HOC
2376
+ * ```tsx
2377
+ * const Animated = motion.create(MyCard)
2378
+ * <Animated animate={{ scale: 1.05 }} class="my-card" />
2379
+ * ```
2380
+ *
2381
+ * Non-string keys (Symbols, well-known properties) return `undefined` so
2382
+ * debugging tools and `typeof` checks see a sane shape.
2383
+ */
2384
+ var motion = new Proxy({}, { get(_target, key) {
2385
+ if (typeof key !== "string") return void 0;
2386
+ if (key === "create") return motionCreate;
2387
+ return makeMotionTag(key);
2388
+ } });
2389
+ //#endregion
2390
+ //#region src/presence.tsx
2391
+ /**
2392
+ * Internal helper: maps the public `mode` prop to the value
2393
+ * `createSwitchTransition` expects. `popLayout` is intentionally deferred
2394
+ * (layout animations are v0.2+); `wait` maps to `out-in`; `sync` is the
2395
+ * default and corresponds to transition-group's `parallel`.
2396
+ */
2397
+ function switchMode(mode) {
2398
+ return mode === "wait" ? "out-in" : "parallel";
2399
+ }
2400
+ /**
2401
+ * Find every motion child registered under `root` (or root itself) and
2402
+ * return their [element, runExit] pairs. Walks the `runExits` map and
2403
+ * tests containment via `Node.contains`, which is O(depth) per check —
2404
+ * cheap because n is bounded by the number of motion children Presence
2405
+ * is tracking. Order isn't load-bearing; callers Promise.all the runExits.
2406
+ */
2407
+ function collectSubtreeExits(root, runExits) {
2408
+ const out = [];
2409
+ for (const [el, fn] of runExits) if (el === root || root.contains(el)) out.push([el, fn]);
2410
+ return out;
2411
+ }
2412
+ /**
2413
+ * Wraps a conditional or iterated JSX subtree and runs the descendants'
2414
+ * `exit` targets before they unmount. Matches motion-react's
2415
+ * `<AnimatePresence>` shape but with Solid's `<Show>` / `<For>` /
2416
+ * `<Index>` patterns instead of conditional children.
2417
+ *
2418
+ * Nested motion children are first-class: when an ancestor unmounts,
2419
+ * Presence walks the subtree from each resolved child and fires every
2420
+ * registered `runExit` it finds in parallel — including motion children
2421
+ * nested inside plain wrappers, or descendants whose `exit` label was
2422
+ * cascaded down via `m.Provider`. Each motion descendant animates with
2423
+ * its own variant/target; transition-group only releases the DOM once
2424
+ * the combined `Promise.all` settles. Mirrors motion-react's behavior
2425
+ * where a `<motion.div exit={...}>` inside an `<AnimatePresence>` boundary
2426
+ * animates correctly regardless of depth.
2427
+ *
2428
+ * @example Single (conditional unmount)
2429
+ * <Presence>
2430
+ * <Show when={open()}>
2431
+ * <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}>
2432
+ * saved
2433
+ * </motion.div>
2434
+ * </Show>
2435
+ * </Presence>
2436
+ *
2437
+ * @example List (items entering and exiting independently)
2438
+ * <Presence>
2439
+ * <For each={items()}>
2440
+ * {(item) => (
2441
+ * <motion.li exit={{ opacity: 0, x: 20 }}>{item.text}</motion.li>
2442
+ * )}
2443
+ * </For>
2444
+ * </Presence>
2445
+ */
2446
+ var Presence = (props) => {
2447
+ const runExits = /* @__PURE__ */ new Map();
2448
+ const runEnters = /* @__PURE__ */ new Map();
2449
+ const [presenceInitial, setPresenceInitial] = createSignal(props.initial ?? true);
2450
+ queueMicrotask(() => setPresenceInitial(true));
2451
+ const ctx = {
2452
+ register: (el, runExit) => {
2453
+ runExits.set(el, runExit);
2454
+ },
2455
+ unregister: (el) => {
2456
+ runExits.delete(el);
2457
+ },
2458
+ beforeUnmount: (el) => {
2459
+ const exiting = collectSubtreeExits(el, runExits);
2460
+ if (exiting.length === 0) return Promise.resolve();
2461
+ return Promise.all(exiting.map((pair) => pair[1]())).then(() => {
2462
+ for (const [exitedEl] of exiting) runExits.delete(exitedEl);
2463
+ });
2464
+ },
2465
+ registerEnter: (el, runEnter) => {
2466
+ runEnters.set(el, runEnter);
2467
+ },
2468
+ beforeMount: (el) => {
2469
+ const fn = runEnters.get(el);
2470
+ runEnters.delete(el);
2471
+ fn?.();
2472
+ },
2473
+ initial: presenceInitial
2474
+ };
2475
+ return createComponent(PresenceContext.Provider, {
2476
+ value: ctx,
2477
+ get children() {
2478
+ return createComponent(PresenceCore, {
2479
+ source: () => props.children,
2480
+ get mode() {
2481
+ return props.mode;
2482
+ },
2483
+ get exitMethod() {
2484
+ return props.exitMethod;
2485
+ },
2486
+ appear: presenceInitial,
2487
+ ctx
2488
+ });
2489
+ }
2490
+ });
2491
+ };
2492
+ var PresenceCore = (p) => {
2493
+ const resolved = resolveElements(p.source);
2494
+ const path = createMemo((prev) => {
2495
+ if (prev) return prev;
2496
+ const v = resolved();
2497
+ if (v == null) return null;
2498
+ return Array.isArray(v) ? "list" : "switch";
2499
+ });
2500
+ if (process.env.NODE_ENV !== "production") createEffect(() => {
2501
+ if (path() === "list" && p.mode === "wait") console.warn("[solidjs-motion] <Presence mode=\"wait\"> has no meaningful effect with a list of children — \"wait\" sequences a single exiting element before a single entering one. Use it with `<Show>`-style conditional rendering.");
2502
+ });
2503
+ return createComponent(Switch, { get children() {
2504
+ return [createComponent(Match, {
2505
+ get when() {
2506
+ return path() === "switch";
2507
+ },
2508
+ keyed: true,
2509
+ children: (_v) => createSwitchTransition(() => {
2510
+ const v = resolved();
2511
+ return Array.isArray(v) ? v[0] ?? null : v;
2512
+ }, {
2513
+ appear: p.appear(),
2514
+ mode: switchMode(p.mode),
2515
+ onExit(el, done) {
2516
+ const motionEl = el;
2517
+ if (motionEl instanceof HTMLElement || motionEl instanceof SVGElement) motionEl.style.pointerEvents = "none";
2518
+ p.ctx.beforeUnmount(motionEl).then(done);
2519
+ },
2520
+ onEnter(el, done) {
2521
+ p.ctx.beforeMount?.(el);
2522
+ done();
2523
+ }
2524
+ })
2525
+ }), createComponent(Match, {
2526
+ get when() {
2527
+ return path() === "list";
2528
+ },
2529
+ keyed: true,
2530
+ children: (_v) => createListTransition(() => resolved.toArray(), {
2531
+ appear: p.appear(),
2532
+ exitMethod: p.exitMethod,
2533
+ onChange({ added, removed, finishRemoved }) {
2534
+ for (const el of added) p.ctx.beforeMount?.(el);
2535
+ if (removed.length === 0) return;
2536
+ for (const el of removed) if (el instanceof HTMLElement || el instanceof SVGElement) el.style.pointerEvents = "none";
2537
+ Promise.all(removed.map((el) => p.ctx.beforeUnmount(el))).then(() => finishRemoved(removed));
2538
+ }
2539
+ })
2540
+ })];
2541
+ } });
2542
+ };
2543
+ function useAnimatePresence(options) {
2544
+ const runExits = /* @__PURE__ */ new Map();
2545
+ const [presenceInitial, setPresenceInitial] = createSignal(options?.initial ?? true);
2546
+ queueMicrotask(() => setPresenceInitial(true));
2547
+ const ctx = {
2548
+ register: (el, runExit) => {
2549
+ runExits.set(el, runExit);
2550
+ },
2551
+ unregister: (el) => {
2552
+ runExits.delete(el);
2553
+ },
2554
+ beforeUnmount: (el) => {
2555
+ const exiting = collectSubtreeExits(el, runExits);
2556
+ if (exiting.length === 0) return Promise.resolve();
2557
+ return Promise.all(exiting.map((pair) => pair[1]())).then(() => {
2558
+ for (const [exitedEl] of exiting) runExits.delete(exitedEl);
2559
+ });
2560
+ },
2561
+ initial: presenceInitial
2562
+ };
2563
+ onCleanup(() => runExits.clear());
2564
+ const Provider = (p) => createComponent(PresenceContext.Provider, {
2565
+ value: ctx,
2566
+ get children() {
2567
+ return p.children;
2568
+ }
2569
+ });
2570
+ const exit = async () => {
2571
+ const snapshot = [...runExits.values()];
2572
+ await Promise.all(snapshot.map((fn) => fn()));
2573
+ };
2574
+ return {
2575
+ Provider,
2576
+ exit
2577
+ };
2578
+ }
2579
+ //#endregion
2580
+ //#region src/primitives/createScroll.ts
2581
+ /**
2582
+ * Bind four {@link MotionValueAccessor}s to a scroll source. Mirrors
2583
+ * motion/react's `useScroll`; defaults to the window when no container is
2584
+ * supplied. Each returned value is callable as a Solid Accessor AND has the
2585
+ * full MotionValue surface, so it composes with `useMotion`'s target,
2586
+ * `animate()`, `createTransform`, and direct JSX reactivity.
2587
+ *
2588
+ * @example
2589
+ * const { scrollY, scrollYProgress } = createScroll()
2590
+ * const opacity = createTransform(scrollYProgress, [0, 1], [1, 0])
2591
+ *
2592
+ * @example
2593
+ * const [el, setEl] = createSignal<HTMLElement>()
2594
+ * const { scrollY } = createScroll({ container: el })
2595
+ * <div ref={setEl} style={{ overflow: "auto" }}>...</div>
2596
+ */
2597
+ function createScroll(options) {
2598
+ const scrollX = createMotionValue(0);
2599
+ const scrollY = createMotionValue(0);
2600
+ const scrollXProgress = createMotionValue(0);
2601
+ const scrollYProgress = createMotionValue(0);
2602
+ const handler = (_progress, info) => {
2603
+ if (!info) return;
2604
+ scrollX.set(info.x.current);
2605
+ scrollY.set(info.y.current);
2606
+ scrollXProgress.set(info.x.progress);
2607
+ scrollYProgress.set(info.y.progress);
2608
+ };
2609
+ createEffect(() => {
2610
+ onCleanup(scroll$1(handler, {
2611
+ container: options?.container?.() ?? void 0,
2612
+ target: options?.target?.() ?? void 0,
2613
+ axis: options?.axis,
2614
+ offset: options?.offset
2615
+ }));
2616
+ });
2617
+ return {
2618
+ scrollX,
2619
+ scrollY,
2620
+ scrollXProgress,
2621
+ scrollYProgress
2622
+ };
2623
+ }
2624
+ //#endregion
2625
+ export { MOTION_OPT_KEYS, MotionConfig, MotionConfigContext, Presence, PresenceContext, VariantContext, animate, createDragControls, createInView, createMotion, createMotionValue, createMotionValueEvent, createPan, createReducedMotion, createScroll, createSpring, createTemplate, createTime, createTransform, createVelocity, effectiveLabels, inView, isMotionValue, motion, motionValue, resolveVariant, scroll, shouldReduceMotion, spring, targetToStyle, toSignal, useAnimatePresence, useMotion, useMotionConfig, usePresenceContext, useVariantContext };
2626
+
2627
+ //# sourceMappingURL=index.js.map