@colorye/react-native-css 0.2.1

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.
@@ -0,0 +1,326 @@
1
+ const calcRe = /calc\(([^)]+)\)/g;
2
+
3
+ const colorRe1 = /rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d+)\s*)?\)/g;
4
+ const colorRe2 = /rgba?\(\s*(\d+)\s+(\d+)\s+(\d+)\s*\/\s*([\d.]+)\s*\)/g;
5
+
6
+ const itohex = (component) => {
7
+ const hex = Number(component).toString(16);
8
+ return hex.length === 1 ? `0${hex}` : hex;
9
+ };
10
+
11
+ function splitTopLevelCommas(str) {
12
+ const parts = [];
13
+ let current = "";
14
+ let depth = 0;
15
+ for (let i = 0; i < str.length; i++) {
16
+ const char = str[i];
17
+ if (char === "(") {
18
+ depth++;
19
+ current += char;
20
+ } else if (char === ")") {
21
+ depth--;
22
+ current += char;
23
+ } else if (char === "," && depth === 0) {
24
+ parts.push(current.trim());
25
+ current = "";
26
+ } else {
27
+ current += char;
28
+ }
29
+ }
30
+ if (current) {
31
+ parts.push(current.trim());
32
+ }
33
+ return parts;
34
+ }
35
+
36
+ function parseColorMixArg(arg) {
37
+ arg = arg.trim();
38
+ // Check if there is a percentage at the end
39
+ const percentEndMatch = arg.match(/\s+([\d.]+)%$/i);
40
+ if (percentEndMatch) {
41
+ const percentage = parseFloat(percentEndMatch[1]);
42
+ const colorStr = arg.substring(0, arg.length - percentEndMatch[0].length).trim();
43
+ return { colorStr, percentage };
44
+ }
45
+
46
+ // Check if there is a percentage at the beginning
47
+ const percentStartMatch = arg.match(/^([\d.]+)%\s+/i);
48
+ if (percentStartMatch) {
49
+ const percentage = parseFloat(percentStartMatch[1]);
50
+ const colorStr = arg.substring(percentStartMatch[0].length).trim();
51
+ return { colorStr, percentage };
52
+ }
53
+
54
+ return { colorStr: arg, percentage: null };
55
+ }
56
+
57
+ function hslToRgb(h, s, l) {
58
+ h = h / 360;
59
+ s = s / 100;
60
+ l = l / 100;
61
+ let r, g, b;
62
+ if (s === 0) {
63
+ r = g = b = l; // achromatic
64
+ } else {
65
+ const hue2rgb = (p, q, t) => {
66
+ if (t < 0) t += 1;
67
+ if (t > 1) t -= 1;
68
+ if (t < 1 / 6) return p + (q - p) * 6 * t;
69
+ if (t < 1 / 2) return q;
70
+ if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
71
+ return p;
72
+ };
73
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
74
+ const p = 2 * l - q;
75
+ r = hue2rgb(p, q, h + 1 / 3);
76
+ g = hue2rgb(p, q, h);
77
+ b = hue2rgb(p, q, h - 1 / 3);
78
+ }
79
+ return { r: Math.round(r * 255), g: Math.round(g * 255), b: Math.round(b * 255) };
80
+ }
81
+
82
+ function parseColor(colorStr) {
83
+ if (!colorStr) return null;
84
+ colorStr = colorStr.trim().toLowerCase();
85
+
86
+ if (colorStr === "transparent") {
87
+ return { r: 0, g: 0, b: 0, a: 0 };
88
+ }
89
+
90
+ // Hex
91
+ if (colorStr.startsWith("#")) {
92
+ const hex = colorStr.substring(1);
93
+ if (hex.length === 3 || hex.length === 4) {
94
+ const r = parseInt(hex[0] + hex[0], 16);
95
+ const g = parseInt(hex[1] + hex[1], 16);
96
+ const b = parseInt(hex[2] + hex[2], 16);
97
+ const a = hex.length === 4 ? parseInt(hex[3] + hex[3], 16) / 255 : 1;
98
+ return { r, g, b, a };
99
+ }
100
+ if (hex.length === 6 || hex.length === 8) {
101
+ const r = parseInt(hex.substring(0, 2), 16);
102
+ const g = parseInt(hex.substring(2, 4), 16);
103
+ const b = parseInt(hex.substring(4, 6), 16);
104
+ const a = hex.length === 8 ? parseInt(hex.substring(6, 8), 16) / 255 : 1;
105
+ return { r, g, b, a };
106
+ }
107
+ return null;
108
+ }
109
+
110
+ // rgb / rgba
111
+ const rgbMatch = colorStr.match(/rgba?\(\s*([\d.]+)(%?)\s*[\s,]\s*([\d.]+)(%?)\s*[\s,]\s*([\d.]+)(%?)(?:\s*[\s,/]\s*([\d.]+)(%?))?\s*\)/);
112
+ if (rgbMatch) {
113
+ let r = parseFloat(rgbMatch[1]);
114
+ if (rgbMatch[2] === "%") r = (r / 100) * 255;
115
+ let g = parseFloat(rgbMatch[3]);
116
+ if (rgbMatch[4] === "%") g = (g / 100) * 255;
117
+ let b = parseFloat(rgbMatch[5]);
118
+ if (rgbMatch[6] === "%") b = (b / 100) * 255;
119
+
120
+ let a = 1;
121
+ if (rgbMatch[7] !== undefined) {
122
+ a = parseFloat(rgbMatch[7]);
123
+ if (rgbMatch[8] === "%") a = a / 100;
124
+ }
125
+ return { r: Math.round(r), g: Math.round(g), b: Math.round(b), a };
126
+ }
127
+
128
+ // hsl / hsla
129
+ const hslMatch = colorStr.match(/hsla?\(\s*([\d.]+)(?:deg)?\s*[\s,]\s*([\d.]+)%\s*[\s,]\s*([\d.]+)%\s*(?:\s*[\s,/]\s*([\d.]+)(%?))?\s*\)/);
130
+ if (hslMatch) {
131
+ const h = parseFloat(hslMatch[1]);
132
+ const s = parseFloat(hslMatch[2]);
133
+ const l = parseFloat(hslMatch[3]);
134
+ let a = 1;
135
+ if (hslMatch[4] !== undefined) {
136
+ a = parseFloat(hslMatch[4]);
137
+ if (hslMatch[5] === "%") a = a / 100;
138
+ }
139
+ const rgb = hslToRgb(h, s, l);
140
+ return { ...rgb, a };
141
+ }
142
+
143
+ // Basic named colors fallback
144
+ const basicColors = {
145
+ white: { r: 255, g: 255, b: 255, a: 1 },
146
+ black: { r: 0, g: 0, b: 0, a: 1 },
147
+ red: { r: 255, g: 0, b: 0, a: 1 },
148
+ green: { r: 0, g: 128, b: 0, a: 1 },
149
+ blue: { r: 0, g: 0, b: 255, a: 1 },
150
+ yellow: { r: 255, g: 255, b: 0, a: 1 },
151
+ magenta: { r: 255, g: 0, b: 255, a: 1 },
152
+ cyan: { r: 0, g: 255, b: 255, a: 1 },
153
+ };
154
+
155
+ if (basicColors[colorStr]) {
156
+ return basicColors[colorStr];
157
+ }
158
+
159
+ return null;
160
+ }
161
+
162
+ function resolveColorMix(value) {
163
+ if (typeof value !== "string") return value;
164
+
165
+ let index;
166
+ while ((index = value.toLowerCase().indexOf("color-mix(")) !== -1) {
167
+ // Find matching closing parenthesis
168
+ let depth = 1;
169
+ let j = index + "color-mix(".length;
170
+ while (j < value.length && depth > 0) {
171
+ if (value[j] === "(") {
172
+ depth++;
173
+ } else if (value[j] === ")") {
174
+ depth--;
175
+ }
176
+ j++;
177
+ }
178
+
179
+ if (depth > 0) {
180
+ // Unmatched parenthesis, break to avoid infinite loop
181
+ break;
182
+ }
183
+
184
+ const innerContent = value.substring(index + "color-mix(".length, j - 1);
185
+
186
+ // Parse innerContent: "in <color-space>, <args>"
187
+ const commaIndex = innerContent.indexOf(",");
188
+ if (commaIndex === -1) {
189
+ // Invalid syntax, skip this one by replacing "color-mix(" with a placeholder temporarily
190
+ value = value.substring(0, index) + "COLOR_MIX_TEMP(" + value.substring(index + "color-mix(".length);
191
+ continue;
192
+ }
193
+
194
+ const argsPart = innerContent.substring(commaIndex + 1).trim();
195
+
196
+ // Check if argsPart contains another color-mix. If so, resolve the inner one first.
197
+ if (argsPart.toLowerCase().includes("color-mix(")) {
198
+ const resolvedArgsPart = resolveColorMix(argsPart);
199
+ value = value.substring(0, index + "color-mix(".length + commaIndex + 1) + " " + resolvedArgsPart + value.substring(j - 1);
200
+ continue;
201
+ }
202
+
203
+ // Now we can resolve this color-mix
204
+ const args = splitTopLevelCommas(argsPart);
205
+ if (args.length !== 2) {
206
+ // Invalid syntax
207
+ value = value.substring(0, index) + "COLOR_MIX_TEMP(" + value.substring(index + "color-mix(".length);
208
+ continue;
209
+ }
210
+
211
+ const arg1 = parseColorMixArg(args[0]);
212
+ const arg2 = parseColorMixArg(args[1]);
213
+
214
+ const c1 = parseColor(arg1.colorStr);
215
+ const c2 = parseColor(arg2.colorStr);
216
+
217
+ if (!c1 || !c2) {
218
+ // Parsing failed, skip
219
+ value = value.substring(0, index) + "COLOR_MIX_TEMP(" + value.substring(index + "color-mix(".length);
220
+ continue;
221
+ }
222
+
223
+ const p1 = arg1.percentage;
224
+ const p2 = arg2.percentage;
225
+
226
+ let w1, w2;
227
+ if (p1 !== null && p2 !== null) {
228
+ const sum = p1 + p2;
229
+ if (sum > 100) {
230
+ w1 = (p1 / sum) * 100;
231
+ w2 = (p2 / sum) * 100;
232
+ } else {
233
+ w1 = p1;
234
+ w2 = p2;
235
+ }
236
+ } else if (p1 !== null) {
237
+ w1 = p1;
238
+ w2 = 100 - p1;
239
+ } else if (p2 !== null) {
240
+ w2 = p2;
241
+ w1 = 100 - p2;
242
+ } else {
243
+ w1 = 50;
244
+ w2 = 50;
245
+ }
246
+
247
+ const a1 = c1.a;
248
+ const a2 = c2.a;
249
+
250
+ const totalWeight = w1 + w2;
251
+ let resolvedColor;
252
+ if (totalWeight === 0) {
253
+ resolvedColor = "transparent";
254
+ } else {
255
+ const f1 = w1 / totalWeight;
256
+ const f2 = w2 / totalWeight;
257
+
258
+ const mixedA = a1 * f1 + a2 * f2;
259
+
260
+ let mixedR, mixedG, mixedB;
261
+ if (mixedA === 0) {
262
+ mixedR = 0;
263
+ mixedG = 0;
264
+ mixedB = 0;
265
+ } else {
266
+ mixedR = Math.round((c1.r * a1 * f1 + c2.r * a2 * f2) / mixedA);
267
+ mixedG = Math.round((c1.g * a1 * f1 + c2.g * a2 * f2) / mixedA);
268
+ mixedB = Math.round((c1.b * a1 * f1 + c2.b * a2 * f2) / mixedA);
269
+ }
270
+
271
+ const finalA = totalWeight < 100 ? mixedA * (totalWeight / 100) : mixedA;
272
+ const roundedA = Math.round(finalA * 10000) / 10000;
273
+
274
+ resolvedColor = `rgba(${mixedR}, ${mixedG}, ${mixedB}, ${roundedA})`;
275
+ }
276
+
277
+ value = value.substring(0, index) + resolvedColor + value.substring(j);
278
+ }
279
+
280
+ // Restore placeholders
281
+ value = value.replace(/COLOR_MIX_TEMP\(/gi, "color-mix(");
282
+
283
+ return value;
284
+ }
285
+
286
+ export default function CssCalc() {
287
+ this.calc = (value) => {
288
+ if (value === undefined || typeof value !== "string") return value;
289
+
290
+ return value.replace(calcRe, (_, calc) => {
291
+ try {
292
+ // eslint-disable-next-line
293
+ const calcFunc = new Function(`return ${calc}`);
294
+ const calcValue = calcFunc();
295
+ return calcValue;
296
+ } catch {
297
+ return 0;
298
+ }
299
+ });
300
+ };
301
+
302
+ this.calcColor = (value) => {
303
+ if (value === undefined || typeof value !== "string") return value;
304
+
305
+ value = resolveColorMix(value);
306
+
307
+ value = value.replace(colorRe1, (_, r, g, b, a) => {
308
+ a = parseFloat(a);
309
+ if (isNaN(a) || a >= 1) return `#${itohex(r)}${itohex(g)}${itohex(b)}`;
310
+
311
+ a = Math.round(a * 255);
312
+ return `#${itohex(r)}${itohex(g)}${itohex(b)}${itohex(a)}`;
313
+ });
314
+ value = value.replace(colorRe2, (_, r, g, b, a) => {
315
+ a = parseFloat(a);
316
+ if (isNaN(a) || a >= 1) return `#${itohex(r)}${itohex(g)}${itohex(b)}`;
317
+
318
+ a = Math.round(a * 255);
319
+ return `#${itohex(r)}${itohex(g)}${itohex(b)}${itohex(a)}`;
320
+ });
321
+
322
+ return value;
323
+ };
324
+
325
+ return this;
326
+ }
@@ -0,0 +1,78 @@
1
+ import { camelize } from "../utils/helper";
2
+ import CssTransform from "./css-transform";
3
+
4
+ const SUPPORTED_MEDIA_TYPE = [
5
+ "min-width",
6
+ "min-height",
7
+ "max-width",
8
+ "max-height",
9
+ "prefers-color-scheme",
10
+ ];
11
+
12
+ export default function CssMedia() {
13
+ const transform = new CssTransform();
14
+
15
+ this.match = (media, { width, height, colorScheme } = {}) => {
16
+ const isMedia = media.startsWith("@media");
17
+ if (!isMedia) return [false];
18
+
19
+ let isValidMedia = true;
20
+ const widthRange = [Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY];
21
+ const heightRange = [Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY];
22
+
23
+ const mediaGroupsRe = /\([^)]*\)/g;
24
+ const mediaGroups = media.match(mediaGroupsRe)?.map((rawGroup) => {
25
+ const mediaGroupRe = /\(\s*([^:)\s]+)\s*:\s*([^:)\s]+)\s*\)/g;
26
+ let [, mediaType, mediaValue] = mediaGroupRe.exec(rawGroup) || [];
27
+
28
+ if (!SUPPORTED_MEDIA_TYPE.includes(mediaType)) {
29
+ isValidMedia = false;
30
+ return null;
31
+ }
32
+
33
+ mediaType = camelize(mediaType);
34
+
35
+ mediaValue = transform.transformUnsupportedUnit(mediaValue);
36
+ mediaValue = transform.transformViewportUnit(mediaValue, { width, height });
37
+ mediaValue = transform.removeUnit(mediaValue);
38
+
39
+ if (mediaType === "minWidth") {
40
+ widthRange[0] = mediaValue;
41
+ } else if (mediaType === "minHeight") {
42
+ heightRange[0] = mediaValue;
43
+ } else if (mediaType === "maxWidth") {
44
+ widthRange[1] = mediaValue;
45
+ } else if (mediaType === "maxHeight") {
46
+ heightRange[1] = mediaValue;
47
+ }
48
+
49
+ return [mediaType, mediaValue];
50
+ });
51
+ if (!Array.isArray(mediaGroups) || mediaGroups.length === 0) {
52
+ isValidMedia = false;
53
+ }
54
+ if (!isValidMedia) return [true, false];
55
+
56
+ if (mediaGroups[0][0] === "prefersColorScheme" && mediaGroups[0][1] === "dark") {
57
+ if (colorScheme === "dark") {
58
+ return [true, true];
59
+ } else {
60
+ return [true, false];
61
+ }
62
+ }
63
+
64
+ const isValidRange = widthRange[0] <= widthRange[1] && heightRange[0] <= heightRange[1];
65
+ if (!isValidRange) return [true, false];
66
+
67
+ const isMatchedMedia =
68
+ widthRange[0] <= width &&
69
+ width <= widthRange[1] &&
70
+ heightRange[0] <= height &&
71
+ height <= heightRange[1];
72
+ if (!isMatchedMedia) return [true, false];
73
+
74
+ return [true, true];
75
+ };
76
+
77
+ return this;
78
+ }