@a-type/ui 3.0.12 → 3.0.13
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/dist/cjs/__tests__/setup.d.ts +1 -0
- package/dist/cjs/__tests__/setup.js +5 -0
- package/dist/cjs/__tests__/setup.js.map +1 -0
- package/dist/cjs/colors.stories.d.ts +1 -8
- package/dist/cjs/colors.stories.js +36 -26
- package/dist/cjs/colors.stories.js.map +1 -1
- package/dist/cjs/components/box/Box.d.ts +1 -1
- package/dist/cjs/components/box/Box.stories.js +2 -2
- package/dist/cjs/components/box/Box.stories.js.map +1 -1
- package/dist/cjs/components/button/Button.d.ts +1 -1
- package/dist/cjs/components/button/Button.stories.js +9 -9
- package/dist/cjs/components/button/Button.stories.js.map +1 -1
- package/dist/cjs/components/button/classes.d.ts +1 -1
- package/dist/cjs/components/card/Card.d.ts +1 -1
- package/dist/cjs/components/chip/Chip.d.ts +1 -1
- package/dist/cjs/components/colorPicker/ColorPicker.d.ts +1 -1
- package/dist/cjs/components/colorPicker/ColorPicker.js +2 -2
- package/dist/cjs/components/colorPicker/ColorPicker.js.map +1 -1
- package/dist/cjs/components/datePicker/DatePicker.d.ts +1 -1
- package/dist/cjs/components/dropdownMenu/DropdownMenu.d.ts +1 -1
- package/dist/cjs/components/note/Note.d.ts +1 -1
- package/dist/cjs/components/progress/Progress.d.ts +1 -1
- package/dist/cjs/components/slider/Slider.d.ts +1 -1
- package/dist/cjs/hooks/useOverrideTheme.d.ts +1 -1
- package/dist/cjs/hooks/useTitleBarColor.d.ts +5 -0
- package/dist/cjs/hooks/useTitleBarColor.js +37 -3
- package/dist/cjs/hooks/useTitleBarColor.js.map +1 -1
- package/dist/cjs/hooks/useTitleBarColor.stories.js +1 -1
- package/dist/cjs/hooks/useTitleBarColor.stories.js.map +1 -1
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.js +2 -4
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/uno/index.d.ts +1 -0
- package/dist/cjs/uno/index.js +8 -0
- package/dist/cjs/uno/index.js.map +1 -0
- package/dist/cjs/uno/logic/color.d.ts +100 -27
- package/dist/cjs/uno/logic/color.js +250 -42
- package/dist/cjs/uno/logic/color.js.map +1 -1
- package/dist/cjs/uno/logic/color.test.d.ts +1 -0
- package/dist/cjs/uno/logic/color.test.js +83 -0
- package/dist/cjs/uno/logic/color.test.js.map +1 -0
- package/dist/cjs/uno/logic/oklch.d.ts +3 -0
- package/dist/cjs/uno/logic/oklch.js +96 -0
- package/dist/cjs/uno/logic/oklch.js.map +1 -0
- package/dist/cjs/uno/logic/palettes.d.ts +63 -0
- package/dist/cjs/uno/logic/palettes.js +99 -0
- package/dist/cjs/uno/logic/palettes.js.map +1 -0
- package/dist/cjs/uno/preflights/colors.js +18 -42
- package/dist/cjs/uno/preflights/colors.js.map +1 -1
- package/dist/cjs/uno/preflights/fonts.d.ts +4 -0
- package/dist/cjs/uno/preflights/fonts.js +20 -0
- package/dist/cjs/uno/preflights/fonts.js.map +1 -0
- package/dist/cjs/uno/preflights/index.d.ts +2 -1
- package/dist/cjs/uno/preflights/index.js +2 -0
- package/dist/cjs/uno/preflights/index.js.map +1 -1
- package/dist/cjs/uno/theme/colors.d.ts +17 -53
- package/dist/cjs/uno/theme/colors.js +11 -13
- package/dist/cjs/uno/theme/colors.js.map +1 -1
- package/dist/cjs/uno/uno.preset.d.ts +2 -1
- package/dist/cjs/uno/uno.preset.js +3 -1
- package/dist/cjs/uno/uno.preset.js.map +1 -1
- package/dist/css/main.css +9 -8
- package/dist/esm/__tests__/setup.d.ts +1 -0
- package/dist/esm/__tests__/setup.js +3 -0
- package/dist/esm/__tests__/setup.js.map +1 -0
- package/dist/esm/colors.stories.d.ts +1 -8
- package/dist/esm/colors.stories.js +33 -26
- package/dist/esm/colors.stories.js.map +1 -1
- package/dist/esm/components/box/Box.d.ts +1 -1
- package/dist/esm/components/box/Box.stories.js +1 -1
- package/dist/esm/components/box/Box.stories.js.map +1 -1
- package/dist/esm/components/button/Button.d.ts +1 -1
- package/dist/esm/components/button/Button.stories.js +1 -1
- package/dist/esm/components/button/Button.stories.js.map +1 -1
- package/dist/esm/components/button/classes.d.ts +1 -1
- package/dist/esm/components/card/Card.d.ts +1 -1
- package/dist/esm/components/chip/Chip.d.ts +1 -1
- package/dist/esm/components/colorPicker/ColorPicker.d.ts +1 -1
- package/dist/esm/components/colorPicker/ColorPicker.js +1 -1
- package/dist/esm/components/colorPicker/ColorPicker.js.map +1 -1
- package/dist/esm/components/datePicker/DatePicker.d.ts +1 -1
- package/dist/esm/components/dropdownMenu/DropdownMenu.d.ts +1 -1
- package/dist/esm/components/note/Note.d.ts +1 -1
- package/dist/esm/components/progress/Progress.d.ts +1 -1
- package/dist/esm/components/slider/Slider.d.ts +1 -1
- package/dist/esm/hooks/useOverrideTheme.d.ts +1 -1
- package/dist/esm/hooks/useTitleBarColor.d.ts +5 -0
- package/dist/esm/hooks/useTitleBarColor.js +36 -3
- package/dist/esm/hooks/useTitleBarColor.js.map +1 -1
- package/dist/esm/hooks/useTitleBarColor.stories.js +2 -2
- package/dist/esm/hooks/useTitleBarColor.stories.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/uno/index.d.ts +1 -0
- package/dist/esm/uno/index.js +3 -0
- package/dist/esm/uno/index.js.map +1 -0
- package/dist/esm/uno/logic/color.d.ts +100 -27
- package/dist/esm/uno/logic/color.js +247 -38
- package/dist/esm/uno/logic/color.js.map +1 -1
- package/dist/esm/uno/logic/color.test.d.ts +1 -0
- package/dist/esm/uno/logic/color.test.js +81 -0
- package/dist/esm/uno/logic/color.test.js.map +1 -0
- package/dist/esm/uno/logic/oklch.d.ts +3 -0
- package/dist/esm/uno/logic/oklch.js +90 -0
- package/dist/esm/uno/logic/oklch.js.map +1 -0
- package/dist/esm/uno/logic/palettes.d.ts +63 -0
- package/dist/esm/uno/logic/palettes.js +95 -0
- package/dist/esm/uno/logic/palettes.js.map +1 -0
- package/dist/esm/uno/preflights/colors.js +17 -41
- package/dist/esm/uno/preflights/colors.js.map +1 -1
- package/dist/esm/uno/preflights/fonts.d.ts +4 -0
- package/dist/esm/uno/preflights/fonts.js +16 -0
- package/dist/esm/uno/preflights/fonts.js.map +1 -0
- package/dist/esm/uno/preflights/index.d.ts +2 -1
- package/dist/esm/uno/preflights/index.js +2 -0
- package/dist/esm/uno/preflights/index.js.map +1 -1
- package/dist/esm/uno/theme/colors.d.ts +17 -53
- package/dist/esm/uno/theme/colors.js +11 -13
- package/dist/esm/uno/theme/colors.js.map +1 -1
- package/dist/esm/uno/uno.preset.d.ts +2 -1
- package/dist/esm/uno/uno.preset.js +3 -1
- package/dist/esm/uno/uno.preset.js.map +1 -1
- package/package.json +132 -125
- package/src/__tests__/setup.ts +1 -0
- package/src/colors.stories.tsx +94 -71
- package/src/components/box/Box.stories.tsx +1 -1
- package/src/components/box/Box.tsx +1 -1
- package/src/components/button/Button.stories.tsx +1 -1
- package/src/components/button/Button.tsx +1 -1
- package/src/components/button/classes.tsx +1 -1
- package/src/components/chip/Chip.tsx +1 -1
- package/src/components/colorPicker/ColorPicker.tsx +1 -1
- package/src/components/datePicker/DatePicker.tsx +1 -1
- package/src/components/dropdownMenu/DropdownMenu.tsx +1 -1
- package/src/components/note/Note.tsx +1 -1
- package/src/components/progress/Progress.tsx +1 -1
- package/src/components/slider/Slider.tsx +1 -1
- package/src/hooks/useOverrideTheme.ts +1 -1
- package/src/hooks/useTitleBarColor.stories.tsx +2 -2
- package/src/hooks/useTitleBarColor.ts +52 -4
- package/src/index.ts +1 -5
- package/src/themes.stories.tsx +1 -1
- package/src/uno/index.ts +5 -0
- package/src/uno/logic/color.test.ts +119 -0
- package/src/uno/logic/color.ts +420 -80
- package/src/uno/logic/oklch.ts +120 -0
- package/src/uno/logic/palettes.ts +266 -0
- package/src/uno/preflights/colors.ts +22 -41
- package/src/uno/preflights/fonts.ts +23 -0
- package/src/uno/preflights/index.ts +3 -1
- package/src/uno/theme/colors.ts +13 -18
- package/src/uno/uno.preset.ts +8 -3
package/src/uno/logic/color.ts
CHANGED
|
@@ -1,84 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function clampedPercent(value: string | number) {
|
|
12
|
-
return `clamp(0%, ${value}, 100%)`;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const paletteHues = {
|
|
16
|
-
primary: 'var(--p-primary-hue,91.8)',
|
|
17
|
-
accent: 'var(--p-accent-hue,160.88)',
|
|
18
|
-
main: 'var(--l-main-hue,var(--p-primary-hue,91.8))',
|
|
19
|
-
attention: 'var(--p-attention-hue,30)',
|
|
20
|
-
success: 'var(--p-success-hue,140)',
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export const paletteNames = [
|
|
24
|
-
'primary',
|
|
25
|
-
'accent',
|
|
26
|
-
'attention',
|
|
27
|
-
'success',
|
|
28
|
-
'lemon',
|
|
29
|
-
'leek',
|
|
30
|
-
'tomato',
|
|
31
|
-
'blueberry',
|
|
32
|
-
'eggplant',
|
|
33
|
-
'gray',
|
|
34
|
-
'high-contrast',
|
|
35
|
-
] as const;
|
|
36
|
-
export type PaletteName = (typeof paletteNames)[number];
|
|
37
|
-
|
|
38
|
-
export function createColorRange(
|
|
39
|
-
sourceHue: string,
|
|
40
|
-
{
|
|
41
|
-
saturation,
|
|
42
|
-
}: {
|
|
43
|
-
saturation?: string;
|
|
44
|
-
} = {},
|
|
45
|
-
) {
|
|
46
|
-
return {
|
|
47
|
-
wash: globalColor(
|
|
48
|
-
`var(--mode-l-neutral) + (var(--mode-l-range-up) * var(--mode-mult,1) * var(--l-lightness-spread,1))`,
|
|
49
|
-
saturation ??
|
|
50
|
-
`var(--mode-s-neutral) + (var(--mode-s-range-up) * var(--mode-mult,1))`,
|
|
51
|
-
sourceHue,
|
|
52
|
-
),
|
|
53
|
-
light: globalColor(
|
|
54
|
-
`var(--mode-l-neutral) + (var(--mode-l-range-up) * 0.5 * var(--mode-mult,1) * var(--l-lightness-spread,1))`,
|
|
55
|
-
saturation ??
|
|
56
|
-
`var(--mode-s-neutral) + (var(--mode-s-range-up) * 0.75 * var(--mode-mult,1))`,
|
|
57
|
-
sourceHue,
|
|
58
|
-
),
|
|
59
|
-
DEFAULT: globalColor(
|
|
60
|
-
`var(--mode-l-neutral)`,
|
|
61
|
-
saturation ?? `var(--mode-s-neutral)`,
|
|
62
|
-
sourceHue,
|
|
63
|
-
),
|
|
64
|
-
dark: globalColor(
|
|
65
|
-
`var(--mode-l-neutral) - (var(--mode-l-range-down) * 0.35 * var(--mode-mult,1) * var(--l-lightness-spread,1))`,
|
|
66
|
-
saturation ??
|
|
67
|
-
`var(--mode-s-neutral) - (var(--mode-s-range-down) * 0.5 * var(--mode-mult,1))`,
|
|
68
|
-
sourceHue,
|
|
69
|
-
),
|
|
70
|
-
ink: globalColor(
|
|
71
|
-
`var(--mode-l-neutral) - (var(--mode-l-range-down) * var(--mode-mult,1) * var(--l-lightness-spread,1))`,
|
|
72
|
-
saturation ??
|
|
73
|
-
`var(--mode-s-neutral) - (var(--mode-s-range-down) * var(--mode-mult,1))`,
|
|
74
|
-
sourceHue,
|
|
75
|
-
),
|
|
76
|
-
};
|
|
77
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
ColorSpace,
|
|
3
|
+
to as convert,
|
|
4
|
+
OKLCH,
|
|
5
|
+
PlainColorObject,
|
|
6
|
+
serialize,
|
|
7
|
+
sRGB,
|
|
8
|
+
toGamut,
|
|
9
|
+
} from 'colorjs.io/fn';
|
|
10
|
+
import { type PaletteName } from './palettes.js';
|
|
78
11
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
});
|
|
12
|
+
ColorSpace.register(sRGB);
|
|
13
|
+
ColorSpace.register(OKLCH);
|
|
82
14
|
|
|
83
15
|
export function lighten(base: string, level: string) {
|
|
84
16
|
return mod(base, level, 1);
|
|
@@ -97,3 +29,411 @@ function mod(base: string, level: string, sign: number) {
|
|
|
97
29
|
` h)`
|
|
98
30
|
);
|
|
99
31
|
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Values to inject for contextual color variables.
|
|
35
|
+
*/
|
|
36
|
+
export interface ColorEvaluationContext {
|
|
37
|
+
mode: {
|
|
38
|
+
lNeutral: string;
|
|
39
|
+
lRangeUp: string;
|
|
40
|
+
lRangeDown: string;
|
|
41
|
+
sNeutral: string;
|
|
42
|
+
sRangeUp: string;
|
|
43
|
+
sRangeDown: string;
|
|
44
|
+
mult: string;
|
|
45
|
+
};
|
|
46
|
+
sourceHue: string;
|
|
47
|
+
globalSaturation: string;
|
|
48
|
+
localLightnessSpread: string;
|
|
49
|
+
localSaturation: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function livePropertyColorContext(
|
|
53
|
+
sourceHue: string,
|
|
54
|
+
): ColorEvaluationContext {
|
|
55
|
+
return {
|
|
56
|
+
mode: {
|
|
57
|
+
lNeutral: 'var(--mode-l-neutral)',
|
|
58
|
+
lRangeUp: 'var(--mode-l-range-up)',
|
|
59
|
+
lRangeDown: 'var(--mode-l-range-down)',
|
|
60
|
+
sNeutral: 'var(--mode-s-neutral)',
|
|
61
|
+
sRangeUp: 'var(--mode-s-range-up)',
|
|
62
|
+
sRangeDown: 'var(--mode-s-range-down)',
|
|
63
|
+
mult: 'var(--mode-mult, 1)',
|
|
64
|
+
},
|
|
65
|
+
sourceHue,
|
|
66
|
+
globalSaturation: 'var(--global-saturation, 1)',
|
|
67
|
+
localLightnessSpread: 'var(--l-lightness-spread, 1)',
|
|
68
|
+
localSaturation: 'var(--l-saturation, 1)',
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// helpers to avoid having to hardcode the synchronized variables
|
|
73
|
+
function stripVar(property: string) {
|
|
74
|
+
return property.replace(/var\((--[a-zA-Z0-9-_]+)(,[^)]+)?\)/g, '$1').trim();
|
|
75
|
+
}
|
|
76
|
+
function getFallback(property: string) {
|
|
77
|
+
const match = property.match(/var\(--[a-zA-Z0-9-_]+,([^)]+)\)/);
|
|
78
|
+
if (match) {
|
|
79
|
+
return match[1].trim();
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
function evaluatePropertiesRecursively(
|
|
84
|
+
computed: CSSStyleDeclaration,
|
|
85
|
+
context: ColorEvaluationContext,
|
|
86
|
+
result: any = {},
|
|
87
|
+
) {
|
|
88
|
+
for (const key in context) {
|
|
89
|
+
const value = context[key as keyof ColorEvaluationContext];
|
|
90
|
+
if (typeof value === 'string') {
|
|
91
|
+
if (value.startsWith('var(')) {
|
|
92
|
+
const varName = stripVar(value);
|
|
93
|
+
const evaluatedVar = computed.getPropertyValue(varName).trim();
|
|
94
|
+
if (evaluatedVar) {
|
|
95
|
+
result[key as keyof ColorEvaluationContext] = evaluatedVar;
|
|
96
|
+
} else {
|
|
97
|
+
const fallback = getFallback(value);
|
|
98
|
+
if (fallback !== null) {
|
|
99
|
+
result[key as keyof ColorEvaluationContext] = fallback;
|
|
100
|
+
} else {
|
|
101
|
+
throw new Error(
|
|
102
|
+
`CSS variable ${varName} is not defined and no fallback is provided.`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
} else {
|
|
108
|
+
result[key as keyof ColorEvaluationContext] =
|
|
109
|
+
evaluatePropertiesRecursively(computed, value as any, {} as any) as any;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* "Snapshots" a color context as real values from the given scope element.
|
|
117
|
+
* Defaults to body.
|
|
118
|
+
*/
|
|
119
|
+
export function snapshotColorContext(
|
|
120
|
+
scope: HTMLElement = document.body,
|
|
121
|
+
palette: PaletteName | 'main',
|
|
122
|
+
): ColorEvaluationContext {
|
|
123
|
+
const styles = getComputedStyle(scope);
|
|
124
|
+
const sourceHue =
|
|
125
|
+
palette === 'main' ? `var(--l-main-hue)` : `var(--p-${palette}-hue)`;
|
|
126
|
+
return evaluatePropertiesRecursively(
|
|
127
|
+
styles,
|
|
128
|
+
livePropertyColorContext(sourceHue),
|
|
129
|
+
) as ColorEvaluationContext;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface OklchColorEquation {
|
|
133
|
+
l: ColorEquation;
|
|
134
|
+
c: ColorEquation;
|
|
135
|
+
h: ColorEquation;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Prints the CSS value of the color equation, including all
|
|
139
|
+
* calculations and variable references.
|
|
140
|
+
*/
|
|
141
|
+
print(context: ColorEvaluationContext): string;
|
|
142
|
+
/**
|
|
143
|
+
* Uses the equation and provided context to compute a static
|
|
144
|
+
* OKLCH color string with calculations and references resolved.
|
|
145
|
+
*/
|
|
146
|
+
computeOklch(context: ColorEvaluationContext): string;
|
|
147
|
+
/**
|
|
148
|
+
* Uses the equation and provided context to compute a static
|
|
149
|
+
* sRGB color string with calculations and references resolved.
|
|
150
|
+
* This is not as accurate as computeOklch, as it converts to sRGB gamut.
|
|
151
|
+
*/
|
|
152
|
+
computeSrgb(context: ColorEvaluationContext): string;
|
|
153
|
+
/**
|
|
154
|
+
* Uses the equation and provided context to compute a static
|
|
155
|
+
* HEX color string with calculations and references resolved.
|
|
156
|
+
* This is not as accurate as computeOklch, as it converts to sRGB gamut.
|
|
157
|
+
*/
|
|
158
|
+
computeHex(context: ColorEvaluationContext): string;
|
|
159
|
+
/**
|
|
160
|
+
* Returns the raw computed L, C, H values as numbers with units.
|
|
161
|
+
*/
|
|
162
|
+
raw(ctx: ColorEvaluationContext): {
|
|
163
|
+
l: ComputationResult;
|
|
164
|
+
c: ComputationResult;
|
|
165
|
+
h: ComputationResult;
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
export type ColorEquation = OperationTree;
|
|
169
|
+
type ColorContextEvaluation = (ctx: ColorEvaluationContext) => string;
|
|
170
|
+
type OperationTree =
|
|
171
|
+
| AddOperation
|
|
172
|
+
| SubtractOperation
|
|
173
|
+
| MultiplyOperation
|
|
174
|
+
| LiteralOperation
|
|
175
|
+
| ClampOperation;
|
|
176
|
+
interface AddOperation {
|
|
177
|
+
type: 'add';
|
|
178
|
+
values: ColorEquation[];
|
|
179
|
+
}
|
|
180
|
+
interface SubtractOperation {
|
|
181
|
+
type: 'subtract';
|
|
182
|
+
values: ColorEquation[];
|
|
183
|
+
}
|
|
184
|
+
interface MultiplyOperation {
|
|
185
|
+
type: 'multiply';
|
|
186
|
+
values: ColorEquation[];
|
|
187
|
+
}
|
|
188
|
+
interface LiteralOperation {
|
|
189
|
+
type: 'literal';
|
|
190
|
+
value: ColorContextEvaluation;
|
|
191
|
+
}
|
|
192
|
+
interface ClampOperation {
|
|
193
|
+
type: 'clamp';
|
|
194
|
+
values: ColorEquation[];
|
|
195
|
+
}
|
|
196
|
+
const colorEquationTools = {
|
|
197
|
+
literal: (value: ColorContextEvaluation): LiteralOperation => {
|
|
198
|
+
return { type: 'literal', value };
|
|
199
|
+
},
|
|
200
|
+
add: (...values: ColorEquation[]): AddOperation => {
|
|
201
|
+
return { type: 'add', values };
|
|
202
|
+
},
|
|
203
|
+
subtract: (...values: ColorEquation[]): SubtractOperation => {
|
|
204
|
+
return { type: 'subtract', values };
|
|
205
|
+
},
|
|
206
|
+
multiply: (...values: ColorEquation[]): MultiplyOperation => {
|
|
207
|
+
return { type: 'multiply', values };
|
|
208
|
+
},
|
|
209
|
+
clamp: (
|
|
210
|
+
equation: ColorEquation,
|
|
211
|
+
min: ColorEquation,
|
|
212
|
+
max: ColorEquation,
|
|
213
|
+
): ColorEquation => {
|
|
214
|
+
return { type: 'clamp', values: [min, equation, max] };
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
function printEquation(
|
|
219
|
+
equation: ColorEquation,
|
|
220
|
+
context: ColorEvaluationContext,
|
|
221
|
+
): string {
|
|
222
|
+
switch (equation.type) {
|
|
223
|
+
case 'literal':
|
|
224
|
+
return equation.value(context);
|
|
225
|
+
case 'add':
|
|
226
|
+
return `(${equation.values
|
|
227
|
+
.map((v) => printEquation(v, context))
|
|
228
|
+
.join(' + ')})`;
|
|
229
|
+
case 'subtract':
|
|
230
|
+
return `(${equation.values
|
|
231
|
+
.map((v) => printEquation(v, context))
|
|
232
|
+
.join(' - ')})`;
|
|
233
|
+
case 'multiply':
|
|
234
|
+
return `(${equation.values
|
|
235
|
+
.map((v) => printEquation(v, context))
|
|
236
|
+
.join(' * ')})`;
|
|
237
|
+
case 'clamp':
|
|
238
|
+
if (equation.values.length !== 3) {
|
|
239
|
+
throw new Error(
|
|
240
|
+
'Clamp operation requires exactly 3 values: min, value, max',
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
return `clamp(${equation.values
|
|
244
|
+
.map((v) => printEquation(v, context))
|
|
245
|
+
.join(', ')})`;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
interface ComputationResult {
|
|
250
|
+
value: number;
|
|
251
|
+
unit: '%' | '';
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function add(a: ComputationResult, b: ComputationResult): ComputationResult {
|
|
255
|
+
if (a.unit !== b.unit) {
|
|
256
|
+
throw new Error('Cannot add values with different units');
|
|
257
|
+
}
|
|
258
|
+
return { value: a.value + b.value, unit: a.unit };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function subtract(
|
|
262
|
+
a: ComputationResult,
|
|
263
|
+
b: ComputationResult,
|
|
264
|
+
): ComputationResult {
|
|
265
|
+
if (a.unit !== b.unit) {
|
|
266
|
+
throw new Error('Cannot subtract values with different units');
|
|
267
|
+
}
|
|
268
|
+
return { value: a.value - b.value, unit: a.unit };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function multiply(
|
|
272
|
+
a: ComputationResult,
|
|
273
|
+
b: ComputationResult,
|
|
274
|
+
): ComputationResult {
|
|
275
|
+
if (a.unit === '%' && b.unit === '%') {
|
|
276
|
+
return { value: (a.value * b.value) / 100, unit: '%' };
|
|
277
|
+
}
|
|
278
|
+
const unit = a.unit === '%' || b.unit === '%' ? '%' : '';
|
|
279
|
+
return { value: a.value * b.value, unit };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function clamp(
|
|
283
|
+
value: ComputationResult,
|
|
284
|
+
min: ComputationResult,
|
|
285
|
+
max: ComputationResult,
|
|
286
|
+
): ComputationResult {
|
|
287
|
+
if (value.unit !== min.unit || value.unit !== max.unit) {
|
|
288
|
+
throw new Error('Cannot clamp values with different units');
|
|
289
|
+
}
|
|
290
|
+
return {
|
|
291
|
+
value: Math.min(Math.max(value.value, min.value), max.value),
|
|
292
|
+
unit: value.unit,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function printComputationResult(result: ComputationResult): string {
|
|
297
|
+
return result.unit === '%' ? `${result.value}%` : `${result.value}`;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function resolveComputationResult(
|
|
301
|
+
result: ComputationResult,
|
|
302
|
+
outputRange: [number, number],
|
|
303
|
+
): number {
|
|
304
|
+
if (result.unit === '%') {
|
|
305
|
+
const [min, max] = outputRange;
|
|
306
|
+
return min + (result.value / 100) * (max - min);
|
|
307
|
+
} else {
|
|
308
|
+
return result.value;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function evaluateLiteral(literal: string): ComputationResult {
|
|
313
|
+
if (literal.endsWith('%')) {
|
|
314
|
+
const asNumber = Number(literal.slice(0, -1));
|
|
315
|
+
if (isNaN(asNumber)) {
|
|
316
|
+
throw new Error(`Literal value did not evaluate to a number: ${literal}`);
|
|
317
|
+
}
|
|
318
|
+
return { value: asNumber, unit: '%' };
|
|
319
|
+
} else {
|
|
320
|
+
const asNumber = Number(literal);
|
|
321
|
+
if (isNaN(asNumber)) {
|
|
322
|
+
throw new Error(`Literal value did not evaluate to a number: ${literal}`);
|
|
323
|
+
}
|
|
324
|
+
return { value: asNumber, unit: '' };
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function computeEquation(
|
|
329
|
+
equation: ColorEquation,
|
|
330
|
+
context: ColorEvaluationContext,
|
|
331
|
+
): ComputationResult {
|
|
332
|
+
switch (equation.type) {
|
|
333
|
+
case 'literal':
|
|
334
|
+
return evaluateLiteral(equation.value(context));
|
|
335
|
+
case 'add':
|
|
336
|
+
return equation.values.reduce<ComputationResult>(
|
|
337
|
+
(sum, v) => add(sum, computeEquation(v, context)),
|
|
338
|
+
{ value: 0, unit: '%' },
|
|
339
|
+
);
|
|
340
|
+
case 'subtract':
|
|
341
|
+
if (equation.values.length === 0) {
|
|
342
|
+
return { value: 0, unit: '%' };
|
|
343
|
+
}
|
|
344
|
+
const first = computeEquation(equation.values[0], context);
|
|
345
|
+
return equation.values
|
|
346
|
+
.slice(1)
|
|
347
|
+
.reduce(
|
|
348
|
+
(difference, v) => subtract(difference, computeEquation(v, context)),
|
|
349
|
+
first,
|
|
350
|
+
);
|
|
351
|
+
case 'multiply':
|
|
352
|
+
return equation.values.reduce<ComputationResult>(
|
|
353
|
+
(product, v) => multiply(product, computeEquation(v, context)),
|
|
354
|
+
{ value: 1, unit: '' },
|
|
355
|
+
);
|
|
356
|
+
case 'clamp':
|
|
357
|
+
if (equation.values.length !== 3) {
|
|
358
|
+
throw new Error(
|
|
359
|
+
'Clamp operation requires exactly 3 values: min, value, max',
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
const min = computeEquation(equation.values[0], context);
|
|
363
|
+
const value = computeEquation(equation.values[1], context);
|
|
364
|
+
const max = computeEquation(equation.values[2], context);
|
|
365
|
+
return clamp(value, min, max);
|
|
366
|
+
default:
|
|
367
|
+
throw new Error(`Unknown equation type: ${(equation as any).type}`);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export function oklchBuilder(
|
|
372
|
+
impl: (tools: typeof colorEquationTools) => {
|
|
373
|
+
l: ColorEquation;
|
|
374
|
+
c: ColorEquation;
|
|
375
|
+
h: ColorEquation;
|
|
376
|
+
},
|
|
377
|
+
): OklchColorEquation {
|
|
378
|
+
const equations = impl(colorEquationTools);
|
|
379
|
+
|
|
380
|
+
return {
|
|
381
|
+
...equations,
|
|
382
|
+
print(context: ColorEvaluationContext): string {
|
|
383
|
+
const l = printEquation(equations.l, context);
|
|
384
|
+
const c = printEquation(equations.c, context);
|
|
385
|
+
const h = printEquation(equations.h, context);
|
|
386
|
+
return `oklch(calc(${l}) calc(${c}) calc(${h}))`;
|
|
387
|
+
},
|
|
388
|
+
computeSrgb(context: ColorEvaluationContext): string {
|
|
389
|
+
const l = computeEquation(equations.l, context);
|
|
390
|
+
const c = computeEquation(equations.c, context);
|
|
391
|
+
const h = computeEquation(equations.h, context);
|
|
392
|
+
const asColor: PlainColorObject = {
|
|
393
|
+
space: OKLCH,
|
|
394
|
+
alpha: 1,
|
|
395
|
+
coords: [
|
|
396
|
+
resolveComputationResult(l, [0, 1]),
|
|
397
|
+
resolveComputationResult(c, [0, 0.4]),
|
|
398
|
+
resolveComputationResult(h, [0, 360]),
|
|
399
|
+
],
|
|
400
|
+
};
|
|
401
|
+
return serialize(toGamut(convert(asColor, 'srgb'), {}));
|
|
402
|
+
},
|
|
403
|
+
computeHex(context: ColorEvaluationContext): string {
|
|
404
|
+
const l = computeEquation(equations.l, context);
|
|
405
|
+
const c = computeEquation(equations.c, context);
|
|
406
|
+
const h = computeEquation(equations.h, context);
|
|
407
|
+
const asColor: PlainColorObject = {
|
|
408
|
+
space: OKLCH,
|
|
409
|
+
alpha: 1,
|
|
410
|
+
coords: [
|
|
411
|
+
resolveComputationResult(l, [0, 1]),
|
|
412
|
+
resolveComputationResult(c, [0, 0.4]),
|
|
413
|
+
resolveComputationResult(h, [0, 360]),
|
|
414
|
+
],
|
|
415
|
+
};
|
|
416
|
+
return serialize(toGamut(convert(asColor, 'srgb')), {
|
|
417
|
+
format: 'hex',
|
|
418
|
+
});
|
|
419
|
+
},
|
|
420
|
+
computeOklch(context: ColorEvaluationContext): string {
|
|
421
|
+
const l = computeEquation(equations.l, context);
|
|
422
|
+
const c = computeEquation(equations.c, context);
|
|
423
|
+
const h = computeEquation(equations.h, context);
|
|
424
|
+
return `oklch(${printComputationResult(l)} ${printComputationResult(
|
|
425
|
+
c,
|
|
426
|
+
)} ${printComputationResult(h)})`;
|
|
427
|
+
},
|
|
428
|
+
raw(context: ColorEvaluationContext): {
|
|
429
|
+
l: ComputationResult;
|
|
430
|
+
c: ComputationResult;
|
|
431
|
+
h: ComputationResult;
|
|
432
|
+
} {
|
|
433
|
+
const l = computeEquation(equations.l, context);
|
|
434
|
+
const c = computeEquation(equations.c, context);
|
|
435
|
+
const h = computeEquation(equations.h, context);
|
|
436
|
+
return { l, c, h };
|
|
437
|
+
},
|
|
438
|
+
};
|
|
439
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
function clampByte(value: number) {
|
|
2
|
+
return Math.min(255, Math.max(0, Math.round(value)));
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
const multiplyMatrices = (A: number[], B: number[]) => {
|
|
6
|
+
return [
|
|
7
|
+
A[0] * B[0] + A[1] * B[1] + A[2] * B[2],
|
|
8
|
+
A[3] * B[0] + A[4] * B[1] + A[5] * B[2],
|
|
9
|
+
A[6] * B[0] + A[7] * B[1] + A[8] * B[2],
|
|
10
|
+
];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const oklch2oklab = ([l, c, h]: number[]) => [
|
|
14
|
+
l,
|
|
15
|
+
isNaN(h) ? 0 : c * Math.cos((h * Math.PI) / 180),
|
|
16
|
+
isNaN(h) ? 0 : c * Math.sin((h * Math.PI) / 180),
|
|
17
|
+
];
|
|
18
|
+
const oklab2oklch = ([l, a, b]: number[]) => [
|
|
19
|
+
l,
|
|
20
|
+
Math.sqrt(a ** 2 + b ** 2),
|
|
21
|
+
Math.abs(a) < 0.0002 && Math.abs(b) < 0.0002
|
|
22
|
+
? NaN
|
|
23
|
+
: ((((Math.atan2(b, a) * 180) / Math.PI) % 360) + 360) % 360,
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
const rgb2srgbLinear = (rgb: number[]) =>
|
|
27
|
+
rgb.map((c) =>
|
|
28
|
+
Math.abs(c) <= 0.04045
|
|
29
|
+
? c / 12.92
|
|
30
|
+
: (c < 0 ? -1 : 1) * ((Math.abs(c) + 0.055) / 1.055) ** 2.4,
|
|
31
|
+
);
|
|
32
|
+
const srgbLinear2rgb = (rgb: number[]) =>
|
|
33
|
+
rgb.map((c) =>
|
|
34
|
+
Math.abs(c) > 0.0031308
|
|
35
|
+
? (c < 0 ? -1 : 1) * (1.055 * Math.abs(c) ** (1 / 2.4) - 0.055)
|
|
36
|
+
: 12.92 * c,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const oklab2xyz = (lab: number[]) => {
|
|
40
|
+
const LMSg = multiplyMatrices(
|
|
41
|
+
[
|
|
42
|
+
1, 0.3963377773761749, 0.2158037573099136, 1, -0.1055613458156586,
|
|
43
|
+
-0.0638541728258133, 1, -0.0894841775298119, -1.2914855480194092,
|
|
44
|
+
],
|
|
45
|
+
lab,
|
|
46
|
+
);
|
|
47
|
+
const LMS = LMSg.map((val) => val ** 3);
|
|
48
|
+
return multiplyMatrices(
|
|
49
|
+
[
|
|
50
|
+
1.2268798758459243, -0.5578149944602171, 0.2813910456659647,
|
|
51
|
+
-0.0405757452148008, 1.112286803280317, -0.0717110580655164,
|
|
52
|
+
-0.0763729366746601, -0.4214933324022432, 1.5869240198367816,
|
|
53
|
+
],
|
|
54
|
+
LMS,
|
|
55
|
+
);
|
|
56
|
+
};
|
|
57
|
+
const xyz2oklab = (xyz: number[]) => {
|
|
58
|
+
const LMS = multiplyMatrices(
|
|
59
|
+
[
|
|
60
|
+
0.819022437996703, 0.3619062600528904, -0.1288737815209879,
|
|
61
|
+
0.0329836539323885, 0.9292868615863434, 0.0361446663506424,
|
|
62
|
+
0.0481771893596242, 0.2642395317527308, 0.6335478284694309,
|
|
63
|
+
],
|
|
64
|
+
xyz,
|
|
65
|
+
);
|
|
66
|
+
const LMSg = LMS.map((val) => Math.cbrt(val));
|
|
67
|
+
return multiplyMatrices(
|
|
68
|
+
[
|
|
69
|
+
0.210454268309314, 0.7936177747023054, -0.0040720430116193,
|
|
70
|
+
1.9779985324311684, -2.4285922420485799, 0.450593709617411,
|
|
71
|
+
0.0259040424655478, 0.7827717124575296, -0.8086757549230774,
|
|
72
|
+
],
|
|
73
|
+
LMSg,
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
const xyz2rgbLinear = (xyz: number[]) => {
|
|
77
|
+
return multiplyMatrices(
|
|
78
|
+
[
|
|
79
|
+
3.2409699419045226, -1.537383177570094, -0.4986107602930034,
|
|
80
|
+
-0.9692436362808796, 1.8759675015077202, 0.04155505740717559,
|
|
81
|
+
0.05563007969699366, -0.20397695888897652, 1.0569715142428786,
|
|
82
|
+
],
|
|
83
|
+
xyz,
|
|
84
|
+
);
|
|
85
|
+
};
|
|
86
|
+
const rgbLinear2xyz = (rgb: number[]) => {
|
|
87
|
+
return multiplyMatrices(
|
|
88
|
+
[
|
|
89
|
+
0.41239079926595934, 0.357584339383878, 0.1804807884018343,
|
|
90
|
+
0.21263900587151027, 0.715168678767756, 0.07219231536073371,
|
|
91
|
+
0.01933081871559182, 0.11919477979462598, 0.9505321522496607,
|
|
92
|
+
],
|
|
93
|
+
rgb,
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
function rgb2Hex([r, g, b]: number[]): string {
|
|
97
|
+
return (
|
|
98
|
+
'#' +
|
|
99
|
+
[r, g, b]
|
|
100
|
+
.map((x) => {
|
|
101
|
+
if (x > 1) {
|
|
102
|
+
x = 1;
|
|
103
|
+
} else if (x < 0) {
|
|
104
|
+
x = 0;
|
|
105
|
+
}
|
|
106
|
+
return Math.round(x * 255)
|
|
107
|
+
.toString(16)
|
|
108
|
+
.padStart(2, '0');
|
|
109
|
+
})
|
|
110
|
+
.join('')
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export const oklch2rgb = (lch: number[]) =>
|
|
115
|
+
srgbLinear2rgb(xyz2rgbLinear(oklab2xyz(oklch2oklab(lch))));
|
|
116
|
+
export const rgb2oklch = (rgb: number[]) =>
|
|
117
|
+
oklab2oklch(xyz2oklab(rgbLinear2xyz(rgb2srgbLinear(rgb))));
|
|
118
|
+
export const oklch2hex = (l: number, c: number, h: number): string => {
|
|
119
|
+
return rgb2Hex(oklch2rgb([l, c, h]));
|
|
120
|
+
};
|