@tenphi/glaze 0.6.3 → 0.7.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 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({ prefix: true });
75
- // → { light: { 'primary-surface': 'okhsl(...)', ... }, dark: { '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
@@ -694,12 +694,12 @@ Combine multiple themes into a single palette:
694
694
  const palette = glaze.palette({ primary, danger, success, warning });
695
695
  ```
696
696
 
697
- ### Token Export
697
+ ### Prefix Behavior
698
698
 
699
- Tokens are grouped by scheme variant, with plain color names as keys:
699
+ Palette export methods (`tokens()`, `tasty()`, `css()`) default to `prefix: true` — all tokens are automatically prefixed with the theme name to avoid collisions:
700
700
 
701
701
  ```ts
702
- const tokens = palette.tokens({ prefix: true });
702
+ const tokens = palette.tokens();
703
703
  // → {
704
704
  // light: { 'primary-surface': 'okhsl(...)', 'danger-surface': 'okhsl(...)' },
705
705
  // dark: { 'primary-surface': 'okhsl(...)', 'danger-surface': 'okhsl(...)' },
@@ -712,15 +712,44 @@ Custom prefix mapping:
712
712
  palette.tokens({ prefix: { primary: 'brand-', danger: 'error-' } });
713
713
  ```
714
714
 
715
+ To disable prefixing entirely, pass `prefix: false` explicitly. Note that tokens with the same name will overwrite each other (last theme wins).
716
+
717
+ ### Primary Theme
718
+
719
+ 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:
720
+
721
+ ```ts
722
+ const palette = glaze.palette({ primary, danger, success });
723
+ const tokens = palette.tokens({ primary: 'primary' });
724
+ // → {
725
+ // light: {
726
+ // 'primary-surface': 'okhsl(...)', // prefixed (all themes)
727
+ // 'danger-surface': 'okhsl(...)',
728
+ // 'success-surface': 'okhsl(...)',
729
+ // 'surface': 'okhsl(...)', // unprefixed alias (primary only)
730
+ // },
731
+ // }
732
+ ```
733
+
734
+ 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
+
736
+ ```ts
737
+ palette.tokens({ prefix: { primary: 'p-', danger: 'd-' }, primary: 'primary' });
738
+ // → 'p-surface' + 'surface' (alias) + 'd-surface'
739
+ ```
740
+
741
+ An error is thrown if the primary name doesn't match any theme in the palette.
742
+
715
743
  ### Tasty Export (for [Tasty](https://cube-ui-kit.vercel.app/?path=/docs/tasty-documentation--docs) style system)
716
744
 
717
745
  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
746
 
719
747
  ```ts
720
- const tastyTokens = palette.tasty({ prefix: true });
748
+ const tastyTokens = palette.tasty({ primary: 'primary' });
721
749
  // → {
722
750
  // '#primary-surface': { '': 'okhsl(...)', '@dark': 'okhsl(...)' },
723
751
  // '#danger-surface': { '': 'okhsl(...)', '@dark': 'okhsl(...)' },
752
+ // '#surface': { '': 'okhsl(...)', '@dark': 'okhsl(...)' }, // alias
724
753
  // }
725
754
  ```
726
755
 
@@ -787,8 +816,10 @@ palette.tasty({ states: { dark: '@dark', highContrast: '@hc' } });
787
816
 
788
817
  ### JSON Export (Framework-Agnostic)
789
818
 
819
+ JSON export groups by theme name (no prefix needed):
820
+
790
821
  ```ts
791
- const data = palette.json({ prefix: true });
822
+ const data = palette.json();
792
823
  // → {
793
824
  // primary: { surface: { light: 'okhsl(...)', dark: 'okhsl(...)' } },
794
825
  // danger: { surface: { light: 'okhsl(...)', dark: 'okhsl(...)' } },
@@ -810,7 +841,7 @@ const css = theme.css();
810
841
  Use in a stylesheet:
811
842
 
812
843
  ```ts
813
- const css = palette.css({ prefix: true });
844
+ const css = palette.css({ primary: 'primary' });
814
845
 
815
846
  const stylesheet = `
