@pixodesk/svg-animator-rn 1.0.35 → 1.0.40
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 +47 -55
- package/dist/index.cjs +113 -89
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -237
- package/dist/index.d.ts +31 -237
- package/dist/index.js +100 -81
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/PixodeskSvgAnimator.tsx +158 -154
- package/src/PxRnErrorBoundary.tsx +13 -4
- package/src/PxRnMatrix.test.ts +2 -2
- package/src/PxRnMatrix.ts +1 -1
- package/src/PxRnPropNames.ts +4 -3
- package/src/PxRnRender.tsx +21 -27
- package/src/PxRnSafety.ts +3 -1
- package/src/PxRnTracks.test.ts +12 -12
- package/src/PxRnTracks.ts +10 -12
- package/src/PxRnTypeMap.ts +1 -0
- package/src/index.ts +0 -9
package/src/PxRnRender.tsx
CHANGED
|
@@ -3,20 +3,13 @@
|
|
|
3
3
|
* Licensed under the MIT License. See the LICENSE file in the project root for details.
|
|
4
4
|
*---------------------------------------------------------------------------------------*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
resolveStyle,
|
|
9
|
-
sanitiseAttributeValue,
|
|
10
|
-
DISALLOWED_SVG_TAGS_LOWER,
|
|
11
|
-
TEXT_ATTR,
|
|
12
|
-
TEXT_CONTENT_ATTR,
|
|
13
|
-
type PxDefs,
|
|
14
|
-
type PxNode,
|
|
15
|
-
} from '@pixodesk/svg-animator-core';
|
|
6
|
+
import { getNormalizedProps, type PxDefs, type PxNode } from '@pixodesk/svg-animator-core';
|
|
7
|
+
import { sanitizeAttributeValue, DISALLOWED_SVG_TAGS_LOWER, TEXT_CONTENT_ATTR } from '@pixodesk/svg-animator-core/internal';
|
|
16
8
|
import { createElement, type ComponentType, type ReactElement, type ReactNode } from 'react';
|
|
17
9
|
import { RN_SVG_COMPONENTS } from './PxRnTypeMap';
|
|
18
10
|
import { toRnPropName, toRnPropValue } from './PxRnPropNames';
|
|
19
11
|
|
|
12
|
+
/** @internal */
|
|
20
13
|
export interface RenderRnNodeOptions {
|
|
21
14
|
/** Collects non-fatal issues (unsupported tags, dropped attrs). */
|
|
22
15
|
warnings?: Array<string>;
|
|
@@ -42,27 +35,29 @@ export interface RenderRnNodeOptions {
|
|
|
42
35
|
}
|
|
43
36
|
|
|
44
37
|
/**
|
|
45
|
-
* Converts core-
|
|
46
|
-
* naming,
|
|
38
|
+
* Converts core-normalized wire props into react-native-svg props: RN prop
|
|
39
|
+
* naming, sanitization (same security rules as the web renderer), numeric
|
|
47
40
|
* coercion where possible.
|
|
41
|
+
* @internal
|
|
48
42
|
*/
|
|
49
43
|
export function toRnProps(props: Record<string, any>, warnings?: Array<string>, tag?: string): Record<string, any> {
|
|
50
|
-
const
|
|
44
|
+
const normalized = getNormalizedProps(props);
|
|
51
45
|
const out: Record<string, any> = {};
|
|
52
|
-
for (const key of Object.keys(
|
|
53
|
-
const
|
|
54
|
-
if (
|
|
46
|
+
for (const key of Object.keys(normalized)) {
|
|
47
|
+
const sanitized = sanitizeAttributeValue(key, normalized[key]);
|
|
48
|
+
if (sanitized === undefined) continue;
|
|
55
49
|
const rnKey = toRnPropName(key);
|
|
56
50
|
if (!rnKey) continue;
|
|
57
|
-
out[rnKey] = toRnPropValue(rnKey, String(
|
|
51
|
+
out[rnKey] = toRnPropValue(rnKey, String(sanitized), tag);
|
|
58
52
|
}
|
|
59
53
|
return out;
|
|
60
54
|
}
|
|
61
55
|
|
|
62
56
|
/**
|
|
63
|
-
* Renders a (
|
|
57
|
+
* Renders a (materialized) PxNode tree to react-native-svg elements.
|
|
64
58
|
* Mirrors the web `renderNode` contract: unsupported/dangerous tags are
|
|
65
59
|
* skipped with a warning, never a crash.
|
|
60
|
+
* @internal
|
|
66
61
|
*/
|
|
67
62
|
export function renderRnNode(node: PxNode, opts: RenderRnNodeOptions = {}, key?: string | number): ReactElement | null {
|
|
68
63
|
if (!node) return null;
|
|
@@ -92,23 +87,22 @@ export function renderRnNode(node: PxNode, opts: RenderRnNodeOptions = {}, key?:
|
|
|
92
87
|
const rnProps = toRnProps(props, opts.warnings, tag);
|
|
93
88
|
if (domType !== undefined) rnProps.type = domType;
|
|
94
89
|
|
|
95
|
-
// `node.style` —
|
|
96
|
-
//
|
|
97
|
-
// applied as ordinary props (the same names, e.g. `fill`, `strokeWidth`).
|
|
90
|
+
// `node.style` — an inline record of declarations. react-native-svg has no CSS, so
|
|
91
|
+
// they are applied as ordinary props (the same names, e.g. `fill`, `strokeWidth`).
|
|
98
92
|
// Explicit attributes on the node win over the style block.
|
|
99
|
-
const resolved =
|
|
93
|
+
const resolved = style;
|
|
100
94
|
if (resolved) {
|
|
101
95
|
for (const [k, v] of Object.entries(resolved)) {
|
|
102
96
|
const rnKey = toRnPropName(k);
|
|
103
97
|
if (!rnKey || rnKey in rnProps) continue;
|
|
104
|
-
const
|
|
105
|
-
if (
|
|
106
|
-
rnProps[rnKey] = toRnPropValue(rnKey, String(
|
|
98
|
+
const sanitized = sanitizeAttributeValue(rnKey, v);
|
|
99
|
+
if (sanitized === undefined) continue;
|
|
100
|
+
rnProps[rnKey] = toRnPropValue(rnKey, String(sanitized), tag);
|
|
107
101
|
}
|
|
108
102
|
}
|
|
109
103
|
|
|
110
|
-
// Text content: wire nodes carry it
|
|
111
|
-
const textContent: string | undefined = props[
|
|
104
|
+
// Text content: wire nodes carry it in `textContent` — the one key.
|
|
105
|
+
const textContent: string | undefined = props[TEXT_CONTENT_ATTR];
|
|
112
106
|
|
|
113
107
|
let childElements: ReactNode = undefined;
|
|
114
108
|
if (Array.isArray(children) && children.length > 0) {
|
package/src/PxRnSafety.ts
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* Licensed under the MIT License. See the LICENSE file in the project root for details.
|
|
4
4
|
*---------------------------------------------------------------------------------------*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { type PxNode } from '@pixodesk/svg-animator-core';
|
|
7
|
+
import { deepClone, generateUniqueId } from '@pixodesk/svg-animator-core/internal';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Workarounds for defects in react-native-svg's NATIVE renderer that would
|
|
@@ -57,6 +58,7 @@ function forEachNode(node: PxNode | undefined, visit: (n: PxNode, parent?: PxNod
|
|
|
57
58
|
* has zero length and nothing changes visually at all.
|
|
58
59
|
*
|
|
59
60
|
* Returns the document unchanged — the same object — when nothing matches.
|
|
61
|
+
* @internal
|
|
60
62
|
*/
|
|
61
63
|
export function openClosedTextPathTargets(doc: PxNode, warnings?: Array<string>): PxNode {
|
|
62
64
|
// Cheap pre-check: the overwhelming majority of documents have no textPath.
|
package/src/PxRnTracks.test.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*---------------------------------------------------------------------------------------*/
|
|
5
5
|
|
|
6
6
|
import { describe, expect, it } from 'vitest';
|
|
7
|
-
import { generateNewIds,
|
|
7
|
+
import { generateNewIds, materializeAllInTree, PxTimelineEngine, type PxAnimatedSvgDocument } from '@pixodesk/svg-animator-core';
|
|
8
8
|
import { compileTracks, sampleProps } from './PxRnTracks';
|
|
9
9
|
import { toRnPropName } from './PxRnPropNames';
|
|
10
10
|
|
|
@@ -37,8 +37,8 @@ function makeDoc(): PxAnimatedSvgDocument {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
function compile(doc = makeDoc(), opts?: Parameters<typeof compileTracks>[1]) {
|
|
40
|
-
const
|
|
41
|
-
return compileTracks(
|
|
40
|
+
const materialized = generateNewIds(materializeAllInTree(doc, PxTimelineEngine.js));
|
|
41
|
+
return compileTracks(materialized, opts);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
describe('compileTracks', () => {
|
|
@@ -76,7 +76,7 @@ describe('compileTracks', () => {
|
|
|
76
76
|
expect(+rect.props.strokeWidth[rect.props.strokeWidth.length - 1]).toBe(5);
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
-
it('samples
|
|
79
|
+
it('samples color props as rgba strings', () => {
|
|
80
80
|
const tracks = compile();
|
|
81
81
|
const rect = tracks.elements.find(e => 'fill' in e.props)!;
|
|
82
82
|
expect(String(rect.props.fill[0])).toMatch(/^rgba\(/);
|
|
@@ -156,8 +156,8 @@ describe('length-list props (stroke-dasharray)', () => {
|
|
|
156
156
|
effects: { strokeTrim: { range: { keyframes: [{ time: 0, value: [0, 0.1] }, { time: 1000, value: [0, 1] }] } } },
|
|
157
157
|
}],
|
|
158
158
|
};
|
|
159
|
-
const
|
|
160
|
-
const tracks = compileTracks(
|
|
159
|
+
const materialized = generateNewIds(materializeAllInTree(doc, PxTimelineEngine.native));
|
|
160
|
+
const tracks = compileTracks(materialized);
|
|
161
161
|
const el = tracks.elements.find(e => 'strokeDasharray' in e.props)!;
|
|
162
162
|
expect(el).toBeDefined();
|
|
163
163
|
const first = el.props.strokeDasharray[0];
|
|
@@ -167,7 +167,7 @@ describe('length-list props (stroke-dasharray)', () => {
|
|
|
167
167
|
});
|
|
168
168
|
});
|
|
169
169
|
|
|
170
|
-
describe('animated <use> flattening (waapi
|
|
170
|
+
describe('animated <use> flattening (waapi materialization)', () => {
|
|
171
171
|
it('inlines animated <use> clones so no live references remain', () => {
|
|
172
172
|
const doc: PxAnimatedSvgDocument = {
|
|
173
173
|
type: 'svg', viewBox: '0 0 300 200',
|
|
@@ -181,12 +181,12 @@ describe('animated <use> flattening (waapi materialisation)', () => {
|
|
|
181
181
|
{ type: 'use', id: 'u2', href: '#sym', x: 80, effects: { clone: { source: '#sym', retime: { start: -600 } } } },
|
|
182
182
|
],
|
|
183
183
|
};
|
|
184
|
-
const
|
|
184
|
+
const materialized = generateNewIds(materializeAllInTree(doc, PxTimelineEngine.native));
|
|
185
185
|
const countUse = (n: any): number =>
|
|
186
186
|
(n.type === 'use' ? 1 : 0) + (n.children || []).reduce((s: number, c: any) => s + countUse(c), 0);
|
|
187
|
-
expect(countUse(
|
|
187
|
+
expect(countUse(materialized)).toBe(0);
|
|
188
188
|
|
|
189
|
-
const tracks = compileTracks(
|
|
189
|
+
const tracks = compileTracks(materialized);
|
|
190
190
|
const cyTracks = tracks.elements.filter(e => 'cy' in e.props);
|
|
191
191
|
expect(cyTracks.length).toBe(2);
|
|
192
192
|
expect(cyTracks[0].props.cy[0]).not.toBe(cyTracks[1].props.cy[0]);
|
|
@@ -195,9 +195,9 @@ describe('animated <use> flattening (waapi materialisation)', () => {
|
|
|
195
195
|
|
|
196
196
|
describe('compileTracks platform gating', () => {
|
|
197
197
|
/** The DEFAULT is the DOM form. react-native-web passes values straight to
|
|
198
|
-
* the DOM, where a matrix array
|
|
198
|
+
* the DOM, where a matrix array serializes to `transform="1,0,0,1,x,y"` —
|
|
199
199
|
* invalid, so the element silently stops moving. Web is the priority
|
|
200
|
-
*
|
|
200
|
+
* behavior; native is the one that has to opt in. */
|
|
201
201
|
it('samples transforms as SVG STRINGS by default', () => {
|
|
202
202
|
const tracks = compile();
|
|
203
203
|
const g = tracks.elements.find(e => 'transform' in e.props)!;
|
package/src/PxRnTracks.ts
CHANGED
|
@@ -3,15 +3,8 @@
|
|
|
3
3
|
* Licensed under the MIT License. See the LICENSE file in the project root for details.
|
|
4
4
|
*---------------------------------------------------------------------------------------*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
getAnimatorConfig,
|
|
9
|
-
getNormalisedBindings,
|
|
10
|
-
DEFAULT_DURATION_MS,
|
|
11
|
-
PxTimelineEngine,
|
|
12
|
-
type PxAnimatedSvgDocument,
|
|
13
|
-
type PxAnimationDefinition,
|
|
14
|
-
} from '@pixodesk/svg-animator-core';
|
|
6
|
+
import { calcAnimationValues, getAnimatorConfig, getNormalizedBindings, PxTimelineEngine, type PxAnimatedSvgDocument, type PxAnimationDefinition } from '@pixodesk/svg-animator-core';
|
|
7
|
+
import { DEFAULT_DURATION_MS } from '@pixodesk/svg-animator-core/internal';
|
|
15
8
|
import { toRnPropName, toRnPropValue } from './PxRnPropNames';
|
|
16
9
|
|
|
17
10
|
/**
|
|
@@ -20,8 +13,9 @@ import { toRnPropName, toRnPropValue } from './PxRnPropNames';
|
|
|
20
13
|
* The compiler densely samples every animated property through core's
|
|
21
14
|
* `calcAnimationValues` — the exact function the web frames engine renders
|
|
22
15
|
* with — so RN playback is value-identical to the web player. Easing, loops,
|
|
23
|
-
* transform composition,
|
|
16
|
+
* transform composition, color interpolation and path morphing are all baked
|
|
24
17
|
* into the samples at compile time; the UI-thread worklet only indexes arrays.
|
|
18
|
+
* @internal
|
|
25
19
|
*/
|
|
26
20
|
export interface PxElementTracks {
|
|
27
21
|
/** Element id (after id regeneration). */
|
|
@@ -30,6 +24,7 @@ export interface PxElementTracks {
|
|
|
30
24
|
props: Record<string, Array<string | number | Array<number>>>;
|
|
31
25
|
}
|
|
32
26
|
|
|
27
|
+
/** @internal */
|
|
33
28
|
export interface PxCompiledTracks {
|
|
34
29
|
/** Per-iteration duration, ms. */
|
|
35
30
|
duration: number;
|
|
@@ -51,6 +46,7 @@ export interface PxCompiledTracks {
|
|
|
51
46
|
elements: Array<PxElementTracks>;
|
|
52
47
|
}
|
|
53
48
|
|
|
49
|
+
/** @internal */
|
|
54
50
|
export interface CompileTracksOptions {
|
|
55
51
|
/** Target sample rate, samples/second. Default 60 (one per frame). */
|
|
56
52
|
sampleRate?: number;
|
|
@@ -70,8 +66,9 @@ export interface CompileTracksOptions {
|
|
|
70
66
|
|
|
71
67
|
|
|
72
68
|
/**
|
|
73
|
-
* Compiles a
|
|
69
|
+
* Compiles a MATERIALIZED document (run `materializeAllInTree(doc, 'frames')`
|
|
74
70
|
* + `generateNewIds` first) into densely sampled per-element tracks.
|
|
71
|
+
* @internal
|
|
75
72
|
*/
|
|
76
73
|
export function compileTracks(doc: PxAnimatedSvgDocument, opts?: CompileTracksOptions): PxCompiledTracks {
|
|
77
74
|
const config = getAnimatorConfig(doc) || {};
|
|
@@ -99,7 +96,7 @@ export function compileTracks(doc: PxAnimatedSvgDocument, opts?: CompileTracksOp
|
|
|
99
96
|
};
|
|
100
97
|
indexTags(doc);
|
|
101
98
|
|
|
102
|
-
const bindings =
|
|
99
|
+
const bindings = getNormalizedBindings(doc, PxTimelineEngine.js) || [];
|
|
103
100
|
|
|
104
101
|
const elements: Array<PxElementTracks> = [];
|
|
105
102
|
for (const binding of bindings) {
|
|
@@ -173,6 +170,7 @@ export const NATIVE_PROP_NAME: Record<string, string> = {
|
|
|
173
170
|
* reanimated animated-props path of a real device. Props that are applied by
|
|
174
171
|
* re-rendering through React (and therefore still pass through react-native-svg's
|
|
175
172
|
* JS layer), and every value on the web, must keep the wire name.
|
|
173
|
+
* @internal
|
|
176
174
|
*/
|
|
177
175
|
export function sampleProps(
|
|
178
176
|
tracks: PxElementTracks,
|
package/src/PxRnTypeMap.ts
CHANGED
|
@@ -63,6 +63,7 @@ import type { ComponentType } from 'react';
|
|
|
63
63
|
*
|
|
64
64
|
* Keys are the wire-format `node.type` values, which follow SVG's own casing
|
|
65
65
|
* (`clipPath`, `feGaussianBlur`, `linearGradient`, …).
|
|
66
|
+
* @internal
|
|
66
67
|
*/
|
|
67
68
|
export const RN_SVG_COMPONENTS: Record<string, ComponentType<any>> = {
|
|
68
69
|
// Root & containers
|
package/src/index.ts
CHANGED
|
@@ -6,12 +6,3 @@
|
|
|
6
6
|
export { PixodeskSvgAnimator, default } from './PixodeskSvgAnimator';
|
|
7
7
|
export type { PixodeskSvgAnimatorProps, RnAnimatorApi } from './PixodeskSvgAnimator';
|
|
8
8
|
|
|
9
|
-
export { renderRnNode, toRnProps } from './PxRnRender';
|
|
10
|
-
export { PxRnErrorBoundary, type PxRnErrorBoundaryProps } from './PxRnErrorBoundary';
|
|
11
|
-
export { openClosedTextPathTargets } from './PxRnSafety';
|
|
12
|
-
export type { RenderRnNodeOptions } from './PxRnRender';
|
|
13
|
-
|
|
14
|
-
export { compileTracks, sampleProps } from './PxRnTracks';
|
|
15
|
-
export type { CompileTracksOptions, PxCompiledTracks, PxElementTracks } from './PxRnTracks';
|
|
16
|
-
|
|
17
|
-
export { RN_SVG_COMPONENTS, toRnPropName } from './PxRnTypeMap';
|