@symbiote-native/engine 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +124 -0
- package/build/accessibility-info/index.android.d.ts +3 -0
- package/build/accessibility-info/index.android.js +166 -0
- package/build/accessibility-info/index.d.ts +1 -0
- package/build/accessibility-info/index.ios.d.ts +3 -0
- package/build/accessibility-info/index.ios.js +219 -0
- package/build/accessibility-info/index.js +5 -0
- package/build/accessibility-info/shared.d.ts +34 -0
- package/build/accessibility-info/shared.js +13 -0
- package/build/action-sheet-ios/index.d.ts +36 -0
- package/build/action-sheet-ios/index.js +74 -0
- package/build/alert/index.android.d.ts +5 -0
- package/build/alert/index.android.js +117 -0
- package/build/alert/index.d.ts +1 -0
- package/build/alert/index.ios.d.ts +7 -0
- package/build/alert/index.ios.js +83 -0
- package/build/alert/index.js +8 -0
- package/build/alert/shared.d.ts +19 -0
- package/build/alert/shared.js +17 -0
- package/build/animated/animated-component-shared.d.ts +5 -0
- package/build/animated/animated-component-shared.js +54 -0
- package/build/animated/animation.d.ts +9 -0
- package/build/animated/animation.js +6 -0
- package/build/animated/animations/base.d.ts +27 -0
- package/build/animated/animations/base.js +90 -0
- package/build/animated/animations/composition.d.ts +38 -0
- package/build/animated/animations/composition.js +236 -0
- package/build/animated/animations/decay.d.ts +22 -0
- package/build/animated/animations/decay.js +65 -0
- package/build/animated/animations/raf.d.ts +5 -0
- package/build/animated/animations/raf.js +39 -0
- package/build/animated/animations/spring-config.d.ts +6 -0
- package/build/animated/animations/spring-config.js +55 -0
- package/build/animated/animations/spring.d.ts +50 -0
- package/build/animated/animations/spring.js +207 -0
- package/build/animated/animations/timing.d.ts +27 -0
- package/build/animated/animations/timing.js +101 -0
- package/build/animated/animations/tracking.d.ts +14 -0
- package/build/animated/animations/tracking.js +43 -0
- package/build/animated/bezier.d.ts +1 -0
- package/build/animated/bezier.js +101 -0
- package/build/animated/color.d.ts +37 -0
- package/build/animated/color.js +183 -0
- package/build/animated/easing.d.ts +20 -0
- package/build/animated/easing.js +96 -0
- package/build/animated/event.d.ts +36 -0
- package/build/animated/event.js +252 -0
- package/build/animated/graph.d.ts +38 -0
- package/build/animated/graph.js +227 -0
- package/build/animated/index.d.ts +20 -0
- package/build/animated/index.js +28 -0
- package/build/animated/interpolation-node.d.ts +16 -0
- package/build/animated/interpolation-node.js +57 -0
- package/build/animated/interpolation.d.ts +22 -0
- package/build/animated/interpolation.js +199 -0
- package/build/animated/mock.d.ts +56 -0
- package/build/animated/mock.js +127 -0
- package/build/animated/native/native-animated.d.ts +43 -0
- package/build/animated/native/native-animated.js +146 -0
- package/build/animated/operators.d.ts +80 -0
- package/build/animated/operators.js +266 -0
- package/build/animated/props.d.ts +20 -0
- package/build/animated/props.js +187 -0
- package/build/animated/style.d.ts +26 -0
- package/build/animated/style.js +187 -0
- package/build/animated/value-xy.d.ts +35 -0
- package/build/animated/value-xy.js +106 -0
- package/build/animated/value.d.ts +36 -0
- package/build/animated/value.js +185 -0
- package/build/app-registry/index.d.ts +40 -0
- package/build/app-registry/index.js +144 -0
- package/build/app-state/index.d.ts +16 -0
- package/build/app-state/index.js +105 -0
- package/build/appearance/index.d.ts +12 -0
- package/build/appearance/index.js +84 -0
- package/build/back-handler/index.d.ts +14 -0
- package/build/back-handler/index.js +106 -0
- package/build/commit.d.ts +16 -0
- package/build/commit.js +678 -0
- package/build/debug.d.ts +5 -0
- package/build/debug.js +18 -0
- package/build/dimensions/index.d.ts +28 -0
- package/build/dimensions/index.js +148 -0
- package/build/dispatch.d.ts +2 -0
- package/build/dispatch.js +18 -0
- package/build/events/index.d.ts +1 -0
- package/build/events/index.js +691 -0
- package/build/fabric.d.ts +32 -0
- package/build/fabric.js +59 -0
- package/build/host-instance/index.d.ts +11 -0
- package/build/host-instance/index.js +49 -0
- package/build/i18n-manager/index.d.ts +13 -0
- package/build/i18n-manager/index.js +91 -0
- package/build/index.d.ts +80 -0
- package/build/index.js +72 -0
- package/build/interaction-manager/index.d.ts +45 -0
- package/build/interaction-manager/index.js +222 -0
- package/build/keyboard/index.d.ts +31 -0
- package/build/keyboard/index.js +142 -0
- package/build/layout-animation/index.d.ts +66 -0
- package/build/layout-animation/index.js +183 -0
- package/build/linking/index.android.d.ts +2 -0
- package/build/linking/index.android.js +18 -0
- package/build/linking/index.d.ts +1 -0
- package/build/linking/index.ios.d.ts +2 -0
- package/build/linking/index.ios.js +9 -0
- package/build/linking/index.js +6 -0
- package/build/linking/shared.d.ts +32 -0
- package/build/linking/shared.js +98 -0
- package/build/native-events.d.ts +24 -0
- package/build/native-events.js +129 -0
- package/build/native-modules.d.ts +6 -0
- package/build/native-modules.js +57 -0
- package/build/node.d.ts +36 -0
- package/build/node.js +194 -0
- package/build/pan-responder/index.d.ts +53 -0
- package/build/pan-responder/index.js +353 -0
- package/build/permissions-android/index.d.ts +115 -0
- package/build/permissions-android/index.js +185 -0
- package/build/pixel-ratio/index.d.ts +8 -0
- package/build/pixel-ratio/index.js +27 -0
- package/build/platform/index.android.d.ts +22 -0
- package/build/platform/index.android.js +60 -0
- package/build/platform/index.d.ts +1 -0
- package/build/platform/index.ios.d.ts +18 -0
- package/build/platform/index.ios.js +62 -0
- package/build/platform/index.js +5 -0
- package/build/platform/shared.d.ts +25 -0
- package/build/platform/shared.js +41 -0
- package/build/platform-color.d.ts +19 -0
- package/build/platform-color.js +25 -0
- package/build/post-commit.d.ts +4 -0
- package/build/post-commit.js +16 -0
- package/build/process-aspect-ratio.d.ts +1 -0
- package/build/process-aspect-ratio.js +34 -0
- package/build/process-background-image/index.d.ts +28 -0
- package/build/process-background-image/index.js +557 -0
- package/build/process-box-shadow/index.d.ts +11 -0
- package/build/process-box-shadow/index.js +193 -0
- package/build/process-filter.d.ts +31 -0
- package/build/process-filter.js +304 -0
- package/build/process-font-variant.d.ts +1 -0
- package/build/process-font-variant.js +17 -0
- package/build/process-transform/index.d.ts +5 -0
- package/build/process-transform/index.js +120 -0
- package/build/process-transform-origin/index.d.ts +3 -0
- package/build/process-transform-origin/index.js +108 -0
- package/build/registry.d.ts +31 -0
- package/build/registry.js +145 -0
- package/build/settings/index.d.ts +8 -0
- package/build/settings/index.js +126 -0
- package/build/share/index.android.d.ts +3 -0
- package/build/share/index.android.js +56 -0
- package/build/share/index.d.ts +1 -0
- package/build/share/index.ios.d.ts +3 -0
- package/build/share/index.ios.js +47 -0
- package/build/share/index.js +6 -0
- package/build/share/shared.d.ts +32 -0
- package/build/share/shared.js +32 -0
- package/build/status-bar/index.android.d.ts +5 -0
- package/build/status-bar/index.android.js +83 -0
- package/build/status-bar/index.d.ts +1 -0
- package/build/status-bar/index.ios.d.ts +5 -0
- package/build/status-bar/index.ios.js +66 -0
- package/build/status-bar/index.js +4 -0
- package/build/status-bar/shared.d.ts +22 -0
- package/build/status-bar/shared.js +22 -0
- package/build/style/index.d.ts +1 -0
- package/build/style/index.js +30 -0
- package/build/style-registry/index.d.ts +11 -0
- package/build/style-registry/index.js +165 -0
- package/build/style-sheet/index.d.ts +20 -0
- package/build/style-sheet/index.js +121 -0
- package/build/styles.d.ts +220 -0
- package/build/styles.js +7 -0
- package/build/surface.d.ts +16 -0
- package/build/surface.js +67 -0
- package/build/tags.d.ts +1 -0
- package/build/tags.js +10 -0
- package/build/text-input-state.d.ts +5 -0
- package/build/text-input-state.js +29 -0
- package/build/toast-android/index.d.ts +10 -0
- package/build/toast-android/index.js +108 -0
- package/build/vibration/index.android.d.ts +2 -0
- package/build/vibration/index.android.js +18 -0
- package/build/vibration/index.d.ts +1 -0
- package/build/vibration/index.ios.d.ts +2 -0
- package/build/vibration/index.ios.js +54 -0
- package/build/vibration/index.js +6 -0
- package/build/vibration/shared.d.ts +15 -0
- package/build/vibration/shared.js +68 -0
- package/build/view-config.d.ts +1 -0
- package/build/view-config.js +114 -0
- package/package.json +41 -0
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
// JS-side port of RN's processBackgroundImage (Libraries/StyleSheet/processBackgroundImage.js).
|
|
2
|
+
// Same root cause as boxShadow/filter/transform: `experimental_backgroundImage` registers with
|
|
3
|
+
// enableNativeCSSParsing(), which DEFAULTS TO FALSE, so RN's stock path parses the CSS gradient
|
|
4
|
+
// string / structured array in JS and sends only the processed array to native — Fabric's C++
|
|
5
|
+
// never sees the raw string. This restores that missing JS parse.
|
|
6
|
+
//
|
|
7
|
+
// processColor is referenced from ../commit at RUNTIME only (inside function bodies), never at
|
|
8
|
+
// module-init, so the cyclic import (commit -> here -> commit) has no TDZ hazard — same pattern
|
|
9
|
+
// as process-box-shadow/process-filter.
|
|
10
|
+
import { processColor } from '../commit';
|
|
11
|
+
import { isOpaqueColorValue } from '../platform-color';
|
|
12
|
+
import { dlog } from '../debug';
|
|
13
|
+
// RN processBackgroundImage.js: pre-compiled patterns.
|
|
14
|
+
const NEWLINE_REGEX = /\n/g;
|
|
15
|
+
const GRADIENT_REGEX = /^(linear|radial)-gradient\(((?:\([^)]*\)|[^()])*)\)/;
|
|
16
|
+
const COMMA_SPLIT_REGEX = /,(?![^(]*\))/;
|
|
17
|
+
const WHITESPACE_SPLIT_REGEX = /\s+/;
|
|
18
|
+
const COLOR_STOP_PARTS_REGEX = /\S+\([^)]*\)|\S+/g;
|
|
19
|
+
const WHITESPACE_NORMALIZE_REGEX = /\s+/g;
|
|
20
|
+
const LINEAR_GRADIENT_DIRECTION_REGEX = /^to\s+(?:top|bottom|left|right)(?:\s+(?:top|bottom|left|right))?/i;
|
|
21
|
+
const LINEAR_GRADIENT_ANGLE_UNIT_REGEX = /^([+-]?\d*\.?\d+)(deg|grad|rad|turn)$/i;
|
|
22
|
+
const LINEAR_GRADIENT_DEFAULT_DIRECTION = { type: 'angle', value: 180 };
|
|
23
|
+
const DEFAULT_RADIAL_SHAPE = 'ellipse';
|
|
24
|
+
const DEFAULT_RADIAL_SIZE = 'farthest-corner';
|
|
25
|
+
const DEFAULT_RADIAL_POSITION = { top: '50%', left: '50%' };
|
|
26
|
+
function isRecord(value) {
|
|
27
|
+
return typeof value === 'object' && value !== null;
|
|
28
|
+
}
|
|
29
|
+
function isStringOrNumber(value) {
|
|
30
|
+
return typeof value === 'string' || typeof value === 'number';
|
|
31
|
+
}
|
|
32
|
+
// A color-stop position is a plain px number or a percentage string; anything else is invalid.
|
|
33
|
+
function isPositionValue(value) {
|
|
34
|
+
return typeof value === 'number' || (typeof value === 'string' && value.endsWith('%'));
|
|
35
|
+
}
|
|
36
|
+
// A gradient color may already be a platform int (array form) or a CSS string/PlatformColor
|
|
37
|
+
// object needing processColor; mirrors process-box-shadow's processShadowColor.
|
|
38
|
+
function processStopColor(color) {
|
|
39
|
+
if (typeof color === 'number')
|
|
40
|
+
return color;
|
|
41
|
+
if (typeof color === 'string' || isOpaqueColorValue(color))
|
|
42
|
+
return processColor(color);
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
// RN processBackgroundImage.js's `getPositionFromCSSValue`: `px` resolves to a plain number,
|
|
46
|
+
// `%` stays a string, anything else is unresolvable (returns undefined, like RN's fallthrough).
|
|
47
|
+
function getPositionFromCSSValue(position) {
|
|
48
|
+
if (position.endsWith('px'))
|
|
49
|
+
return parseFloat(position);
|
|
50
|
+
if (position.endsWith('%'))
|
|
51
|
+
return position;
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
function getAngleInDegrees(angle) {
|
|
55
|
+
const match = angle.match(LINEAR_GRADIENT_ANGLE_UNIT_REGEX);
|
|
56
|
+
if (!match || match[1] == null)
|
|
57
|
+
return null;
|
|
58
|
+
const value = parseFloat(match[1]);
|
|
59
|
+
switch (match[2]) {
|
|
60
|
+
case 'deg':
|
|
61
|
+
return value;
|
|
62
|
+
case 'grad':
|
|
63
|
+
return value * 0.9;
|
|
64
|
+
case 'rad':
|
|
65
|
+
return (value * 180) / Math.PI;
|
|
66
|
+
case 'turn':
|
|
67
|
+
return value * 360;
|
|
68
|
+
default:
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function getDirectionForKeyword(direction) {
|
|
73
|
+
const normalized = direction.replace(WHITESPACE_NORMALIZE_REGEX, ' ').toLowerCase();
|
|
74
|
+
switch (normalized) {
|
|
75
|
+
case 'to top':
|
|
76
|
+
return { type: 'angle', value: 0 };
|
|
77
|
+
case 'to right':
|
|
78
|
+
return { type: 'angle', value: 90 };
|
|
79
|
+
case 'to bottom':
|
|
80
|
+
return { type: 'angle', value: 180 };
|
|
81
|
+
case 'to left':
|
|
82
|
+
return { type: 'angle', value: 270 };
|
|
83
|
+
case 'to top right':
|
|
84
|
+
case 'to right top':
|
|
85
|
+
return { type: 'keyword', value: 'to top right' };
|
|
86
|
+
case 'to bottom right':
|
|
87
|
+
case 'to right bottom':
|
|
88
|
+
return { type: 'keyword', value: 'to bottom right' };
|
|
89
|
+
case 'to top left':
|
|
90
|
+
case 'to left top':
|
|
91
|
+
return { type: 'keyword', value: 'to top left' };
|
|
92
|
+
case 'to bottom left':
|
|
93
|
+
case 'to left bottom':
|
|
94
|
+
return { type: 'keyword', value: 'to bottom left' };
|
|
95
|
+
default:
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
//#region array form (structured objects, e.g. the animated / sticky-header hot path)
|
|
100
|
+
function resolveRadialShape(value) {
|
|
101
|
+
if (value == null)
|
|
102
|
+
return DEFAULT_RADIAL_SHAPE;
|
|
103
|
+
if (value === 'circle' || value === 'ellipse')
|
|
104
|
+
return value;
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
function resolveRadialSize(value) {
|
|
108
|
+
if (value == null)
|
|
109
|
+
return DEFAULT_RADIAL_SIZE;
|
|
110
|
+
if (value === 'closest-side' ||
|
|
111
|
+
value === 'closest-corner' ||
|
|
112
|
+
value === 'farthest-side' ||
|
|
113
|
+
value === 'farthest-corner') {
|
|
114
|
+
return value;
|
|
115
|
+
}
|
|
116
|
+
if (isRecord(value) && isStringOrNumber(value.x) && isStringOrNumber(value.y)) {
|
|
117
|
+
return { x: value.x, y: value.y };
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
function isRadialPositionValue(value) {
|
|
122
|
+
if (!isRecord(value))
|
|
123
|
+
return false;
|
|
124
|
+
const hasVertical = isStringOrNumber(value.top) || isStringOrNumber(value.bottom);
|
|
125
|
+
const hasHorizontal = isStringOrNumber(value.left) || isStringOrNumber(value.right);
|
|
126
|
+
return hasVertical && hasHorizontal;
|
|
127
|
+
}
|
|
128
|
+
// RN processBackgroundImage.js's `processColorStops`. Returns `null` on any invalid stop (web
|
|
129
|
+
// semantics: an invalid gradient applies none of it, same as processBoxShadow/processFilter).
|
|
130
|
+
function processColorStopsArray(rawColorStops) {
|
|
131
|
+
if (!Array.isArray(rawColorStops))
|
|
132
|
+
return null;
|
|
133
|
+
const processed = [];
|
|
134
|
+
for (const rawStop of rawColorStops) {
|
|
135
|
+
if (!isRecord(rawStop))
|
|
136
|
+
return null;
|
|
137
|
+
const positions = rawStop.positions;
|
|
138
|
+
// Color transition hint syntax (`red, 20%, blue`): a position with no color of its own.
|
|
139
|
+
if (rawStop.color == null && Array.isArray(positions) && positions.length === 1) {
|
|
140
|
+
const position = positions[0];
|
|
141
|
+
if (!isPositionValue(position))
|
|
142
|
+
return null;
|
|
143
|
+
processed.push({ color: null, position });
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
const processedColor = processStopColor(rawStop.color);
|
|
147
|
+
if (processedColor == null)
|
|
148
|
+
return null;
|
|
149
|
+
// Two+ positions on one color-stop object is CSS's shorthand for repeating that color at
|
|
150
|
+
// each position (e.g. `red 0% 50%` == two adjacent stops both colored red).
|
|
151
|
+
if (Array.isArray(positions) && positions.length > 0) {
|
|
152
|
+
for (const position of positions) {
|
|
153
|
+
if (!isPositionValue(position))
|
|
154
|
+
return null;
|
|
155
|
+
processed.push({ color: processedColor, position });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
processed.push({ color: processedColor, position: null });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return processed;
|
|
163
|
+
}
|
|
164
|
+
function processBackgroundImageArray(rawList) {
|
|
165
|
+
const result = [];
|
|
166
|
+
for (const rawBgImage of rawList) {
|
|
167
|
+
const colorStops = processColorStopsArray(rawBgImage.colorStops);
|
|
168
|
+
if (colorStops == null) {
|
|
169
|
+
dlog('processBackgroundImage reject: invalid color stop');
|
|
170
|
+
return [];
|
|
171
|
+
}
|
|
172
|
+
if (rawBgImage.type === 'linear-gradient') {
|
|
173
|
+
let direction = LINEAR_GRADIENT_DEFAULT_DIRECTION;
|
|
174
|
+
const rawDirection = rawBgImage.direction;
|
|
175
|
+
const bgDirection = typeof rawDirection === 'string' ? rawDirection.toLowerCase() : null;
|
|
176
|
+
if (bgDirection != null) {
|
|
177
|
+
if (LINEAR_GRADIENT_ANGLE_UNIT_REGEX.test(bgDirection)) {
|
|
178
|
+
const parsedAngle = getAngleInDegrees(bgDirection);
|
|
179
|
+
if (parsedAngle == null) {
|
|
180
|
+
dlog(`processBackgroundImage reject: invalid linear-gradient angle "${bgDirection}"`);
|
|
181
|
+
return [];
|
|
182
|
+
}
|
|
183
|
+
direction = { type: 'angle', value: parsedAngle };
|
|
184
|
+
}
|
|
185
|
+
else if (LINEAR_GRADIENT_DIRECTION_REGEX.test(bgDirection)) {
|
|
186
|
+
const parsedDirection = getDirectionForKeyword(bgDirection);
|
|
187
|
+
if (parsedDirection == null) {
|
|
188
|
+
dlog(`processBackgroundImage reject: invalid linear-gradient direction "${bgDirection}"`);
|
|
189
|
+
return [];
|
|
190
|
+
}
|
|
191
|
+
direction = parsedDirection;
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
dlog(`processBackgroundImage reject: invalid linear-gradient direction "${bgDirection}"`);
|
|
195
|
+
return [];
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
result.push({ type: 'linear-gradient', direction, colorStops });
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (rawBgImage.type === 'radial-gradient') {
|
|
202
|
+
const shape = resolveRadialShape(rawBgImage.shape);
|
|
203
|
+
if (shape == null) {
|
|
204
|
+
dlog('processBackgroundImage reject: invalid radial-gradient shape');
|
|
205
|
+
return [];
|
|
206
|
+
}
|
|
207
|
+
const size = resolveRadialSize(rawBgImage.size);
|
|
208
|
+
if (size == null) {
|
|
209
|
+
dlog('processBackgroundImage reject: invalid radial-gradient size');
|
|
210
|
+
return [];
|
|
211
|
+
}
|
|
212
|
+
const position = isRadialPositionValue(rawBgImage.position)
|
|
213
|
+
? rawBgImage.position
|
|
214
|
+
: DEFAULT_RADIAL_POSITION;
|
|
215
|
+
result.push({ type: 'radial-gradient', shape, size, position, colorStops });
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return result;
|
|
219
|
+
}
|
|
220
|
+
//#endregion array form
|
|
221
|
+
//#region CSS string form
|
|
222
|
+
// Split a `background-image` value on its TOP-LEVEL commas only (multiple gradients, or a
|
|
223
|
+
// color function's internal commas like `rgba(0, 0, 0, .3)`, must not be confused for one
|
|
224
|
+
// another) — depth-tracking instead of a lookahead regex because a gradient's own arg list can
|
|
225
|
+
// itself contain nested parens (`linear-gradient(to right, rgba(0,0,0,.5), blue)`).
|
|
226
|
+
function splitGradients(input) {
|
|
227
|
+
const result = [];
|
|
228
|
+
let current = '';
|
|
229
|
+
let depth = 0;
|
|
230
|
+
for (const char of input) {
|
|
231
|
+
if (char === '(') {
|
|
232
|
+
depth++;
|
|
233
|
+
}
|
|
234
|
+
else if (char === ')') {
|
|
235
|
+
depth--;
|
|
236
|
+
}
|
|
237
|
+
else if (char === ',' && depth === 0) {
|
|
238
|
+
result.push(current.trim());
|
|
239
|
+
current = '';
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
current += char;
|
|
243
|
+
}
|
|
244
|
+
if (current.trim() !== '')
|
|
245
|
+
result.push(current.trim());
|
|
246
|
+
return result;
|
|
247
|
+
}
|
|
248
|
+
function parseColorStopsCSSString(parts) {
|
|
249
|
+
const stops = parts.join(',').split(COMMA_SPLIT_REGEX);
|
|
250
|
+
const colorStops = [];
|
|
251
|
+
let prevStopParts = null;
|
|
252
|
+
for (let i = 0; i < stops.length; i++) {
|
|
253
|
+
const trimmedStop = stops[i].trim().toLowerCase();
|
|
254
|
+
const colorStopParts = trimmedStop.match(COLOR_STOP_PARTS_REGEX);
|
|
255
|
+
if (colorStopParts == null)
|
|
256
|
+
return null;
|
|
257
|
+
if (colorStopParts.length === 3) {
|
|
258
|
+
const position1 = getPositionFromCSSValue(colorStopParts[1]);
|
|
259
|
+
const position2 = getPositionFromCSSValue(colorStopParts[2]);
|
|
260
|
+
const processedColor = processStopColor(colorStopParts[0]);
|
|
261
|
+
if (processedColor == null || position1 == null || position2 == null)
|
|
262
|
+
return null;
|
|
263
|
+
colorStops.push({ color: processedColor, position: position1 });
|
|
264
|
+
colorStops.push({ color: processedColor, position: position2 });
|
|
265
|
+
}
|
|
266
|
+
else if (colorStopParts.length === 2) {
|
|
267
|
+
const position = getPositionFromCSSValue(colorStopParts[1]);
|
|
268
|
+
const processedColor = processStopColor(colorStopParts[0]);
|
|
269
|
+
if (processedColor == null || position == null)
|
|
270
|
+
return null;
|
|
271
|
+
colorStops.push({ color: processedColor, position });
|
|
272
|
+
}
|
|
273
|
+
else if (colorStopParts.length === 1) {
|
|
274
|
+
const position = getPositionFromCSSValue(colorStopParts[0]);
|
|
275
|
+
if (position != null) {
|
|
276
|
+
// Transition-hint syntax must have a color-bearing stop on both sides.
|
|
277
|
+
const prevWasPositionOnly = prevStopParts != null &&
|
|
278
|
+
prevStopParts.length === 1 &&
|
|
279
|
+
getPositionFromCSSValue(prevStopParts[0]) != null;
|
|
280
|
+
if (prevWasPositionOnly || i === stops.length - 1 || i === 0)
|
|
281
|
+
return null;
|
|
282
|
+
colorStops.push({ color: null, position });
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
const processedColor = processStopColor(colorStopParts[0]);
|
|
286
|
+
if (processedColor == null)
|
|
287
|
+
return null;
|
|
288
|
+
colorStops.push({ color: processedColor, position: null });
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
prevStopParts = colorStopParts;
|
|
295
|
+
}
|
|
296
|
+
return colorStops;
|
|
297
|
+
}
|
|
298
|
+
function parseLinearGradientCSSString(gradientContent) {
|
|
299
|
+
const parts = gradientContent.split(',');
|
|
300
|
+
let direction = LINEAR_GRADIENT_DEFAULT_DIRECTION;
|
|
301
|
+
const trimmedDirection = (parts[0] ?? '').trim().toLowerCase();
|
|
302
|
+
if (LINEAR_GRADIENT_ANGLE_UNIT_REGEX.test(trimmedDirection)) {
|
|
303
|
+
const parsedAngle = getAngleInDegrees(trimmedDirection);
|
|
304
|
+
if (parsedAngle == null)
|
|
305
|
+
return null;
|
|
306
|
+
direction = { type: 'angle', value: parsedAngle };
|
|
307
|
+
parts.shift();
|
|
308
|
+
}
|
|
309
|
+
else if (LINEAR_GRADIENT_DIRECTION_REGEX.test(trimmedDirection)) {
|
|
310
|
+
const parsedDirection = getDirectionForKeyword(trimmedDirection);
|
|
311
|
+
if (parsedDirection == null)
|
|
312
|
+
return null;
|
|
313
|
+
direction = parsedDirection;
|
|
314
|
+
parts.shift();
|
|
315
|
+
}
|
|
316
|
+
const colorStops = parseColorStopsCSSString(parts);
|
|
317
|
+
if (colorStops == null)
|
|
318
|
+
return null;
|
|
319
|
+
return { type: 'linear-gradient', direction, colorStops };
|
|
320
|
+
}
|
|
321
|
+
// The `at <position>` clause of `radial-gradient(... at <position>, <color-stops>)`. Drains
|
|
322
|
+
// `tokens` (the SAME queue the caller is walking) via `shift()`, mirroring RN's in-place mutation
|
|
323
|
+
// of `firstPartTokens` — not a copy, so the caller sees it emptied after this returns.
|
|
324
|
+
function parseRadialGradientPositionTokens(tokens) {
|
|
325
|
+
if (tokens.length === 0)
|
|
326
|
+
return null;
|
|
327
|
+
let top;
|
|
328
|
+
let left;
|
|
329
|
+
let right;
|
|
330
|
+
let bottom;
|
|
331
|
+
// 1. [ left | center | right | top | bottom | <length-percentage> ]
|
|
332
|
+
if (tokens.length === 1) {
|
|
333
|
+
const token = tokens.shift();
|
|
334
|
+
if (token == null)
|
|
335
|
+
return null;
|
|
336
|
+
const tokenTrimmed = token.toLowerCase().trim();
|
|
337
|
+
if (tokenTrimmed === 'left') {
|
|
338
|
+
left = '0%';
|
|
339
|
+
top = '50%';
|
|
340
|
+
}
|
|
341
|
+
else if (tokenTrimmed === 'center') {
|
|
342
|
+
left = '50%';
|
|
343
|
+
top = '50%';
|
|
344
|
+
}
|
|
345
|
+
else if (tokenTrimmed === 'right') {
|
|
346
|
+
left = '100%';
|
|
347
|
+
top = '50%';
|
|
348
|
+
}
|
|
349
|
+
else if (tokenTrimmed === 'top') {
|
|
350
|
+
left = '50%';
|
|
351
|
+
top = '0%';
|
|
352
|
+
}
|
|
353
|
+
else if (tokenTrimmed === 'bottom') {
|
|
354
|
+
left = '50%';
|
|
355
|
+
top = '100%';
|
|
356
|
+
}
|
|
357
|
+
else if (tokenTrimmed.endsWith('px') || tokenTrimmed.endsWith('%')) {
|
|
358
|
+
const value = getPositionFromCSSValue(tokenTrimmed);
|
|
359
|
+
if (value == null)
|
|
360
|
+
return null;
|
|
361
|
+
left = value;
|
|
362
|
+
top = '50%';
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
// 2. [ left | center | right ] && [ top | center | bottom ], or two length-percentages
|
|
366
|
+
if (tokens.length === 2) {
|
|
367
|
+
const t1 = tokens.shift();
|
|
368
|
+
const t2 = tokens.shift();
|
|
369
|
+
if (t1 == null || t2 == null)
|
|
370
|
+
return null;
|
|
371
|
+
const token1 = t1.toLowerCase().trim();
|
|
372
|
+
const token2 = t2.toLowerCase().trim();
|
|
373
|
+
const horizontalPositions = ['left', 'center', 'right'];
|
|
374
|
+
const verticalPositions = ['top', 'center', 'bottom'];
|
|
375
|
+
if (horizontalPositions.includes(token1) && verticalPositions.includes(token2)) {
|
|
376
|
+
left = token1 === 'left' ? '0%' : token1 === 'center' ? '50%' : '100%';
|
|
377
|
+
top = token2 === 'top' ? '0%' : token2 === 'center' ? '50%' : '100%';
|
|
378
|
+
}
|
|
379
|
+
else if (verticalPositions.includes(token1) && horizontalPositions.includes(token2)) {
|
|
380
|
+
left = token2 === 'left' ? '0%' : token2 === 'center' ? '50%' : '100%';
|
|
381
|
+
top = token1 === 'top' ? '0%' : token1 === 'center' ? '50%' : '100%';
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
if (token1 === 'left')
|
|
385
|
+
left = '0%';
|
|
386
|
+
else if (token1 === 'center')
|
|
387
|
+
left = '50%';
|
|
388
|
+
else if (token1 === 'right')
|
|
389
|
+
left = '100%';
|
|
390
|
+
else if (token1.endsWith('px') || token1.endsWith('%')) {
|
|
391
|
+
const value = getPositionFromCSSValue(token1);
|
|
392
|
+
if (value == null)
|
|
393
|
+
return null;
|
|
394
|
+
left = value;
|
|
395
|
+
}
|
|
396
|
+
else
|
|
397
|
+
return null;
|
|
398
|
+
if (token2 === 'top')
|
|
399
|
+
top = '0%';
|
|
400
|
+
else if (token2 === 'center')
|
|
401
|
+
top = '50%';
|
|
402
|
+
else if (token2 === 'bottom')
|
|
403
|
+
top = '100%';
|
|
404
|
+
else if (token2.endsWith('px') || token2.endsWith('%')) {
|
|
405
|
+
const value = getPositionFromCSSValue(token2);
|
|
406
|
+
if (value == null)
|
|
407
|
+
return null;
|
|
408
|
+
top = value;
|
|
409
|
+
}
|
|
410
|
+
else
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
// 3. [ [ left | right ] <length-percentage> ] && [ [ top | bottom ] <length-percentage> ]
|
|
415
|
+
if (tokens.length === 4) {
|
|
416
|
+
const [t1, t2, t3, t4] = tokens.splice(0, 4);
|
|
417
|
+
if (t1 == null || t2 == null || t3 == null || t4 == null)
|
|
418
|
+
return null;
|
|
419
|
+
const keyword1 = t1.toLowerCase().trim();
|
|
420
|
+
const value1 = getPositionFromCSSValue(t2.toLowerCase().trim());
|
|
421
|
+
const keyword2 = t3.toLowerCase().trim();
|
|
422
|
+
const value2 = getPositionFromCSSValue(t4.toLowerCase().trim());
|
|
423
|
+
if (value1 == null || value2 == null)
|
|
424
|
+
return null;
|
|
425
|
+
if (keyword1 === 'left')
|
|
426
|
+
left = value1;
|
|
427
|
+
else if (keyword1 === 'right')
|
|
428
|
+
right = value1;
|
|
429
|
+
else if (keyword1 === 'top')
|
|
430
|
+
top = value1;
|
|
431
|
+
else if (keyword1 === 'bottom')
|
|
432
|
+
bottom = value1;
|
|
433
|
+
else
|
|
434
|
+
return null;
|
|
435
|
+
if (keyword2 === 'left')
|
|
436
|
+
left = value2;
|
|
437
|
+
else if (keyword2 === 'right')
|
|
438
|
+
right = value2;
|
|
439
|
+
else if (keyword2 === 'top')
|
|
440
|
+
top = value2;
|
|
441
|
+
else if (keyword2 === 'bottom')
|
|
442
|
+
bottom = value2;
|
|
443
|
+
else
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
if (top != null && left != null)
|
|
447
|
+
return { top, left };
|
|
448
|
+
if (bottom != null && right != null)
|
|
449
|
+
return { bottom, right };
|
|
450
|
+
if (top != null && right != null)
|
|
451
|
+
return { top, right };
|
|
452
|
+
if (bottom != null && left != null)
|
|
453
|
+
return { bottom, left };
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
function parseRadialGradientCSSString(gradientContent) {
|
|
457
|
+
let shape = DEFAULT_RADIAL_SHAPE;
|
|
458
|
+
let size = DEFAULT_RADIAL_SIZE;
|
|
459
|
+
let position = { ...DEFAULT_RADIAL_POSITION };
|
|
460
|
+
const parts = gradientContent.split(COMMA_SPLIT_REGEX);
|
|
461
|
+
const firstPartStr = (parts[0] ?? '').trim();
|
|
462
|
+
const remainingParts = [...parts];
|
|
463
|
+
let hasShapeSizeOrPositionString = false;
|
|
464
|
+
let hasExplicitSingleSize = false;
|
|
465
|
+
let hasExplicitShape = false;
|
|
466
|
+
const firstPartTokens = firstPartStr.split(WHITESPACE_SPLIT_REGEX);
|
|
467
|
+
while (firstPartTokens.length > 0) {
|
|
468
|
+
let token = firstPartTokens.shift();
|
|
469
|
+
if (token == null)
|
|
470
|
+
continue;
|
|
471
|
+
let tokenTrimmed = token.toLowerCase().trim();
|
|
472
|
+
if (tokenTrimmed === 'circle' || tokenTrimmed === 'ellipse') {
|
|
473
|
+
shape = tokenTrimmed;
|
|
474
|
+
hasShapeSizeOrPositionString = true;
|
|
475
|
+
hasExplicitShape = true;
|
|
476
|
+
}
|
|
477
|
+
else if (tokenTrimmed === 'closest-corner' ||
|
|
478
|
+
tokenTrimmed === 'farthest-corner' ||
|
|
479
|
+
tokenTrimmed === 'closest-side' ||
|
|
480
|
+
tokenTrimmed === 'farthest-side') {
|
|
481
|
+
size = tokenTrimmed;
|
|
482
|
+
hasShapeSizeOrPositionString = true;
|
|
483
|
+
}
|
|
484
|
+
else if (tokenTrimmed.endsWith('px') || tokenTrimmed.endsWith('%')) {
|
|
485
|
+
const sizeX = getPositionFromCSSValue(tokenTrimmed);
|
|
486
|
+
if (sizeX == null || (typeof sizeX === 'number' && sizeX < 0))
|
|
487
|
+
return null;
|
|
488
|
+
hasShapeSizeOrPositionString = true;
|
|
489
|
+
size = { x: sizeX, y: sizeX };
|
|
490
|
+
token = firstPartTokens.shift();
|
|
491
|
+
if (token == null) {
|
|
492
|
+
hasExplicitSingleSize = true;
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
tokenTrimmed = token.toLowerCase().trim();
|
|
496
|
+
if (tokenTrimmed.endsWith('px') || tokenTrimmed.endsWith('%')) {
|
|
497
|
+
const sizeY = getPositionFromCSSValue(tokenTrimmed);
|
|
498
|
+
if (sizeY == null || (typeof sizeY === 'number' && sizeY < 0))
|
|
499
|
+
return null;
|
|
500
|
+
size = { x: sizeX, y: sizeY };
|
|
501
|
+
}
|
|
502
|
+
else {
|
|
503
|
+
hasExplicitSingleSize = true;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
else if (tokenTrimmed === 'at') {
|
|
507
|
+
hasShapeSizeOrPositionString = true;
|
|
508
|
+
const parsedPosition = parseRadialGradientPositionTokens(firstPartTokens);
|
|
509
|
+
if (parsedPosition == null)
|
|
510
|
+
return null;
|
|
511
|
+
position = parsedPosition;
|
|
512
|
+
break;
|
|
513
|
+
}
|
|
514
|
+
// No shape/size/position token found in this iteration — the rest of the first part is a
|
|
515
|
+
// color stop, not gradient config.
|
|
516
|
+
if (!hasShapeSizeOrPositionString)
|
|
517
|
+
break;
|
|
518
|
+
}
|
|
519
|
+
if (hasShapeSizeOrPositionString) {
|
|
520
|
+
remainingParts.shift();
|
|
521
|
+
if (!hasExplicitShape && hasExplicitSingleSize)
|
|
522
|
+
shape = 'circle';
|
|
523
|
+
if (hasExplicitSingleSize && hasExplicitShape && shape === 'ellipse')
|
|
524
|
+
return null;
|
|
525
|
+
}
|
|
526
|
+
const colorStops = parseColorStopsCSSString(remainingParts);
|
|
527
|
+
if (colorStops == null)
|
|
528
|
+
return null;
|
|
529
|
+
return { type: 'radial-gradient', shape, size, position, colorStops };
|
|
530
|
+
}
|
|
531
|
+
function parseBackgroundImageCSSString(cssString) {
|
|
532
|
+
const gradients = [];
|
|
533
|
+
for (const bgImageString of splitGradients(cssString)) {
|
|
534
|
+
const bgImage = bgImageString.toLowerCase();
|
|
535
|
+
const match = GRADIENT_REGEX.exec(bgImage);
|
|
536
|
+
if (!match || match[1] == null || match[2] == null)
|
|
537
|
+
continue;
|
|
538
|
+
const isRadial = match[1].toLowerCase() === 'radial';
|
|
539
|
+
const gradient = isRadial
|
|
540
|
+
? parseRadialGradientCSSString(match[2])
|
|
541
|
+
: parseLinearGradientCSSString(match[2]);
|
|
542
|
+
if (gradient != null)
|
|
543
|
+
gradients.push(gradient);
|
|
544
|
+
}
|
|
545
|
+
return gradients;
|
|
546
|
+
}
|
|
547
|
+
//#endregion CSS string form
|
|
548
|
+
// RN processBackgroundImage.js's default export. Returns [] on any invalid gradient — web
|
|
549
|
+
// semantics: an invalid `background-image` paints none of it rather than a partial gradient.
|
|
550
|
+
export function processBackgroundImage(backgroundImage) {
|
|
551
|
+
if (backgroundImage == null)
|
|
552
|
+
return [];
|
|
553
|
+
if (typeof backgroundImage === 'string') {
|
|
554
|
+
return parseBackgroundImageCSSString(backgroundImage.replace(NEWLINE_REGEX, ' '));
|
|
555
|
+
}
|
|
556
|
+
return processBackgroundImageArray(backgroundImage);
|
|
557
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface IParsedBoxShadow {
|
|
2
|
+
offsetX: number;
|
|
3
|
+
offsetY: number;
|
|
4
|
+
color?: unknown;
|
|
5
|
+
blurRadius?: number;
|
|
6
|
+
spreadDistance?: number;
|
|
7
|
+
inset?: boolean;
|
|
8
|
+
}
|
|
9
|
+
type IRawBoxShadow = Record<string, unknown>;
|
|
10
|
+
export declare function processBoxShadow(rawBoxShadows: ReadonlyArray<IRawBoxShadow> | string | undefined): IParsedBoxShadow[];
|
|
11
|
+
export type { IRawBoxShadow };
|