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