@tenphi/glaze 0.6.3 → 0.8.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 +70 -30
- package/dist/index.cjs +79 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +44 -18
- package/dist/index.d.mts +44 -18
- package/dist/index.mjs +79 -27
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -71,8 +71,8 @@ const success = primary.extend({ hue: 157 });
|
|
|
71
71
|
|
|
72
72
|
// Compose into a palette and export
|
|
73
73
|
const palette = glaze.palette({ primary, danger, success });
|
|
74
|
-
const tokens = palette.tokens({
|
|
75
|
-
// → { light: { 'primary-surface': 'okhsl(...)',
|
|
74
|
+
const tokens = palette.tokens({ primary: 'primary' });
|
|
75
|
+
// → { light: { 'primary-surface': 'okhsl(...)', 'surface': 'okhsl(...)', ... }, dark: { ... } }
|
|
76
76
|
```
|
|
77
77
|
|
|
78
78
|
## Core Concepts
|
|
@@ -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. 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 same Möbius curve over the full [0, 100] range. Fixed uses identity (`L`). This allows HC colors to reach the full 0–100 range.
|
|
658
665
|
|
|
659
666
|
### Saturation
|
|
660
667
|
|
|
@@ -694,12 +701,12 @@ Combine multiple themes into a single palette:
|
|
|
694
701
|
const palette = glaze.palette({ primary, danger, success, warning });
|
|
695
702
|
```
|
|
696
703
|
|
|
697
|
-
###
|
|
704
|
+
### Prefix Behavior
|
|
698
705
|
|
|
699
|
-
|
|
706
|
+
Palette export methods (`tokens()`, `tasty()`, `css()`) default to `prefix: true` — all tokens are automatically prefixed with the theme name to avoid collisions:
|
|
700
707
|
|
|
701
708
|
```ts
|
|
702
|
-
const tokens = palette.tokens(
|
|
709
|
+
const tokens = palette.tokens();
|
|
703
710
|
// → {
|
|
704
711
|
// light: { 'primary-surface': 'okhsl(...)', 'danger-surface': 'okhsl(...)' },
|
|
705
712
|
// dark: { 'primary-surface': 'okhsl(...)', 'danger-surface': 'okhsl(...)' },
|
|
@@ -712,15 +719,44 @@ Custom prefix mapping:
|
|
|
712
719
|
palette.tokens({ prefix: { primary: 'brand-', danger: 'error-' } });
|
|
713
720
|
```
|
|
714
721
|
|
|
722
|
+
To disable prefixing entirely, pass `prefix: false` explicitly. Note that tokens with the same name will overwrite each other (last theme wins).
|
|
723
|
+
|
|
724
|
+
### Primary Theme
|
|
725
|
+
|
|
726
|
+
Use the `primary` option to designate one theme as the primary. Its tokens are duplicated without prefix, providing convenient short aliases alongside the prefixed versions:
|
|
727
|
+
|
|
728
|
+
```ts
|
|
729
|
+
const palette = glaze.palette({ primary, danger, success });
|
|
730
|
+
const tokens = palette.tokens({ primary: 'primary' });
|
|
731
|
+
// → {
|
|
732
|
+
// light: {
|
|
733
|
+
// 'primary-surface': 'okhsl(...)', // prefixed (all themes)
|
|
734
|
+
// 'danger-surface': 'okhsl(...)',
|
|
735
|
+
// 'success-surface': 'okhsl(...)',
|
|
736
|
+
// 'surface': 'okhsl(...)', // unprefixed alias (primary only)
|
|
737
|
+
// },
|
|
738
|
+
// }
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
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:
|
|
742
|
+
|
|
743
|
+
```ts
|
|
744
|
+
palette.tokens({ prefix: { primary: 'p-', danger: 'd-' }, primary: 'primary' });
|
|
745
|
+
// → 'p-surface' + 'surface' (alias) + 'd-surface'
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
An error is thrown if the primary name doesn't match any theme in the palette.
|
|
749
|
+
|
|
715
750
|
### Tasty Export (for [Tasty](https://cube-ui-kit.vercel.app/?path=/docs/tasty-documentation--docs) style system)
|
|
716
751
|
|
|
717
752
|
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.):
|
|
718
753
|
|
|
719
754
|
```ts
|
|
720
|
-
const tastyTokens = palette.tasty({
|
|
755
|
+
const tastyTokens = palette.tasty({ primary: 'primary' });
|
|
721
756
|
// → {
|
|
722
757
|
// '#primary-surface': { '': 'okhsl(...)', '@dark': 'okhsl(...)' },
|
|
723
758
|
// '#danger-surface': { '': 'okhsl(...)', '@dark': 'okhsl(...)' },
|
|
759
|
+
// '#surface': { '': 'okhsl(...)', '@dark': 'okhsl(...)' }, // alias
|
|
724
760
|
// }
|
|
725
761
|
```
|
|
726
762
|
|
|
@@ -787,8 +823,10 @@ palette.tasty({ states: { dark: '@dark', highContrast: '@hc' } });
|
|
|
787
823
|
|
|
788
824
|
### JSON Export (Framework-Agnostic)
|
|
789
825
|
|
|
826
|
+
JSON export groups by theme name (no prefix needed):
|
|
827
|
+
|
|
790
828
|
```ts
|
|
791
|
-
const data = palette.json(
|
|
829
|
+
const data = palette.json();
|
|
792
830
|
// → {
|
|
793
831
|
// primary: { surface: { light: 'okhsl(...)', dark: 'okhsl(...)' } },
|
|
794
832
|
// danger: { surface: { light: 'okhsl(...)', dark: 'okhsl(...)' } },
|
|
@@ -810,7 +848,7 @@ const css = theme.css();
|
|
|
810
848
|
Use in a stylesheet:
|
|
811
849
|
|
|
812
850
|
```ts
|
|
813
|
-
const css = palette.css({
|
|
851
|
+
const css = palette.css({ primary: 'primary' });
|
|
814
852
|
|
|
815
853
|
const stylesheet = `
|
|
816
854
|
:root { ${css.light} }
|
|
@@ -826,7 +864,8 @@ Options:
|
|
|
826
864
|
|---|---|---|
|
|
827
865
|
| `format` | `'rgb'` | Color format (`'rgb'`, `'hsl'`, `'okhsl'`, `'oklch'`) |
|
|
828
866
|
| `suffix` | `'-color'` | Suffix appended to each CSS property name |
|
|
829
|
-
| `prefix` |
|
|
867
|
+
| `prefix` | `true` (palette) | (palette only) `true` uses `"<themeName>-"`, or provide a custom map |
|
|
868
|
+
| `primary` | — | (palette only) Theme name to duplicate without prefix |
|
|
830
869
|
|
|
831
870
|
```ts
|
|
832
871
|
// Custom suffix
|
|
@@ -837,9 +876,9 @@ theme.css({ suffix: '' });
|
|
|
837
876
|
theme.css({ format: 'hsl' });
|
|
838
877
|
// → "--surface-color: hsl(...);"
|
|
839
878
|
|
|
840
|
-
// Palette with
|
|
841
|
-
palette.css({
|
|
842
|
-
// → "--primary-surface-color: rgb(...);\n--danger-surface-color: rgb(...);"
|
|
879
|
+
// Palette with primary
|
|
880
|
+
palette.css({ primary: 'primary' });
|
|
881
|
+
// → "--primary-surface-color: rgb(...);\n--surface-color: rgb(...);\n--danger-surface-color: rgb(...);"
|
|
843
882
|
```
|
|
844
883
|
|
|
845
884
|
## Output Modes
|
|
@@ -872,9 +911,10 @@ Resolution priority (highest first):
|
|
|
872
911
|
|
|
873
912
|
```ts
|
|
874
913
|
glaze.configure({
|
|
875
|
-
lightLightness: [10, 100], // Light scheme lightness window [lo, hi]
|
|
876
|
-
darkLightness: [15, 95], // Dark scheme lightness window [lo, hi]
|
|
914
|
+
lightLightness: [10, 100], // Light scheme lightness window [lo, hi] (bypassed in HC)
|
|
915
|
+
darkLightness: [15, 95], // Dark scheme lightness window [lo, hi] (bypassed in HC)
|
|
877
916
|
darkDesaturation: 0.1, // Saturation reduction in dark scheme (0–1)
|
|
917
|
+
darkCurve: 0.5, // Möbius beta for dark auto-inversion (0–1, lower = more expansion)
|
|
878
918
|
states: {
|
|
879
919
|
dark: '@dark', // State alias for dark mode tokens
|
|
880
920
|
highContrast: '@high-contrast',
|
|
@@ -1011,16 +1051,16 @@ const note = primary.extend({ hue: 302 });
|
|
|
1011
1051
|
|
|
1012
1052
|
const palette = glaze.palette({ primary, danger, success, warning, note });
|
|
1013
1053
|
|
|
1014
|
-
// Export as flat token map grouped by variant
|
|
1015
|
-
const tokens = palette.tokens({
|
|
1016
|
-
// tokens.light → { 'primary-surface': '
|
|
1054
|
+
// Export as flat token map grouped by variant (prefix defaults to true)
|
|
1055
|
+
const tokens = palette.tokens({ primary: 'primary' });
|
|
1056
|
+
// tokens.light → { 'primary-surface': '...', 'surface': '...', 'danger-surface': '...' }
|
|
1017
1057
|
|
|
1018
1058
|
// Export as tasty style-to-state bindings (for Tasty style system)
|
|
1019
|
-
const tastyTokens = palette.tasty({
|
|
1059
|
+
const tastyTokens = palette.tasty({ primary: 'primary' });
|
|
1020
1060
|
|
|
1021
1061
|
// Export as CSS custom properties (rgb format by default)
|
|
1022
|
-
const css = palette.css({
|
|
1023
|
-
// css.light → "--primary-surface-color: rgb(...);\n--
|
|
1062
|
+
const css = palette.css({ primary: 'primary' });
|
|
1063
|
+
// css.light → "--primary-surface-color: rgb(...);\n--surface-color: rgb(...);\n--danger-surface-color: rgb(...);"
|
|
1024
1064
|
|
|
1025
1065
|
// Standalone shadow computation
|
|
1026
1066
|
const v = glaze.shadow({ bg: '#f0eef5', fg: '#1a1a2e', intensity: 10 });
|
package/dist/index.cjs
CHANGED
|
@@ -470,7 +470,7 @@ function formatOklch(h, s, l) {
|
|
|
470
470
|
const C = Math.sqrt(a * a + b * b);
|
|
471
471
|
let hh = Math.atan2(b, a) * (180 / Math.PI);
|
|
472
472
|
hh = constrainAngle(hh);
|
|
473
|
-
return `oklch(${fmt$1(L, 4)} ${fmt$1(C, 4)} ${fmt$1(hh,
|
|
473
|
+
return `oklch(${fmt$1(L, 4)} ${fmt$1(C, 4)} ${fmt$1(hh, 2)})`;
|
|
474
474
|
}
|
|
475
475
|
|
|
476
476
|
//#endregion
|
|
@@ -620,7 +620,7 @@ function coarseScan(h, s, lo, hi, yBase, target, epsilon, maxIter) {
|
|
|
620
620
|
function findLightnessForContrast(options) {
|
|
621
621
|
const { hue, saturation, preferredLightness, baseLinearRgb, contrast: contrastInput, lightnessRange = [0, 1], epsilon = 1e-4, maxIterations = 14 } = options;
|
|
622
622
|
const target = resolveMinContrast(contrastInput);
|
|
623
|
-
const searchTarget = target * 1.
|
|
623
|
+
const searchTarget = target * 1.007;
|
|
624
624
|
const yBase = gamutClampedLuminance(baseLinearRgb);
|
|
625
625
|
const crPref = contrastRatioFromLuminance(cachedLuminance(hue, saturation, preferredLightness), yBase);
|
|
626
626
|
if (crPref >= searchTarget) return {
|
|
@@ -744,7 +744,7 @@ function searchMixBranch(lo, hi, yBase, target, epsilon, maxIter, preferred, lum
|
|
|
744
744
|
function findValueForMixContrast(options) {
|
|
745
745
|
const { preferredValue, baseLinearRgb, contrast: contrastInput, luminanceAtValue, epsilon = 1e-4, maxIterations = 20 } = options;
|
|
746
746
|
const target = resolveMinContrast(contrastInput);
|
|
747
|
-
const searchTarget = target * 1.
|
|
747
|
+
const searchTarget = target * 1.01;
|
|
748
748
|
const yBase = gamutClampedLuminance(baseLinearRgb);
|
|
749
749
|
const crPref = contrastRatioFromLuminance(luminanceAtValue(preferredValue), yBase);
|
|
750
750
|
if (crPref >= searchTarget) return {
|
|
@@ -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,23 +962,42 @@ function topoSort(defs) {
|
|
|
961
962
|
for (const name of Object.keys(defs)) visit(name);
|
|
962
963
|
return result;
|
|
963
964
|
}
|
|
964
|
-
function mapLightnessLight(l, mode) {
|
|
965
|
-
if (mode === "static") return l;
|
|
965
|
+
function mapLightnessLight(l, mode, isHighContrast) {
|
|
966
|
+
if (mode === "static" || isHighContrast) return l;
|
|
966
967
|
const [lo, hi] = globalConfig.lightLightness;
|
|
967
968
|
return l * (hi - lo) / 100 + lo;
|
|
968
969
|
}
|
|
969
|
-
function
|
|
970
|
+
function mobiusCurve(t, beta) {
|
|
971
|
+
if (beta >= 1) return t;
|
|
972
|
+
return t / (t + beta * (1 - t));
|
|
973
|
+
}
|
|
974
|
+
function mapLightnessDark(l, mode, isHighContrast) {
|
|
970
975
|
if (mode === "static") return l;
|
|
971
|
-
const
|
|
972
|
-
if (
|
|
973
|
-
|
|
976
|
+
const beta = globalConfig.darkCurve;
|
|
977
|
+
if (isHighContrast) {
|
|
978
|
+
if (mode === "fixed") return l;
|
|
979
|
+
return 100 * mobiusCurve((100 - l) / 100, beta);
|
|
980
|
+
}
|
|
981
|
+
const [darkLo, darkHi] = globalConfig.darkLightness;
|
|
982
|
+
if (mode === "fixed") return l * (darkHi - darkLo) / 100 + darkLo;
|
|
983
|
+
const [lightLo, lightHi] = globalConfig.lightLightness;
|
|
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 = globalConfig.darkCurve;
|
|
989
|
+
if (isHighContrast) return 100 * mobiusCurve((100 - lightL) / 100, beta);
|
|
990
|
+
const [lightLo, lightHi] = globalConfig.lightLightness;
|
|
991
|
+
const [darkLo, darkHi] = globalConfig.darkLightness;
|
|
992
|
+
const t = (lightHi - clamp(lightL, lightLo, lightHi)) / (lightHi - lightLo);
|
|
993
|
+
return darkLo + (darkHi - darkLo) * mobiusCurve(t, beta);
|
|
974
994
|
}
|
|
975
995
|
function mapSaturationDark(s, mode) {
|
|
976
996
|
if (mode === "static") return s;
|
|
977
997
|
return s * (1 - globalConfig.darkDesaturation);
|
|
978
998
|
}
|
|
979
|
-
function schemeLightnessRange(isDark, mode) {
|
|
980
|
-
if (mode === "static") return [0, 1];
|
|
999
|
+
function schemeLightnessRange(isDark, mode, isHighContrast) {
|
|
1000
|
+
if (mode === "static" || isHighContrast) return [0, 1];
|
|
981
1001
|
const [lo, hi] = isDark ? globalConfig.darkLightness : globalConfig.lightLightness;
|
|
982
1002
|
return [lo / 100, hi / 100];
|
|
983
1003
|
}
|
|
@@ -1037,26 +1057,26 @@ function resolveDependentColor(name, def, ctx, isHighContrast, isDark, effective
|
|
|
1037
1057
|
else {
|
|
1038
1058
|
const parsed = parseRelativeOrAbsolute(isHighContrast ? pairHC(rawLightness) : pairNormal(rawLightness));
|
|
1039
1059
|
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);
|
|
1060
|
+
const delta = parsed.value;
|
|
1061
|
+
if (isDark && mode === "auto") preferredL = lightMappedToDark(clamp(getSchemeVariant(baseResolved, false, isHighContrast).l * 100 + delta, 0, 100), isHighContrast);
|
|
1062
|
+
else preferredL = clamp(baseL + delta, 0, 100);
|
|
1063
|
+
} else if (isDark) preferredL = mapLightnessDark(parsed.value, mode, isHighContrast);
|
|
1064
|
+
else preferredL = mapLightnessLight(parsed.value, mode, isHighContrast);
|
|
1045
1065
|
}
|
|
1046
1066
|
const rawContrast = def.contrast;
|
|
1047
1067
|
if (rawContrast !== void 0) {
|
|
1048
1068
|
const minCr = isHighContrast ? pairHC(rawContrast) : pairNormal(rawContrast);
|
|
1049
1069
|
const effectiveSat = isDark ? mapSaturationDark(satFactor * ctx.saturation / 100, mode) : satFactor * ctx.saturation / 100;
|
|
1050
1070
|
const baseLinearRgb = okhslToLinearSrgb(baseVariant.h, baseVariant.s, baseVariant.l);
|
|
1051
|
-
const
|
|
1071
|
+
const windowRange = schemeLightnessRange(isDark, mode, isHighContrast);
|
|
1052
1072
|
return {
|
|
1053
1073
|
l: findLightnessForContrast({
|
|
1054
1074
|
hue: effectiveHue,
|
|
1055
1075
|
saturation: effectiveSat,
|
|
1056
|
-
preferredLightness: clamp(preferredL / 100,
|
|
1076
|
+
preferredLightness: clamp(preferredL / 100, windowRange[0], windowRange[1]),
|
|
1057
1077
|
baseLinearRgb,
|
|
1058
1078
|
contrast: minCr,
|
|
1059
|
-
lightnessRange
|
|
1079
|
+
lightnessRange: [0, 1]
|
|
1060
1080
|
}).lightness * 100,
|
|
1061
1081
|
satFactor
|
|
1062
1082
|
};
|
|
@@ -1093,13 +1113,13 @@ function resolveColorForScheme(name, def, ctx, isDark, isHighContrast) {
|
|
|
1093
1113
|
let finalL;
|
|
1094
1114
|
let finalSat;
|
|
1095
1115
|
if (isDark && isRoot) {
|
|
1096
|
-
finalL = mapLightnessDark(lightL, mode);
|
|
1116
|
+
finalL = mapLightnessDark(lightL, mode, isHighContrast);
|
|
1097
1117
|
finalSat = mapSaturationDark(satFactor * ctx.saturation / 100, mode);
|
|
1098
1118
|
} else if (isDark && !isRoot) {
|
|
1099
1119
|
finalL = lightL;
|
|
1100
1120
|
finalSat = mapSaturationDark(satFactor * ctx.saturation / 100, mode);
|
|
1101
1121
|
} else if (isRoot) {
|
|
1102
|
-
finalL = mapLightnessLight(lightL, mode);
|
|
1122
|
+
finalL = mapLightnessLight(lightL, mode, isHighContrast);
|
|
1103
1123
|
finalSat = satFactor * ctx.saturation / 100;
|
|
1104
1124
|
} else {
|
|
1105
1125
|
finalL = lightL;
|
|
@@ -1421,26 +1441,40 @@ function createTheme(hue, saturation, initialColors) {
|
|
|
1421
1441
|
}
|
|
1422
1442
|
};
|
|
1423
1443
|
}
|
|
1424
|
-
function resolvePrefix(options, themeName) {
|
|
1425
|
-
|
|
1426
|
-
if (
|
|
1444
|
+
function resolvePrefix(options, themeName, defaultPrefix = false) {
|
|
1445
|
+
const prefix = options?.prefix ?? defaultPrefix;
|
|
1446
|
+
if (prefix === true) return `${themeName}-`;
|
|
1447
|
+
if (typeof prefix === "object" && prefix !== null) return prefix[themeName] ?? `${themeName}-`;
|
|
1427
1448
|
return "";
|
|
1428
1449
|
}
|
|
1450
|
+
function validatePrimaryTheme(primary, themes) {
|
|
1451
|
+
if (primary !== void 0 && !(primary in themes)) {
|
|
1452
|
+
const available = Object.keys(themes).join(", ");
|
|
1453
|
+
throw new Error(`glaze: primary theme "${primary}" not found in palette. Available: ${available}.`);
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1429
1456
|
function createPalette(themes) {
|
|
1430
1457
|
return {
|
|
1431
1458
|
tokens(options) {
|
|
1459
|
+
validatePrimaryTheme(options?.primary, themes);
|
|
1432
1460
|
const modes = resolveModes(options?.modes);
|
|
1433
1461
|
const allTokens = {};
|
|
1434
1462
|
for (const [themeName, theme] of Object.entries(themes)) {
|
|
1435
|
-
const
|
|
1463
|
+
const resolved = theme.resolve();
|
|
1464
|
+
const tokens = buildFlatTokenMap(resolved, resolvePrefix(options, themeName, true), modes, options?.format);
|
|
1436
1465
|
for (const variant of Object.keys(tokens)) {
|
|
1437
1466
|
if (!allTokens[variant]) allTokens[variant] = {};
|
|
1438
1467
|
Object.assign(allTokens[variant], tokens[variant]);
|
|
1439
1468
|
}
|
|
1469
|
+
if (themeName === options?.primary) {
|
|
1470
|
+
const unprefixed = buildFlatTokenMap(resolved, "", modes, options?.format);
|
|
1471
|
+
for (const variant of Object.keys(unprefixed)) Object.assign(allTokens[variant], unprefixed[variant]);
|
|
1472
|
+
}
|
|
1440
1473
|
}
|
|
1441
1474
|
return allTokens;
|
|
1442
1475
|
},
|
|
1443
1476
|
tasty(options) {
|
|
1477
|
+
validatePrimaryTheme(options?.primary, themes);
|
|
1444
1478
|
const states = {
|
|
1445
1479
|
dark: options?.states?.dark ?? globalConfig.states.dark,
|
|
1446
1480
|
highContrast: options?.states?.highContrast ?? globalConfig.states.highContrast
|
|
@@ -1448,8 +1482,13 @@ function createPalette(themes) {
|
|
|
1448
1482
|
const modes = resolveModes(options?.modes);
|
|
1449
1483
|
const allTokens = {};
|
|
1450
1484
|
for (const [themeName, theme] of Object.entries(themes)) {
|
|
1451
|
-
const
|
|
1485
|
+
const resolved = theme.resolve();
|
|
1486
|
+
const tokens = buildTokenMap(resolved, resolvePrefix(options, themeName, true), states, modes, options?.format);
|
|
1452
1487
|
Object.assign(allTokens, tokens);
|
|
1488
|
+
if (themeName === options?.primary) {
|
|
1489
|
+
const unprefixed = buildTokenMap(resolved, "", states, modes, options?.format);
|
|
1490
|
+
Object.assign(allTokens, unprefixed);
|
|
1491
|
+
}
|
|
1453
1492
|
}
|
|
1454
1493
|
return allTokens;
|
|
1455
1494
|
},
|
|
@@ -1460,6 +1499,7 @@ function createPalette(themes) {
|
|
|
1460
1499
|
return result;
|
|
1461
1500
|
},
|
|
1462
1501
|
css(options) {
|
|
1502
|
+
validatePrimaryTheme(options?.primary, themes);
|
|
1463
1503
|
const suffix = options?.suffix ?? "-color";
|
|
1464
1504
|
const format = options?.format ?? "rgb";
|
|
1465
1505
|
const allLines = {
|
|
@@ -1469,13 +1509,23 @@ function createPalette(themes) {
|
|
|
1469
1509
|
darkContrast: []
|
|
1470
1510
|
};
|
|
1471
1511
|
for (const [themeName, theme] of Object.entries(themes)) {
|
|
1472
|
-
const
|
|
1512
|
+
const resolved = theme.resolve();
|
|
1513
|
+
const css = buildCssMap(resolved, resolvePrefix(options, themeName, true), suffix, format);
|
|
1473
1514
|
for (const key of [
|
|
1474
1515
|
"light",
|
|
1475
1516
|
"dark",
|
|
1476
1517
|
"lightContrast",
|
|
1477
1518
|
"darkContrast"
|
|
1478
1519
|
]) if (css[key]) allLines[key].push(css[key]);
|
|
1520
|
+
if (themeName === options?.primary) {
|
|
1521
|
+
const unprefixed = buildCssMap(resolved, "", suffix, format);
|
|
1522
|
+
for (const key of [
|
|
1523
|
+
"light",
|
|
1524
|
+
"dark",
|
|
1525
|
+
"lightContrast",
|
|
1526
|
+
"darkContrast"
|
|
1527
|
+
]) if (unprefixed[key]) allLines[key].push(unprefixed[key]);
|
|
1528
|
+
}
|
|
1479
1529
|
}
|
|
1480
1530
|
return {
|
|
1481
1531
|
light: allLines.light.join("\n"),
|
|
@@ -1535,6 +1585,7 @@ glaze.configure = function configure(config) {
|
|
|
1535
1585
|
lightLightness: config.lightLightness ?? globalConfig.lightLightness,
|
|
1536
1586
|
darkLightness: config.darkLightness ?? globalConfig.darkLightness,
|
|
1537
1587
|
darkDesaturation: config.darkDesaturation ?? globalConfig.darkDesaturation,
|
|
1588
|
+
darkCurve: config.darkCurve ?? globalConfig.darkCurve,
|
|
1538
1589
|
states: {
|
|
1539
1590
|
dark: config.states?.dark ?? globalConfig.states.dark,
|
|
1540
1591
|
highContrast: config.states?.highContrast ?? globalConfig.states.highContrast
|
|
@@ -1634,6 +1685,7 @@ glaze.resetConfig = function resetConfig() {
|
|
|
1634
1685
|
lightLightness: [10, 100],
|
|
1635
1686
|
darkLightness: [15, 95],
|
|
1636
1687
|
darkDesaturation: .1,
|
|
1688
|
+
darkCurve: .5,
|
|
1637
1689
|
states: {
|
|
1638
1690
|
dark: "@dark",
|
|
1639
1691
|
highContrast: "@high-contrast"
|