@happyvertical/smrt-ui 0.30.0 → 0.31.1

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.
Files changed (39) hide show
  1. package/dist/actions/__tests__/ripple.test.js +55 -1
  2. package/dist/actions/ripple.d.ts +9 -1
  3. package/dist/actions/ripple.d.ts.map +1 -1
  4. package/dist/actions/ripple.js +56 -7
  5. package/dist/components/display/ConfidenceBadge.svelte +14 -10
  6. package/dist/components/display/ConfidenceBadge.svelte.d.ts.map +1 -1
  7. package/dist/components/display/StatusBadge.svelte +81 -75
  8. package/dist/components/display/StatusBadge.svelte.d.ts.map +1 -1
  9. package/dist/components/display/__tests__/ConfidenceBadge.test.js +13 -0
  10. package/dist/components/display/__tests__/StatusBadge.test.js +17 -2
  11. package/dist/components/feedback/ConfirmDialog.svelte +78 -2
  12. package/dist/components/feedback/ConfirmDialog.svelte.d.ts.map +1 -1
  13. package/dist/components/feedback/LoadingOverlay.svelte +6 -1
  14. package/dist/components/feedback/LoadingOverlay.svelte.d.ts.map +1 -1
  15. package/dist/components/feedback/__tests__/ConfirmDialog.test.js +55 -3
  16. package/dist/components/feedback/__tests__/LoadingOverlay.test.js +14 -0
  17. package/dist/components/roles/RoleSelector.svelte +106 -13
  18. package/dist/components/roles/RoleSelector.svelte.d.ts +10 -0
  19. package/dist/components/roles/RoleSelector.svelte.d.ts.map +1 -1
  20. package/dist/components/roles/__tests__/RoleSelector.test.js +134 -0
  21. package/dist/components/ui/Button.svelte +12 -2
  22. package/dist/components/ui/Pagination.svelte +3 -1
  23. package/dist/components/ui/Pagination.svelte.d.ts.map +1 -1
  24. package/dist/components/ui/__tests__/Pagination.test.js +14 -0
  25. package/dist/themes/__tests__/create-theme.test.js +79 -0
  26. package/dist/themes/__tests__/css-generator.test.js +55 -0
  27. package/dist/themes/create-theme.d.ts.map +1 -1
  28. package/dist/themes/create-theme.js +29 -22
  29. package/dist/themes/css-generator.d.ts.map +1 -1
  30. package/dist/themes/css-generator.js +4 -1
  31. package/dist/themes/studio/index.d.ts.map +1 -1
  32. package/dist/themes/studio/index.js +19 -1
  33. package/dist/themes/types.d.ts +8 -1
  34. package/dist/themes/types.d.ts.map +1 -1
  35. package/dist/utils/theme/__tests__/color.test.js +17 -0
  36. package/dist/utils/theme/color.d.ts +11 -0
  37. package/dist/utils/theme/color.d.ts.map +1 -1
  38. package/dist/utils/theme/color.js +39 -4
  39. package/package.json +2 -2
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Regression tests for createTheme color handling (#1586, epic #1354).
3
+ *
4
+ * The dark-color derivation and primary-container derivation used to assume the
5
+ * primary was 6-digit hex. For `rgb()`, 3-digit hex, or a named color they
6
+ * emitted invalid CSS — `rgb(0,122,255)20`, `#f6320`, `#NaNNaNNaN`. Inputs are
7
+ * now normalized to RGB first (or rejected with a clear error), so the generated
8
+ * palette is always valid CSS.
9
+ */
10
+ import { describe, expect, it } from 'vitest';
11
+ import { createTheme } from '../create-theme';
12
+ const HEX6 = /^#[0-9a-fA-F]{6}$/;
13
+ const HEX_ALPHA = /^#[0-9a-fA-F]{8}$/;
14
+ /** Every color value in a palette must be syntactically valid CSS. */
15
+ function expectAllColorsValid(colors) {
16
+ for (const [key, value] of Object.entries(colors)) {
17
+ expect(typeof value).toBe('string');
18
+ // The regression: derivations used to emit '#NaNNaNNaN' / 'rgb(...)20'.
19
+ expect(value, `${key}=${value}`).not.toContain('NaN');
20
+ // Hex values must be well-formed: exactly 3, 4, 6, or 8 digits (all valid
21
+ // CSS). A 5- or 7-digit hex (e.g. the '#f6320' the bug produced) must fail.
22
+ // rgb()/rgba() and other formats pass through unchanged.
23
+ if (value.startsWith('#')) {
24
+ expect(value, `${key}=${value}`).toMatch(/^#([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/);
25
+ }
26
+ }
27
+ }
28
+ describe('createTheme color handling (#1586)', () => {
29
+ it('derives a valid primary container from a 6-digit hex primary', () => {
30
+ const theme = createTheme({
31
+ id: 'brand6',
32
+ name: 'Brand 6',
33
+ light: { primary: '#0b57d0', background: '#ffffff' },
34
+ });
35
+ // 8-digit hex alpha form: primary + "20".
36
+ expect(theme.light.primaryContainer).toMatch(HEX_ALPHA);
37
+ expect(theme.light.primaryContainer.toLowerCase()).toBe('#0b57d020');
38
+ expectAllColorsValid(theme.light);
39
+ expectAllColorsValid(theme.dark);
40
+ });
41
+ it('normalizes an rgb() primary instead of emitting invalid CSS', () => {
42
+ const theme = createTheme({
43
+ id: 'brandRgb',
44
+ name: 'Brand RGB',
45
+ light: { primary: 'rgb(0, 122, 255)', background: '#ffffff' },
46
+ });
47
+ // Was 'rgb(0, 122, 255)20' (invalid); now a normalized 8-digit hex.
48
+ expect(theme.light.primaryContainer).not.toContain('rgb(');
49
+ expect(theme.light.primaryContainer).toMatch(HEX_ALPHA);
50
+ expect(theme.light.primaryContainer.toLowerCase()).toBe('#007aff20');
51
+ // Auto-generated dark colors used to be '#NaNNaNNaN' for an rgb() input;
52
+ // every value is now valid CSS (hex values are well-formed, rgb() values
53
+ // pass through unchanged).
54
+ expectAllColorsValid(theme.light);
55
+ expectAllColorsValid(theme.dark);
56
+ // The brightness-derived dark colors normalize to proper hex.
57
+ expect(theme.dark.onPrimary).toMatch(HEX6);
58
+ expect(theme.dark.secondary).toMatch(HEX6);
59
+ });
60
+ it('expands a 3-digit hex primary correctly', () => {
61
+ const theme = createTheme({
62
+ id: 'brand3',
63
+ name: 'Brand 3',
64
+ light: { primary: '#07f', background: '#fff' },
65
+ });
66
+ // '#07f' -> '#0077ff', container '#0077ff20'.
67
+ expect(theme.light.primaryContainer.toLowerCase()).toBe('#0077ff20');
68
+ expectAllColorsValid(theme.light);
69
+ expectAllColorsValid(theme.dark);
70
+ expect(theme.dark.onPrimary).toMatch(HEX6);
71
+ });
72
+ it('throws a clear error for an unsupported named color', () => {
73
+ expect(() => createTheme({
74
+ id: 'brandNamed',
75
+ name: 'Brand Named',
76
+ light: { primary: 'tomato', background: '#ffffff' },
77
+ })).toThrow(/unsupported color "tomato"/);
78
+ });
79
+ });
@@ -1,9 +1,39 @@
1
1
  /**
2
2
  * Unit tests for the theme CSS generator (coverage uplift, S6 gate).
3
3
  */
4
+ import { readFileSync } from 'node:fs';
5
+ import { join } from 'node:path';
4
6
  import { describe, expect, it } from 'vitest';
5
7
  import { generateAllThemesCSS, generateThemeCSS, generateThemeVariables, variablesToStyleString, } from '../css-generator';
6
8
  import { getTheme } from '../registry';
9
+ // Raw CSS text of the static studio preset, used to prove the static-CSS
10
+ // delivery path and the JS generator emit identical elevation values (#1586).
11
+ // Resolved from process.cwd() (NOT import.meta.url): the smrt-vitest plugin runs
12
+ // each package's suite with cwd === the package root, so this is stable here,
13
+ // whereas `new URL('…', import.meta.url)` fails to resolve under the vite-node
14
+ // loader this suite runs in.
15
+ const studioCss = readFileSync(join(process.cwd(), 'src/themes/styles/studio.css'), 'utf8');
16
+ /**
17
+ * Pull the `--smrt-elevation-0..5` values out of a specific
18
+ * `[data-theme="<preset>"][data-color-scheme="<scheme>"]` block of a static CSS
19
+ * preset file.
20
+ */
21
+ function staticElevation(css, preset, scheme) {
22
+ const selector = `[data-theme="${preset}"][data-color-scheme="${scheme}"]`;
23
+ const start = css.indexOf(selector);
24
+ if (start === -1)
25
+ throw new Error(`block not found: ${selector}`);
26
+ const open = css.indexOf('{', start);
27
+ const close = css.indexOf('}', open);
28
+ const body = css.slice(open + 1, close);
29
+ const out = {};
30
+ for (const line of body.split('\n')) {
31
+ const m = line.match(/(--smrt-elevation-\d):\s*(.+);/);
32
+ if (m)
33
+ out[m[1]] = m[2].trim();
34
+ }
35
+ return out;
36
+ }
7
37
  describe('theme CSS generator', () => {
8
38
  const theme = getTheme('material');
9
39
  it('generateThemeVariables returns --smrt-* custom properties', () => {
@@ -29,4 +59,29 @@ describe('theme CSS generator', () => {
29
59
  const css = await generateAllThemesCSS();
30
60
  expect(css.length).toBeGreaterThan(0);
31
61
  });
62
+ describe('studio per-scheme elevation (#1586)', () => {
63
+ const studio = getTheme('studio');
64
+ it('emits distinct dark-scheme elevation, not the light values', () => {
65
+ const light = generateThemeVariables(studio, false);
66
+ const dark = generateThemeVariables(studio, true);
67
+ // Level 1 is a white inset hairline in dark vs a tinted inset in light.
68
+ expect(dark['--smrt-elevation-1']).toContain('rgba(255, 255, 255');
69
+ expect(light['--smrt-elevation-1']).toContain('rgba(60, 64, 67');
70
+ expect(dark['--smrt-elevation-1']).not.toBe(light['--smrt-elevation-1']);
71
+ // Deeper black shadows above level 1.
72
+ expect(dark['--smrt-elevation-5']).toContain('rgba(0, 0, 0, 0.25)');
73
+ });
74
+ it('matches the static studio.css for both schemes (drift guard)', () => {
75
+ // The static-CSS delivery path and the JS generator must agree on VALUES,
76
+ // not just token names — this is what let the studio dark shadows diverge.
77
+ for (const scheme of ['light', 'dark']) {
78
+ const generated = generateThemeVariables(studio, scheme === 'dark');
79
+ const fromCss = staticElevation(studioCss, 'studio', scheme);
80
+ for (let level = 0; level <= 5; level += 1) {
81
+ const key = `--smrt-elevation-${level}`;
82
+ expect(fromCss[key], `${scheme} ${key}`).toBe(generated[key]);
83
+ }
84
+ }
85
+ });
86
+ });
32
87
  });
@@ -1 +1 @@
1
- {"version":3,"file":"create-theme.d.ts","sourceRoot":"","sources":["../../src/themes/create-theme.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAaH,OAAO,KAAK,EACV,YAAY,EACZ,WAAW,EACX,cAAc,EACd,YAAY,EACZ,KAAK,EACL,WAAW,EACX,eAAe,EAChB,MAAM,YAAY,CAAC;AAEpB;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,wCAAwC;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,+BAA+B;IAC/B,KAAK,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,IAAI,CAAC,YAAY,EAAE,SAAS,GAAG,YAAY,CAAC,CAAC;IAC5E,iFAAiF;IACjF,IAAI,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IAC7B,wDAAwD;IACxD,UAAU,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;IACtC,mCAAmC;IACnC,SAAS,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;IACpC,sCAAsC;IACtC,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IAC9B,+BAA+B;IAC/B,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,kBAAkB;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAsPD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,KAAK,CAkD9D;AAkBD;;;GAGG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAG1D;AAgBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAEhD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,CAIjE;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAEpE;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,YAAY,EAAE,MAAM,EACpB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,IAAI,CAAC,kBAAkB,EAAE,IAAI,GAAG,MAAM,GAAG,OAAO,CAAC,GAC1D,KAAK,CAWP"}
1
+ {"version":3,"file":"create-theme.d.ts","sourceRoot":"","sources":["../../src/themes/create-theme.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAcH,OAAO,KAAK,EACV,YAAY,EACZ,WAAW,EACX,cAAc,EACd,YAAY,EACZ,KAAK,EACL,WAAW,EACX,eAAe,EAChB,MAAM,YAAY,CAAC;AAEpB;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,wCAAwC;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,+BAA+B;IAC/B,KAAK,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,IAAI,CAAC,YAAY,EAAE,SAAS,GAAG,YAAY,CAAC,CAAC;IAC5E,iFAAiF;IACjF,IAAI,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IAC7B,wDAAwD;IACxD,UAAU,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;IACtC,mCAAmC;IACnC,SAAS,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;IACpC,sCAAsC;IACtC,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IAC9B,+BAA+B;IAC/B,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,kBAAkB;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAyPD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,KAAK,CAsD9D;AAkBD;;;GAGG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAG1D;AAgBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAEhD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,CAIjE;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAEpE;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,YAAY,EAAE,MAAM,EACpB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,IAAI,CAAC,kBAAkB,EAAE,IAAI,GAAG,MAAM,GAAG,OAAO,CAAC,GAC1D,KAAK,CAWP"}
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * Helper functions for creating custom themes that match the SMRT theme system.
5
5
  */
6
+ import { hexToRgb, rgbToHex } from '../utils/theme/color.js';
6
7
  import { hasCustomTheme, registerCustomTheme, themes as registeredThemes, } from './registry.js';
7
8
  import { borderRadiusScale, durationScale, materialEasing, spacingScale, } from './shared.js';
8
9
  /**
@@ -54,34 +55,36 @@ const defaultColorStructure = {
54
55
  scrim: 'rgba(0, 0, 0, 0.5)',
55
56
  };
56
57
  /**
57
- * Helper function to clamp a value between min and max
58
- */
59
- function clamp(value, min, max) {
60
- return Math.min(max, Math.max(min, value));
61
- }
62
- /**
63
- * Helper function to convert RGB component to 2-digit hex
58
+ * Normalize any supported CSS color string to a canonical 6-digit hex.
59
+ *
60
+ * Accepts 6-digit hex, 3-digit shorthand hex, and `rgb()`/`rgba()` (via the
61
+ * shared `hexToRgb`). Throws on input that can't be parsed (e.g. a named color
62
+ * like `tomato`) so an unsupported value fails loudly instead of silently
63
+ * producing invalid CSS like `#NaNNaNNaN` or `rgb(...)20` (#1586).
64
64
  */
65
- function toHexComponent(value) {
66
- const clamped = Math.round(clamp(value, 0, 255));
67
- return clamped.toString(16).padStart(2, '0');
65
+ function normalizeHex(color) {
66
+ const { r, g, b } = hexToRgb(color);
67
+ if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) {
68
+ throw new Error(`createTheme: unsupported color "${color}". Use 6-digit hex (#rrggbb), ` +
69
+ '3-digit hex (#rgb), or rgb()/rgba() notation.');
70
+ }
71
+ return rgbToHex(r, g, b);
68
72
  }
69
73
  /**
70
74
  * Generate dark mode colors from light mode colors
71
75
  * Uses simple inversion logic - for production use, manually define dark colors
72
76
  */
73
77
  function generateDarkColors(light) {
74
- // Helper to adjust color brightness with proper rounding and hex conversion
75
- const adjustBrightness = (hex, factor) => {
76
- // Handle 3-character hex codes
77
- let fullHex = hex;
78
- if (hex.length === 4 && hex[0] === '#') {
79
- fullHex = `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}`;
78
+ // Adjust color brightness by a multiplicative factor. Normalizes the input to
79
+ // RGB first (via the shared parser), so 3-digit hex and rgb()/rgba() inputs no
80
+ // longer NaN-poison the output into `#NaNNaNNaN` (#1586).
81
+ const adjustBrightness = (color, factor) => {
82
+ const { r, g, b } = hexToRgb(color);
83
+ if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) {
84
+ throw new Error(`createTheme: unsupported color "${color}". Use 6-digit hex (#rrggbb), ` +
85
+ '3-digit hex (#rgb), or rgb()/rgba() notation.');
80
86
  }
81
- const r = parseInt(fullHex.slice(1, 3), 16) * factor;
82
- const g = parseInt(fullHex.slice(3, 5), 16) * factor;
83
- const b = parseInt(fullHex.slice(5, 7), 16) * factor;
84
- return `#${toHexComponent(r)}${toHexComponent(g)}${toHexComponent(b)}`;
87
+ return rgbToHex(r * factor, g * factor, b * factor);
85
88
  };
86
89
  return {
87
90
  ...light,
@@ -266,8 +269,12 @@ export function createTheme(options) {
266
269
  ...defaultColorStructure,
267
270
  ...baseTheme?.light,
268
271
  ...lightPartial,
269
- // Generate derived colors
270
- primaryContainer: lightPartial.primaryContainer || `${lightPartial.primary}20`,
272
+ // Generate derived colors. The container is the primary at ~12.5% alpha,
273
+ // expressed as 8-digit hex. Normalize the primary to canonical 6-hex first
274
+ // so a 3-digit hex or rgb()/rgba() primary can't yield invalid CSS like
275
+ // `rgb(0,122,255)20` or `#f6320` (#1586).
276
+ primaryContainer: lightPartial.primaryContainer ||
277
+ `${normalizeHex(lightPartial.primary)}20`,
271
278
  onPrimaryContainer: lightPartial.onPrimaryContainer || lightPartial.primary,
272
279
  inversePrimary: lightPartial.inversePrimary || lightPartial.primary,
273
280
  };
@@ -1 +1 @@
1
- {"version":3,"file":"css-generator.d.ts","sourceRoot":"","sources":["../../src/themes/css-generator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAWH,OAAO,KAAK,EAQV,KAAK,EAEN,MAAM,YAAY,CAAC;AAEpB;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AA+LD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,OAAO,EACf,OAAO,GAAE,kBAAuB,GAC/B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAsCxB;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAChC,MAAM,CAIR;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,OAAO,EACf,OAAO,GAAE,kBAAuB,GAC/B,MAAM,CAaR;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,MAAM,CAAC,CAS5D"}
1
+ {"version":3,"file":"css-generator.d.ts","sourceRoot":"","sources":["../../src/themes/css-generator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAWH,OAAO,KAAK,EAQV,KAAK,EAEN,MAAM,YAAY,CAAC;AAEpB;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AA+LD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,OAAO,EACf,OAAO,GAAE,kBAAuB,GAC/B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA0CxB;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAChC,MAAM,CAIR;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,OAAO,EACf,OAAO,GAAE,kBAAuB,GAC/B,MAAM,CAaR;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,MAAM,CAAC,CAS5D"}
@@ -157,6 +157,9 @@ function generateStaticTokens(prefix) {
157
157
  export function generateThemeVariables(theme, isDark, options = {}) {
158
158
  const { prefix = '--smrt' } = options;
159
159
  const colors = isDark ? theme.dark : theme.light;
160
+ // Per-scheme elevation: dark uses `darkElevation` when the preset defines it,
161
+ // so the JS generator and the static dark CSS stay in lockstep (#1586).
162
+ const elevation = isDark && theme.darkElevation ? theme.darkElevation : theme.elevation;
160
163
  return {
161
164
  // Theme identification
162
165
  [`${prefix}-theme-id`]: theme.id,
@@ -172,7 +175,7 @@ export function generateThemeVariables(theme, isDark, options = {}) {
172
175
  // Border radius
173
176
  ...generateBorderRadiusVariables(theme.borderRadius, prefix),
174
177
  // Elevation
175
- ...generateElevationVariables(theme.elevation, prefix),
178
+ ...generateElevationVariables(elevation, prefix),
176
179
  // Duration
177
180
  ...generateDurationVariables(theme.duration, prefix),
178
181
  // Easing
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/themes/studio/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH,OAAO,KAAK,EAGV,KAAK,EAEN,MAAM,aAAa,CAAC;AA+QrB;;GAEG;AACH,eAAO,MAAM,WAAW,EAAE,KAYzB,CAAC;AAEF,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/themes/studio/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH,OAAO,KAAK,EAGV,KAAK,EAEN,MAAM,aAAa,CAAC;AAiSrB;;GAEG;AACH,eAAO,MAAM,WAAW,EAAE,KAazB,CAAC;AAEF,eAAe,WAAW,CAAC"}
@@ -240,7 +240,7 @@ const typography = {
240
240
  },
241
241
  };
242
242
  /**
243
- * Studio elevation - Minimal, subtle shadows
243
+ * Studio elevation (light) - Minimal, subtle shadows
244
244
  * Flat design philosophy with only slight depth indication
245
245
  */
246
246
  const elevation = {
@@ -251,6 +251,23 @@ const elevation = {
251
251
  4: '0 4px 8px 0 rgba(60, 64, 67, 0.1), 0 2px 4px 0 rgba(60, 64, 67, 0.06)',
252
252
  5: '0 8px 16px 0 rgba(60, 64, 67, 0.12), 0 4px 8px 0 rgba(60, 64, 67, 0.06)',
253
253
  };
254
+ /**
255
+ * Studio elevation (dark) - Tuned for a near-black surface.
256
+ *
257
+ * The flat light shadows (tinted with the light surface color, very low alpha)
258
+ * read as invisible on a dark surface, so dark mode uses a white inset hairline
259
+ * at level 1 and deeper black shadows above it. Defining this here — rather than
260
+ * only in the static `styles/studio.css` — keeps the JS ThemeProvider output and
261
+ * the static dark CSS in lockstep (#1586).
262
+ */
263
+ const darkElevation = {
264
+ 0: 'none',
265
+ 1: 'inset 0 0 0 1px rgba(255, 255, 255, 0.08)',
266
+ 2: '0 1px 2px 0 rgba(0, 0, 0, 0.2), 0 1px 3px 0 rgba(0, 0, 0, 0.1)',
267
+ 3: '0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 1px 2px 0 rgba(0, 0, 0, 0.1)',
268
+ 4: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 2px 4px 0 rgba(0, 0, 0, 0.1)',
269
+ 5: '0 8px 16px 0 rgba(0, 0, 0, 0.25), 0 4px 8px 0 rgba(0, 0, 0, 0.1)',
270
+ };
254
271
  /**
255
272
  * Studio theme definition
256
273
  */
@@ -261,6 +278,7 @@ export const studioTheme = {
261
278
  dark: darkColors,
262
279
  typography,
263
280
  elevation,
281
+ darkElevation,
264
282
  spacing: spacingScale,
265
283
  borderRadius: borderRadiusScale,
266
284
  duration: durationScale,
@@ -191,8 +191,15 @@ export interface Theme {
191
191
  dark: ColorPalette;
192
192
  /** Typography scale */
193
193
  typography: TypographyScale;
194
- /** Elevation shadows */
194
+ /** Elevation shadows (used for both schemes unless `darkElevation` is set) */
195
195
  elevation: ElevationScale;
196
+ /**
197
+ * Optional dark-scheme elevation overrides. When present, the CSS generator
198
+ * emits these for the dark scheme instead of `elevation`. Lets a preset tune
199
+ * shadows for a dark surface (e.g. studio's punchier dark shadows) so the JS
200
+ * generator and the static dark CSS can't diverge (#1586).
201
+ */
202
+ darkElevation?: ElevationScale;
196
203
  /** Spacing scale */
197
204
  spacing: SpacingScale;
198
205
  /** Border radius scale */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/themes/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/themes/types.ts"],"names":[],"mappings":"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"}
@@ -6,6 +6,23 @@ describe('Theme Color Utilities', () => {
6
6
  expect(hexToRgb('#ffffff')).toEqual({ r: 255, g: 255, b: 255 });
7
7
  expect(hexToRgb('#FF5733')).toEqual({ r: 255, g: 87, b: 51 });
8
8
  });
9
+ it('expands 3-digit shorthand hex (#abc -> #aabbcc) (#1586)', () => {
10
+ // Regression: shorthand hex used to NaN-poison the b channel (only the first
11
+ // 2 chars were read), corrupting any palette generated from a 3-digit seed.
12
+ expect(hexToRgb('#abc')).toEqual({ r: 170, g: 187, b: 204 });
13
+ expect(hexToRgb('#000')).toEqual({ r: 0, g: 0, b: 0 });
14
+ expect(hexToRgb('#fff')).toEqual({ r: 255, g: 255, b: 255 });
15
+ });
16
+ it('parses functional rgb()/rgba() notation (#1586)', () => {
17
+ expect(hexToRgb('rgb(0, 122, 255)')).toEqual({ r: 0, g: 122, b: 255 });
18
+ expect(hexToRgb('rgb(0 122 255)')).toEqual({ r: 0, g: 122, b: 255 });
19
+ expect(hexToRgb('rgba(0, 122, 255, 0.5)')).toEqual({
20
+ r: 0,
21
+ g: 122,
22
+ b: 255,
23
+ });
24
+ expect(hexToRgb('rgb(100%, 0%, 50%)')).toEqual({ r: 255, g: 0, b: 128 });
25
+ });
9
26
  it('should convert rgb to hex correctly', () => {
10
27
  expect(rgbToHex(0, 0, 0)).toBe('#000000');
11
28
  expect(rgbToHex(255, 255, 255)).toBe('#ffffff');
@@ -46,6 +46,17 @@ export interface Theme {
46
46
  surfaceContainerHigh: string;
47
47
  surfaceContainerHighest: string;
48
48
  }
49
+ /**
50
+ * Parse a CSS color string into an {@link Rgb}.
51
+ *
52
+ * Accepts 6-digit hex (`#rrggbb`), 3-digit shorthand hex (`#rgb`, expanded by
53
+ * doubling each nibble), and functional `rgb()` / `rgba()` notation (commas or
54
+ * spaces, integer or percentage channels). Named colors and other formats are
55
+ * not supported and yield `{ r: NaN, g: NaN, b: NaN }`; callers that accept
56
+ * arbitrary input should validate before relying on the result.
57
+ *
58
+ * The leading `#` is optional for hex input.
59
+ */
49
60
  export declare function hexToRgb(hex: string): Rgb;
50
61
  export declare function rgbToHex(r: number, g: number, b: number): string;
51
62
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"color.d.ts","sourceRoot":"","sources":["../../../src/utils/theme/color.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,GAAG;IAClB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,GAAG;IAClB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAGD,MAAM,WAAW,KAAK;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,gBAAgB,EAAE,MAAM,CAAC;IACzB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,uBAAuB,EAAE,MAAM,CAAC;CACjC;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAMzC;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAMhE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAM7C;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,GAAG,MAAM,CAMzD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,EAAE,EAAE,MAAM,GAAG,GAAG,EAChB,EAAE,EAAE,MAAM,GAAG,GAAG,EAChB,SAAS,UAAQ,GAChB,OAAO,CAKT;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,MAAM,GAAG,GAAG,EAChB,EAAE,EAAE,MAAM,GAAG,GAAG,EAChB,SAAS,UAAQ,GAChB,OAAO,CAKT;AAmED,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,EACjB,IAAI,GAAE,OAAO,GAAG,MAAgB,GAC/B,KAAK,CA2HP"}
1
+ {"version":3,"file":"color.d.ts","sourceRoot":"","sources":["../../../src/utils/theme/color.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,GAAG;IAClB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,GAAG;IAClB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAGD,MAAM,WAAW,KAAK;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,gBAAgB,EAAE,MAAM,CAAC;IACzB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,uBAAuB,EAAE,MAAM,CAAC;CACjC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAkCzC;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAMhE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAM7C;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,GAAG,MAAM,CAMzD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,EAAE,EAAE,MAAM,GAAG,GAAG,EAChB,EAAE,EAAE,MAAM,GAAG,GAAG,EAChB,SAAS,UAAQ,GAChB,OAAO,CAKT;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,MAAM,GAAG,GAAG,EAChB,EAAE,EAAE,MAAM,GAAG,GAAG,EAChB,SAAS,UAAQ,GAChB,OAAO,CAKT;AAmED,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,EACjB,IAAI,GAAE,OAAO,GAAG,MAAgB,GAC/B,KAAK,CA2HP"}
@@ -1,8 +1,43 @@
1
+ /**
2
+ * Parse a CSS color string into an {@link Rgb}.
3
+ *
4
+ * Accepts 6-digit hex (`#rrggbb`), 3-digit shorthand hex (`#rgb`, expanded by
5
+ * doubling each nibble), and functional `rgb()` / `rgba()` notation (commas or
6
+ * spaces, integer or percentage channels). Named colors and other formats are
7
+ * not supported and yield `{ r: NaN, g: NaN, b: NaN }`; callers that accept
8
+ * arbitrary input should validate before relying on the result.
9
+ *
10
+ * The leading `#` is optional for hex input.
11
+ */
1
12
  export function hexToRgb(hex) {
2
- const cleanHex = hex.replace('#', '');
3
- const r = parseInt(cleanHex.substring(0, 2), 16);
4
- const g = parseInt(cleanHex.substring(2, 4), 16);
5
- const b = parseInt(cleanHex.substring(4, 6), 16);
13
+ const input = hex.trim();
14
+ // Functional rgb()/rgba() notation, e.g. rgb(0, 122, 255) or rgb(0 122 255 / .5).
15
+ const rgbMatch = input.match(/^rgba?\(([^)]+)\)$/i);
16
+ if (rgbMatch) {
17
+ const channels = rgbMatch[1]
18
+ .split(/[\s,/]+/)
19
+ .filter(Boolean)
20
+ .slice(0, 3);
21
+ const toChannel = (part) => {
22
+ if (part.endsWith('%')) {
23
+ return Math.round((Number.parseFloat(part) / 100) * 255);
24
+ }
25
+ return Number.parseInt(part, 10);
26
+ };
27
+ const [r, g, b] = channels.map(toChannel);
28
+ return { r, g, b };
29
+ }
30
+ const cleanHex = input.replace('#', '');
31
+ // 3-digit shorthand hex: #abc -> #aabbcc.
32
+ if (cleanHex.length === 3) {
33
+ const r = Number.parseInt(cleanHex[0] + cleanHex[0], 16);
34
+ const g = Number.parseInt(cleanHex[1] + cleanHex[1], 16);
35
+ const b = Number.parseInt(cleanHex[2] + cleanHex[2], 16);
36
+ return { r, g, b };
37
+ }
38
+ const r = Number.parseInt(cleanHex.substring(0, 2), 16);
39
+ const g = Number.parseInt(cleanHex.substring(2, 4), 16);
40
+ const b = Number.parseInt(cleanHex.substring(4, 6), 16);
6
41
  return { r, g, b };
7
42
  }
8
43
  export function rgbToHex(r, g, b) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/smrt-ui",
3
- "version": "0.30.0",
3
+ "version": "0.31.1",
4
4
  "description": "Domain-agnostic Svelte 5 UI runtime for SMRT: primitives, i18n client, theme system, and module UI registry",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -108,7 +108,7 @@
108
108
  },
109
109
  "dependencies": {
110
110
  "esm-env": "^1.2.2",
111
- "@happyvertical/smrt-types": "0.30.0"
111
+ "@happyvertical/smrt-types": "0.31.1"
112
112
  },
113
113
  "peerDependencies": {
114
114
  "svelte": "^5.18.2"