@rootnative/inertia-svg 0.0.0-alpha.0 → 0.0.0-alpha.2
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/README.md +7 -0
- package/dist/index.d.mts +164 -24
- package/dist/index.d.ts +164 -24
- package/dist/index.js +236 -3
- package/dist/index.mjs +236 -7
- package/llms.txt +80 -3
- package/package.json +4 -3
- package/src/createMotionSvgComponent.tsx +417 -0
- package/src/index.ts +27 -9
- package/src/shapes.tsx +90 -0
- package/dist/index.js.map +0 -1
- package/dist/index.mjs.map +0 -1
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import { useEffect, useRef, type ComponentType } from 'react'
|
|
2
|
+
import Animated, {
|
|
3
|
+
useAnimatedProps,
|
|
4
|
+
useSharedValue,
|
|
5
|
+
type SharedValue,
|
|
6
|
+
} from 'react-native-reanimated'
|
|
7
|
+
import {
|
|
8
|
+
resolveNamedTransition,
|
|
9
|
+
resolveTransition,
|
|
10
|
+
useNamedTransitions,
|
|
11
|
+
useShouldReduceMotion,
|
|
12
|
+
type NamedTransitions,
|
|
13
|
+
type TransitionConfig,
|
|
14
|
+
type TransitionInput,
|
|
15
|
+
} from '@rootnative/inertia'
|
|
16
|
+
|
|
17
|
+
const NO_ANIMATION: TransitionConfig = { type: 'no-animation' }
|
|
18
|
+
|
|
19
|
+
/** String keys of the wrapped component's props. */
|
|
20
|
+
type SvgKey<P> = Extract<keyof P, string>
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Animatable target snapshot for a factory-built motion SVG component. Every
|
|
24
|
+
* field is optional — include only the dimensions you want to animate; the
|
|
25
|
+
* rest fall back to the static props on the component.
|
|
26
|
+
*/
|
|
27
|
+
export type SvgAnimate<
|
|
28
|
+
NK extends string = never,
|
|
29
|
+
CK extends string = never,
|
|
30
|
+
AK extends string = never,
|
|
31
|
+
> = Partial<Record<NK, number>> &
|
|
32
|
+
Partial<Record<CK, string>> &
|
|
33
|
+
Partial<Record<AK, readonly number[]>>
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Per-property transition map for a factory-built motion SVG component. Each
|
|
37
|
+
* entry accepts a `TransitionConfig` or a `TransitionName` registered on the
|
|
38
|
+
* nearest `<MotionConfig transitions>`.
|
|
39
|
+
*/
|
|
40
|
+
export type SvgPerPropertyTransition<K extends string> = Partial<
|
|
41
|
+
Record<K, TransitionInput>
|
|
42
|
+
>
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Transition shape accepted by factory-built motion SVG components: a single
|
|
46
|
+
* config (or registered name) applied to every animated dimension, or a
|
|
47
|
+
* per-property map. Per-property entries win over the top-level transition.
|
|
48
|
+
*/
|
|
49
|
+
export type SvgTransition<K extends string> =
|
|
50
|
+
| TransitionInput
|
|
51
|
+
| SvgPerPropertyTransition<K>
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Configuration for `createMotionSvgComponent`. Declares which of the wrapped
|
|
55
|
+
* component's props are animatable and how each interpolates:
|
|
56
|
+
*
|
|
57
|
+
* - `animatableProps` — numeric props (`cx`, `r`, `strokeDashoffset`, …),
|
|
58
|
+
* spring / timing / decay-driven.
|
|
59
|
+
* - `colorProps` — color-string props (`fill`, `stroke`), interpolated via
|
|
60
|
+
* Reanimated's native color animation.
|
|
61
|
+
* - `arrayProps` — numeric-array props (`strokeDasharray`), interpolated
|
|
62
|
+
* element-wise. **The array length is locked at first render** — the same
|
|
63
|
+
* shape-locked-at-mount rule `MotionPath` applies to path commands. Remount
|
|
64
|
+
* with a new `key` to change the length.
|
|
65
|
+
*/
|
|
66
|
+
export interface CreateMotionSvgComponentConfig<
|
|
67
|
+
P,
|
|
68
|
+
NK extends SvgKey<P>,
|
|
69
|
+
CK extends SvgKey<P>,
|
|
70
|
+
AK extends SvgKey<P>,
|
|
71
|
+
> {
|
|
72
|
+
animatableProps: readonly NK[]
|
|
73
|
+
colorProps?: readonly CK[]
|
|
74
|
+
arrayProps?: readonly AK[]
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Props of a factory-built motion SVG component: the wrapped component's own
|
|
79
|
+
* props (with the animatable keys narrowed to the animatable value shape)
|
|
80
|
+
* plus `initial` / `animate` / `transition`.
|
|
81
|
+
*/
|
|
82
|
+
export type MotionSvgComponentProps<
|
|
83
|
+
P,
|
|
84
|
+
NK extends string,
|
|
85
|
+
CK extends string,
|
|
86
|
+
AK extends string,
|
|
87
|
+
> = Omit<P, NK | CK | AK> &
|
|
88
|
+
Partial<Record<NK, number>> &
|
|
89
|
+
Partial<Record<CK, string>> &
|
|
90
|
+
Partial<Record<AK, readonly number[]>> & {
|
|
91
|
+
/**
|
|
92
|
+
* Initial frame override. When present, the component mounts displaying
|
|
93
|
+
* these values, then animates to `animate` on the next effect. Pass
|
|
94
|
+
* `false` to skip the initial-mount animation entirely.
|
|
95
|
+
*/
|
|
96
|
+
initial?: SvgAnimate<NK, CK, AK> | false
|
|
97
|
+
/** Target animation state. */
|
|
98
|
+
animate?: SvgAnimate<NK, CK, AK>
|
|
99
|
+
/**
|
|
100
|
+
* Transition config — a single `TransitionConfig` (or `TransitionName`
|
|
101
|
+
* registered on the nearest `<MotionConfig transitions>`) applied to
|
|
102
|
+
* every animated dimension, or a per-property map. Per-property entries
|
|
103
|
+
* win over the top-level transition.
|
|
104
|
+
*/
|
|
105
|
+
transition?: SvgTransition<NK | CK | AK>
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function pickTransition(
|
|
109
|
+
transition: SvgTransition<string> | undefined,
|
|
110
|
+
key: string,
|
|
111
|
+
registry: NamedTransitions,
|
|
112
|
+
): TransitionConfig | undefined {
|
|
113
|
+
if (!transition) return undefined
|
|
114
|
+
if (typeof transition === 'string' || 'type' in transition) {
|
|
115
|
+
return resolveNamedTransition(transition as TransitionInput, registry)
|
|
116
|
+
}
|
|
117
|
+
return resolveNamedTransition(
|
|
118
|
+
(transition as SvgPerPropertyTransition<string>)[key],
|
|
119
|
+
registry,
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Build an animatable wrapper around any `react-native-svg` element, driven
|
|
125
|
+
* by the same `initial` / `animate` / `transition` shape as the core
|
|
126
|
+
* `Motion.*` primitives. This is the mechanism behind the prebuilt
|
|
127
|
+
* `MotionCircle` / `MotionRect` / `MotionLine` — use it directly for any
|
|
128
|
+
* element the package doesn't prebuild (`Ellipse`, `Stop`, …).
|
|
129
|
+
*
|
|
130
|
+
* Semantics shared with the rest of the library:
|
|
131
|
+
*
|
|
132
|
+
* - `transition` accepts a config, a registered `TransitionName`, or a
|
|
133
|
+
* per-property map (entries accept names too); names resolve at the
|
|
134
|
+
* nearest `<MotionConfig transitions>`.
|
|
135
|
+
* - `<MotionConfig reducedMotion>` collapses every transition to
|
|
136
|
+
* `no-animation` so values snap.
|
|
137
|
+
* - `initial` seeds the first frame and is read once on mount;
|
|
138
|
+
* `initial={false}` mounts directly at the `animate` target.
|
|
139
|
+
*
|
|
140
|
+
* Factory-specific rules:
|
|
141
|
+
*
|
|
142
|
+
* - **A key only renders through the animation pipeline when it is present
|
|
143
|
+
* at mount** — in the static props, `initial`, or `animate`. Keys
|
|
144
|
+
* introduced into `animate` after mount warn in dev and are ignored
|
|
145
|
+
* (remount with `key={...}` to pick them up). Keys never engaged pass
|
|
146
|
+
* through as ordinary static props.
|
|
147
|
+
* - **Array props lock their length at first render.** Element-wise
|
|
148
|
+
* interpolation needs a stable slot count — a target with a different
|
|
149
|
+
* length throws in dev and is ignored in production.
|
|
150
|
+
* - Numeric keys engaged only via `animate` seed from `0`; color keys seed
|
|
151
|
+
* from `'transparent'`. Provide a static prop or `initial` value when the
|
|
152
|
+
* mount animation should start elsewhere.
|
|
153
|
+
*
|
|
154
|
+
* @example An animatable `<Ellipse>`
|
|
155
|
+
* ```tsx
|
|
156
|
+
* import { Ellipse } from 'react-native-svg'
|
|
157
|
+
* import { createMotionSvgComponent } from '@rootnative/inertia-svg'
|
|
158
|
+
*
|
|
159
|
+
* const MotionEllipse = createMotionSvgComponent(Ellipse, {
|
|
160
|
+
* animatableProps: ['cx', 'cy', 'rx', 'ry', 'opacity'],
|
|
161
|
+
* colorProps: ['fill', 'stroke'],
|
|
162
|
+
* })
|
|
163
|
+
*
|
|
164
|
+
* <MotionEllipse
|
|
165
|
+
* cx={50} cy={50} rx={10} ry={20}
|
|
166
|
+
* animate={{ rx: 30, fill: '#7c3aed' }}
|
|
167
|
+
* transition={{ type: 'spring', tension: 180, friction: 14 }}
|
|
168
|
+
* />
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
171
|
+
export function createMotionSvgComponent<
|
|
172
|
+
P extends object,
|
|
173
|
+
NK extends SvgKey<P>,
|
|
174
|
+
CK extends SvgKey<P> = never,
|
|
175
|
+
AK extends SvgKey<P> = never,
|
|
176
|
+
>(
|
|
177
|
+
Component: ComponentType<P>,
|
|
178
|
+
config: CreateMotionSvgComponentConfig<P, NK, CK, AK>,
|
|
179
|
+
): ComponentType<MotionSvgComponentProps<P, NK, CK, AK>> {
|
|
180
|
+
const AnimatedComponent = Animated.createAnimatedComponent(
|
|
181
|
+
Component as ComponentType<Record<string, unknown>>,
|
|
182
|
+
)
|
|
183
|
+
const numericKeys = config.animatableProps
|
|
184
|
+
const colorKeys = config.colorProps ?? []
|
|
185
|
+
const arrayKeys = config.arrayProps ?? []
|
|
186
|
+
|
|
187
|
+
function MotionSvgComponent(props: MotionSvgComponentProps<P, NK, CK, AK>) {
|
|
188
|
+
const { initial, animate, transition, ...rest } = props
|
|
189
|
+
const statics = rest as Record<string, unknown>
|
|
190
|
+
const seedSource = initial === false ? animate : (initial ?? undefined)
|
|
191
|
+
const reduce = useShouldReduceMotion()
|
|
192
|
+
const registry = useNamedTransitions()
|
|
193
|
+
|
|
194
|
+
// Lock the engaged key set and array lengths at mount. A key is engaged
|
|
195
|
+
// when any source defines it on the first render; only engaged keys are
|
|
196
|
+
// written by the worklet, so un-animated props keep their static /
|
|
197
|
+
// element-default rendering instead of being stomped by generic seeds.
|
|
198
|
+
const mountRef = useRef<{
|
|
199
|
+
engaged: Record<string, boolean>
|
|
200
|
+
engagedNumeric: string[]
|
|
201
|
+
engagedColor: string[]
|
|
202
|
+
engagedArray: string[]
|
|
203
|
+
arrayLengths: Record<string, number>
|
|
204
|
+
} | null>(null)
|
|
205
|
+
if (mountRef.current === null) {
|
|
206
|
+
const engaged: Record<string, boolean> = {}
|
|
207
|
+
const engagedNumeric: string[] = []
|
|
208
|
+
const engagedColor: string[] = []
|
|
209
|
+
const engagedArray: string[] = []
|
|
210
|
+
const arrayLengths: Record<string, number> = {}
|
|
211
|
+
const anim = animate as SvgAnimate<string, string, string> | undefined
|
|
212
|
+
const seed = seedSource as SvgAnimate<string, string, string> | undefined
|
|
213
|
+
for (const k of numericKeys) {
|
|
214
|
+
engaged[k] =
|
|
215
|
+
seed?.[k] !== undefined ||
|
|
216
|
+
anim?.[k] !== undefined ||
|
|
217
|
+
statics[k] !== undefined
|
|
218
|
+
if (engaged[k]) engagedNumeric.push(k)
|
|
219
|
+
}
|
|
220
|
+
for (const k of colorKeys) {
|
|
221
|
+
engaged[k] =
|
|
222
|
+
seed?.[k] !== undefined ||
|
|
223
|
+
anim?.[k] !== undefined ||
|
|
224
|
+
statics[k] !== undefined
|
|
225
|
+
if (engaged[k]) engagedColor.push(k)
|
|
226
|
+
}
|
|
227
|
+
for (const k of arrayKeys) {
|
|
228
|
+
const src = seed?.[k] ?? statics[k] ?? anim?.[k]
|
|
229
|
+
arrayLengths[k] = Array.isArray(src) ? src.length : 0
|
|
230
|
+
engaged[k] = arrayLengths[k] > 0
|
|
231
|
+
if (engaged[k]) engagedArray.push(k)
|
|
232
|
+
}
|
|
233
|
+
mountRef.current = {
|
|
234
|
+
engaged,
|
|
235
|
+
engagedNumeric,
|
|
236
|
+
engagedColor,
|
|
237
|
+
engagedArray,
|
|
238
|
+
arrayLengths,
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
const {
|
|
242
|
+
engaged,
|
|
243
|
+
engagedNumeric,
|
|
244
|
+
engagedColor,
|
|
245
|
+
engagedArray,
|
|
246
|
+
arrayLengths,
|
|
247
|
+
} = mountRef.current
|
|
248
|
+
|
|
249
|
+
const seed = seedSource as SvgAnimate<string, string, string> | undefined
|
|
250
|
+
const anim = animate as SvgAnimate<string, string, string> | undefined
|
|
251
|
+
|
|
252
|
+
// Loop-of-hooks over the config key lists — safe because the lists are
|
|
253
|
+
// fixed at factory time and array lengths are locked at mount above.
|
|
254
|
+
const numericSvs: Record<string, SharedValue<number>> = {}
|
|
255
|
+
for (const k of numericKeys) {
|
|
256
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
257
|
+
numericSvs[k] = useSharedValue<number>(
|
|
258
|
+
(seed?.[k] ?? statics[k] ?? 0) as number,
|
|
259
|
+
)
|
|
260
|
+
}
|
|
261
|
+
const colorSvs: Record<string, SharedValue<string>> = {}
|
|
262
|
+
for (const k of colorKeys) {
|
|
263
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
264
|
+
colorSvs[k] = useSharedValue<string>(
|
|
265
|
+
(seed?.[k] ?? statics[k] ?? 'transparent') as string,
|
|
266
|
+
)
|
|
267
|
+
}
|
|
268
|
+
const arraySvs: Record<string, SharedValue<number>[]> = {}
|
|
269
|
+
for (const k of arrayKeys) {
|
|
270
|
+
const len = arrayLengths[k]!
|
|
271
|
+
const seedArr = (seed?.[k] ?? statics[k] ?? anim?.[k]) as
|
|
272
|
+
| readonly number[]
|
|
273
|
+
| undefined
|
|
274
|
+
const svs: SharedValue<number>[] = []
|
|
275
|
+
for (let i = 0; i < len; i++) {
|
|
276
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
277
|
+
svs.push(useSharedValue<number>(Number(seedArr?.[i]) || 0))
|
|
278
|
+
}
|
|
279
|
+
arraySvs[k] = svs
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (__DEV__) {
|
|
283
|
+
// Mirror MotionPath's template guard: a static array prop that changes
|
|
284
|
+
// length after mount silently breaks element-wise interpolation, so
|
|
285
|
+
// fail loudly in dev.
|
|
286
|
+
for (const k of arrayKeys) {
|
|
287
|
+
const v = statics[k]
|
|
288
|
+
if (Array.isArray(v) && engaged[k] && v.length !== arrayLengths[k]) {
|
|
289
|
+
throw new Error(
|
|
290
|
+
`[inertia-svg] ${k} length changed after mount ` +
|
|
291
|
+
`(${arrayLengths[k]} → ${v.length}). Array props are locked at ` +
|
|
292
|
+
`first render — remount with key={...} to change the length.`,
|
|
293
|
+
)
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// One effect per configured key (fixed count). Targets are read into
|
|
299
|
+
// locals so the dep arrays key on the values, not on a fresh `animate`
|
|
300
|
+
// literal each render.
|
|
301
|
+
for (const k of numericKeys) {
|
|
302
|
+
const target = anim?.[k] as number | undefined
|
|
303
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
304
|
+
useEffect(() => {
|
|
305
|
+
if (target === undefined) return
|
|
306
|
+
if (!engaged[k]) {
|
|
307
|
+
if (__DEV__) warnNotEngaged(k)
|
|
308
|
+
return
|
|
309
|
+
}
|
|
310
|
+
const cfg = reduce
|
|
311
|
+
? NO_ANIMATION
|
|
312
|
+
: pickTransition(transition, k, registry)
|
|
313
|
+
numericSvs[k]!.value = resolveTransition(cfg, target) as number
|
|
314
|
+
// SVs / engaged set are mount-stable; registry changes re-resolve via
|
|
315
|
+
// the transition dep on the next animate change.
|
|
316
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
317
|
+
}, [target, reduce, transition])
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
for (const k of colorKeys) {
|
|
321
|
+
const target = anim?.[k] as string | undefined
|
|
322
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
323
|
+
useEffect(() => {
|
|
324
|
+
if (target === undefined) return
|
|
325
|
+
if (!engaged[k]) {
|
|
326
|
+
if (__DEV__) warnNotEngaged(k)
|
|
327
|
+
return
|
|
328
|
+
}
|
|
329
|
+
const cfg = reduce
|
|
330
|
+
? NO_ANIMATION
|
|
331
|
+
: pickTransition(transition, k, registry)
|
|
332
|
+
colorSvs[k]!.value = resolveTransition(cfg, target) as string
|
|
333
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
334
|
+
}, [target, reduce, transition])
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
for (const k of arrayKeys) {
|
|
338
|
+
const target = anim?.[k] as readonly number[] | undefined
|
|
339
|
+
const sig = target ? target.join(',') : undefined
|
|
340
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
341
|
+
useEffect(() => {
|
|
342
|
+
if (target === undefined) return
|
|
343
|
+
if (!engaged[k]) {
|
|
344
|
+
if (__DEV__) warnNotEngaged(k)
|
|
345
|
+
return
|
|
346
|
+
}
|
|
347
|
+
const len = arrayLengths[k]!
|
|
348
|
+
if (target.length !== len) {
|
|
349
|
+
if (__DEV__) {
|
|
350
|
+
throw new Error(
|
|
351
|
+
`[inertia-svg] animate.${k} length mismatch ` +
|
|
352
|
+
`(${len} → ${target.length}). Array props are locked at ` +
|
|
353
|
+
`first render — remount with key={...} to change the length.`,
|
|
354
|
+
)
|
|
355
|
+
}
|
|
356
|
+
return
|
|
357
|
+
}
|
|
358
|
+
const cfg = reduce
|
|
359
|
+
? NO_ANIMATION
|
|
360
|
+
: pickTransition(transition, k, registry)
|
|
361
|
+
const svs = arraySvs[k]!
|
|
362
|
+
for (let i = 0; i < len; i++) {
|
|
363
|
+
svs[i]!.value = resolveTransition(cfg, target[i] ?? 0) as number
|
|
364
|
+
}
|
|
365
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
366
|
+
}, [sig, reduce, transition])
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const animatedProps = useAnimatedProps(() => {
|
|
370
|
+
'worklet'
|
|
371
|
+
const out: Record<string, unknown> = {}
|
|
372
|
+
for (let i = 0; i < engagedNumeric.length; i++) {
|
|
373
|
+
const k = engagedNumeric[i]!
|
|
374
|
+
out[k] = numericSvs[k]!.value
|
|
375
|
+
}
|
|
376
|
+
for (let i = 0; i < engagedColor.length; i++) {
|
|
377
|
+
const k = engagedColor[i]!
|
|
378
|
+
out[k] = colorSvs[k]!.value
|
|
379
|
+
}
|
|
380
|
+
for (let i = 0; i < engagedArray.length; i++) {
|
|
381
|
+
const k = engagedArray[i]!
|
|
382
|
+
const svs = arraySvs[k]!
|
|
383
|
+
const arr = new Array<number>(svs.length)
|
|
384
|
+
for (let j = 0; j < svs.length; j++) arr[j] = svs[j]!.value
|
|
385
|
+
out[k] = arr
|
|
386
|
+
}
|
|
387
|
+
return out
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
return (
|
|
391
|
+
// `animatedProps` overrides every engaged key each frame; the static
|
|
392
|
+
// props in `rest` are the first-render seeds so the element renders
|
|
393
|
+
// before the first effect tick. The cast sheds Reanimated's strict-prop
|
|
394
|
+
// constraint that the worklet's return type can't express — the runtime
|
|
395
|
+
// shape is the same.
|
|
396
|
+
<AnimatedComponent animatedProps={animatedProps as never} {...statics} />
|
|
397
|
+
)
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const base = Component.displayName ?? Component.name ?? 'SvgComponent'
|
|
401
|
+
MotionSvgComponent.displayName = `Motion${base}`
|
|
402
|
+
|
|
403
|
+
return MotionSvgComponent as ComponentType<
|
|
404
|
+
MotionSvgComponentProps<P, NK, CK, AK>
|
|
405
|
+
>
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function warnNotEngaged(key: string) {
|
|
409
|
+
console.warn(
|
|
410
|
+
`[inertia-svg] animate.${key} was introduced after mount — the key ` +
|
|
411
|
+
`wasn't present at mount (static prop, initial, or animate), so its ` +
|
|
412
|
+
`animated value can't render. Include ${key} at mount or remount ` +
|
|
413
|
+
`with key={...}.`,
|
|
414
|
+
)
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
declare const __DEV__: boolean
|
package/src/index.ts
CHANGED
|
@@ -2,19 +2,23 @@
|
|
|
2
2
|
* `@rootnative/inertia-svg` — animatable SVG primitives for
|
|
3
3
|
* `@rootnative/inertia`.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* Surface:
|
|
6
6
|
* - `MotionPath` / `MotionSvg.Path` — animatable `<Path>` over
|
|
7
7
|
* `react-native-svg`. Supports path morphing on the `d` attribute (source
|
|
8
8
|
* and target must share the same command sequence) plus animatable
|
|
9
9
|
* `fill`, `stroke`, `strokeWidth`, `strokeOpacity`, `fillOpacity`,
|
|
10
10
|
* `opacity`, and `strokeDashoffset` with the same `initial` /
|
|
11
11
|
* `animate` / `transition` shape as the core `Motion.*` primitives.
|
|
12
|
+
* - `MotionCircle` / `MotionRect` / `MotionLine` (also on the `MotionSvg`
|
|
13
|
+
* namespace) — prebuilt animatable shapes with numeric, color, and
|
|
14
|
+
* `strokeDasharray` (array, length locked at mount) animation.
|
|
15
|
+
* - `createMotionSvgComponent(Component, config)` — the factory behind the
|
|
16
|
+
* prebuilt shapes; wraps any `react-native-svg` element with declarative
|
|
17
|
+
* `initial` / `animate` / `transition` props (named transitions included).
|
|
12
18
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* v0.2 — use structurally-compatible source/target paths and remount with
|
|
17
|
-
* `key={...}` to switch shape.
|
|
19
|
+
* Path normalization (resampling between structurally different paths) is
|
|
20
|
+
* out of scope — use structurally-compatible source/target paths and remount
|
|
21
|
+
* with `key={...}` to switch shape.
|
|
18
22
|
*/
|
|
19
23
|
export { MotionPath } from './MotionPath'
|
|
20
24
|
export type { MotionPathProps } from './MotionPath'
|
|
@@ -25,6 +29,16 @@ export type {
|
|
|
25
29
|
PathTransition,
|
|
26
30
|
} from './types'
|
|
27
31
|
|
|
32
|
+
export { createMotionSvgComponent } from './createMotionSvgComponent'
|
|
33
|
+
export type {
|
|
34
|
+
CreateMotionSvgComponentConfig,
|
|
35
|
+
MotionSvgComponentProps,
|
|
36
|
+
SvgAnimate,
|
|
37
|
+
SvgPerPropertyTransition,
|
|
38
|
+
SvgTransition,
|
|
39
|
+
} from './createMotionSvgComponent'
|
|
40
|
+
export { MotionCircle, MotionLine, MotionRect } from './shapes'
|
|
41
|
+
|
|
28
42
|
export {
|
|
29
43
|
parsePathD,
|
|
30
44
|
templateOf,
|
|
@@ -36,12 +50,16 @@ export {
|
|
|
36
50
|
} from './path'
|
|
37
51
|
|
|
38
52
|
import { MotionPath } from './MotionPath'
|
|
53
|
+
import { MotionCircle, MotionLine, MotionRect } from './shapes'
|
|
39
54
|
|
|
40
55
|
/**
|
|
41
|
-
* Namespace bundling every animatable SVG primitive. Use `MotionSvg.Path`
|
|
42
|
-
* autocomplete-friendly grouping or import
|
|
43
|
-
* point at the same component.
|
|
56
|
+
* Namespace bundling every animatable SVG primitive. Use `MotionSvg.Path` /
|
|
57
|
+
* `MotionSvg.Circle` for autocomplete-friendly grouping or import
|
|
58
|
+
* `MotionPath` / `MotionCircle` directly — both point at the same component.
|
|
44
59
|
*/
|
|
45
60
|
export const MotionSvg = {
|
|
46
61
|
Path: MotionPath,
|
|
62
|
+
Circle: MotionCircle,
|
|
63
|
+
Rect: MotionRect,
|
|
64
|
+
Line: MotionLine,
|
|
47
65
|
} as const
|
package/src/shapes.tsx
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Circle, Line, Rect } from 'react-native-svg'
|
|
2
|
+
import { createMotionSvgComponent } from './createMotionSvgComponent'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Animatable `<Circle>` from `react-native-svg`, built with
|
|
6
|
+
* `createMotionSvgComponent`. Animatable dimensions: `cx`, `cy`, `r`,
|
|
7
|
+
* `strokeWidth`, `strokeOpacity`, `fillOpacity`, `opacity`,
|
|
8
|
+
* `strokeDashoffset` (numeric), `fill` / `stroke` (color), and
|
|
9
|
+
* `strokeDasharray` (numeric array, length locked at mount).
|
|
10
|
+
*
|
|
11
|
+
* The canonical consumer is a circular progress ring — animate
|
|
12
|
+
* `strokeDashoffset` against a static `strokeDasharray` of the circumference:
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```tsx
|
|
16
|
+
* const CIRCUMFERENCE = 2 * Math.PI * 45
|
|
17
|
+
*
|
|
18
|
+
* <Svg viewBox="0 0 100 100">
|
|
19
|
+
* <MotionCircle
|
|
20
|
+
* cx={50} cy={50} r={45}
|
|
21
|
+
* stroke="#0ea5e9" strokeWidth={8} fill="none"
|
|
22
|
+
* strokeDasharray={[CIRCUMFERENCE]}
|
|
23
|
+
* strokeDashoffset={CIRCUMFERENCE}
|
|
24
|
+
* animate={{ strokeDashoffset: CIRCUMFERENCE * (1 - progress) }}
|
|
25
|
+
* transition={{ type: 'timing', duration: 300 }}
|
|
26
|
+
* />
|
|
27
|
+
* </Svg>
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export const MotionCircle = createMotionSvgComponent(Circle, {
|
|
31
|
+
animatableProps: [
|
|
32
|
+
'cx',
|
|
33
|
+
'cy',
|
|
34
|
+
'r',
|
|
35
|
+
'strokeWidth',
|
|
36
|
+
'strokeOpacity',
|
|
37
|
+
'fillOpacity',
|
|
38
|
+
'opacity',
|
|
39
|
+
'strokeDashoffset',
|
|
40
|
+
],
|
|
41
|
+
colorProps: ['fill', 'stroke'],
|
|
42
|
+
arrayProps: ['strokeDasharray'],
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Animatable `<Rect>` from `react-native-svg`, built with
|
|
47
|
+
* `createMotionSvgComponent`. Animatable dimensions: `x`, `y`, `width`,
|
|
48
|
+
* `height`, `rx`, `ry`, `strokeWidth`, `strokeOpacity`, `fillOpacity`,
|
|
49
|
+
* `opacity`, `strokeDashoffset` (numeric), `fill` / `stroke` (color), and
|
|
50
|
+
* `strokeDasharray` (numeric array, length locked at mount).
|
|
51
|
+
*/
|
|
52
|
+
export const MotionRect = createMotionSvgComponent(Rect, {
|
|
53
|
+
animatableProps: [
|
|
54
|
+
'x',
|
|
55
|
+
'y',
|
|
56
|
+
'width',
|
|
57
|
+
'height',
|
|
58
|
+
'rx',
|
|
59
|
+
'ry',
|
|
60
|
+
'strokeWidth',
|
|
61
|
+
'strokeOpacity',
|
|
62
|
+
'fillOpacity',
|
|
63
|
+
'opacity',
|
|
64
|
+
'strokeDashoffset',
|
|
65
|
+
],
|
|
66
|
+
colorProps: ['fill', 'stroke'],
|
|
67
|
+
arrayProps: ['strokeDasharray'],
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Animatable `<Line>` from `react-native-svg`, built with
|
|
72
|
+
* `createMotionSvgComponent`. Animatable dimensions: `x1`, `y1`, `x2`, `y2`,
|
|
73
|
+
* `strokeWidth`, `strokeOpacity`, `opacity`, `strokeDashoffset` (numeric),
|
|
74
|
+
* `stroke` (color), and `strokeDasharray` (numeric array, length locked at
|
|
75
|
+
* mount).
|
|
76
|
+
*/
|
|
77
|
+
export const MotionLine = createMotionSvgComponent(Line, {
|
|
78
|
+
animatableProps: [
|
|
79
|
+
'x1',
|
|
80
|
+
'y1',
|
|
81
|
+
'x2',
|
|
82
|
+
'y2',
|
|
83
|
+
'strokeWidth',
|
|
84
|
+
'strokeOpacity',
|
|
85
|
+
'opacity',
|
|
86
|
+
'strokeDashoffset',
|
|
87
|
+
],
|
|
88
|
+
colorProps: ['stroke'],
|
|
89
|
+
arrayProps: ['strokeDasharray'],
|
|
90
|
+
})
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/path.ts","../src/MotionPath.tsx","../src/index.ts"],"names":["Animated","Path","useRef","useMemo","useSharedValue","useShouldReduceMotion","useEffect","resolveTransition","useAnimatedProps","jsx"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * SVG path-string utilities used by `MotionSvg.Path`. Everything here runs on\n * the JS thread — paths are tokenized into a normalized command list at mount\n * and when `animate.d` changes; the worklet only ever consumes flat number\n * arrays + a frozen command template.\n *\n * Path morphing in v0.2 requires **structural compatibility**: the source and\n * every target `d` must produce the same command sequence (same command\n * letters, in the same order, after implicit-repeat expansion). Element-wise\n * numeric interpolation is the entire morphing model — we do not resample\n * paths or insert/remove commands. Same-shape morphs (e.g. a heart breathing,\n * a chevron flipping, a check mark tracing in) are the supported use case.\n */\n\n/** Arg count per SVG path command. `Z`/`z` close the subpath and take none. */\nconst CMD_ARGS: Readonly<Record<string, number>> = {\n M: 2,\n m: 2,\n L: 2,\n l: 2,\n H: 1,\n h: 1,\n V: 1,\n v: 1,\n C: 6,\n c: 6,\n S: 4,\n s: 4,\n Q: 4,\n q: 4,\n T: 2,\n t: 2,\n A: 7,\n a: 7,\n Z: 0,\n z: 0,\n}\n\n/**\n * After an explicit `M`/`m` the SVG spec says additional coordinate pairs are\n * implicit `L`/`l` commands. Every other command repeats itself.\n */\nconst CMD_REPEAT: Readonly<Record<string, string>> = {\n M: 'L',\n m: 'l',\n}\n\n/**\n * A single normalized path command after implicit-repeat expansion. The cmd\n * letter is preserved (absolute vs relative — case is meaningful to the SVG\n * renderer). `args` always has exactly `CMD_ARGS[cmd]` entries.\n */\nexport interface PathSegment {\n cmd: string\n args: number[]\n}\n\nconst isDigit = (c: string): boolean => c >= '0' && c <= '9'\n\n/**\n * Tokenize a path `d` string into a stream of (command-letter | number)\n * tokens. Handles SVG's \"compact\" number forms — adjacent numbers separated\n * only by sign (`1-2`) or decimal point (`.5.6`) — so author-written paths\n * with mixed spacing all parse to the same tokens.\n */\nfunction tokenize(d: string): Array<string | number> {\n const out: Array<string | number> = []\n const len = d.length\n let i = 0\n while (i < len) {\n const c = d[i]!\n // SVG path whitespace + comma separators.\n if (c === ' ' || c === ',' || c === '\\t' || c === '\\n' || c === '\\r') {\n i++\n continue\n }\n // Command letter — any ASCII letter not adjacent to a number context.\n if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {\n if (!(c in CMD_ARGS)) {\n throw new Error(\n `[inertia-svg] unknown path command '${c}' at position ${i}`,\n )\n }\n out.push(c)\n i++\n continue\n }\n // Number. Reaches here for digits, `.`, `+`, `-`.\n const start = i\n let hasDigit = false\n let hasDot = false\n if (c === '+' || c === '-') i++\n while (i < len) {\n const ch = d[i]!\n if (isDigit(ch)) {\n hasDigit = true\n i++\n } else if (ch === '.' && !hasDot) {\n hasDot = true\n i++\n } else {\n break\n }\n }\n if (hasDigit && (d[i] === 'e' || d[i] === 'E')) {\n i++\n if (d[i] === '+' || d[i] === '-') i++\n while (i < len && isDigit(d[i]!)) i++\n }\n if (!hasDigit) {\n throw new Error(\n `[inertia-svg] expected number at position ${start} in path '${d}', got '${c}'`,\n )\n }\n out.push(Number(d.substring(start, i)))\n }\n return out\n}\n\n/**\n * Parse a path `d` string into a flat list of normalized segments. Implicit\n * repeats are expanded — `M 0 0 10 10 20 20` becomes three segments\n * (`M 0 0`, `L 10 10`, `L 20 20`) so the segment list can be compared and\n * interpolated 1:1 against another path.\n */\nexport function parsePathD(d: string): PathSegment[] {\n const tokens = tokenize(d)\n const segments: PathSegment[] = []\n let i = 0\n while (i < tokens.length) {\n const t = tokens[i]\n if (typeof t !== 'string') {\n throw new Error(\n `[inertia-svg] expected command letter at token ${i}, got number ${t} — paths must start with a command`,\n )\n }\n const cmd = t\n const argCount = CMD_ARGS[cmd]!\n i++\n if (argCount === 0) {\n segments.push({ cmd, args: [] })\n continue\n }\n // First explicit batch for this command.\n const first: number[] = []\n for (let j = 0; j < argCount; j++) {\n const v = tokens[i++]\n if (typeof v !== 'number') {\n throw new Error(\n `[inertia-svg] command '${cmd}' expected ${argCount} numbers, got '${v}' at token ${i - 1}`,\n )\n }\n first.push(v)\n }\n segments.push({ cmd, args: first })\n // Repeated batches consume numbers up to the next command letter, applying\n // the implicit-repeat command (M → L, m → l, everything else → itself).\n // `argCount === 0` is handled above with an early continue, so the loop\n // body here always makes forward progress.\n const repeatCmd = CMD_REPEAT[cmd] ?? cmd\n while (i < tokens.length && typeof tokens[i] === 'number') {\n const batch: number[] = []\n for (let j = 0; j < argCount; j++) {\n const v = tokens[i++]\n if (typeof v !== 'number') {\n throw new Error(\n `[inertia-svg] command '${cmd}' (implicit repeat as '${repeatCmd}') expected ${argCount} numbers`,\n )\n }\n batch.push(v)\n }\n segments.push({ cmd: repeatCmd, args: batch })\n }\n }\n return segments\n}\n\n/**\n * The frozen \"shape\" of a path — just command letters and arg widths. Two\n * paths are morphable iff their templates are equal.\n */\nexport interface PathTemplate {\n cmds: ReadonlyArray<string>\n /** Flat width per segment, indexed parallel to `cmds`. */\n widths: ReadonlyArray<number>\n /** Total scalar count across all segments — `widths.reduce((a,b)=>a+b,0)`. */\n size: number\n}\n\nexport function templateOf(segments: ReadonlyArray<PathSegment>): PathTemplate {\n const cmds = segments.map((s) => s.cmd)\n const widths = segments.map((s) => s.args.length)\n let size = 0\n for (let i = 0; i < widths.length; i++) size += widths[i]!\n return { cmds, widths, size }\n}\n\n/** Flatten a parsed segment list into a single number array (length === size). */\nexport function flattenParams(segments: ReadonlyArray<PathSegment>): number[] {\n const out: number[] = []\n for (let i = 0; i < segments.length; i++) {\n const args = segments[i]!.args\n for (let j = 0; j < args.length; j++) out.push(args[j]!)\n }\n return out\n}\n\n/**\n * Verify a target template matches the source. Returns `null` on match or a\n * descriptive error string on mismatch — callers throw in `__DEV__` and\n * skip the bad target in production (the path keeps its current `d`).\n */\nexport function diffTemplate(\n source: PathTemplate,\n target: PathTemplate,\n): string | null {\n if (source.cmds.length !== target.cmds.length) {\n return `command count differs: source has ${source.cmds.length} segments, target has ${target.cmds.length}. Paths must produce the same command sequence after implicit-repeat expansion.`\n }\n for (let i = 0; i < source.cmds.length; i++) {\n if (source.cmds[i] !== target.cmds[i]) {\n return `command at segment ${i} differs: source '${source.cmds[i]}' vs target '${target.cmds[i]}'. Command letters (including case — absolute vs relative) must match.`\n }\n }\n return null\n}\n\n/**\n * Build a path `d` string from a template + flat param array. Runs inside the\n * worklet on the UI thread, so it must not capture any JS-thread closures or\n * use Array.prototype helpers that allocate intermediates the Hermes runtime\n * boxes into JS objects. Manual loops + `+=` string concat keep the worklet\n * cheap.\n *\n * MUST be a worklet — call sites in `MotionPath` wrap it with `'worklet'` via\n * `useAnimatedProps`.\n */\nexport function serializePath(\n template: PathTemplate,\n params: ReadonlyArray<number>,\n): string {\n 'worklet'\n let out = ''\n let p = 0\n for (let i = 0; i < template.cmds.length; i++) {\n out += template.cmds[i]\n const w = template.widths[i]!\n for (let j = 0; j < w; j++) {\n out += ' '\n out += params[p++]\n }\n }\n return out\n}\n","import { useEffect, useMemo, useRef } from 'react'\nimport { Path, type PathProps } from 'react-native-svg'\nimport Animated, {\n useAnimatedProps,\n useSharedValue,\n type SharedValue,\n} from 'react-native-reanimated'\nimport {\n resolveTransition,\n useShouldReduceMotion,\n type TransitionConfig,\n} from '@rootnative/inertia'\nimport {\n diffTemplate,\n flattenParams,\n parsePathD,\n serializePath,\n templateOf,\n type PathTemplate,\n} from './path'\nimport type {\n PathAnimate,\n PathPerPropertyTransition,\n PathTransition,\n} from './types'\n\nconst AnimatedPath = Animated.createAnimatedComponent(Path)\n\nconst NO_ANIMATION: TransitionConfig = { type: 'no-animation' }\n\nfunction pickTransition(\n per: PathTransition | undefined,\n key: keyof PathPerPropertyTransition,\n): TransitionConfig | undefined {\n if (!per) return undefined\n if ('type' in per) return per as TransitionConfig\n return (per as PathPerPropertyTransition)[key]\n}\n\nexport interface MotionPathProps extends Omit<\n PathProps,\n | 'd'\n | 'fill'\n | 'stroke'\n | 'strokeWidth'\n | 'strokeOpacity'\n | 'fillOpacity'\n | 'opacity'\n | 'strokeDashoffset'\n> {\n /**\n * Initial path data. **The command sequence is locked at first render** —\n * every target `d` passed via `animate` / `initial` must produce the same\n * command letters in the same order after implicit-repeat expansion. To\n * morph between structurally different paths, remount with a new `key`.\n */\n d: string\n fill?: string\n stroke?: string\n strokeWidth?: number\n strokeOpacity?: number\n fillOpacity?: number\n opacity?: number\n strokeDashoffset?: number\n /**\n * Initial frame override. When present, the component mounts displaying\n * these values, then animates to `animate` on the next effect. Pass `false`\n * to skip the initial-mount animation entirely.\n */\n initial?: PathAnimate | false\n /** Target animation state. */\n animate?: PathAnimate\n /**\n * Transition config — either a single `TransitionConfig` applied to every\n * animated dimension, or a per-property map. Per-property entries win over\n * the top-level transition.\n */\n transition?: PathTransition\n}\n\n/**\n * Animatable `<Path>` from `react-native-svg`. Wraps `Path` with declarative\n * `initial` / `animate` / `transition` props.\n *\n * Animatable dimensions:\n * - `d` — path morph via element-wise scalar interpolation. Source and target\n * must share the same command sequence (e.g. both `M L L L Z`).\n * - `fill`, `stroke` — color strings, interpolated via Reanimated's native\n * color animation.\n * - `strokeWidth`, `strokeOpacity`, `fillOpacity`, `opacity`,\n * `strokeDashoffset` — numeric, spring or timing-driven.\n *\n * Example:\n * ```tsx\n * <Svg viewBox=\"0 0 100 100\">\n * <MotionPath\n * d=\"M 50 20 L 80 80 L 20 80 Z\"\n * animate={{ d: \"M 50 80 L 80 20 L 20 20 Z\", fill: '#7c3aed' }}\n * transition={{ type: 'spring', tension: 140, friction: 12 }}\n * fill=\"#0ea5e9\"\n * />\n * </Svg>\n * ```\n */\nexport function MotionPath(props: MotionPathProps) {\n const {\n d,\n fill,\n stroke,\n strokeWidth,\n strokeOpacity,\n fillOpacity,\n opacity,\n strokeDashoffset,\n initial,\n animate,\n transition,\n ...rest\n } = props\n\n // Parse + freeze the source template at mount. The number of scalar params\n // is locked here so the shared-value array allocated below has a stable\n // length across renders.\n const sourceRef = useRef<{\n template: PathTemplate\n params: number[]\n } | null>(null)\n if (sourceRef.current === null) {\n const segments = parsePathD(d)\n sourceRef.current = {\n template: templateOf(segments),\n params: flattenParams(segments),\n }\n }\n const template = sourceRef.current.template\n\n if (__DEV__) {\n // Re-parse the current `d` prop and verify the template hasn't shifted.\n // Catches the easy mistake of swapping a star for a hexagon without\n // remounting via `key`.\n const segments = parsePathD(d)\n const live = templateOf(segments)\n const err = diffTemplate(template, live)\n if (err) {\n throw new Error(\n `[inertia-svg] d prop template changed after mount: ${err}\\n` +\n `If you need to swap to a structurally different path, remount with key={...}.`,\n )\n }\n }\n\n // `initial: false` → start at the animate target (no mount animation).\n // `initial: {...}` → explicit seed values.\n // `initial: undefined` → seed from the static props.\n const seedSource = initial === false ? animate : (initial ?? undefined)\n\n // Seed the path params. If `initial.d` is provided, parse it and verify\n // it's template-compatible before seeding.\n const seedParams: number[] = useMemo(() => {\n if (!seedSource?.d) return sourceRef.current!.params\n const segs = parsePathD(seedSource.d)\n const t = templateOf(segs)\n const err = diffTemplate(template, t)\n if (err) {\n if (__DEV__) {\n throw new Error(`[inertia-svg] initial.d template mismatch: ${err}`)\n }\n return sourceRef.current!.params\n }\n return flattenParams(segs)\n // template is stable for the component's lifetime; seedSource is the\n // only meaningful input. We intentionally ignore `template` in deps.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [seedSource?.d])\n\n // Loop-of-hooks per scalar param — safe because `template.size` is locked\n // at mount via the source ref above.\n const paramSvs: SharedValue<number>[] = []\n for (let i = 0; i < template.size; i++) {\n // eslint-disable-next-line react-hooks/rules-of-hooks\n paramSvs.push(useSharedValue<number>(seedParams[i] ?? 0))\n }\n\n // Scalar property SVs. Strings (`fill`, `stroke`) use color seeds so\n // Reanimated recognizes them as colors from frame 1.\n const fillSv = useSharedValue<string>(\n seedSource?.fill ?? fill ?? 'transparent',\n )\n const strokeSv = useSharedValue<string>(\n seedSource?.stroke ?? stroke ?? 'transparent',\n )\n const strokeWidthSv = useSharedValue<number>(\n seedSource?.strokeWidth ?? strokeWidth ?? 1,\n )\n const strokeOpacitySv = useSharedValue<number>(\n seedSource?.strokeOpacity ?? strokeOpacity ?? 1,\n )\n const fillOpacitySv = useSharedValue<number>(\n seedSource?.fillOpacity ?? fillOpacity ?? 1,\n )\n const opacitySv = useSharedValue<number>(seedSource?.opacity ?? opacity ?? 1)\n const strokeDashoffsetSv = useSharedValue<number>(\n seedSource?.strokeDashoffset ?? strokeDashoffset ?? 0,\n )\n\n const reduce = useShouldReduceMotion()\n\n // Serialize scalar targets into stable keys so effects re-run on value\n // change, not on every parent re-render (a fresh `animate` literal each\n // render is the common case).\n const animateD = animate?.d\n const animateFill = animate?.fill\n const animateStroke = animate?.stroke\n const animateStrokeWidth = animate?.strokeWidth\n const animateStrokeOpacity = animate?.strokeOpacity\n const animateFillOpacity = animate?.fillOpacity\n const animateOpacity = animate?.opacity\n const animateStrokeDashoffset = animate?.strokeDashoffset\n\n useEffect(() => {\n if (animateD === undefined) return\n const segments = parsePathD(animateD)\n const t = templateOf(segments)\n const err = diffTemplate(template, t)\n if (err) {\n if (__DEV__) {\n throw new Error(`[inertia-svg] animate.d template mismatch: ${err}`)\n }\n return\n }\n const target = flattenParams(segments)\n const cfg = reduce ? NO_ANIMATION : pickTransition(transition, 'd')\n for (let i = 0; i < paramSvs.length; i++) {\n paramSvs[i]!.value = resolveTransition(cfg, target[i] ?? 0) as number\n }\n // paramSvs / template are stable across renders by the locks above.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [animateD, reduce, transition])\n\n useEffect(() => {\n if (animateFill === undefined) return\n const cfg = reduce ? NO_ANIMATION : pickTransition(transition, 'fill')\n fillSv.value = resolveTransition(cfg, animateFill) as string\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [animateFill, reduce, transition])\n\n useEffect(() => {\n if (animateStroke === undefined) return\n const cfg = reduce ? NO_ANIMATION : pickTransition(transition, 'stroke')\n strokeSv.value = resolveTransition(cfg, animateStroke) as string\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [animateStroke, reduce, transition])\n\n useEffect(() => {\n if (animateStrokeWidth === undefined) return\n const cfg = reduce\n ? NO_ANIMATION\n : pickTransition(transition, 'strokeWidth')\n strokeWidthSv.value = resolveTransition(cfg, animateStrokeWidth) as number\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [animateStrokeWidth, reduce, transition])\n\n useEffect(() => {\n if (animateStrokeOpacity === undefined) return\n const cfg = reduce\n ? NO_ANIMATION\n : pickTransition(transition, 'strokeOpacity')\n strokeOpacitySv.value = resolveTransition(\n cfg,\n animateStrokeOpacity,\n ) as number\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [animateStrokeOpacity, reduce, transition])\n\n useEffect(() => {\n if (animateFillOpacity === undefined) return\n const cfg = reduce\n ? NO_ANIMATION\n : pickTransition(transition, 'fillOpacity')\n fillOpacitySv.value = resolveTransition(cfg, animateFillOpacity) as number\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [animateFillOpacity, reduce, transition])\n\n useEffect(() => {\n if (animateOpacity === undefined) return\n const cfg = reduce ? NO_ANIMATION : pickTransition(transition, 'opacity')\n opacitySv.value = resolveTransition(cfg, animateOpacity) as number\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [animateOpacity, reduce, transition])\n\n useEffect(() => {\n if (animateStrokeDashoffset === undefined) return\n const cfg = reduce\n ? NO_ANIMATION\n : pickTransition(transition, 'strokeDashoffset')\n strokeDashoffsetSv.value = resolveTransition(\n cfg,\n animateStrokeDashoffset,\n ) as number\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [animateStrokeDashoffset, reduce, transition])\n\n const animatedProps = useAnimatedProps(() => {\n 'worklet'\n const params = new Array<number>(paramSvs.length)\n for (let i = 0; i < paramSvs.length; i++) params[i] = paramSvs[i]!.value\n return {\n d: serializePath(template, params),\n fill: fillSv.value,\n stroke: strokeSv.value,\n strokeWidth: strokeWidthSv.value,\n strokeOpacity: strokeOpacitySv.value,\n fillOpacity: fillOpacitySv.value,\n opacity: opacitySv.value,\n strokeDashoffset: strokeDashoffsetSv.value,\n }\n })\n\n return (\n <AnimatedPath\n // `animatedProps` overrides every animated key each frame; the static\n // props below are the first-render seeds so the path renders before the\n // first effect tick. The cast sheds Reanimated's strict-prop constraint\n // that the worklet's return type can't express — the runtime shape is\n // the same.\n animatedProps={animatedProps as never}\n d={d}\n fill={fill}\n stroke={stroke}\n strokeWidth={strokeWidth}\n strokeOpacity={strokeOpacity}\n fillOpacity={fillOpacity}\n opacity={opacity}\n strokeDashoffset={strokeDashoffset}\n {...rest}\n />\n )\n}\n\ndeclare const __DEV__: boolean\n","/**\n * `@rootnative/inertia-svg` — animatable SVG primitives for\n * `@rootnative/inertia`.\n *\n * v0.2 surface:\n * - `MotionPath` / `MotionSvg.Path` — animatable `<Path>` over\n * `react-native-svg`. Supports path morphing on the `d` attribute (source\n * and target must share the same command sequence) plus animatable\n * `fill`, `stroke`, `strokeWidth`, `strokeOpacity`, `fillOpacity`,\n * `opacity`, and `strokeDashoffset` with the same `initial` /\n * `animate` / `transition` shape as the core `Motion.*` primitives.\n *\n * Additional shape primitives (`Circle`, `Rect`, `Line`, `Ellipse`) land in\n * a follow-up once the path morphing API is validated. Path normalization\n * (resampling between structurally different paths) is out of scope for\n * v0.2 — use structurally-compatible source/target paths and remount with\n * `key={...}` to switch shape.\n */\nexport { MotionPath } from './MotionPath'\nexport type { MotionPathProps } from './MotionPath'\nexport type {\n PathAnimate,\n PathPerPropertyTransition,\n PathStateShape,\n PathTransition,\n} from './types'\n\nexport {\n parsePathD,\n templateOf,\n diffTemplate,\n flattenParams,\n serializePath,\n type PathSegment,\n type PathTemplate,\n} from './path'\n\nimport { MotionPath } from './MotionPath'\n\n/**\n * Namespace bundling every animatable SVG primitive. Use `MotionSvg.Path` for\n * autocomplete-friendly grouping or import `MotionPath` directly — both\n * point at the same component.\n */\nexport const MotionSvg = {\n Path: MotionPath,\n} as const\n"]}
|