@sigx/lynx-zero 0.4.9 → 0.5.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.
@@ -1,68 +1,68 @@
1
- /**
2
- * Engine-side color mixing.
3
- *
4
- * Lynx's CSS engine has no `color-mix()` and can't alpha-compose `var()`
5
- * colors — but theme palettes are plain JS data, so tints can be computed
6
- * where the palette lives instead of in CSS. `registerTheme()` uses this to
7
- * materialize the `*-soft` tokens (see `./registry.ts`).
8
- *
9
- * Accepted inputs are the same "engine-safe" color strings themes already
10
- * use: `#rgb`, `#rrggbb`, `#rrggbbaa`, `rgb(r, g, b)`, `rgba(r, g, b, a)`.
11
- * Anything else (named colors, `oklch()`, `var()`) is not parseable here —
12
- * `mixColors` then falls back to the base color unchanged, which degrades to
13
- * a neutral surface rather than a wrong tint.
14
- */
15
-
16
- type Rgb = readonly [number, number, number];
17
-
18
- function parseColor(value: string): Rgb | undefined {
19
- const v = value.trim();
20
-
21
- if (v.startsWith('#')) {
22
- const hex = v.slice(1);
23
- if (hex.length === 3) {
24
- const r = parseInt(hex[0] + hex[0], 16);
25
- const g = parseInt(hex[1] + hex[1], 16);
26
- const b = parseInt(hex[2] + hex[2], 16);
27
- if ([r, g, b].some(Number.isNaN)) return undefined;
28
- return [r, g, b];
29
- }
30
- if (hex.length === 6 || hex.length === 8) {
31
- const r = parseInt(hex.slice(0, 2), 16);
32
- const g = parseInt(hex.slice(2, 4), 16);
33
- const b = parseInt(hex.slice(4, 6), 16);
34
- if ([r, g, b].some(Number.isNaN)) return undefined;
35
- return [r, g, b];
36
- }
37
- return undefined;
38
- }
39
-
40
- const m = /^rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*[\d.]+\s*)?\)$/i.exec(v);
41
- if (m) {
42
- const r = Number(m[1]);
43
- const g = Number(m[2]);
44
- const b = Number(m[3]);
45
- if ([r, g, b].some((n) => n > 255)) return undefined;
46
- return [r, g, b];
47
- }
48
-
49
- return undefined;
50
- }
51
-
52
- const toHex = (n: number): string => Math.round(n).toString(16).padStart(2, '0');
53
-
54
- /**
55
- * Mix `ratio` of `color` into `base` (linear sRGB per-channel, like
56
- * `color-mix(in srgb, color ratio, base)`), returning a hex string. If either
57
- * input can't be parsed, returns `base` unchanged.
58
- */
59
- export function mixColors(color: string, base: string, ratio: number): string {
60
- const fg = parseColor(color);
61
- const bg = parseColor(base);
62
- // Non-finite ratios (NaN softMix etc.) are unmixable — fall back to the
63
- // base rather than emitting `#NaNNaNNaN`.
64
- if (!fg || !bg || !Number.isFinite(ratio)) return base;
65
- const t = Math.min(1, Math.max(0, ratio));
66
- const mix = (i: 0 | 1 | 2) => fg[i] * t + bg[i] * (1 - t);
67
- return `#${toHex(mix(0))}${toHex(mix(1))}${toHex(mix(2))}`;
68
- }
1
+ /**
2
+ * Engine-side color mixing.
3
+ *
4
+ * Lynx's CSS engine has no `color-mix()` and can't alpha-compose `var()`
5
+ * colors — but theme palettes are plain JS data, so tints can be computed
6
+ * where the palette lives instead of in CSS. `registerTheme()` uses this to
7
+ * materialize the `*-soft` tokens (see `./registry.ts`).
8
+ *
9
+ * Accepted inputs are the same "engine-safe" color strings themes already
10
+ * use: `#rgb`, `#rrggbb`, `#rrggbbaa`, `rgb(r, g, b)`, `rgba(r, g, b, a)`.
11
+ * Anything else (named colors, `oklch()`, `var()`) is not parseable here —
12
+ * `mixColors` then falls back to the base color unchanged, which degrades to
13
+ * a neutral surface rather than a wrong tint.
14
+ */
15
+
16
+ type Rgb = readonly [number, number, number];
17
+
18
+ function parseColor(value: string): Rgb | undefined {
19
+ const v = value.trim();
20
+
21
+ if (v.startsWith('#')) {
22
+ const hex = v.slice(1);
23
+ if (hex.length === 3) {
24
+ const r = parseInt(hex[0] + hex[0], 16);
25
+ const g = parseInt(hex[1] + hex[1], 16);
26
+ const b = parseInt(hex[2] + hex[2], 16);
27
+ if ([r, g, b].some(Number.isNaN)) return undefined;
28
+ return [r, g, b];
29
+ }
30
+ if (hex.length === 6 || hex.length === 8) {
31
+ const r = parseInt(hex.slice(0, 2), 16);
32
+ const g = parseInt(hex.slice(2, 4), 16);
33
+ const b = parseInt(hex.slice(4, 6), 16);
34
+ if ([r, g, b].some(Number.isNaN)) return undefined;
35
+ return [r, g, b];
36
+ }
37
+ return undefined;
38
+ }
39
+
40
+ const m = /^rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*[\d.]+\s*)?\)$/i.exec(v);
41
+ if (m) {
42
+ const r = Number(m[1]);
43
+ const g = Number(m[2]);
44
+ const b = Number(m[3]);
45
+ if ([r, g, b].some((n) => n > 255)) return undefined;
46
+ return [r, g, b];
47
+ }
48
+
49
+ return undefined;
50
+ }
51
+
52
+ const toHex = (n: number): string => Math.round(n).toString(16).padStart(2, '0');
53
+
54
+ /**
55
+ * Mix `ratio` of `color` into `base` (linear sRGB per-channel, like
56
+ * `color-mix(in srgb, color ratio, base)`), returning a hex string. If either
57
+ * input can't be parsed, returns `base` unchanged.
58
+ */
59
+ export function mixColors(color: string, base: string, ratio: number): string {
60
+ const fg = parseColor(color);
61
+ const bg = parseColor(base);
62
+ // Non-finite ratios (NaN softMix etc.) are unmixable — fall back to the
63
+ // base rather than emitting `#NaNNaNNaN`.
64
+ if (!fg || !bg || !Number.isFinite(ratio)) return base;
65
+ const t = Math.min(1, Math.max(0, ratio));
66
+ const mix = (i: 0 | 1 | 2) => fg[i] * t + bg[i] * (1 - t);
67
+ return `#${toHex(mix(0))}${toHex(mix(1))}${toHex(mix(2))}`;
68
+ }