@tenphi/glaze 0.0.0-snapshot.d38eee5 → 0.0.0-snapshot.e052c9b
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 +3 -0
- package/dist/index.cjs +599 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +333 -2
- package/dist/index.d.mts +333 -2
- package/dist/index.mjs +595 -52
- package/dist/index.mjs.map +1 -1
- package/docs/api.md +214 -14
- package/docs/methodology.md +437 -222
- package/docs/migration.md +70 -5
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -590,6 +590,24 @@ function formatOkhsl(h, s, l, pastel = false) {
|
|
|
590
590
|
return `okhsl(${fmt$1(h, 2)} ${fmt$1(outS, 2)}% ${fmt$1(l, 2)}%)`;
|
|
591
591
|
}
|
|
592
592
|
/**
|
|
593
|
+
* Format OKHST values as a CSS `okhst(H S% T%)` string.
|
|
594
|
+
* h: 0–360, s: 0–100, t: 0–100 (percentage scale for s and t).
|
|
595
|
+
*
|
|
596
|
+
* Pastel recompute matches `formatOkhsl`: convert via OKLab so external
|
|
597
|
+
* parsers that only understand non-pastel OKHST render identically.
|
|
598
|
+
*/
|
|
599
|
+
function formatOkhst(h, s, t, pastel = false) {
|
|
600
|
+
let outS = s;
|
|
601
|
+
if (pastel) {
|
|
602
|
+
const REF_EPS = .05;
|
|
603
|
+
const den = Math.log(1 + REF_EPS) - Math.log(REF_EPS);
|
|
604
|
+
const y = Math.exp(t / 100 * den + Math.log(REF_EPS)) - REF_EPS;
|
|
605
|
+
const l = toe(Math.cbrt(Math.max(0, y)));
|
|
606
|
+
outS = oklabToOkhsl(okhslToOklab(h, s / 100, l, true), false)[1] * 100;
|
|
607
|
+
}
|
|
608
|
+
return `okhst(${fmt$1(h, 2)} ${fmt$1(outS, 2)}% ${fmt$1(t, 2)}%)`;
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
593
611
|
* Format OKHSL values as a CSS `rgb(R G B)` string.
|
|
594
612
|
* Uses 2 decimal places to avoid 8-bit quantization contrast loss.
|
|
595
613
|
* h: 0–360, s: 0–100, l: 0–100 (percentage scale for s and l).
|
|
@@ -623,12 +641,34 @@ function formatHsl(h, s, l, pastel = false) {
|
|
|
623
641
|
* h: 0–360, s: 0–100, l: 0–100 (percentage scale for s and l).
|
|
624
642
|
*/
|
|
625
643
|
function formatOklch(h, s, l, pastel = false) {
|
|
626
|
-
const [L,
|
|
627
|
-
const C = Math.sqrt(a * a + b * b);
|
|
628
|
-
let hh = Math.atan2(b, a) * (180 / Math.PI);
|
|
629
|
-
hh = constrainAngle(hh);
|
|
644
|
+
const [L, C, hh] = okhslToOklch(h, s / 100, l / 100, pastel);
|
|
630
645
|
return `oklch(${fmt$1(L, 4)} ${fmt$1(C, 4)} ${fmt$1(hh, 2)})`;
|
|
631
646
|
}
|
|
647
|
+
/**
|
|
648
|
+
* Convert gamma-encoded sRGB channels (0–1) to a 6-digit lowercase hex
|
|
649
|
+
* string (`#rrggbb`). Channels are clamped to [0,1] and rounded to 8-bit.
|
|
650
|
+
* Alpha is not encoded here — DTCG carries it as a separate `alpha` field.
|
|
651
|
+
*/
|
|
652
|
+
function srgbToHex(rgb) {
|
|
653
|
+
const toByte = (c) => Math.max(0, Math.min(255, Math.round(c * 255)));
|
|
654
|
+
const r = toByte(rgb[0]);
|
|
655
|
+
const g = toByte(rgb[1]);
|
|
656
|
+
const b = toByte(rgb[2]);
|
|
657
|
+
return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Convert OKHSL (h: 0–360, s: 0–1, l: 0–1) to OKLCH components `[L, C, H]`.
|
|
661
|
+
* L: 0–1, C: 0–~0.4 (chroma), H: 0–360 (hue). Shared by `formatOklch` and
|
|
662
|
+
* the DTCG `oklch` colorSpace exporter so the two never drift apart.
|
|
663
|
+
*/
|
|
664
|
+
function okhslToOklch(h, s, l, pastel = false) {
|
|
665
|
+
const [L, a, b] = okhslToOklab(h, s, l, pastel);
|
|
666
|
+
return [
|
|
667
|
+
L,
|
|
668
|
+
Math.sqrt(a * a + b * b),
|
|
669
|
+
constrainAngle(Math.atan2(b, a) * (180 / Math.PI))
|
|
670
|
+
];
|
|
671
|
+
}
|
|
632
672
|
|
|
633
673
|
//#endregion
|
|
634
674
|
//#region src/config.ts
|
|
@@ -728,6 +768,52 @@ function mergeConfig(base, override) {
|
|
|
728
768
|
};
|
|
729
769
|
}
|
|
730
770
|
|
|
771
|
+
//#endregion
|
|
772
|
+
//#region src/format-guard.ts
|
|
773
|
+
const NON_NATIVE_FORMATS = new Set(["okhsl", "okhst"]);
|
|
774
|
+
/**
|
|
775
|
+
* Throw when a non-native Glaze color space is requested for an export that
|
|
776
|
+
* emits raw CSS or non-Tasty token maps.
|
|
777
|
+
*/
|
|
778
|
+
function assertNativeFormat(format, method) {
|
|
779
|
+
if (format !== void 0 && NON_NATIVE_FORMATS.has(format)) throw new Error(`glaze: ${format} output is only supported by tasty() (not a native CSS color space). Use tasty({ format: '${format}' }) or pick a native format (oklch|hsl|rgb) for ${method}().`);
|
|
780
|
+
}
|
|
781
|
+
const SCHEME_FIELDS = [
|
|
782
|
+
{
|
|
783
|
+
field: "light",
|
|
784
|
+
modes: () => true
|
|
785
|
+
},
|
|
786
|
+
{
|
|
787
|
+
field: "dark",
|
|
788
|
+
modes: (m) => m.dark
|
|
789
|
+
},
|
|
790
|
+
{
|
|
791
|
+
field: "lightContrast",
|
|
792
|
+
modes: (m) => m.highContrast
|
|
793
|
+
},
|
|
794
|
+
{
|
|
795
|
+
field: "darkContrast",
|
|
796
|
+
modes: (m) => m.dark && m.highContrast
|
|
797
|
+
}
|
|
798
|
+
];
|
|
799
|
+
/**
|
|
800
|
+
* Throw when `splitChannels` is enabled but any exported color is not pastel.
|
|
801
|
+
* Hue rotation is only clip-free when chroma is bounded by the hue-independent
|
|
802
|
+
* safe chroma (`computeSafeChromaOKLCH`).
|
|
803
|
+
*/
|
|
804
|
+
function assertAllPastel(resolved, modes) {
|
|
805
|
+
const nonPastel = [];
|
|
806
|
+
for (const [name, color] of resolved) for (const { field, modes: active } of SCHEME_FIELDS) {
|
|
807
|
+
if (!active(modes)) continue;
|
|
808
|
+
if (color[field].pastel !== true) {
|
|
809
|
+
if (!nonPastel.includes(name)) nonPastel.push(name);
|
|
810
|
+
break;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
if (nonPastel.length === 0) return;
|
|
814
|
+
throw new Error(`glaze: splitChannels requires every color to be pastel (hue rotation is only clip-free when chroma is bounded by the hue-independent safe chroma). Non-pastel: ${nonPastel.join(", ")}. Set pastel: true (global or per-color) or drop splitChannels.`);
|
|
815
|
+
}
|
|
816
|
+
|
|
731
817
|
//#endregion
|
|
732
818
|
//#region src/hc-pair.ts
|
|
733
819
|
function pairNormal(p) {
|
|
@@ -2075,17 +2161,121 @@ function resolveAllColors(hue, saturation, defs, config, externalBases) {
|
|
|
2075
2161
|
return result;
|
|
2076
2162
|
}
|
|
2077
2163
|
|
|
2164
|
+
//#endregion
|
|
2165
|
+
//#region src/channels.ts
|
|
2166
|
+
/**
|
|
2167
|
+
* Hue channel planning for `splitChannels` exports.
|
|
2168
|
+
*
|
|
2169
|
+
* Builds per-color hue var references and scheme-independent `--*-hue`
|
|
2170
|
+
* declarations for oklch CSS / Tasty output when every color is pastel.
|
|
2171
|
+
*/
|
|
2172
|
+
const ACHROMATIC_EPSILON = 1e-6;
|
|
2173
|
+
function cssProp(prefix, name, suffix) {
|
|
2174
|
+
return `--${prefix}${name}${suffix}`;
|
|
2175
|
+
}
|
|
2176
|
+
function isAchromatic(v) {
|
|
2177
|
+
return v.s <= ACHROMATIC_EPSILON;
|
|
2178
|
+
}
|
|
2179
|
+
function themeHuePlan(name, def, variant, ctx) {
|
|
2180
|
+
if (def === void 0 || isShadowDef(def) || isMixDef(def) || isAchromatic(variant)) return {
|
|
2181
|
+
hueVar: "",
|
|
2182
|
+
inline: true,
|
|
2183
|
+
declarations: []
|
|
2184
|
+
};
|
|
2185
|
+
const regDef = def;
|
|
2186
|
+
const baseHueVar = `var(--${ctx.baseName}-hue)`;
|
|
2187
|
+
if (regDef.hue === void 0) return {
|
|
2188
|
+
hueVar: baseHueVar,
|
|
2189
|
+
inline: false,
|
|
2190
|
+
declarations: []
|
|
2191
|
+
};
|
|
2192
|
+
const parsed = parseRelativeOrAbsolute(regDef.hue);
|
|
2193
|
+
const prop = cssProp(ctx.prefix, name, "-hue");
|
|
2194
|
+
if (parsed.relative) {
|
|
2195
|
+
const sign = parsed.value >= 0 ? "+" : "-";
|
|
2196
|
+
const magnitude = Math.abs(parsed.value);
|
|
2197
|
+
const value = `calc(var(--${ctx.baseName}-hue) ${sign} ${magnitude})`;
|
|
2198
|
+
return {
|
|
2199
|
+
hueVar: `var(${prop})`,
|
|
2200
|
+
inline: false,
|
|
2201
|
+
declarations: [{
|
|
2202
|
+
prop,
|
|
2203
|
+
value
|
|
2204
|
+
}]
|
|
2205
|
+
};
|
|
2206
|
+
}
|
|
2207
|
+
const absHue = (parsed.value % 360 + 360) % 360;
|
|
2208
|
+
return {
|
|
2209
|
+
hueVar: `var(${prop})`,
|
|
2210
|
+
inline: false,
|
|
2211
|
+
declarations: [{
|
|
2212
|
+
prop,
|
|
2213
|
+
value: String(absHue)
|
|
2214
|
+
}]
|
|
2215
|
+
};
|
|
2216
|
+
}
|
|
2217
|
+
function standaloneHuePlan(name, variant, ctx) {
|
|
2218
|
+
if (isAchromatic(variant)) return {
|
|
2219
|
+
hueVar: "",
|
|
2220
|
+
inline: true,
|
|
2221
|
+
declarations: []
|
|
2222
|
+
};
|
|
2223
|
+
const hue = ctx.resolvedHue ?? variant.h;
|
|
2224
|
+
const prop = cssProp(ctx.prefix, name, "-hue");
|
|
2225
|
+
return {
|
|
2226
|
+
hueVar: `var(${prop})`,
|
|
2227
|
+
inline: false,
|
|
2228
|
+
declarations: [{
|
|
2229
|
+
prop,
|
|
2230
|
+
value: String(hue)
|
|
2231
|
+
}]
|
|
2232
|
+
};
|
|
2233
|
+
}
|
|
2234
|
+
function buildHuePlan(name, def, variant, ctx) {
|
|
2235
|
+
if (ctx.mode === "standalone") return standaloneHuePlan(name, variant, ctx);
|
|
2236
|
+
return themeHuePlan(name, def, variant, ctx);
|
|
2237
|
+
}
|
|
2238
|
+
/** Collect unique hue declarations across all colors (theme + per-color). */
|
|
2239
|
+
function collectHueDeclarations(resolved, ctx) {
|
|
2240
|
+
if (ctx.emitDeclarations === false) return [];
|
|
2241
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2242
|
+
const out = [];
|
|
2243
|
+
const push = (decl) => {
|
|
2244
|
+
if (seen.has(decl.prop)) return;
|
|
2245
|
+
seen.add(decl.prop);
|
|
2246
|
+
out.push(decl);
|
|
2247
|
+
};
|
|
2248
|
+
if (ctx.mode === "theme") push({
|
|
2249
|
+
prop: `--${ctx.baseName}-hue`,
|
|
2250
|
+
value: String(ctx.seedHue)
|
|
2251
|
+
});
|
|
2252
|
+
for (const [name, color] of resolved) {
|
|
2253
|
+
const def = ctx.defs[name];
|
|
2254
|
+
const plan = buildHuePlan(name, def, color.light, ctx);
|
|
2255
|
+
for (const decl of plan.declarations) push(decl);
|
|
2256
|
+
}
|
|
2257
|
+
return out;
|
|
2258
|
+
}
|
|
2259
|
+
function buildHuePlans(resolved, ctx) {
|
|
2260
|
+
const plans = /* @__PURE__ */ new Map();
|
|
2261
|
+
for (const [name, color] of resolved) plans.set(name, buildHuePlan(name, ctx.defs[name], color.light, ctx));
|
|
2262
|
+
return plans;
|
|
2263
|
+
}
|
|
2264
|
+
|
|
2078
2265
|
//#endregion
|
|
2079
2266
|
//#region src/formatters.ts
|
|
2080
2267
|
/**
|
|
2081
2268
|
* Output formatting for resolved color maps.
|
|
2082
2269
|
*
|
|
2083
2270
|
* Owns the CSS-string formatter dispatch table (`okhsl` / `rgb` / `hsl` /
|
|
2084
|
-
* `oklch`) and the
|
|
2271
|
+
* `oklch`) and the token-map shapes Glaze emits:
|
|
2085
2272
|
* - `buildTokenMap` — Tasty style-to-state bindings (`#name` keys, state aliases).
|
|
2086
2273
|
* - `buildFlatTokenMap` — `{ light, dark, ... }` per-variant maps.
|
|
2087
2274
|
* - `buildJsonMap` — `{ name: { light, dark, ... } }` per-color JSON.
|
|
2088
2275
|
* - `buildCssMap` — CSS custom property declaration strings per variant.
|
|
2276
|
+
* - `buildDtcgMap` — W3C DTCG (2025.10) token documents, one per scheme.
|
|
2277
|
+
* - `buildDtcgResolver` — W3C DTCG Resolver-Module document (one modifier, a context per scheme).
|
|
2278
|
+
* - `buildTailwindMap` — Tailwind v4 `@theme` block + dark/HC overrides.
|
|
2089
2279
|
*/
|
|
2090
2280
|
const formatters = {
|
|
2091
2281
|
okhsl: formatOkhsl,
|
|
@@ -2097,13 +2287,37 @@ function fmt(value, decimals) {
|
|
|
2097
2287
|
return parseFloat(value.toFixed(decimals)).toString();
|
|
2098
2288
|
}
|
|
2099
2289
|
function formatVariant(v, format = "okhsl", pastel = false) {
|
|
2290
|
+
const effectivePastel = v.pastel ?? pastel;
|
|
2291
|
+
let base;
|
|
2292
|
+
if (format === "okhst") base = formatOkhst(v.h, v.s * 100, v.t * 100, effectivePastel);
|
|
2293
|
+
else {
|
|
2294
|
+
const { l } = variantToOkhsl(v);
|
|
2295
|
+
base = formatters[format](v.h, v.s * 100, l * 100, effectivePastel);
|
|
2296
|
+
}
|
|
2297
|
+
if (v.alpha >= 1) return base;
|
|
2298
|
+
const closing = base.lastIndexOf(")");
|
|
2299
|
+
return `${base.slice(0, closing)} / ${fmt(v.alpha, 4)})`;
|
|
2300
|
+
}
|
|
2301
|
+
/**
|
|
2302
|
+
* Format a resolved variant as `oklch(L C <hueVar>)`, splicing a CSS hue var
|
|
2303
|
+
* for `splitChannels` exports. Falls back to inline when the plan is inline.
|
|
2304
|
+
*/
|
|
2305
|
+
function formatVariantHue(v, plan, pastel = false) {
|
|
2100
2306
|
const effectivePastel = v.pastel ?? pastel;
|
|
2101
2307
|
const { l } = variantToOkhsl(v);
|
|
2102
|
-
const
|
|
2308
|
+
const [L, C] = okhslToOklch(v.h, v.s, l, effectivePastel);
|
|
2309
|
+
let base;
|
|
2310
|
+
if (plan.inline) if (v.s <= 1e-6) base = `oklch(${fmt(L, 4)} 0 0)`;
|
|
2311
|
+
else base = formatOklch(v.h, v.s * 100, l * 100, effectivePastel);
|
|
2312
|
+
else base = `oklch(${fmt(L, 4)} ${fmt(C, 4)} ${plan.hueVar})`;
|
|
2103
2313
|
if (v.alpha >= 1) return base;
|
|
2104
2314
|
const closing = base.lastIndexOf(")");
|
|
2105
2315
|
return `${base.slice(0, closing)} / ${fmt(v.alpha, 4)})`;
|
|
2106
2316
|
}
|
|
2317
|
+
function formatColorValue(v, format, pastel, huePlan) {
|
|
2318
|
+
if (format === "oklch" && huePlan !== void 0) return formatVariantHue(v, huePlan, pastel);
|
|
2319
|
+
return formatVariant(v, format, pastel);
|
|
2320
|
+
}
|
|
2107
2321
|
function resolveModes(override) {
|
|
2108
2322
|
const cfg = getConfig();
|
|
2109
2323
|
return {
|
|
@@ -2111,18 +2325,36 @@ function resolveModes(override) {
|
|
|
2111
2325
|
highContrast: override?.highContrast ?? cfg.modes.highContrast
|
|
2112
2326
|
};
|
|
2113
2327
|
}
|
|
2114
|
-
function buildTokenMap(resolved, prefix, states, modes, format = "okhsl", pastel = false) {
|
|
2328
|
+
function buildTokenMap(resolved, prefix, states, modes, format = "okhsl", pastel = false, channelCtx) {
|
|
2115
2329
|
const tokens = {};
|
|
2330
|
+
const huePlans = channelCtx !== void 0 && format === "oklch" ? buildHuePlans(resolved, channelCtx) : void 0;
|
|
2331
|
+
if (huePlans !== void 0 && channelCtx !== void 0) {
|
|
2332
|
+
const emitDecls = channelCtx.emitDeclarations !== false;
|
|
2333
|
+
if (emitDecls && channelCtx.mode === "theme") tokens[`#${channelCtx.baseName}-hue`] = { "": String(channelCtx.seedHue) };
|
|
2334
|
+
for (const [name, color] of resolved) {
|
|
2335
|
+
const plan = huePlans.get(name);
|
|
2336
|
+
if (emitDecls) for (const decl of plan.declarations) {
|
|
2337
|
+
const key = `#${decl.prop.slice(2)}`;
|
|
2338
|
+
if (!(key in tokens)) tokens[key] = { "": decl.value };
|
|
2339
|
+
}
|
|
2340
|
+
const colorKey = `#${prefix}${name}`;
|
|
2341
|
+
tokens[colorKey] = buildTokenEntry(color, states, modes, format, pastel, huePlans.get(name));
|
|
2342
|
+
}
|
|
2343
|
+
return tokens;
|
|
2344
|
+
}
|
|
2116
2345
|
for (const [name, color] of resolved) {
|
|
2117
2346
|
const key = `#${prefix}${name}`;
|
|
2118
|
-
|
|
2119
|
-
if (modes.dark) entry[states.dark] = formatVariant(color.dark, format, pastel);
|
|
2120
|
-
if (modes.highContrast) entry[states.highContrast] = formatVariant(color.lightContrast, format, pastel);
|
|
2121
|
-
if (modes.dark && modes.highContrast) entry[`${states.dark} & ${states.highContrast}`] = formatVariant(color.darkContrast, format, pastel);
|
|
2122
|
-
tokens[key] = entry;
|
|
2347
|
+
tokens[key] = buildTokenEntry(color, states, modes, format, pastel);
|
|
2123
2348
|
}
|
|
2124
2349
|
return tokens;
|
|
2125
2350
|
}
|
|
2351
|
+
function buildTokenEntry(color, states, modes, format, pastel, huePlan) {
|
|
2352
|
+
const entry = { "": formatColorValue(color.light, format, pastel, huePlan) };
|
|
2353
|
+
if (modes.dark) entry[states.dark] = formatColorValue(color.dark, format, pastel, huePlan);
|
|
2354
|
+
if (modes.highContrast) entry[states.highContrast] = formatColorValue(color.lightContrast, format, pastel, huePlan);
|
|
2355
|
+
if (modes.dark && modes.highContrast) entry[`${states.dark} & ${states.highContrast}`] = formatColorValue(color.darkContrast, format, pastel, huePlan);
|
|
2356
|
+
return entry;
|
|
2357
|
+
}
|
|
2126
2358
|
function buildFlatTokenMap(resolved, prefix, modes, format = "okhsl", pastel = false) {
|
|
2127
2359
|
const result = { light: {} };
|
|
2128
2360
|
if (modes.dark) result.dark = {};
|
|
@@ -2148,19 +2380,22 @@ function buildJsonMap(resolved, modes, format = "okhsl", pastel = false) {
|
|
|
2148
2380
|
}
|
|
2149
2381
|
return result;
|
|
2150
2382
|
}
|
|
2151
|
-
function buildCssMap(resolved, prefix, suffix, format, pastel = false) {
|
|
2383
|
+
function buildCssMap(resolved, prefix, suffix, format, pastel = false, channelCtx) {
|
|
2152
2384
|
const lines = {
|
|
2153
2385
|
light: [],
|
|
2154
2386
|
dark: [],
|
|
2155
2387
|
lightContrast: [],
|
|
2156
2388
|
darkContrast: []
|
|
2157
2389
|
};
|
|
2390
|
+
const huePlans = channelCtx !== void 0 && format === "oklch" ? buildHuePlans(resolved, channelCtx) : void 0;
|
|
2391
|
+
if (huePlans !== void 0 && channelCtx !== void 0) for (const decl of collectHueDeclarations(resolved, channelCtx)) lines.light.push(`${decl.prop}: ${decl.value};`);
|
|
2158
2392
|
for (const [name, color] of resolved) {
|
|
2159
2393
|
const prop = `--${prefix}${name}${suffix}`;
|
|
2160
|
-
|
|
2161
|
-
lines.
|
|
2162
|
-
lines.
|
|
2163
|
-
lines.
|
|
2394
|
+
const plan = huePlans?.get(name);
|
|
2395
|
+
lines.light.push(`${prop}: ${formatColorValue(color.light, format, pastel, plan)};`);
|
|
2396
|
+
lines.dark.push(`${prop}: ${formatColorValue(color.dark, format, pastel, plan)};`);
|
|
2397
|
+
lines.lightContrast.push(`${prop}: ${formatColorValue(color.lightContrast, format, pastel, plan)};`);
|
|
2398
|
+
lines.darkContrast.push(`${prop}: ${formatColorValue(color.darkContrast, format, pastel, plan)};`);
|
|
2164
2399
|
}
|
|
2165
2400
|
return {
|
|
2166
2401
|
light: lines.light.join("\n"),
|
|
@@ -2169,6 +2404,193 @@ function buildCssMap(resolved, prefix, suffix, format, pastel = false) {
|
|
|
2169
2404
|
darkContrast: lines.darkContrast.join("\n")
|
|
2170
2405
|
};
|
|
2171
2406
|
}
|
|
2407
|
+
function roundTo(value, decimals) {
|
|
2408
|
+
return parseFloat(value.toFixed(decimals));
|
|
2409
|
+
}
|
|
2410
|
+
/**
|
|
2411
|
+
* Build a DTCG `$value` color object for a resolved variant.
|
|
2412
|
+
*
|
|
2413
|
+
* `srgb` (default) emits gamma sRGB components in 0–1 plus a 6-digit `hex`
|
|
2414
|
+
* hint — the most universally understood form (Figma, Tokens Studio, Style
|
|
2415
|
+
* Dictionary). `oklch` emits `[L, C, H]` components with no hex — Glaze-native
|
|
2416
|
+
* and wide-gamut. `alpha` is included only when below 1.
|
|
2417
|
+
*/
|
|
2418
|
+
function dtcgColorValue(v, colorSpace = "srgb", pastel = false) {
|
|
2419
|
+
const effectivePastel = v.pastel ?? pastel;
|
|
2420
|
+
const { l } = variantToOkhsl(v);
|
|
2421
|
+
const alpha = v.alpha < 1 ? roundTo(v.alpha, 6) : void 0;
|
|
2422
|
+
if (colorSpace === "oklch") {
|
|
2423
|
+
const [L, C, H] = okhslToOklch(v.h, v.s, l, effectivePastel);
|
|
2424
|
+
const value = {
|
|
2425
|
+
colorSpace: "oklch",
|
|
2426
|
+
components: [
|
|
2427
|
+
roundTo(L, 6),
|
|
2428
|
+
roundTo(C, 6),
|
|
2429
|
+
roundTo(H, 4)
|
|
2430
|
+
]
|
|
2431
|
+
};
|
|
2432
|
+
if (alpha !== void 0) value.alpha = alpha;
|
|
2433
|
+
return value;
|
|
2434
|
+
}
|
|
2435
|
+
const [r, g, b] = okhslToSrgb(v.h, v.s, l, effectivePastel);
|
|
2436
|
+
const value = {
|
|
2437
|
+
colorSpace: "srgb",
|
|
2438
|
+
components: [
|
|
2439
|
+
roundTo(r, 6),
|
|
2440
|
+
roundTo(g, 6),
|
|
2441
|
+
roundTo(b, 6)
|
|
2442
|
+
],
|
|
2443
|
+
hex: srgbToHex([
|
|
2444
|
+
r,
|
|
2445
|
+
g,
|
|
2446
|
+
b
|
|
2447
|
+
])
|
|
2448
|
+
};
|
|
2449
|
+
if (alpha !== void 0) value.alpha = alpha;
|
|
2450
|
+
return value;
|
|
2451
|
+
}
|
|
2452
|
+
function dtcgToken(v, colorSpace, pastel) {
|
|
2453
|
+
return {
|
|
2454
|
+
$type: "color",
|
|
2455
|
+
$value: dtcgColorValue(v, colorSpace, pastel)
|
|
2456
|
+
};
|
|
2457
|
+
}
|
|
2458
|
+
/**
|
|
2459
|
+
* Build a `GlazeDtcgResult`: one spec-conformant DTCG token document per
|
|
2460
|
+
* scheme variant, gated by `modes`. Light is always present.
|
|
2461
|
+
*/
|
|
2462
|
+
function buildDtcgMap(resolved, prefix, modes, colorSpace = "srgb", pastel = false) {
|
|
2463
|
+
const light = {};
|
|
2464
|
+
const dark = modes.dark ? {} : void 0;
|
|
2465
|
+
const lightContrast = modes.highContrast ? {} : void 0;
|
|
2466
|
+
const darkContrast = modes.dark && modes.highContrast ? {} : void 0;
|
|
2467
|
+
for (const [name, color] of resolved) {
|
|
2468
|
+
const key = `${prefix}${name}`;
|
|
2469
|
+
light[key] = dtcgToken(color.light, colorSpace, pastel);
|
|
2470
|
+
if (dark) dark[key] = dtcgToken(color.dark, colorSpace, pastel);
|
|
2471
|
+
if (lightContrast) lightContrast[key] = dtcgToken(color.lightContrast, colorSpace, pastel);
|
|
2472
|
+
if (darkContrast) darkContrast[key] = dtcgToken(color.darkContrast, colorSpace, pastel);
|
|
2473
|
+
}
|
|
2474
|
+
return {
|
|
2475
|
+
light,
|
|
2476
|
+
dark,
|
|
2477
|
+
lightContrast,
|
|
2478
|
+
darkContrast
|
|
2479
|
+
};
|
|
2480
|
+
}
|
|
2481
|
+
/**
|
|
2482
|
+
* Default context names emitted on the `scheme` modifier — the Glaze variant
|
|
2483
|
+
* keys, so the resolver document mirrors `GlazeDtcgResult` exactly.
|
|
2484
|
+
*/
|
|
2485
|
+
const DEFAULT_DTCG_CONTEXT_NAMES = {
|
|
2486
|
+
light: "light",
|
|
2487
|
+
dark: "dark",
|
|
2488
|
+
lightContrast: "lightContrast",
|
|
2489
|
+
darkContrast: "darkContrast"
|
|
2490
|
+
};
|
|
2491
|
+
/**
|
|
2492
|
+
* Wrap a per-scheme `GlazeDtcgResult` into a single W3C DTCG Resolver-Module
|
|
2493
|
+
* document. The light document becomes `sets[setName].sources[0]` (the default
|
|
2494
|
+
* context); each other present variant becomes a `contexts[ctx]` override
|
|
2495
|
+
* array on a single `modifiers[modifierName]`. Absent variants (per the
|
|
2496
|
+
* `modes` already applied to `result`) are omitted — light is always present
|
|
2497
|
+
* and is the modifier `default`. Only the resolver-specific options are read;
|
|
2498
|
+
* `modes` / `colorSpace` were already consumed by the `buildDtcgMap` call that
|
|
2499
|
+
* produced `result`.
|
|
2500
|
+
*/
|
|
2501
|
+
function buildDtcgResolver(result, options) {
|
|
2502
|
+
const setName = options?.setName ?? "base";
|
|
2503
|
+
const modifierName = options?.modifierName ?? "scheme";
|
|
2504
|
+
const ctx = {
|
|
2505
|
+
...DEFAULT_DTCG_CONTEXT_NAMES,
|
|
2506
|
+
...options?.contextNames
|
|
2507
|
+
};
|
|
2508
|
+
const contexts = { [ctx.light]: [] };
|
|
2509
|
+
if (result.dark) contexts[ctx.dark] = [result.dark];
|
|
2510
|
+
if (result.lightContrast) contexts[ctx.lightContrast] = [result.lightContrast];
|
|
2511
|
+
if (result.darkContrast) contexts[ctx.darkContrast] = [result.darkContrast];
|
|
2512
|
+
return {
|
|
2513
|
+
version: options?.version ?? "2025.10",
|
|
2514
|
+
sets: { [setName]: { sources: [result.light] } },
|
|
2515
|
+
modifiers: { [modifierName]: {
|
|
2516
|
+
default: ctx.light,
|
|
2517
|
+
contexts
|
|
2518
|
+
} },
|
|
2519
|
+
resolutionOrder: [{ $ref: `#/sets/${setName}` }, { $ref: `#/modifiers/${modifierName}` }]
|
|
2520
|
+
};
|
|
2521
|
+
}
|
|
2522
|
+
function tailwindLinesFor(resolved, themePrefix, cssPrefix, format, pastel) {
|
|
2523
|
+
const lines = {
|
|
2524
|
+
light: [],
|
|
2525
|
+
dark: [],
|
|
2526
|
+
lightContrast: [],
|
|
2527
|
+
darkContrast: []
|
|
2528
|
+
};
|
|
2529
|
+
for (const [name, color] of resolved) {
|
|
2530
|
+
const prop = `--${cssPrefix}${themePrefix}${name}`;
|
|
2531
|
+
lines.light.push(`${prop}: ${formatVariant(color.light, format, pastel)};`);
|
|
2532
|
+
lines.dark.push(`${prop}: ${formatVariant(color.dark, format, pastel)};`);
|
|
2533
|
+
lines.lightContrast.push(`${prop}: ${formatVariant(color.lightContrast, format, pastel)};`);
|
|
2534
|
+
lines.darkContrast.push(`${prop}: ${formatVariant(color.darkContrast, format, pastel)};`);
|
|
2535
|
+
}
|
|
2536
|
+
return lines;
|
|
2537
|
+
}
|
|
2538
|
+
function indentBlock(text, pad) {
|
|
2539
|
+
return text.split("\n").map((line) => line.length === 0 ? line : pad + line).join("\n");
|
|
2540
|
+
}
|
|
2541
|
+
function emitRule(selector, body) {
|
|
2542
|
+
return `${selector} {\n${indentBlock(body, " ")}\n}`;
|
|
2543
|
+
}
|
|
2544
|
+
/**
|
|
2545
|
+
* Emit a CSS block for a set of declarations scoped by one or more selectors
|
|
2546
|
+
* / at-rules. Class-like selectors concatenate (`.dark.high-contrast`);
|
|
2547
|
+
* at-rules (`@media …`) nest `:root` (or the chained selector) inside.
|
|
2548
|
+
*/
|
|
2549
|
+
function emitScoped(scopes, declarations) {
|
|
2550
|
+
if (declarations.length === 0) return void 0;
|
|
2551
|
+
const atRules = [];
|
|
2552
|
+
let selectorChain = "";
|
|
2553
|
+
for (const scope of scopes) if (scope.startsWith("@")) atRules.push(scope);
|
|
2554
|
+
else selectorChain += scope;
|
|
2555
|
+
let css = emitRule(selectorChain || ":root", declarations.join("\n"));
|
|
2556
|
+
for (const rule of atRules) css = emitRule(rule, css);
|
|
2557
|
+
return css;
|
|
2558
|
+
}
|
|
2559
|
+
/**
|
|
2560
|
+
* Render accumulated per-scheme declaration lines as a Tailwind v4 CSS string:
|
|
2561
|
+
* an `@theme` block (light baseline) plus dark / high-contrast overrides under
|
|
2562
|
+
* the configured selectors. Empty blocks are skipped.
|
|
2563
|
+
*/
|
|
2564
|
+
function emitTailwindCss(lines, modes, darkSelector, highContrastSelector) {
|
|
2565
|
+
const blocks = [];
|
|
2566
|
+
if (lines.light.length > 0) blocks.push(emitRule("@theme", lines.light.join("\n")));
|
|
2567
|
+
if (modes.dark) {
|
|
2568
|
+
const dark = emitScoped([darkSelector], lines.dark);
|
|
2569
|
+
if (dark) blocks.push(dark);
|
|
2570
|
+
}
|
|
2571
|
+
if (modes.highContrast) {
|
|
2572
|
+
const hc = emitScoped([highContrastSelector], lines.lightContrast);
|
|
2573
|
+
if (hc) blocks.push(hc);
|
|
2574
|
+
}
|
|
2575
|
+
if (modes.dark && modes.highContrast) {
|
|
2576
|
+
const dhc = emitScoped([darkSelector, highContrastSelector], lines.darkContrast);
|
|
2577
|
+
if (dhc) blocks.push(dhc);
|
|
2578
|
+
}
|
|
2579
|
+
return blocks.join("\n\n");
|
|
2580
|
+
}
|
|
2581
|
+
/**
|
|
2582
|
+
* Build per-scheme declaration lines for a single theme (used by
|
|
2583
|
+
* `theme.tailwind()` and as the palette `buildOne` step).
|
|
2584
|
+
*/
|
|
2585
|
+
function buildTailwindLines(resolved, themePrefix, cssPrefix, format, pastel) {
|
|
2586
|
+
return tailwindLinesFor(resolved, themePrefix, cssPrefix, format, pastel);
|
|
2587
|
+
}
|
|
2588
|
+
/**
|
|
2589
|
+
* Build a complete Tailwind v4 CSS string for a single theme.
|
|
2590
|
+
*/
|
|
2591
|
+
function buildTailwindMap(resolved, themePrefix, cssPrefix, modes, format, darkSelector, highContrastSelector, pastel = false) {
|
|
2592
|
+
return emitTailwindCss(tailwindLinesFor(resolved, themePrefix, cssPrefix, format, pastel), modes, darkSelector, highContrastSelector);
|
|
2593
|
+
}
|
|
2172
2594
|
|
|
2173
2595
|
//#endregion
|
|
2174
2596
|
//#region src/color-token.ts
|
|
@@ -2554,10 +2976,51 @@ function createColorTokenFromDefs(seedHue, seedSaturation, defs, primary, effect
|
|
|
2554
2976
|
token: tokenLike,
|
|
2555
2977
|
tasty: tokenLike,
|
|
2556
2978
|
json(options) {
|
|
2557
|
-
|
|
2979
|
+
const format = options?.format ?? "oklch";
|
|
2980
|
+
assertNativeFormat(format, "json");
|
|
2981
|
+
return buildJsonMap(resolveOnce(), resolveModes(options?.modes), format, effectiveConfig.pastel)[primary];
|
|
2558
2982
|
},
|
|
2559
2983
|
css(options) {
|
|
2560
|
-
|
|
2984
|
+
const format = options.format ?? "rgb";
|
|
2985
|
+
assertNativeFormat(format, "css");
|
|
2986
|
+
const resolved = resolveOnce().get(primary);
|
|
2987
|
+
const renamed = new Map([[options.name, resolved]]);
|
|
2988
|
+
let channelCtx;
|
|
2989
|
+
if (options.splitChannels && format === "oklch") {
|
|
2990
|
+
assertAllPastel(renamed, resolveModes());
|
|
2991
|
+
channelCtx = {
|
|
2992
|
+
seedHue,
|
|
2993
|
+
baseName: options.name,
|
|
2994
|
+
prefix: "",
|
|
2995
|
+
defs: { [options.name]: defs[primary] },
|
|
2996
|
+
mode: "standalone",
|
|
2997
|
+
resolvedHue: resolved.light.h
|
|
2998
|
+
};
|
|
2999
|
+
}
|
|
3000
|
+
return buildCssMap(renamed, "", options.suffix ?? "-color", format, effectiveConfig.pastel, channelCtx);
|
|
3001
|
+
},
|
|
3002
|
+
dtcg(options) {
|
|
3003
|
+
const modes = resolveModes(options?.modes);
|
|
3004
|
+
const doc = buildDtcgMap(resolveOnce(), "", modes, options?.colorSpace ?? "srgb", effectiveConfig.pastel);
|
|
3005
|
+
const result = { light: doc.light[primary] };
|
|
3006
|
+
if (doc.dark) result.dark = doc.dark[primary];
|
|
3007
|
+
if (doc.lightContrast) result.lightContrast = doc.lightContrast[primary];
|
|
3008
|
+
if (doc.darkContrast) result.darkContrast = doc.darkContrast[primary];
|
|
3009
|
+
return result;
|
|
3010
|
+
},
|
|
3011
|
+
dtcgResolver(options) {
|
|
3012
|
+
const doc = buildDtcgMap(resolveOnce(), "", resolveModes(options?.modes), options?.colorSpace ?? "srgb", effectiveConfig.pastel);
|
|
3013
|
+
const name = options.name;
|
|
3014
|
+
const result = { light: { [name]: doc.light[primary] } };
|
|
3015
|
+
if (doc.dark) result.dark = { [name]: doc.dark[primary] };
|
|
3016
|
+
if (doc.lightContrast) result.lightContrast = { [name]: doc.lightContrast[primary] };
|
|
3017
|
+
if (doc.darkContrast) result.darkContrast = { [name]: doc.darkContrast[primary] };
|
|
3018
|
+
return buildDtcgResolver(result, options);
|
|
3019
|
+
},
|
|
3020
|
+
tailwind(options) {
|
|
3021
|
+
const format = options.format ?? "oklch";
|
|
3022
|
+
assertNativeFormat(format, "tailwind");
|
|
3023
|
+
return buildTailwindMap(new Map([[options.name, resolveOnce().get(primary)]]), "", options.namespace ?? "color-", resolveModes(options?.modes), format, options.darkSelector ?? ".dark", options.highContrastSelector ?? ".high-contrast", effectiveConfig.pastel);
|
|
2561
3024
|
},
|
|
2562
3025
|
export: exportData
|
|
2563
3026
|
};
|
|
@@ -2747,16 +3210,6 @@ function colorFromExport(data) {
|
|
|
2747
3210
|
|
|
2748
3211
|
//#endregion
|
|
2749
3212
|
//#region src/palette.ts
|
|
2750
|
-
/**
|
|
2751
|
-
* Palette factory.
|
|
2752
|
-
*
|
|
2753
|
-
* Composes multiple themes into a single token namespace with optional
|
|
2754
|
-
* theme-name prefixes and a "primary theme" that also surfaces an
|
|
2755
|
-
* unprefixed copy of its tokens. All four export methods (`tokens` /
|
|
2756
|
-
* `tasty` / `json` / `css`) share a `buildPaletteOutput` driver that
|
|
2757
|
-
* handles validation, per-theme iteration, prefix resolution, collision
|
|
2758
|
-
* filtering, and primary duplication.
|
|
2759
|
-
*/
|
|
2760
3213
|
function resolvePrefix(options, themeName, defaultPrefix = false) {
|
|
2761
3214
|
const prefix = options?.prefix ?? defaultPrefix;
|
|
2762
3215
|
if (prefix === true) return `${themeName}-`;
|
|
@@ -2796,6 +3249,26 @@ function filterCollisions(resolved, prefix, seen, themeName, isPrimary) {
|
|
|
2796
3249
|
}
|
|
2797
3250
|
return filtered;
|
|
2798
3251
|
}
|
|
3252
|
+
function colorMapFromTheme(theme) {
|
|
3253
|
+
const defs = {};
|
|
3254
|
+
for (const name of theme.list()) {
|
|
3255
|
+
const def = theme.color(name);
|
|
3256
|
+
if (def !== void 0) defs[name] = def;
|
|
3257
|
+
}
|
|
3258
|
+
return defs;
|
|
3259
|
+
}
|
|
3260
|
+
function channelCtxForTheme(theme, themeName, passPrefix, themedPrefix, splitChannels, format, modes, filtered) {
|
|
3261
|
+
if (!splitChannels || format !== "oklch") return void 0;
|
|
3262
|
+
assertAllPastel(filtered, modes);
|
|
3263
|
+
return {
|
|
3264
|
+
seedHue: theme.hue,
|
|
3265
|
+
baseName: themeName,
|
|
3266
|
+
prefix: themedPrefix,
|
|
3267
|
+
defs: colorMapFromTheme(theme),
|
|
3268
|
+
mode: "theme",
|
|
3269
|
+
emitDeclarations: passPrefix === themedPrefix
|
|
3270
|
+
};
|
|
3271
|
+
}
|
|
2799
3272
|
/**
|
|
2800
3273
|
* Shared per-theme driver for `tokens` / `tasty` / `css`. `json` skips
|
|
2801
3274
|
* this because it doesn't do collision filtering or primary duplication.
|
|
@@ -2809,17 +3282,29 @@ function buildPaletteOutput(themes, paletteOptions, options, buildOne, merge, em
|
|
|
2809
3282
|
const resolved = theme.resolve();
|
|
2810
3283
|
const pastel = theme.getConfig().pastel;
|
|
2811
3284
|
const prefix = resolvePrefix(options, themeName, true);
|
|
2812
|
-
merge(acc, buildOne(filterCollisions(resolved, prefix, seen, themeName), prefix, pastel));
|
|
2813
|
-
if (themeName === effectivePrimary) merge(acc, buildOne(filterCollisions(resolved, "", seen, themeName, true), "", pastel));
|
|
3285
|
+
merge(acc, buildOne(filterCollisions(resolved, prefix, seen, themeName), prefix, pastel, themeName, theme));
|
|
3286
|
+
if (themeName === effectivePrimary) merge(acc, buildOne(filterCollisions(resolved, "", seen, themeName, true), "", pastel, themeName, theme));
|
|
2814
3287
|
}
|
|
2815
3288
|
return acc;
|
|
2816
3289
|
}
|
|
2817
3290
|
function createPalette(themes, paletteOptions) {
|
|
2818
3291
|
validatePrimaryTheme(paletteOptions?.primary, themes);
|
|
3292
|
+
const buildDtcgResult = (options) => {
|
|
3293
|
+
const modes = resolveModes(options?.modes);
|
|
3294
|
+
const colorSpace = options?.colorSpace ?? "srgb";
|
|
3295
|
+
return buildPaletteOutput(themes, paletteOptions, options, (filtered, prefix, pastel, _themeName, _theme) => buildDtcgMap(filtered, prefix, modes, colorSpace, pastel), (acc, part) => {
|
|
3296
|
+
Object.assign(acc.light, part.light);
|
|
3297
|
+
if (part.dark) acc.dark = Object.assign(acc.dark ?? {}, part.dark);
|
|
3298
|
+
if (part.lightContrast) acc.lightContrast = Object.assign(acc.lightContrast ?? {}, part.lightContrast);
|
|
3299
|
+
if (part.darkContrast) acc.darkContrast = Object.assign(acc.darkContrast ?? {}, part.darkContrast);
|
|
3300
|
+
}, () => ({ light: {} }));
|
|
3301
|
+
};
|
|
2819
3302
|
return {
|
|
2820
3303
|
tokens(options) {
|
|
3304
|
+
const format = options?.format ?? "oklch";
|
|
3305
|
+
assertNativeFormat(format, "tokens");
|
|
2821
3306
|
const modes = resolveModes(options?.modes);
|
|
2822
|
-
return buildPaletteOutput(themes, paletteOptions, options, (filtered, prefix, pastel) => buildFlatTokenMap(filtered, prefix, modes,
|
|
3307
|
+
return buildPaletteOutput(themes, paletteOptions, options, (filtered, prefix, pastel) => buildFlatTokenMap(filtered, prefix, modes, format, pastel), (acc, part) => {
|
|
2823
3308
|
for (const variant of Object.keys(part)) {
|
|
2824
3309
|
if (!acc[variant]) acc[variant] = {};
|
|
2825
3310
|
Object.assign(acc[variant], part[variant]);
|
|
@@ -2833,18 +3318,27 @@ function createPalette(themes, paletteOptions) {
|
|
|
2833
3318
|
highContrast: options?.states?.highContrast ?? cfg.states.highContrast
|
|
2834
3319
|
};
|
|
2835
3320
|
const modes = resolveModes(options?.modes);
|
|
2836
|
-
|
|
3321
|
+
const format = options?.format ?? "okhsl";
|
|
3322
|
+
return buildPaletteOutput(themes, paletteOptions, options, (filtered, prefix, pastel, themeName, theme) => {
|
|
3323
|
+
return buildTokenMap(filtered, prefix, states, modes, format, pastel, channelCtxForTheme(theme, themeName, prefix, resolvePrefix(options, themeName, true), options?.splitChannels, format, modes, filtered));
|
|
3324
|
+
}, (acc, part) => Object.assign(acc, part), () => ({}));
|
|
2837
3325
|
},
|
|
2838
3326
|
json(options) {
|
|
3327
|
+
const format = options?.format ?? "oklch";
|
|
3328
|
+
assertNativeFormat(format, "json");
|
|
2839
3329
|
const modes = resolveModes(options?.modes);
|
|
2840
3330
|
const result = {};
|
|
2841
|
-
for (const [themeName, theme] of Object.entries(themes)) result[themeName] = buildJsonMap(theme.resolve(), modes,
|
|
3331
|
+
for (const [themeName, theme] of Object.entries(themes)) result[themeName] = buildJsonMap(theme.resolve(), modes, format, theme.getConfig().pastel);
|
|
2842
3332
|
return result;
|
|
2843
3333
|
},
|
|
2844
3334
|
css(options) {
|
|
2845
3335
|
const suffix = options?.suffix ?? "-color";
|
|
2846
3336
|
const format = options?.format ?? "rgb";
|
|
2847
|
-
|
|
3337
|
+
assertNativeFormat(format, "css");
|
|
3338
|
+
const modes = resolveModes();
|
|
3339
|
+
const lines = buildPaletteOutput(themes, paletteOptions, options, (filtered, prefix, pastel, themeName, theme) => {
|
|
3340
|
+
return buildCssMap(filtered, prefix, suffix, format, pastel, channelCtxForTheme(theme, themeName, prefix, resolvePrefix(options, themeName, true), options?.splitChannels, format, modes, filtered));
|
|
3341
|
+
}, (acc, part) => {
|
|
2848
3342
|
for (const key of [
|
|
2849
3343
|
"light",
|
|
2850
3344
|
"dark",
|
|
@@ -2863,24 +3357,39 @@ function createPalette(themes, paletteOptions) {
|
|
|
2863
3357
|
lightContrast: lines.lightContrast.join("\n"),
|
|
2864
3358
|
darkContrast: lines.darkContrast.join("\n")
|
|
2865
3359
|
};
|
|
3360
|
+
},
|
|
3361
|
+
dtcg(options) {
|
|
3362
|
+
return buildDtcgResult(options);
|
|
3363
|
+
},
|
|
3364
|
+
dtcgResolver(options) {
|
|
3365
|
+
return buildDtcgResolver(buildDtcgResult(options), options);
|
|
3366
|
+
},
|
|
3367
|
+
tailwind(options) {
|
|
3368
|
+
const modes = resolveModes(options?.modes);
|
|
3369
|
+
const cssPrefix = options?.namespace ?? "color-";
|
|
3370
|
+
const format = options?.format ?? "oklch";
|
|
3371
|
+
assertNativeFormat(format, "tailwind");
|
|
3372
|
+
const darkSelector = options?.darkSelector ?? ".dark";
|
|
3373
|
+
const highContrastSelector = options?.highContrastSelector ?? ".high-contrast";
|
|
3374
|
+
return emitTailwindCss(buildPaletteOutput(themes, paletteOptions, options, (filtered, prefix, pastel, _themeName, _theme) => buildTailwindLines(filtered, prefix, cssPrefix, format, pastel), (acc, part) => {
|
|
3375
|
+
for (const variant of [
|
|
3376
|
+
"light",
|
|
3377
|
+
"dark",
|
|
3378
|
+
"lightContrast",
|
|
3379
|
+
"darkContrast"
|
|
3380
|
+
]) acc[variant].push(...part[variant]);
|
|
3381
|
+
}, () => ({
|
|
3382
|
+
light: [],
|
|
3383
|
+
dark: [],
|
|
3384
|
+
lightContrast: [],
|
|
3385
|
+
darkContrast: []
|
|
3386
|
+
})), modes, darkSelector, highContrastSelector);
|
|
2866
3387
|
}
|
|
2867
3388
|
};
|
|
2868
3389
|
}
|
|
2869
3390
|
|
|
2870
3391
|
//#endregion
|
|
2871
3392
|
//#region src/theme.ts
|
|
2872
|
-
/**
|
|
2873
|
-
* Theme factory.
|
|
2874
|
-
*
|
|
2875
|
-
* Wraps a hue/saturation seed, a mutable `ColorMap`, and an optional
|
|
2876
|
-
* per-theme `GlazeConfigOverride`. Exposes `tokens()` / `tasty()` /
|
|
2877
|
-
* `json()` / `css()` / `resolve()` / `export()` / `extend()`.
|
|
2878
|
-
*
|
|
2879
|
-
* The per-theme config override is **merged over the live global config at
|
|
2880
|
-
* resolve time** so the theme still reacts to later `configure()` calls
|
|
2881
|
-
* for fields it didn't override. The merged config is memoized by
|
|
2882
|
-
* `configVersion` to avoid rebuilding it on every export call.
|
|
2883
|
-
*/
|
|
2884
3393
|
function createTheme(hue, saturation, initialColors, configOverride) {
|
|
2885
3394
|
let colorDefs = initialColors ? { ...initialColors } : {};
|
|
2886
3395
|
let cache = null;
|
|
@@ -2904,6 +3413,18 @@ function createTheme(hue, saturation, initialColors, configOverride) {
|
|
|
2904
3413
|
function invalidate() {
|
|
2905
3414
|
cache = null;
|
|
2906
3415
|
}
|
|
3416
|
+
function channelCtxFor(options, formatDefault, prefix) {
|
|
3417
|
+
const format = options?.format ?? formatDefault;
|
|
3418
|
+
if (!options?.splitChannels || format !== "oklch") return void 0;
|
|
3419
|
+
assertAllPastel(resolveCached(), resolveModes(options?.modes));
|
|
3420
|
+
return {
|
|
3421
|
+
seedHue: hue,
|
|
3422
|
+
baseName: options.name ?? "theme",
|
|
3423
|
+
prefix,
|
|
3424
|
+
defs: colorDefs,
|
|
3425
|
+
mode: "theme"
|
|
3426
|
+
};
|
|
3427
|
+
}
|
|
2907
3428
|
return {
|
|
2908
3429
|
get hue() {
|
|
2909
3430
|
return hue;
|
|
@@ -2967,8 +3488,10 @@ function createTheme(hue, saturation, initialColors, configOverride) {
|
|
|
2967
3488
|
return new Map(resolveCached());
|
|
2968
3489
|
},
|
|
2969
3490
|
tokens(options) {
|
|
3491
|
+
const format = options?.format ?? "oklch";
|
|
3492
|
+
assertNativeFormat(format, "tokens");
|
|
2970
3493
|
const modes = resolveModes(options?.modes);
|
|
2971
|
-
return buildFlatTokenMap(resolveCached(), "", modes,
|
|
3494
|
+
return buildFlatTokenMap(resolveCached(), "", modes, format, getEffectiveConfig().pastel);
|
|
2972
3495
|
},
|
|
2973
3496
|
tasty(options) {
|
|
2974
3497
|
const cfg = getEffectiveConfig();
|
|
@@ -2977,14 +3500,34 @@ function createTheme(hue, saturation, initialColors, configOverride) {
|
|
|
2977
3500
|
highContrast: options?.states?.highContrast ?? cfg.states.highContrast
|
|
2978
3501
|
};
|
|
2979
3502
|
const modes = resolveModes(options?.modes);
|
|
2980
|
-
|
|
3503
|
+
const format = options?.format ?? "okhsl";
|
|
3504
|
+
const channelCtx = channelCtxFor(options, "okhsl", "");
|
|
3505
|
+
return buildTokenMap(resolveCached(), "", states, modes, format, cfg.pastel, channelCtx);
|
|
2981
3506
|
},
|
|
2982
3507
|
json(options) {
|
|
3508
|
+
const format = options?.format ?? "oklch";
|
|
3509
|
+
assertNativeFormat(format, "json");
|
|
2983
3510
|
const modes = resolveModes(options?.modes);
|
|
2984
|
-
return buildJsonMap(resolveCached(), modes,
|
|
3511
|
+
return buildJsonMap(resolveCached(), modes, format, getEffectiveConfig().pastel);
|
|
2985
3512
|
},
|
|
2986
3513
|
css(options) {
|
|
2987
|
-
|
|
3514
|
+
const format = options?.format ?? "rgb";
|
|
3515
|
+
assertNativeFormat(format, "css");
|
|
3516
|
+
const channelCtx = channelCtxFor(options, "rgb", "");
|
|
3517
|
+
return buildCssMap(resolveCached(), "", options?.suffix ?? "-color", format, getEffectiveConfig().pastel, channelCtx);
|
|
3518
|
+
},
|
|
3519
|
+
dtcg(options) {
|
|
3520
|
+
const modes = resolveModes(options?.modes);
|
|
3521
|
+
return buildDtcgMap(resolveCached(), "", modes, options?.colorSpace ?? "srgb", getEffectiveConfig().pastel);
|
|
3522
|
+
},
|
|
3523
|
+
dtcgResolver(options) {
|
|
3524
|
+
return buildDtcgResolver(buildDtcgMap(resolveCached(), "", resolveModes(options?.modes), options?.colorSpace ?? "srgb", getEffectiveConfig().pastel), options);
|
|
3525
|
+
},
|
|
3526
|
+
tailwind(options) {
|
|
3527
|
+
const format = options?.format ?? "oklch";
|
|
3528
|
+
assertNativeFormat(format, "tailwind");
|
|
3529
|
+
const modes = resolveModes(options?.modes);
|
|
3530
|
+
return buildTailwindMap(resolveCached(), "", options?.namespace ?? "color-", modes, format, options?.darkSelector ?? ".dark", options?.highContrastSelector ?? ".high-contrast", getEffectiveConfig().pastel);
|
|
2988
3531
|
}
|
|
2989
3532
|
};
|
|
2990
3533
|
}
|
|
@@ -3178,12 +3721,15 @@ glaze.resetConfig = function resetConfig$1() {
|
|
|
3178
3721
|
//#endregion
|
|
3179
3722
|
exports.REF_EPS = REF_EPS;
|
|
3180
3723
|
exports.apcaContrast = apcaContrast;
|
|
3724
|
+
exports.assertAllPastel = assertAllPastel;
|
|
3725
|
+
exports.assertNativeFormat = assertNativeFormat;
|
|
3181
3726
|
exports.contrastRatioFromLuminance = contrastRatioFromLuminance;
|
|
3182
3727
|
exports.cuspLightness = cuspLightness;
|
|
3183
3728
|
exports.findToneForContrast = findToneForContrast;
|
|
3184
3729
|
exports.findValueForMixContrast = findValueForMixContrast;
|
|
3185
3730
|
exports.formatHsl = formatHsl;
|
|
3186
3731
|
exports.formatOkhsl = formatOkhsl;
|
|
3732
|
+
exports.formatOkhst = formatOkhst;
|
|
3187
3733
|
exports.formatOklch = formatOklch;
|
|
3188
3734
|
exports.formatRgb = formatRgb;
|
|
3189
3735
|
exports.fromTone = fromTone;
|
|
@@ -3195,6 +3741,7 @@ exports.normalizeRole = normalizeRole;
|
|
|
3195
3741
|
exports.okhslToLinearSrgb = okhslToLinearSrgb;
|
|
3196
3742
|
exports.okhslToOkhst = okhslToOkhst;
|
|
3197
3743
|
exports.okhslToOklab = okhslToOklab;
|
|
3744
|
+
exports.okhslToOklch = okhslToOklch;
|
|
3198
3745
|
exports.okhslToSrgb = okhslToSrgb;
|
|
3199
3746
|
exports.okhstToOkhsl = okhstToOkhsl;
|
|
3200
3747
|
exports.oklabToOkhsl = oklabToOkhsl;
|
|
@@ -3206,6 +3753,7 @@ exports.resolveApcaTarget = resolveApcaTarget;
|
|
|
3206
3753
|
exports.resolveContrastForMode = resolveContrastForMode;
|
|
3207
3754
|
exports.resolveMinContrast = resolveMinContrast;
|
|
3208
3755
|
exports.roleToPolarity = roleToPolarity;
|
|
3756
|
+
exports.srgbToHex = srgbToHex;
|
|
3209
3757
|
exports.srgbToOkhsl = srgbToOkhsl;
|
|
3210
3758
|
exports.toTone = toTone;
|
|
3211
3759
|
exports.toneFromY = toneFromY;
|