@tenphi/glaze 0.5.8 → 0.6.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/README.md +157 -3
- package/dist/index.cjs +248 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +78 -4
- package/dist/index.d.mts +78 -4
- package/dist/index.mjs +247 -53
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* against a base color. Used by glaze when resolving dependent colors
|
|
7
7
|
* with `contrast`.
|
|
8
8
|
*/
|
|
9
|
+
type LinearRgb = [number, number, number];
|
|
9
10
|
type ContrastPreset = 'AA' | 'AAA' | 'AA-large' | 'AAA-large';
|
|
10
11
|
type MinContrast$1 = number | ContrastPreset;
|
|
11
12
|
interface FindLightnessForContrastOptions {
|
|
@@ -42,6 +43,39 @@ declare function resolveMinContrast(value: MinContrast$1): number;
|
|
|
42
43
|
* against a base color, staying as close to `preferredLightness` as possible.
|
|
43
44
|
*/
|
|
44
45
|
declare function findLightnessForContrast(options: FindLightnessForContrastOptions): FindLightnessForContrastResult;
|
|
46
|
+
interface FindValueForMixContrastOptions {
|
|
47
|
+
/** Preferred mix parameter (0–1). */
|
|
48
|
+
preferredValue: number;
|
|
49
|
+
/** Base color as linear sRGB. */
|
|
50
|
+
baseLinearRgb: LinearRgb;
|
|
51
|
+
/** Target color as linear sRGB. */
|
|
52
|
+
targetLinearRgb: LinearRgb;
|
|
53
|
+
/** WCAG contrast target. */
|
|
54
|
+
contrast: MinContrast$1;
|
|
55
|
+
/**
|
|
56
|
+
* Compute the luminance of the mixed color at parameter t.
|
|
57
|
+
* For opaque: luminance of OKHSL-interpolated color.
|
|
58
|
+
* For transparent: luminance of alpha-composited color over base.
|
|
59
|
+
*/
|
|
60
|
+
luminanceAtValue: (t: number) => number;
|
|
61
|
+
/** Convergence threshold. Default: 1e-4. */
|
|
62
|
+
epsilon?: number;
|
|
63
|
+
/** Maximum binary-search iterations per branch. Default: 20. */
|
|
64
|
+
maxIterations?: number;
|
|
65
|
+
}
|
|
66
|
+
interface FindValueForMixContrastResult {
|
|
67
|
+
/** Chosen mix parameter (0–1). */
|
|
68
|
+
value: number;
|
|
69
|
+
/** Achieved WCAG contrast ratio. */
|
|
70
|
+
contrast: number;
|
|
71
|
+
/** Whether the target was reached. */
|
|
72
|
+
met: boolean;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Find the mix parameter (ratio or opacity) that satisfies a WCAG 2 contrast
|
|
76
|
+
* target against a base color, staying as close to `preferredValue` as possible.
|
|
77
|
+
*/
|
|
78
|
+
declare function findValueForMixContrast(options: FindValueForMixContrastOptions): FindValueForMixContrastResult;
|
|
45
79
|
//#endregion
|
|
46
80
|
//#region src/types.d.ts
|
|
47
81
|
/** A value or [normal, high-contrast] pair. */
|
|
@@ -144,7 +178,41 @@ interface ShadowColorDef {
|
|
|
144
178
|
/** Override default tuning. Merged field-by-field with global `shadowTuning`. */
|
|
145
179
|
tuning?: ShadowTuning;
|
|
146
180
|
}
|
|
147
|
-
|
|
181
|
+
interface MixColorDef {
|
|
182
|
+
type: 'mix';
|
|
183
|
+
/** Background/base color name — the "from" color. */
|
|
184
|
+
base: string;
|
|
185
|
+
/** Target color name — the "to" color to mix toward. */
|
|
186
|
+
target: string;
|
|
187
|
+
/**
|
|
188
|
+
* Mix ratio 0–100 (0 = pure base, 100 = pure target).
|
|
189
|
+
* In 'transparent' blend mode, this controls the opacity of the target.
|
|
190
|
+
* Supports [normal, highContrast] pair.
|
|
191
|
+
*/
|
|
192
|
+
value: HCPair<number>;
|
|
193
|
+
/**
|
|
194
|
+
* Blending mode. Default: 'opaque'.
|
|
195
|
+
* - 'opaque': produces a solid color by interpolating base and target.
|
|
196
|
+
* - 'transparent': produces the target color with alpha = value/100.
|
|
197
|
+
*/
|
|
198
|
+
blend?: 'opaque' | 'transparent';
|
|
199
|
+
/**
|
|
200
|
+
* Interpolation color space for opaque blending. Default: 'okhsl'.
|
|
201
|
+
* - 'okhsl': perceptually uniform, consistent with Glaze's internal model.
|
|
202
|
+
* - 'srgb': linear sRGB interpolation, matches browser compositing.
|
|
203
|
+
*
|
|
204
|
+
* Ignored for 'transparent' blend (always composites in linear sRGB).
|
|
205
|
+
*/
|
|
206
|
+
space?: 'okhsl' | 'srgb';
|
|
207
|
+
/**
|
|
208
|
+
* Minimum WCAG contrast between the base and the resulting color.
|
|
209
|
+
* In 'opaque' mode, adjusts the mix ratio to meet contrast.
|
|
210
|
+
* In 'transparent' mode, adjusts opacity to meet contrast against the composite.
|
|
211
|
+
* Supports [normal, highContrast] pair.
|
|
212
|
+
*/
|
|
213
|
+
contrast?: HCPair<MinContrast>;
|
|
214
|
+
}
|
|
215
|
+
type ColorDef = RegularColorDef | ShadowColorDef | MixColorDef;
|
|
148
216
|
type ColorMap = Record<string, ColorDef>;
|
|
149
217
|
/** Resolved color for a single scheme variant. */
|
|
150
218
|
interface ResolvedColorVariant {
|
|
@@ -394,6 +462,10 @@ declare namespace glaze {
|
|
|
394
462
|
* computation for WCAG 2 contrast calculations, and multi-format output
|
|
395
463
|
* (okhsl, rgb, hsl, oklch).
|
|
396
464
|
*/
|
|
465
|
+
/**
|
|
466
|
+
* Convert OKHSL (h: 0–360, s: 0–1, l: 0–1) to OKLab [L, a, b].
|
|
467
|
+
*/
|
|
468
|
+
declare function okhslToOklab(h: number, s: number, l: number): [number, number, number];
|
|
397
469
|
/**
|
|
398
470
|
* Convert OKHSL (h: 0–360, s: 0–1, l: 0–1) to linear sRGB.
|
|
399
471
|
* Channels may exceed [0, 1] near gamut boundaries — caller must clamp if needed.
|
|
@@ -413,9 +485,11 @@ declare function contrastRatioFromLuminance(yA: number, yB: number): number;
|
|
|
413
485
|
*/
|
|
414
486
|
declare function okhslToSrgb(h: number, s: number, l: number): [number, number, number];
|
|
415
487
|
/**
|
|
416
|
-
*
|
|
488
|
+
* Compute WCAG 2 relative luminance from linear sRGB, matching the browser
|
|
489
|
+
* rendering pipeline: gamma-encode, clamp to sRGB gamut [0,1], then linearize.
|
|
490
|
+
* This avoids over/under-estimating luminance for out-of-gamut OKHSL colors.
|
|
417
491
|
*/
|
|
418
|
-
declare function
|
|
492
|
+
declare function gamutClampedLuminance(linearRgb: [number, number, number]): number;
|
|
419
493
|
/**
|
|
420
494
|
* Convert gamma-encoded sRGB (0–1 per channel) to OKHSL.
|
|
421
495
|
* Returns [h, s, l] where h: 0–360, s: 0–1, l: 0–1.
|
|
@@ -447,5 +521,5 @@ declare function formatHsl(h: number, s: number, l: number): string;
|
|
|
447
521
|
*/
|
|
448
522
|
declare function formatOklch(h: number, s: number, l: number): string;
|
|
449
523
|
//#endregion
|
|
450
|
-
export { type AdaptationMode, type ColorDef, type ColorMap, type ContrastPreset, type FindLightnessForContrastOptions, type FindLightnessForContrastResult, type GlazeColorFormat, type GlazeColorInput, type GlazeColorToken, type GlazeConfig, type GlazeCssOptions, type GlazeCssResult, type GlazeExtendOptions, type GlazeJsonOptions, type GlazeOutputModes, type GlazePalette, type GlazeShadowInput, type GlazeTheme, type GlazeThemeExport, type GlazeTokenOptions, type HCPair, type HexColor, type MinContrast, type OkhslColor, type RegularColorDef, type RelativeValue, type ResolvedColor, type ResolvedColorVariant, type ShadowColorDef, type ShadowTuning, contrastRatioFromLuminance, findLightnessForContrast, formatHsl, formatOkhsl, formatOklch, formatRgb, glaze, okhslToLinearSrgb, okhslToOklab, okhslToSrgb, parseHex, relativeLuminanceFromLinearRgb, resolveMinContrast, srgbToOkhsl };
|
|
524
|
+
export { type AdaptationMode, type ColorDef, type ColorMap, type ContrastPreset, type FindLightnessForContrastOptions, type FindLightnessForContrastResult, type FindValueForMixContrastOptions, type FindValueForMixContrastResult, type GlazeColorFormat, type GlazeColorInput, type GlazeColorToken, type GlazeConfig, type GlazeCssOptions, type GlazeCssResult, type GlazeExtendOptions, type GlazeJsonOptions, type GlazeOutputModes, type GlazePalette, type GlazeShadowInput, type GlazeTheme, type GlazeThemeExport, type GlazeTokenOptions, type HCPair, type HexColor, type MinContrast, type MixColorDef, type OkhslColor, type RegularColorDef, type RelativeValue, type ResolvedColor, type ResolvedColorVariant, type ShadowColorDef, type ShadowTuning, contrastRatioFromLuminance, findLightnessForContrast, findValueForMixContrast, formatHsl, formatOkhsl, formatOklch, formatRgb, gamutClampedLuminance, glaze, okhslToLinearSrgb, okhslToOklab, okhslToSrgb, parseHex, relativeLuminanceFromLinearRgb, resolveMinContrast, srgbToOkhsl };
|
|
451
525
|
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.mts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* against a base color. Used by glaze when resolving dependent colors
|
|
7
7
|
* with `contrast`.
|
|
8
8
|
*/
|
|
9
|
+
type LinearRgb = [number, number, number];
|
|
9
10
|
type ContrastPreset = 'AA' | 'AAA' | 'AA-large' | 'AAA-large';
|
|
10
11
|
type MinContrast$1 = number | ContrastPreset;
|
|
11
12
|
interface FindLightnessForContrastOptions {
|
|
@@ -42,6 +43,39 @@ declare function resolveMinContrast(value: MinContrast$1): number;
|
|
|
42
43
|
* against a base color, staying as close to `preferredLightness` as possible.
|
|
43
44
|
*/
|
|
44
45
|
declare function findLightnessForContrast(options: FindLightnessForContrastOptions): FindLightnessForContrastResult;
|
|
46
|
+
interface FindValueForMixContrastOptions {
|
|
47
|
+
/** Preferred mix parameter (0–1). */
|
|
48
|
+
preferredValue: number;
|
|
49
|
+
/** Base color as linear sRGB. */
|
|
50
|
+
baseLinearRgb: LinearRgb;
|
|
51
|
+
/** Target color as linear sRGB. */
|
|
52
|
+
targetLinearRgb: LinearRgb;
|
|
53
|
+
/** WCAG contrast target. */
|
|
54
|
+
contrast: MinContrast$1;
|
|
55
|
+
/**
|
|
56
|
+
* Compute the luminance of the mixed color at parameter t.
|
|
57
|
+
* For opaque: luminance of OKHSL-interpolated color.
|
|
58
|
+
* For transparent: luminance of alpha-composited color over base.
|
|
59
|
+
*/
|
|
60
|
+
luminanceAtValue: (t: number) => number;
|
|
61
|
+
/** Convergence threshold. Default: 1e-4. */
|
|
62
|
+
epsilon?: number;
|
|
63
|
+
/** Maximum binary-search iterations per branch. Default: 20. */
|
|
64
|
+
maxIterations?: number;
|
|
65
|
+
}
|
|
66
|
+
interface FindValueForMixContrastResult {
|
|
67
|
+
/** Chosen mix parameter (0–1). */
|
|
68
|
+
value: number;
|
|
69
|
+
/** Achieved WCAG contrast ratio. */
|
|
70
|
+
contrast: number;
|
|
71
|
+
/** Whether the target was reached. */
|
|
72
|
+
met: boolean;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Find the mix parameter (ratio or opacity) that satisfies a WCAG 2 contrast
|
|
76
|
+
* target against a base color, staying as close to `preferredValue` as possible.
|
|
77
|
+
*/
|
|
78
|
+
declare function findValueForMixContrast(options: FindValueForMixContrastOptions): FindValueForMixContrastResult;
|
|
45
79
|
//#endregion
|
|
46
80
|
//#region src/types.d.ts
|
|
47
81
|
/** A value or [normal, high-contrast] pair. */
|
|
@@ -144,7 +178,41 @@ interface ShadowColorDef {
|
|
|
144
178
|
/** Override default tuning. Merged field-by-field with global `shadowTuning`. */
|
|
145
179
|
tuning?: ShadowTuning;
|
|
146
180
|
}
|
|
147
|
-
|
|
181
|
+
interface MixColorDef {
|
|
182
|
+
type: 'mix';
|
|
183
|
+
/** Background/base color name — the "from" color. */
|
|
184
|
+
base: string;
|
|
185
|
+
/** Target color name — the "to" color to mix toward. */
|
|
186
|
+
target: string;
|
|
187
|
+
/**
|
|
188
|
+
* Mix ratio 0–100 (0 = pure base, 100 = pure target).
|
|
189
|
+
* In 'transparent' blend mode, this controls the opacity of the target.
|
|
190
|
+
* Supports [normal, highContrast] pair.
|
|
191
|
+
*/
|
|
192
|
+
value: HCPair<number>;
|
|
193
|
+
/**
|
|
194
|
+
* Blending mode. Default: 'opaque'.
|
|
195
|
+
* - 'opaque': produces a solid color by interpolating base and target.
|
|
196
|
+
* - 'transparent': produces the target color with alpha = value/100.
|
|
197
|
+
*/
|
|
198
|
+
blend?: 'opaque' | 'transparent';
|
|
199
|
+
/**
|
|
200
|
+
* Interpolation color space for opaque blending. Default: 'okhsl'.
|
|
201
|
+
* - 'okhsl': perceptually uniform, consistent with Glaze's internal model.
|
|
202
|
+
* - 'srgb': linear sRGB interpolation, matches browser compositing.
|
|
203
|
+
*
|
|
204
|
+
* Ignored for 'transparent' blend (always composites in linear sRGB).
|
|
205
|
+
*/
|
|
206
|
+
space?: 'okhsl' | 'srgb';
|
|
207
|
+
/**
|
|
208
|
+
* Minimum WCAG contrast between the base and the resulting color.
|
|
209
|
+
* In 'opaque' mode, adjusts the mix ratio to meet contrast.
|
|
210
|
+
* In 'transparent' mode, adjusts opacity to meet contrast against the composite.
|
|
211
|
+
* Supports [normal, highContrast] pair.
|
|
212
|
+
*/
|
|
213
|
+
contrast?: HCPair<MinContrast>;
|
|
214
|
+
}
|
|
215
|
+
type ColorDef = RegularColorDef | ShadowColorDef | MixColorDef;
|
|
148
216
|
type ColorMap = Record<string, ColorDef>;
|
|
149
217
|
/** Resolved color for a single scheme variant. */
|
|
150
218
|
interface ResolvedColorVariant {
|
|
@@ -394,6 +462,10 @@ declare namespace glaze {
|
|
|
394
462
|
* computation for WCAG 2 contrast calculations, and multi-format output
|
|
395
463
|
* (okhsl, rgb, hsl, oklch).
|
|
396
464
|
*/
|
|
465
|
+
/**
|
|
466
|
+
* Convert OKHSL (h: 0–360, s: 0–1, l: 0–1) to OKLab [L, a, b].
|
|
467
|
+
*/
|
|
468
|
+
declare function okhslToOklab(h: number, s: number, l: number): [number, number, number];
|
|
397
469
|
/**
|
|
398
470
|
* Convert OKHSL (h: 0–360, s: 0–1, l: 0–1) to linear sRGB.
|
|
399
471
|
* Channels may exceed [0, 1] near gamut boundaries — caller must clamp if needed.
|
|
@@ -413,9 +485,11 @@ declare function contrastRatioFromLuminance(yA: number, yB: number): number;
|
|
|
413
485
|
*/
|
|
414
486
|
declare function okhslToSrgb(h: number, s: number, l: number): [number, number, number];
|
|
415
487
|
/**
|
|
416
|
-
*
|
|
488
|
+
* Compute WCAG 2 relative luminance from linear sRGB, matching the browser
|
|
489
|
+
* rendering pipeline: gamma-encode, clamp to sRGB gamut [0,1], then linearize.
|
|
490
|
+
* This avoids over/under-estimating luminance for out-of-gamut OKHSL colors.
|
|
417
491
|
*/
|
|
418
|
-
declare function
|
|
492
|
+
declare function gamutClampedLuminance(linearRgb: [number, number, number]): number;
|
|
419
493
|
/**
|
|
420
494
|
* Convert gamma-encoded sRGB (0–1 per channel) to OKHSL.
|
|
421
495
|
* Returns [h, s, l] where h: 0–360, s: 0–1, l: 0–1.
|
|
@@ -447,5 +521,5 @@ declare function formatHsl(h: number, s: number, l: number): string;
|
|
|
447
521
|
*/
|
|
448
522
|
declare function formatOklch(h: number, s: number, l: number): string;
|
|
449
523
|
//#endregion
|
|
450
|
-
export { type AdaptationMode, type ColorDef, type ColorMap, type ContrastPreset, type FindLightnessForContrastOptions, type FindLightnessForContrastResult, type GlazeColorFormat, type GlazeColorInput, type GlazeColorToken, type GlazeConfig, type GlazeCssOptions, type GlazeCssResult, type GlazeExtendOptions, type GlazeJsonOptions, type GlazeOutputModes, type GlazePalette, type GlazeShadowInput, type GlazeTheme, type GlazeThemeExport, type GlazeTokenOptions, type HCPair, type HexColor, type MinContrast, type OkhslColor, type RegularColorDef, type RelativeValue, type ResolvedColor, type ResolvedColorVariant, type ShadowColorDef, type ShadowTuning, contrastRatioFromLuminance, findLightnessForContrast, formatHsl, formatOkhsl, formatOklch, formatRgb, glaze, okhslToLinearSrgb, okhslToOklab, okhslToSrgb, parseHex, relativeLuminanceFromLinearRgb, resolveMinContrast, srgbToOkhsl };
|
|
524
|
+
export { type AdaptationMode, type ColorDef, type ColorMap, type ContrastPreset, type FindLightnessForContrastOptions, type FindLightnessForContrastResult, type FindValueForMixContrastOptions, type FindValueForMixContrastResult, type GlazeColorFormat, type GlazeColorInput, type GlazeColorToken, type GlazeConfig, type GlazeCssOptions, type GlazeCssResult, type GlazeExtendOptions, type GlazeJsonOptions, type GlazeOutputModes, type GlazePalette, type GlazeShadowInput, type GlazeTheme, type GlazeThemeExport, type GlazeTokenOptions, type HCPair, type HexColor, type MinContrast, type MixColorDef, type OkhslColor, type RegularColorDef, type RelativeValue, type ResolvedColor, type ResolvedColorVariant, type ShadowColorDef, type ShadowTuning, contrastRatioFromLuminance, findLightnessForContrast, findValueForMixContrast, formatHsl, formatOkhsl, formatOklch, formatRgb, gamutClampedLuminance, glaze, okhslToLinearSrgb, okhslToOklab, okhslToSrgb, parseHex, relativeLuminanceFromLinearRgb, resolveMinContrast, srgbToOkhsl };
|
|
451
525
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
CHANGED
|
@@ -79,8 +79,8 @@ const OKLab_to_linear_sRGB_coefficients = [
|
|
|
79
79
|
.73956515,
|
|
80
80
|
-.45954404,
|
|
81
81
|
.08285427,
|
|
82
|
-
.
|
|
83
|
-
|
|
82
|
+
.1254107,
|
|
83
|
+
.14503204
|
|
84
84
|
]],
|
|
85
85
|
[[.13110757611180954, 1.813339709266608], [
|
|
86
86
|
1.35733652,
|
|
@@ -252,10 +252,9 @@ const getCs = (L, a, b, cusp) => {
|
|
|
252
252
|
];
|
|
253
253
|
};
|
|
254
254
|
/**
|
|
255
|
-
* Convert OKHSL (h: 0–360, s: 0–1, l: 0–1) to
|
|
256
|
-
* Channels may exceed [0, 1] near gamut boundaries — caller must clamp if needed.
|
|
255
|
+
* Convert OKHSL (h: 0–360, s: 0–1, l: 0–1) to OKLab [L, a, b].
|
|
257
256
|
*/
|
|
258
|
-
function
|
|
257
|
+
function okhslToOklab(h, s, l) {
|
|
259
258
|
const L = toeInv(l);
|
|
260
259
|
let a = 0;
|
|
261
260
|
let b = 0;
|
|
@@ -282,11 +281,18 @@ function okhslToLinearSrgb(h, s, l) {
|
|
|
282
281
|
a = c * a_;
|
|
283
282
|
b = c * b_;
|
|
284
283
|
}
|
|
285
|
-
return
|
|
284
|
+
return [
|
|
286
285
|
L,
|
|
287
286
|
a,
|
|
288
287
|
b
|
|
289
|
-
]
|
|
288
|
+
];
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Convert OKHSL (h: 0–360, s: 0–1, l: 0–1) to linear sRGB.
|
|
292
|
+
* Channels may exceed [0, 1] near gamut boundaries — caller must clamp if needed.
|
|
293
|
+
*/
|
|
294
|
+
function okhslToLinearSrgb(h, s, l) {
|
|
295
|
+
return OKLabToLinearSRGB(okhslToOklab(h, s, l));
|
|
290
296
|
}
|
|
291
297
|
/**
|
|
292
298
|
* Compute relative luminance Y from linear sRGB channels.
|
|
@@ -325,40 +331,15 @@ function okhslToSrgb(h, s, l) {
|
|
|
325
331
|
];
|
|
326
332
|
}
|
|
327
333
|
/**
|
|
328
|
-
*
|
|
334
|
+
* Compute WCAG 2 relative luminance from linear sRGB, matching the browser
|
|
335
|
+
* rendering pipeline: gamma-encode, clamp to sRGB gamut [0,1], then linearize.
|
|
336
|
+
* This avoids over/under-estimating luminance for out-of-gamut OKHSL colors.
|
|
329
337
|
*/
|
|
330
|
-
function
|
|
331
|
-
const
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
if (L !== 0 && L !== 1 && s !== 0) {
|
|
336
|
-
const a_ = Math.cos(TAU * hNorm);
|
|
337
|
-
const b_ = Math.sin(TAU * hNorm);
|
|
338
|
-
const [c0, cMid, cMax] = getCs(L, a_, b_, findCuspOKLCH(a_, b_));
|
|
339
|
-
const mid = .8;
|
|
340
|
-
const midInv = 1.25;
|
|
341
|
-
let t, k0, k1, k2;
|
|
342
|
-
if (s < mid) {
|
|
343
|
-
t = midInv * s;
|
|
344
|
-
k0 = 0;
|
|
345
|
-
k1 = mid * c0;
|
|
346
|
-
k2 = 1 - k1 / cMid;
|
|
347
|
-
} else {
|
|
348
|
-
t = 5 * (s - .8);
|
|
349
|
-
k0 = cMid;
|
|
350
|
-
k1 = .2 * cMid ** 2 * 1.25 ** 2 / c0;
|
|
351
|
-
k2 = 1 - k1 / (cMax - cMid);
|
|
352
|
-
}
|
|
353
|
-
const c = k0 + t * k1 / (1 - k2 * t);
|
|
354
|
-
a = c * a_;
|
|
355
|
-
b = c * b_;
|
|
356
|
-
}
|
|
357
|
-
return [
|
|
358
|
-
L,
|
|
359
|
-
a,
|
|
360
|
-
b
|
|
361
|
-
];
|
|
338
|
+
function gamutClampedLuminance(linearRgb) {
|
|
339
|
+
const r = sRGBGammaToLinear(Math.max(0, Math.min(1, sRGBLinearToGamma(linearRgb[0]))));
|
|
340
|
+
const g = sRGBGammaToLinear(Math.max(0, Math.min(1, sRGBLinearToGamma(linearRgb[1]))));
|
|
341
|
+
const b = sRGBGammaToLinear(Math.max(0, Math.min(1, sRGBLinearToGamma(linearRgb[2]))));
|
|
342
|
+
return .2126 * r + .7152 * g + .0722 * b;
|
|
362
343
|
}
|
|
363
344
|
const linearSrgbToOklab = (rgb) => {
|
|
364
345
|
return transform(cbrt3(transform(rgb, linear_sRGB_to_LMS_M)), LMS_to_OKLab_M);
|
|
@@ -511,17 +492,6 @@ function resolveMinContrast(value) {
|
|
|
511
492
|
const CACHE_SIZE = 512;
|
|
512
493
|
const luminanceCache = /* @__PURE__ */ new Map();
|
|
513
494
|
const cacheOrder = [];
|
|
514
|
-
/**
|
|
515
|
-
* Compute WCAG 2 relative luminance from linear sRGB, matching the browser
|
|
516
|
-
* rendering pipeline: gamma-encode, clamp to sRGB gamut [0,1], then linearize.
|
|
517
|
-
* This avoids over/under-estimating luminance for out-of-gamut OKHSL colors.
|
|
518
|
-
*/
|
|
519
|
-
function gamutClampedLuminance(linearRgb) {
|
|
520
|
-
const r = sRGBGammaToLinear(Math.max(0, Math.min(1, sRGBLinearToGamma(linearRgb[0]))));
|
|
521
|
-
const g = sRGBGammaToLinear(Math.max(0, Math.min(1, sRGBLinearToGamma(linearRgb[1]))));
|
|
522
|
-
const b = sRGBGammaToLinear(Math.max(0, Math.min(1, sRGBLinearToGamma(linearRgb[2]))));
|
|
523
|
-
return .2126 * r + .7152 * g + .0722 * b;
|
|
524
|
-
}
|
|
525
495
|
function cachedLuminance(h, s, l) {
|
|
526
496
|
const lRounded = Math.round(l * 1e4) / 1e4;
|
|
527
497
|
const key = `${h}|${s}|${lRounded}`;
|
|
@@ -699,6 +669,135 @@ function findLightnessForContrast(options) {
|
|
|
699
669
|
candidates.sort((a, b) => b.contrast - a.contrast);
|
|
700
670
|
return candidates[0];
|
|
701
671
|
}
|
|
672
|
+
/**
|
|
673
|
+
* Binary-search one branch [lo, hi] for the nearest passing mix value
|
|
674
|
+
* to `preferred`.
|
|
675
|
+
*/
|
|
676
|
+
function searchMixBranch(lo, hi, yBase, target, epsilon, maxIter, preferred, luminanceAt) {
|
|
677
|
+
const crLo = contrastRatioFromLuminance(luminanceAt(lo), yBase);
|
|
678
|
+
const crHi = contrastRatioFromLuminance(luminanceAt(hi), yBase);
|
|
679
|
+
if (crLo < target && crHi < target) {
|
|
680
|
+
if (crLo >= crHi) return {
|
|
681
|
+
lightness: lo,
|
|
682
|
+
contrast: crLo,
|
|
683
|
+
met: false
|
|
684
|
+
};
|
|
685
|
+
return {
|
|
686
|
+
lightness: hi,
|
|
687
|
+
contrast: crHi,
|
|
688
|
+
met: false
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
let low = lo;
|
|
692
|
+
let high = hi;
|
|
693
|
+
for (let i = 0; i < maxIter; i++) {
|
|
694
|
+
if (high - low < epsilon) break;
|
|
695
|
+
const mid = (low + high) / 2;
|
|
696
|
+
if (contrastRatioFromLuminance(luminanceAt(mid), yBase) >= target) if (mid < preferred) low = mid;
|
|
697
|
+
else high = mid;
|
|
698
|
+
else if (mid < preferred) high = mid;
|
|
699
|
+
else low = mid;
|
|
700
|
+
}
|
|
701
|
+
const crLow = contrastRatioFromLuminance(luminanceAt(low), yBase);
|
|
702
|
+
const crHigh = contrastRatioFromLuminance(luminanceAt(high), yBase);
|
|
703
|
+
const lowPasses = crLow >= target;
|
|
704
|
+
const highPasses = crHigh >= target;
|
|
705
|
+
if (lowPasses && highPasses) {
|
|
706
|
+
if (Math.abs(low - preferred) <= Math.abs(high - preferred)) return {
|
|
707
|
+
lightness: low,
|
|
708
|
+
contrast: crLow,
|
|
709
|
+
met: true
|
|
710
|
+
};
|
|
711
|
+
return {
|
|
712
|
+
lightness: high,
|
|
713
|
+
contrast: crHigh,
|
|
714
|
+
met: true
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
if (lowPasses) return {
|
|
718
|
+
lightness: low,
|
|
719
|
+
contrast: crLow,
|
|
720
|
+
met: true
|
|
721
|
+
};
|
|
722
|
+
if (highPasses) return {
|
|
723
|
+
lightness: high,
|
|
724
|
+
contrast: crHigh,
|
|
725
|
+
met: true
|
|
726
|
+
};
|
|
727
|
+
return crLow >= crHigh ? {
|
|
728
|
+
lightness: low,
|
|
729
|
+
contrast: crLow,
|
|
730
|
+
met: false
|
|
731
|
+
} : {
|
|
732
|
+
lightness: high,
|
|
733
|
+
contrast: crHigh,
|
|
734
|
+
met: false
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Find the mix parameter (ratio or opacity) that satisfies a WCAG 2 contrast
|
|
739
|
+
* target against a base color, staying as close to `preferredValue` as possible.
|
|
740
|
+
*/
|
|
741
|
+
function findValueForMixContrast(options) {
|
|
742
|
+
const { preferredValue, baseLinearRgb, contrast: contrastInput, luminanceAtValue, epsilon = 1e-4, maxIterations = 20 } = options;
|
|
743
|
+
const target = resolveMinContrast(contrastInput);
|
|
744
|
+
const searchTarget = target + .01;
|
|
745
|
+
const yBase = gamutClampedLuminance(baseLinearRgb);
|
|
746
|
+
const crPref = contrastRatioFromLuminance(luminanceAtValue(preferredValue), yBase);
|
|
747
|
+
if (crPref >= searchTarget) return {
|
|
748
|
+
value: preferredValue,
|
|
749
|
+
contrast: crPref,
|
|
750
|
+
met: true
|
|
751
|
+
};
|
|
752
|
+
const darkerResult = preferredValue > 0 ? searchMixBranch(0, preferredValue, yBase, searchTarget, epsilon, maxIterations, preferredValue, luminanceAtValue) : null;
|
|
753
|
+
const lighterResult = preferredValue < 1 ? searchMixBranch(preferredValue, 1, yBase, searchTarget, epsilon, maxIterations, preferredValue, luminanceAtValue) : null;
|
|
754
|
+
if (darkerResult) darkerResult.met = darkerResult.contrast >= target;
|
|
755
|
+
if (lighterResult) lighterResult.met = lighterResult.contrast >= target;
|
|
756
|
+
const darkerPasses = darkerResult?.met ?? false;
|
|
757
|
+
const lighterPasses = lighterResult?.met ?? false;
|
|
758
|
+
if (darkerPasses && lighterPasses) {
|
|
759
|
+
if (Math.abs(darkerResult.lightness - preferredValue) <= Math.abs(lighterResult.lightness - preferredValue)) return {
|
|
760
|
+
value: darkerResult.lightness,
|
|
761
|
+
contrast: darkerResult.contrast,
|
|
762
|
+
met: true
|
|
763
|
+
};
|
|
764
|
+
return {
|
|
765
|
+
value: lighterResult.lightness,
|
|
766
|
+
contrast: lighterResult.contrast,
|
|
767
|
+
met: true
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
if (darkerPasses) return {
|
|
771
|
+
value: darkerResult.lightness,
|
|
772
|
+
contrast: darkerResult.contrast,
|
|
773
|
+
met: true
|
|
774
|
+
};
|
|
775
|
+
if (lighterPasses) return {
|
|
776
|
+
value: lighterResult.lightness,
|
|
777
|
+
contrast: lighterResult.contrast,
|
|
778
|
+
met: true
|
|
779
|
+
};
|
|
780
|
+
const candidates = [];
|
|
781
|
+
if (darkerResult) candidates.push({
|
|
782
|
+
...darkerResult,
|
|
783
|
+
branch: "lower"
|
|
784
|
+
});
|
|
785
|
+
if (lighterResult) candidates.push({
|
|
786
|
+
...lighterResult,
|
|
787
|
+
branch: "upper"
|
|
788
|
+
});
|
|
789
|
+
if (candidates.length === 0) return {
|
|
790
|
+
value: preferredValue,
|
|
791
|
+
contrast: crPref,
|
|
792
|
+
met: false
|
|
793
|
+
};
|
|
794
|
+
candidates.sort((a, b) => b.contrast - a.contrast);
|
|
795
|
+
return {
|
|
796
|
+
value: candidates[0].lightness,
|
|
797
|
+
contrast: candidates[0].contrast,
|
|
798
|
+
met: candidates[0].met
|
|
799
|
+
};
|
|
800
|
+
}
|
|
702
801
|
|
|
703
802
|
//#endregion
|
|
704
803
|
//#region src/glaze.ts
|
|
@@ -730,6 +829,9 @@ function pairHC(p) {
|
|
|
730
829
|
function isShadowDef(def) {
|
|
731
830
|
return def.type === "shadow";
|
|
732
831
|
}
|
|
832
|
+
function isMixDef(def) {
|
|
833
|
+
return def.type === "mix";
|
|
834
|
+
}
|
|
733
835
|
const DEFAULT_SHADOW_TUNING = {
|
|
734
836
|
saturationFactor: .18,
|
|
735
837
|
maxSaturation: .25,
|
|
@@ -797,6 +899,13 @@ function validateColorDefs(defs) {
|
|
|
797
899
|
}
|
|
798
900
|
continue;
|
|
799
901
|
}
|
|
902
|
+
if (isMixDef(def)) {
|
|
903
|
+
if (!names.has(def.base)) throw new Error(`glaze: mix "${name}" references non-existent base "${def.base}".`);
|
|
904
|
+
if (!names.has(def.target)) throw new Error(`glaze: mix "${name}" references non-existent target "${def.target}".`);
|
|
905
|
+
if (isShadowDef(defs[def.base])) throw new Error(`glaze: mix "${name}" base "${def.base}" references a shadow color.`);
|
|
906
|
+
if (isShadowDef(defs[def.target])) throw new Error(`glaze: mix "${name}" target "${def.target}" references a shadow color.`);
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
800
909
|
const regDef = def;
|
|
801
910
|
if (regDef.contrast !== void 0 && !regDef.base) throw new Error(`glaze: color "${name}" has "contrast" without "base".`);
|
|
802
911
|
if (regDef.lightness !== void 0 && !isAbsoluteLightness(regDef.lightness) && !regDef.base) throw new Error(`glaze: color "${name}" has relative "lightness" without "base".`);
|
|
@@ -815,6 +924,9 @@ function validateColorDefs(defs) {
|
|
|
815
924
|
if (isShadowDef(def)) {
|
|
816
925
|
dfs(def.bg);
|
|
817
926
|
if (def.fg) dfs(def.fg);
|
|
927
|
+
} else if (isMixDef(def)) {
|
|
928
|
+
dfs(def.base);
|
|
929
|
+
dfs(def.target);
|
|
818
930
|
} else {
|
|
819
931
|
const regDef = def;
|
|
820
932
|
if (regDef.base) dfs(regDef.base);
|
|
@@ -834,6 +946,9 @@ function topoSort(defs) {
|
|
|
834
946
|
if (isShadowDef(def)) {
|
|
835
947
|
visit(def.bg);
|
|
836
948
|
if (def.fg) visit(def.fg);
|
|
949
|
+
} else if (isMixDef(def)) {
|
|
950
|
+
visit(def.base);
|
|
951
|
+
visit(def.target);
|
|
837
952
|
} else {
|
|
838
953
|
const regDef = def;
|
|
839
954
|
if (regDef.base) visit(regDef.base);
|
|
@@ -949,6 +1064,7 @@ function getSchemeVariant(color, isDark, isHighContrast) {
|
|
|
949
1064
|
}
|
|
950
1065
|
function resolveColorForScheme(name, def, ctx, isDark, isHighContrast) {
|
|
951
1066
|
if (isShadowDef(def)) return resolveShadowForScheme(def, ctx, isDark, isHighContrast);
|
|
1067
|
+
if (isMixDef(def)) return resolveMixForScheme(def, ctx, isDark, isHighContrast);
|
|
952
1068
|
const regDef = def;
|
|
953
1069
|
const mode = regDef.mode ?? "auto";
|
|
954
1070
|
const isRoot = isAbsoluteLightness(regDef.lightness) && !regDef.base;
|
|
@@ -994,6 +1110,83 @@ function resolveShadowForScheme(def, ctx, isDark, isHighContrast) {
|
|
|
994
1110
|
const tuning = resolveShadowTuning(def.tuning);
|
|
995
1111
|
return computeShadow(bgVariant, fgVariant, intensity, tuning);
|
|
996
1112
|
}
|
|
1113
|
+
function variantToLinearRgb(v) {
|
|
1114
|
+
return okhslToLinearSrgb(v.h, v.s, v.l);
|
|
1115
|
+
}
|
|
1116
|
+
/**
|
|
1117
|
+
* Resolve hue for OKHSL mixing, handling achromatic colors.
|
|
1118
|
+
* When one color has no saturation, its hue is meaningless —
|
|
1119
|
+
* use the hue from the color that has saturation (matches CSS
|
|
1120
|
+
* color-mix "missing component" behavior).
|
|
1121
|
+
*/
|
|
1122
|
+
function mixHue(base, target, t) {
|
|
1123
|
+
const SAT_EPSILON = 1e-6;
|
|
1124
|
+
const baseHasSat = base.s > SAT_EPSILON;
|
|
1125
|
+
const targetHasSat = target.s > SAT_EPSILON;
|
|
1126
|
+
if (baseHasSat && targetHasSat) return circularLerp(base.h, target.h, t);
|
|
1127
|
+
if (targetHasSat) return target.h;
|
|
1128
|
+
return base.h;
|
|
1129
|
+
}
|
|
1130
|
+
function linearSrgbLerp(base, target, t) {
|
|
1131
|
+
return [
|
|
1132
|
+
base[0] + (target[0] - base[0]) * t,
|
|
1133
|
+
base[1] + (target[1] - base[1]) * t,
|
|
1134
|
+
base[2] + (target[2] - base[2]) * t
|
|
1135
|
+
];
|
|
1136
|
+
}
|
|
1137
|
+
function linearRgbToVariant(rgb) {
|
|
1138
|
+
const [h, s, l] = srgbToOkhsl([
|
|
1139
|
+
Math.max(0, Math.min(1, sRGBLinearToGamma(rgb[0]))),
|
|
1140
|
+
Math.max(0, Math.min(1, sRGBLinearToGamma(rgb[1]))),
|
|
1141
|
+
Math.max(0, Math.min(1, sRGBLinearToGamma(rgb[2])))
|
|
1142
|
+
]);
|
|
1143
|
+
return {
|
|
1144
|
+
h,
|
|
1145
|
+
s,
|
|
1146
|
+
l,
|
|
1147
|
+
alpha: 1
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
function resolveMixForScheme(def, ctx, isDark, isHighContrast) {
|
|
1151
|
+
const baseResolved = ctx.resolved.get(def.base);
|
|
1152
|
+
const targetResolved = ctx.resolved.get(def.target);
|
|
1153
|
+
const baseVariant = getSchemeVariant(baseResolved, isDark, isHighContrast);
|
|
1154
|
+
const targetVariant = getSchemeVariant(targetResolved, isDark, isHighContrast);
|
|
1155
|
+
let t = clamp(isHighContrast ? pairHC(def.value) : pairNormal(def.value), 0, 100) / 100;
|
|
1156
|
+
const blend = def.blend ?? "opaque";
|
|
1157
|
+
const space = def.space ?? "okhsl";
|
|
1158
|
+
const baseLinear = variantToLinearRgb(baseVariant);
|
|
1159
|
+
const targetLinear = variantToLinearRgb(targetVariant);
|
|
1160
|
+
if (def.contrast !== void 0) {
|
|
1161
|
+
const minCr = isHighContrast ? pairHC(def.contrast) : pairNormal(def.contrast);
|
|
1162
|
+
let luminanceAt;
|
|
1163
|
+
if (blend === "transparent") luminanceAt = (v) => gamutClampedLuminance(linearSrgbLerp(baseLinear, targetLinear, v));
|
|
1164
|
+
else if (space === "srgb") luminanceAt = (v) => gamutClampedLuminance(linearSrgbLerp(baseLinear, targetLinear, v));
|
|
1165
|
+
else luminanceAt = (v) => {
|
|
1166
|
+
return gamutClampedLuminance(okhslToLinearSrgb(mixHue(baseVariant, targetVariant, v), baseVariant.s + (targetVariant.s - baseVariant.s) * v, baseVariant.l + (targetVariant.l - baseVariant.l) * v));
|
|
1167
|
+
};
|
|
1168
|
+
t = findValueForMixContrast({
|
|
1169
|
+
preferredValue: t,
|
|
1170
|
+
baseLinearRgb: baseLinear,
|
|
1171
|
+
targetLinearRgb: targetLinear,
|
|
1172
|
+
contrast: minCr,
|
|
1173
|
+
luminanceAtValue: luminanceAt
|
|
1174
|
+
}).value;
|
|
1175
|
+
}
|
|
1176
|
+
if (blend === "transparent") return {
|
|
1177
|
+
h: targetVariant.h,
|
|
1178
|
+
s: targetVariant.s,
|
|
1179
|
+
l: targetVariant.l,
|
|
1180
|
+
alpha: clamp(t, 0, 1)
|
|
1181
|
+
};
|
|
1182
|
+
if (space === "srgb") return linearRgbToVariant(linearSrgbLerp(baseLinear, targetLinear, t));
|
|
1183
|
+
return {
|
|
1184
|
+
h: mixHue(baseVariant, targetVariant, t),
|
|
1185
|
+
s: clamp(baseVariant.s + (targetVariant.s - baseVariant.s) * t, 0, 1),
|
|
1186
|
+
l: clamp(baseVariant.l + (targetVariant.l - baseVariant.l) * t, 0, 1),
|
|
1187
|
+
alpha: 1
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
997
1190
|
function resolveAllColors(hue, saturation, defs) {
|
|
998
1191
|
validateColorDefs(defs);
|
|
999
1192
|
const order = topoSort(defs);
|
|
@@ -1004,7 +1197,8 @@ function resolveAllColors(hue, saturation, defs) {
|
|
|
1004
1197
|
resolved: /* @__PURE__ */ new Map()
|
|
1005
1198
|
};
|
|
1006
1199
|
function defMode(def) {
|
|
1007
|
-
|
|
1200
|
+
if (isShadowDef(def) || isMixDef(def)) return void 0;
|
|
1201
|
+
return def.mode ?? "auto";
|
|
1008
1202
|
}
|
|
1009
1203
|
const lightMap = /* @__PURE__ */ new Map();
|
|
1010
1204
|
for (const name of order) {
|
|
@@ -1442,5 +1636,5 @@ glaze.resetConfig = function resetConfig() {
|
|
|
1442
1636
|
};
|
|
1443
1637
|
|
|
1444
1638
|
//#endregion
|
|
1445
|
-
export { contrastRatioFromLuminance, findLightnessForContrast, formatHsl, formatOkhsl, formatOklch, formatRgb, glaze, okhslToLinearSrgb, okhslToOklab, okhslToSrgb, parseHex, relativeLuminanceFromLinearRgb, resolveMinContrast, srgbToOkhsl };
|
|
1639
|
+
export { contrastRatioFromLuminance, findLightnessForContrast, findValueForMixContrast, formatHsl, formatOkhsl, formatOklch, formatRgb, gamutClampedLuminance, glaze, okhslToLinearSrgb, okhslToOklab, okhslToSrgb, parseHex, relativeLuminanceFromLinearRgb, resolveMinContrast, srgbToOkhsl };
|
|
1446
1640
|
//# sourceMappingURL=index.mjs.map
|