816
847
  :root { ${css.light} }
@@ -826,7 +857,8 @@ Options:
826
857
  |---|---|---|
827
858
  | `format` | `'rgb'` | Color format (`'rgb'`, `'hsl'`, `'okhsl'`, `'oklch'`) |
828
859
  | `suffix` | `'-color'` | Suffix appended to each CSS property name |
829
- | `prefix` | | (palette only) Same prefix behavior as `tokens()` |
860
+ | `prefix` | `true` (palette) | (palette only) `true` uses `"<themeName>-"`, or provide a custom map |
861
+ | `primary` | — | (palette only) Theme name to duplicate without prefix |
830
862
 
831
863
  ```ts
832
864
  // Custom suffix
@@ -837,9 +869,9 @@ theme.css({ suffix: '' });
837
869
  theme.css({ format: 'hsl' });
838
870
  // → "--surface-color: hsl(...);"
839
871
 
840
- // Palette with prefix
841
- palette.css({ prefix: true });
842
- // → "--primary-surface-color: rgb(...);\n--danger-surface-color: rgb(...);"
872
+ // Palette with primary
873
+ palette.css({ primary: 'primary' });
874
+ // → "--primary-surface-color: rgb(...);\n--surface-color: rgb(...);\n--danger-surface-color: rgb(...);"
843
875
  ```
844
876
 
845
877
  ## Output Modes
@@ -1011,16 +1043,16 @@ const note = primary.extend({ hue: 302 });
1011
1043
 
1012
1044
  const palette = glaze.palette({ primary, danger, success, warning, note });
1013
1045
 
1014
- // Export as flat token map grouped by variant
1015
- const tokens = palette.tokens({ prefix: true });
1016
- // tokens.light → { 'primary-surface': 'okhsl(...)', 'primary-shadow-md': 'okhsl(... / 0.1)' }
1046
+ // Export as flat token map grouped by variant (prefix defaults to true)
1047
+ const tokens = palette.tokens({ primary: 'primary' });
1048
+ // tokens.light → { 'primary-surface': '...', 'surface': '...', 'danger-surface': '...' }
1017
1049
 
1018
1050
  // Export as tasty style-to-state bindings (for Tasty style system)
1019
- const tastyTokens = palette.tasty({ prefix: true });
1051
+ const tastyTokens = palette.tasty({ primary: 'primary' });
1020
1052
 
1021
1053
  // Export as CSS custom properties (rgb format by default)
1022
- const css = palette.css({ prefix: true });
1023
- // css.light → "--primary-surface-color: rgb(...);\n--primary-shadow-md-color: rgb(... / 0.1);"
1054
+ const css = palette.css({ primary: 'primary' });
1055
+ // css.light → "--primary-surface-color: rgb(...);\n--surface-color: rgb(...);\n--danger-surface-color: rgb(...);"
1024
1056
 
1025
1057
  // Standalone shadow computation
