@symbiote-native/engine 0.4.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.md +39 -14
  2. package/android/CMakeLists.txt +51 -0
  3. package/android/build.gradle +90 -0
  4. package/android/src/main/AndroidManifest.xml +1 -0
  5. package/android/src/main/cpp/SymbioteEngineJni.cpp +72 -0
  6. package/android/src/main/java/dev/symbiotenative/engine/SymbioteEngineModule.kt +43 -0
  7. package/android/src/main/java/dev/symbiotenative/engine/SymbioteEnginePackage.kt +35 -0
  8. package/build/accessibility-info/shared.js +1 -1
  9. package/build/accessibility-props.d.ts +1 -7
  10. package/build/accessibility-props.js +22 -21
  11. package/build/animated/animations/composition.d.ts +1 -1
  12. package/build/animated/animations/composition.js +18 -4
  13. package/build/animated/easing.d.ts +3 -2
  14. package/build/animated/easing.js +17 -88
  15. package/build/animated/event.js +6 -1
  16. package/build/animated/graph.d.ts +2 -0
  17. package/build/animated/graph.js +14 -0
  18. package/build/animated/host-binding.d.ts +39 -0
  19. package/build/animated/host-binding.js +278 -0
  20. package/build/animated/index.d.ts +1 -1
  21. package/build/animated/leaf-lifecycle.js +10 -22
  22. package/build/animated/mock.d.ts +1 -19
  23. package/build/animated/props.js +1 -1
  24. package/build/animated/rgba.js +16 -50
  25. package/build/events/index.js +123 -33
  26. package/build/fabric-props.d.ts +1 -1
  27. package/build/fabric-props.js +129 -182
  28. package/build/fabric.d.ts +10 -0
  29. package/build/fabric.js +40 -0
  30. package/build/host-access.d.ts +125 -0
  31. package/build/host-access.js +280 -0
  32. package/build/host-behavior.d.ts +107 -7
  33. package/build/host-behavior.js +309 -31
  34. package/build/image-source-write.d.ts +16 -0
  35. package/build/image-source-write.js +65 -0
  36. package/build/imperative.d.ts +49 -0
  37. package/build/imperative.js +258 -0
  38. package/build/index.d.ts +18 -10
  39. package/build/index.js +65 -10
  40. package/build/mutation-buffer.d.ts +222 -0
  41. package/build/mutation-buffer.js +491 -0
  42. package/build/native-engine.d.ts +182 -0
  43. package/build/native-engine.js +178 -0
  44. package/build/native-tree-host.d.ts +25 -0
  45. package/build/native-tree-host.js +66 -0
  46. package/build/node.d.ts +191 -60
  47. package/build/node.js +1034 -327
  48. package/build/pan-responder/index.d.ts +2 -2
  49. package/build/pan-responder/index.js +37 -56
  50. package/build/platform-color/index.d.ts +1 -1
  51. package/build/platform-color/index.js +11 -4
  52. package/build/process-background-image/index.js +30 -566
  53. package/build/process-background-longhands.d.ts +4 -0
  54. package/build/process-background-longhands.js +44 -0
  55. package/build/process-box-shadow/index.js +23 -187
  56. package/build/process-filter.js +27 -300
  57. package/build/process-transform/index.d.ts +1 -1
  58. package/build/process-transform/index.js +25 -107
  59. package/build/process-transform-origin/index.d.ts +1 -1
  60. package/build/process-transform-origin/index.js +29 -102
  61. package/build/registry.d.ts +36 -0
  62. package/build/registry.js +73 -0
  63. package/build/sound-manager/index.d.ts +3 -0
  64. package/build/sound-manager/index.js +36 -0
  65. package/build/structured-style.d.ts +10 -0
  66. package/build/structured-style.js +180 -0
  67. package/build/style-registry/index.d.ts +14 -0
  68. package/build/style-registry/index.js +60 -11
  69. package/build/styles.d.ts +5 -1
  70. package/build/surface.d.ts +31 -2
  71. package/build/surface.js +138 -42
  72. package/build/text-input-state.d.ts +1 -0
  73. package/build/text-input-state.js +17 -3
  74. package/build/tree-host.d.ts +307 -0
  75. package/build/tree-host.js +211 -0
  76. package/build/view-config.js +4 -4
  77. package/codegen-specs/NativeSymbioteEngine.ts +27 -0
  78. package/cpp/SymbioteDebug.cpp +51 -0
  79. package/cpp/SymbioteDebug.h +54 -0
  80. package/cpp/SymbioteEngineBindings.cpp +232 -0
  81. package/cpp/SymbioteEngineBindings.h +59 -0
  82. package/cpp/SymbioteFabricProps.cpp +2619 -0
  83. package/cpp/SymbioteFabricProps.h +223 -0
  84. package/cpp/SymbioteTree.cpp +2478 -0
  85. package/cpp/SymbioteTree.h +257 -0
  86. package/ios/SymbioteEngineModule.h +25 -0
  87. package/ios/SymbioteEngineModule.mm +44 -0
  88. package/package.json +31 -3
  89. package/react-native.config.cjs +23 -0
  90. package/symbiote-engine.podspec +42 -0
  91. package/build/animated/bezier.d.ts +0 -1
  92. package/build/animated/bezier.js +0 -102
  93. package/build/commit.d.ts +0 -49
  94. package/build/commit.js +0 -1030
  95. package/build/tags.d.ts +0 -2
  96. package/build/tags.js +0 -40
