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