@pixodesk/svg-animator-rn 1.0.34 → 1.0.39
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 +38 -30
- package/dist/index.cjs +117 -75
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -62
- package/dist/index.d.ts +40 -62
- package/dist/index.js +130 -73
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/PixodeskSvgAnimator.tsx +176 -162
- package/src/PxRnErrorBoundary.tsx +5 -1
- package/src/PxRnMatrix.test.ts +2 -2
- package/src/PxRnMatrix.ts +1 -1
- package/src/PxRnPropNames.ts +2 -2
- package/src/PxRnRender.tsx +14 -15
- package/src/PxRnTracks.test.ts +16 -16
- package/src/PxRnTracks.ts +5 -5
package/src/PxRnRender.tsx
CHANGED
|
@@ -6,9 +6,8 @@
|
|
|
6
6
|
import {
|
|
7
7
|
getNormalizedProps,
|
|
8
8
|
resolveStyle,
|
|
9
|
-
|
|
9
|
+
sanitizeAttributeValue,
|
|
10
10
|
DISALLOWED_SVG_TAGS_LOWER,
|
|
11
|
-
TEXT_ATTR,
|
|
12
11
|
TEXT_CONTENT_ATTR,
|
|
13
12
|
type PxDefs,
|
|
14
13
|
type PxNode,
|
|
@@ -42,25 +41,25 @@ export interface RenderRnNodeOptions {
|
|
|
42
41
|
}
|
|
43
42
|
|
|
44
43
|
/**
|
|
45
|
-
* Converts core-
|
|
46
|
-
* naming,
|
|
44
|
+
* Converts core-normalized wire props into react-native-svg props: RN prop
|
|
45
|
+
* naming, sanitization (same security rules as the web renderer), numeric
|
|
47
46
|
* coercion where possible.
|
|
48
47
|
*/
|
|
49
48
|
export function toRnProps(props: Record<string, any>, warnings?: Array<string>, tag?: string): Record<string, any> {
|
|
50
|
-
const
|
|
49
|
+
const normalized = getNormalizedProps(props);
|
|
51
50
|
const out: Record<string, any> = {};
|
|
52
|
-
for (const key of Object.keys(
|
|
53
|
-
const
|
|
54
|
-
if (
|
|
51
|
+
for (const key of Object.keys(normalized)) {
|
|
52
|
+
const sanitized = sanitizeAttributeValue(key, normalized[key]);
|
|
53
|
+
if (sanitized === undefined) continue;
|
|
55
54
|
const rnKey = toRnPropName(key);
|
|
56
55
|
if (!rnKey) continue;
|
|
57
|
-
out[rnKey] = toRnPropValue(rnKey, String(
|
|
56
|
+
out[rnKey] = toRnPropValue(rnKey, String(sanitized), tag);
|
|
58
57
|
}
|
|
59
58
|
return out;
|
|
60
59
|
}
|
|
61
60
|
|
|
62
61
|
/**
|
|
63
|
-
* Renders a (
|
|
62
|
+
* Renders a (materialized) PxNode tree to react-native-svg elements.
|
|
64
63
|
* Mirrors the web `renderNode` contract: unsupported/dangerous tags are
|
|
65
64
|
* skipped with a warning, never a crash.
|
|
66
65
|
*/
|
|
@@ -101,14 +100,14 @@ export function renderRnNode(node: PxNode, opts: RenderRnNodeOptions = {}, key?:
|
|
|
101
100
|
for (const [k, v] of Object.entries(resolved)) {
|
|
102
101
|
const rnKey = toRnPropName(k);
|
|
103
102
|
if (!rnKey || rnKey in rnProps) continue;
|
|
104
|
-
const
|
|
105
|
-
if (
|
|
106
|
-
rnProps[rnKey] = toRnPropValue(rnKey, String(
|
|
103
|
+
const sanitized = sanitizeAttributeValue(rnKey, v);
|
|
104
|
+
if (sanitized === undefined) continue;
|
|
105
|
+
rnProps[rnKey] = toRnPropValue(rnKey, String(sanitized), tag);
|
|
107
106
|
}
|
|
108
107
|
}
|
|
109
108
|
|
|
110
|
-
// Text content: wire nodes carry it
|
|
111
|
-
const textContent: string | undefined = props[
|
|
109
|
+
// Text content: wire nodes carry it in `textContent` — the one key.
|
|
110
|
+
const textContent: string | undefined = props[TEXT_CONTENT_ATTR];
|
|
112
111
|
|
|
113
112
|
let childElements: ReactNode = undefined;
|
|
114
113
|
if (Array.isArray(children) && children.length > 0) {
|
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
|
|
|
@@ -12,7 +12,7 @@ function makeDoc(): PxAnimatedSvgDocument {
|
|
|
12
12
|
return {
|
|
13
13
|
type: 'svg',
|
|
14
14
|
viewBox: '0 0 200 200',
|
|
15
|
-
animator: {
|
|
15
|
+
animator: { timeline: { engine: 'js', duration: 1000, iterations: 2, direction: 'alternate' } },
|
|
16
16
|
children: [
|
|
17
17
|
{
|
|
18
18
|
type: 'rect',
|
|
@@ -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\(/);
|
|
@@ -150,14 +150,14 @@ describe('length-list props (stroke-dasharray)', () => {
|
|
|
150
150
|
it('compiles stroke-dasharray into number arrays (rn-svg native shape)', () => {
|
|
151
151
|
const doc: PxAnimatedSvgDocument = {
|
|
152
152
|
type: 'svg', viewBox: '0 0 100 100',
|
|
153
|
-
animator: {
|
|
153
|
+
animator: { timeline: { engine: 'js', duration: 1000 } },
|
|
154
154
|
children: [{
|
|
155
155
|
type: 'path', id: 'p', d: 'M 0 50 L 100 50', stroke: '#000', fill: 'none',
|
|
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,26 +167,26 @@ 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',
|
|
174
|
-
animator: {
|
|
174
|
+
animator: { timeline: { engine: 'js', duration: 2000 } },
|
|
175
175
|
children: [
|
|
176
176
|
{ type: 'defs', children: [{ type: 'g', id: 'sym', children: [{
|
|
177
177
|
type: 'circle', id: 'c', cx: 30, cy: 40, r: 16, fill: '#f59e0b',
|
|
178
178
|
animate: { cy: { keyframes: [{ time: 0, value: 40 }, { time: 2000, value: 160 }] } },
|
|
179
179
|
}] }] },
|
|
180
180
|
{ type: 'use', id: 'u1', href: '#sym' },
|
|
181
|
-
{ type: 'use', id: 'u2', href: '#sym', x: 80, effects: { clone: {
|
|
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
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
import {
|
|
7
7
|
calcAnimationValues,
|
|
8
8
|
getAnimatorConfig,
|
|
9
|
-
|
|
9
|
+
getNormalizedBindings,
|
|
10
10
|
DEFAULT_DURATION_MS,
|
|
11
|
-
|
|
11
|
+
PxTimelineEngine,
|
|
12
12
|
type PxAnimatedSvgDocument,
|
|
13
13
|
type PxAnimationDefinition,
|
|
14
14
|
} from '@pixodesk/svg-animator-core';
|
|
@@ -20,7 +20,7 @@ import { toRnPropName, toRnPropValue } from './PxRnPropNames';
|
|
|
20
20
|
* The compiler densely samples every animated property through core's
|
|
21
21
|
* `calcAnimationValues` — the exact function the web frames engine renders
|
|
22
22
|
* with — so RN playback is value-identical to the web player. Easing, loops,
|
|
23
|
-
* transform composition,
|
|
23
|
+
* transform composition, color interpolation and path morphing are all baked
|
|
24
24
|
* into the samples at compile time; the UI-thread worklet only indexes arrays.
|
|
25
25
|
*/
|
|
26
26
|
export interface PxElementTracks {
|
|
@@ -70,7 +70,7 @@ export interface CompileTracksOptions {
|
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
/**
|
|
73
|
-
* Compiles a
|
|
73
|
+
* Compiles a MATERIALIZED document (run `materializeAllInTree(doc, 'frames')`
|
|
74
74
|
* + `generateNewIds` first) into densely sampled per-element tracks.
|
|
75
75
|
*/
|
|
76
76
|
export function compileTracks(doc: PxAnimatedSvgDocument, opts?: CompileTracksOptions): PxCompiledTracks {
|
|
@@ -99,7 +99,7 @@ export function compileTracks(doc: PxAnimatedSvgDocument, opts?: CompileTracksOp
|
|
|
99
99
|
};
|
|
100
100
|
indexTags(doc);
|
|
101
101
|
|
|
102
|
-
const bindings =
|
|
102
|
+
const bindings = getNormalizedBindings(doc, PxTimelineEngine.js) || [];
|
|
103
103
|
|
|
104
104
|
const elements: Array<PxElementTracks> = [];
|
|
105
105
|
for (const binding of bindings) {
|