@@ -1,189 +1,25 @@
1
- // JS-side port of RN's processBoxShadow (Libraries/StyleSheet/processBoxShadow.js).
2
- // RN registers `boxShadow` with enableNativeCSSParsing(), which DEFAULTS TO FALSE, so
3
- // the stock path parses the CSS string / structured array in JS and sends only the
4
- // processed array to native; Fabric's C++ never sees the raw string. symbiote was
5
- // forwarding the raw value, which native (CSS parsing off) silently ignores: shadow
6
- // rendered nothing. This restores the missing JS parse + per-color processColor.
7
- import { isOpaqueColorValue, processColor } from '../platform-color/index.js';
8
- import { dlog } from '../debug.js';
9
- // RN processBoxShadow.js:16-19: split args only on the delimiters that are NOT inside
10
- // a parenthesized color like rgba(0,0,0,1).
11
- const COMMA_SPLIT_REGEX = /,(?![^()]*\))/;
12
- const WHITESPACE_SPLIT_REGEX = /\s+(?![^(]*\))/;
13
- const LENGTH_PARSE_REGEX = /^([+-]?\d*\.?\d+)(px)?$/;
14
- const NEWLINE_REGEX = /\n/g;
15
- // A length field may be a CSS string ("22px") or a number; resolve to a number or null.
16
- // Mirrors RN's `typeof x === 'string' ? parseLength(x) : x`, but typed from `unknown`.
17
- function resolveLength(value) {
18
- if (typeof value === 'string')
19
- return parseLength(value);
20
- if (typeof value === 'number')
21
- return value;
22
- return null;
23
- }
24
- // A shadow color may already be a platform int (number) or undefined; processColor only
25
- // types CSS strings / opaque PlatformColor objects. A number is passed through (already
26
- // resolved), anything else (incl. undefined) is null, i.e. unprocessable, like RN.
27
- function processShadowColor(color) {
28
- if (typeof color === 'number')
29
- return color;
30
- if (typeof color === 'string' || isOpaqueColorValue(color))
31
- return processColor(color);
32
- return null;
33
- }
34
- // RN processBoxShadow.js:30-111. Returns [] on any invalid primitive (matches web: an
35
- // invalid box-shadow paints nothing rather than a partial shadow).
1
+ // RN's own processBoxShadow, imported rather than ported.
2
+ //
3
+ // Why it is needed at all: RN registers `boxShadow` with `nativeCSSParsing ? true : {process:
4
+ // processBoxShadow}`, and enableNativeCSSParsing() defaults to false - so the stock path parses
5
+ // the CSS string / structured array in JS and sends native only the processed array. Symbiote
6
+ // forwarded the raw value, which native silently ignores with CSS parsing off: the shadow simply
7
+ // did not paint.
8
+ //
9
+ // Why no try/catch, unlike processTransform's wrapper: upstream has zero `invariant` and zero
10
+ // `throw` sites here. It answers `[]` for anything it cannot parse - web semantics, where an
11
+ // invalid box-shadow paints nothing rather than a partial shadow. A guard that can never fire is
12
+ // worse than none: it reads as verified and has never run.
13
+ //
14
+ // What changed by importing: colour now goes through RN's own `processColor` (upstream imports it
15
+ // directly at processBoxShadow.js:14) instead of our injected `setColorProcessor` seam. On a real
16
+ // host those are the same function - bootstrapHost injects exactly `processColor` and nothing in
17
+ // the repo passes a custom one. The seam still governs every OTHER colour prop; it simply no
18
+ // longer governs the colours inside a box-shadow.
19
+ //
20
+ // @ts-expect-error - untyped Flow source. Metro compiles it; vitest.config.ts strips the types.
21
+ import processBoxShadowUpstream from 'react-native/Libraries/StyleSheet/processBoxShadow';
36
22
  export function processBoxShadow(rawBoxShadows) {
37
- const result = [];
38
- if (rawBoxShadows == null) {
39
- return result;
40
- }
41
- const boxShadowList = typeof rawBoxShadows === 'string'
42
- ? parseBoxShadowString(rawBoxShadows.replace(NEWLINE_REGEX, ' '))
43
- : rawBoxShadows;
44
- for (const rawBoxShadow of boxShadowList) {
45
- const parsedBoxShadow = { offsetX: 0, offsetY: 0 };
46
- for (const arg of Object.keys(rawBoxShadow)) {
47
- switch (arg) {
48
- case 'offsetX': {
49
- const value = resolveLength(rawBoxShadow.offsetX);
50
- if (value == null) {
51
- dlog(`processBoxShadow reject: invalid offsetX "${String(rawBoxShadow.offsetX)}"`);
52
- return [];
53
- }
54
- parsedBoxShadow.offsetX = value;
55
- break;
56
- }
57
- case 'offsetY': {
58
- const value = resolveLength(rawBoxShadow.offsetY);
59
- if (value == null) {
60
- dlog(`processBoxShadow reject: invalid offsetY "${String(rawBoxShadow.offsetY)}"`);
61
- return [];
62
- }
63
- parsedBoxShadow.offsetY = value;
64
- break;
65
- }
66
- case 'spreadDistance': {
67
- const value = resolveLength(rawBoxShadow.spreadDistance);
68
- if (value == null) {
69
- dlog(`processBoxShadow reject: invalid spreadDistance`);
70
- return [];
71
- }
72
- parsedBoxShadow.spreadDistance = value;
73
- break;
74
- }
75
- case 'blurRadius': {
76
- const value = resolveLength(rawBoxShadow.blurRadius);
77
- if (value == null || value < 0) {
78
- dlog(`processBoxShadow reject: invalid blurRadius`);
79
- return [];
80
- }
81
- parsedBoxShadow.blurRadius = value;
82
- break;
83
- }
84
- case 'color': {
85
- const color = processShadowColor(rawBoxShadow.color);
86
- if (color == null) {
87
- dlog(`processBoxShadow reject: unprocessable color`);
88
- return [];
89
- }
90
- parsedBoxShadow.color = color;
91
- break;
92
- }
93
- case 'inset':
94
- if (typeof rawBoxShadow.inset === 'boolean')
95
- parsedBoxShadow.inset = rawBoxShadow.inset;
96
- }
97
- }
98
- result.push(parsedBoxShadow);
99
- }
100
- return result;
101
- }
102
- // RN processBoxShadow.js:113-197. Walks each comma-separated shadow, classifying each
103
- // whitespace-separated arg as a color, the `inset` keyword, or a length by position.
104
- function parseBoxShadowString(rawBoxShadows) {
105
- const result = [];
106
- for (const rawBoxShadow of rawBoxShadows
107
- .split(COMMA_SPLIT_REGEX)
108
- .map(bS => bS.trim())
109
- .filter(bS => bS !== '')) {
110
- const boxShadow = { offsetX: 0, offsetY: 0 };
111
- let offsetX;
112
- let offsetY;
113
- let keywordDetectedAfterLength = false;
114
- let lengthCount = 0;
115
- const args = rawBoxShadow.split(WHITESPACE_SPLIT_REGEX);
116
- for (const arg of args) {
117
- const processedColor = processColor(arg);
118
- if (processedColor != null) {
119
- if (boxShadow.color != null) {
120
- return [];
121
- }
122
- if (offsetX != null) {
123
- keywordDetectedAfterLength = true;
124
- }
125
- boxShadow.color = arg;
126
- continue;
127
- }
128
- if (arg === 'inset') {
129
- if (boxShadow.inset != null) {
130
- return [];
131
- }
132
- if (offsetX != null) {
133
- keywordDetectedAfterLength = true;
134
- }
135
- boxShadow.inset = true;
136
- continue;
137
- }
138
- switch (lengthCount) {
139
- case 0:
140
- offsetX = arg;
141
- lengthCount++;
142
- break;
143
- case 1:
144
- if (keywordDetectedAfterLength) {
145
- return [];
146
- }
147
- offsetY = arg;
148
- lengthCount++;
149
- break;
150
- case 2:
151
- if (keywordDetectedAfterLength) {
152
- return [];
153
- }
154
- boxShadow.blurRadius = arg;
155
- lengthCount++;
156
- break;
157
- case 3:
158
- if (keywordDetectedAfterLength) {
159
- return [];
160
- }
161
- boxShadow.spreadDistance = arg;
162
- lengthCount++;
163
- break;
164
- default:
165
- return [];
166
- }
167
- }
168
- if (offsetX == null || offsetY == null) {
169
- return [];
170
- }
171
- boxShadow.offsetX = offsetX;
172
- boxShadow.offsetY = offsetY;
173
- result.push(boxShadow);
174
- }
175
- return result;
176
- }
177
- // RN processBoxShadow.js:199-214. Accepts a unitless 0 or any `<n>px`; rejects a
178
- // non-zero number with no unit (CSS requires a unit on lengths).
179
- function parseLength(length) {
180
- const match = LENGTH_PARSE_REGEX.exec(length);
181
- if (!match) {
182
- return null;
183
- }
184
- const value = parseFloat(match[1]);
185
- if (match[2] == null && value !== 0) {
186
- return null;
187
- }
188
- return value;
23
+ const parsed = processBoxShadowUpstream(rawBoxShadows);
24
+ return Array.isArray(parsed) ? parsed : [];
189
25
  }
