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/src/types.ts ADDED
@@ -0,0 +1,538 @@
1
+ import type {
2
+ AnimationPlaybackControls,
3
+ MotionValue,
4
+ PanInfo,
5
+ PressGestureInfo,
6
+ ResolvedValues,
7
+ SpringOptions,
8
+ Transition,
9
+ } from "motion"
10
+ import type { InertiaOptions } from "motion-dom"
11
+ import type { Accessor, Component, JSX } from "solid-js"
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // Re-exports from motion. These are the upstream types we adopt as-is so the
15
+ // migration story stays clean and our wrappers don't drift from the engine.
16
+ // ---------------------------------------------------------------------------
17
+
18
+ export type {
19
+ AnimationPlaybackControls,
20
+ MotionValue,
21
+ PanInfo,
22
+ ResolvedValues,
23
+ SpringOptions,
24
+ Transition,
25
+ }
26
+
27
+ /**
28
+ * Callable {@link MotionValue} — has every MotionValue method (`.get`, `.set`,
29
+ * `.jump`, `.on`, `.getVelocity`, ...) AND can be invoked as a Solid-tracked
30
+ * Accessor. Returned by `createMotionValue`, `createTransform`,
31
+ * `createSpring`, `createTime`, `createVelocity`, and `createTemplate`.
32
+ *
33
+ * - `mv()` — Solid-tracked read (use in JSX, `createEffect`, `createMemo`)
34
+ * - `mv.get()` — sync untracked read (motion engine's API; matches motion/react)
35
+ * - `mv.set(v)` / `mv.jump(v)` — imperative writes (also trigger the Solid
36
+ * signal via an internal `mv.on("change", ...)` bridge)
37
+ *
38
+ * Passes motion's `isMotionValue` (duck-typed on `.getVelocity`), so the same
39
+ * value can be used as a target in `useMotion({ animate: { x: mv } })` or as
40
+ * the target of `animate(mv, 100)`.
41
+ */
42
+ export type MotionValueAccessor<T> = MotionValue<T> & (() => T)
43
+
44
+ /** Per-press info delivered to onPressStart / onPress / onPressCancel. */
45
+ export type PressInfo = PressGestureInfo
46
+
47
+ /**
48
+ * Options for {@link DragControls.start}.
49
+ *
50
+ * Only `snapToCursor` is exposed for v0.1 (Q9b — anything else can be added
51
+ * non-breakingly later).
52
+ */
53
+ export type DragControlsStartOptions = {
54
+ /**
55
+ * When true, center the dragged element under the pointer on drag-start.
56
+ * Useful for "drag handle hand-off" patterns where the handle and the
57
+ * dragged element are distinct — the user clicks the handle and the
58
+ * dragged element jumps to follow the pointer.
59
+ */
60
+ snapToCursor?: boolean
61
+ }
62
+
63
+ /**
64
+ * Imperative drag controls returned from `createDragControls()`. Defined
65
+ * locally because motion's public `motion` export doesn't surface its
66
+ * DragControls class type.
67
+ *
68
+ * Usage (Q9): one controls instance binds to one motion element via
69
+ * `dragControls: controls` in MotionOptions. A separate UI element (e.g., a
70
+ * drag-handle button) captures the pointer event and forwards it via
71
+ * `controls.start(event)`, decoupling the drag-listener element from the
72
+ * element that actually moves.
73
+ */
74
+ export type DragControls = {
75
+ /**
76
+ * Begin a drag from an externally-captured pointer event. Bypasses
77
+ * createDrag's threshold gate — drag starts immediately at the event's
78
+ * coordinates (the user explicitly invoked, so no "did they really
79
+ * mean it" hysteresis is needed).
80
+ *
81
+ * No-op when no motion element is currently registered with this
82
+ * controls instance.
83
+ */
84
+ start: (event: PointerEvent, options?: DragControlsStartOptions) => void
85
+ }
86
+
87
+ // ---------------------------------------------------------------------------
88
+ // Value types — a property's value can be a literal, a MotionValue, or a Solid
89
+ // Accessor. Motion's engine subscribes to MotionValues natively; Accessors are
90
+ // snapshotted at effect time.
91
+ // ---------------------------------------------------------------------------
92
+
93
+ type Numeric = number | MotionValue<number> | Accessor<number>
94
+ type Stringish = string | MotionValue<string> | Accessor<string>
95
+ type AnyValue = Numeric | Stringish
96
+
97
+ /** A property value or an array of values (keyframe sequence). */
98
+ export type Keyframes<T> = T | T[]
99
+
100
+ // ---------------------------------------------------------------------------
101
+ // Target — strict transform shorthand + index signature for arbitrary CSS.
102
+ // Plain values, MotionValues, and Accessors all accepted. A `transition` key
103
+ // can also be present for per-target transition overrides.
104
+ // ---------------------------------------------------------------------------
105
+
106
+ export type Target = {
107
+ // Translate shorthand (px when number)
108
+ x?: Keyframes<Numeric | Stringish>
109
+ y?: Keyframes<Numeric | Stringish>
110
+ z?: Keyframes<Numeric | Stringish>
111
+
112
+ // Scale (dimensionless)
113
+ scale?: Keyframes<Numeric>
114
+ scaleX?: Keyframes<Numeric>
115
+ scaleY?: Keyframes<Numeric>
116
+ scaleZ?: Keyframes<Numeric>
117
+
118
+ // Rotate (deg when number)
119
+ rotate?: Keyframes<Numeric | Stringish>
120
+ rotateX?: Keyframes<Numeric | Stringish>
121
+ rotateY?: Keyframes<Numeric | Stringish>
122
+ rotateZ?: Keyframes<Numeric | Stringish>
123
+
124
+ // Skew (deg when number)
125
+ skew?: Keyframes<Numeric | Stringish>
126
+ skewX?: Keyframes<Numeric | Stringish>
127
+ skewY?: Keyframes<Numeric | Stringish>
128
+
129
+ // Transform-related but not shorthand
130
+ transformPerspective?: Keyframes<Numeric>
131
+ transformOrigin?: Stringish
132
+
133
+ // Common CSS with narrowed types
134
+ opacity?: Keyframes<Numeric>
135
+ zIndex?: Keyframes<Numeric>
136
+
137
+ // Per-target transition override (Q3 sub-3)
138
+ transition?: Transition
139
+
140
+ // Catch-all for arbitrary CSS (incl. CSS variables like "--foo").
141
+ // Index signature must accept every named property above, plus Transition.
142
+ [key: string]: Keyframes<AnyValue> | Transition | undefined
143
+ }
144
+
145
+ // ---------------------------------------------------------------------------
146
+ // Variants
147
+ // ---------------------------------------------------------------------------
148
+
149
+ /** A single variant — a target shape, or a function producing one from `custom`. */
150
+ export type Variant = Target | ((custom: unknown) => Target)
151
+
152
+ /** A named map of variants. */
153
+ export type Variants = Record<string, Variant>
154
+
155
+ /** What can be passed where a variant name is expected (string, array of strings). */
156
+ export type VariantLabels = string | string[]
157
+
158
+ /** What can drive an animation: a target object, a variant name, or an array of names. */
159
+ export type AnimateValue = Target | VariantLabels
160
+
161
+ // ---------------------------------------------------------------------------
162
+ // Viewport / IntersectionObserver options for inView gesture and createInView.
163
+ // ---------------------------------------------------------------------------
164
+
165
+ export type ViewportOptions = {
166
+ /** Stop observing after first entry (default false). */
167
+ once?: boolean
168
+ /** rootMargin string passed to IntersectionObserver. */
169
+ margin?: string
170
+ /**
171
+ * Threshold(s) at which IntersectionObserver fires its callback.
172
+ *
173
+ * - `"some"` (default) — fires the moment any pixel intersects.
174
+ * - `"all"` — fires only when fully visible.
175
+ * - `number` 0–1 — single threshold; fires at that intersection ratio.
176
+ * - `number[]` — an array of thresholds; fires at each crossing. Use this
177
+ * for continuous `intersectionRatio` updates (e.g., for scroll-linked
178
+ * fades). With a single threshold the observer is silent between
179
+ * crossings, so `entry.intersectionRatio` stays stale.
180
+ */
181
+ amount?: "some" | "all" | number | number[]
182
+ /** Solid-style accessor returning the root element; defaults to viewport. */
183
+ root?: () => Element | null
184
+ }
185
+
186
+ // ---------------------------------------------------------------------------
187
+ // Drag options
188
+ // ---------------------------------------------------------------------------
189
+
190
+ /**
191
+ * Drag bounds (Q8). Three shapes:
192
+ *
193
+ * - **Numeric** (`{ top, left, right, bottom }`): absolute MV-value bounds.
194
+ * Missing keys are unbounded on that side.
195
+ * - **HTMLElement**: container the dragged element must stay inside. Bounds
196
+ * are computed at drag-start from the container's bounding rect.
197
+ * - **`() => HTMLElement | null`**: Solid-style accessor for a reactive
198
+ * container ref. Called at drag-start.
199
+ */
200
+ export type DragConstraints =
201
+ | { top?: number; left?: number; right?: number; bottom?: number }
202
+ | HTMLElement
203
+ | (() => HTMLElement | null)
204
+
205
+ export type DragOptions = {
206
+ drag?: boolean | "x" | "y"
207
+ dragConstraints?: DragConstraints
208
+ /** Elastic resistance past constraints (0–1, default 0.5). */
209
+ dragElastic?: number
210
+ /** Carry momentum after release (default true). */
211
+ dragMomentum?: boolean
212
+ /**
213
+ * Options for the momentum / snap-back animation. Inertia by default
214
+ * (Q15d preset). Shallow-merges over the defaults — user fields override
215
+ * any single setting (bounceStiffness, timeConstant, etc.).
216
+ */
217
+ dragTransition?: Partial<InertiaOptions>
218
+ /** Snap back to origin on release (default false). */
219
+ dragSnapToOrigin?: boolean
220
+ /** Imperatively-triggered drag (from createDragControls). */
221
+ dragControls?: DragControls
222
+ }
223
+
224
+ // ---------------------------------------------------------------------------
225
+ // Lifecycle callbacks. Every hook is typed in Phase 1; only the animation-
226
+ // lifecycle ones (start/complete/cancel/update) are wired in Phase 1. Gesture
227
+ // and drag hooks fire when those features land in Phase 2.
228
+ // ---------------------------------------------------------------------------
229
+
230
+ export type MotionCallbacks = {
231
+ // Animation lifecycle (Phase 1)
232
+ onAnimationStart?: () => void
233
+ onAnimationComplete?: (definition: AnimateValue) => void
234
+ onAnimationCancel?: () => void
235
+ onUpdate?: (latest: ResolvedValues) => void
236
+
237
+ // Gesture lifecycle (Phase 2)
238
+ onHoverStart?: (e: PointerEvent) => void
239
+ onHoverEnd?: (e: PointerEvent) => void
240
+ /** Press began. `info.success` isn't meaningful yet — see {@link onPress} / {@link onPressCancel}. */
241
+ onPressStart?: (e: PointerEvent) => void
242
+ /** Press completed with the pointer still over the element. */
243
+ onPress?: (e: PointerEvent, info: PressInfo) => void
244
+ /** Press cancelled — pointer left the element before pointer-up. */
245
+ onPressCancel?: (e: PointerEvent, info: PressInfo) => void
246
+ onFocus?: (e: FocusEvent) => void
247
+ onBlur?: (e: FocusEvent) => void
248
+ onPanStart?: (e: PointerEvent, info: PanInfo) => void
249
+ onPan?: (e: PointerEvent, info: PanInfo) => void
250
+ onPanEnd?: (e: PointerEvent, info: PanInfo) => void
251
+ onViewportEnter?: (entry: IntersectionObserverEntry) => void
252
+ onViewportLeave?: (entry: IntersectionObserverEntry) => void
253
+
254
+ // Drag lifecycle (Phase 2)
255
+ onDragStart?: (e: PointerEvent, info: PanInfo) => void
256
+ onDrag?: (e: PointerEvent, info: PanInfo) => void
257
+ onDragEnd?: (e: PointerEvent, info: PanInfo) => void
258
+ onDragTransitionEnd?: () => void
259
+ }
260
+
261
+ // ---------------------------------------------------------------------------
262
+ // MotionOptions — the full options bag accepted by useMotion / createMotion /
263
+ // <motion.div> / motion(). Includes animation targets, gesture targets,
264
+ // drag config, variants, callbacks, custom prop.
265
+ // ---------------------------------------------------------------------------
266
+
267
+ export type MotionOptions = MotionCallbacks &
268
+ DragOptions & {
269
+ /** Starting state. `false` means "don't apply an initial style". */
270
+ initial?: AnimateValue | false
271
+ /** Target the element animates to. */
272
+ animate?: AnimateValue
273
+ /** Target applied when component unmounts inside <Presence>. */
274
+ exit?: AnimateValue
275
+
276
+ /** Hover gesture target. */
277
+ hover?: AnimateValue
278
+ /** Press gesture target. */
279
+ press?: AnimateValue
280
+ /** Focus gesture target. */
281
+ focus?: AnimateValue
282
+ /** Viewport gesture target. */
283
+ inView?: AnimateValue
284
+ /** Viewport observer config for the inView gesture. */
285
+ inViewOptions?: ViewportOptions
286
+
287
+ /**
288
+ * Visual-state target applied while a drag gesture is active. Like
289
+ * other gesture targets, this can be a Target object or a variant
290
+ * label. Common idiom: `whileDrag: { scale: 1.05 }` for a lift-while-
291
+ * dragging effect — composes with drag's translation via the shared
292
+ * VisualElement (Q5/C-lean).
293
+ */
294
+ whileDrag?: AnimateValue
295
+
296
+ /**
297
+ * Minimum cumulative pointer movement (in px) before pan/drag start fires.
298
+ * Distinguishes a pan from a click. Q11a default: 3px (matches motion).
299
+ */
300
+ panThreshold?: number
301
+
302
+ /** Default transition applied to every property in animate/gesture targets. */
303
+ transition?: Transition
304
+
305
+ /** Named variant map; resolved by variant name in animate/gesture targets. */
306
+ variants?: Variants
307
+
308
+ /** Value passed to function variants. */
309
+ custom?: unknown
310
+ }
311
+
312
+ // ---------------------------------------------------------------------------
313
+ // useMotion return type — a getter function that merges user props with
314
+ // motion's props (style, ref, hydration marker), plus a Provider component
315
+ // for opt-in variant context propagation to descendants.
316
+ // ---------------------------------------------------------------------------
317
+
318
+ /**
319
+ * The element types `useMotion` can attach to. HTMLElement covers the bulk;
320
+ * SVGElement is included so `motion.path`, `motion.circle`, etc. (Phase 4)
321
+ * thread through without a `ref` type-cast. `createMotion` only uses methods
322
+ * available on both (style, addEventListener, getBoundingClientRect) so the
323
+ * union is honored at runtime. Drag stays HTMLElement-only via an
324
+ * `instanceof` check in createMotion's body — motion-dom's VisualElement
325
+ * layer is HTML-specific.
326
+ */
327
+ export type MotionElement = HTMLElement | SVGElement
328
+
329
+ // ---------------------------------------------------------------------------
330
+ // MotionStyle — the `style` prop shape accepted by useMotion + motion.X.
331
+ //
332
+ // Extends `JSX.CSSProperties` with two additions:
333
+ //
334
+ // 1. Motion's transform-shortcut keys (`x`, `y`, `scale`, `rotate`, ...).
335
+ // These aren't valid CSS property names — motion composes them into
336
+ // the `transform` CSS property at runtime via the registry-writer.
337
+ //
338
+ // 2. MotionValue variants of every value. `style={{ opacity: opacityMV,
339
+ // scale: scaleMV }}` typechecks because every key (CSS or transform
340
+ // shortcut) accepts either its native value type OR a MotionValue.
341
+ //
342
+ // The runtime contract lives in useMotion's `captureStyleEntries`:
343
+ // MotionValues land in the registry and the writer composes them on
344
+ // every change; static transform shortcuts seed transient registry
345
+ // entries; plain CSS keys pass through to Solid's style binding.
346
+ // ---------------------------------------------------------------------------
347
+
348
+ /**
349
+ * Motion's transform-shortcut keys and their value types. Each key may be
350
+ * a value OR a `MotionValue` holding that value.
351
+ *
352
+ * Value units (when expressed as a `number`):
353
+ * - `x` / `y` / `z` / `transformPerspective` → px
354
+ * - `scale*` → dimensionless multiplier
355
+ * - `rotate*` / `skew*` → deg
356
+ *
357
+ * Strings with explicit units pass through verbatim (e.g.
358
+ * `x: "50%"`, `rotate: "0.5turn"`).
359
+ *
360
+ * Variance note: motion's `MotionValue<T>` is invariant in `T` (it has both
361
+ * `get(): T` and `set(v: T)`), which means a user's `MotionValue<number>`
362
+ * cannot widen to `MotionValue<number | string>`. We use `MotionValue<any>`
363
+ * across the shortcut value types so a literal `createMotionValue(1)` is
364
+ * assignable as `x` / `y` / etc. without forcing the caller to type the MV
365
+ * with the full union. Type-safety on the value side is mostly recovered by
366
+ * the runtime: `formatProperty` and `transformFunctionFor` handle the
367
+ * number/string distinction.
368
+ */
369
+ // biome-ignore lint/suspicious/noExplicitAny: see variance note above.
370
+ type AnyMotionValue = MotionValue<any>
371
+
372
+ export type MotionTransformShortcuts = {
373
+ x?: number | string | AnyMotionValue
374
+ y?: number | string | AnyMotionValue
375
+ z?: number | string | AnyMotionValue
376
+ scale?: number | string | AnyMotionValue
377
+ scaleX?: number | string | AnyMotionValue
378
+ scaleY?: number | string | AnyMotionValue
379
+ scaleZ?: number | string | AnyMotionValue
380
+ rotate?: number | string | AnyMotionValue
381
+ rotateX?: number | string | AnyMotionValue
382
+ rotateY?: number | string | AnyMotionValue
383
+ rotateZ?: number | string | AnyMotionValue
384
+ skew?: number | string | AnyMotionValue
385
+ skewX?: number | string | AnyMotionValue
386
+ skewY?: number | string | AnyMotionValue
387
+ transformPerspective?: number | string | AnyMotionValue
388
+ }
389
+
390
+ /**
391
+ * Widen each value of `T` to also accept any `MotionValue`. Pragmatic shape:
392
+ * motion's `MotionValue<T>` is invariant in T, so a strict per-key
393
+ * `MotionValue<NonNullable<T[K]>>` would reject e.g. `MotionValue<number>` for
394
+ * a `width: string | number` key. `MotionValue<any>` is the necessary
395
+ * escape hatch — the runtime always normalizes via `mv.get()` and
396
+ * `formatProperty`.
397
+ */
398
+ type WithMotionValues<T> = {
399
+ [K in keyof T]?: T[K] | AnyMotionValue
400
+ }
401
+
402
+ /**
403
+ * The `style` prop shape for motion-aware elements: every native CSS
404
+ * property (with values optionally widened to a `MotionValue`), plus motion's
405
+ * transform-shortcut keys (with the same widening).
406
+ *
407
+ * Some CSS individual-transform properties (`scale`, `rotate`) collide with
408
+ * motion shortcut keys of the same name. We strip them from the CSS side
409
+ * before intersecting — motion's semantics win, the legacy CSS individual-
410
+ * transform path is a corner case users almost never reach for.
411
+ *
412
+ * @example
413
+ * const scale = createMotionValue(1)
414
+ * const m = useMotion({})
415
+ * <div {...m({ style: { scale, opacity: 0.5, color: "red" } })} />
416
+ */
417
+ export type MotionStyle = MotionTransformShortcuts &
418
+ WithMotionValues<Omit<JSX.CSSProperties, keyof MotionTransformShortcuts>>
419
+
420
+ // Override `ref` + `style` from `JSX.HTMLAttributes` rather than intersecting
421
+ // (the latter would produce a `string | MotionStyle` mess for `style` because
422
+ // the base allows a raw CSS string and TypeScript can't narrow the union
423
+ // through the consumer code paths in useMotion).
424
+ export type ElementProps = Omit<JSX.HTMLAttributes<MotionElement>, "ref" | "style"> & {
425
+ ref?: ((el: MotionElement) => void) | MotionElement | undefined
426
+ style?: MotionStyle
427
+ }
428
+
429
+ export type MotionMergedProps<P extends ElementProps> = Omit<P, "ref" | "style"> & {
430
+ // Output style is the INTERSECTION of MotionStyle and JSX.CSSProperties.
431
+ // This is the only shape that works in both directions:
432
+ //
433
+ // • Spread onto a raw JSX element: `<div {...m({})} />` — element's
434
+ // `style` expects `string | CSSProperties | undefined`. Intersection's
435
+ // scale/rotate collapse to `number | string` (MV variants drop out
436
+ // against the CSS Scale/Rotation types), assignable to CSSProperties.
437
+ //
438
+ // • Chained back as input: `fade(slide({ class: "card" }))` — outer
439
+ // useMotion's `m()` expects `ElementProps` with `style: MotionStyle`.
440
+ // The intersection is also assignable to MotionStyle (narrower).
441
+ //
442
+ // Runtime emits MV-stripped values via `stripStyleEntriesOwnedByRegistry`,
443
+ // so the actually-rendered prop carries only the intersection-narrow
444
+ // values — type and runtime agree at this point.
445
+ style: MotionStyle & JSX.CSSProperties
446
+ ref: (el: MotionElement) => void
447
+ "data-motion-hydrated"?: ""
448
+ }
449
+
450
+ export type MotionGetProps = <P extends ElementProps>(userProps?: P) => MotionMergedProps<P>
451
+
452
+ export type UseMotionResult = MotionGetProps & {
453
+ /** Opt-in: wrap descendant motion elements to receive this element's variant context. */
454
+ Provider: Component<{ children: JSX.Element }>
455
+ }
456
+
457
+ // ---------------------------------------------------------------------------
458
+ // Contexts
459
+ // ---------------------------------------------------------------------------
460
+
461
+ /** Propagates the variant state from a parent motion element to descendants. */
462
+ export type VariantContextValue = {
463
+ variants?: Accessor<Variants | undefined>
464
+ /** Propagated only when the parent's `initial` is a variant name (not `false` or an explicit Target). */
465
+ initial?: Accessor<VariantLabels | undefined>
466
+ animate?: Accessor<VariantLabels | undefined>
467
+ hover?: Accessor<VariantLabels | undefined>
468
+ press?: Accessor<VariantLabels | undefined>
469
+ focus?: Accessor<VariantLabels | undefined>
470
+ inView?: Accessor<VariantLabels | undefined>
471
+ exit?: Accessor<VariantLabels | undefined>
472
+ custom?: Accessor<unknown>
473
+ transition?: Accessor<Transition | undefined>
474
+ }
475
+
476
+ /**
477
+ * Wired with a no-op default; the real implementation is provided by
478
+ * `<Presence>` and `useAnimatePresence()` in Phase 3.
479
+ *
480
+ * Inverted shape vs. motion-react's `AnimatePresenceProps`: the child
481
+ * registers a `runExit` callable that knows how to animate ITSELF out (closes
482
+ * over its own state machine). Presence just coordinates timing — it doesn't
483
+ * resolve targets or merge transitions. See ADR 0003.
484
+ *
485
+ * - `register(el, runExit)` — called from `createMotion` when `opts.exit` is
486
+ * set. `runExit` flips the state machine's `exit` flag and awaits the
487
+ * resulting animate's completion.
488
+ * - `unregister(el)` — Presence (or the hook's `exit()`) prunes after exit.
489
+ * createMotion deliberately omits self-unregister; Solid disposes the
490
+ * child owner well before transition-group's onExit runs.
491
+ * - `beforeUnmount(el)` — Presence (or the hook's `exit()`) dispatches to the
492
+ * registered `runExit`. Returns a resolved promise if no `runExit` is
493
+ * registered, so non-exit children pass through cleanly.
494
+ * - `registerEnter` / `beforeMount` — symmetric to the exit pair. createMotion
495
+ * defers its first-mount animate when it's inside a real Presence (the
496
+ * no-op default has no `registerEnter`) because the element may be off-DOM
497
+ * at the moment the gesture-state-machine first iterates (e.g., the new
498
+ * child during a `mode: "wait"` swap is created BEFORE the old child's
499
+ * exit completes). Running motion's `animate()` on a disconnected element
500
+ * completes off-DOM and the final `commitStyles` silently no-ops, leaving
501
+ * the element painted at its `initial` target when it finally enters the
502
+ * DOM. Presence's `onEnter` (switch) / `onChange.added` (list) calls
503
+ * `beforeMount(el)` once the element is actually connected; the child's
504
+ * registered `runEnter` flips its readiness signal and the state machine
505
+ * dispatches the first animate against a live element.
506
+ * - `initial` — when an enclosing `<Presence initial={false}>` (or the hook
507
+ * with `initial: false`) is active, descendants suppress their first-mount
508
+ * animation. Accessor-shaped so the implementation can flip post-mount.
509
+ */
510
+ export type PresenceContextValue = {
511
+ register: (el: MotionElement, runExit: () => Promise<void>) => void
512
+ unregister: (el: MotionElement) => void
513
+ beforeUnmount: (el: MotionElement) => Promise<void>
514
+ registerEnter?: (el: MotionElement, runEnter: () => void) => void
515
+ beforeMount?: (el: MotionElement) => void
516
+ initial?: Accessor<boolean>
517
+ }
518
+
519
+ /** <MotionConfig> provides defaults that flow to every descendant motion element. */
520
+ export type MotionConfigContextValue = {
521
+ /** "always" forces snap; "never" ignores system pref; "user" respects prefers-reduced-motion. */
522
+ reducedMotion: Accessor<"always" | "never" | "user">
523
+ /** Default transition merged with descendant transitions (descendant wins on conflict). */
524
+ transition: Accessor<Transition | undefined>
525
+ /** CSP nonce applied to any inline style emissions (advanced). */
526
+ nonce: Accessor<string | undefined>
527
+ }
528
+
529
+ // ---------------------------------------------------------------------------
530
+ // MotionConfig component props
531
+ // ---------------------------------------------------------------------------
532
+
533
+ export type MotionConfigProps = {
534
+ reducedMotion?: "always" | "never" | "user"
535
+ transition?: Transition
536
+ nonce?: string
537
+ children: JSX.Element
538
+ }