@tenphi/glaze 0.7.0 → 0.9.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/README.md +78 -30
- package/dist/index.cjs +91 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +38 -22
- package/dist/index.d.mts +38 -22
- package/dist/index.mjs +91 -33
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -70,8 +70,8 @@ const danger = primary.extend({ hue: 23 });
|
|
|
70
70
|
const success = primary.extend({ hue: 157 });
|
|
71
71
|
|
|
72
72
|
// Compose into a palette and export
|
|
73
|
-
const palette = glaze.palette({ primary, danger, success });
|
|
74
|
-
const tokens = palette.tokens(
|
|
73
|
+
const palette = glaze.palette({ primary, danger, success }, { primary: 'primary' });
|
|
74
|
+
const tokens = palette.tokens();
|
|
75
75
|
// → { light: { 'primary-surface': 'okhsl(...)', 'surface': 'okhsl(...)', ... }, dark: { ... } }
|
|
76
76
|
```
|
|
77
77
|
|
|
@@ -194,6 +194,8 @@ A single value applies to both modes. All control is local and explicit.
|
|
|
194
194
|
'muted': { base: 'surface', lightness: ['-35', '-50'], contrast: ['AA-large', 'AA'] }
|
|
195
195
|
```
|
|
196
196
|
|
|
197
|
+
**Full lightness spectrum in HC mode:** In high-contrast variants, the `lightLightness` and `darkLightness` window constraints are bypassed entirely. Colors can reach the full 0–100 lightness range, maximizing perceivable contrast. Normal (non-HC) variants continue to use the configured windows.
|
|
198
|
+
|
|
197
199
|
## Theme Color Management
|
|
198
200
|
|
|
199
201
|
### Adding Colors
|
|
@@ -601,7 +603,7 @@ Modes control how colors adapt across schemes:
|
|
|
601
603
|
|
|
602
604
|
```ts
|
|
603
605
|
// Light: surface L=97, text lightness='-52' → L=45 (dark text on light bg)
|
|
604
|
-
// Dark: surface inverts to L≈
|
|
606
|
+
// Dark: surface inverts to L≈20 (Möbius curve), sign flips → L=20+52=72
|
|
605
607
|
// contrast solver may push further (light text on dark bg)
|
|
606
608
|
```
|
|
607
609
|
|
|
@@ -625,7 +627,7 @@ const [lo, hi] = lightLightness; // default: [10, 100]
|
|
|
625
627
|
const mappedL = (lightness * (hi - lo)) / 100 + lo;
|
|
626
628
|
```
|
|
627
629
|
|
|
628
|
-
Both `auto` and `fixed` modes use the same linear formula. `static` mode
|
|
630
|
+
Both `auto` and `fixed` modes use the same linear formula. `static` mode and high-contrast variants bypass the mapping entirely (identity: `mappedL = l`).
|
|
629
631
|
|
|
630
632
|
| Color | Raw L | Mapped L (default [10, 100]) |
|
|
631
633
|
|---|---|---|
|
|
@@ -637,24 +639,29 @@ Both `auto` and `fixed` modes use the same linear formula. `static` mode bypasse
|
|
|
637
639
|
|
|
638
640
|
### Lightness
|
|
639
641
|
|
|
640
|
-
**`auto`** — inverted within the configured window:
|
|
642
|
+
**`auto`** — inverted with a Möbius transformation within the configured window:
|
|
641
643
|
|
|
642
644
|
```ts
|
|
643
645
|
const [lo, hi] = darkLightness; // default: [15, 95]
|
|
644
|
-
const
|
|
646
|
+
const t = (100 - lightness) / 100;
|
|
647
|
+
const invertedL = lo + (hi - lo) * t / (t + darkCurve * (1 - t)); // darkCurve default: 0.5
|
|
645
648
|
```
|
|
646
649
|
|
|
647
|
-
|
|
650
|
+
The `darkCurve` parameter (default `0.5`, range 0–1) controls how much the dark-mode inversion expands lightness deltas. Lower values produce stronger expansion; `1` gives linear (legacy) behavior. Accepts a `[normal, highContrast]` pair for separate HC tuning (e.g. `darkCurve: [0.5, 0.3]`); a single number applies to both. Unlike a power curve, the Möbius transformation provides **proportional expansion** — small and large deltas are scaled by similar ratios, preserving the visual hierarchy of the light theme.
|
|
651
|
+
|
|
652
|
+
**`fixed`** — mapped without inversion (not affected by `darkCurve`):
|
|
648
653
|
|
|
649
654
|
```ts
|
|
650
655
|
const mappedL = (lightness * (hi - lo)) / 100 + lo;
|
|
651
656
|
```
|
|
652
657
|
|
|
653
|
-
| Color | Light L | Auto (
|
|
654
|
-
|
|
655
|
-
| surface (L=97) | 97 | 17.4 | 92.6 |
|
|
656
|
-
| accent-fill (L=52) | 52 | 53.4 | 56.6 |
|
|
657
|
-
| accent-text (L=100) | 100 | 15 | 95 |
|
|
658
|
+
| Color | Light L | Auto (curve=0.5) | Auto (curve=1, linear) | Fixed (mapped) |
|
|
659
|
+
|---|---|---|---|---|
|
|
660
|
+
| surface (L=97) | 97 | 19.7 | 17.4 | 92.6 |
|
|
661
|
+
| accent-fill (L=52) | 52 | 66.9 | 53.4 | 56.6 |
|
|
662
|
+
| accent-text (L=100) | 100 | 15 | 15 | 95 |
|
|
663
|
+
|
|
664
|
+
In high-contrast variants, the `darkLightness` window is bypassed — auto uses the Möbius curve over the full [0, 100] range, and fixed uses identity (`L`). To use a different curve shape for HC, pass a `[normal, hc]` pair to `darkCurve` (e.g. `darkCurve: [0.5, 0.3]`).
|
|
658
665
|
|
|
659
666
|
### Saturation
|
|
660
667
|
|
|
@@ -694,6 +701,15 @@ Combine multiple themes into a single palette:
|
|
|
694
701
|
const palette = glaze.palette({ primary, danger, success, warning });
|
|
695
702
|
```
|
|
696
703
|
|
|
704
|
+
Optionally designate a primary theme at creation time:
|
|
705
|
+
|
|
706
|
+
```ts
|
|
707
|
+
const palette = glaze.palette(
|
|
708
|
+
{ primary, danger, success, warning },
|
|
709
|
+
{ primary: 'primary' },
|
|
710
|
+
);
|
|
711
|
+
```
|
|
712
|
+
|
|
697
713
|
### Prefix Behavior
|
|
698
714
|
|
|
699
715
|
Palette export methods (`tokens()`, `tasty()`, `css()`) default to `prefix: true` — all tokens are automatically prefixed with the theme name to avoid collisions:
|
|
@@ -712,15 +728,28 @@ Custom prefix mapping:
|
|
|
712
728
|
palette.tokens({ prefix: { primary: 'brand-', danger: 'error-' } });
|
|
713
729
|
```
|
|
714
730
|
|
|
715
|
-
To disable prefixing entirely, pass `prefix: false` explicitly.
|
|
731
|
+
To disable prefixing entirely, pass `prefix: false` explicitly.
|
|
732
|
+
|
|
733
|
+
### Collision Detection
|
|
734
|
+
|
|
735
|
+
When two themes produce the same output key (via `prefix: false`, custom prefix maps, or primary unprefixed aliases), the first-written value wins and a `console.warn` is emitted:
|
|
736
|
+
|
|
737
|
+
```ts
|
|
738
|
+
const palette = glaze.palette({ a, b });
|
|
739
|
+
palette.tokens({ prefix: false });
|
|
740
|
+
// ⚠ glaze: token "surface" from theme "b" collides with theme "a" — skipping.
|
|
741
|
+
```
|
|
716
742
|
|
|
717
743
|
### Primary Theme
|
|
718
744
|
|
|
719
|
-
|
|
745
|
+
The primary theme's tokens are duplicated without prefix, providing convenient short aliases alongside the prefixed versions. Set at palette creation to apply to all exports automatically:
|
|
720
746
|
|
|
721
747
|
```ts
|
|
722
|
-
const palette = glaze.palette(
|
|
723
|
-
|
|
748
|
+
const palette = glaze.palette(
|
|
749
|
+
{ primary, danger, success },
|
|
750
|
+
{ primary: 'primary' },
|
|
751
|
+
);
|
|
752
|
+
const tokens = palette.tokens();
|
|
724
753
|
// → {
|
|
725
754
|
// light: {
|
|
726
755
|
// 'primary-surface': 'okhsl(...)', // prefixed (all themes)
|
|
@@ -731,11 +760,18 @@ const tokens = palette.tokens({ primary: 'primary' });
|
|
|
731
760
|
// }
|
|
732
761
|
```
|
|
733
762
|
|
|
763
|
+
Override or disable per-export:
|
|
764
|
+
|
|
765
|
+
```ts
|
|
766
|
+
palette.tokens({ primary: 'danger' }); // use danger as primary for this call
|
|
767
|
+
palette.tokens({ primary: false }); // no primary for this call
|
|
768
|
+
```
|
|
769
|
+
|
|
734
770
|
The `primary` option works on `tokens()`, `tasty()`, and `css()`. It combines with any prefix mode — when using a custom prefix map, primary tokens are still duplicated without prefix:
|
|
735
771
|
|
|
736
772
|
```ts
|
|
737
|
-
palette.tokens({ prefix: { primary: 'p-', danger: 'd-' }
|
|
738
|
-
// → 'p-surface' + 'surface' (alias) + 'd-surface'
|
|
773
|
+
palette.tokens({ prefix: { primary: 'p-', danger: 'd-' } });
|
|
774
|
+
// → 'p-surface' + 'surface' (alias from palette-level primary) + 'd-surface'
|
|
739
775
|
```
|
|
740
776
|
|
|
741
777
|
An error is thrown if the primary name doesn't match any theme in the palette.
|
|
@@ -745,7 +781,11 @@ An error is thrown if the primary name doesn't match any theme in the palette.
|
|
|
745
781
|
The `tasty()` method exports tokens in the [Tasty](https://cube-ui-kit.vercel.app/?path=/docs/tasty-documentation--docs) style-to-state binding format — `#name` color token keys with state aliases (`''`, `@dark`, etc.):
|
|
746
782
|
|
|
747
783
|
```ts
|
|
748
|
-
const
|
|
784
|
+
const palette = glaze.palette(
|
|
785
|
+
{ primary, danger, success },
|
|
786
|
+
{ primary: 'primary' },
|
|
787
|
+
);
|
|
788
|
+
const tastyTokens = palette.tasty();
|
|
749
789
|
// → {
|
|
750
790
|
// '#primary-surface': { '': 'okhsl(...)', '@dark': 'okhsl(...)' },
|
|
751
791
|
// '#danger-surface': { '': 'okhsl(...)', '@dark': 'okhsl(...)' },
|
|
@@ -841,7 +881,11 @@ const css = theme.css();
|
|
|
841
881
|
Use in a stylesheet:
|
|
842
882
|
|
|
843
883
|
```ts
|
|
844
|
-
const
|
|
884
|
+
const palette = glaze.palette(
|
|
885
|
+
{ primary, danger, success },
|
|
886
|
+
{ primary: 'primary' },
|
|
887
|
+
);
|
|
888
|
+
const css = palette.css();
|
|
845
889
|
|
|
846
890
|
const stylesheet = `
|
|
847
891
|
:root { ${css.light} }
|
|
@@ -858,7 +902,7 @@ Options:
|
|
|
858
902
|
| `format` | `'rgb'` | Color format (`'rgb'`, `'hsl'`, `'okhsl'`, `'oklch'`) |
|
|
859
903
|
| `suffix` | `'-color'` | Suffix appended to each CSS property name |
|
|
860
904
|
| `prefix` | `true` (palette) | (palette only) `true` uses `"<themeName>-"`, or provide a custom map |
|
|
861
|
-
| `primary` |
|
|
905
|
+
| `primary` | inherited | (palette only) Override or disable (`false`) the palette-level primary for this call |
|
|
862
906
|
|
|
863
907
|
```ts
|
|
864
908
|
// Custom suffix
|
|
@@ -869,8 +913,8 @@ theme.css({ suffix: '' });
|
|
|
869
913
|
theme.css({ format: 'hsl' });
|
|
870
914
|
// → "--surface-color: hsl(...);"
|
|
871
915
|
|
|
872
|
-
// Palette with primary
|
|
873
|
-
palette.css(
|
|
916
|
+
// Palette with primary (inherited from palette creation)
|
|
917
|
+
palette.css();
|
|
874
918
|
// → "--primary-surface-color: rgb(...);\n--surface-color: rgb(...);\n--danger-surface-color: rgb(...);"
|
|
875
919
|
```
|
|
876
920
|
|
|
@@ -904,9 +948,10 @@ Resolution priority (highest first):
|
|
|
904
948
|
|
|
905
949
|
```ts
|
|
906
950
|
glaze.configure({
|
|
907
|
-
lightLightness: [10, 100], // Light scheme lightness window [lo, hi]
|
|
908
|
-
darkLightness: [15, 95], // Dark scheme lightness window [lo, hi]
|
|
951
|
+
lightLightness: [10, 100], // Light scheme lightness window [lo, hi] (bypassed in HC)
|
|
952
|
+
darkLightness: [15, 95], // Dark scheme lightness window [lo, hi] (bypassed in HC)
|
|
909
953
|
darkDesaturation: 0.1, // Saturation reduction in dark scheme (0–1)
|
|
954
|
+
darkCurve: 0.5, // Möbius beta for dark auto-inversion (0–1); or [normal, hc] pair
|
|
910
955
|
states: {
|
|
911
956
|
dark: '@dark', // State alias for dark mode tokens
|
|
912
957
|
highContrast: '@high-contrast',
|
|
@@ -1041,17 +1086,20 @@ const success = primary.extend({ hue: 157 });
|
|
|
1041
1086
|
const warning = primary.extend({ hue: 84 });
|
|
1042
1087
|
const note = primary.extend({ hue: 302 });
|
|
1043
1088
|
|
|
1044
|
-
const palette = glaze.palette(
|
|
1089
|
+
const palette = glaze.palette(
|
|
1090
|
+
{ primary, danger, success, warning, note },
|
|
1091
|
+
{ primary: 'primary' },
|
|
1092
|
+
);
|
|
1045
1093
|
|
|
1046
1094
|
// Export as flat token map grouped by variant (prefix defaults to true)
|
|
1047
|
-
const tokens = palette.tokens(
|
|
1095
|
+
const tokens = palette.tokens();
|
|
1048
1096
|
// tokens.light → { 'primary-surface': '...', 'surface': '...', 'danger-surface': '...' }
|
|
1049
1097
|
|
|
1050
1098
|
// Export as tasty style-to-state bindings (for Tasty style system)
|
|
1051
|
-
const tastyTokens = palette.tasty(
|
|
1099
|
+
const tastyTokens = palette.tasty();
|
|
1052
1100
|
|
|
1053
1101
|
// Export as CSS custom properties (rgb format by default)
|
|
1054
|
-
const css = palette.css(
|
|
1102
|
+
const css = palette.css();
|
|
1055
1103
|
// css.light → "--primary-surface-color: rgb(...);\n--surface-color: rgb(...);\n--danger-surface-color: rgb(...);"
|
|
1056
1104
|
|
|
1057
1105
|
// Standalone shadow computation
|
|
@@ -1107,7 +1155,7 @@ brand.colors({ surface: { lightness: 97 }, text: { base: 'surface', lightness: '
|
|
|
1107
1155
|
| Method | Description |
|
|
1108
1156
|
|---|---|
|
|
1109
1157
|
| `glaze.configure(config)` | Set global configuration |
|
|
1110
|
-
| `glaze.palette(themes)` | Compose themes into a palette |
|
|
1158
|
+
| `glaze.palette(themes, options?)` | Compose themes into a palette (options: `{ primary? }`) |
|
|
1111
1159
|
| `glaze.getConfig()` | Get current global config |
|
|
1112
1160
|
| `glaze.resetConfig()` | Reset to defaults |
|
|
1113
1161
|
|
package/dist/index.cjs
CHANGED
|
@@ -814,6 +814,7 @@ let globalConfig = {
|
|
|
814
814
|
lightLightness: [10, 100],
|
|
815
815
|
darkLightness: [15, 95],
|
|
816
816
|
darkDesaturation: .1,
|
|
817
|
+
darkCurve: .5,
|
|
817
818
|
states: {
|
|
818
819
|
dark: "@dark",
|
|
819
820
|
highContrast: "@high-contrast"
|
|
@@ -961,24 +962,42 @@ function topoSort(defs) {
|
|
|
961
962
|
for (const name of Object.keys(defs)) visit(name);
|
|
962
963
|
return result;
|
|
963
964
|
}
|
|
964
|
-
function
|
|
965
|
+
function lightnessWindow(isHighContrast, kind) {
|
|
966
|
+
if (isHighContrast) return [0, 100];
|
|
967
|
+
return kind === "dark" ? globalConfig.darkLightness : globalConfig.lightLightness;
|
|
968
|
+
}
|
|
969
|
+
function mapLightnessLight(l, mode, isHighContrast) {
|
|
965
970
|
if (mode === "static") return l;
|
|
966
|
-
const [lo, hi] =
|
|
971
|
+
const [lo, hi] = lightnessWindow(isHighContrast, "light");
|
|
967
972
|
return l * (hi - lo) / 100 + lo;
|
|
968
973
|
}
|
|
969
|
-
function
|
|
974
|
+
function mobiusCurve(t, beta) {
|
|
975
|
+
if (beta >= 1) return t;
|
|
976
|
+
return t / (t + beta * (1 - t));
|
|
977
|
+
}
|
|
978
|
+
function mapLightnessDark(l, mode, isHighContrast) {
|
|
970
979
|
if (mode === "static") return l;
|
|
971
|
-
const
|
|
972
|
-
|
|
973
|
-
|
|
980
|
+
const beta = isHighContrast ? pairHC(globalConfig.darkCurve) : pairNormal(globalConfig.darkCurve);
|
|
981
|
+
const [darkLo, darkHi] = lightnessWindow(isHighContrast, "dark");
|
|
982
|
+
if (mode === "fixed") return l * (darkHi - darkLo) / 100 + darkLo;
|
|
983
|
+
const [lightLo, lightHi] = lightnessWindow(isHighContrast, "light");
|
|
984
|
+
const t = (lightHi - (l * (lightHi - lightLo) / 100 + lightLo)) / (lightHi - lightLo);
|
|
985
|
+
return darkLo + (darkHi - darkLo) * mobiusCurve(t, beta);
|
|
986
|
+
}
|
|
987
|
+
function lightMappedToDark(lightL, isHighContrast) {
|
|
988
|
+
const beta = isHighContrast ? pairHC(globalConfig.darkCurve) : pairNormal(globalConfig.darkCurve);
|
|
989
|
+
const [lightLo, lightHi] = lightnessWindow(isHighContrast, "light");
|
|
990
|
+
const [darkLo, darkHi] = lightnessWindow(isHighContrast, "dark");
|
|
991
|
+
const t = (lightHi - clamp(lightL, lightLo, lightHi)) / (lightHi - lightLo);
|
|
992
|
+
return darkLo + (darkHi - darkLo) * mobiusCurve(t, beta);
|
|
974
993
|
}
|
|
975
994
|
function mapSaturationDark(s, mode) {
|
|
976
995
|
if (mode === "static") return s;
|
|
977
996
|
return s * (1 - globalConfig.darkDesaturation);
|
|
978
997
|
}
|
|
979
|
-
function schemeLightnessRange(isDark, mode) {
|
|
998
|
+
function schemeLightnessRange(isDark, mode, isHighContrast) {
|
|
980
999
|
if (mode === "static") return [0, 1];
|
|
981
|
-
const [lo, hi] = isDark ?
|
|
1000
|
+
const [lo, hi] = lightnessWindow(isHighContrast, isDark ? "dark" : "light");
|
|
982
1001
|
return [lo / 100, hi / 100];
|
|
983
1002
|
}
|
|
984
1003
|
function clamp(v, min, max) {
|
|
@@ -1037,26 +1056,26 @@ function resolveDependentColor(name, def, ctx, isHighContrast, isDark, effective
|
|
|
1037
1056
|
else {
|
|
1038
1057
|
const parsed = parseRelativeOrAbsolute(isHighContrast ? pairHC(rawLightness) : pairNormal(rawLightness));
|
|
1039
1058
|
if (parsed.relative) {
|
|
1040
|
-
|
|
1041
|
-
if (isDark && mode === "auto")
|
|
1042
|
-
preferredL = clamp(baseL + delta, 0, 100);
|
|
1043
|
-
} else if (isDark) preferredL = mapLightnessDark(parsed.value, mode);
|
|
1044
|
-
else preferredL = mapLightnessLight(parsed.value, mode);
|
|
1059
|
+
const delta = parsed.value;
|
|
1060
|
+
if (isDark && mode === "auto") preferredL = lightMappedToDark(clamp(getSchemeVariant(baseResolved, false, isHighContrast).l * 100 + delta, 0, 100), isHighContrast);
|
|
1061
|
+
else preferredL = clamp(baseL + delta, 0, 100);
|
|
1062
|
+
} else if (isDark) preferredL = mapLightnessDark(parsed.value, mode, isHighContrast);
|
|
1063
|
+
else preferredL = mapLightnessLight(parsed.value, mode, isHighContrast);
|
|
1045
1064
|
}
|
|
1046
1065
|
const rawContrast = def.contrast;
|
|
1047
1066
|
if (rawContrast !== void 0) {
|
|
1048
1067
|
const minCr = isHighContrast ? pairHC(rawContrast) : pairNormal(rawContrast);
|
|
1049
1068
|
const effectiveSat = isDark ? mapSaturationDark(satFactor * ctx.saturation / 100, mode) : satFactor * ctx.saturation / 100;
|
|
1050
1069
|
const baseLinearRgb = okhslToLinearSrgb(baseVariant.h, baseVariant.s, baseVariant.l);
|
|
1051
|
-
const
|
|
1070
|
+
const windowRange = schemeLightnessRange(isDark, mode, isHighContrast);
|
|
1052
1071
|
return {
|
|
1053
1072
|
l: findLightnessForContrast({
|
|
1054
1073
|
hue: effectiveHue,
|
|
1055
1074
|
saturation: effectiveSat,
|
|
1056
|
-
preferredLightness: clamp(preferredL / 100,
|
|
1075
|
+
preferredLightness: clamp(preferredL / 100, windowRange[0], windowRange[1]),
|
|
1057
1076
|
baseLinearRgb,
|
|
1058
1077
|
contrast: minCr,
|
|
1059
|
-
lightnessRange
|
|
1078
|
+
lightnessRange: [0, 1]
|
|
1060
1079
|
}).lightness * 100,
|
|
1061
1080
|
satFactor
|
|
1062
1081
|
};
|
|
@@ -1093,13 +1112,13 @@ function resolveColorForScheme(name, def, ctx, isDark, isHighContrast) {
|
|
|
1093
1112
|
let finalL;
|
|
1094
1113
|
let finalSat;
|
|
1095
1114
|
if (isDark && isRoot) {
|
|
1096
|
-
finalL = mapLightnessDark(lightL, mode);
|
|
1115
|
+
finalL = mapLightnessDark(lightL, mode, isHighContrast);
|
|
1097
1116
|
finalSat = mapSaturationDark(satFactor * ctx.saturation / 100, mode);
|
|
1098
1117
|
} else if (isDark && !isRoot) {
|
|
1099
1118
|
finalL = lightL;
|
|
1100
1119
|
finalSat = mapSaturationDark(satFactor * ctx.saturation / 100, mode);
|
|
1101
1120
|
} else if (isRoot) {
|
|
1102
|
-
finalL = mapLightnessLight(lightL, mode);
|
|
1121
|
+
finalL = mapLightnessLight(lightL, mode, isHighContrast);
|
|
1103
1122
|
finalSat = satFactor * ctx.saturation / 100;
|
|
1104
1123
|
} else {
|
|
1105
1124
|
finalL = lightL;
|
|
@@ -1433,40 +1452,74 @@ function validatePrimaryTheme(primary, themes) {
|
|
|
1433
1452
|
throw new Error(`glaze: primary theme "${primary}" not found in palette. Available: ${available}.`);
|
|
1434
1453
|
}
|
|
1435
1454
|
}
|
|
1436
|
-
|
|
1455
|
+
/**
|
|
1456
|
+
* Resolve the effective primary for an export call.
|
|
1457
|
+
* `false` disables, a string overrides, `undefined` inherits from palette.
|
|
1458
|
+
*/
|
|
1459
|
+
function resolveEffectivePrimary(exportPrimary, palettePrimary) {
|
|
1460
|
+
if (exportPrimary === false) return void 0;
|
|
1461
|
+
return exportPrimary ?? palettePrimary;
|
|
1462
|
+
}
|
|
1463
|
+
/**
|
|
1464
|
+
* Filter a resolved color map, skipping keys already in `seen`.
|
|
1465
|
+
* Warns on collision and keeps the first-written value (first-write-wins).
|
|
1466
|
+
* Returns a new map containing only non-colliding entries.
|
|
1467
|
+
*/
|
|
1468
|
+
function filterCollisions(resolved, prefix, seen, themeName, isPrimary) {
|
|
1469
|
+
const filtered = /* @__PURE__ */ new Map();
|
|
1470
|
+
const label = isPrimary ? `${themeName} (primary)` : themeName;
|
|
1471
|
+
for (const [name, color] of resolved) {
|
|
1472
|
+
const key = `${prefix}${name}`;
|
|
1473
|
+
if (seen.has(key)) {
|
|
1474
|
+
console.warn(`glaze: token "${key}" from theme "${label}" collides with theme "${seen.get(key)}" — skipping.`);
|
|
1475
|
+
continue;
|
|
1476
|
+
}
|
|
1477
|
+
seen.set(key, label);
|
|
1478
|
+
filtered.set(name, color);
|
|
1479
|
+
}
|
|
1480
|
+
return filtered;
|
|
1481
|
+
}
|
|
1482
|
+
function createPalette(themes, paletteOptions) {
|
|
1483
|
+
validatePrimaryTheme(paletteOptions?.primary, themes);
|
|
1437
1484
|
return {
|
|
1438
1485
|
tokens(options) {
|
|
1439
|
-
|
|
1486
|
+
const effectivePrimary = resolveEffectivePrimary(options?.primary, paletteOptions?.primary);
|
|
1487
|
+
if (options?.primary !== void 0) validatePrimaryTheme(effectivePrimary, themes);
|
|
1440
1488
|
const modes = resolveModes(options?.modes);
|
|
1441
1489
|
const allTokens = {};
|
|
1490
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1442
1491
|
for (const [themeName, theme] of Object.entries(themes)) {
|
|
1443
1492
|
const resolved = theme.resolve();
|
|
1444
|
-
const
|
|
1493
|
+
const prefix = resolvePrefix(options, themeName, true);
|
|
1494
|
+
const tokens = buildFlatTokenMap(filterCollisions(resolved, prefix, seen, themeName), prefix, modes, options?.format);
|
|
1445
1495
|
for (const variant of Object.keys(tokens)) {
|
|
1446
1496
|
if (!allTokens[variant]) allTokens[variant] = {};
|
|
1447
1497
|
Object.assign(allTokens[variant], tokens[variant]);
|
|
1448
1498
|
}
|
|
1449
|
-
if (themeName ===
|
|
1450
|
-
const unprefixed = buildFlatTokenMap(resolved, "", modes, options?.format);
|
|
1499
|
+
if (themeName === effectivePrimary) {
|
|
1500
|
+
const unprefixed = buildFlatTokenMap(filterCollisions(resolved, "", seen, themeName, true), "", modes, options?.format);
|
|
1451
1501
|
for (const variant of Object.keys(unprefixed)) Object.assign(allTokens[variant], unprefixed[variant]);
|
|
1452
1502
|
}
|
|
1453
1503
|
}
|
|
1454
1504
|
return allTokens;
|
|
1455
1505
|
},
|
|
1456
1506
|
tasty(options) {
|
|
1457
|
-
|
|
1507
|
+
const effectivePrimary = resolveEffectivePrimary(options?.primary, paletteOptions?.primary);
|
|
1508
|
+
if (options?.primary !== void 0) validatePrimaryTheme(effectivePrimary, themes);
|
|
1458
1509
|
const states = {
|
|
1459
1510
|
dark: options?.states?.dark ?? globalConfig.states.dark,
|
|
1460
1511
|
highContrast: options?.states?.highContrast ?? globalConfig.states.highContrast
|
|
1461
1512
|
};
|
|
1462
1513
|
const modes = resolveModes(options?.modes);
|
|
1463
1514
|
const allTokens = {};
|
|
1515
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1464
1516
|
for (const [themeName, theme] of Object.entries(themes)) {
|
|
1465
1517
|
const resolved = theme.resolve();
|
|
1466
|
-
const
|
|
1518
|
+
const prefix = resolvePrefix(options, themeName, true);
|
|
1519
|
+
const tokens = buildTokenMap(filterCollisions(resolved, prefix, seen, themeName), prefix, states, modes, options?.format);
|
|
1467
1520
|
Object.assign(allTokens, tokens);
|
|
1468
|
-
if (themeName ===
|
|
1469
|
-
const unprefixed = buildTokenMap(resolved, "", states, modes, options?.format);
|
|
1521
|
+
if (themeName === effectivePrimary) {
|
|
1522
|
+
const unprefixed = buildTokenMap(filterCollisions(resolved, "", seen, themeName, true), "", states, modes, options?.format);
|
|
1470
1523
|
Object.assign(allTokens, unprefixed);
|
|
1471
1524
|
}
|
|
1472
1525
|
}
|
|
@@ -1479,7 +1532,8 @@ function createPalette(themes) {
|
|
|
1479
1532
|
return result;
|
|
1480
1533
|
},
|
|
1481
1534
|
css(options) {
|
|
1482
|
-
|
|
1535
|
+
const effectivePrimary = resolveEffectivePrimary(options?.primary, paletteOptions?.primary);
|
|
1536
|
+
if (options?.primary !== void 0) validatePrimaryTheme(effectivePrimary, themes);
|
|
1483
1537
|
const suffix = options?.suffix ?? "-color";
|
|
1484
1538
|
const format = options?.format ?? "rgb";
|
|
1485
1539
|
const allLines = {
|
|
@@ -1488,17 +1542,19 @@ function createPalette(themes) {
|
|
|
1488
1542
|
lightContrast: [],
|
|
1489
1543
|
darkContrast: []
|
|
1490
1544
|
};
|
|
1545
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1491
1546
|
for (const [themeName, theme] of Object.entries(themes)) {
|
|
1492
1547
|
const resolved = theme.resolve();
|
|
1493
|
-
const
|
|
1548
|
+
const prefix = resolvePrefix(options, themeName, true);
|
|
1549
|
+
const css = buildCssMap(filterCollisions(resolved, prefix, seen, themeName), prefix, suffix, format);
|
|
1494
1550
|
for (const key of [
|
|
1495
1551
|
"light",
|
|
1496
1552
|
"dark",
|
|
1497
1553
|
"lightContrast",
|
|
1498
1554
|
"darkContrast"
|
|
1499
1555
|
]) if (css[key]) allLines[key].push(css[key]);
|
|
1500
|
-
if (themeName ===
|
|
1501
|
-
const unprefixed = buildCssMap(resolved, "", suffix, format);
|
|
1556
|
+
if (themeName === effectivePrimary) {
|
|
1557
|
+
const unprefixed = buildCssMap(filterCollisions(resolved, "", seen, themeName, true), "", suffix, format);
|
|
1502
1558
|
for (const key of [
|
|
1503
1559
|
"light",
|
|
1504
1560
|
"dark",
|
|
@@ -1565,6 +1621,7 @@ glaze.configure = function configure(config) {
|
|
|
1565
1621
|
lightLightness: config.lightLightness ?? globalConfig.lightLightness,
|
|
1566
1622
|
darkLightness: config.darkLightness ?? globalConfig.darkLightness,
|
|
1567
1623
|
darkDesaturation: config.darkDesaturation ?? globalConfig.darkDesaturation,
|
|
1624
|
+
darkCurve: config.darkCurve ?? globalConfig.darkCurve,
|
|
1568
1625
|
states: {
|
|
1569
1626
|
dark: config.states?.dark ?? globalConfig.states.dark,
|
|
1570
1627
|
highContrast: config.states?.highContrast ?? globalConfig.states.highContrast
|
|
@@ -1579,8 +1636,8 @@ glaze.configure = function configure(config) {
|
|
|
1579
1636
|
/**
|
|
1580
1637
|
* Compose multiple themes into a palette.
|
|
1581
1638
|
*/
|
|
1582
|
-
glaze.palette = function palette(themes) {
|
|
1583
|
-
return createPalette(themes);
|
|
1639
|
+
glaze.palette = function palette(themes, options) {
|
|
1640
|
+
return createPalette(themes, options);
|
|
1584
1641
|
};
|
|
1585
1642
|
/**
|
|
1586
1643
|
* Create a theme from a serialized export.
|
|
@@ -1664,6 +1721,7 @@ glaze.resetConfig = function resetConfig() {
|
|
|
1664
1721
|
lightLightness: [10, 100],
|
|
1665
1722
|
darkLightness: [15, 95],
|
|
1666
1723
|
darkDesaturation: .1,
|
|
1724
|
+
darkCurve: .5,
|
|
1667
1725
|
states: {
|
|
1668
1726
|
dark: "@dark",
|
|
1669
1727
|
highContrast: "@high-contrast"
|