@ng-native/fabric 0.0.1 → 0.1.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.
- package/LICENSE +21 -0
- package/README.md +54 -1
- package/dist/color-mix.d.ts +20 -0
- package/dist/color-mix.js +306 -0
- package/dist/color-mix.js.map +1 -0
- package/dist/css.d.ts +615 -0
- package/dist/css.js +1405 -0
- package/dist/css.js.map +1 -0
- package/dist/defer-triggers.d.ts +53 -0
- package/dist/defer-triggers.js +184 -0
- package/dist/defer-triggers.js.map +1 -0
- package/dist/engine.d.ts +1066 -0
- package/dist/engine.js +2764 -0
- package/dist/engine.js.map +1 -0
- package/dist/fabric.d.ts +20 -0
- package/dist/fabric.js +44 -0
- package/dist/fabric.js.map +1 -0
- package/dist/forms-inputs.d.ts +15 -0
- package/dist/forms-inputs.js +54 -0
- package/dist/forms-inputs.js.map +1 -0
- package/dist/host.d.ts +133 -0
- package/dist/host.js +55 -0
- package/dist/host.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/inline-token.d.ts +12 -0
- package/dist/inline-token.js +52 -0
- package/dist/inline-token.js.map +1 -0
- package/dist/inline-transform.d.ts +14 -0
- package/dist/inline-transform.js +41 -0
- package/dist/inline-transform.js.map +1 -0
- package/dist/native-drive.d.ts +100 -0
- package/dist/native-drive.js +318 -0
- package/dist/native-drive.js.map +1 -0
- package/dist/relative-colour.d.ts +17 -0
- package/dist/relative-colour.js +71 -0
- package/dist/relative-colour.js.map +1 -0
- package/dist/scroll-animation.d.ts +41 -0
- package/dist/scroll-animation.js +169 -0
- package/dist/scroll-animation.js.map +1 -0
- package/dist/transition.d.ts +139 -0
- package/dist/transition.js +569 -0
- package/dist/transition.js.map +1 -0
- package/package.json +43 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ashley Hunter
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,56 @@
|
|
|
1
1
|
# @ng-native/fabric
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The framework-agnostic engine underneath Angular Native: a retained view tree, a commit step into
|
|
4
|
+
React Native's Fabric, and a CSS engine with a real cascade. No `@angular/core` import anywhere in
|
|
5
|
+
it - that boundary is enforced by lint, not just convention.
|
|
6
|
+
|
|
7
|
+
Alpha: APIs may change before 1.0.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
Most apps never depend on this directly; `@ng-native/platform` and `@ng-native/components` pull it
|
|
12
|
+
in. Install it explicitly only to call `nativePlatform()` or `registerViewName()` yourself:
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npm install @ng-native/fabric
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Example
|
|
19
|
+
|
|
20
|
+
Registering a third-party Fabric component that ships no Angular bindings of its own:
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { registerViewName } from '@ng-native/fabric';
|
|
24
|
+
|
|
25
|
+
registerViewName('rns-screen', 'RNSScreen');
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Reading the platform in a component that needs to answer differently per platform:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { nativePlatform } from '@ng-native/fabric';
|
|
32
|
+
|
|
33
|
+
const platform = nativePlatform(); // 'ios' | 'android'
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## What's in the package
|
|
37
|
+
|
|
38
|
+
- `getFabricUIManager()` - reads `global.nativeFabricUIManager`, passed to `mount()`.
|
|
39
|
+
- `registerViewName()` / `registerPlatformComponents()` - map element and third-party component
|
|
40
|
+
names onto Fabric's native components.
|
|
41
|
+
- `nativePlatform()` - the current platform, without importing `react-native`'s `Platform`.
|
|
42
|
+
- The CSS engine that `@ng-native/metro` compiles component styles for, and that
|
|
43
|
+
`@ng-native/tailwind` compiles utility classes for.
|
|
44
|
+
|
|
45
|
+
## Docs
|
|
46
|
+
|
|
47
|
+
- [Fabric](https://ng-native.com/packages/fabric)
|
|
48
|
+
- [The CSS engine](https://ng-native.com/packages/fabric/css-engine),
|
|
49
|
+
[what CSS reaches a device](https://ng-native.com/packages/fabric/supported-css) and
|
|
50
|
+
[animation](https://ng-native.com/packages/fabric/animation)
|
|
51
|
+
- [Root README](https://github.com/ng-native/ng-native/blob/main/README.md) and
|
|
52
|
+
[ARCHITECTURE.md](https://github.com/ng-native/ng-native/blob/main/ARCHITECTURE.md)
|
|
53
|
+
|
|
54
|
+
## License
|
|
55
|
+
|
|
56
|
+
MIT
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** The spaces a mix is worked out in: every one the compiler also folds a literal mix in. */
|
|
2
|
+
export type MixSpace = 'srgb' | 'oklab' | 'oklch' | 'lab' | 'lch' | 'hsl' | 'hwb';
|
|
3
|
+
export declare const MIX_SPACES: ReadonlySet<string>;
|
|
4
|
+
export type HueMethod = 'shorter' | 'longer' | 'increasing' | 'decreasing';
|
|
5
|
+
export type Vector = [number, number, number];
|
|
6
|
+
/** An sRGB colour, channels 0 to 1, in the space's own coordinates. */
|
|
7
|
+
export declare function into(space: MixSpace, rgb: Vector): Vector;
|
|
8
|
+
/** Back to sRGB, 0 to 1, brought inside the gamut where the space reaches past it. */
|
|
9
|
+
export declare function outOf(space: MixSpace, coords: Vector): Vector;
|
|
10
|
+
/** One side of a mix: a colour as native paints it, and the percentage written beside it. */
|
|
11
|
+
export interface MixSide {
|
|
12
|
+
readonly colour: string;
|
|
13
|
+
readonly percentage?: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* `color-mix(in <space> [<hue> hue], <a> [p%], <b> [q%])`, as the `rgb()` or `rgba()` native
|
|
17
|
+
* reads. Undefined when a side is not a colour it can read - a platform colour, say - or when the
|
|
18
|
+
* two percentages come to zero, which CSS says is not a colour at all.
|
|
19
|
+
*/
|
|
20
|
+
export declare function mixColours(space: MixSpace, a: MixSide, b: MixSide, hue?: HueMethod): string | undefined;
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `color-mix()` worked out on device, for a mix with a token in it.
|
|
3
|
+
*
|
|
4
|
+
* The compiler folds a mix of literal colours at build time (lightningcss does the arithmetic); a
|
|
5
|
+
* mix with a `var()` in it can only be done once the token is known, so it is done here, with CSS
|
|
6
|
+
* Color 4 and 5's own arithmetic: each colour converted into the named space, premultiplied by its
|
|
7
|
+
* alpha, interpolated (a hue the shorter way round), and brought back to the sRGB native paints,
|
|
8
|
+
* inside its gamut by CSS Color 4's chroma reduction rather than a clip.
|
|
9
|
+
*
|
|
10
|
+
* The matrices and the gamut mapping are the ones `@ng-native/metro`'s `color-spaces.cjs` uses to
|
|
11
|
+
* convert a literal colour at build time, copied here because the engine cannot depend on the
|
|
12
|
+
* build step; the tests hold the two to the same answers.
|
|
13
|
+
*/
|
|
14
|
+
import { parseColor } from "./transition.js";
|
|
15
|
+
export const MIX_SPACES = new Set([
|
|
16
|
+
'srgb',
|
|
17
|
+
'oklab',
|
|
18
|
+
'oklch',
|
|
19
|
+
'lab',
|
|
20
|
+
'lch',
|
|
21
|
+
'hsl',
|
|
22
|
+
'hwb',
|
|
23
|
+
]);
|
|
24
|
+
const times = (m, [x, y, z]) => m.map(([a, b, c]) => a * x + b * y + c * z);
|
|
25
|
+
const OKLAB_TO_LMS = [
|
|
26
|
+
[1.0, 0.3963377773761749, 0.2158037573099136],
|
|
27
|
+
[1.0, -0.1055613458156586, -0.0638541728258133],
|
|
28
|
+
[1.0, -0.0894841775298119, -1.2914855480194092],
|
|
29
|
+
];
|
|
30
|
+
const LMS_TO_XYZ = [
|
|
31
|
+
[1.2268798758459243, -0.5578149944602171, 0.2813910456659647],
|
|
32
|
+
[-0.0405757452148008, 1.112286803280317, -0.0717110580655164],
|
|
33
|
+
[-0.0763729366746601, -0.4214933324022432, 1.5869240198367816],
|
|
34
|
+
];
|
|
35
|
+
const XYZ_TO_LMS = [
|
|
36
|
+
[0.819022437996703, 0.3619062600528904, -0.1288737815209879],
|
|
37
|
+
[0.0329836539323885, 0.9292868615863434, 0.0361446663506424],
|
|
38
|
+
[0.0481771893596242, 0.2642395317527308, 0.6335478284694309],
|
|
39
|
+
];
|
|
40
|
+
const LMS_TO_OKLAB = [
|
|
41
|
+
[0.210454268309314, 0.7936177747023054, -0.0040720430116193],
|
|
42
|
+
[1.9779985324311684, -2.4285922420485799, 0.450593709617411],
|
|
43
|
+
[0.0259040424655478, 0.7827717124575296, -0.8086757549230774],
|
|
44
|
+
];
|
|
45
|
+
const XYZ_TO_LINEAR_SRGB = [
|
|
46
|
+
[3.2409699419045226, -1.537383177570094, -0.4986107602930034],
|
|
47
|
+
[-0.9692436362808796, 1.8759675015077202, 0.04155505740717559],
|
|
48
|
+
[0.05563007969699366, -0.20397695888897652, 1.0569715142428786],
|
|
49
|
+
];
|
|
50
|
+
const LINEAR_SRGB_TO_XYZ = [
|
|
51
|
+
[0.41239079926595934, 0.357584339383878, 0.1804807884018343],
|
|
52
|
+
[0.21263900587151027, 0.715168678767756, 0.07219231536073371],
|
|
53
|
+
[0.01933081871559182, 0.11919477979462598, 0.9505321522496607],
|
|
54
|
+
];
|
|
55
|
+
/** Bradford chromatic adaptation between `lab()`'s D50 white and sRGB's D65. */
|
|
56
|
+
const D50_TO_D65 = [
|
|
57
|
+
[0.955473421488075, -0.02309845494876471, 0.06325924320057072],
|
|
58
|
+
[-0.0283697093338637, 1.0099953980813041, 0.021041441191917323],
|
|
59
|
+
[0.012314014864481998, -0.020507649298898964, 1.330365926242124],
|
|
60
|
+
];
|
|
61
|
+
const D65_TO_D50 = [
|
|
62
|
+
[1.0479298208405488, 0.022946793341019088, -0.05019222954313557],
|
|
63
|
+
[0.029627815688159344, 0.990434484573249, -0.01707382502938514],
|
|
64
|
+
[-0.009243058152591178, 0.015055144896577895, 0.7518742899580008],
|
|
65
|
+
];
|
|
66
|
+
const D50_WHITE = [0.3457 / 0.3585, 1, (1 - 0.3457 - 0.3585) / 0.3585];
|
|
67
|
+
const encode = (c) => Math.sign(c) * (Math.abs(c) > 0.0031308 ? 1.055 * Math.abs(c) ** (1 / 2.4) - 0.055 : 12.92 * c);
|
|
68
|
+
const decode = (c) => Math.sign(c) * (Math.abs(c) > 0.04045 ? ((Math.abs(c) + 0.055) / 1.055) ** 2.4 : c / 12.92);
|
|
69
|
+
const srgbToXyz = (rgb) => times(LINEAR_SRGB_TO_XYZ, rgb.map(decode));
|
|
70
|
+
const xyzToSrgb = (xyz) => times(XYZ_TO_LINEAR_SRGB, xyz).map(encode);
|
|
71
|
+
const xyzToOklab = (xyz) => times(LMS_TO_OKLAB, times(XYZ_TO_LMS, xyz).map(Math.cbrt));
|
|
72
|
+
const oklabToXyz = (lab) => times(LMS_TO_XYZ, times(OKLAB_TO_LMS, lab).map((c) => c ** 3));
|
|
73
|
+
const KAPPA = 24389 / 27;
|
|
74
|
+
const EPSILON = 216 / 24389;
|
|
75
|
+
function xyzToLab(xyz) {
|
|
76
|
+
const [x, y, z] = times(D65_TO_D50, xyz).map((c, i) => c / D50_WHITE[i]);
|
|
77
|
+
const f = (c) => (c > EPSILON ? Math.cbrt(c) : (KAPPA * c + 16) / 116);
|
|
78
|
+
const [fx, fy, fz] = [f(x), f(y), f(z)];
|
|
79
|
+
return [116 * fy - 16, 500 * (fx - fy), 200 * (fy - fz)];
|
|
80
|
+
}
|
|
81
|
+
function labToXyz([l, a, b]) {
|
|
82
|
+
const fy = (l + 16) / 116;
|
|
83
|
+
const fx = a / 500 + fy;
|
|
84
|
+
const fz = fy - b / 200;
|
|
85
|
+
const x = fx ** 3 > EPSILON ? fx ** 3 : (116 * fx - 16) / KAPPA;
|
|
86
|
+
const y = l > KAPPA * EPSILON ? fy ** 3 : l / KAPPA;
|
|
87
|
+
const z = fz ** 3 > EPSILON ? fz ** 3 : (116 * fz - 16) / KAPPA;
|
|
88
|
+
return times(D50_TO_D65, [x, y, z].map((c, i) => c * D50_WHITE[i]));
|
|
89
|
+
}
|
|
90
|
+
/** Lightness, chroma and hue in degrees, from the rectangular form, and back. */
|
|
91
|
+
const polar = ([l, a, b]) => {
|
|
92
|
+
const hue = (Math.atan2(b, a) * 180) / Math.PI;
|
|
93
|
+
return [l, Math.hypot(a, b), hue < 0 ? hue + 360 : hue];
|
|
94
|
+
};
|
|
95
|
+
const rectangular = ([l, c, h]) => [
|
|
96
|
+
l,
|
|
97
|
+
c * Math.cos((h * Math.PI) / 180),
|
|
98
|
+
c * Math.sin((h * Math.PI) / 180),
|
|
99
|
+
];
|
|
100
|
+
function srgbToHsl([r, g, b]) {
|
|
101
|
+
const max = Math.max(r, g, b);
|
|
102
|
+
const min = Math.min(r, g, b);
|
|
103
|
+
const l = (max + min) / 2;
|
|
104
|
+
const d = max - min;
|
|
105
|
+
if (d === 0)
|
|
106
|
+
return [NaN, 0, l];
|
|
107
|
+
const s = l === 0 || l === 1 ? 0 : (max - l) / Math.min(l, 1 - l);
|
|
108
|
+
const h = max === r
|
|
109
|
+
? ((g - b) / d + (g < b ? 6 : 0)) * 60
|
|
110
|
+
: max === g
|
|
111
|
+
? ((b - r) / d + 2) * 60
|
|
112
|
+
: ((r - g) / d + 4) * 60;
|
|
113
|
+
return [h, s, l];
|
|
114
|
+
}
|
|
115
|
+
function hslToSrgb([h, s, l]) {
|
|
116
|
+
const hue = Number.isFinite(h) ? ((h % 360) + 360) % 360 : 0;
|
|
117
|
+
const f = (n) => {
|
|
118
|
+
const k = (n + hue / 30) % 12;
|
|
119
|
+
return l - s * Math.min(l, 1 - l) * Math.max(-1, Math.min(k - 3, 9 - k, 1));
|
|
120
|
+
};
|
|
121
|
+
return [f(0), f(8), f(4)];
|
|
122
|
+
}
|
|
123
|
+
function srgbToHwb(rgb) {
|
|
124
|
+
const [h] = srgbToHsl(rgb);
|
|
125
|
+
return [h, Math.min(...rgb), 1 - Math.max(...rgb)];
|
|
126
|
+
}
|
|
127
|
+
function hwbToSrgb([h, w, b]) {
|
|
128
|
+
if (w + b >= 1) {
|
|
129
|
+
const grey = w / (w + b);
|
|
130
|
+
return [grey, grey, grey];
|
|
131
|
+
}
|
|
132
|
+
return hslToSrgb([h, 1, 0.5]).map((c) => c * (1 - w - b) + w);
|
|
133
|
+
}
|
|
134
|
+
/** Where the hue sits in each space's coordinates, if it has one. */
|
|
135
|
+
const HUE_INDEX = { oklch: 2, lch: 2, hsl: 0, hwb: 0 };
|
|
136
|
+
/** An sRGB colour, channels 0 to 1, in the space's own coordinates. */
|
|
137
|
+
export function into(space, rgb) {
|
|
138
|
+
switch (space) {
|
|
139
|
+
case 'srgb':
|
|
140
|
+
return rgb;
|
|
141
|
+
case 'oklab':
|
|
142
|
+
return xyzToOklab(srgbToXyz(rgb));
|
|
143
|
+
case 'oklch':
|
|
144
|
+
return powerless(polar(xyzToOklab(srgbToXyz(rgb))), 0.000004);
|
|
145
|
+
case 'lab':
|
|
146
|
+
return xyzToLab(srgbToXyz(rgb));
|
|
147
|
+
case 'lch':
|
|
148
|
+
return powerless(polar(xyzToLab(srgbToXyz(rgb))), 0.0015);
|
|
149
|
+
case 'hsl':
|
|
150
|
+
return srgbToHsl(rgb);
|
|
151
|
+
case 'hwb':
|
|
152
|
+
return srgbToHwb(rgb);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/** A hue that does not matter, for a colour with no chroma to speak of, is missing, as CSS says. */
|
|
156
|
+
const powerless = ([l, c, h], threshold) => [
|
|
157
|
+
l,
|
|
158
|
+
c,
|
|
159
|
+
c <= threshold ? NaN : h,
|
|
160
|
+
];
|
|
161
|
+
/** Back to sRGB, 0 to 1, brought inside the gamut where the space reaches past it. */
|
|
162
|
+
export function outOf(space, coords) {
|
|
163
|
+
const finite = coords.map((c) => (Number.isFinite(c) ? c : 0));
|
|
164
|
+
switch (space) {
|
|
165
|
+
case 'srgb':
|
|
166
|
+
return clip(finite);
|
|
167
|
+
case 'oklab':
|
|
168
|
+
return gamutMap(finite);
|
|
169
|
+
case 'oklch':
|
|
170
|
+
return gamutMap(rectangular(finite));
|
|
171
|
+
case 'lab':
|
|
172
|
+
return gamutMap(xyzToOklab(labToXyz(finite)));
|
|
173
|
+
case 'lch':
|
|
174
|
+
return gamutMap(xyzToOklab(labToXyz(rectangular(finite))));
|
|
175
|
+
case 'hsl':
|
|
176
|
+
return clip(hslToSrgb(finite));
|
|
177
|
+
case 'hwb':
|
|
178
|
+
return clip(hwbToSrgb(finite));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const clip = (rgb) => rgb.map((c) => Math.min(1, Math.max(0, c)));
|
|
182
|
+
const inGamut = (rgb) => rgb.every((c) => c >= -1e-6 && c <= 1 + 1e-6);
|
|
183
|
+
const deltaEOK = (a, b) => Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]);
|
|
184
|
+
/** CSS Color 4 section 13.2.2: reduce OKLCh chroma until clipping no longer shows. */
|
|
185
|
+
function gamutMap(oklab) {
|
|
186
|
+
const origin = polar(oklab);
|
|
187
|
+
if (origin[0] >= 1)
|
|
188
|
+
return [1, 1, 1];
|
|
189
|
+
if (origin[0] <= 0)
|
|
190
|
+
return [0, 0, 0];
|
|
191
|
+
const srgbOf = (lch) => xyzToSrgb(oklabToXyz(rectangular(lch)));
|
|
192
|
+
const rgb = srgbOf(origin);
|
|
193
|
+
if (inGamut(rgb))
|
|
194
|
+
return clip(rgb);
|
|
195
|
+
const JND = 0.02;
|
|
196
|
+
const EPS = 0.0001;
|
|
197
|
+
let clipped = clip(rgb);
|
|
198
|
+
if (deltaEOK(xyzToOklab(srgbToXyz(clipped)), oklab) < JND)
|
|
199
|
+
return clipped;
|
|
200
|
+
let min = 0;
|
|
201
|
+
let max = origin[1];
|
|
202
|
+
let minInGamut = true;
|
|
203
|
+
while (max - min > EPS) {
|
|
204
|
+
const chroma = (min + max) / 2;
|
|
205
|
+
const current = [origin[0], chroma, origin[2]];
|
|
206
|
+
const converted = srgbOf(current);
|
|
207
|
+
if (minInGamut && inGamut(converted)) {
|
|
208
|
+
min = chroma;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
clipped = clip(converted);
|
|
212
|
+
const error = deltaEOK(xyzToOklab(srgbToXyz(clipped)), rectangular(current));
|
|
213
|
+
if (error < JND) {
|
|
214
|
+
if (JND - error < EPS)
|
|
215
|
+
return clipped;
|
|
216
|
+
minInGamut = false;
|
|
217
|
+
min = chroma;
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
max = chroma;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return clipped;
|
|
224
|
+
}
|
|
225
|
+
/** Two hues, one moved by whole turns so that interpolating between them goes the way asked. */
|
|
226
|
+
function fixHues(a, b, method) {
|
|
227
|
+
const delta = b - a;
|
|
228
|
+
const lift = HUE_LIFTS[method](delta);
|
|
229
|
+
return lift === 'a' ? [a + 360, b] : lift === 'b' ? [a, b + 360] : [a, b];
|
|
230
|
+
}
|
|
231
|
+
/** For each method, which of the two hues takes a whole turn more, given how far apart they are. */
|
|
232
|
+
const HUE_LIFTS = {
|
|
233
|
+
shorter: (delta) => (delta > 180 ? 'a' : delta < -180 ? 'b' : null),
|
|
234
|
+
longer: (delta) => (delta > 0 && delta < 180 ? 'a' : delta > -180 && delta <= 0 ? 'b' : null),
|
|
235
|
+
increasing: (delta) => (delta < 0 ? 'b' : null),
|
|
236
|
+
decreasing: (delta) => (delta > 0 ? 'a' : null),
|
|
237
|
+
};
|
|
238
|
+
/**
|
|
239
|
+
* `color-mix(in <space> [<hue> hue], <a> [p%], <b> [q%])`, as the `rgb()` or `rgba()` native
|
|
240
|
+
* reads. Undefined when a side is not a colour it can read - a platform colour, say - or when the
|
|
241
|
+
* two percentages come to zero, which CSS says is not a colour at all.
|
|
242
|
+
*/
|
|
243
|
+
export function mixColours(space, a, b, hue = 'shorter') {
|
|
244
|
+
const first = parseColor(a.colour);
|
|
245
|
+
const second = parseColor(b.colour);
|
|
246
|
+
const weights = weightsOf(a.percentage, b.percentage);
|
|
247
|
+
if (!first || !second || !weights)
|
|
248
|
+
return undefined;
|
|
249
|
+
const [c1, c2] = [first, second].map(([r, g, bl, alpha]) => ({
|
|
250
|
+
coords: into(space, [r / 255, g / 255, bl / 255]),
|
|
251
|
+
alpha,
|
|
252
|
+
}));
|
|
253
|
+
const hueAt = HUE_INDEX[space];
|
|
254
|
+
alignComponents(c1, c2, hueAt, hue);
|
|
255
|
+
const { t, alphaScale } = weights;
|
|
256
|
+
const alpha = c1.alpha * (1 - t) + c2.alpha * t;
|
|
257
|
+
const mixed = interpolate(c1, c2, t, alpha, hueAt);
|
|
258
|
+
const [r, g, bl] = outOf(space, mixed).map((c) => Math.round(c * 255));
|
|
259
|
+
const opacity = Math.round(alpha * alphaScale * 1000) / 1000;
|
|
260
|
+
return opacity >= 1 ? `rgb(${r}, ${g}, ${bl})` : `rgba(${r}, ${g}, ${bl}, ${opacity})`;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* CSS Color 5, 2.1: missing percentages take what the other leaves, or half each; the second
|
|
264
|
+
* colour's share is `t`, and a pair summing under 100 scales the alpha down.
|
|
265
|
+
*/
|
|
266
|
+
function weightsOf(p, q) {
|
|
267
|
+
const [first, second] = p === undefined && q === undefined
|
|
268
|
+
? [50, 50]
|
|
269
|
+
: p === undefined
|
|
270
|
+
? [100 - q, q]
|
|
271
|
+
: q === undefined
|
|
272
|
+
? [p, 100 - p]
|
|
273
|
+
: [p, q];
|
|
274
|
+
const sum = first + second;
|
|
275
|
+
if (sum <= 0)
|
|
276
|
+
return null;
|
|
277
|
+
return { t: second / sum, alphaScale: sum < 100 ? sum / 100 : 1 };
|
|
278
|
+
}
|
|
279
|
+
/** A missing component takes the other colour's, and the hues are set to go the way asked. */
|
|
280
|
+
function alignComponents(c1, c2, hueAt, hue) {
|
|
281
|
+
for (let i = 0; i < 3; i++) {
|
|
282
|
+
if (Number.isNaN(c1.coords[i]))
|
|
283
|
+
c1.coords[i] = c2.coords[i];
|
|
284
|
+
if (Number.isNaN(c2.coords[i]))
|
|
285
|
+
c2.coords[i] = c1.coords[i];
|
|
286
|
+
}
|
|
287
|
+
if (hueAt === undefined)
|
|
288
|
+
return;
|
|
289
|
+
const [h1, h2] = [c1.coords[hueAt], c2.coords[hueAt]];
|
|
290
|
+
if (Number.isFinite(h1) && Number.isFinite(h2)) {
|
|
291
|
+
[c1.coords[hueAt], c2.coords[hueAt]] = fixHues(h1, h2, hue);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
/** Premultiplied by alpha, except the hue, which is an angle and not an amount. */
|
|
295
|
+
function interpolate(c1, c2, t, alpha, hueAt) {
|
|
296
|
+
const mixed = [0, 1, 2].map((i) => {
|
|
297
|
+
if (i === hueAt)
|
|
298
|
+
return c1.coords[i] * (1 - t) + c2.coords[i] * t;
|
|
299
|
+
const premixed = c1.coords[i] * c1.alpha * (1 - t) + c2.coords[i] * c2.alpha * t;
|
|
300
|
+
return alpha === 0 ? 0 : premixed / alpha;
|
|
301
|
+
});
|
|
302
|
+
if (hueAt !== undefined)
|
|
303
|
+
mixed[hueAt] = ((mixed[hueAt] % 360) + 360) % 360;
|
|
304
|
+
return mixed;
|
|
305
|
+
}
|
|
306
|
+
//# sourceMappingURL=color-mix.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"color-mix.js","sourceRoot":"","sources":["../src/color-mix.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `color-mix()` worked out on device, for a mix with a token in it.\n *\n * The compiler folds a mix of literal colours at build time (lightningcss does the arithmetic); a\n * mix with a `var()` in it can only be done once the token is known, so it is done here, with CSS\n * Color 4 and 5's own arithmetic: each colour converted into the named space, premultiplied by its\n * alpha, interpolated (a hue the shorter way round), and brought back to the sRGB native paints,\n * inside its gamut by CSS Color 4's chroma reduction rather than a clip.\n *\n * The matrices and the gamut mapping are the ones `@ng-native/metro`'s `color-spaces.cjs` uses to\n * convert a literal colour at build time, copied here because the engine cannot depend on the\n * build step; the tests hold the two to the same answers.\n */\nimport { parseColor } from './transition.ts';\n\n/** The spaces a mix is worked out in: every one the compiler also folds a literal mix in. */\nexport type MixSpace = 'srgb' | 'oklab' | 'oklch' | 'lab' | 'lch' | 'hsl' | 'hwb';\n\nexport const MIX_SPACES: ReadonlySet<string> = new Set<MixSpace>([\n 'srgb',\n 'oklab',\n 'oklch',\n 'lab',\n 'lch',\n 'hsl',\n 'hwb',\n]);\n\nexport type HueMethod = 'shorter' | 'longer' | 'increasing' | 'decreasing';\n\nexport type Vector = [number, number, number];\n\nconst times = (m: readonly Vector[], [x, y, z]: Vector): Vector =>\n m.map(([a, b, c]) => a * x + b * y + c * z) as Vector;\n\nconst OKLAB_TO_LMS: Vector[] = [\n [1.0, 0.3963377773761749, 0.2158037573099136],\n [1.0, -0.1055613458156586, -0.0638541728258133],\n [1.0, -0.0894841775298119, -1.2914855480194092],\n];\nconst LMS_TO_XYZ: Vector[] = [\n [1.2268798758459243, -0.5578149944602171, 0.2813910456659647],\n [-0.0405757452148008, 1.112286803280317, -0.0717110580655164],\n [-0.0763729366746601, -0.4214933324022432, 1.5869240198367816],\n];\nconst XYZ_TO_LMS: Vector[] = [\n [0.819022437996703, 0.3619062600528904, -0.1288737815209879],\n [0.0329836539323885, 0.9292868615863434, 0.0361446663506424],\n [0.0481771893596242, 0.2642395317527308, 0.6335478284694309],\n];\nconst LMS_TO_OKLAB: Vector[] = [\n [0.210454268309314, 0.7936177747023054, -0.0040720430116193],\n [1.9779985324311684, -2.4285922420485799, 0.450593709617411],\n [0.0259040424655478, 0.7827717124575296, -0.8086757549230774],\n];\nconst XYZ_TO_LINEAR_SRGB: Vector[] = [\n [3.2409699419045226, -1.537383177570094, -0.4986107602930034],\n [-0.9692436362808796, 1.8759675015077202, 0.04155505740717559],\n [0.05563007969699366, -0.20397695888897652, 1.0569715142428786],\n];\nconst LINEAR_SRGB_TO_XYZ: Vector[] = [\n [0.41239079926595934, 0.357584339383878, 0.1804807884018343],\n [0.21263900587151027, 0.715168678767756, 0.07219231536073371],\n [0.01933081871559182, 0.11919477979462598, 0.9505321522496607],\n];\n/** Bradford chromatic adaptation between `lab()`'s D50 white and sRGB's D65. */\nconst D50_TO_D65: Vector[] = [\n [0.955473421488075, -0.02309845494876471, 0.06325924320057072],\n [-0.0283697093338637, 1.0099953980813041, 0.021041441191917323],\n [0.012314014864481998, -0.020507649298898964, 1.330365926242124],\n];\nconst D65_TO_D50: Vector[] = [\n [1.0479298208405488, 0.022946793341019088, -0.05019222954313557],\n [0.029627815688159344, 0.990434484573249, -0.01707382502938514],\n [-0.009243058152591178, 0.015055144896577895, 0.7518742899580008],\n];\nconst D50_WHITE: Vector = [0.3457 / 0.3585, 1, (1 - 0.3457 - 0.3585) / 0.3585];\n\nconst encode = (c: number) =>\n Math.sign(c) * (Math.abs(c) > 0.0031308 ? 1.055 * Math.abs(c) ** (1 / 2.4) - 0.055 : 12.92 * c);\nconst decode = (c: number) =>\n Math.sign(c) * (Math.abs(c) > 0.04045 ? ((Math.abs(c) + 0.055) / 1.055) ** 2.4 : c / 12.92);\n\nconst srgbToXyz = (rgb: Vector) => times(LINEAR_SRGB_TO_XYZ, rgb.map(decode) as Vector);\nconst xyzToSrgb = (xyz: Vector) => times(XYZ_TO_LINEAR_SRGB, xyz).map(encode) as Vector;\nconst xyzToOklab = (xyz: Vector) =>\n times(LMS_TO_OKLAB, times(XYZ_TO_LMS, xyz).map(Math.cbrt) as Vector);\nconst oklabToXyz = (lab: Vector) =>\n times(LMS_TO_XYZ, times(OKLAB_TO_LMS, lab).map((c) => c ** 3) as Vector);\n\nconst KAPPA = 24389 / 27;\nconst EPSILON = 216 / 24389;\n\nfunction xyzToLab(xyz: Vector): Vector {\n const [x, y, z] = times(D65_TO_D50, xyz).map((c, i) => c / D50_WHITE[i]!);\n const f = (c: number) => (c > EPSILON ? Math.cbrt(c) : (KAPPA * c + 16) / 116);\n const [fx, fy, fz] = [f(x!), f(y!), f(z!)];\n return [116 * fy - 16, 500 * (fx - fy), 200 * (fy - fz)];\n}\n\nfunction labToXyz([l, a, b]: Vector): Vector {\n const fy = (l + 16) / 116;\n const fx = a / 500 + fy;\n const fz = fy - b / 200;\n const x = fx ** 3 > EPSILON ? fx ** 3 : (116 * fx - 16) / KAPPA;\n const y = l > KAPPA * EPSILON ? fy ** 3 : l / KAPPA;\n const z = fz ** 3 > EPSILON ? fz ** 3 : (116 * fz - 16) / KAPPA;\n return times(D50_TO_D65, [x, y, z].map((c, i) => c * D50_WHITE[i]!) as Vector);\n}\n\n/** Lightness, chroma and hue in degrees, from the rectangular form, and back. */\nconst polar = ([l, a, b]: Vector): Vector => {\n const hue = (Math.atan2(b, a) * 180) / Math.PI;\n return [l, Math.hypot(a, b), hue < 0 ? hue + 360 : hue];\n};\nconst rectangular = ([l, c, h]: Vector): Vector => [\n l,\n c * Math.cos((h * Math.PI) / 180),\n c * Math.sin((h * Math.PI) / 180),\n];\n\nfunction srgbToHsl([r, g, b]: Vector): Vector {\n const max = Math.max(r, g, b);\n const min = Math.min(r, g, b);\n const l = (max + min) / 2;\n const d = max - min;\n if (d === 0) return [NaN, 0, l];\n const s = l === 0 || l === 1 ? 0 : (max - l) / Math.min(l, 1 - l);\n const h =\n max === r\n ? ((g - b) / d + (g < b ? 6 : 0)) * 60\n : max === g\n ? ((b - r) / d + 2) * 60\n : ((r - g) / d + 4) * 60;\n return [h, s, l];\n}\n\nfunction hslToSrgb([h, s, l]: Vector): Vector {\n const hue = Number.isFinite(h) ? ((h % 360) + 360) % 360 : 0;\n const f = (n: number) => {\n const k = (n + hue / 30) % 12;\n return l - s * Math.min(l, 1 - l) * Math.max(-1, Math.min(k - 3, 9 - k, 1));\n };\n return [f(0), f(8), f(4)];\n}\n\nfunction srgbToHwb(rgb: Vector): Vector {\n const [h] = srgbToHsl(rgb);\n return [h, Math.min(...rgb), 1 - Math.max(...rgb)];\n}\n\nfunction hwbToSrgb([h, w, b]: Vector): Vector {\n if (w + b >= 1) {\n const grey = w / (w + b);\n return [grey, grey, grey];\n }\n return hslToSrgb([h, 1, 0.5]).map((c) => c * (1 - w - b) + w) as Vector;\n}\n\n/** Where the hue sits in each space's coordinates, if it has one. */\nconst HUE_INDEX: Partial<Record<MixSpace, number>> = { oklch: 2, lch: 2, hsl: 0, hwb: 0 };\n\n/** An sRGB colour, channels 0 to 1, in the space's own coordinates. */\nexport function into(space: MixSpace, rgb: Vector): Vector {\n switch (space) {\n case 'srgb':\n return rgb;\n case 'oklab':\n return xyzToOklab(srgbToXyz(rgb));\n case 'oklch':\n return powerless(polar(xyzToOklab(srgbToXyz(rgb))), 0.000004);\n case 'lab':\n return xyzToLab(srgbToXyz(rgb));\n case 'lch':\n return powerless(polar(xyzToLab(srgbToXyz(rgb))), 0.0015);\n case 'hsl':\n return srgbToHsl(rgb);\n case 'hwb':\n return srgbToHwb(rgb);\n }\n}\n\n/** A hue that does not matter, for a colour with no chroma to speak of, is missing, as CSS says. */\nconst powerless = ([l, c, h]: Vector, threshold: number): Vector => [\n l,\n c,\n c <= threshold ? NaN : h,\n];\n\n/** Back to sRGB, 0 to 1, brought inside the gamut where the space reaches past it. */\nexport function outOf(space: MixSpace, coords: Vector): Vector {\n const finite = coords.map((c) => (Number.isFinite(c) ? c : 0)) as Vector;\n switch (space) {\n case 'srgb':\n return clip(finite);\n case 'oklab':\n return gamutMap(finite);\n case 'oklch':\n return gamutMap(rectangular(finite));\n case 'lab':\n return gamutMap(xyzToOklab(labToXyz(finite)));\n case 'lch':\n return gamutMap(xyzToOklab(labToXyz(rectangular(finite))));\n case 'hsl':\n return clip(hslToSrgb(finite));\n case 'hwb':\n return clip(hwbToSrgb(finite));\n }\n}\n\nconst clip = (rgb: Vector) => rgb.map((c) => Math.min(1, Math.max(0, c))) as Vector;\nconst inGamut = (rgb: Vector) => rgb.every((c) => c >= -1e-6 && c <= 1 + 1e-6);\nconst deltaEOK = (a: Vector, b: Vector) => Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]);\n\n/** CSS Color 4 section 13.2.2: reduce OKLCh chroma until clipping no longer shows. */\nfunction gamutMap(oklab: Vector): Vector {\n const origin = polar(oklab);\n if (origin[0] >= 1) return [1, 1, 1];\n if (origin[0] <= 0) return [0, 0, 0];\n const srgbOf = (lch: Vector) => xyzToSrgb(oklabToXyz(rectangular(lch)));\n const rgb = srgbOf(origin);\n if (inGamut(rgb)) return clip(rgb);\n\n const JND = 0.02;\n const EPS = 0.0001;\n let clipped = clip(rgb);\n if (deltaEOK(xyzToOklab(srgbToXyz(clipped)), oklab) < JND) return clipped;\n let min = 0;\n let max = origin[1];\n let minInGamut = true;\n while (max - min > EPS) {\n const chroma = (min + max) / 2;\n const current: Vector = [origin[0], chroma, origin[2]];\n const converted = srgbOf(current);\n if (minInGamut && inGamut(converted)) {\n min = chroma;\n continue;\n }\n clipped = clip(converted);\n const error = deltaEOK(xyzToOklab(srgbToXyz(clipped)), rectangular(current));\n if (error < JND) {\n if (JND - error < EPS) return clipped;\n minInGamut = false;\n min = chroma;\n } else {\n max = chroma;\n }\n }\n return clipped;\n}\n\n/** Two hues, one moved by whole turns so that interpolating between them goes the way asked. */\nfunction fixHues(a: number, b: number, method: HueMethod): [number, number] {\n const delta = b - a;\n const lift = HUE_LIFTS[method](delta);\n return lift === 'a' ? [a + 360, b] : lift === 'b' ? [a, b + 360] : [a, b];\n}\n\n/** For each method, which of the two hues takes a whole turn more, given how far apart they are. */\nconst HUE_LIFTS: Record<HueMethod, (delta: number) => 'a' | 'b' | null> = {\n shorter: (delta) => (delta > 180 ? 'a' : delta < -180 ? 'b' : null),\n longer: (delta) => (delta > 0 && delta < 180 ? 'a' : delta > -180 && delta <= 0 ? 'b' : null),\n increasing: (delta) => (delta < 0 ? 'b' : null),\n decreasing: (delta) => (delta > 0 ? 'a' : null),\n};\n\n/** One side of a mix: a colour as native paints it, and the percentage written beside it. */\nexport interface MixSide {\n readonly colour: string;\n readonly percentage?: number;\n}\n\n/**\n * `color-mix(in <space> [<hue> hue], <a> [p%], <b> [q%])`, as the `rgb()` or `rgba()` native\n * reads. Undefined when a side is not a colour it can read - a platform colour, say - or when the\n * two percentages come to zero, which CSS says is not a colour at all.\n */\nexport function mixColours(\n space: MixSpace,\n a: MixSide,\n b: MixSide,\n hue: HueMethod = 'shorter',\n): string | undefined {\n const first = parseColor(a.colour);\n const second = parseColor(b.colour);\n const weights = weightsOf(a.percentage, b.percentage);\n if (!first || !second || !weights) return undefined;\n\n const [c1, c2] = [first, second].map(([r, g, bl, alpha]) => ({\n coords: into(space, [r / 255, g / 255, bl / 255]),\n alpha,\n })) as [Side, Side];\n const hueAt = HUE_INDEX[space];\n alignComponents(c1, c2, hueAt, hue);\n\n const { t, alphaScale } = weights;\n const alpha = c1.alpha * (1 - t) + c2.alpha * t;\n const mixed = interpolate(c1, c2, t, alpha, hueAt);\n const [r, g, bl] = outOf(space, mixed).map((c) => Math.round(c * 255));\n const opacity = Math.round(alpha * alphaScale * 1000) / 1000;\n return opacity >= 1 ? `rgb(${r}, ${g}, ${bl})` : `rgba(${r}, ${g}, ${bl}, ${opacity})`;\n}\n\ninterface Side {\n coords: Vector;\n alpha: number;\n}\n\n/**\n * CSS Color 5, 2.1: missing percentages take what the other leaves, or half each; the second\n * colour's share is `t`, and a pair summing under 100 scales the alpha down.\n */\nfunction weightsOf(p?: number, q?: number): { t: number; alphaScale: number } | null {\n const [first, second] =\n p === undefined && q === undefined\n ? [50, 50]\n : p === undefined\n ? [100 - q!, q!]\n : q === undefined\n ? [p, 100 - p]\n : [p, q];\n const sum = first + second;\n if (sum <= 0) return null;\n return { t: second / sum, alphaScale: sum < 100 ? sum / 100 : 1 };\n}\n\n/** A missing component takes the other colour's, and the hues are set to go the way asked. */\nfunction alignComponents(c1: Side, c2: Side, hueAt: number | undefined, hue: HueMethod): void {\n for (let i = 0; i < 3; i++) {\n if (Number.isNaN(c1.coords[i])) c1.coords[i] = c2.coords[i]!;\n if (Number.isNaN(c2.coords[i])) c2.coords[i] = c1.coords[i]!;\n }\n if (hueAt === undefined) return;\n const [h1, h2] = [c1.coords[hueAt]!, c2.coords[hueAt]!];\n if (Number.isFinite(h1) && Number.isFinite(h2)) {\n [c1.coords[hueAt], c2.coords[hueAt]] = fixHues(h1, h2, hue);\n }\n}\n\n/** Premultiplied by alpha, except the hue, which is an angle and not an amount. */\nfunction interpolate(c1: Side, c2: Side, t: number, alpha: number, hueAt?: number): Vector {\n const mixed = [0, 1, 2].map((i) => {\n if (i === hueAt) return c1.coords[i]! * (1 - t) + c2.coords[i]! * t;\n const premixed = c1.coords[i]! * c1.alpha * (1 - t) + c2.coords[i]! * c2.alpha * t;\n return alpha === 0 ? 0 : premixed / alpha;\n }) as Vector;\n if (hueAt !== undefined) mixed[hueAt] = ((mixed[hueAt]! % 360) + 360) % 360;\n return mixed;\n}\n"]}
|