@@ -1,305 +1,32 @@
1
- // JS-side port of RN's processFilter (Libraries/StyleSheet/processFilter.js).
2
- // Same root cause as boxShadow: `filter` registers with enableNativeCSSParsing()
3
- // (DEFAULT FALSE), so RN parses the CSS string / array in JS and forwards only the
4
- // structured result. symbiote forwarded the raw value; the array form already worked
5
- // (Fabric accepts it raw, `filter:[{brightness:0.5}]` was device-verified), but the
6
- // CSS-string form and drop-shadow color processing were missing. This restores them.
7
- import { isOpaqueColorValue, processColor } from './platform-color/index.js';
1
+ // RN's own processFilter, imported rather than ported.
2
+ //
3
+ // Why it is needed at all: RN registers `filter` with `nativeCSSParsing ? true : {process:
4
+ // processFilter}`, and enableNativeCSSParsing() defaults to false - so the stock path parses the
5
+ // CSS string / structured array in JS and native never sees the raw string.
6
+ //
7
+ // Why the try/catch, unlike processBoxShadow's wrapper: upstream throws exactly once
8
+ // (processFilter.js:120, a TypeError for an input that is neither string nor array), and a commit
9
+ // path must never throw. fabric-props narrows before calling, but `processFilter` is on the public
10
+ // barrel, so the guard is reachable rather than ceremonial.
11
+ //
12
+ // What changed by importing: a drop-shadow's colour now goes through RN's own `processColor`
13
+ // (upstream imports it directly at processFilter.js:16) instead of our injected
14
+ // `setColorProcessor` seam. On a real host those are the same function - bootstrapHost injects
15
+ // exactly `processColor` - so the seam simply no longer governs the colour inside a filter.
16
+ //
17
+ // Platform caveat worth keeping in view when demoing this: on iOS RN paints only `brightness` and
18
+ // `opacity` unless enableSwiftUIBasedFilters is on, so grayscale/blur/saturate/contrast/hueRotate
19
+ // parse correctly here and still do nothing on screen.
8
20
  import { dlog } from './debug.js';
