@tenphi/glaze 0.12.0 → 0.13.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/dist/index.cjs +238 -157
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +76 -58
- package/dist/index.d.mts +76 -58
- package/dist/index.mjs +238 -157
- package/dist/index.mjs.map +1 -1
- package/docs/api.md +135 -69
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -627,6 +627,25 @@ function resetConfig() {
|
|
|
627
627
|
configVersion++;
|
|
628
628
|
globalConfig = defaultConfig();
|
|
629
629
|
}
|
|
630
|
+
/**
|
|
631
|
+
* Merge a per-instance config override over a base resolved config.
|
|
632
|
+
* Only fields present in `override` are replaced; others fall through
|
|
633
|
+
* from `base`. `false` for lightness windows passes through as-is
|
|
634
|
+
* (treated as `[0, 100]` by `lightnessWindow()` in scheme-mapping).
|
|
635
|
+
*/
|
|
636
|
+
function mergeConfig(base, override) {
|
|
637
|
+
if (!override) return base;
|
|
638
|
+
return {
|
|
639
|
+
lightLightness: override.lightLightness !== void 0 ? override.lightLightness : base.lightLightness,
|
|
640
|
+
darkLightness: override.darkLightness !== void 0 ? override.darkLightness : base.darkLightness,
|
|
641
|
+
darkDesaturation: override.darkDesaturation ?? base.darkDesaturation,
|
|
642
|
+
darkCurve: override.darkCurve ?? base.darkCurve,
|
|
643
|
+
states: base.states,
|
|
644
|
+
modes: base.modes,
|
|
645
|
+
shadowTuning: override.shadowTuning ?? base.shadowTuning,
|
|
646
|
+
autoFlip: override.autoFlip ?? base.autoFlip
|
|
647
|
+
};
|
|
648
|
+
}
|
|
630
649
|
|
|
631
650
|
//#endregion
|
|
632
651
|
//#region src/hc-pair.ts
|
|
@@ -1049,8 +1068,7 @@ const DEFAULT_SHADOW_TUNING = {
|
|
|
1049
1068
|
alphaMax: 1,
|
|
1050
1069
|
bgHueBlend: .2
|
|
1051
1070
|
};
|
|
1052
|
-
function resolveShadowTuning(perColor) {
|
|
1053
|
-
const globalTuning = getConfig().shadowTuning;
|
|
1071
|
+
function resolveShadowTuning(perColor, globalTuning) {
|
|
1054
1072
|
return {
|
|
1055
1073
|
...DEFAULT_SHADOW_TUNING,
|
|
1056
1074
|
...globalTuning,
|
|
@@ -1102,61 +1120,58 @@ function computeShadow(bg, fg, intensity, tuning) {
|
|
|
1102
1120
|
/**
|
|
1103
1121
|
* Light / dark scheme lightness mappings.
|
|
1104
1122
|
*
|
|
1105
|
-
* Owns the active lightness window selection (
|
|
1106
|
-
*
|
|
1107
|
-
*
|
|
1108
|
-
*
|
|
1123
|
+
* Owns the active lightness window selection (from a resolved effective
|
|
1124
|
+
* config passed in), the Möbius curve used by the `'auto'` dark
|
|
1125
|
+
* adaptation, and the saturation-desaturation reducer for dark mode.
|
|
1126
|
+
*
|
|
1127
|
+
* All functions take a `GlazeConfigResolved` so the full config
|
|
1128
|
+
* (including per-instance overrides) is available without re-reading
|
|
1129
|
+
* the global singleton inside the resolver.
|
|
1109
1130
|
*/
|
|
1110
1131
|
/**
|
|
1111
1132
|
* Resolve the active lightness window for a scheme.
|
|
1112
|
-
* - HC variants always return `[0, 100]` (
|
|
1113
|
-
* -
|
|
1114
|
-
*
|
|
1133
|
+
* - HC variants always return `[0, 100]` (no clamping in high-contrast).
|
|
1134
|
+
* - `false` (= "no clamping") is treated as `[0, 100]`.
|
|
1135
|
+
* - Otherwise uses the window from the resolved effective config.
|
|
1115
1136
|
*/
|
|
1116
|
-
function lightnessWindow(isHighContrast, kind,
|
|
1137
|
+
function lightnessWindow(isHighContrast, kind, config) {
|
|
1117
1138
|
if (isHighContrast) return [0, 100];
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
if (override !== void 0) return override;
|
|
1122
|
-
}
|
|
1123
|
-
const cfg = getConfig();
|
|
1124
|
-
return kind === "dark" ? cfg.darkLightness : cfg.lightLightness;
|
|
1139
|
+
const win = kind === "dark" ? config.darkLightness : config.lightLightness;
|
|
1140
|
+
if (win === false) return [0, 100];
|
|
1141
|
+
return win;
|
|
1125
1142
|
}
|
|
1126
|
-
function mapLightnessLight(l, mode, isHighContrast,
|
|
1143
|
+
function mapLightnessLight(l, mode, isHighContrast, config) {
|
|
1127
1144
|
if (mode === "static") return l;
|
|
1128
|
-
const [lo, hi] = lightnessWindow(isHighContrast, "light",
|
|
1145
|
+
const [lo, hi] = lightnessWindow(isHighContrast, "light", config);
|
|
1129
1146
|
return l * (hi - lo) / 100 + lo;
|
|
1130
1147
|
}
|
|
1131
1148
|
function mobiusCurve(t, beta) {
|
|
1132
1149
|
if (beta >= 1) return t;
|
|
1133
1150
|
return t / (t + beta * (1 - t));
|
|
1134
1151
|
}
|
|
1135
|
-
function mapLightnessDark(l, mode, isHighContrast,
|
|
1152
|
+
function mapLightnessDark(l, mode, isHighContrast, config) {
|
|
1136
1153
|
if (mode === "static") return l;
|
|
1137
|
-
const
|
|
1138
|
-
const
|
|
1139
|
-
const [darkLo, darkHi] = lightnessWindow(isHighContrast, "dark", scaling);
|
|
1154
|
+
const beta = isHighContrast ? pairHC(config.darkCurve) : pairNormal(config.darkCurve);
|
|
1155
|
+
const [darkLo, darkHi] = lightnessWindow(isHighContrast, "dark", config);
|
|
1140
1156
|
if (mode === "fixed") return l * (darkHi - darkLo) / 100 + darkLo;
|
|
1141
|
-
const [lightLo, lightHi] = lightnessWindow(isHighContrast, "light",
|
|
1157
|
+
const [lightLo, lightHi] = lightnessWindow(isHighContrast, "light", config);
|
|
1142
1158
|
const t = (lightHi - (l * (lightHi - lightLo) / 100 + lightLo)) / (lightHi - lightLo);
|
|
1143
1159
|
return darkLo + (darkHi - darkLo) * mobiusCurve(t, beta);
|
|
1144
1160
|
}
|
|
1145
|
-
function lightMappedToDark(lightL, isHighContrast,
|
|
1146
|
-
const
|
|
1147
|
-
const
|
|
1148
|
-
const [
|
|
1149
|
-
const [darkLo, darkHi] = lightnessWindow(isHighContrast, "dark", scaling);
|
|
1161
|
+
function lightMappedToDark(lightL, isHighContrast, config) {
|
|
1162
|
+
const beta = isHighContrast ? pairHC(config.darkCurve) : pairNormal(config.darkCurve);
|
|
1163
|
+
const [lightLo, lightHi] = lightnessWindow(isHighContrast, "light", config);
|
|
1164
|
+
const [darkLo, darkHi] = lightnessWindow(isHighContrast, "dark", config);
|
|
1150
1165
|
const t = (lightHi - clamp(lightL, lightLo, lightHi)) / (lightHi - lightLo);
|
|
1151
1166
|
return darkLo + (darkHi - darkLo) * mobiusCurve(t, beta);
|
|
1152
1167
|
}
|
|
1153
|
-
function mapSaturationDark(s, mode) {
|
|
1168
|
+
function mapSaturationDark(s, mode, config) {
|
|
1154
1169
|
if (mode === "static") return s;
|
|
1155
|
-
return s * (1 -
|
|
1170
|
+
return s * (1 - config.darkDesaturation);
|
|
1156
1171
|
}
|
|
1157
|
-
function schemeLightnessRange(isDark, mode, isHighContrast,
|
|
1172
|
+
function schemeLightnessRange(isDark, mode, isHighContrast, config) {
|
|
1158
1173
|
if (mode === "static") return [0, 1];
|
|
1159
|
-
const [lo, hi] = lightnessWindow(isHighContrast, isDark ? "dark" : "light",
|
|
1174
|
+
const [lo, hi] = lightnessWindow(isHighContrast, isDark ? "dark" : "light", config);
|
|
1160
1175
|
return [lo / 100, hi / 100];
|
|
1161
1176
|
}
|
|
1162
1177
|
|
|
@@ -1294,6 +1309,10 @@ function warnContrastUnmet(name, isDark, isHighContrast, target, actual) {
|
|
|
1294
1309
|
* turns a `ColorMap` into a fully resolved `ResolvedColor` per name.
|
|
1295
1310
|
* Owns the per-scheme resolve helpers for regular, shadow, and mix
|
|
1296
1311
|
* color defs.
|
|
1312
|
+
*
|
|
1313
|
+
* Every function receives a single `GlazeConfigResolved` so the full
|
|
1314
|
+
* per-instance config (including overrides) is available without
|
|
1315
|
+
* re-reading the global singleton mid-resolve.
|
|
1297
1316
|
*/
|
|
1298
1317
|
function getSchemeVariant(color, isDark, isHighContrast) {
|
|
1299
1318
|
if (isDark && isHighContrast) return color.darkContrast;
|
|
@@ -1323,18 +1342,17 @@ function resolveDependentColor(name, def, ctx, isHighContrast, isDark, effective
|
|
|
1323
1342
|
const parsed = parseRelativeOrAbsolute(isHighContrast ? pairHC(rawLightness) : pairNormal(rawLightness));
|
|
1324
1343
|
if (parsed.relative) {
|
|
1325
1344
|
const delta = parsed.value;
|
|
1326
|
-
if (isDark && mode === "auto") preferredL = lightMappedToDark(clamp(getSchemeVariant(baseResolved, false, isHighContrast).l * 100 + delta, 0, 100), isHighContrast, ctx.
|
|
1345
|
+
if (isDark && mode === "auto") preferredL = lightMappedToDark(clamp(getSchemeVariant(baseResolved, false, isHighContrast).l * 100 + delta, 0, 100), isHighContrast, ctx.config);
|
|
1327
1346
|
else preferredL = clamp(baseL + delta, 0, 100);
|
|
1328
|
-
} else if (isDark) preferredL = mapLightnessDark(parsed.value, mode, isHighContrast, ctx.
|
|
1329
|
-
else preferredL = mapLightnessLight(parsed.value, mode, isHighContrast, ctx.
|
|
1347
|
+
} else if (isDark) preferredL = mapLightnessDark(parsed.value, mode, isHighContrast, ctx.config);
|
|
1348
|
+
else preferredL = mapLightnessLight(parsed.value, mode, isHighContrast, ctx.config);
|
|
1330
1349
|
}
|
|
1331
1350
|
const rawContrast = def.contrast;
|
|
1332
1351
|
if (rawContrast !== void 0) {
|
|
1333
1352
|
const minCr = isHighContrast ? pairHC(rawContrast) : pairNormal(rawContrast);
|
|
1334
|
-
const effectiveSat = isDark ? mapSaturationDark(satFactor * ctx.saturation / 100, mode) : satFactor * ctx.saturation / 100;
|
|
1353
|
+
const effectiveSat = isDark ? mapSaturationDark(satFactor * ctx.saturation / 100, mode, ctx.config) : satFactor * ctx.saturation / 100;
|
|
1335
1354
|
const baseLinearRgb = okhslToLinearSrgb(baseVariant.h, baseVariant.s, baseVariant.l);
|
|
1336
|
-
const windowRange = schemeLightnessRange(isDark, mode, isHighContrast, ctx.
|
|
1337
|
-
const autoFlip = ctx.autoFlip ?? getConfig().autoFlip;
|
|
1355
|
+
const windowRange = schemeLightnessRange(isDark, mode, isHighContrast, ctx.config);
|
|
1338
1356
|
let initialDirection;
|
|
1339
1357
|
if (preferredL < baseL) initialDirection = "darker";
|
|
1340
1358
|
else if (preferredL > baseL) initialDirection = "lighter";
|
|
@@ -1346,7 +1364,7 @@ function resolveDependentColor(name, def, ctx, isHighContrast, isDark, effective
|
|
|
1346
1364
|
contrast: minCr,
|
|
1347
1365
|
lightnessRange: [0, 1],
|
|
1348
1366
|
initialDirection,
|
|
1349
|
-
flip: autoFlip
|
|
1367
|
+
flip: ctx.config.autoFlip
|
|
1350
1368
|
});
|
|
1351
1369
|
if (!result.met) warnContrastUnmet(name, isDark, isHighContrast, minCr, result.contrast);
|
|
1352
1370
|
return {
|
|
@@ -1380,13 +1398,13 @@ function resolveColorForScheme(name, def, ctx, isDark, isHighContrast) {
|
|
|
1380
1398
|
let finalL;
|
|
1381
1399
|
let finalSat;
|
|
1382
1400
|
if (isDark && isRoot) {
|
|
1383
|
-
finalL = mapLightnessDark(lightL, mode, isHighContrast, ctx.
|
|
1384
|
-
finalSat = mapSaturationDark(satFactor * ctx.saturation / 100, mode);
|
|
1401
|
+
finalL = mapLightnessDark(lightL, mode, isHighContrast, ctx.config);
|
|
1402
|
+
finalSat = mapSaturationDark(satFactor * ctx.saturation / 100, mode, ctx.config);
|
|
1385
1403
|
} else if (isDark && !isRoot) {
|
|
1386
1404
|
finalL = lightL;
|
|
1387
|
-
finalSat = mapSaturationDark(satFactor * ctx.saturation / 100, mode);
|
|
1405
|
+
finalSat = mapSaturationDark(satFactor * ctx.saturation / 100, mode, ctx.config);
|
|
1388
1406
|
} else if (isRoot) {
|
|
1389
|
-
finalL = mapLightnessLight(lightL, mode, isHighContrast, ctx.
|
|
1407
|
+
finalL = mapLightnessLight(lightL, mode, isHighContrast, ctx.config);
|
|
1390
1408
|
finalSat = satFactor * ctx.saturation / 100;
|
|
1391
1409
|
} else {
|
|
1392
1410
|
finalL = lightL;
|
|
@@ -1404,7 +1422,7 @@ function resolveShadowForScheme(def, ctx, isDark, isHighContrast) {
|
|
|
1404
1422
|
let fgVariant;
|
|
1405
1423
|
if (def.fg) fgVariant = getSchemeVariant(ctx.resolved.get(def.fg), isDark, isHighContrast);
|
|
1406
1424
|
const intensity = isHighContrast ? pairHC(def.intensity) : pairNormal(def.intensity);
|
|
1407
|
-
const tuning = resolveShadowTuning(def.tuning);
|
|
1425
|
+
const tuning = resolveShadowTuning(def.tuning, ctx.config.shadowTuning);
|
|
1408
1426
|
return computeShadow(bgVariant, fgVariant, intensity, tuning);
|
|
1409
1427
|
}
|
|
1410
1428
|
function variantToLinearRgb(v) {
|
|
@@ -1462,14 +1480,13 @@ function resolveMixForScheme(def, ctx, isDark, isHighContrast) {
|
|
|
1462
1480
|
else luminanceAt = (v) => {
|
|
1463
1481
|
return gamutClampedLuminance(okhslToLinearSrgb(mixHue(baseVariant, targetVariant, v), baseVariant.s + (targetVariant.s - baseVariant.s) * v, baseVariant.l + (targetVariant.l - baseVariant.l) * v));
|
|
1464
1482
|
};
|
|
1465
|
-
const autoFlip = ctx.autoFlip ?? getConfig().autoFlip;
|
|
1466
1483
|
t = findValueForMixContrast({
|
|
1467
1484
|
preferredValue: t,
|
|
1468
1485
|
baseLinearRgb: baseLinear,
|
|
1469
1486
|
targetLinearRgb: targetLinear,
|
|
1470
1487
|
contrast: minCr,
|
|
1471
1488
|
luminanceAtValue: luminanceAt,
|
|
1472
|
-
flip: autoFlip
|
|
1489
|
+
flip: ctx.config.autoFlip
|
|
1473
1490
|
}).value;
|
|
1474
1491
|
}
|
|
1475
1492
|
if (blend === "transparent") return {
|
|
@@ -1530,17 +1547,15 @@ function seedField(order, ctx, field, source) {
|
|
|
1530
1547
|
});
|
|
1531
1548
|
}
|
|
1532
1549
|
}
|
|
1533
|
-
function resolveAllColors(hue, saturation, defs,
|
|
1550
|
+
function resolveAllColors(hue, saturation, defs, config, externalBases) {
|
|
1534
1551
|
validateColorDefs(defs, externalBases);
|
|
1535
1552
|
const order = topoSort(defs);
|
|
1536
|
-
const cfg = getConfig();
|
|
1537
1553
|
const ctx = {
|
|
1538
1554
|
hue,
|
|
1539
1555
|
saturation,
|
|
1540
1556
|
defs,
|
|
1541
1557
|
resolved: /* @__PURE__ */ new Map(),
|
|
1542
|
-
|
|
1543
|
-
autoFlip: overrideAutoFlip ?? cfg.autoFlip
|
|
1558
|
+
config
|
|
1544
1559
|
};
|
|
1545
1560
|
if (externalBases) for (const [name, color] of externalBases) ctx.resolved.set(name, color);
|
|
1546
1561
|
const lightMap = runPass(order, defs, ctx, false, false, "light");
|
|
@@ -1666,11 +1681,12 @@ function buildCssMap(resolved, prefix, suffix, format) {
|
|
|
1666
1681
|
* validator, the two factory paths (value vs structured), and the
|
|
1667
1682
|
* JSON-safe export / rehydration round-trip.
|
|
1668
1683
|
*
|
|
1669
|
-
* Standalone tokens snapshot the
|
|
1670
|
-
*
|
|
1671
|
-
*
|
|
1672
|
-
* `
|
|
1673
|
-
*
|
|
1684
|
+
* Standalone tokens snapshot the full effective config at create time
|
|
1685
|
+
* so later `configure()` calls do not retroactively change exported
|
|
1686
|
+
* tokens. The snapshot is built eagerly in
|
|
1687
|
+
* `buildValueFormConfigOverride()` / `buildStructuredConfigOverride()`.
|
|
1688
|
+
* The token's resolved variants are then memoized on first
|
|
1689
|
+
* `.resolve()` / `.token()` / ... call.
|
|
1674
1690
|
*/
|
|
1675
1691
|
/** Internal name of the user-facing standalone color in the synthesized def map. */
|
|
1676
1692
|
const STANDALONE_VALUE = "value";
|
|
@@ -1685,39 +1701,47 @@ const RESERVED_STANDALONE_NAMES = new Set([
|
|
|
1685
1701
|
STANDALONE_BASE
|
|
1686
1702
|
]);
|
|
1687
1703
|
/**
|
|
1688
|
-
*
|
|
1689
|
-
*
|
|
1690
|
-
*
|
|
1691
|
-
*
|
|
1704
|
+
* Build the per-token effective config override for a value-form color.
|
|
1705
|
+
*
|
|
1706
|
+
* Light window defaults to `false` (preserve input lightness exactly).
|
|
1707
|
+
* All other fields snapshot from global at create time. User override
|
|
1708
|
+
* fields win over all defaults.
|
|
1692
1709
|
*/
|
|
1693
|
-
function
|
|
1710
|
+
function buildValueFormConfigOverride(userOverride) {
|
|
1711
|
+
const cfg = getConfig();
|
|
1694
1712
|
return {
|
|
1695
|
-
lightLightness: false,
|
|
1696
|
-
darkLightness:
|
|
1713
|
+
lightLightness: userOverride?.lightLightness !== void 0 ? userOverride.lightLightness : false,
|
|
1714
|
+
darkLightness: userOverride?.darkLightness !== void 0 ? userOverride.darkLightness : cfg.darkLightness,
|
|
1715
|
+
darkDesaturation: userOverride?.darkDesaturation ?? cfg.darkDesaturation,
|
|
1716
|
+
darkCurve: userOverride?.darkCurve ?? cfg.darkCurve,
|
|
1717
|
+
autoFlip: userOverride?.autoFlip ?? cfg.autoFlip,
|
|
1718
|
+
shadowTuning: userOverride?.shadowTuning ?? cfg.shadowTuning
|
|
1697
1719
|
};
|
|
1698
1720
|
}
|
|
1699
1721
|
/**
|
|
1700
|
-
*
|
|
1701
|
-
*
|
|
1702
|
-
*
|
|
1722
|
+
* Build the per-token effective config override for a structured-form color.
|
|
1723
|
+
*
|
|
1724
|
+
* Both light and dark windows snapshot from global at create time.
|
|
1725
|
+
* User override fields win.
|
|
1703
1726
|
*/
|
|
1704
|
-
function
|
|
1727
|
+
function buildStructuredConfigOverride(userOverride) {
|
|
1705
1728
|
const cfg = getConfig();
|
|
1706
1729
|
return {
|
|
1707
|
-
lightLightness: cfg.lightLightness,
|
|
1708
|
-
darkLightness: cfg.darkLightness
|
|
1730
|
+
lightLightness: userOverride?.lightLightness !== void 0 ? userOverride.lightLightness : cfg.lightLightness,
|
|
1731
|
+
darkLightness: userOverride?.darkLightness !== void 0 ? userOverride.darkLightness : cfg.darkLightness,
|
|
1732
|
+
darkDesaturation: userOverride?.darkDesaturation ?? cfg.darkDesaturation,
|
|
1733
|
+
darkCurve: userOverride?.darkCurve ?? cfg.darkCurve,
|
|
1734
|
+
autoFlip: userOverride?.autoFlip ?? cfg.autoFlip,
|
|
1735
|
+
shadowTuning: userOverride?.shadowTuning ?? cfg.shadowTuning
|
|
1709
1736
|
};
|
|
1710
1737
|
}
|
|
1711
1738
|
/**
|
|
1712
|
-
*
|
|
1713
|
-
*
|
|
1714
|
-
*
|
|
1739
|
+
* Build the `GlazeConfigResolved` to pass to `resolveAllColors` from a
|
|
1740
|
+
* snapshot override. Uses `defaultConfig()` as the base so all required
|
|
1741
|
+
* fields are present; the snapshot fields win.
|
|
1715
1742
|
*/
|
|
1716
|
-
function
|
|
1717
|
-
return
|
|
1718
|
-
}
|
|
1719
|
-
function isStructuredColorInput(input) {
|
|
1720
|
-
return typeof input === "object" && input !== null && !Array.isArray(input) && "hue" in input && "lightness" in input;
|
|
1743
|
+
function resolvedConfigFromOverride(override) {
|
|
1744
|
+
return mergeConfig(defaultConfig(), override);
|
|
1721
1745
|
}
|
|
1722
1746
|
/**
|
|
1723
1747
|
* Matches the CSS color functions Glaze itself emits (`rgb()`, `hsl()`,
|
|
@@ -1945,9 +1969,7 @@ function extractOkhslFromValue(value) {
|
|
|
1945
1969
|
* Build the `ColorMap` for a value-shorthand `glaze.color()` call.
|
|
1946
1970
|
*
|
|
1947
1971
|
* The user-facing color (`STANDALONE_VALUE`) defaults to `mode: 'auto'`
|
|
1948
|
-
* across every value-shorthand form
|
|
1949
|
-
* `globalConfig.darkLightness` window (light lightness preserved via
|
|
1950
|
-
* `lightLightness: false`).
|
|
1972
|
+
* across every value-shorthand form.
|
|
1951
1973
|
*
|
|
1952
1974
|
* When the user requests `contrast` or relative `lightness`, a hidden
|
|
1953
1975
|
* `STANDALONE_SEED` def is synthesized at `mode: 'static'`. That keeps
|
|
@@ -1988,11 +2010,11 @@ function buildStandaloneValueDefs(main, options) {
|
|
|
1988
2010
|
primary
|
|
1989
2011
|
};
|
|
1990
2012
|
}
|
|
1991
|
-
function createColorTokenFromDefs(seedHue, seedSaturation, defs, primary,
|
|
2013
|
+
function createColorTokenFromDefs(seedHue, seedSaturation, defs, primary, effectiveConfig, baseToken, exportData) {
|
|
1992
2014
|
let cached;
|
|
1993
2015
|
const resolveOnce = () => {
|
|
1994
2016
|
if (cached) return cached;
|
|
1995
|
-
cached = resolveAllColors(seedHue, seedSaturation, defs,
|
|
2017
|
+
cached = resolveAllColors(seedHue, seedSaturation, defs, effectiveConfig, baseToken ? new Map([[STANDALONE_BASE, baseToken.resolve()]]) : void 0);
|
|
1996
2018
|
return cached;
|
|
1997
2019
|
};
|
|
1998
2020
|
const resolveStates = (options) => {
|
|
@@ -2021,17 +2043,43 @@ function createColorTokenFromDefs(seedHue, seedSaturation, defs, primary, effect
|
|
|
2021
2043
|
};
|
|
2022
2044
|
}
|
|
2023
2045
|
/**
|
|
2046
|
+
* When a value/`from` color links to a base that was created via the
|
|
2047
|
+
* structured form (with explicit `hue`/`saturation`/`lightness`), resolve
|
|
2048
|
+
* that base with `lightLightness: false` for the linking math so the
|
|
2049
|
+
* contrast/lightness anchor matches the input lightness — not the
|
|
2050
|
+
* windowed output. The original base token's `.resolve()` is unaffected.
|
|
2051
|
+
*/
|
|
2052
|
+
function toLinkingBase(base) {
|
|
2053
|
+
if (!base) return void 0;
|
|
2054
|
+
const exp = base.export();
|
|
2055
|
+
if (exp.form !== "structured") return base;
|
|
2056
|
+
const linkingConfig = {
|
|
2057
|
+
...exp.config ?? {},
|
|
2058
|
+
lightLightness: false
|
|
2059
|
+
};
|
|
2060
|
+
return colorFromExport({
|
|
2061
|
+
...exp,
|
|
2062
|
+
config: linkingConfig
|
|
2063
|
+
});
|
|
2064
|
+
}
|
|
2065
|
+
/**
|
|
2024
2066
|
* Resolve `base` (which may be a token reference or a raw color value)
|
|
2025
2067
|
* into a `GlazeColorToken`. Raw values are auto-wrapped via
|
|
2026
|
-
* `
|
|
2027
|
-
* an explicit wrap. Returns `undefined` when no base is provided.
|
|
2068
|
+
* `createColorTokenFromValue` so they pick up the same auto-invert
|
|
2069
|
+
* defaults as an explicit wrap. Returns `undefined` when no base is provided.
|
|
2028
2070
|
*/
|
|
2029
2071
|
function resolveBaseToken(base) {
|
|
2030
2072
|
if (base === void 0) return void 0;
|
|
2031
2073
|
if (isGlazeColorToken(base)) return base;
|
|
2032
2074
|
return createColorTokenFromValue(base, void 0, void 0);
|
|
2033
2075
|
}
|
|
2034
|
-
|
|
2076
|
+
/**
|
|
2077
|
+
* Discriminate a `GlazeColorToken` from a raw `GlazeColorValue`.
|
|
2078
|
+
*/
|
|
2079
|
+
function isGlazeColorToken(candidate) {
|
|
2080
|
+
return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate) && "resolve" in candidate && typeof candidate.resolve === "function";
|
|
2081
|
+
}
|
|
2082
|
+
function createColorToken(input, configOverride) {
|
|
2035
2083
|
validateStructuredInput(input);
|
|
2036
2084
|
const userName = input.name;
|
|
2037
2085
|
if (userName !== void 0) validateStandaloneName(userName);
|
|
@@ -2052,30 +2100,28 @@ function createColorToken(input, scaling, overrideAutoFlip) {
|
|
|
2052
2100
|
saturation: 1,
|
|
2053
2101
|
mode: "static"
|
|
2054
2102
|
};
|
|
2055
|
-
const
|
|
2056
|
-
const
|
|
2103
|
+
const effectiveConfigOverride = buildStructuredConfigOverride(configOverride);
|
|
2104
|
+
const effectiveConfig = resolvedConfigFromOverride(effectiveConfigOverride);
|
|
2057
2105
|
const exportData = () => ({
|
|
2058
2106
|
form: "structured",
|
|
2059
2107
|
input: buildStructuredInputExport(input),
|
|
2060
|
-
|
|
2061
|
-
autoFlip
|
|
2108
|
+
config: effectiveConfigOverride
|
|
2062
2109
|
});
|
|
2063
|
-
return createColorTokenFromDefs(input.hue, input.saturation, defs, primary,
|
|
2110
|
+
return createColorTokenFromDefs(input.hue, input.saturation, defs, primary, effectiveConfig, baseToken, exportData);
|
|
2064
2111
|
}
|
|
2065
|
-
function createColorTokenFromValue(value, options,
|
|
2112
|
+
function createColorTokenFromValue(value, options, configOverride) {
|
|
2066
2113
|
const main = extractOkhslFromValue(value);
|
|
2067
|
-
const
|
|
2114
|
+
const linkingBase = toLinkingBase(resolveBaseToken(options?.base));
|
|
2068
2115
|
const { seedHue, seedSaturation, defs, primary } = buildStandaloneValueDefs(main, options);
|
|
2069
|
-
const
|
|
2070
|
-
const
|
|
2116
|
+
const effectiveConfigOverride = buildValueFormConfigOverride(configOverride);
|
|
2117
|
+
const effectiveConfig = resolvedConfigFromOverride(effectiveConfigOverride);
|
|
2071
2118
|
const exportData = () => ({
|
|
2072
2119
|
form: "value",
|
|
2073
2120
|
input: value,
|
|
2074
2121
|
...options !== void 0 ? { overrides: buildOverridesExport(options) } : {},
|
|
2075
|
-
|
|
2076
|
-
autoFlip
|
|
2122
|
+
config: effectiveConfigOverride
|
|
2077
2123
|
});
|
|
2078
|
-
return createColorTokenFromDefs(seedHue, seedSaturation, defs, primary,
|
|
2124
|
+
return createColorTokenFromDefs(seedHue, seedSaturation, defs, primary, effectiveConfig, linkingBase, exportData);
|
|
2079
2125
|
}
|
|
2080
2126
|
/**
|
|
2081
2127
|
* Build a JSON-safe snapshot of `GlazeColorOverrides`. `base` is
|
|
@@ -2111,8 +2157,6 @@ function buildStructuredInputExport(input) {
|
|
|
2111
2157
|
}
|
|
2112
2158
|
/**
|
|
2113
2159
|
* Discriminate a `GlazeColorTokenExport` from a raw `GlazeColorValue`.
|
|
2114
|
-
* `GlazeColorTokenExport` always has a `form` field set to either
|
|
2115
|
-
* `'value'` or `'structured'`; raw values never do.
|
|
2116
2160
|
*/
|
|
2117
2161
|
function isExportedToken(candidate) {
|
|
2118
2162
|
return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate) && "form" in candidate && (candidate.form === "value" || candidate.form === "structured");
|
|
@@ -2147,6 +2191,10 @@ function rehydrateStructuredInput(data) {
|
|
|
2147
2191
|
/**
|
|
2148
2192
|
* Rehydrate a token from its `.export()` snapshot. Recursively rebuilds
|
|
2149
2193
|
* any base dependency. Inverse of `GlazeColorToken.export()`.
|
|
2194
|
+
*
|
|
2195
|
+
* The stored `config` field contains the full effective config override
|
|
2196
|
+
* snapshotted at creation time, so the rehydrated token is deterministic
|
|
2197
|
+
* regardless of subsequent `glaze.configure()` calls.
|
|
2150
2198
|
*/
|
|
2151
2199
|
function colorFromExport(data) {
|
|
2152
2200
|
if (data === null || typeof data !== "object") throw new Error(`glaze.colorFrom: expected an object from token.export(), got ${data === null ? "null" : typeof data}.`);
|
|
@@ -2154,15 +2202,9 @@ function colorFromExport(data) {
|
|
|
2154
2202
|
if (data.input === void 0) throw new Error(`glaze.colorFrom: missing "input" field — expected the original ${data.form === "value" ? "GlazeColorValue" : "GlazeColorInput"}.`);
|
|
2155
2203
|
if (data.form === "value") {
|
|
2156
2204
|
const value = data.input;
|
|
2157
|
-
|
|
2158
|
-
const cfg = getConfig();
|
|
2159
|
-
const effectiveAutoFlip = data.autoFlip ?? cfg.autoFlip;
|
|
2160
|
-
return createColorTokenFromValue(value, overrides, data.scaling, effectiveAutoFlip);
|
|
2205
|
+
return createColorTokenFromValue(value, data.overrides ? rehydrateOverrides(data.overrides) : void 0, data.config);
|
|
2161
2206
|
}
|
|
2162
|
-
|
|
2163
|
-
const cfg = getConfig();
|
|
2164
|
-
const effectiveAutoFlip = data.autoFlip ?? cfg.autoFlip;
|
|
2165
|
-
return createColorToken(input, data.scaling, effectiveAutoFlip);
|
|
2207
|
+
return createColorToken(rehydrateStructuredInput(data.input), data.config);
|
|
2166
2208
|
}
|
|
2167
2209
|
|
|
2168
2210
|
//#endregion
|
|
@@ -2291,21 +2333,32 @@ function createPalette(themes, paletteOptions) {
|
|
|
2291
2333
|
/**
|
|
2292
2334
|
* Theme factory.
|
|
2293
2335
|
*
|
|
2294
|
-
* Wraps a hue/saturation seed
|
|
2295
|
-
*
|
|
2296
|
-
* / `
|
|
2297
|
-
*
|
|
2336
|
+
* Wraps a hue/saturation seed, a mutable `ColorMap`, and an optional
|
|
2337
|
+
* per-theme `GlazeConfigOverride`. Exposes `tokens()` / `tasty()` /
|
|
2338
|
+
* `json()` / `css()` / `resolve()` / `export()` / `extend()`.
|
|
2339
|
+
*
|
|
2340
|
+
* The per-theme config override is **merged over the live global config at
|
|
2341
|
+
* resolve time** so the theme still reacts to later `configure()` calls
|
|
2342
|
+
* for fields it didn't override. The merged config is memoized by
|
|
2343
|
+
* `configVersion` to avoid rebuilding it on every export call.
|
|
2298
2344
|
*/
|
|
2299
|
-
function createTheme(hue, saturation, initialColors) {
|
|
2345
|
+
function createTheme(hue, saturation, initialColors, configOverride) {
|
|
2300
2346
|
let colorDefs = initialColors ? { ...initialColors } : {};
|
|
2301
2347
|
let cache = null;
|
|
2348
|
+
function getEffectiveConfig() {
|
|
2349
|
+
const version = getConfigVersion();
|
|
2350
|
+
if (cache && cache.version === version) return cache.effectiveConfig;
|
|
2351
|
+
return mergeConfig(getConfig(), configOverride);
|
|
2352
|
+
}
|
|
2302
2353
|
function resolveCached() {
|
|
2303
2354
|
const version = getConfigVersion();
|
|
2304
2355
|
if (cache && cache.version === version) return cache.map;
|
|
2305
|
-
const
|
|
2356
|
+
const effectiveConfig = mergeConfig(getConfig(), configOverride);
|
|
2357
|
+
const map = resolveAllColors(hue, saturation, colorDefs, effectiveConfig);
|
|
2306
2358
|
cache = {
|
|
2307
2359
|
map,
|
|
2308
|
-
version
|
|
2360
|
+
version,
|
|
2361
|
+
effectiveConfig
|
|
2309
2362
|
};
|
|
2310
2363
|
return map;
|
|
2311
2364
|
}
|
|
@@ -2347,11 +2400,13 @@ function createTheme(hue, saturation, initialColors) {
|
|
|
2347
2400
|
invalidate();
|
|
2348
2401
|
},
|
|
2349
2402
|
export() {
|
|
2350
|
-
|
|
2403
|
+
const out = {
|
|
2351
2404
|
hue,
|
|
2352
2405
|
saturation,
|
|
2353
2406
|
colors: { ...colorDefs }
|
|
2354
2407
|
};
|
|
2408
|
+
if (configOverride !== void 0) out.config = configOverride;
|
|
2409
|
+
return out;
|
|
2355
2410
|
},
|
|
2356
2411
|
extend(options) {
|
|
2357
2412
|
const newHue = options.hue ?? hue;
|
|
@@ -2361,7 +2416,10 @@ function createTheme(hue, saturation, initialColors) {
|
|
|
2361
2416
|
return createTheme(newHue, newSat, options.colors ? {
|
|
2362
2417
|
...inheritedColors,
|
|
2363
2418
|
...options.colors
|
|
2364
|
-
} : { ...inheritedColors }
|
|
2419
|
+
} : { ...inheritedColors }, configOverride || options.config ? {
|
|
2420
|
+
...configOverride ?? {},
|
|
2421
|
+
...options.config ?? {}
|
|
2422
|
+
} : void 0);
|
|
2365
2423
|
},
|
|
2366
2424
|
resolve() {
|
|
2367
2425
|
return new Map(resolveCached());
|
|
@@ -2371,7 +2429,7 @@ function createTheme(hue, saturation, initialColors) {
|
|
|
2371
2429
|
return buildFlatTokenMap(resolveCached(), "", modes, options?.format);
|
|
2372
2430
|
},
|
|
2373
2431
|
tasty(options) {
|
|
2374
|
-
const cfg =
|
|
2432
|
+
const cfg = getEffectiveConfig();
|
|
2375
2433
|
const states = {
|
|
2376
2434
|
dark: options?.states?.dark ?? cfg.states.dark,
|
|
2377
2435
|
highContrast: options?.states?.highContrast ?? cfg.states.highContrast
|
|
@@ -2406,16 +2464,24 @@ function createTheme(hue, saturation, initialColors) {
|
|
|
2406
2464
|
/**
|
|
2407
2465
|
* Create a single-hue glaze theme.
|
|
2408
2466
|
*
|
|
2467
|
+
* An optional `config` override can be supplied to customize the resolve
|
|
2468
|
+
* behavior for this theme (lightness windows, dark curve, etc.). The
|
|
2469
|
+
* override is **merged over the live global config at resolve time** —
|
|
2470
|
+
* the theme still reacts to later `configure()` calls for fields it
|
|
2471
|
+
* didn't override.
|
|
2472
|
+
*
|
|
2409
2473
|
* @example
|
|
2410
2474
|
* ```ts
|
|
2411
|
-
* const primary = glaze({ hue: 280, saturation: 80 });
|
|
2412
|
-
* // or shorthand:
|
|
2413
2475
|
* const primary = glaze(280, 80);
|
|
2476
|
+
* // or shorthand:
|
|
2477
|
+
* const primary = glaze({ hue: 280, saturation: 80 });
|
|
2478
|
+
* // with config override:
|
|
2479
|
+
* const raw = glaze(280, 80, { lightLightness: false });
|
|
2414
2480
|
* ```
|
|
2415
2481
|
*/
|
|
2416
|
-
function glaze(hueOrOptions, saturation) {
|
|
2417
|
-
if (typeof hueOrOptions === "number") return createTheme(hueOrOptions, saturation ?? 100);
|
|
2418
|
-
return createTheme(hueOrOptions.hue, hueOrOptions.saturation);
|
|
2482
|
+
function glaze(hueOrOptions, saturation, config) {
|
|
2483
|
+
if (typeof hueOrOptions === "number") return createTheme(hueOrOptions, saturation ?? 100, void 0, config);
|
|
2484
|
+
return createTheme(hueOrOptions.hue, hueOrOptions.saturation, void 0, config);
|
|
2419
2485
|
}
|
|
2420
2486
|
/** Configure global glaze settings. */
|
|
2421
2487
|
glaze.configure = function configure$1(config) {
|
|
@@ -2427,45 +2493,59 @@ glaze.palette = function palette(themes, options) {
|
|
|
2427
2493
|
};
|
|
2428
2494
|
/** Create a theme from a serialized export. */
|
|
2429
2495
|
glaze.from = function from(data) {
|
|
2430
|
-
return createTheme(data.hue, data.saturation, data.colors);
|
|
2496
|
+
return createTheme(data.hue, data.saturation, data.colors, data.config);
|
|
2431
2497
|
};
|
|
2432
2498
|
/**
|
|
2433
2499
|
* Create a standalone single-color token.
|
|
2434
2500
|
*
|
|
2435
|
-
*
|
|
2436
|
-
* - `glaze.color(input, scaling?)` — structured form:
|
|
2437
|
-
* `{ hue, saturation, lightness, ... }` plus an optional per-call
|
|
2438
|
-
* lightness-window override.
|
|
2439
|
-
* - `glaze.color(value, overrides?, scaling?)` — value-shorthand: a hex
|
|
2440
|
-
* string (3/6/8 digits), one of the CSS color functions Glaze itself
|
|
2441
|
-
* emits (`rgb()`, `hsl()`, `okhsl()`, `oklch()`), or literal objects
|
|
2442
|
-
* `{ r, g, b }` (0–255), `{ h, s, l }` (OKHSL 0–1), `{ l, c, h }`
|
|
2443
|
-
* (OKLCh, matching `oklch()` strings).
|
|
2501
|
+
* **arg1 — the color** (four accepted shapes, discriminated by structure):
|
|
2444
2502
|
*
|
|
2445
|
-
*
|
|
2446
|
-
*
|
|
2447
|
-
*
|
|
2448
|
-
*
|
|
2449
|
-
*
|
|
2450
|
-
*
|
|
2503
|
+
* | Shape | Example | Notes |
|
|
2504
|
+
* |---|---|---|
|
|
2505
|
+
* | Bare string | `'#26fcb2'`, `'rgb(38 252 178)'` | Hex or CSS color function |
|
|
2506
|
+
* | Value object | `{ h: 152, s: 0.95, l: 0.74 }` | OKHSL, `{r,g,b}`, `{l,c,h}` |
|
|
2507
|
+
* | `{ from, ...overrides }` | `{ from: '#fff', base: bg, contrast: 'AA' }` | Value + color overrides |
|
|
2508
|
+
* | Structured | `{ hue: 152, saturation: 95, lightness: 74 }` | Full theme-style token |
|
|
2451
2509
|
*
|
|
2452
|
-
*
|
|
2453
|
-
*
|
|
2454
|
-
*
|
|
2510
|
+
* **arg2 — config override** (optional, all shapes):
|
|
2511
|
+
* Overrides the resolve-relevant global config fields for this token.
|
|
2512
|
+
* Fields that are omitted fall through to the live global config at
|
|
2513
|
+
* create time (and are snapshotted). Pass `false` for a lightness window
|
|
2514
|
+
* to disable clamping entirely.
|
|
2455
2515
|
*
|
|
2456
|
-
*
|
|
2457
|
-
*
|
|
2458
|
-
*
|
|
2459
|
-
* `GlazeColorToken`) to anchor `contrast` and relative `lightness`
|
|
2460
|
-
* against another color's resolved variant per scheme instead. Relative
|
|
2461
|
-
* `hue: '+N'` always anchors to the seed.
|
|
2516
|
+
* ```ts
|
|
2517
|
+
* // Bare string — no overrides
|
|
2518
|
+
* glaze.color('#26fcb2')
|
|
2462
2519
|
*
|
|
2463
|
-
*
|
|
2464
|
-
*
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2520
|
+
* // From form — value + color overrides
|
|
2521
|
+
* glaze.color({ from: '#fff', base: bg, contrast: 'AA' })
|
|
2522
|
+
*
|
|
2523
|
+
* // Structured form — full theme-style token
|
|
2524
|
+
* glaze.color({ hue: 152, saturation: 95, lightness: 74 })
|
|
2525
|
+
*
|
|
2526
|
+
* // Config override on any form
|
|
2527
|
+
* glaze.color('#26fcb2', { darkLightness: false, autoFlip: false })
|
|
2528
|
+
* glaze.color({ from: '#fff', base: bg }, { darkCurve: 0.3 })
|
|
2529
|
+
* ```
|
|
2530
|
+
*
|
|
2531
|
+
* Defaults: every form defaults to `mode: 'auto'`. Value-shorthand forms
|
|
2532
|
+
* (bare strings and value objects) preserve light lightness exactly
|
|
2533
|
+
* (`lightLightness: false` internally). Structured form snapshots both
|
|
2534
|
+
* lightness windows from `globalConfig` at create time.
|
|
2535
|
+
*
|
|
2536
|
+
* Relative `lightness: '+N'` and `contrast` anchor to the literal seed by
|
|
2537
|
+
* default; when `base` is set they anchor to the base's resolved variant
|
|
2538
|
+
* per scheme. Relative `hue: '+N'` always anchors to the seed, not the base.
|
|
2539
|
+
*/
|
|
2540
|
+
glaze.color = function color(input, config) {
|
|
2541
|
+
if (typeof input === "string") return createColorTokenFromValue(input, void 0, config);
|
|
2542
|
+
const obj = input;
|
|
2543
|
+
if ("from" in obj) {
|
|
2544
|
+
const { from, ...overrides } = input;
|
|
2545
|
+
return createColorTokenFromValue(from, overrides, config);
|
|
2546
|
+
}
|
|
2547
|
+
if ("hue" in obj) return createColorToken(input, config);
|
|
2548
|
+
return createColorTokenFromValue(input, void 0, config);
|
|
2469
2549
|
};
|
|
2470
2550
|
/**
|
|
2471
2551
|
* Compute a shadow color from a bg/fg pair and intensity.
|
|
@@ -2477,7 +2557,8 @@ glaze.color = function color(input, arg2, arg3) {
|
|
|
2477
2557
|
glaze.shadow = function shadow(input) {
|
|
2478
2558
|
const bg = extractOkhslFromValue(input.bg);
|
|
2479
2559
|
const fg = input.fg ? extractOkhslFromValue(input.fg) : void 0;
|
|
2480
|
-
const
|
|
2560
|
+
const cfg = getConfig();
|
|
2561
|
+
const tuning = resolveShadowTuning(input.tuning, cfg.shadowTuning);
|
|
2481
2562
|
return computeShadow({
|
|
2482
2563
|
...bg,
|
|
2483
2564
|
alpha: 1
|
|
@@ -2517,12 +2598,12 @@ glaze.fromRgb = function fromRgb(r, g, b) {
|
|
|
2517
2598
|
*
|
|
2518
2599
|
* The snapshot is a plain JSON-safe object containing the original
|
|
2519
2600
|
* input value, overrides (with any `base` token recursively serialized),
|
|
2520
|
-
* and the
|
|
2521
|
-
* behavior to the original at the time of export.
|
|
2601
|
+
* and the effective config snapshot. The reconstructed token is identical
|
|
2602
|
+
* in behavior to the original at the time of export.
|
|
2522
2603
|
*
|
|
2523
2604
|
* @example
|
|
2524
2605
|
* ```ts
|
|
2525
|
-
* const text = glaze.color('#1a1a1a',
|
|
2606
|
+
* const text = glaze.color({ from: '#1a1a1a', contrast: 'AA' });
|
|
2526
2607
|
* const data = text.export(); // JSON-safe
|
|
2527
2608
|
* localStorage.setItem('text', JSON.stringify(data));
|
|
2528
2609
|
* // ...later...
|