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
@@ -0,0 +1,374 @@
1
+ import { AnimationPlaybackControls, MotionValue, PanInfo, PressGestureInfo, ResolvedValues, SpringOptions, Transition } from 'motion';
2
+ import { InertiaOptions } from 'motion-dom';
3
+ import { Accessor, Component, JSX } from 'solid-js';
4
+ export type { AnimationPlaybackControls, MotionValue, PanInfo, ResolvedValues, SpringOptions, Transition, };
5
+ /**
6
+ * Callable {@link MotionValue} — has every MotionValue method (`.get`, `.set`,
7
+ * `.jump`, `.on`, `.getVelocity`, ...) AND can be invoked as a Solid-tracked
8
+ * Accessor. Returned by `createMotionValue`, `createTransform`,
9
+ * `createSpring`, `createTime`, `createVelocity`, and `createTemplate`.
10
+ *
11
+ * - `mv()` — Solid-tracked read (use in JSX, `createEffect`, `createMemo`)
12
+ * - `mv.get()` — sync untracked read (motion engine's API; matches motion/react)
13
+ * - `mv.set(v)` / `mv.jump(v)` — imperative writes (also trigger the Solid
14
+ * signal via an internal `mv.on("change", ...)` bridge)
15
+ *
16
+ * Passes motion's `isMotionValue` (duck-typed on `.getVelocity`), so the same
17
+ * value can be used as a target in `useMotion({ animate: { x: mv } })` or as
18
+ * the target of `animate(mv, 100)`.
19
+ */
20
+ export type MotionValueAccessor<T> = MotionValue<T> & (() => T);
21
+ /** Per-press info delivered to onPressStart / onPress / onPressCancel. */
22
+ export type PressInfo = PressGestureInfo;
23
+ /**
24
+ * Options for {@link DragControls.start}.
25
+ *
26
+ * Only `snapToCursor` is exposed for v0.1 (Q9b — anything else can be added
27
+ * non-breakingly later).
28
+ */
29
+ export type DragControlsStartOptions = {
30
+ /**
31
+ * When true, center the dragged element under the pointer on drag-start.
32
+ * Useful for "drag handle hand-off" patterns where the handle and the
33
+ * dragged element are distinct — the user clicks the handle and the
34
+ * dragged element jumps to follow the pointer.
35
+ */
36
+ snapToCursor?: boolean;
37
+ };
38
+ /**
39
+ * Imperative drag controls returned from `createDragControls()`. Defined
40
+ * locally because motion's public `motion` export doesn't surface its
41
+ * DragControls class type.
42
+ *
43
+ * Usage (Q9): one controls instance binds to one motion element via
44
+ * `dragControls: controls` in MotionOptions. A separate UI element (e.g., a
45
+ * drag-handle button) captures the pointer event and forwards it via
46
+ * `controls.start(event)`, decoupling the drag-listener element from the
47
+ * element that actually moves.
48
+ */
49
+ export type DragControls = {
50
+ /**
51
+ * Begin a drag from an externally-captured pointer event. Bypasses
52
+ * createDrag's threshold gate — drag starts immediately at the event's
53
+ * coordinates (the user explicitly invoked, so no "did they really
54
+ * mean it" hysteresis is needed).
55
+ *
56
+ * No-op when no motion element is currently registered with this
57
+ * controls instance.
58
+ */
59
+ start: (event: PointerEvent, options?: DragControlsStartOptions) => void;
60
+ };
61
+ type Numeric = number | MotionValue<number> | Accessor<number>;
62
+ type Stringish = string | MotionValue<string> | Accessor<string>;
63
+ type AnyValue = Numeric | Stringish;
64
+ /** A property value or an array of values (keyframe sequence). */
65
+ export type Keyframes<T> = T | T[];
66
+ export type Target = {
67
+ x?: Keyframes<Numeric | Stringish>;
68
+ y?: Keyframes<Numeric | Stringish>;
69
+ z?: Keyframes<Numeric | Stringish>;
70
+ scale?: Keyframes<Numeric>;
71
+ scaleX?: Keyframes<Numeric>;
72
+ scaleY?: Keyframes<Numeric>;
73
+ scaleZ?: Keyframes<Numeric>;
74
+ rotate?: Keyframes<Numeric | Stringish>;
75
+ rotateX?: Keyframes<Numeric | Stringish>;
76
+ rotateY?: Keyframes<Numeric | Stringish>;
77
+ rotateZ?: Keyframes<Numeric | Stringish>;
78
+ skew?: Keyframes<Numeric | Stringish>;
79
+ skewX?: Keyframes<Numeric | Stringish>;
80
+ skewY?: Keyframes<Numeric | Stringish>;
81
+ transformPerspective?: Keyframes<Numeric>;
82
+ transformOrigin?: Stringish;
83
+ opacity?: Keyframes<Numeric>;
84
+ zIndex?: Keyframes<Numeric>;
85
+ transition?: Transition;
86
+ [key: string]: Keyframes<AnyValue> | Transition | undefined;
87
+ };
88
+ /** A single variant — a target shape, or a function producing one from `custom`. */
89
+ export type Variant = Target | ((custom: unknown) => Target);
90
+ /** A named map of variants. */
91
+ export type Variants = Record<string, Variant>;
92
+ /** What can be passed where a variant name is expected (string, array of strings). */
93
+ export type VariantLabels = string | string[];
94
+ /** What can drive an animation: a target object, a variant name, or an array of names. */
95
+ export type AnimateValue = Target | VariantLabels;
96
+ export type ViewportOptions = {
97
+ /** Stop observing after first entry (default false). */
98
+ once?: boolean;
99
+ /** rootMargin string passed to IntersectionObserver. */
100
+ margin?: string;
101
+ /**
102
+ * Threshold(s) at which IntersectionObserver fires its callback.
103
+ *
104
+ * - `"some"` (default) — fires the moment any pixel intersects.
105
+ * - `"all"` — fires only when fully visible.
106
+ * - `number` 0–1 — single threshold; fires at that intersection ratio.
107
+ * - `number[]` — an array of thresholds; fires at each crossing. Use this
108
+ * for continuous `intersectionRatio` updates (e.g., for scroll-linked
109
+ * fades). With a single threshold the observer is silent between
110
+ * crossings, so `entry.intersectionRatio` stays stale.
111
+ */
112
+ amount?: "some" | "all" | number | number[];
113
+ /** Solid-style accessor returning the root element; defaults to viewport. */
114
+ root?: () => Element | null;
115
+ };
116
+ /**
117
+ * Drag bounds (Q8). Three shapes:
118
+ *
119
+ * - **Numeric** (`{ top, left, right, bottom }`): absolute MV-value bounds.
120
+ * Missing keys are unbounded on that side.
121
+ * - **HTMLElement**: container the dragged element must stay inside. Bounds
122
+ * are computed at drag-start from the container's bounding rect.
123
+ * - **`() => HTMLElement | null`**: Solid-style accessor for a reactive
124
+ * container ref. Called at drag-start.
125
+ */
126
+ export type DragConstraints = {
127
+ top?: number;
128
+ left?: number;
129
+ right?: number;
130
+ bottom?: number;
131
+ } | HTMLElement | (() => HTMLElement | null);
132
+ export type DragOptions = {
133
+ drag?: boolean | "x" | "y";
134
+ dragConstraints?: DragConstraints;
135
+ /** Elastic resistance past constraints (0–1, default 0.5). */
136
+ dragElastic?: number;
137
+ /** Carry momentum after release (default true). */
138
+ dragMomentum?: boolean;
139
+ /**
140
+ * Options for the momentum / snap-back animation. Inertia by default
141
+ * (Q15d preset). Shallow-merges over the defaults — user fields override
142
+ * any single setting (bounceStiffness, timeConstant, etc.).
143
+ */
144
+ dragTransition?: Partial<InertiaOptions>;
145
+ /** Snap back to origin on release (default false). */
146
+ dragSnapToOrigin?: boolean;
147
+ /** Imperatively-triggered drag (from createDragControls). */
148
+ dragControls?: DragControls;
149
+ };
150
+ export type MotionCallbacks = {
151
+ onAnimationStart?: () => void;
152
+ onAnimationComplete?: (definition: AnimateValue) => void;
153
+ onAnimationCancel?: () => void;
154
+ onUpdate?: (latest: ResolvedValues) => void;
155
+ onHoverStart?: (e: PointerEvent) => void;
156
+ onHoverEnd?: (e: PointerEvent) => void;
157
+ /** Press began. `info.success` isn't meaningful yet — see {@link onPress} / {@link onPressCancel}. */
158
+ onPressStart?: (e: PointerEvent) => void;
159
+ /** Press completed with the pointer still over the element. */
160
+ onPress?: (e: PointerEvent, info: PressInfo) => void;
161
+ /** Press cancelled — pointer left the element before pointer-up. */
162
+ onPressCancel?: (e: PointerEvent, info: PressInfo) => void;
163
+ onFocus?: (e: FocusEvent) => void;
164
+ onBlur?: (e: FocusEvent) => void;
165
+ onPanStart?: (e: PointerEvent, info: PanInfo) => void;
166
+ onPan?: (e: PointerEvent, info: PanInfo) => void;
167
+ onPanEnd?: (e: PointerEvent, info: PanInfo) => void;
168
+ onViewportEnter?: (entry: IntersectionObserverEntry) => void;
169
+ onViewportLeave?: (entry: IntersectionObserverEntry) => void;
170
+ onDragStart?: (e: PointerEvent, info: PanInfo) => void;
171
+ onDrag?: (e: PointerEvent, info: PanInfo) => void;
172
+ onDragEnd?: (e: PointerEvent, info: PanInfo) => void;
173
+ onDragTransitionEnd?: () => void;
174
+ };
175
+ export type MotionOptions = MotionCallbacks & DragOptions & {
176
+ /** Starting state. `false` means "don't apply an initial style". */
177
+ initial?: AnimateValue | false;
178
+ /** Target the element animates to. */
179
+ animate?: AnimateValue;
180
+ /** Target applied when component unmounts inside <Presence>. */
181
+ exit?: AnimateValue;
182
+ /** Hover gesture target. */
183
+ hover?: AnimateValue;
184
+ /** Press gesture target. */
185
+ press?: AnimateValue;
186
+ /** Focus gesture target. */
187
+ focus?: AnimateValue;
188
+ /** Viewport gesture target. */
189
+ inView?: AnimateValue;
190
+ /** Viewport observer config for the inView gesture. */
191
+ inViewOptions?: ViewportOptions;
192
+ /**
193
+ * Visual-state target applied while a drag gesture is active. Like
194
+ * other gesture targets, this can be a Target object or a variant
195
+ * label. Common idiom: `whileDrag: { scale: 1.05 }` for a lift-while-
196
+ * dragging effect — composes with drag's translation via the shared
197
+ * VisualElement (Q5/C-lean).
198
+ */
199
+ whileDrag?: AnimateValue;
200
+ /**
201
+ * Minimum cumulative pointer movement (in px) before pan/drag start fires.
202
+ * Distinguishes a pan from a click. Q11a default: 3px (matches motion).
203
+ */
204
+ panThreshold?: number;
205
+ /** Default transition applied to every property in animate/gesture targets. */
206
+ transition?: Transition;
207
+ /** Named variant map; resolved by variant name in animate/gesture targets. */
208
+ variants?: Variants;
209
+ /** Value passed to function variants. */
210
+ custom?: unknown;
211
+ };
212
+ /**
213
+ * The element types `useMotion` can attach to. HTMLElement covers the bulk;
214
+ * SVGElement is included so `motion.path`, `motion.circle`, etc. (Phase 4)
215
+ * thread through without a `ref` type-cast. `createMotion` only uses methods
216
+ * available on both (style, addEventListener, getBoundingClientRect) so the
217
+ * union is honored at runtime. Drag stays HTMLElement-only via an
218
+ * `instanceof` check in createMotion's body — motion-dom's VisualElement
219
+ * layer is HTML-specific.
220
+ */
221
+ export type MotionElement = HTMLElement | SVGElement;
222
+ /**
223
+ * Motion's transform-shortcut keys and their value types. Each key may be
224
+ * a value OR a `MotionValue` holding that value.
225
+ *
226
+ * Value units (when expressed as a `number`):
227
+ * - `x` / `y` / `z` / `transformPerspective` → px
228
+ * - `scale*` → dimensionless multiplier
229
+ * - `rotate*` / `skew*` → deg
230
+ *
231
+ * Strings with explicit units pass through verbatim (e.g.
232
+ * `x: "50%"`, `rotate: "0.5turn"`).
233
+ *
234
+ * Variance note: motion's `MotionValue<T>` is invariant in `T` (it has both
235
+ * `get(): T` and `set(v: T)`), which means a user's `MotionValue<number>`
236
+ * cannot widen to `MotionValue<number | string>`. We use `MotionValue<any>`
237
+ * across the shortcut value types so a literal `createMotionValue(1)` is
238
+ * assignable as `x` / `y` / etc. without forcing the caller to type the MV
239
+ * with the full union. Type-safety on the value side is mostly recovered by
240
+ * the runtime: `formatProperty` and `transformFunctionFor` handle the
241
+ * number/string distinction.
242
+ */
243
+ type AnyMotionValue = MotionValue<any>;
244
+ export type MotionTransformShortcuts = {
245
+ x?: number | string | AnyMotionValue;
246
+ y?: number | string | AnyMotionValue;
247
+ z?: number | string | AnyMotionValue;
248
+ scale?: number | string | AnyMotionValue;
249
+ scaleX?: number | string | AnyMotionValue;
250
+ scaleY?: number | string | AnyMotionValue;
251
+ scaleZ?: number | string | AnyMotionValue;
252
+ rotate?: number | string | AnyMotionValue;
253
+ rotateX?: number | string | AnyMotionValue;
254
+ rotateY?: number | string | AnyMotionValue;
255
+ rotateZ?: number | string | AnyMotionValue;
256
+ skew?: number | string | AnyMotionValue;
257
+ skewX?: number | string | AnyMotionValue;
258
+ skewY?: number | string | AnyMotionValue;
259
+ transformPerspective?: number | string | AnyMotionValue;
260
+ };
261
+ /**
262
+ * Widen each value of `T` to also accept any `MotionValue`. Pragmatic shape:
263
+ * motion's `MotionValue<T>` is invariant in T, so a strict per-key
264
+ * `MotionValue<NonNullable<T[K]>>` would reject e.g. `MotionValue<number>` for
265
+ * a `width: string | number` key. `MotionValue<any>` is the necessary
266
+ * escape hatch — the runtime always normalizes via `mv.get()` and
267
+ * `formatProperty`.
268
+ */
269
+ type WithMotionValues<T> = {
270
+ [K in keyof T]?: T[K] | AnyMotionValue;
271
+ };
272
+ /**
273
+ * The `style` prop shape for motion-aware elements: every native CSS
274
+ * property (with values optionally widened to a `MotionValue`), plus motion's
275
+ * transform-shortcut keys (with the same widening).
276
+ *
277
+ * Some CSS individual-transform properties (`scale`, `rotate`) collide with
278
+ * motion shortcut keys of the same name. We strip them from the CSS side
279
+ * before intersecting — motion's semantics win, the legacy CSS individual-
280
+ * transform path is a corner case users almost never reach for.
281
+ *
282
+ * @example
283
+ * const scale = createMotionValue(1)
284
+ * const m = useMotion({})
285
+ * <div {...m({ style: { scale, opacity: 0.5, color: "red" } })} />
286
+ */
287
+ export type MotionStyle = MotionTransformShortcuts & WithMotionValues<Omit<JSX.CSSProperties, keyof MotionTransformShortcuts>>;
288
+ export type ElementProps = Omit<JSX.HTMLAttributes<MotionElement>, "ref" | "style"> & {
289
+ ref?: ((el: MotionElement) => void) | MotionElement | undefined;
290
+ style?: MotionStyle;
291
+ };
292
+ export type MotionMergedProps<P extends ElementProps> = Omit<P, "ref" | "style"> & {
293
+ style: MotionStyle & JSX.CSSProperties;
294
+ ref: (el: MotionElement) => void;
295
+ "data-motion-hydrated"?: "";
296
+ };
297
+ export type MotionGetProps = <P extends ElementProps>(userProps?: P) => MotionMergedProps<P>;
298
+ export type UseMotionResult = MotionGetProps & {
299
+ /** Opt-in: wrap descendant motion elements to receive this element's variant context. */
300
+ Provider: Component<{
301
+ children: JSX.Element;
302
+ }>;
303
+ };
304
+ /** Propagates the variant state from a parent motion element to descendants. */
305
+ export type VariantContextValue = {
306
+ variants?: Accessor<Variants | undefined>;
307
+ /** Propagated only when the parent's `initial` is a variant name (not `false` or an explicit Target). */
308
+ initial?: Accessor<VariantLabels | undefined>;
309
+ animate?: Accessor<VariantLabels | undefined>;
310
+ hover?: Accessor<VariantLabels | undefined>;
311
+ press?: Accessor<VariantLabels | undefined>;
312
+ focus?: Accessor<VariantLabels | undefined>;
313
+ inView?: Accessor<VariantLabels | undefined>;
314
+ exit?: Accessor<VariantLabels | undefined>;
315
+ custom?: Accessor<unknown>;
316
+ transition?: Accessor<Transition | undefined>;
317
+ };
318
+ /**
319
+ * Wired with a no-op default; the real implementation is provided by
320
+ * `<Presence>` and `useAnimatePresence()` in Phase 3.
321
+ *
322
+ * Inverted shape vs. motion-react's `AnimatePresenceProps`: the child
323
+ * registers a `runExit` callable that knows how to animate ITSELF out (closes
324
+ * over its own state machine). Presence just coordinates timing — it doesn't
325
+ * resolve targets or merge transitions. See ADR 0003.
326
+ *
327
+ * - `register(el, runExit)` — called from `createMotion` when `opts.exit` is
328
+ * set. `runExit` flips the state machine's `exit` flag and awaits the
329
+ * resulting animate's completion.
330
+ * - `unregister(el)` — Presence (or the hook's `exit()`) prunes after exit.
331
+ * createMotion deliberately omits self-unregister; Solid disposes the
332
+ * child owner well before transition-group's onExit runs.
333
+ * - `beforeUnmount(el)` — Presence (or the hook's `exit()`) dispatches to the
334
+ * registered `runExit`. Returns a resolved promise if no `runExit` is
335
+ * registered, so non-exit children pass through cleanly.
336
+ * - `registerEnter` / `beforeMount` — symmetric to the exit pair. createMotion
337
+ * defers its first-mount animate when it's inside a real Presence (the
338
+ * no-op default has no `registerEnter`) because the element may be off-DOM
339
+ * at the moment the gesture-state-machine first iterates (e.g., the new
340
+ * child during a `mode: "wait"` swap is created BEFORE the old child's
341
+ * exit completes). Running motion's `animate()` on a disconnected element
342
+ * completes off-DOM and the final `commitStyles` silently no-ops, leaving
343
+ * the element painted at its `initial` target when it finally enters the
344
+ * DOM. Presence's `onEnter` (switch) / `onChange.added` (list) calls
345
+ * `beforeMount(el)` once the element is actually connected; the child's
346
+ * registered `runEnter` flips its readiness signal and the state machine
347
+ * dispatches the first animate against a live element.
348
+ * - `initial` — when an enclosing `<Presence initial={false}>` (or the hook
349
+ * with `initial: false`) is active, descendants suppress their first-mount
350
+ * animation. Accessor-shaped so the implementation can flip post-mount.
351
+ */
352
+ export type PresenceContextValue = {
353
+ register: (el: MotionElement, runExit: () => Promise<void>) => void;
354
+ unregister: (el: MotionElement) => void;
355
+ beforeUnmount: (el: MotionElement) => Promise<void>;
356
+ registerEnter?: (el: MotionElement, runEnter: () => void) => void;
357
+ beforeMount?: (el: MotionElement) => void;
358
+ initial?: Accessor<boolean>;
359
+ };
360
+ /** <MotionConfig> provides defaults that flow to every descendant motion element. */
361
+ export type MotionConfigContextValue = {
362
+ /** "always" forces snap; "never" ignores system pref; "user" respects prefers-reduced-motion. */
363
+ reducedMotion: Accessor<"always" | "never" | "user">;
364
+ /** Default transition merged with descendant transitions (descendant wins on conflict). */
365
+ transition: Accessor<Transition | undefined>;
366
+ /** CSP nonce applied to any inline style emissions (advanced). */
367
+ nonce: Accessor<string | undefined>;
368
+ };
369
+ export type MotionConfigProps = {
370
+ reducedMotion?: "always" | "never" | "user";
371
+ transition?: Transition;
372
+ nonce?: string;
373
+ children: JSX.Element;
374
+ };
@@ -0,0 +1,35 @@
1
+ import { MotionOptions, Transition, UseMotionResult } from './types';
2
+ /**
3
+ * Wire motion to an element via a getter function.
4
+ *
5
+ * ```tsx
6
+ * const motion = useMotion({
7
+ * initial: { opacity: 0, y: 20 },
8
+ * animate: { opacity: 1, y: 0 },
9
+ * transition: { duration: 0.6 },
10
+ * })
11
+ *
12
+ * <div {...motion({ class: "card" })}>Hello</div>
13
+ * ```
14
+ *
15
+ * **Reactive form**: pass a function to track signals.
16
+ * ```tsx
17
+ * useMotion(() => ({ animate: { x: x() } }))
18
+ * ```
19
+ *
20
+ * **Variant context propagation**: `useMotion` only *consumes* the parent
21
+ * variant context. To propagate to descendants, wrap them in `motion.Provider`:
22
+ * ```tsx
23
+ * const m = useMotion({ animate: "visible", variants })
24
+ * <div {...m()}>
25
+ * <m.Provider>
26
+ * <ChildMotion />
27
+ * </m.Provider>
28
+ * </div>
29
+ * ```
30
+ *
31
+ * For the common "JSX wrapper does propagation automatically" pattern, use
32
+ * `<motion.div>` (Phase 4).
33
+ */
34
+ export declare function useMotion(opts: MotionOptions | (() => MotionOptions)): UseMotionResult;
35
+ export type { Transition };
@@ -0,0 +1,64 @@
1
+ import { Context } from 'solid-js';
2
+ import { MotionOptions, Target, VariantContextValue, VariantLabels, Variants } from './types';
3
+ /**
4
+ * Solid context propagating variant state from a motion ancestor to its
5
+ * descendants. Only the wrapper components (`<motion.div>`, `motion(...)`)
6
+ * provide a value. Bare `useMotion` consumers can opt in via the `Provider`
7
+ * returned alongside the getter.
8
+ */
9
+ export declare const VariantContext: Context<VariantContextValue>;
10
+ export declare function useVariantContext(): VariantContextValue;
11
+ /**
12
+ * Resolve a variant label (or array of labels) against a variants map and the
13
+ * current `custom` value. Returns a {@link Target} object, or `null` if nothing
14
+ * resolves.
15
+ *
16
+ * Resolution rules locked in Phase 1 Q4:
17
+ *
18
+ * - Child's own `variants` always wins for a given name (sub-1A).
19
+ * - No cascade: if a child has no `variants` of its own, parent's are NOT
20
+ * consulted (sub-1B / Pattern X). Callers pass `variants = undefined` in
21
+ * that case and this returns `null`.
22
+ * - String + array forms both supported; array variants merge in order
23
+ * (later wins on conflicting keys).
24
+ * - Function variants are invoked with `custom`; the value can be any type.
25
+ *
26
+ * @example
27
+ * const variants = { visible: { opacity: 1 }, hidden: { opacity: 0 } }
28
+ * resolveVariant("visible", variants, undefined)
29
+ * // { opacity: 1 }
30
+ *
31
+ * resolveVariant(["visible", "highlighted"], variants, undefined)
32
+ * // merged in order, last variant's keys override
33
+ *
34
+ * resolveVariant("visible", { visible: (i: number) => ({ x: i * 10 }) }, 3)
35
+ * // { x: 30 }
36
+ */
37
+ export declare function resolveVariant(names: VariantLabels | undefined, variants: Variants | undefined, custom: unknown): Target | null;
38
+ /**
39
+ * Determine the effective variant name for a given motion state. If the caller
40
+ * provided an explicit value (string, array, or Target object), that wins.
41
+ * Otherwise the parent context's value (per gesture/state) is used as a fall-
42
+ * back.
43
+ *
44
+ * Returns the explicit value as-is when it's a Target object (used by callers
45
+ * to detect "explicit target — skip variant lookup entirely").
46
+ */
47
+ export declare function effectiveLabels(own: VariantLabels | Target | undefined, parent: VariantLabels | undefined): VariantLabels | Target | undefined;
48
+ /**
49
+ * A motion node is "controlling variants" when any of its variant slots
50
+ * (`initial`, `animate`, `hover`, `press`, `focus`, `inView`, `exit`) carries
51
+ * a variant *label* (string or array of strings).
52
+ *
53
+ * Mirrors motion-dom's same-named check. A controlling node opts OUT of
54
+ * inheriting its parent's variant cascade — it provides its own. Descendants
55
+ * with no controlling props of their own DO inherit from the nearest
56
+ * controlling ancestor.
57
+ *
58
+ * Behavior is binary (any single controlling-slot label flips the node into
59
+ * controlling mode); not per-slot.
60
+ *
61
+ * Object-shaped values (`animate: \{ x: 100 \}`) do NOT make a node
62
+ * controlling — they're treated as targets, not as variant references.
63
+ */
64
+ export declare function isControllingVariants(opts: MotionOptions): boolean;
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "solidjs-motion",
3
+ "version": "0.1.0",
4
+ "description": "An animation library for SolidJS — port of motion/react patterns built on the framework-agnostic motion package",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "solid": "./src/index.ts",
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "src",
19
+ "README.md",
20
+ "CHANGELOG.md",
21
+ "LICENSE"
22
+ ],
23
+ "sideEffects": false,
24
+ "scripts": {
25
+ "build": "vite build",
26
+ "build:watch": "vite build --watch",
27
+ "dev": "vite build --watch",
28
+ "test": "vitest run && vitest run --config vitest.ssr.config.ts",
29
+ "test:watch": "vitest",
30
+ "test:ssr": "vitest run --config vitest.ssr.config.ts",
31
+ "bench": "vitest bench --run --config vitest.bench.config.ts",
32
+ "typecheck": "tsc --noEmit",
33
+ "clean": "rm -rf dist node_modules",
34
+ "publish:npm": "bun run build && npm publish --access public",
35
+ "publish:jsr": "bunx jsr publish"
36
+ },
37
+ "peerDependencies": {
38
+ "solid-js": "^1.9.0",
39
+ "motion": "^12.0.0",
40
+ "motion-dom": "^12.0.0"
41
+ },
42
+ "dependencies": {
43
+ "@solid-primitives/refs": "^1.1.3",
44
+ "@solid-primitives/transition-group": "^1.1.2"
45
+ },
46
+ "devDependencies": {
47
+ "@solidjs/testing-library": "^0.8.10",
48
+ "@testing-library/jest-dom": "^6.9.1",
49
+ "jsdom": "^29.1.1",
50
+ "motion": "^12.38.0",
51
+ "motion-dom": "^12.38.0",
52
+ "solid-js": "^1.9.13",
53
+ "vite": "^8.0.13",
54
+ "vite-plugin-dts": "^5.0.0",
55
+ "vite-plugin-solid": "^2.11.12",
56
+ "vitest": "^4.1.6"
57
+ },
58
+ "keywords": [
59
+ "solid",
60
+ "solidjs",
61
+ "animation",
62
+ "motion",
63
+ "framer-motion",
64
+ "spring",
65
+ "transition",
66
+ "gesture"
67
+ ],
68
+ "repository": {
69
+ "type": "git",
70
+ "url": "git+https://github.com/solidjs-motion/motion.git",
71
+ "directory": "packages/motion"
72
+ },
73
+ "bugs": {
74
+ "url": "https://github.com/solidjs-motion/motion/issues"
75
+ },
76
+ "homepage": "https://github.com/solidjs-motion/motion#readme",
77
+ "license": "MIT"
78
+ }
@@ -0,0 +1,52 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Default value table for transform-class and a few well-known CSS properties.
3
+ // Used by the gesture state machine's "removed keys" fallback (Q7): when a
4
+ // higher-priority gesture deactivates and no lower-priority state defines a
5
+ // key, we animate the key back to:
6
+ // 1. the user's `initial` target (captured at mount) — handled at call site
7
+ // 2. otherwise the value from this table
8
+ // 3. otherwise `null` (motion's animate() reads from computed style)
9
+ //
10
+ // The values here mirror motion-dom's conventions (scale → 1, x/y/z → 0,
11
+ // rotate/skew → 0, opacity → 1). We don't import motion-dom's defaultValueTypes
12
+ // directly because (a) it's a value-type system, not a defaults table, and
13
+ // (b) keeping a stable local source insulates us from motion-dom internal churn.
14
+ // ---------------------------------------------------------------------------
15
+
16
+ const TRANSFORM_DEFAULTS: Readonly<Record<string, number>> = {
17
+ // Translate
18
+ x: 0,
19
+ y: 0,
20
+ z: 0,
21
+ translateX: 0,
22
+ translateY: 0,
23
+ translateZ: 0,
24
+ // Scale (multiplicative identity)
25
+ scale: 1,
26
+ scaleX: 1,
27
+ scaleY: 1,
28
+ scaleZ: 1,
29
+ // Rotate
30
+ rotate: 0,
31
+ rotateX: 0,
32
+ rotateY: 0,
33
+ rotateZ: 0,
34
+ // Skew
35
+ skew: 0,
36
+ skewX: 0,
37
+ skewY: 0,
38
+ // Perspective
39
+ perspective: 0,
40
+ transformPerspective: 0,
41
+ // Opacity (the one non-transform key with a strong default)
42
+ opacity: 1,
43
+ }
44
+
45
+ /**
46
+ * Look up the canonical fallback value for a property key. Returns the table
47
+ * value if known, else `null` — which motion's `animate()` interprets as
48
+ * "read from computed style at animation start."
49
+ */
50
+ export function getMotionDefault(key: string): number | null {
51
+ return TRANSFORM_DEFAULTS[key] ?? null
52
+ }
package/src/index.ts ADDED
@@ -0,0 +1,60 @@
1
+ // Public API surface.
2
+
3
+ // Re-exports from the upstream motion engine.
4
+ export { animate, inView, isMotionValue, motionValue, scroll, spring } from "motion"
5
+ // Context layer.
6
+ export { MotionConfig, MotionConfigContext, useMotionConfig } from "./motion-config"
7
+ // motion proxy (Phase 4) — indexable surface yielding cached, motion-aware
8
+ // tag-components per HTML/SVG tag. `motion.create` (the HOC entry point)
9
+ // lands in the follow-up commit.
10
+ export { MOTION_OPT_KEYS, type Motion, motion } from "./motion-proxy"
11
+ // Presence — exit-animation coordinator + imperative hook (Phase 3).
12
+ export {
13
+ Presence,
14
+ type PresenceProps,
15
+ type UseAnimatePresenceOptions,
16
+ type UseAnimatePresenceResult,
17
+ useAnimatePresence,
18
+ } from "./presence"
19
+ export { PresenceContext, usePresenceContext } from "./presence-context"
20
+ // Imperative motion primitive (advanced — most users want useMotion instead).
21
+ export { createDragControls } from "./primitives/createDragControls"
22
+ export {
23
+ type CreateInViewOptions,
24
+ type CreateInViewResult,
25
+ createInView,
26
+ } from "./primitives/createInView"
27
+ export { createMotion } from "./primitives/createMotion"
28
+ export {
29
+ type CreatePanOptions,
30
+ type CreatePanResult,
31
+ createPan,
32
+ type PanAxisPair,
33
+ } from "./primitives/createPan"
34
+ export {
35
+ type CreateScrollOptions,
36
+ type CreateScrollResult,
37
+ createScroll,
38
+ } from "./primitives/createScroll"
39
+ // MotionValue family — callable-hybrid primitives. Every value returned here
40
+ // is both a Solid Accessor (call it: `mv()`) AND a motion.MotionValue
41
+ // (methods: `.get`, `.set`, `.jump`, `.on`, etc.).
42
+ export {
43
+ createMotionValue,
44
+ createMotionValueEvent,
45
+ createSpring,
46
+ createTemplate,
47
+ createTime,
48
+ createTransform,
49
+ createVelocity,
50
+ toSignal,
51
+ } from "./primitives/motion-value"
52
+ // Reduced motion.
53
+ export { createReducedMotion, shouldReduceMotion } from "./reduced-motion"
54
+ // Style helper (also useful for users with custom directives or imperative DOM).
55
+ export { targetToStyle } from "./style"
56
+ export type * from "./types"
57
+ // Canonical hook.
58
+ export { useMotion } from "./use-motion"
59
+ // Variant resolution.
60
+ export { effectiveLabels, resolveVariant, useVariantContext, VariantContext } from "./variants"