@tenphi/glaze 0.0.0-snapshot.c8281e2 → 0.0.0-snapshot.cdd8acc
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 +140 -38
- package/dist/index.cjs +315 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +137 -27
- package/dist/index.d.mts +137 -27
- package/dist/index.mjs +314 -52
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -346,6 +346,11 @@ function gamutClampedLuminance(linearRgb) {
|
|
|
346
346
|
const linearSrgbToOklab = (rgb) => {
|
|
347
347
|
return transform(cbrt3(transform(rgb, linear_sRGB_to_LMS_M)), LMS_to_OKLab_M);
|
|
348
348
|
};
|
|
349
|
+
/**
|
|
350
|
+
* Convert OKLab to OKHSL.
|
|
351
|
+
* Input: [L, a, b] where L: 0–1, a/b: roughly -0.5 to 0.5.
|
|
352
|
+
* Returns [h, s, l] where h: 0–360, s: 0–1, l: 0–1.
|
|
353
|
+
*/
|
|
349
354
|
const oklabToOkhsl = (lab) => {
|
|
350
355
|
const L = lab[0];
|
|
351
356
|
const a = lab[1];
|
|
@@ -393,6 +398,39 @@ function srgbToOkhsl(rgb) {
|
|
|
393
398
|
]));
|
|
394
399
|
}
|
|
395
400
|
/**
|
|
401
|
+
* Convert CSS HSL (sRGB-based) to gamma-encoded sRGB [r, g, b] in 0–1 range.
|
|
402
|
+
* h: 0–360, s: 0–1, l: 0–1.
|
|
403
|
+
*
|
|
404
|
+
* Note: CSS HSL is not the same as OKHSL — it's HSL in the sRGB color space.
|
|
405
|
+
* Use this when parsing `hsl(...)` strings before passing to `srgbToOkhsl`.
|
|
406
|
+
*/
|
|
407
|
+
function hslToSrgb(h, s, l) {
|
|
408
|
+
const hh = (h % 360 + 360) % 360 / 360;
|
|
409
|
+
const ss = clampVal(s, 0, 1);
|
|
410
|
+
const ll = clampVal(l, 0, 1);
|
|
411
|
+
if (ss === 0) return [
|
|
412
|
+
ll,
|
|
413
|
+
ll,
|
|
414
|
+
ll
|
|
415
|
+
];
|
|
416
|
+
const q = ll < .5 ? ll * (1 + ss) : ll + ss - ll * ss;
|
|
417
|
+
const p = 2 * ll - q;
|
|
418
|
+
const hueToChannel = (t) => {
|
|
419
|
+
let tt = t;
|
|
420
|
+
if (tt < 0) tt += 1;
|
|
421
|
+
if (tt > 1) tt -= 1;
|
|
422
|
+
if (tt < 1 / 6) return p + (q - p) * 6 * tt;
|
|
423
|
+
if (tt < 1 / 2) return q;
|
|
424
|
+
if (tt < 2 / 3) return p + (q - p) * (2 / 3 - tt) * 6;
|
|
425
|
+
return p;
|
|
426
|
+
};
|
|
427
|
+
return [
|
|
428
|
+
hueToChannel(hh + 1 / 3),
|
|
429
|
+
hueToChannel(hh),
|
|
430
|
+
hueToChannel(hh - 1 / 3)
|
|
431
|
+
];
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
396
434
|
* Parse a hex color string (#rgb or #rrggbb) to sRGB [r, g, b] in 0–1 range.
|
|
397
435
|
* Returns null if the string is not a valid hex color.
|
|
398
436
|
*/
|
|
@@ -620,7 +658,7 @@ function coarseScan(h, s, lo, hi, yBase, target, epsilon, maxIter) {
|
|
|
620
658
|
function findLightnessForContrast(options) {
|
|
621
659
|
const { hue, saturation, preferredLightness, baseLinearRgb, contrast: contrastInput, lightnessRange = [0, 1], epsilon = 1e-4, maxIterations = 14 } = options;
|
|
622
660
|
const target = resolveMinContrast(contrastInput);
|
|
623
|
-
const searchTarget = target * 1.
|
|
661
|
+
const searchTarget = target * 1.01;
|
|
624
662
|
const yBase = gamutClampedLuminance(baseLinearRgb);
|
|
625
663
|
const crPref = contrastRatioFromLuminance(cachedLuminance(hue, saturation, preferredLightness), yBase);
|
|
626
664
|
if (crPref >= searchTarget) return {
|
|
@@ -814,6 +852,7 @@ let globalConfig = {
|
|
|
814
852
|
lightLightness: [10, 100],
|
|
815
853
|
darkLightness: [15, 95],
|
|
816
854
|
darkDesaturation: .1,
|
|
855
|
+
darkCurve: .5,
|
|
817
856
|
states: {
|
|
818
857
|
dark: "@dark",
|
|
819
858
|
highContrast: "@high-contrast"
|
|
@@ -961,25 +1000,42 @@ function topoSort(defs) {
|
|
|
961
1000
|
for (const name of Object.keys(defs)) visit(name);
|
|
962
1001
|
return result;
|
|
963
1002
|
}
|
|
1003
|
+
function lightnessWindow(isHighContrast, kind) {
|
|
1004
|
+
if (isHighContrast) return [0, 100];
|
|
1005
|
+
return kind === "dark" ? globalConfig.darkLightness : globalConfig.lightLightness;
|
|
1006
|
+
}
|
|
964
1007
|
function mapLightnessLight(l, mode, isHighContrast) {
|
|
965
|
-
if (mode === "static"
|
|
966
|
-
const [lo, hi] =
|
|
1008
|
+
if (mode === "static") return l;
|
|
1009
|
+
const [lo, hi] = lightnessWindow(isHighContrast, "light");
|
|
967
1010
|
return l * (hi - lo) / 100 + lo;
|
|
968
1011
|
}
|
|
1012
|
+
function mobiusCurve(t, beta) {
|
|
1013
|
+
if (beta >= 1) return t;
|
|
1014
|
+
return t / (t + beta * (1 - t));
|
|
1015
|
+
}
|
|
969
1016
|
function mapLightnessDark(l, mode, isHighContrast) {
|
|
970
1017
|
if (mode === "static") return l;
|
|
971
|
-
|
|
972
|
-
const [
|
|
973
|
-
if (mode === "fixed") return l * (
|
|
974
|
-
|
|
1018
|
+
const beta = isHighContrast ? pairHC(globalConfig.darkCurve) : pairNormal(globalConfig.darkCurve);
|
|
1019
|
+
const [darkLo, darkHi] = lightnessWindow(isHighContrast, "dark");
|
|
1020
|
+
if (mode === "fixed") return l * (darkHi - darkLo) / 100 + darkLo;
|
|
1021
|
+
const [lightLo, lightHi] = lightnessWindow(isHighContrast, "light");
|
|
1022
|
+
const t = (lightHi - (l * (lightHi - lightLo) / 100 + lightLo)) / (lightHi - lightLo);
|
|
1023
|
+
return darkLo + (darkHi - darkLo) * mobiusCurve(t, beta);
|
|
1024
|
+
}
|
|
1025
|
+
function lightMappedToDark(lightL, isHighContrast) {
|
|
1026
|
+
const beta = isHighContrast ? pairHC(globalConfig.darkCurve) : pairNormal(globalConfig.darkCurve);
|
|
1027
|
+
const [lightLo, lightHi] = lightnessWindow(isHighContrast, "light");
|
|
1028
|
+
const [darkLo, darkHi] = lightnessWindow(isHighContrast, "dark");
|
|
1029
|
+
const t = (lightHi - clamp(lightL, lightLo, lightHi)) / (lightHi - lightLo);
|
|
1030
|
+
return darkLo + (darkHi - darkLo) * mobiusCurve(t, beta);
|
|
975
1031
|
}
|
|
976
1032
|
function mapSaturationDark(s, mode) {
|
|
977
1033
|
if (mode === "static") return s;
|
|
978
1034
|
return s * (1 - globalConfig.darkDesaturation);
|
|
979
1035
|
}
|
|
980
1036
|
function schemeLightnessRange(isDark, mode, isHighContrast) {
|
|
981
|
-
if (mode === "static"
|
|
982
|
-
const [lo, hi] = isDark ?
|
|
1037
|
+
if (mode === "static") return [0, 1];
|
|
1038
|
+
const [lo, hi] = lightnessWindow(isHighContrast, isDark ? "dark" : "light");
|
|
983
1039
|
return [lo / 100, hi / 100];
|
|
984
1040
|
}
|
|
985
1041
|
function clamp(v, min, max) {
|
|
@@ -1038,9 +1094,9 @@ function resolveDependentColor(name, def, ctx, isHighContrast, isDark, effective
|
|
|
1038
1094
|
else {
|
|
1039
1095
|
const parsed = parseRelativeOrAbsolute(isHighContrast ? pairHC(rawLightness) : pairNormal(rawLightness));
|
|
1040
1096
|
if (parsed.relative) {
|
|
1041
|
-
|
|
1042
|
-
if (isDark && mode === "auto")
|
|
1043
|
-
preferredL = clamp(baseL + delta, 0, 100);
|
|
1097
|
+
const delta = parsed.value;
|
|
1098
|
+
if (isDark && mode === "auto") preferredL = lightMappedToDark(clamp(getSchemeVariant(baseResolved, false, isHighContrast).l * 100 + delta, 0, 100), isHighContrast);
|
|
1099
|
+
else preferredL = clamp(baseL + delta, 0, 100);
|
|
1044
1100
|
} else if (isDark) preferredL = mapLightnessDark(parsed.value, mode, isHighContrast);
|
|
1045
1101
|
else preferredL = mapLightnessLight(parsed.value, mode, isHighContrast);
|
|
1046
1102
|
}
|
|
@@ -1049,15 +1105,15 @@ function resolveDependentColor(name, def, ctx, isHighContrast, isDark, effective
|
|
|
1049
1105
|
const minCr = isHighContrast ? pairHC(rawContrast) : pairNormal(rawContrast);
|
|
1050
1106
|
const effectiveSat = isDark ? mapSaturationDark(satFactor * ctx.saturation / 100, mode) : satFactor * ctx.saturation / 100;
|
|
1051
1107
|
const baseLinearRgb = okhslToLinearSrgb(baseVariant.h, baseVariant.s, baseVariant.l);
|
|
1052
|
-
const
|
|
1108
|
+
const windowRange = schemeLightnessRange(isDark, mode, isHighContrast);
|
|
1053
1109
|
return {
|
|
1054
1110
|
l: findLightnessForContrast({
|
|
1055
1111
|
hue: effectiveHue,
|
|
1056
1112
|
saturation: effectiveSat,
|
|
1057
|
-
preferredLightness: clamp(preferredL / 100,
|
|
1113
|
+
preferredLightness: clamp(preferredL / 100, windowRange[0], windowRange[1]),
|
|
1058
1114
|
baseLinearRgb,
|
|
1059
1115
|
contrast: minCr,
|
|
1060
|
-
lightnessRange
|
|
1116
|
+
lightnessRange: [0, 1]
|
|
1061
1117
|
}).lightness * 100,
|
|
1062
1118
|
satFactor
|
|
1063
1119
|
};
|
|
@@ -1397,10 +1453,14 @@ function createTheme(hue, saturation, initialColors) {
|
|
|
1397
1453
|
};
|
|
1398
1454
|
},
|
|
1399
1455
|
extend(options) {
|
|
1400
|
-
|
|
1401
|
-
|
|
1456
|
+
const newHue = options.hue ?? hue;
|
|
1457
|
+
const newSat = options.saturation ?? saturation;
|
|
1458
|
+
const inheritedColors = {};
|
|
1459
|
+
for (const [name, def] of Object.entries(colorDefs)) if (def.inherit !== false) inheritedColors[name] = def;
|
|
1460
|
+
return createTheme(newHue, newSat, options.colors ? {
|
|
1461
|
+
...inheritedColors,
|
|
1402
1462
|
...options.colors
|
|
1403
|
-
} : { ...
|
|
1463
|
+
} : { ...inheritedColors });
|
|
1404
1464
|
},
|
|
1405
1465
|
resolve() {
|
|
1406
1466
|
return resolveAllColors(hue, saturation, colorDefs);
|
|
@@ -1434,40 +1494,74 @@ function validatePrimaryTheme(primary, themes) {
|
|
|
1434
1494
|
throw new Error(`glaze: primary theme "${primary}" not found in palette. Available: ${available}.`);
|
|
1435
1495
|
}
|
|
1436
1496
|
}
|
|
1437
|
-
|
|
1497
|
+
/**
|
|
1498
|
+
* Resolve the effective primary for an export call.
|
|
1499
|
+
* `false` disables, a string overrides, `undefined` inherits from palette.
|
|
1500
|
+
*/
|
|
1501
|
+
function resolveEffectivePrimary(exportPrimary, palettePrimary) {
|
|
1502
|
+
if (exportPrimary === false) return void 0;
|
|
1503
|
+
return exportPrimary ?? palettePrimary;
|
|
1504
|
+
}
|
|
1505
|
+
/**
|
|
1506
|
+
* Filter a resolved color map, skipping keys already in `seen`.
|
|
1507
|
+
* Warns on collision and keeps the first-written value (first-write-wins).
|
|
1508
|
+
* Returns a new map containing only non-colliding entries.
|
|
1509
|
+
*/
|
|
1510
|
+
function filterCollisions(resolved, prefix, seen, themeName, isPrimary) {
|
|
1511
|
+
const filtered = /* @__PURE__ */ new Map();
|
|
1512
|
+
const label = isPrimary ? `${themeName} (primary)` : themeName;
|
|
1513
|
+
for (const [name, color] of resolved) {
|
|
1514
|
+
const key = `${prefix}${name}`;
|
|
1515
|
+
if (seen.has(key)) {
|
|
1516
|
+
console.warn(`glaze: token "${key}" from theme "${label}" collides with theme "${seen.get(key)}" — skipping.`);
|
|
1517
|
+
continue;
|
|
1518
|
+
}
|
|
1519
|
+
seen.set(key, label);
|
|
1520
|
+
filtered.set(name, color);
|
|
1521
|
+
}
|
|
1522
|
+
return filtered;
|
|
1523
|
+
}
|
|
1524
|
+
function createPalette(themes, paletteOptions) {
|
|
1525
|
+
validatePrimaryTheme(paletteOptions?.primary, themes);
|
|
1438
1526
|
return {
|
|
1439
1527
|
tokens(options) {
|
|
1440
|
-
|
|
1528
|
+
const effectivePrimary = resolveEffectivePrimary(options?.primary, paletteOptions?.primary);
|
|
1529
|
+
if (options?.primary !== void 0) validatePrimaryTheme(effectivePrimary, themes);
|
|
1441
1530
|
const modes = resolveModes(options?.modes);
|
|
1442
1531
|
const allTokens = {};
|
|
1532
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1443
1533
|
for (const [themeName, theme] of Object.entries(themes)) {
|
|
1444
1534
|
const resolved = theme.resolve();
|
|
1445
|
-
const
|
|
1535
|
+
const prefix = resolvePrefix(options, themeName, true);
|
|
1536
|
+
const tokens = buildFlatTokenMap(filterCollisions(resolved, prefix, seen, themeName), prefix, modes, options?.format);
|
|
1446
1537
|
for (const variant of Object.keys(tokens)) {
|
|
1447
1538
|
if (!allTokens[variant]) allTokens[variant] = {};
|
|
1448
1539
|
Object.assign(allTokens[variant], tokens[variant]);
|
|
1449
1540
|
}
|
|
1450
|
-
if (themeName ===
|
|
1451
|
-
const unprefixed = buildFlatTokenMap(resolved, "", modes, options?.format);
|
|
1541
|
+
if (themeName === effectivePrimary) {
|
|
1542
|
+
const unprefixed = buildFlatTokenMap(filterCollisions(resolved, "", seen, themeName, true), "", modes, options?.format);
|
|
1452
1543
|
for (const variant of Object.keys(unprefixed)) Object.assign(allTokens[variant], unprefixed[variant]);
|
|
1453
1544
|
}
|
|
1454
1545
|
}
|
|
1455
1546
|
return allTokens;
|
|
1456
1547
|
},
|
|
1457
1548
|
tasty(options) {
|
|
1458
|
-
|
|
1549
|
+
const effectivePrimary = resolveEffectivePrimary(options?.primary, paletteOptions?.primary);
|
|
1550
|
+
if (options?.primary !== void 0) validatePrimaryTheme(effectivePrimary, themes);
|
|
1459
1551
|
const states = {
|
|
1460
1552
|
dark: options?.states?.dark ?? globalConfig.states.dark,
|
|
1461
1553
|
highContrast: options?.states?.highContrast ?? globalConfig.states.highContrast
|
|
1462
1554
|
};
|
|
1463
1555
|
const modes = resolveModes(options?.modes);
|
|
1464
1556
|
const allTokens = {};
|
|
1557
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1465
1558
|
for (const [themeName, theme] of Object.entries(themes)) {
|
|
1466
1559
|
const resolved = theme.resolve();
|
|
1467
|
-
const
|
|
1560
|
+
const prefix = resolvePrefix(options, themeName, true);
|
|
1561
|
+
const tokens = buildTokenMap(filterCollisions(resolved, prefix, seen, themeName), prefix, states, modes, options?.format);
|
|
1468
1562
|
Object.assign(allTokens, tokens);
|
|
1469
|
-
if (themeName ===
|
|
1470
|
-
const unprefixed = buildTokenMap(resolved, "", states, modes, options?.format);
|
|
1563
|
+
if (themeName === effectivePrimary) {
|
|
1564
|
+
const unprefixed = buildTokenMap(filterCollisions(resolved, "", seen, themeName, true), "", states, modes, options?.format);
|
|
1471
1565
|
Object.assign(allTokens, unprefixed);
|
|
1472
1566
|
}
|
|
1473
1567
|
}
|
|
@@ -1480,7 +1574,8 @@ function createPalette(themes) {
|
|
|
1480
1574
|
return result;
|
|
1481
1575
|
},
|
|
1482
1576
|
css(options) {
|
|
1483
|
-
|
|
1577
|
+
const effectivePrimary = resolveEffectivePrimary(options?.primary, paletteOptions?.primary);
|
|
1578
|
+
if (options?.primary !== void 0) validatePrimaryTheme(effectivePrimary, themes);
|
|
1484
1579
|
const suffix = options?.suffix ?? "-color";
|
|
1485
1580
|
const format = options?.format ?? "rgb";
|
|
1486
1581
|
const allLines = {
|
|
@@ -1489,17 +1584,19 @@ function createPalette(themes) {
|
|
|
1489
1584
|
lightContrast: [],
|
|
1490
1585
|
darkContrast: []
|
|
1491
1586
|
};
|
|
1587
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1492
1588
|
for (const [themeName, theme] of Object.entries(themes)) {
|
|
1493
1589
|
const resolved = theme.resolve();
|
|
1494
|
-
const
|
|
1590
|
+
const prefix = resolvePrefix(options, themeName, true);
|
|
1591
|
+
const css = buildCssMap(filterCollisions(resolved, prefix, seen, themeName), prefix, suffix, format);
|
|
1495
1592
|
for (const key of [
|
|
1496
1593
|
"light",
|
|
1497
1594
|
"dark",
|
|
1498
1595
|
"lightContrast",
|
|
1499
1596
|
"darkContrast"
|
|
1500
1597
|
]) if (css[key]) allLines[key].push(css[key]);
|
|
1501
|
-
if (themeName ===
|
|
1502
|
-
const unprefixed = buildCssMap(resolved, "", suffix, format);
|
|
1598
|
+
if (themeName === effectivePrimary) {
|
|
1599
|
+
const unprefixed = buildCssMap(filterCollisions(resolved, "", seen, themeName, true), "", suffix, format);
|
|
1503
1600
|
for (const key of [
|
|
1504
1601
|
"light",
|
|
1505
1602
|
"dark",
|
|
@@ -1517,33 +1614,183 @@ function createPalette(themes) {
|
|
|
1517
1614
|
}
|
|
1518
1615
|
};
|
|
1519
1616
|
}
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1617
|
+
/** Matches CSS color functions Glaze itself emits, plus their legacy alpha aliases. */
|
|
1618
|
+
const COLOR_FN_RE = /^(rgba?|hsla?|okhsl|oklch)\(\s*([^)]*)\s*\)$/i;
|
|
1619
|
+
function parseNumberOrPercent(raw, percentScale) {
|
|
1620
|
+
if (raw.endsWith("%")) return parseFloat(raw) / 100 * percentScale;
|
|
1621
|
+
return parseFloat(raw);
|
|
1622
|
+
}
|
|
1623
|
+
function parseColorString(input) {
|
|
1624
|
+
if (input.startsWith("#")) {
|
|
1625
|
+
const rgb = parseHex(input);
|
|
1626
|
+
if (!rgb) throw new Error(`glaze: invalid hex color "${input}".`);
|
|
1627
|
+
const [h, s, l] = srgbToOkhsl(rgb);
|
|
1628
|
+
return {
|
|
1629
|
+
h,
|
|
1630
|
+
s,
|
|
1631
|
+
l
|
|
1632
|
+
};
|
|
1633
|
+
}
|
|
1634
|
+
const m = input.match(COLOR_FN_RE);
|
|
1635
|
+
if (!m) throw new Error(`glaze: unsupported color string "${input}".`);
|
|
1636
|
+
const fn = m[1].toLowerCase();
|
|
1637
|
+
const body = m[2].trim();
|
|
1638
|
+
let parts;
|
|
1639
|
+
let hasAlpha = false;
|
|
1640
|
+
const slashIdx = body.indexOf("/");
|
|
1641
|
+
if (slashIdx !== -1) {
|
|
1642
|
+
parts = body.slice(0, slashIdx).trim().split(/[\s,]+/).filter(Boolean);
|
|
1643
|
+
hasAlpha = body.slice(slashIdx + 1).trim().length > 0;
|
|
1644
|
+
} else {
|
|
1645
|
+
parts = body.split(/[\s,]+/).filter(Boolean);
|
|
1646
|
+
if (parts.length === 4) {
|
|
1647
|
+
parts.pop();
|
|
1648
|
+
hasAlpha = true;
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
if (hasAlpha) console.warn(`glaze: alpha component dropped from "${input}" (standalone color has no opacity field).`);
|
|
1652
|
+
if (parts.length !== 3) throw new Error(`glaze: expected 3 components in "${input}".`);
|
|
1653
|
+
switch (fn) {
|
|
1654
|
+
case "rgb":
|
|
1655
|
+
case "rgba": {
|
|
1656
|
+
const [h, s, l] = srgbToOkhsl([
|
|
1657
|
+
parseNumberOrPercent(parts[0], 255) / 255,
|
|
1658
|
+
parseNumberOrPercent(parts[1], 255) / 255,
|
|
1659
|
+
parseNumberOrPercent(parts[2], 255) / 255
|
|
1660
|
+
]);
|
|
1661
|
+
return {
|
|
1662
|
+
h,
|
|
1663
|
+
s,
|
|
1664
|
+
l
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
case "hsl":
|
|
1668
|
+
case "hsla": {
|
|
1669
|
+
const [oh, os, ol] = srgbToOkhsl(hslToSrgb(parseFloat(parts[0]), parseNumberOrPercent(parts[1], 1), parseNumberOrPercent(parts[2], 1)));
|
|
1670
|
+
return {
|
|
1671
|
+
h: oh,
|
|
1672
|
+
s: os,
|
|
1673
|
+
l: ol
|
|
1674
|
+
};
|
|
1675
|
+
}
|
|
1676
|
+
case "okhsl": return {
|
|
1677
|
+
h: parseFloat(parts[0]),
|
|
1678
|
+
s: parseNumberOrPercent(parts[1], 1),
|
|
1679
|
+
l: parseNumberOrPercent(parts[2], 1)
|
|
1680
|
+
};
|
|
1681
|
+
case "oklch": {
|
|
1682
|
+
const L = parseNumberOrPercent(parts[0], 1);
|
|
1683
|
+
const C = parseFloat(parts[1]);
|
|
1684
|
+
const hRad = parseFloat(parts[2]) * Math.PI / 180;
|
|
1685
|
+
const [h, s, l] = oklabToOkhsl([
|
|
1686
|
+
L,
|
|
1687
|
+
C * Math.cos(hRad),
|
|
1688
|
+
C * Math.sin(hRad)
|
|
1689
|
+
]);
|
|
1690
|
+
return {
|
|
1691
|
+
h,
|
|
1692
|
+
s,
|
|
1693
|
+
l
|
|
1694
|
+
};
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
throw new Error(`glaze: unsupported color function "${fn}".`);
|
|
1698
|
+
}
|
|
1699
|
+
function extractOkhslFromValue(value) {
|
|
1700
|
+
if (typeof value === "string") return parseColorString(value);
|
|
1701
|
+
if (Array.isArray(value)) {
|
|
1702
|
+
const [r, g, b] = value;
|
|
1703
|
+
const [h, s, l] = srgbToOkhsl([
|
|
1704
|
+
r / 255,
|
|
1705
|
+
g / 255,
|
|
1706
|
+
b / 255
|
|
1707
|
+
]);
|
|
1708
|
+
return {
|
|
1709
|
+
h,
|
|
1710
|
+
s,
|
|
1711
|
+
l
|
|
1712
|
+
};
|
|
1713
|
+
}
|
|
1714
|
+
return value;
|
|
1715
|
+
}
|
|
1716
|
+
function buildValueDefs(main, options) {
|
|
1717
|
+
const absoluteSeedHue = typeof options?.hue === "number" ? options.hue : main.h;
|
|
1718
|
+
const seedSaturation = options?.saturation ?? main.s * 100;
|
|
1719
|
+
const relativeHue = typeof options?.hue === "string" ? options.hue : void 0;
|
|
1720
|
+
if (options?.base !== void 0) {
|
|
1721
|
+
const baseOkhsl = extractOkhslFromValue(options.base);
|
|
1722
|
+
const baseSatFactor = seedSaturation > 0 ? Math.min(baseOkhsl.s * 100 / seedSaturation, 1) : 0;
|
|
1723
|
+
return {
|
|
1724
|
+
seedHue: absoluteSeedHue,
|
|
1725
|
+
seedSaturation,
|
|
1726
|
+
defs: {
|
|
1727
|
+
__base__: {
|
|
1728
|
+
hue: baseOkhsl.h,
|
|
1729
|
+
saturation: baseSatFactor,
|
|
1730
|
+
lightness: baseOkhsl.l * 100,
|
|
1731
|
+
mode: options.mode
|
|
1732
|
+
},
|
|
1733
|
+
__color__: {
|
|
1734
|
+
base: "__base__",
|
|
1735
|
+
hue: relativeHue,
|
|
1736
|
+
saturation: options.saturationFactor,
|
|
1737
|
+
lightness: options.lightness ?? main.l * 100,
|
|
1738
|
+
contrast: options.contrast,
|
|
1739
|
+
mode: options.mode
|
|
1740
|
+
}
|
|
1741
|
+
},
|
|
1742
|
+
primary: "__color__"
|
|
1743
|
+
};
|
|
1744
|
+
}
|
|
1745
|
+
return {
|
|
1746
|
+
seedHue: absoluteSeedHue,
|
|
1747
|
+
seedSaturation,
|
|
1748
|
+
defs: { __color__: {
|
|
1749
|
+
hue: relativeHue,
|
|
1750
|
+
saturation: options?.saturationFactor,
|
|
1751
|
+
lightness: options?.lightness ?? main.l * 100,
|
|
1752
|
+
contrast: options?.contrast,
|
|
1753
|
+
mode: options?.mode
|
|
1754
|
+
} },
|
|
1755
|
+
primary: "__color__"
|
|
1756
|
+
};
|
|
1757
|
+
}
|
|
1758
|
+
function createColorTokenFromDefs(seedHue, seedSaturation, defs, primary) {
|
|
1759
|
+
const resolveStates = (options) => ({
|
|
1760
|
+
dark: options?.states?.dark ?? globalConfig.states.dark,
|
|
1761
|
+
highContrast: options?.states?.highContrast ?? globalConfig.states.highContrast
|
|
1762
|
+
});
|
|
1526
1763
|
return {
|
|
1527
1764
|
resolve() {
|
|
1528
|
-
return resolveAllColors(
|
|
1765
|
+
return resolveAllColors(seedHue, seedSaturation, defs).get(primary);
|
|
1529
1766
|
},
|
|
1530
1767
|
token(options) {
|
|
1531
|
-
return buildTokenMap(resolveAllColors(
|
|
1532
|
-
dark: options?.states?.dark ?? globalConfig.states.dark,
|
|
1533
|
-
highContrast: options?.states?.highContrast ?? globalConfig.states.highContrast
|
|
1534
|
-
}, resolveModes(options?.modes), options?.format)["#__color__"];
|
|
1768
|
+
return buildTokenMap(resolveAllColors(seedHue, seedSaturation, defs), "", resolveStates(options), resolveModes(options?.modes), options?.format)[`#${primary}`];
|
|
1535
1769
|
},
|
|
1536
1770
|
tasty(options) {
|
|
1537
|
-
return buildTokenMap(resolveAllColors(
|
|
1538
|
-
dark: options?.states?.dark ?? globalConfig.states.dark,
|
|
1539
|
-
highContrast: options?.states?.highContrast ?? globalConfig.states.highContrast
|
|
1540
|
-
}, resolveModes(options?.modes), options?.format)["#__color__"];
|
|
1771
|
+
return buildTokenMap(resolveAllColors(seedHue, seedSaturation, defs), "", resolveStates(options), resolveModes(options?.modes), options?.format)[`#${primary}`];
|
|
1541
1772
|
},
|
|
1542
1773
|
json(options) {
|
|
1543
|
-
return buildJsonMap(resolveAllColors(
|
|
1774
|
+
return buildJsonMap(resolveAllColors(seedHue, seedSaturation, defs), resolveModes(options?.modes), options?.format)[primary];
|
|
1775
|
+
},
|
|
1776
|
+
css(options) {
|
|
1777
|
+
const resolved = resolveAllColors(seedHue, seedSaturation, defs);
|
|
1778
|
+
return buildCssMap(new Map([[options.name, resolved.get(primary)]]), "", options.suffix ?? "-color", options.format ?? "rgb");
|
|
1544
1779
|
}
|
|
1545
1780
|
};
|
|
1546
1781
|
}
|
|
1782
|
+
function createColorToken(input) {
|
|
1783
|
+
const defs = { __color__: {
|
|
1784
|
+
lightness: input.lightness,
|
|
1785
|
+
saturation: input.saturationFactor,
|
|
1786
|
+
mode: input.mode
|
|
1787
|
+
} };
|
|
1788
|
+
return createColorTokenFromDefs(input.hue, input.saturation, defs, "__color__");
|
|
1789
|
+
}
|
|
1790
|
+
function createColorTokenFromValue(value, options) {
|
|
1791
|
+
const { seedHue, seedSaturation, defs, primary } = buildValueDefs(extractOkhslFromValue(value), options);
|
|
1792
|
+
return createColorTokenFromDefs(seedHue, seedSaturation, defs, primary);
|
|
1793
|
+
}
|
|
1547
1794
|
/**
|
|
1548
1795
|
* Create a single-hue glaze theme.
|
|
1549
1796
|
*
|
|
@@ -1566,6 +1813,7 @@ glaze.configure = function configure(config) {
|
|
|
1566
1813
|
lightLightness: config.lightLightness ?? globalConfig.lightLightness,
|
|
1567
1814
|
darkLightness: config.darkLightness ?? globalConfig.darkLightness,
|
|
1568
1815
|
darkDesaturation: config.darkDesaturation ?? globalConfig.darkDesaturation,
|
|
1816
|
+
darkCurve: config.darkCurve ?? globalConfig.darkCurve,
|
|
1569
1817
|
states: {
|
|
1570
1818
|
dark: config.states?.dark ?? globalConfig.states.dark,
|
|
1571
1819
|
highContrast: config.states?.highContrast ?? globalConfig.states.highContrast
|
|
@@ -1580,8 +1828,8 @@ glaze.configure = function configure(config) {
|
|
|
1580
1828
|
/**
|
|
1581
1829
|
* Compose multiple themes into a palette.
|
|
1582
1830
|
*/
|
|
1583
|
-
glaze.palette = function palette(themes) {
|
|
1584
|
-
return createPalette(themes);
|
|
1831
|
+
glaze.palette = function palette(themes, options) {
|
|
1832
|
+
return createPalette(themes, options);
|
|
1585
1833
|
};
|
|
1586
1834
|
/**
|
|
1587
1835
|
* Create a theme from a serialized export.
|
|
@@ -1589,11 +1837,24 @@ glaze.palette = function palette(themes) {
|
|
|
1589
1837
|
glaze.from = function from(data) {
|
|
1590
1838
|
return createTheme(data.hue, data.saturation, data.colors);
|
|
1591
1839
|
};
|
|
1840
|
+
function isStructuredColorInput(input) {
|
|
1841
|
+
return typeof input === "object" && input !== null && !Array.isArray(input) && "hue" in input && "lightness" in input;
|
|
1842
|
+
}
|
|
1592
1843
|
/**
|
|
1593
1844
|
* Create a standalone single-color token.
|
|
1845
|
+
*
|
|
1846
|
+
* Two overloads:
|
|
1847
|
+
* - `glaze.color(input)` — structured form: `{ hue, saturation, lightness, ... }`.
|
|
1848
|
+
* - `glaze.color(value, overrides?)` — value-shorthand: a hex string,
|
|
1849
|
+
* one of the CSS color functions Glaze itself emits
|
|
1850
|
+
* (`rgb()`, `hsl()`, `okhsl()`, `oklch()`), an `OkhslColor` object
|
|
1851
|
+
* `{ h, s, l }`, or an `[r, g, b]` (0–255) tuple. Optional overrides
|
|
1852
|
+
* accept absolute or relative `hue` / `lightness`, `saturation`,
|
|
1853
|
+
* `mode`, `base` (any value form), and `contrast` against the base.
|
|
1594
1854
|
*/
|
|
1595
|
-
glaze.color = function color(input) {
|
|
1596
|
-
return createColorToken(input);
|
|
1855
|
+
glaze.color = function color(input, options) {
|
|
1856
|
+
if (isStructuredColorInput(input)) return createColorToken(input);
|
|
1857
|
+
return createColorTokenFromValue(input, options);
|
|
1597
1858
|
};
|
|
1598
1859
|
/**
|
|
1599
1860
|
* Compute a shadow color from a bg/fg pair and intensity.
|
|
@@ -1665,6 +1926,7 @@ glaze.resetConfig = function resetConfig() {
|
|
|
1665
1926
|
lightLightness: [10, 100],
|
|
1666
1927
|
darkLightness: [15, 95],
|
|
1667
1928
|
darkDesaturation: .1,
|
|
1929
|
+
darkCurve: .5,
|
|
1668
1930
|
states: {
|
|
1669
1931
|
dark: "@dark",
|
|
1670
1932
|
highContrast: "@high-contrast"
|
|
@@ -1686,9 +1948,11 @@ exports.formatOklch = formatOklch;
|
|
|
1686
1948
|
exports.formatRgb = formatRgb;
|
|
1687
1949
|
exports.gamutClampedLuminance = gamutClampedLuminance;
|
|
1688
1950
|
exports.glaze = glaze;
|
|
1951
|
+
exports.hslToSrgb = hslToSrgb;
|
|
1689
1952
|
exports.okhslToLinearSrgb = okhslToLinearSrgb;
|
|
1690
1953
|
exports.okhslToOklab = okhslToOklab;
|
|
1691
1954
|
exports.okhslToSrgb = okhslToSrgb;
|
|
1955
|
+
exports.oklabToOkhsl = oklabToOkhsl;
|
|
1692
1956
|
exports.parseHex = parseHex;
|
|
1693
1957
|
exports.relativeLuminanceFromLinearRgb = relativeLuminanceFromLinearRgb;
|
|
1694
1958
|
exports.resolveMinContrast = resolveMinContrast;
|