9
- import { isRecord } from './type-guards.js';
10
- // RN processFilter.js:19-24: pre-compiled patterns.
11
- const NEWLINE_REGEX = /\n/g;
12
- const FILTER_FUNCTION_REGEX = /([\w-]+)\(([^()]*|\([^()]*\)|[^()]*\([^()]*\)[^()]*)\)/g;
13
- const ARGS_WITH_UNITS_REGEX = /([+-]?\d*(\.\d+)?)([a-zA-Z%]+)?/g;
14
- const WHITESPACE_SPLIT_REGEX = /\s+(?![^(]*\))/;
15
- const LENGTH_PARSE_REGEX = /([+-]?\d*(\.\d+)?)([\w\W]+)?/g;
16
- const RADIANS_TO_DEGREES = 180 / Math.PI;
17
- // A length field may be a CSS string or a number; resolve to a number or null.
18
- function resolveLength(value) {
19
- if (typeof value === 'string')
20
- return parseLength(value);
21
- if (typeof value === 'number')
22
- return value;
23
- return null;
24
- }
25
- // A drop-shadow color may already be a platform int (number) or undefined; processColor
26
- // only types CSS strings / opaque PlatformColor objects. Number passes through, anything
27
- // else (incl. undefined) is null: unprocessable, like RN.
28
- function processShadowColor(color) {
29
- if (typeof color === 'number')
30
- return color;
31
- if (typeof color === 'string' || isOpaqueColorValue(color))
32
- return processColor(color);
33
- return null;
34
- }
35
- // RN processFilter.js:45-124. Returns [] on any invalid primitive (web semantics: an
36
- // invalid filter applies none of them, so the missing effect is obvious).
21
+ // @ts-expect-error - untyped Flow source. Metro compiles it; vitest.config.ts strips the types.
22
+ import processFilterUpstream from 'react-native/Libraries/StyleSheet/processFilter';
37
23
  export function processFilter(filter) {
38
- const result = [];
39
- if (filter == null) {
40
- return result;
41
- }
42
- if (typeof filter === 'string') {
43
- const text = filter.replace(NEWLINE_REGEX, ' ');
44
- // Matches functions with args and nested functions like
45
- // "drop-shadow(10 10 10 rgba(0, 0, 0, 1))".
46
- FILTER_FUNCTION_REGEX.lastIndex = 0;
47
- let matches;
48
- while ((matches = FILTER_FUNCTION_REGEX.exec(text)) !== null) {
49
- const filterName = matches[1].toLowerCase();
50
- if (filterName === 'drop-shadow') {
51
- const dropShadow = parseDropShadow(matches[2]);
52
- if (dropShadow != null) {
53
- result.push({ dropShadow });
54
- }
55
- else {
56
- dlog(`processFilter reject: invalid drop-shadow "${matches[2]}"`);
57
- return [];
58
- }
59
- }
60
- else {
61
- const camelizedName = filterName === 'hue-rotate' ? 'hueRotate' : filterName;
62
- const amount = getFilterAmount(camelizedName, matches[2]);
63
- if (amount != null) {
64
- result.push(filterEntry(camelizedName, amount));
65
- }
66
- else {
67
- dlog(`processFilter reject: invalid ${camelizedName} "${matches[2]}"`);
68
- return [];
69
- }
70
- }
71
- }
72
- }
73
- else if (Array.isArray(filter)) {
74
- for (const filterFunction of filter) {
75
- const [filterName, filterValue] = Object.entries(filterFunction)[0];
76
- if (filterName === 'dropShadow') {
77
- const dropShadow = isDropShadowValue(filterValue)
78
- ? parseDropShadow(filterValue)
79
- : null;
80
- if (dropShadow == null) {
81
- dlog(`processFilter reject: invalid dropShadow in array`);
82
- return [];
83
- }
84
- result.push({ dropShadow });
85
- }
86
- else {
87
- const amount = getFilterAmount(filterName, filterValue);
88
- if (amount != null) {
89
- result.push(filterEntry(filterName, amount));
90
- }
91
- else {
92
- dlog(`processFilter reject: invalid ${filterName} in array`);
93
- return [];
94
- }
95
- }
96
- }
97
- }
98
- else {
99
- throw new TypeError(`${typeof filter} filter is not a string or array`);
100
- }
101
- return result;
102
- }
103
- // RN sets `filterFunction[camelizedName] = amount` and pushes the loose object. We
104
- // build the discriminated union explicitly to keep it typed without an `as` cast.
105
- function filterEntry(name, amount) {
106
- switch (name) {
107
- case 'brightness':
108
- return { brightness: amount };
109
- case 'blur':
110
- return { blur: amount };
111
- case 'contrast':
112
- return { contrast: amount };
113
- case 'grayscale':
114
- return { grayscale: amount };
115
- case 'hueRotate':
116
- return { hueRotate: amount };
117
- case 'invert':
118
- return { invert: amount };
119
- case 'opacity':
120
- return { opacity: amount };
121
- case 'saturate':
122
- return { saturate: amount };
123
- default:
124
- return { sepia: amount };
125
- }
126
- }
127
- function isDropShadowValue(value) {
128
- return typeof value === 'string' || (isRecord(value) && 'offsetX' in value);
129
- }
130
- // RN processFilter.js:126-186.
131
- function getFilterAmount(filterName, filterArgs) {
132
- let filterArgAsNumber;
133
- let unit;
134
- if (typeof filterArgs === 'string') {
135
- // Matches args with units like "1.5 5% -80deg".
136
- ARGS_WITH_UNITS_REGEX.lastIndex = 0;
137
- const match = ARGS_WITH_UNITS_REGEX.exec(filterArgs);
138
- if (!match || isNaN(Number(match[1]))) {
139
- return undefined;
140
- }
141
- filterArgAsNumber = Number(match[1]);
142
- unit = match[3];
143
- }
144
- else if (typeof filterArgs === 'number') {
145
- filterArgAsNumber = filterArgs;
146
- }
147
- else {
148
- return undefined;
149
- }
150
- switch (filterName) {
151
- // hueRotate takes an angle that can carry a unit and be negative; bare 0 is allowed.
152
- case 'hueRotate':
153
- if (filterArgAsNumber === 0) {
154
- return 0;
155
- }
156
- if (unit !== 'deg' && unit !== 'rad') {
157
- return undefined;
158
- }
159
- return unit === 'rad'
160
- ? RADIANS_TO_DEGREES * filterArgAsNumber
161
- : filterArgAsNumber;
162
- // blur takes any non-negative CSS length that is not a percent; RN only has DIPs.
163
- case 'blur':
164
- if ((unit != null && unit !== 'px') || filterArgAsNumber < 0) {
165
- return undefined;
166
- }
167
- return filterArgAsNumber;
168
- // The rest take a non-negative number or percentage (50% == 0.5).
169
- case 'brightness':
170
- case 'contrast':
171
- case 'grayscale':
172
- case 'invert':
173
- case 'opacity':
174
- case 'saturate':
175
- case 'sepia':
176
- if ((unit != null && unit !== '%' && unit !== 'px') ||
177
- filterArgAsNumber < 0) {
178
- return undefined;
179
- }
180
- return unit === '%' ? filterArgAsNumber / 100 : filterArgAsNumber;
181
- default:
182
- return undefined;
183
- }
184
- }
185
- // RN processFilter.js:188-256.
186
- function parseDropShadow(rawDropShadow) {
187
- const dropShadow = typeof rawDropShadow === 'string'
188
- ? parseDropShadowString(rawDropShadow)
189
- : rawDropShadow;
190
- if (dropShadow == null) {
191
- return null;
192
- }
193
- const parsedDropShadow = { offsetX: 0, offsetY: 0 };
194
- let offsetX;
195
- let offsetY;
196
- for (const arg of Object.keys(dropShadow)) {
197
- switch (arg) {
198
- case 'offsetX': {
199
- const value = resolveLength(dropShadow.offsetX);
200
- if (value == null) {
201
- return null;
202
- }
203
- offsetX = value;
204
- break;
205
- }
206
- case 'offsetY': {
207
- const value = resolveLength(dropShadow.offsetY);
208
- if (value == null) {
209
- return null;
210
- }
211
- offsetY = value;
212
- break;
213
- }
214
- case 'standardDeviation': {
215
- const value = resolveLength(dropShadow.standardDeviation);
216
- if (value == null || value < 0) {
217
- return null;
218
- }
219
- parsedDropShadow.standardDeviation = value;
220
- break;
221
- }
222
- case 'color': {
223
- const color = processShadowColor(dropShadow.color);
224
- if (color == null) {
225
- return null;
226
- }
227
- parsedDropShadow.color = color;
228
- break;
229
- }
230
- default:
231
- return null;
232
- }
233
- }
234
- if (offsetX == null || offsetY == null) {
235
- return null;
236
- }
237
- parsedDropShadow.offsetX = offsetX;
238
- parsedDropShadow.offsetY = offsetY;
239
- return parsedDropShadow;
240
- }
241
- // RN processFilter.js:258-312.
242
- function parseDropShadowString(rawDropShadow) {
243
- const dropShadow = { offsetX: 0, offsetY: 0 };
244
- let offsetX;
245
- let offsetY;
246
- let lengthCount = 0;
247
- let keywordDetectedAfterLength = false;
248
- for (const arg of rawDropShadow.split(WHITESPACE_SPLIT_REGEX)) {
249
- const processedColor = processColor(arg);
250
- if (processedColor != null) {
251
- if (dropShadow.color != null) {
252
- return null;
253
- }
254
- if (offsetX != null) {
255
- keywordDetectedAfterLength = true;
256
- }
257
- dropShadow.color = arg;
258
- continue;
259
- }
260
- switch (lengthCount) {
261
- case 0:
262
- offsetX = arg;
263
- lengthCount++;
264
- break;
265
- case 1:
266
- if (keywordDetectedAfterLength) {
267
- return null;
268
- }
269
- offsetY = arg;
270
- lengthCount++;
271
- break;
272
- case 2:
273
- if (keywordDetectedAfterLength) {
274
- return null;
275
- }
276
- dropShadow.standardDeviation = arg;
277
- lengthCount++;
278
- break;
279
- default:
280
- return null;
281
- }
282
- }
283
- if (offsetX == null || offsetY == null) {
284
- return null;
285
- }
286
- dropShadow.offsetX = offsetX;
287
- dropShadow.offsetY = offsetY;
288
- return dropShadow;
289
- }
290
- // RN processFilter.js:314-332. Accepts a unitless 0 or any `<n>px`; rejects a non-zero
291
- // unitless length or a non-px unit.
292
- function parseLength(length) {
293
- LENGTH_PARSE_REGEX.lastIndex = 0;
294
- const match = LENGTH_PARSE_REGEX.exec(length);
295
- if (!match || isNaN(Number(match[1]))) {
296
- return null;
297
- }
298
- if (match[3] != null && match[3] !== 'px') {
299
- return null;
24
+ try {
25
+ const parsed = processFilterUpstream(filter);
26
+ return Array.isArray(parsed) ? parsed : [];
300
27
  }
301
- if (match[3] == null && match[1] !== '0') {
302
- return null;
28
+ catch (error) {
29
+ dlog(`processFilter: dropped an invalid filter - ${error instanceof Error ? error.message : String(error)}`);
30
+ return [];
303
31
  }
304
- return Number(match[1]);
305
32
  }
@@ -1,5 +1,5 @@
1
- type ITransformEntry = Record<string, ITransformValue>;
2
1
  type ITransformValue = number | string | Array<number | string> | undefined;
2
+ type ITransformEntry = Record<string, ITransformValue>;
3
3
  type IRawTransform = Record<string, unknown>;
4
4
  export declare function processTransform(transform: ReadonlyArray<IRawTransform> | string | undefined): ReadonlyArray<IRawTransform>;
5
5
  export type { ITransformEntry, IRawTransform };