@tenphi/glaze 0.0.0-snapshot.718119b → 0.0.0-snapshot.78261ef
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 +80 -30
- package/dist/index.cjs +115 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +61 -22
- package/dist/index.d.mts +61 -22
- package/dist/index.mjs +115 -40
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -618,7 +618,7 @@ function coarseScan(h, s, lo, hi, yBase, target, epsilon, maxIter) {
|
|
|
618
618
|
function findLightnessForContrast(options) {
|
|
619
619
|
const { hue, saturation, preferredLightness, baseLinearRgb, contrast: contrastInput, lightnessRange = [0, 1], epsilon = 1e-4, maxIterations = 14 } = options;
|
|
620
620
|
const target = resolveMinContrast(contrastInput);
|
|
621
|
-
const searchTarget = target * 1.
|
|
621
|
+
const searchTarget = target * 1.007;
|
|
622
622
|
const yBase = gamutClampedLuminance(baseLinearRgb);
|
|
623
623
|
const crPref = contrastRatioFromLuminance(cachedLuminance(hue, saturation, preferredLightness), yBase);
|
|
624
624
|
if (crPref >= searchTarget) return {
|
|
@@ -812,6 +812,7 @@ let globalConfig = {
|
|
|
812
812
|
lightLightness: [10, 100],
|
|
813
813
|
darkLightness: [15, 95],
|
|
814
814
|
darkDesaturation: .1,
|
|
815
|
+
darkCurve: .5,
|
|
815
816
|
states: {
|
|
816
817
|
dark: "@dark",
|
|
817
818
|
highContrast: "@high-contrast"
|
|
@@ -840,7 +841,8 @@ const DEFAULT_SHADOW_TUNING = {
|
|
|
840
841
|
lightnessBounds: [.05, .2],
|
|
841
842
|
minGapTarget: .05,
|
|
842
843
|
alphaMax: 1,
|
|
843
|
-
bgHueBlend: .2
|
|
844
|
+
bgHueBlend: .2,
|
|
845
|
+
darkShadowCurve: .4
|
|
844
846
|
};
|
|
845
847
|
function resolveShadowTuning(perColor) {
|
|
846
848
|
return {
|
|
@@ -959,24 +961,42 @@ function topoSort(defs) {
|
|
|
959
961
|
for (const name of Object.keys(defs)) visit(name);
|
|
960
962
|
return result;
|
|
961
963
|
}
|
|
962
|
-
function
|
|
964
|
+
function lightnessWindow(isHighContrast, kind) {
|
|
965
|
+
if (isHighContrast) return [0, 100];
|
|
966
|
+
return kind === "dark" ? globalConfig.darkLightness : globalConfig.lightLightness;
|
|
967
|
+
}
|
|
968
|
+
function mapLightnessLight(l, mode, isHighContrast) {
|
|
963
969
|
if (mode === "static") return l;
|
|
964
|
-
const [lo, hi] =
|
|
970
|
+
const [lo, hi] = lightnessWindow(isHighContrast, "light");
|
|
965
971
|
return l * (hi - lo) / 100 + lo;
|
|
966
972
|
}
|
|
967
|
-
function
|
|
973
|
+
function mobiusCurve(t, beta) {
|
|
974
|
+
if (beta >= 1) return t;
|
|
975
|
+
return t / (t + beta * (1 - t));
|
|
976
|
+
}
|
|
977
|
+
function mapLightnessDark(l, mode, isHighContrast) {
|
|
968
978
|
if (mode === "static") return l;
|
|
969
|
-
const
|
|
970
|
-
|
|
971
|
-
|
|
979
|
+
const beta = isHighContrast ? pairHC(globalConfig.darkCurve) : pairNormal(globalConfig.darkCurve);
|
|
980
|
+
const [darkLo, darkHi] = lightnessWindow(isHighContrast, "dark");
|
|
981
|
+
if (mode === "fixed") return l * (darkHi - darkLo) / 100 + darkLo;
|
|
982
|
+
const [lightLo, lightHi] = lightnessWindow(isHighContrast, "light");
|
|
983
|
+
const t = (lightHi - (l * (lightHi - lightLo) / 100 + lightLo)) / (lightHi - lightLo);
|
|
984
|
+
return darkLo + (darkHi - darkLo) * mobiusCurve(t, beta);
|
|
985
|
+
}
|
|
986
|
+
function lightMappedToDark(lightL, isHighContrast) {
|
|
987
|
+
const beta = isHighContrast ? pairHC(globalConfig.darkCurve) : pairNormal(globalConfig.darkCurve);
|
|
988
|
+
const [lightLo, lightHi] = lightnessWindow(isHighContrast, "light");
|
|
989
|
+
const [darkLo, darkHi] = lightnessWindow(isHighContrast, "dark");
|
|
990
|
+
const t = (lightHi - clamp(lightL, lightLo, lightHi)) / (lightHi - lightLo);
|
|
991
|
+
return darkLo + (darkHi - darkLo) * mobiusCurve(t, beta);
|
|
972
992
|
}
|
|
973
993
|
function mapSaturationDark(s, mode) {
|
|
974
994
|
if (mode === "static") return s;
|
|
975
995
|
return s * (1 - globalConfig.darkDesaturation);
|
|
976
996
|
}
|
|
977
|
-
function schemeLightnessRange(isDark, mode) {
|
|
997
|
+
function schemeLightnessRange(isDark, mode, isHighContrast) {
|
|
978
998
|
if (mode === "static") return [0, 1];
|
|
979
|
-
const [lo, hi] = isDark ?
|
|
999
|
+
const [lo, hi] = lightnessWindow(isHighContrast, isDark ? "dark" : "light");
|
|
980
1000
|
return [lo / 100, hi / 100];
|
|
981
1001
|
}
|
|
982
1002
|
function clamp(v, min, max) {
|
|
@@ -1035,26 +1055,26 @@ function resolveDependentColor(name, def, ctx, isHighContrast, isDark, effective
|
|
|
1035
1055
|
else {
|
|
1036
1056
|
const parsed = parseRelativeOrAbsolute(isHighContrast ? pairHC(rawLightness) : pairNormal(rawLightness));
|
|
1037
1057
|
if (parsed.relative) {
|
|
1038
|
-
|
|
1039
|
-
if (isDark && mode === "auto")
|
|
1040
|
-
preferredL = clamp(baseL + delta, 0, 100);
|
|
1041
|
-
} else if (isDark) preferredL = mapLightnessDark(parsed.value, mode);
|
|
1042
|
-
else preferredL = mapLightnessLight(parsed.value, mode);
|
|
1058
|
+
const delta = parsed.value;
|
|
1059
|
+
if (isDark && mode === "auto") preferredL = lightMappedToDark(clamp(getSchemeVariant(baseResolved, false, isHighContrast).l * 100 + delta, 0, 100), isHighContrast);
|
|
1060
|
+
else preferredL = clamp(baseL + delta, 0, 100);
|
|
1061
|
+
} else if (isDark) preferredL = mapLightnessDark(parsed.value, mode, isHighContrast);
|
|
1062
|
+
else preferredL = mapLightnessLight(parsed.value, mode, isHighContrast);
|
|
1043
1063
|
}
|
|
1044
1064
|
const rawContrast = def.contrast;
|
|
1045
1065
|
if (rawContrast !== void 0) {
|
|
1046
1066
|
const minCr = isHighContrast ? pairHC(rawContrast) : pairNormal(rawContrast);
|
|
1047
1067
|
const effectiveSat = isDark ? mapSaturationDark(satFactor * ctx.saturation / 100, mode) : satFactor * ctx.saturation / 100;
|
|
1048
1068
|
const baseLinearRgb = okhslToLinearSrgb(baseVariant.h, baseVariant.s, baseVariant.l);
|
|
1049
|
-
const
|
|
1069
|
+
const windowRange = schemeLightnessRange(isDark, mode, isHighContrast);
|
|
1050
1070
|
return {
|
|
1051
1071
|
l: findLightnessForContrast({
|
|
1052
1072
|
hue: effectiveHue,
|
|
1053
1073
|
saturation: effectiveSat,
|
|
1054
|
-
preferredLightness: clamp(preferredL / 100,
|
|
1074
|
+
preferredLightness: clamp(preferredL / 100, windowRange[0], windowRange[1]),
|
|
1055
1075
|
baseLinearRgb,
|
|
1056
1076
|
contrast: minCr,
|
|
1057
|
-
lightnessRange
|
|
1077
|
+
lightnessRange: [0, 1]
|
|
1058
1078
|
}).lightness * 100,
|
|
1059
1079
|
satFactor
|
|
1060
1080
|
};
|
|
@@ -1091,13 +1111,13 @@ function resolveColorForScheme(name, def, ctx, isDark, isHighContrast) {
|
|
|
1091
1111
|
let finalL;
|
|
1092
1112
|
let finalSat;
|
|
1093
1113
|
if (isDark && isRoot) {
|
|
1094
|
-
finalL = mapLightnessDark(lightL, mode);
|
|
1114
|
+
finalL = mapLightnessDark(lightL, mode, isHighContrast);
|
|
1095
1115
|
finalSat = mapSaturationDark(satFactor * ctx.saturation / 100, mode);
|
|
1096
1116
|
} else if (isDark && !isRoot) {
|
|
1097
1117
|
finalL = lightL;
|
|
1098
1118
|
finalSat = mapSaturationDark(satFactor * ctx.saturation / 100, mode);
|
|
1099
1119
|
} else if (isRoot) {
|
|
1100
|
-
finalL = mapLightnessLight(lightL, mode);
|
|
1120
|
+
finalL = mapLightnessLight(lightL, mode, isHighContrast);
|
|
1101
1121
|
finalSat = satFactor * ctx.saturation / 100;
|
|
1102
1122
|
} else {
|
|
1103
1123
|
finalL = lightL;
|
|
@@ -1116,7 +1136,13 @@ function resolveShadowForScheme(def, ctx, isDark, isHighContrast) {
|
|
|
1116
1136
|
if (def.fg) fgVariant = getSchemeVariant(ctx.resolved.get(def.fg), isDark, isHighContrast);
|
|
1117
1137
|
const intensity = isHighContrast ? pairHC(def.intensity) : pairNormal(def.intensity);
|
|
1118
1138
|
const tuning = resolveShadowTuning(def.tuning);
|
|
1119
|
-
|
|
1139
|
+
const result = computeShadow(bgVariant, fgVariant, intensity, tuning);
|
|
1140
|
+
if (isDark && tuning.darkShadowCurve < 1 && result.alpha > 0) {
|
|
1141
|
+
const normalized = result.alpha / tuning.alphaMax;
|
|
1142
|
+
const exponent = 1 / tuning.darkShadowCurve;
|
|
1143
|
+
result.alpha = tuning.alphaMax * Math.pow(normalized, exponent);
|
|
1144
|
+
}
|
|
1145
|
+
return result;
|
|
1120
1146
|
}
|
|
1121
1147
|
function variantToLinearRgb(v) {
|
|
1122
1148
|
return okhslToLinearSrgb(v.h, v.s, v.l);
|
|
@@ -1394,10 +1420,14 @@ function createTheme(hue, saturation, initialColors) {
|
|
|
1394
1420
|
};
|
|
1395
1421
|
},
|
|
1396
1422
|
extend(options) {
|
|
1397
|
-
|
|
1398
|
-
|
|
1423
|
+
const newHue = options.hue ?? hue;
|
|
1424
|
+
const newSat = options.saturation ?? saturation;
|
|
1425
|
+
const inheritedColors = {};
|
|
1426
|
+
for (const [name, def] of Object.entries(colorDefs)) if (def.inherit !== false) inheritedColors[name] = def;
|
|
1427
|
+
return createTheme(newHue, newSat, options.colors ? {
|
|
1428
|
+
...inheritedColors,
|
|
1399
1429
|
...options.colors
|
|
1400
|
-
} : { ...
|
|
1430
|
+
} : { ...inheritedColors });
|
|
1401
1431
|
},
|
|
1402
1432
|
resolve() {
|
|
1403
1433
|
return resolveAllColors(hue, saturation, colorDefs);
|
|
@@ -1431,40 +1461,74 @@ function validatePrimaryTheme(primary, themes) {
|
|
|
1431
1461
|
throw new Error(`glaze: primary theme "${primary}" not found in palette. Available: ${available}.`);
|
|
1432
1462
|
}
|
|
1433
1463
|
}
|
|
1434
|
-
|
|
1464
|
+
/**
|
|
1465
|
+
* Resolve the effective primary for an export call.
|
|
1466
|
+
* `false` disables, a string overrides, `undefined` inherits from palette.
|
|
1467
|
+
*/
|
|
1468
|
+
function resolveEffectivePrimary(exportPrimary, palettePrimary) {
|
|
1469
|
+
if (exportPrimary === false) return void 0;
|
|
1470
|
+
return exportPrimary ?? palettePrimary;
|
|
1471
|
+
}
|
|
1472
|
+
/**
|
|
1473
|
+
* Filter a resolved color map, skipping keys already in `seen`.
|
|
1474
|
+
* Warns on collision and keeps the first-written value (first-write-wins).
|
|
1475
|
+
* Returns a new map containing only non-colliding entries.
|
|
1476
|
+
*/
|
|
1477
|
+
function filterCollisions(resolved, prefix, seen, themeName, isPrimary) {
|
|
1478
|
+
const filtered = /* @__PURE__ */ new Map();
|
|
1479
|
+
const label = isPrimary ? `${themeName} (primary)` : themeName;
|
|
1480
|
+
for (const [name, color] of resolved) {
|
|
1481
|
+
const key = `${prefix}${name}`;
|
|
1482
|
+
if (seen.has(key)) {
|
|
1483
|
+
console.warn(`glaze: token "${key}" from theme "${label}" collides with theme "${seen.get(key)}" — skipping.`);
|
|
1484
|
+
continue;
|
|
1485
|
+
}
|
|
1486
|
+
seen.set(key, label);
|
|
1487
|
+
filtered.set(name, color);
|
|
1488
|
+
}
|
|
1489
|
+
return filtered;
|
|
1490
|
+
}
|
|
1491
|
+
function createPalette(themes, paletteOptions) {
|
|
1492
|
+
validatePrimaryTheme(paletteOptions?.primary, themes);
|
|
1435
1493
|
return {
|
|
1436
1494
|
tokens(options) {
|
|
1437
|
-
|
|
1495
|
+
const effectivePrimary = resolveEffectivePrimary(options?.primary, paletteOptions?.primary);
|
|
1496
|
+
if (options?.primary !== void 0) validatePrimaryTheme(effectivePrimary, themes);
|
|
1438
1497
|
const modes = resolveModes(options?.modes);
|
|
1439
1498
|
const allTokens = {};
|
|
1499
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1440
1500
|
for (const [themeName, theme] of Object.entries(themes)) {
|
|
1441
1501
|
const resolved = theme.resolve();
|
|
1442
|
-
const
|
|
1502
|
+
const prefix = resolvePrefix(options, themeName, true);
|
|
1503
|
+
const tokens = buildFlatTokenMap(filterCollisions(resolved, prefix, seen, themeName), prefix, modes, options?.format);
|
|
1443
1504
|
for (const variant of Object.keys(tokens)) {
|
|
1444
1505
|
if (!allTokens[variant]) allTokens[variant] = {};
|
|
1445
1506
|
Object.assign(allTokens[variant], tokens[variant]);
|
|
1446
1507
|
}
|
|
1447
|
-
if (themeName ===
|
|
1448
|
-
const unprefixed = buildFlatTokenMap(resolved, "", modes, options?.format);
|
|
1508
|
+
if (themeName === effectivePrimary) {
|
|
1509
|
+
const unprefixed = buildFlatTokenMap(filterCollisions(resolved, "", seen, themeName, true), "", modes, options?.format);
|
|
1449
1510
|
for (const variant of Object.keys(unprefixed)) Object.assign(allTokens[variant], unprefixed[variant]);
|
|
1450
1511
|
}
|
|
1451
1512
|
}
|
|
1452
1513
|
return allTokens;
|
|
1453
1514
|
},
|
|
1454
1515
|
tasty(options) {
|
|
1455
|
-
|
|
1516
|
+
const effectivePrimary = resolveEffectivePrimary(options?.primary, paletteOptions?.primary);
|
|
1517
|
+
if (options?.primary !== void 0) validatePrimaryTheme(effectivePrimary, themes);
|
|
1456
1518
|
const states = {
|
|
1457
1519
|
dark: options?.states?.dark ?? globalConfig.states.dark,
|
|
1458
1520
|
highContrast: options?.states?.highContrast ?? globalConfig.states.highContrast
|
|
1459
1521
|
};
|
|
1460
1522
|
const modes = resolveModes(options?.modes);
|
|
1461
1523
|
const allTokens = {};
|
|
1524
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1462
1525
|
for (const [themeName, theme] of Object.entries(themes)) {
|
|
1463
1526
|
const resolved = theme.resolve();
|
|
1464
|
-
const
|
|
1527
|
+
const prefix = resolvePrefix(options, themeName, true);
|
|
1528
|
+
const tokens = buildTokenMap(filterCollisions(resolved, prefix, seen, themeName), prefix, states, modes, options?.format);
|
|
1465
1529
|
Object.assign(allTokens, tokens);
|
|
1466
|
-
if (themeName ===
|
|
1467
|
-
const unprefixed = buildTokenMap(resolved, "", states, modes, options?.format);
|
|
1530
|
+
if (themeName === effectivePrimary) {
|
|
1531
|
+
const unprefixed = buildTokenMap(filterCollisions(resolved, "", seen, themeName, true), "", states, modes, options?.format);
|
|
1468
1532
|
Object.assign(allTokens, unprefixed);
|
|
1469
1533
|
}
|
|
1470
1534
|
}
|
|
@@ -1477,7 +1541,8 @@ function createPalette(themes) {
|
|
|
1477
1541
|
return result;
|
|
1478
1542
|
},
|
|
1479
1543
|
css(options) {
|
|
1480
|
-
|
|
1544
|
+
const effectivePrimary = resolveEffectivePrimary(options?.primary, paletteOptions?.primary);
|
|
1545
|
+
if (options?.primary !== void 0) validatePrimaryTheme(effectivePrimary, themes);
|
|
1481
1546
|
const suffix = options?.suffix ?? "-color";
|
|
1482
1547
|
const format = options?.format ?? "rgb";
|
|
1483
1548
|
const allLines = {
|
|
@@ -1486,17 +1551,19 @@ function createPalette(themes) {
|
|
|
1486
1551
|
lightContrast: [],
|
|
1487
1552
|
darkContrast: []
|
|
1488
1553
|
};
|
|
1554
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1489
1555
|
for (const [themeName, theme] of Object.entries(themes)) {
|
|
1490
1556
|
const resolved = theme.resolve();
|
|
1491
|
-
const
|
|
1557
|
+
const prefix = resolvePrefix(options, themeName, true);
|
|
1558
|
+
const css = buildCssMap(filterCollisions(resolved, prefix, seen, themeName), prefix, suffix, format);
|
|
1492
1559
|
for (const key of [
|
|
1493
1560
|
"light",
|
|
1494
1561
|
"dark",
|
|
1495
1562
|
"lightContrast",
|
|
1496
1563
|
"darkContrast"
|
|
1497
1564
|
]) if (css[key]) allLines[key].push(css[key]);
|
|
1498
|
-
if (themeName ===
|
|
1499
|
-
const unprefixed = buildCssMap(resolved, "", suffix, format);
|
|
1565
|
+
if (themeName === effectivePrimary) {
|
|
1566
|
+
const unprefixed = buildCssMap(filterCollisions(resolved, "", seen, themeName, true), "", suffix, format);
|
|
1500
1567
|
for (const key of [
|
|
1501
1568
|
"light",
|
|
1502
1569
|
"dark",
|
|
@@ -1563,6 +1630,7 @@ glaze.configure = function configure(config) {
|
|
|
1563
1630
|
lightLightness: config.lightLightness ?? globalConfig.lightLightness,
|
|
1564
1631
|
darkLightness: config.darkLightness ?? globalConfig.darkLightness,
|
|
1565
1632
|
darkDesaturation: config.darkDesaturation ?? globalConfig.darkDesaturation,
|
|
1633
|
+
darkCurve: config.darkCurve ?? globalConfig.darkCurve,
|
|
1566
1634
|
states: {
|
|
1567
1635
|
dark: config.states?.dark ?? globalConfig.states.dark,
|
|
1568
1636
|
highContrast: config.states?.highContrast ?? globalConfig.states.highContrast
|
|
@@ -1577,8 +1645,8 @@ glaze.configure = function configure(config) {
|
|
|
1577
1645
|
/**
|
|
1578
1646
|
* Compose multiple themes into a palette.
|
|
1579
1647
|
*/
|
|
1580
|
-
glaze.palette = function palette(themes) {
|
|
1581
|
-
return createPalette(themes);
|
|
1648
|
+
glaze.palette = function palette(themes, options) {
|
|
1649
|
+
return createPalette(themes, options);
|
|
1582
1650
|
};
|
|
1583
1651
|
/**
|
|
1584
1652
|
* Create a theme from a serialized export.
|
|
@@ -1599,13 +1667,19 @@ glaze.shadow = function shadow(input) {
|
|
|
1599
1667
|
const bg = parseOkhslInput(input.bg);
|
|
1600
1668
|
const fg = input.fg ? parseOkhslInput(input.fg) : void 0;
|
|
1601
1669
|
const tuning = resolveShadowTuning(input.tuning);
|
|
1602
|
-
|
|
1670
|
+
const result = computeShadow({
|
|
1603
1671
|
...bg,
|
|
1604
1672
|
alpha: 1
|
|
1605
1673
|
}, fg ? {
|
|
1606
1674
|
...fg,
|
|
1607
1675
|
alpha: 1
|
|
1608
1676
|
} : void 0, input.intensity, tuning);
|
|
1677
|
+
if (input.dark && tuning.darkShadowCurve < 1 && result.alpha > 0) {
|
|
1678
|
+
const normalized = result.alpha / tuning.alphaMax;
|
|
1679
|
+
const exponent = 1 / tuning.darkShadowCurve;
|
|
1680
|
+
result.alpha = tuning.alphaMax * Math.pow(normalized, exponent);
|
|
1681
|
+
}
|
|
1682
|
+
return result;
|
|
1609
1683
|
};
|
|
1610
1684
|
/**
|
|
1611
1685
|
* Format a resolved color variant as a CSS string.
|
|
@@ -1662,6 +1736,7 @@ glaze.resetConfig = function resetConfig() {
|
|
|
1662
1736
|
lightLightness: [10, 100],
|
|
1663
1737
|
darkLightness: [15, 95],
|
|
1664
1738
|
darkDesaturation: .1,
|
|
1739
|
+
darkCurve: .5,
|
|
1665
1740
|
states: {
|
|
1666
1741
|
dark: "@dark",
|
|
1667
1742
|
highContrast: "@high-contrast"
|