1026
1058
  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, 1)})`;
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.005;
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.005;
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 {
@@ -1421,26 +1421,40 @@ function createTheme(hue, saturation, initialColors) {
1421
1421
  }
1422
1422
  };
1423
1423
  }
1424
- function resolvePrefix(options, themeName) {
1425
- if (options?.prefix === true) return `${themeName}-`;
1426
- if (typeof options?.prefix === "object" && options.prefix !== null) return options.prefix[themeName] ?? `${themeName}-`;
1424
+ function resolvePrefix(options, themeName, defaultPrefix = false) {
1425
+ const prefix = options?.prefix ?? defaultPrefix;
1426
+ if (prefix === true) return `${themeName}-`;
1427
+ if (typeof prefix === "object" && prefix !== null) return prefix[themeName] ?? `${themeName}-`;
1427
1428
  return "";
1428
1429
  }
1430
+ function validatePrimaryTheme(primary, themes) {
1431
+ if (primary !== void 0 && !(primary in themes)) {
1432
+ const available = Object.keys(themes).join(", ");
1433
+ throw new Error(`glaze: primary theme "${primary}" not found in palette. Available: ${available}.`);
1434
+ }
1435
+ }
1429
1436
  function createPalette(themes) {
1430
1437
  return {
1431
1438
  tokens(options) {
1439
+ validatePrimaryTheme(options?.primary, themes);
1432
1440
  const modes = resolveModes(options?.modes);
1433
1441
  const allTokens = {};
1434
1442
  for (const [themeName, theme] of Object.entries(themes)) {
1435
- const tokens = buildFlatTokenMap(theme.resolve(), resolvePrefix(options, themeName), modes, options?.format);
1443
+ const resolved = theme.resolve();
1444
+ const tokens = buildFlatTokenMap(resolved, resolvePrefix(options, themeName, true), modes, options?.format);
1436
1445
  for (const variant of Object.keys(tokens)) {
1437
1446
  if (!allTokens[variant]) allTokens[variant] = {};
1438
1447
  Object.assign(allTokens[variant], tokens[variant]);
1439
1448
  }
1449
+ if (themeName === options?.primary) {
1450
+ const unprefixed = buildFlatTokenMap(resolved, "", modes, options?.format);
1451
+ for (const variant of Object.keys(unprefixed)) Object.assign(allTokens[variant], unprefixed[variant]);
1452
+ }
1440
1453
  }
1441
1454
  return allTokens;
1442
1455
  },
1443
1456
  tasty(options) {
1457
+ validatePrimaryTheme(options?.primary, themes);
1444
1458
  const states = {
1445
1459
  dark: options?.states?.dark ?? globalConfig.states.dark,
1446
1460
  highContrast: options?.states?.highContrast ?? globalConfig.states.highContrast
@@ -1448,8 +1462,13 @@ function createPalette(themes) {
1448
1462
  const modes = resolveModes(options?.modes);
1449
1463
  const allTokens = {};
1450
1464
  for (const [themeName, theme] of Object.entries(themes)) {
1451
- const tokens = buildTokenMap(theme.resolve(), resolvePrefix(options, themeName), states, modes, options?.format);
1465
+ const resolved = theme.resolve();
1466
+ const tokens = buildTokenMap(resolved, resolvePrefix(options, themeName, true), states, modes, options?.format);
1452
1467
  Object.assign(allTokens, tokens);
1468
+ if (themeName === options?.primary) {
1469
+ const unprefixed = buildTokenMap(resolved, "", states, modes, options?.format);
1470
+ Object.assign(allTokens, unprefixed);
1471
+ }
1453
1472
  }
1454
1473
  return allTokens;
1455
1474
  },
@@ -1460,6 +1479,7 @@ function createPalette(themes) {
1460
1479
  return result;
1461
1480
  },
1462
1481
  css(options) {
1482
+ validatePrimaryTheme(options?.primary, themes);
1463
1483
  const suffix = options?.suffix ?? "-color";
1464
1484
  const format = options?.format ?? "rgb";
1465
1485
  const allLines = {
@@ -1469,13 +1489,23 @@ function createPalette(themes) {
1469
1489
  darkContrast: []
1470
1490
  };
1471
1491
  for (const [themeName, theme] of Object.entries(themes)) {
1472
- const css = buildCssMap(theme.resolve(), resolvePrefix(options, themeName), suffix, format);
1492
+ const resolved = theme.resolve();
1493
+ const css = buildCssMap(resolved, resolvePrefix(options, themeName, true), suffix, format);
1473
1494
  for (const key of [
1474
1495
  "light",
1475
1496
  "dark",
1476
1497
  "lightContrast",
1477
1498
  "darkContrast"
1478
1499
  ]) if (css[key]) allLines[key].push(css[key]);
1500
+ if (themeName === options?.primary) {
1501
+ const unprefixed = buildCssMap(resolved, "", suffix, format);
1502
+ for (const key of [
1503
+ "light",
1504
+ "dark",
1505
+ "lightContrast",
1506
+ "darkContrast"
1507
+ ]) if (unprefixed[key]) allLines[key].push(unprefixed[key]);
1508
+ }
1479
1509
  }
1480
1510
  return {
1481
1511
  light: allLines.light.join("\n"),