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.
- package/CHANGELOG.md +117 -0
- package/LICENSE +21 -0
- package/README.md +140 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2627 -0
- package/dist/index.js.map +1 -0
- package/dist/src/default-values.d.ts +6 -0
- package/dist/src/index.d.ts +16 -0
- package/dist/src/motion-config.d.ts +14 -0
- package/dist/src/motion-proxy.d.ts +103 -0
- package/dist/src/presence-context.d.ts +4 -0
- package/dist/src/presence.d.ts +95 -0
- package/dist/src/primitives/createDrag.d.ts +16 -0
- package/dist/src/primitives/createDragControls.d.ts +30 -0
- package/dist/src/primitives/createGestures.d.ts +8 -0
- package/dist/src/primitives/createInView.d.ts +51 -0
- package/dist/src/primitives/createMotion.d.ts +82 -0
- package/dist/src/primitives/createPan.d.ts +83 -0
- package/dist/src/primitives/createScroll.d.ts +40 -0
- package/dist/src/primitives/gesture-state.d.ts +108 -0
- package/dist/src/primitives/motion-value.d.ts +111 -0
- package/dist/src/primitives/value-registry.d.ts +34 -0
- package/dist/src/reduced-motion.d.ts +29 -0
- package/dist/src/style.d.ts +79 -0
- package/dist/src/types.d.ts +374 -0
- package/dist/src/use-motion.d.ts +35 -0
- package/dist/src/variants.d.ts +64 -0
- package/package.json +78 -0
- package/src/default-values.ts +52 -0
- package/src/index.ts +60 -0
- package/src/motion-config.tsx +37 -0
- package/src/motion-proxy.tsx +377 -0
- package/src/presence-context.ts +32 -0
- package/src/presence.tsx +466 -0
- package/src/primitives/createDrag.ts +670 -0
- package/src/primitives/createDragControls.ts +101 -0
- package/src/primitives/createGestures.ts +145 -0
- package/src/primitives/createInView.ts +124 -0
- package/src/primitives/createMotion.ts +638 -0
- package/src/primitives/createPan.ts +338 -0
- package/src/primitives/createScroll.ts +101 -0
- package/src/primitives/gesture-state.ts +772 -0
- package/src/primitives/motion-value.ts +328 -0
- package/src/primitives/value-registry.ts +114 -0
- package/src/reduced-motion.ts +51 -0
- package/src/style.ts +266 -0
- package/src/types.ts +538 -0
- package/src/use-motion.tsx +412 -0
- package/src/variants.ts +134 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type Context, createContext, createMemo, type JSX, useContext } from "solid-js"
|
|
2
|
+
import type { MotionConfigContextValue, MotionConfigProps } from "./types"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Default context — no reduced motion override, no inherited transition, no
|
|
6
|
+
* CSP nonce. Descendants without a `<MotionConfig>` ancestor get this.
|
|
7
|
+
*/
|
|
8
|
+
const defaultMotionConfig: MotionConfigContextValue = {
|
|
9
|
+
reducedMotion: () => "never",
|
|
10
|
+
transition: () => undefined,
|
|
11
|
+
nonce: () => undefined,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const MotionConfigContext: Context<MotionConfigContextValue> =
|
|
15
|
+
createContext<MotionConfigContextValue>(defaultMotionConfig)
|
|
16
|
+
|
|
17
|
+
export function useMotionConfig(): MotionConfigContextValue {
|
|
18
|
+
return useContext(MotionConfigContext)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Provider that flows reduced-motion mode, default transition, and CSP nonce
|
|
23
|
+
* to every descendant motion element.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* <MotionConfig reducedMotion="user" transition={{ duration: 0.4 }}>
|
|
27
|
+
* <App />
|
|
28
|
+
* </MotionConfig>
|
|
29
|
+
*/
|
|
30
|
+
export function MotionConfig(props: MotionConfigProps): JSX.Element {
|
|
31
|
+
const value: MotionConfigContextValue = {
|
|
32
|
+
reducedMotion: createMemo(() => props.reducedMotion ?? "never"),
|
|
33
|
+
transition: createMemo(() => props.transition),
|
|
34
|
+
nonce: createMemo(() => props.nonce),
|
|
35
|
+
}
|
|
36
|
+
return <MotionConfigContext.Provider value={value}>{props.children}</MotionConfigContext.Provider>
|
|
37
|
+
}
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import { mergeRefs } from "@solid-primitives/refs"
|
|
2
|
+
import { type Component, type JSX, mergeProps, onMount, splitProps } from "solid-js"
|
|
3
|
+
import { Dynamic } from "solid-js/web"
|
|
4
|
+
import type { ElementProps, MotionElement, MotionOptions, MotionStyle } from "./types"
|
|
5
|
+
import { useMotion } from "./use-motion"
|
|
6
|
+
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// motion proxy — Phase 4.
|
|
9
|
+
//
|
|
10
|
+
// `motion` is a Proxy whose property accesses return cached, memoized
|
|
11
|
+
// tag-components: `motion.div`, `motion.svg`, `motion.path`, etc. Each
|
|
12
|
+
// tag-component:
|
|
13
|
+
//
|
|
14
|
+
// 1. Splits incoming props into motion options (fed to useMotion) and
|
|
15
|
+
// element attributes (forwarded to the underlying element reactively).
|
|
16
|
+
// 2. Wires useMotion in its function form so reactive options propagate
|
|
17
|
+
// through to the gesture state machine.
|
|
18
|
+
// 3. Wraps the rendered element in `m.Provider` UNCONDITIONALLY so a
|
|
19
|
+
// variant cascade reaches every motion descendant — the canonical
|
|
20
|
+
// motion-react ergonomic (B1 in ADR 0004).
|
|
21
|
+
// 4. Renders the actual element through <Dynamic>, which transparently
|
|
22
|
+
// handles SVG-vs-HTML namespace resolution.
|
|
23
|
+
//
|
|
24
|
+
// The HOC entry point (`motion.create`) lands in the follow-up commit. This
|
|
25
|
+
// commit ships only the indexable surface for tag-components.
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The exhaustive list of keys that `motion.X` (and `motion.create`,
|
|
30
|
+
* landing next) route to `useMotion` rather than the underlying element.
|
|
31
|
+
* Anything not in this array falls through to `rest` and is spread onto
|
|
32
|
+
* the DOM element via Solid's reactive spread — keeping things like
|
|
33
|
+
* `class`, `onClick`, dynamic style, etc. reactive end-to-end.
|
|
34
|
+
*
|
|
35
|
+
* The `satisfies` clause ensures every entry IS a valid `MotionOptions`
|
|
36
|
+
* key (typos fail to compile). The `_ensureExhaustive` constant below
|
|
37
|
+
* asserts the converse — every `MotionOptions` key appears in this
|
|
38
|
+
* array — so adding a new option to types.ts without registering here
|
|
39
|
+
* fails to compile with the missing key surfaced in the error.
|
|
40
|
+
*/
|
|
41
|
+
/**
|
|
42
|
+
* Union of every `MotionOptions` key the proxy splits off from element
|
|
43
|
+
* attributes. Hardcoded as a literal union — NOT derived from
|
|
44
|
+
* `typeof MOTION_OPT_KEYS[number]` — so JSR's "slow types" rule can
|
|
45
|
+
* resolve the public type without inferring it from a const.
|
|
46
|
+
*
|
|
47
|
+
* Two compile-time exhaustiveness directions guarantee consistency
|
|
48
|
+
* between this type and the runtime `MOTION_OPT_KEYS` array:
|
|
49
|
+
*
|
|
50
|
+
* 1. `_MissingMotionOptKeys` (below) verifies the union covers every
|
|
51
|
+
* key in `keyof MotionOptions`.
|
|
52
|
+
* 2. The `satisfies` clause on `MOTION_OPT_KEYS` verifies every array
|
|
53
|
+
* entry IS a `MotionOptKey` (typo-proof at the runtime layer).
|
|
54
|
+
*
|
|
55
|
+
* If `MotionOptions` grows a new key, both `_MissingMotionOptKeys`
|
|
56
|
+
* AND `splitProps` at runtime break — the type check surfaces the
|
|
57
|
+
* specific missing key by name.
|
|
58
|
+
*/
|
|
59
|
+
export type MotionOptKey =
|
|
60
|
+
// Variant slots
|
|
61
|
+
| "initial"
|
|
62
|
+
| "animate"
|
|
63
|
+
| "exit"
|
|
64
|
+
// Gesture targets
|
|
65
|
+
| "hover"
|
|
66
|
+
| "press"
|
|
67
|
+
| "focus"
|
|
68
|
+
| "inView"
|
|
69
|
+
| "inViewOptions"
|
|
70
|
+
// Drag config
|
|
71
|
+
| "drag"
|
|
72
|
+
| "dragConstraints"
|
|
73
|
+
| "dragElastic"
|
|
74
|
+
| "dragMomentum"
|
|
75
|
+
| "dragTransition"
|
|
76
|
+
| "dragSnapToOrigin"
|
|
77
|
+
| "dragControls"
|
|
78
|
+
| "whileDrag"
|
|
79
|
+
// Pan
|
|
80
|
+
| "panThreshold"
|
|
81
|
+
// Variants + transition
|
|
82
|
+
| "variants"
|
|
83
|
+
| "custom"
|
|
84
|
+
| "transition"
|
|
85
|
+
// Animation lifecycle
|
|
86
|
+
| "onAnimationStart"
|
|
87
|
+
| "onAnimationComplete"
|
|
88
|
+
| "onAnimationCancel"
|
|
89
|
+
| "onUpdate"
|
|
90
|
+
// Gesture lifecycle
|
|
91
|
+
| "onHoverStart"
|
|
92
|
+
| "onHoverEnd"
|
|
93
|
+
| "onPressStart"
|
|
94
|
+
| "onPress"
|
|
95
|
+
| "onPressCancel"
|
|
96
|
+
| "onFocus"
|
|
97
|
+
| "onBlur"
|
|
98
|
+
| "onPanStart"
|
|
99
|
+
| "onPan"
|
|
100
|
+
| "onPanEnd"
|
|
101
|
+
| "onViewportEnter"
|
|
102
|
+
| "onViewportLeave"
|
|
103
|
+
// Drag lifecycle
|
|
104
|
+
| "onDragStart"
|
|
105
|
+
| "onDrag"
|
|
106
|
+
| "onDragEnd"
|
|
107
|
+
| "onDragTransitionEnd"
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Frozen list of `MotionOptKey`s — fed to `splitProps` at every
|
|
111
|
+
* tag-component render to separate motion options from element
|
|
112
|
+
* attributes. The `satisfies` clause checks every entry against
|
|
113
|
+
* `MotionOptKey` at compile time so typos / drift between the union
|
|
114
|
+
* and the array surface as errors.
|
|
115
|
+
*/
|
|
116
|
+
export const MOTION_OPT_KEYS: readonly MotionOptKey[] = [
|
|
117
|
+
// Variant slots
|
|
118
|
+
"initial",
|
|
119
|
+
"animate",
|
|
120
|
+
"exit",
|
|
121
|
+
// Gesture targets
|
|
122
|
+
"hover",
|
|
123
|
+
"press",
|
|
124
|
+
"focus",
|
|
125
|
+
"inView",
|
|
126
|
+
"inViewOptions",
|
|
127
|
+
// Drag config
|
|
128
|
+
"drag",
|
|
129
|
+
"dragConstraints",
|
|
130
|
+
"dragElastic",
|
|
131
|
+
"dragMomentum",
|
|
132
|
+
"dragTransition",
|
|
133
|
+
"dragSnapToOrigin",
|
|
134
|
+
"dragControls",
|
|
135
|
+
"whileDrag",
|
|
136
|
+
// Pan
|
|
137
|
+
"panThreshold",
|
|
138
|
+
// Variants + transition
|
|
139
|
+
"variants",
|
|
140
|
+
"custom",
|
|
141
|
+
"transition",
|
|
142
|
+
// Animation lifecycle
|
|
143
|
+
"onAnimationStart",
|
|
144
|
+
"onAnimationComplete",
|
|
145
|
+
"onAnimationCancel",
|
|
146
|
+
"onUpdate",
|
|
147
|
+
// Gesture lifecycle
|
|
148
|
+
"onHoverStart",
|
|
149
|
+
"onHoverEnd",
|
|
150
|
+
"onPressStart",
|
|
151
|
+
"onPress",
|
|
152
|
+
"onPressCancel",
|
|
153
|
+
"onFocus",
|
|
154
|
+
"onBlur",
|
|
155
|
+
"onPanStart",
|
|
156
|
+
"onPan",
|
|
157
|
+
"onPanEnd",
|
|
158
|
+
"onViewportEnter",
|
|
159
|
+
"onViewportLeave",
|
|
160
|
+
// Drag lifecycle
|
|
161
|
+
"onDragStart",
|
|
162
|
+
"onDrag",
|
|
163
|
+
"onDragEnd",
|
|
164
|
+
"onDragTransitionEnd",
|
|
165
|
+
] as const satisfies readonly MotionOptKey[]
|
|
166
|
+
|
|
167
|
+
// Compile-time exhaustiveness check (union → keys direction). If a new
|
|
168
|
+
// MotionOptions key is added without being registered in MotionOptKey,
|
|
169
|
+
// TypeScript surfaces the missing key by name here.
|
|
170
|
+
type _MissingMotionOptKeys = Exclude<keyof MotionOptions, MotionOptKey>
|
|
171
|
+
// Variable prefixed with `_` so biome's noUnusedVariables exempts it —
|
|
172
|
+
// the const exists purely so TypeScript evaluates its type and surfaces
|
|
173
|
+
// the missing key when the constraint fails.
|
|
174
|
+
const _ensureExhaustive: [_MissingMotionOptKeys] extends [never]
|
|
175
|
+
? true
|
|
176
|
+
: { _missing: _MissingMotionOptKeys } = true
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* The shape of the `motion` proxy: every HTML/SVG intrinsic element name
|
|
180
|
+
* maps to a typed `Component` whose props are that element's native
|
|
181
|
+
* attribute set intersected with {@link MotionOptions}.
|
|
182
|
+
*
|
|
183
|
+
* The intersected `{ create: ... }` member adds the HOC entry point. The
|
|
184
|
+
* intersection's explicit `create` field wins over the mapped type's
|
|
185
|
+
* lookup (and there's no HTML/SVG tag named `create`), so `motion.create`
|
|
186
|
+
* is unambiguously typed as the HOC.
|
|
187
|
+
*/
|
|
188
|
+
export type Motion = {
|
|
189
|
+
// Override each intrinsic element's `style` to accept `MotionStyle` (which
|
|
190
|
+
// adds transform shortcuts + MotionValue variants on top of standard CSS).
|
|
191
|
+
// Without this override, `<motion.div style={{ scale: mv }} />` wouldn't
|
|
192
|
+
// typecheck — the intrinsic `style: JSX.CSSProperties` doesn't know about
|
|
193
|
+
// motion's transform-shortcut keys or MV values.
|
|
194
|
+
[Tag in keyof JSX.IntrinsicElements]: Component<
|
|
195
|
+
Omit<JSX.IntrinsicElements[Tag], "style"> & { style?: MotionStyle } & MotionOptions
|
|
196
|
+
>
|
|
197
|
+
} & {
|
|
198
|
+
/**
|
|
199
|
+
* Wrap a custom Component with motion's behavior. The wrapped Component
|
|
200
|
+
* must forward props (specifically `ref` and `style`) to a single DOM
|
|
201
|
+
* element root — either by spreading `{...props}` on its root or by
|
|
202
|
+
* explicitly setting `ref={props.ref}` and `style={props.style}`. Solid
|
|
203
|
+
* doesn't have `forwardRef`; the contract is enforced by convention and
|
|
204
|
+
* a dev-mode runtime warning if motion's ref never reaches the DOM.
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* ```tsx
|
|
208
|
+
* function MyCard(props) {
|
|
209
|
+
* return <div {...props}>{props.children}</div>
|
|
210
|
+
* }
|
|
211
|
+
* const Animated = motion.create(MyCard)
|
|
212
|
+
* <Animated animate={{ x: 100 }} hover={{ scale: 1.05 }} class="card" />
|
|
213
|
+
* ```
|
|
214
|
+
*/
|
|
215
|
+
// biome-ignore lint/suspicious/noExplicitAny: Solid's Component<P> requires P extends Record<string, any>
|
|
216
|
+
create: <P extends Record<string, any>>(Component: Component<P>) => Component<P & MotionOptions>
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Module-level cache. `motion.div` returns the SAME component instance
|
|
220
|
+
// across reads, which (a) lets Solid's reconciler skip redundant work and
|
|
221
|
+
// (b) keeps component identity stable for HMR + dev tooling.
|
|
222
|
+
// Solid's `Component<P>` is constrained to `P extends Record<string, any>`,
|
|
223
|
+
// so we use `any` here. The Motion type narrows the per-tag prop shape at
|
|
224
|
+
// the call site (motion.div has DivProps & MotionOptions); this storage
|
|
225
|
+
// uniformity is purely internal.
|
|
226
|
+
// biome-ignore lint/suspicious/noExplicitAny: Component<P> requires P extends Record<string, any>
|
|
227
|
+
type AnyComponent = Component<any>
|
|
228
|
+
|
|
229
|
+
const tagComponentCache = new Map<string, AnyComponent>()
|
|
230
|
+
|
|
231
|
+
// WeakSet of every component the proxy has manufactured (tag-components AND
|
|
232
|
+
// HOC-wrapped components). Used for the dev-mode `motion.create(motion.X)`
|
|
233
|
+
// double-wrap warning. WeakSet so HMR-replaced components don't pin their
|
|
234
|
+
// predecessors alive.
|
|
235
|
+
const motionComponents = new WeakSet<object>()
|
|
236
|
+
|
|
237
|
+
function makeMotionTag(tag: string): AnyComponent {
|
|
238
|
+
const cached = tagComponentCache.get(tag)
|
|
239
|
+
if (cached) return cached
|
|
240
|
+
|
|
241
|
+
const Tag: Component<ElementProps & MotionOptions> = (props) => {
|
|
242
|
+
const [motionOpts, rest] = splitProps(props, MOTION_OPT_KEYS)
|
|
243
|
+
const m = useMotion(() => motionOpts)
|
|
244
|
+
return (
|
|
245
|
+
<m.Provider>
|
|
246
|
+
<Dynamic component={tag} {...m(rest as ElementProps)} />
|
|
247
|
+
</m.Provider>
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
const stored = Tag as AnyComponent
|
|
251
|
+
tagComponentCache.set(tag, stored)
|
|
252
|
+
motionComponents.add(stored)
|
|
253
|
+
return stored
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* `motion.create(Component)` — wraps a custom Component with motion's
|
|
258
|
+
* behavior. The wrapped Component must forward props to a single DOM
|
|
259
|
+
* element root; the contract is documented in the {@link Motion.create}
|
|
260
|
+
* JSDoc above and enforced at runtime (in dev mode) by detecting whether
|
|
261
|
+
* motion's ref ever reaches the DOM after mount.
|
|
262
|
+
*/
|
|
263
|
+
// biome-ignore lint/suspicious/noExplicitAny: matches Motion.create's generic constraint
|
|
264
|
+
function motionCreate<P extends Record<string, any>>(
|
|
265
|
+
Component: Component<P>,
|
|
266
|
+
): Component<P & MotionOptions> {
|
|
267
|
+
// Dev-mode `motion.create(motion.X)` warning. Double-wrapping puts two
|
|
268
|
+
// motion state machines on the SAME root element — both register with
|
|
269
|
+
// Presence, both dispatch animate() writes, and the resulting writes
|
|
270
|
+
// race. Users almost always meant to compose options at one layer.
|
|
271
|
+
if (
|
|
272
|
+
process.env.NODE_ENV !== "production" &&
|
|
273
|
+
motionComponents.has(Component as unknown as object)
|
|
274
|
+
) {
|
|
275
|
+
console.warn(
|
|
276
|
+
"[solidjs-motion] motion.create(motion.X) double-wraps the same element " +
|
|
277
|
+
"with two motion state machines. Compose options on a single layer instead.",
|
|
278
|
+
)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const Wrapped: Component<P & MotionOptions> = (props) => {
|
|
282
|
+
const [motionOpts, rest] = splitProps(
|
|
283
|
+
props as unknown as Record<string, unknown>,
|
|
284
|
+
MOTION_OPT_KEYS,
|
|
285
|
+
)
|
|
286
|
+
const m = useMotion(() => motionOpts as MotionOptions)
|
|
287
|
+
|
|
288
|
+
// Dev-mode wrap-validity check (Q7 in the design grill / future ADR
|
|
289
|
+
// 0004): the wrapped Component must forward props.ref to a DOM
|
|
290
|
+
// element so motion's animations and exit registration can actually
|
|
291
|
+
// wire up. We can't enforce this at the type level in Solid (refs
|
|
292
|
+
// are conventionally optional, indistinguishable from "missing"), so
|
|
293
|
+
// we detect at runtime by riding a sentinel through the user-ref
|
|
294
|
+
// slot. `m()`'s internal mergeRefs combines this sentinel with
|
|
295
|
+
// motion's own ref — both fire together when the wrapped Component
|
|
296
|
+
// forwards props.ref to a DOM element. If neither fires after the
|
|
297
|
+
// mount cycle, the wrap is broken.
|
|
298
|
+
//
|
|
299
|
+
// The sentinel-merged ref is computed eagerly (not as a getter) so
|
|
300
|
+
// Solid's spread equality check doesn't churn — refs are stable
|
|
301
|
+
// callbacks set once per mount.
|
|
302
|
+
let refFired = false
|
|
303
|
+
const detector = (_el: MotionElement) => {
|
|
304
|
+
refFired = true
|
|
305
|
+
}
|
|
306
|
+
const userRef = (rest as { ref?: ((el: MotionElement) => void) | MotionElement }).ref
|
|
307
|
+
const mergedUserRef =
|
|
308
|
+
process.env.NODE_ENV !== "production"
|
|
309
|
+
? mergeRefs(userRef as ((el: MotionElement) => void) | undefined, detector)
|
|
310
|
+
: userRef
|
|
311
|
+
const restWithDetector =
|
|
312
|
+
process.env.NODE_ENV !== "production" ? mergeProps(rest, { ref: mergedUserRef }) : rest
|
|
313
|
+
|
|
314
|
+
if (process.env.NODE_ENV !== "production") {
|
|
315
|
+
onMount(() => {
|
|
316
|
+
// Defer one microtask so any synchronous-but-deep ref chain has
|
|
317
|
+
// had a chance to fire. Solid's createRenderEffect runs refs
|
|
318
|
+
// during the synchronous mount, but the Component might wrap
|
|
319
|
+
// its DOM root in another <Show>-like deferral.
|
|
320
|
+
queueMicrotask(() => {
|
|
321
|
+
if (!refFired) {
|
|
322
|
+
console.warn(
|
|
323
|
+
"[solidjs-motion] motion.create wrapped a Component whose root " +
|
|
324
|
+
"didn't receive motion's ref. The wrapped Component must " +
|
|
325
|
+
"either spread {...props} on a single DOM element OR " +
|
|
326
|
+
"explicitly forward `props.ref` to its root. Motion's " +
|
|
327
|
+
"animations and exit registration won't run until this " +
|
|
328
|
+
"is fixed.",
|
|
329
|
+
)
|
|
330
|
+
}
|
|
331
|
+
})
|
|
332
|
+
})
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return (
|
|
336
|
+
<m.Provider>
|
|
337
|
+
<Component {...(m(restWithDetector as ElementProps) as unknown as P)} />
|
|
338
|
+
</m.Provider>
|
|
339
|
+
)
|
|
340
|
+
}
|
|
341
|
+
motionComponents.add(Wrapped)
|
|
342
|
+
return Wrapped
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* `motion` — the indexable proxy. Every property access returns a cached
|
|
347
|
+
* motion-aware component for the given HTML/SVG tag. The reserved
|
|
348
|
+
* `motion.create` key returns the HOC entry point.
|
|
349
|
+
*
|
|
350
|
+
* @example HTML element
|
|
351
|
+
* ```tsx
|
|
352
|
+
* <motion.div animate={{ x: 100 }} hover={{ scale: 1.05 }}>
|
|
353
|
+
* draggable card
|
|
354
|
+
* </motion.div>
|
|
355
|
+
* ```
|
|
356
|
+
*
|
|
357
|
+
* @example SVG element (handled transparently via <Dynamic>)
|
|
358
|
+
* ```tsx
|
|
359
|
+
* <motion.path d="M0 0 L100 100" animate={{ pathLength: 1 }} />
|
|
360
|
+
* ```
|
|
361
|
+
*
|
|
362
|
+
* @example Wrapping a custom Component via the HOC
|
|
363
|
+
* ```tsx
|
|
364
|
+
* const Animated = motion.create(MyCard)
|
|
365
|
+
* <Animated animate={{ scale: 1.05 }} class="my-card" />
|
|
366
|
+
* ```
|
|
367
|
+
*
|
|
368
|
+
* Non-string keys (Symbols, well-known properties) return `undefined` so
|
|
369
|
+
* debugging tools and `typeof` checks see a sane shape.
|
|
370
|
+
*/
|
|
371
|
+
export const motion: Motion = new Proxy({} as Motion, {
|
|
372
|
+
get(_target, key) {
|
|
373
|
+
if (typeof key !== "string") return undefined
|
|
374
|
+
if (key === "create") return motionCreate
|
|
375
|
+
return makeMotionTag(key) as never
|
|
376
|
+
},
|
|
377
|
+
}) as Motion
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type Context, createContext, useContext } from "solid-js"
|
|
2
|
+
import type { PresenceContextValue } from "./types"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* No-op default. `useMotion` consumers wire their `exit` targets through this
|
|
6
|
+
* context, but without an enclosing `<Presence>` or `useAnimatePresence()`
|
|
7
|
+
* the registration is silently dropped. The real implementations live in
|
|
8
|
+
* `presence.tsx` (Phase 3).
|
|
9
|
+
*
|
|
10
|
+
* The shape is the inverted one (see types.ts JSDoc): children register a
|
|
11
|
+
* `runExit` callable; Presence dispatches via `beforeUnmount`. The no-op
|
|
12
|
+
* accepts the registration and resolves `beforeUnmount` immediately so
|
|
13
|
+
* motion children outside a `<Presence>` unmount instantly without trying
|
|
14
|
+
* to animate.
|
|
15
|
+
*/
|
|
16
|
+
const noopPresenceContext: PresenceContextValue = {
|
|
17
|
+
register: () => {},
|
|
18
|
+
unregister: () => {},
|
|
19
|
+
beforeUnmount: () => Promise.resolve(),
|
|
20
|
+
// `registerEnter` / `beforeMount` / `initial` intentionally LEFT UNDEFINED.
|
|
21
|
+
// createMotion detects "in a real Presence" by `presence.initial !== undefined`
|
|
22
|
+
// (the no-op leaves it undefined). Without that signal it animates first-
|
|
23
|
+
// mount immediately — outside a Presence the element is always already in
|
|
24
|
+
// the DOM by the time createMotion runs, so no defer is needed.
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const PresenceContext: Context<PresenceContextValue> =
|
|
28
|
+
createContext<PresenceContextValue>(noopPresenceContext)
|
|
29
|
+
|
|
30
|
+
export function usePresenceContext(): PresenceContextValue {
|
|
31
|
+
return useContext(PresenceContext)
|
|
32
|
+
}
|