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,108 @@
1
+ import { MotionValue } from 'motion';
2
+ import { Accessor } from 'solid-js';
3
+ import { SetStoreFunction, Store } from 'solid-js/store';
4
+ import { MotionConfigContextValue, MotionElement, MotionOptions, Target, VariantContextValue } from '../types';
5
+ /** State names, ordered low → high priority. Matches motion-dom's variantPriorityOrder. */
6
+ declare const STATE_NAMES: readonly ["animate", "whileInView", "whileHover", "whilePress", "whileFocus", "whileDrag", "exit"];
7
+ export type GestureStateName = (typeof STATE_NAMES)[number];
8
+ export type SetActive = (state: GestureStateName, isActive: boolean) => void;
9
+ /** The reactive store of active gesture flags, lifted to the caller for sharing. */
10
+ export type ActiveStore = Store<Record<GestureStateName, boolean>>;
11
+ export type SetActiveStore = SetStoreFunction<Record<GestureStateName, boolean>>;
12
+ export type ActiveStoreTuple = [ActiveStore, SetActiveStore];
13
+ export type CreateGestureStateMachineDeps = {
14
+ el: MotionElement;
15
+ getOpts: () => MotionOptions;
16
+ parentVariantCtx: VariantContextValue;
17
+ motionConfig: MotionConfigContextValue;
18
+ systemReducedMotion: Accessor<boolean>;
19
+ /** Captured at construction. Used as the first stop in the removed-key fallback chain (Q7). */
20
+ initialTarget: Target | null;
21
+ /**
22
+ * Optional external active store (Q4 — useMotion lifts this up so its
23
+ * `myVariantCtx` can read the same flags it propagates to descendants).
24
+ * When omitted, the state machine creates its own internal store —
25
+ * backward-compatible for `createMotion` direct users.
26
+ */
27
+ externalActiveStore?: ActiveStoreTuple;
28
+ /**
29
+ * Phase 3 — when an enclosing `<Presence initial={false}>` is active, this
30
+ * is passed through to suppress the first-mount animate (mirrors the
31
+ * existing `initial: false` user-opt-out, but driven from above by
32
+ * Presence instead of by the user's own options).
33
+ */
34
+ suppressFirstMount?: boolean;
35
+ /**
36
+ * Phase 3 — readiness gate for the first-mount animate when this motion
37
+ * element is wrapped in a real `<Presence>`. The state machine reads this
38
+ * on each iteration; when it's `false` AND we haven't run yet, the diff
39
+ * effect short-circuits (no animate dispatch, no MV subscriptions sealed).
40
+ * Presence flips it to `true` from its `onEnter` / `onChange.added`
41
+ * callback, at which point the effect re-runs and treats THAT iteration
42
+ * as the first.
43
+ *
44
+ * Outside a Presence (no-op default context), createMotion leaves this
45
+ * `undefined` — the state machine treats absence as `ready=true` and the
46
+ * existing eager-first-iteration behavior is unchanged.
47
+ *
48
+ * Rationale: real `motion.animate()` is a Web Animations API call that
49
+ * runs even on a disconnected element, but its terminal `commitStyles`
50
+ * silently no-ops when the element is off-DOM. For a `mode: "wait"` swap
51
+ * the new child is created BEFORE the old child's exit completes, so
52
+ * dispatching the first animate eagerly would let it complete in the
53
+ * detached state and the element would paint at its `initial` target
54
+ * when it finally enters the DOM. Deferring until `onEnter` (when
55
+ * transition-group has synchronously inserted the element via
56
+ * `setReturned`) closes that gap.
57
+ */
58
+ enterReady?: Accessor<boolean>;
59
+ /**
60
+ * MV-in-style Stage 3 bridge. When provided, the diff effect calls this
61
+ * per animate-target key. A returned `MotionValue` routes the animation
62
+ * through that MV (`animate(mv, value, opts)`) — its change-subscription
63
+ * (in createMotion) composes `el.style` from the registry. `undefined`
64
+ * routes the key down the existing `animate(el, target, opts)` WAA path.
65
+ *
66
+ * createMotion only activates this when at least one external MV is
67
+ * registered (i.e., the user supplied `style: { scale: mv }`-shaped
68
+ * options). Inactive in the common case → 293 baseline tests stay on
69
+ * the original code path, their animateSpy assertions unaffected.
70
+ */
71
+ getValueForAnimate?: (key: string, fallback: unknown) => MotionValue<unknown> | undefined;
72
+ };
73
+ export type GestureStateMachine = {
74
+ /** Imperatively toggle a gesture state. Triggers re-resolution + animate(). */
75
+ setActive: SetActive;
76
+ /**
77
+ * Resolves when the next animate dispatched while `exit` is the highest-
78
+ * priority active driver state completes. If no exit animation is in
79
+ * flight AND no exit target is defined, resolves immediately.
80
+ *
81
+ * Used by `createMotion`'s presence registration: the registered
82
+ * `runExit` callable does `setActive("exit", true)` then awaits this.
83
+ * When the exit animation settles, `<Presence>` (or the hook) gets a
84
+ * resolved Promise and proceeds with DOM removal.
85
+ *
86
+ * Multiple concurrent waiters are supported — they all resolve from the
87
+ * same animation's completion.
88
+ */
89
+ onceExitComplete: () => Promise<void>;
90
+ };
91
+ /**
92
+ * Construct the per-element gesture state machine.
93
+ *
94
+ * Wired primitives:
95
+ * - `createStore` for the seven active flags — Solid tracks per-path, so
96
+ * toggling `whileHover` doesn't dirty memos reading `whilePress`.
97
+ * - `createMemo` for `stateTargets` — cached, re-runs only when opts/parent
98
+ * context change.
99
+ * - `createMemo` for `winners` — same caching, re-runs when `active` flags or
100
+ * `stateTargets` change.
101
+ * - `createEffect` for the diff-and-animate loop — fires on `winners` change;
102
+ * compares against `lastApplied` to compute changed/removed keys.
103
+ * - `onCleanup` inside the effect for per-iteration MV subscriptions — scoped
104
+ * to each effect run (fires on re-run AND owner disposal). Same iteration-
105
+ * scoped cleanup pattern Phase 1 established.
106
+ */
107
+ export declare function createGestureStateMachine(deps: CreateGestureStateMachineDeps): GestureStateMachine;
108
+ export {};
@@ -0,0 +1,111 @@
1
+ import { MotionValue, transform as motionTransform, SpringOptions } from 'motion';
2
+ import { Accessor } from 'solid-js';
3
+ import { MotionValueAccessor } from '../types';
4
+ type MotionValueEvent = "change" | "animationStart" | "animationComplete" | "animationCancel";
5
+ /**
6
+ * Create a {@link MotionValueAccessor} bound to the current reactive scope.
7
+ *
8
+ * The returned value has two access patterns:
9
+ *
10
+ * - `mv()` — invoke as a Solid Accessor. Tracks in JSX, `createEffect`,
11
+ * `createMemo`, etc.
12
+ * - `mv.get()` / `mv.set(v)` / `mv.jump(v)` / `mv.on(...)` — the full upstream
13
+ * {@link MotionValue} surface. Matches motion/react idioms.
14
+ *
15
+ * The same value can be passed as a target in
16
+ * `useMotion({ animate: { x: mv } })` (motion engine sees `.getVelocity` via
17
+ * the Proxy and treats it as a motion value) or directly as the target of
18
+ * `animate(mv, 100)`.
19
+ *
20
+ * Auto-destroyed via `onCleanup` when the owner is disposed.
21
+ *
22
+ * @example
23
+ * const x = createMotionValue(0)
24
+ * x.set(100)
25
+ * animate(x, 200, { duration: 0.5 })
26
+ * <p>{x()}</p> // reactive read in JSX
27
+ */
28
+ export declare function createMotionValue<T>(initial: T): MotionValueAccessor<T>;
29
+ /**
30
+ * Bridge a raw {@link MotionValue} (from motion's `motionValue()` factory or
31
+ * any other motion API that doesn't return our hybrid) to a Solid
32
+ * {@link Accessor}. Seeds with the current value and updates on every
33
+ * `change` event.
34
+ *
35
+ * **You usually don't need this.** Values returned by `createMotionValue`,
36
+ * `createTransform`, `createSpring`, `createTime`, `createVelocity`, and
37
+ * `createTemplate` are already callable — you can do `mv()` directly. Reach
38
+ * for `toSignal` only when you receive a raw MotionValue from an external API.
39
+ *
40
+ * @example
41
+ * import { motionValue } from "motion"
42
+ * const rawMv = motionValue(0)
43
+ * const xSignal = toSignal(rawMv)
44
+ */
45
+ export declare function toSignal<T>(mv: MotionValue<T>): Accessor<T>;
46
+ /**
47
+ * Subscribe to a {@link MotionValue} event with automatic cleanup. Convenience
48
+ * wrapper around `mv.on(event, cb)` for parity with motion/react's
49
+ * `useMotionValueEvent`. For per-change reactivity, prefer
50
+ * `createComputed(() => fn(mv()))` since hybrids are directly callable.
51
+ *
52
+ * @example
53
+ * const x = createMotionValue(0)
54
+ * createMotionValueEvent(x, "animationComplete", () => console.log("done"))
55
+ */
56
+ export declare function createMotionValueEvent<T>(mv: MotionValue<T>, event: MotionValueEvent, callback: (latest: T) => void): void;
57
+ type TransformOptions = NonNullable<Parameters<typeof motionTransform>[2]>;
58
+ /**
59
+ * Create a {@link MotionValueAccessor} that maps an input through a range/
60
+ * output pair. Mirrors motion/react's `useTransform`. The input can be a
61
+ * MotionValue, our hybrid, or any Solid Accessor; the output composes with
62
+ * `animate()`, `useMotion`'s targets, and JSX reactivity.
63
+ *
64
+ * @example
65
+ * const { scrollY } = createScroll()
66
+ * const opacity = createTransform(scrollY, [0, 200], [1, 0])
67
+ * <div style={{ opacity: opacity() }}>...</div>
68
+ */
69
+ export declare function createTransform<I extends number, O>(input: MotionValue<I> | Accessor<I>, inputRange: I[], outputRange: O[], options?: TransformOptions): MotionValueAccessor<O>;
70
+ /**
71
+ * Spring-smoothed mirror of a numeric input. Returns a
72
+ * {@link MotionValueAccessor} that tracks the source with physics-based easing.
73
+ *
74
+ * @example
75
+ * const x = createMotionValue(0)
76
+ * const smoothX = createSpring(x, { stiffness: 100, damping: 20 })
77
+ */
78
+ export declare function createSpring(source: MotionValue<number> | Accessor<number>, options?: SpringOptions): MotionValueAccessor<number>;
79
+ /**
80
+ * {@link MotionValueAccessor} that advances every animation frame, holding
81
+ * the milliseconds elapsed since this primitive was called. Driver for
82
+ * time-based animations and {@link createTransform}-derived values.
83
+ *
84
+ * @example
85
+ * const t = createTime()
86
+ * const wobble = createTransform(t, [0, 1000, 2000], [0, 10, 0])
87
+ */
88
+ export declare function createTime(): MotionValueAccessor<number>;
89
+ /**
90
+ * {@link MotionValueAccessor} reporting the velocity of a source motion value.
91
+ * Updated whenever the source changes.
92
+ *
93
+ * @example
94
+ * const x = createMotionValue(0)
95
+ * const xVelocity = createVelocity(x)
96
+ */
97
+ export declare function createVelocity(source: MotionValue<number>): MotionValueAccessor<number>;
98
+ type TemplateInput = MotionValue<any> | Accessor<unknown> | string | number;
99
+ /**
100
+ * Tagged template producing a {@link MotionValueAccessor}\<string\>.
101
+ * Interpolated {@link MotionValue}s, hybrids, and Solid Accessors recompute
102
+ * the output string on change; primitives and static strings are baked in.
103
+ *
104
+ * @example
105
+ * const x = createMotionValue(0)
106
+ * const y = createMotionValue(0)
107
+ * const transform = createTemplate`translate(${x}px, ${y}px) scale(1.1)`
108
+ * <div style={{ transform: transform() }} />
109
+ */
110
+ export declare function createTemplate(strings: TemplateStringsArray, ...values: TemplateInput[]): MotionValueAccessor<string>;
111
+ export {};
@@ -0,0 +1,34 @@
1
+ import { MotionValue } from 'motion';
2
+ export type ValueRegistry = {
3
+ /** Returns the MV registered for `key`, or `undefined` if none. */
4
+ get(key: string): MotionValue<unknown> | undefined;
5
+ /** Has any MV been registered for `key`? */
6
+ has(key: string): boolean;
7
+ /**
8
+ * Number of entries currently registered. Used by `createMotion` to decide
9
+ * whether the per-element writer can take a specialized single-key path
10
+ * (size === 1) or needs the general-purpose `applyStaticStyle` walk.
11
+ */
12
+ readonly size: number;
13
+ /**
14
+ * Register a user-provided MV. The registry will NOT dispose it on
15
+ * teardown. If a transient MV exists for the key, it is replaced (the
16
+ * external MV becomes the new source of truth).
17
+ */
18
+ setExternal(key: string, mv: MotionValue<unknown>): void;
19
+ /**
20
+ * Get the MV for `key`, creating a transient one initialized to
21
+ * `fallback` if absent. Transient MVs are disposed on `dispose()`.
22
+ */
23
+ getOrCreateTransient(key: string, fallback: unknown): MotionValue<unknown>;
24
+ /** Iterate every (key, MV) pair currently registered. */
25
+ entries(): IterableIterator<[string, MotionValue<unknown>]>;
26
+ /**
27
+ * Drop registry-owned (transient) MVs. External MVs are untouched.
28
+ * Subscription cleanups are owned by whoever called `mv.on(...)`; they
29
+ * tie to the surrounding Solid owner via `onCleanup` in Stage 2+ so we
30
+ * don't unsubscribe imperatively here.
31
+ */
32
+ dispose(): void;
33
+ };
34
+ export declare function createValueRegistry(): ValueRegistry;
@@ -0,0 +1,29 @@
1
+ import { Accessor } from 'solid-js';
2
+ /**
3
+ * A reactive `Accessor<boolean>` tracking the user's
4
+ * `prefers-reduced-motion: reduce` media query.
5
+ *
6
+ * Returns `false` server-side (no `window.matchMedia`). On the client, it seeds
7
+ * with the current match state and updates as the system preference toggles.
8
+ * The matchMedia listener is removed automatically on owner disposal via
9
+ * `from`'s teardown callback.
10
+ *
11
+ * @example
12
+ * const reduced = createReducedMotion()
13
+ * createEffect(() => {
14
+ * if (reduced()) console.log("user prefers reduced motion")
15
+ * })
16
+ */
17
+ export declare function createReducedMotion(): Accessor<boolean>;
18
+ /**
19
+ * Compute the effective reduced-motion state by combining a {@link MotionConfig}
20
+ * `reducedMotion` setting with the system preference.
21
+ *
22
+ * - `"always"` — forced reduced, regardless of system pref
23
+ * - `"never"` — never reduced, regardless of system pref
24
+ * - `"user"` — respect system pref
25
+ *
26
+ * @example
27
+ * const reduced = shouldReduceMotion("user", createReducedMotion()())
28
+ */
29
+ export declare function shouldReduceMotion(configValue: "always" | "never" | "user", systemReduced: boolean): boolean;
@@ -0,0 +1,79 @@
1
+ import { JSX } from 'solid-js';
2
+ import { Target } from './types';
3
+ /**
4
+ * Set of CSS shortcut keys motion treats as transform components. Re-used by
5
+ * `createMotion`'s Stage 3 animate bridge to decide whether an animate-target
6
+ * key should be routed through the value registry (composed via the writer's
7
+ * `el.style.transform =`) or sent down the existing WAA path.
8
+ */
9
+ export declare const TRANSFORM_KEYS: Set<string>;
10
+ type Leaf = string | number;
11
+ /**
12
+ * Reduce a target-value (which may be raw, a MotionValue, an Accessor, or a
13
+ * keyframe array) to a concrete leaf value the writer can apply to the DOM
14
+ * or use to initialize a transient MotionValue. The cascade follows motion's
15
+ * own semantics:
16
+ *
17
+ * - `null` / `undefined` → `undefined` (caller drops the key)
18
+ * - keyframe array → first frame (consistent with motion-vanilla's
19
+ * initial-style snapshot)
20
+ * - `MotionValue` → its current `.get()`
21
+ * - `Accessor` (a bare zero-arg function) → its invocation result
22
+ * - primitive (string / number) → returned as-is
23
+ *
24
+ * Exported for the MV-in-style Stage 4 work: createMotion uses it to
25
+ * snapshot initial-target entries when registering them into the value
26
+ * registry as transient MVs.
27
+ */
28
+ export declare function snapshotValue(value: unknown): Leaf | undefined;
29
+ /**
30
+ * Per-key transform formatter functions. Pre-built once at module load and
31
+ * shared across all elements. Used by `createMotion`'s specialized writer
32
+ * to avoid evaluating the transform-key switch on every single-key write —
33
+ * at Sierpinski-scale fan-out (thousands of writes per frame) the switch's
34
+ * 15 case-comparisons add up to non-trivial CPU.
35
+ *
36
+ * Pre-pick the formatter with `pickTransformFormatter(key)` ONCE at writer-
37
+ * compile time, then the per-call cost in the hot path is just `formatter(v)`.
38
+ */
39
+ type TransformFormatter = (value: Leaf) => string;
40
+ /**
41
+ * Look up the formatter for a transform-shortcut key. Returns `undefined`
42
+ * for non-transform keys; callers should check `TRANSFORM_KEYS.has(key)`
43
+ * before assuming a formatter exists.
44
+ */
45
+ export declare function pickTransformFormatter(key: string): TransformFormatter | undefined;
46
+ /**
47
+ * Format a motion transform-shortcut key + value as the corresponding CSS
48
+ * transform function string (e.g. `transformFunctionFor("scale", 1.05)`
49
+ * → `"scale(1.05)"`). One-shot variant — for hot paths, use
50
+ * `pickTransformFormatter(key)` once at compile time and reuse.
51
+ */
52
+ export declare function transformFunctionFor(key: string, value: Leaf): string;
53
+ /**
54
+ * Format a non-transform CSS property's value (e.g. `formatProperty("width", 100)`
55
+ * → `"100px"`, `formatProperty("opacity", 0.5)` → `0.5`). Applies motion's
56
+ * default-unit table (PX for dimensional CSS, dimensionless otherwise);
57
+ * leaves CSS variables alone. Exported for `createMotion`'s writer fast path.
58
+ */
59
+ export declare function formatProperty(key: string, value: Leaf): string | number;
60
+ /**
61
+ * Convert a {@link Target} to a Solid {@link JSX.CSSProperties} object.
62
+ *
63
+ * - Composes transform shorthand (`x`, `y`, `scale`, `rotate`, etc.) into a
64
+ * single `transform: "..."` string in motion's canonical order.
65
+ * - Applies the default-unit table (px for dimensional CSS, deg for rotate/
66
+ * skew, dimensionless for scale/opacity/etc.).
67
+ * - For keyframe arrays, uses the first frame; a leading `null`/`undefined`
68
+ * keyframe omits the property entirely.
69
+ * - MotionValues and Solid Accessors are snapshotted at call time. Callers
70
+ * wrap in `untrack` if they don't want the read to subscribe.
71
+ * - Skips the `transition` key (animation config, not style).
72
+ * - CSS variables (`--foo`) emit raw values, no unit guess.
73
+ *
74
+ * @example
75
+ * targetToStyle({ x: 100, scale: 0.9, opacity: 0.5 })
76
+ * // { transform: "translateX(100px) scale(0.9)", opacity: 0.5 }
77
+ */
78
+ export declare function targetToStyle(target: Target): JSX.CSSProperties;
79
+ export {};