@happyvertical/smrt-ui 0.55.17 → 0.55.19

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 (90) hide show
  1. package/README.md +7 -0
  2. package/dist/components/calendar/CalendarView.svelte +2 -2
  3. package/dist/components/chat/ReactionPicker.svelte +2 -2
  4. package/dist/components/data/CollectionList.svelte +1 -1
  5. package/dist/components/data/CollectionToolbar.svelte +1 -1
  6. package/dist/components/data/DataTable.svelte +44 -32
  7. package/dist/components/data/SortableHeader.svelte +2 -2
  8. package/dist/components/feedback/Alert.svelte +4 -3
  9. package/dist/components/feedback/LoadingOverlay.svelte +2 -2
  10. package/dist/components/feedback/Modal.svelte +11 -9
  11. package/dist/components/feedback/PhoneSheet.svelte +2 -2
  12. package/dist/components/feedback/ToastViewport.svelte +1 -1
  13. package/dist/components/forms/CameraCapture.svelte +2 -2
  14. package/dist/components/forms/Checkbox.svelte +1 -1
  15. package/dist/components/forms/Combobox.svelte +1 -1
  16. package/dist/components/forms/FilePicker.svelte +1 -1
  17. package/dist/components/forms/Input.svelte +11 -7
  18. package/dist/components/forms/InputGroup.svelte +1 -1
  19. package/dist/components/forms/Listbox.svelte +1 -1
  20. package/dist/components/forms/Radio.svelte +1 -1
  21. package/dist/components/forms/RangeSlider.svelte +1 -1
  22. package/dist/components/forms/SearchInput.svelte +2 -2
  23. package/dist/components/forms/SegmentedControl.svelte +1 -1
  24. package/dist/components/forms/Select.svelte +11 -7
  25. package/dist/components/forms/SignaturePad.svelte +2 -2
  26. package/dist/components/forms/Slider.svelte +1 -1
  27. package/dist/components/forms/StagedControlReview.svelte +1 -1
  28. package/dist/components/forms/Switch.svelte +1 -1
  29. package/dist/components/forms/TagsInput.svelte +1 -1
  30. package/dist/components/forms/Textarea.svelte +10 -6
  31. package/dist/components/forms/Toggle.svelte +2 -2
  32. package/dist/components/forms/ToggleButton.svelte +1 -1
  33. package/dist/components/layout/PageHeader.svelte +2 -2
  34. package/dist/components/nav/Tabs.svelte +47 -17
  35. package/dist/components/permissions/PermissionPicker.svelte +1 -1
  36. package/dist/components/roles/RoleSelector.svelte +2 -2
  37. package/dist/components/ui/AccordionItem.svelte +1 -1
  38. package/dist/components/ui/Badge.svelte +2 -1
  39. package/dist/components/ui/Button.svelte +38 -29
  40. package/dist/components/ui/Card.svelte +11 -10
  41. package/dist/components/ui/Chip.svelte +31 -7
  42. package/dist/components/ui/Disclosure.svelte +1 -1
  43. package/dist/components/ui/Dropdown.svelte +20 -15
  44. package/dist/components/ui/IconToggle.svelte +2 -2
  45. package/dist/components/ui/Popover.svelte +4 -4
  46. package/dist/components/ui/Tooltip.svelte +1 -0
  47. package/dist/components/ui/Tree.svelte +2 -2
  48. package/dist/themes/CUSTOM_THEME_GUIDE.md +16 -1
  49. package/dist/themes/README.md +126 -0
  50. package/dist/themes/ThemeProvider.svelte +173 -13
  51. package/dist/themes/ThemeProvider.svelte.d.ts +7 -2
  52. package/dist/themes/ThemeProvider.svelte.d.ts.map +1 -1
  53. package/dist/themes/create-theme.d.ts +22 -1
  54. package/dist/themes/create-theme.d.ts.map +1 -1
  55. package/dist/themes/create-theme.js +93 -5
  56. package/dist/themes/css-generator.d.ts.map +1 -1
  57. package/dist/themes/css-generator.js +15 -16
  58. package/dist/themes/derive.d.ts +31 -0
  59. package/dist/themes/derive.d.ts.map +1 -0
  60. package/dist/themes/derive.js +183 -0
  61. package/dist/themes/glass/index.d.ts.map +1 -1
  62. package/dist/themes/glass/index.js +127 -14
  63. package/dist/themes/happyvertical/index.d.ts.map +1 -1
  64. package/dist/themes/happyvertical/index.js +101 -20
  65. package/dist/themes/index.d.ts +2 -2
  66. package/dist/themes/index.d.ts.map +1 -1
  67. package/dist/themes/index.js +1 -1
  68. package/dist/themes/material/index.d.ts.map +1 -1
  69. package/dist/themes/material/index.js +129 -28
  70. package/dist/themes/required-tokens.generated.json +31 -0
  71. package/dist/themes/shared.d.ts +35 -0
  72. package/dist/themes/shared.d.ts.map +1 -1
  73. package/dist/themes/shared.js +32 -0
  74. package/dist/themes/smrt/index.d.ts +3 -0
  75. package/dist/themes/smrt/index.d.ts.map +1 -1
  76. package/dist/themes/smrt/index.js +108 -26
  77. package/dist/themes/studio/index.d.ts.map +1 -1
  78. package/dist/themes/studio/index.js +116 -40
  79. package/dist/themes/styles/base.css +225 -0
  80. package/dist/themes/styles/glass.css +103 -19
  81. package/dist/themes/styles/happyvertical.css +104 -12
  82. package/dist/themes/styles/material.css +127 -35
  83. package/dist/themes/styles/smrt.css +122 -30
  84. package/dist/themes/styles/studio.css +128 -36
  85. package/dist/themes/theme-script.d.ts +3 -1
  86. package/dist/themes/theme-script.d.ts.map +1 -1
  87. package/dist/themes/theme-script.js +7 -2
  88. package/dist/themes/types.d.ts +118 -6
  89. package/dist/themes/types.d.ts.map +1 -1
  90. package/package.json +3 -3
@@ -114,14 +114,14 @@ $effect(() => {
114
114
  @layer smrt.reset, smrt.structure, smrt.theme;
115
115
  @layer smrt.theme {
116
116
  .popover { position: relative; display: inline-flex; }
117
- .popover__trigger { appearance: none; padding: var(--smrt-spacing-2) var(--smrt-spacing-3); border: 1px solid var(--smrt-color-outline); border-radius: var(--smrt-radius-medium); background: var(--smrt-color-surface); color: var(--smrt-color-on-surface); font: inherit; cursor: pointer; }
118
- .popover__trigger:focus-visible, .popover__panel:focus-visible { outline: 2px solid var(--smrt-color-primary); outline-offset: 2px; }
119
- .popover__trigger:disabled { opacity: .5; cursor: not-allowed; }
117
+ .popover__trigger { appearance: none; padding: var(--smrt-spacing-2) var(--smrt-spacing-3); border: var(--smrt-hairline-strong); border-radius: var(--smrt-control-radius); background: var(--smrt-surface-control); color: var(--smrt-color-on-surface); font: inherit; font-weight: var(--smrt-control-weight); cursor: pointer; }
118
+ .popover__trigger:focus-visible, .popover__panel:focus-visible { outline: var(--smrt-focus-ring-width) solid var(--smrt-focus-ring-color); outline-offset: var(--smrt-focus-ring-offset); }
119
+ .popover__trigger:disabled { opacity: var(--smrt-state-disabled); cursor: not-allowed; }
120
120
  /* Icon trigger: a 44px round glyph in the surrounding colour. Hover and open nudge the glyph; focus keeps the standard primary ring (a soft ring in the glyph's colour fell under 3:1 on ordinary surfaces). */
121
121
  .popover__trigger--icon { display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box; inline-size: 2.75rem; block-size: 2.75rem; padding: 0; border: none; border-radius: var(--smrt-radius-full); background: transparent; color: inherit; }
122
122
  .popover__trigger--icon:hover:not(:disabled), .popover__trigger--icon[aria-expanded='true'] { background: transparent; color: color-mix(in srgb, currentColor 80%, var(--smrt-color-on-surface)); }
123
123
  .popover__trigger--icon:focus-visible { background: transparent; }
124
- .popover__panel { position: absolute; z-index: var(--smrt-z-index-popover, 1000); box-sizing: border-box; inline-size: var(--smrt-popover-panel-width, auto); min-width: min(16rem, calc(100vw - 2rem)); max-width: calc(100vw - 2rem); padding: var(--smrt-popover-panel-padding, var(--smrt-spacing-4)); border: 1px solid var(--smrt-color-outline-variant); border-radius: var(--smrt-radius-large); background: var(--smrt-color-surface-container); color: var(--smrt-color-on-surface); box-shadow: var(--smrt-elevation-3); }
124
+ .popover__panel { position: absolute; z-index: var(--smrt-z-index-popover, 1000); box-sizing: border-box; inline-size: var(--smrt-popover-panel-width, auto); min-width: min(16rem, calc(100vw - 2rem)); max-width: calc(100vw - 2rem); padding: var(--smrt-popover-panel-padding, var(--smrt-spacing-4)); border: var(--smrt-hairline); border-radius: var(--smrt-radius-large); background: var(--smrt-glass-surface); backdrop-filter: blur(var(--smrt-glass-blur)) saturate(var(--smrt-glass-saturation)); color: var(--smrt-color-on-surface); box-shadow: var(--smrt-inset-highlight), var(--smrt-elevation-3); }
125
125
  .popover__panel--bottom-start { top: calc(100% + var(--smrt-spacing-1)); left: 0; }
126
126
  .popover__panel--bottom-end { top: calc(100% + var(--smrt-spacing-1)); right: 0; }
127
127
  .popover__panel--top-start { bottom: calc(100% + var(--smrt-spacing-1)); left: 0; }
@@ -88,6 +88,7 @@ function onKeydown(event: KeyboardEvent) {
88
88
  color: var(--smrt-color-inverse-on-surface);
89
89
  font-size: var(--smrt-typography-body-small-size, 0.75rem);
90
90
  line-height: var(--smrt-typography-body-small-line-height, 1.4);
91
+ box-shadow: var(--smrt-elevation-3);
91
92
  pointer-events: none;
92
93
  }
93
94
 
@@ -181,8 +181,8 @@ function onItemKeydown(event: KeyboardEvent, index: number, item: FlatNode) {
181
181
  background: var(--smrt-color-surface-container-high);
182
182
  }
183
183
  .tree__item:focus-visible {
184
- outline: 2px solid var(--smrt-color-primary);
185
- outline-offset: -2px;
184
+ outline: var(--smrt-focus-ring-width) solid var(--smrt-focus-ring-color);
185
+ outline-offset: calc(var(--smrt-focus-ring-width) * -1);
186
186
  }
187
187
  .tree__item.selected {
188
188
  background: var(--smrt-color-primary-container);
@@ -330,19 +330,34 @@ Every theme (presets and `createTheme()` output) must define these names. Genera
330
330
  `scripts/generate-smrt-required-tokens.mjs`; do not edit by hand. Component-private
331
331
  `--smrt-<component>-*` knobs are excluded.
332
332
 
333
- - **color** (40): `--smrt-color-background`, `--smrt-color-error`, `--smrt-color-error-container`, `--smrt-color-inverse-on-surface`, `--smrt-color-inverse-surface`, `--smrt-color-on-background`, `--smrt-color-on-error`, `--smrt-color-on-error-container`, `--smrt-color-on-primary`, `--smrt-color-on-primary-container`, `--smrt-color-on-secondary`, `--smrt-color-on-secondary-container`, `--smrt-color-on-success-container`, `--smrt-color-on-surface`, `--smrt-color-on-surface-variant`, `--smrt-color-on-tertiary`, `--smrt-color-on-tertiary-container`, `--smrt-color-on-warning-container`, `--smrt-color-outline`, `--smrt-color-outline-variant`, `--smrt-color-primary`, `--smrt-color-primary-container`, `--smrt-color-scrim`, `--smrt-color-secondary`, `--smrt-color-secondary-container`, `--smrt-color-shadow`, `--smrt-color-success`, `--smrt-color-success-container`, `--smrt-color-surface`, `--smrt-color-surface-container`, `--smrt-color-surface-container-high`, `--smrt-color-surface-container-highest`, `--smrt-color-surface-container-low`, `--smrt-color-surface-container-lowest`, `--smrt-color-surface-dim`, `--smrt-color-surface-variant`, `--smrt-color-tertiary`, `--smrt-color-tertiary-container`, `--smrt-color-warning`, `--smrt-color-warning-container`
333
+ - **color** (42): `--smrt-color-background`, `--smrt-color-error`, `--smrt-color-error-container`, `--smrt-color-info`, `--smrt-color-info-container`, `--smrt-color-inverse-on-surface`, `--smrt-color-inverse-surface`, `--smrt-color-on-background`, `--smrt-color-on-error`, `--smrt-color-on-error-container`, `--smrt-color-on-primary`, `--smrt-color-on-primary-container`, `--smrt-color-on-secondary`, `--smrt-color-on-secondary-container`, `--smrt-color-on-success-container`, `--smrt-color-on-surface`, `--smrt-color-on-surface-variant`, `--smrt-color-on-tertiary`, `--smrt-color-on-tertiary-container`, `--smrt-color-on-warning-container`, `--smrt-color-outline`, `--smrt-color-outline-variant`, `--smrt-color-primary`, `--smrt-color-primary-container`, `--smrt-color-scrim`, `--smrt-color-secondary`, `--smrt-color-secondary-container`, `--smrt-color-shadow`, `--smrt-color-success`, `--smrt-color-success-container`, `--smrt-color-surface`, `--smrt-color-surface-container`, `--smrt-color-surface-container-high`, `--smrt-color-surface-container-highest`, `--smrt-color-surface-container-low`, `--smrt-color-surface-container-lowest`, `--smrt-color-surface-dim`, `--smrt-color-surface-variant`, `--smrt-color-tertiary`, `--smrt-color-tertiary-container`, `--smrt-color-warning`, `--smrt-color-warning-container`
334
+ - **control** (4): `--smrt-control-height`, `--smrt-control-height-small`, `--smrt-control-radius`, `--smrt-control-radius-small`
334
335
  - **duration** (6): `--smrt-duration-fast`, `--smrt-duration-medium1`, `--smrt-duration-normal`, `--smrt-duration-short2`, `--smrt-duration-short3`, `--smrt-duration-short4`
335
336
  - **easing** (3): `--smrt-easing-emphasized`, `--smrt-easing-emphasized-decelerate`, `--smrt-easing-standard`
336
337
  - **elevation** (5): `--smrt-elevation-1`, `--smrt-elevation-2`, `--smrt-elevation-3`, `--smrt-elevation-4`, `--smrt-elevation-5`
338
+ - **focus** (3): `--smrt-focus-ring-color`, `--smrt-focus-ring-offset`, `--smrt-focus-ring-width`
337
339
  - **font** (2): `--smrt-font-family`, `--smrt-font-family-mono`
340
+ - **glass** (4): `--smrt-glass-blur`, `--smrt-glass-saturation`, `--smrt-glass-surface`, `--smrt-glass-surface-strong`
341
+ - **hairline** (4): `--smrt-hairline`, `--smrt-hairline-color`, `--smrt-hairline-strong`, `--smrt-hairline-strong-color`
342
+ - **inset** (1): `--smrt-inset-highlight`
338
343
  - **radius** (11): `--smrt-radius-2xl`, `--smrt-radius-extra-large`, `--smrt-radius-extra-small`, `--smrt-radius-full`, `--smrt-radius-large`, `--smrt-radius-lg`, `--smrt-radius-md`, `--smrt-radius-medium`, `--smrt-radius-sm`, `--smrt-radius-small`, `--smrt-radius-xl`
344
+ - **scrollbar** (2): `--smrt-scrollbar-thumb`, `--smrt-scrollbar-track`
339
345
  - **spacing** (19): `--smrt-spacing-0_5`, `--smrt-spacing-1`, `--smrt-spacing-10`, `--smrt-spacing-11`, `--smrt-spacing-12`, `--smrt-spacing-1_5`, `--smrt-spacing-2`, `--smrt-spacing-2xl`, `--smrt-spacing-3`, `--smrt-spacing-3xl`, `--smrt-spacing-4`, `--smrt-spacing-5`, `--smrt-spacing-6`, `--smrt-spacing-8`, `--smrt-spacing-lg`, `--smrt-spacing-md`, `--smrt-spacing-sm`, `--smrt-spacing-xl`, `--smrt-spacing-xs`
346
+ - **state** (5): `--smrt-state-disabled`, `--smrt-state-dragged`, `--smrt-state-hover`, `--smrt-state-pressed`, `--smrt-state-selected`
347
+ - **surface** (6): `--smrt-surface-control`, `--smrt-surface-ground`, `--smrt-surface-overlay`, `--smrt-surface-panel`, `--smrt-surface-raised`, `--smrt-surface-sunken`
340
348
  - **touch** (1): `--smrt-touch-target-min`
341
349
  - **typography** (56): `--smrt-typography-body-large-font`, `--smrt-typography-body-large-line-height`, `--smrt-typography-body-large-size`, `--smrt-typography-body-large-tracking`, `--smrt-typography-body-medium-font`, `--smrt-typography-body-medium-line-height`, `--smrt-typography-body-medium-size`, `--smrt-typography-body-medium-tracking`, `--smrt-typography-body-medium-weight`, `--smrt-typography-body-small-font`, `--smrt-typography-body-small-line-height`, `--smrt-typography-body-small-size`, `--smrt-typography-body-small-weight`, `--smrt-typography-display-medium-line-height`, `--smrt-typography-display-medium-size`, `--smrt-typography-display-small-size`, `--smrt-typography-headline-large-line-height`, `--smrt-typography-headline-large-size`, `--smrt-typography-headline-large-weight`, `--smrt-typography-headline-medium-line-height`, `--smrt-typography-headline-medium-size`, `--smrt-typography-headline-medium-weight`, `--smrt-typography-headline-small-font`, `--smrt-typography-headline-small-line-height`, `--smrt-typography-headline-small-size`, `--smrt-typography-headline-small-weight`, `--smrt-typography-label-large-font`, `--smrt-typography-label-large-size`, `--smrt-typography-label-large-tracking`, `--smrt-typography-label-large-weight`, `--smrt-typography-label-medium-font`, `--smrt-typography-label-medium-size`, `--smrt-typography-label-medium-tracking`, `--smrt-typography-label-medium-weight`, `--smrt-typography-label-small-font`, `--smrt-typography-label-small-line-height`, `--smrt-typography-label-small-size`, `--smrt-typography-label-small-tracking`, `--smrt-typography-label-small-weight`, `--smrt-typography-title-large-font`, `--smrt-typography-title-large-line-height`, `--smrt-typography-title-large-size`, `--smrt-typography-title-medium-font`, `--smrt-typography-title-medium-font-family`, `--smrt-typography-title-medium-line-height`, `--smrt-typography-title-medium-size`, `--smrt-typography-title-medium-tracking`, `--smrt-typography-title-medium-weight`, `--smrt-typography-title-small-font`, `--smrt-typography-title-small-size`, `--smrt-typography-title-small-tracking`, `--smrt-typography-title-small-weight`, `--smrt-typography-weight-bold`, `--smrt-typography-weight-medium`, `--smrt-typography-weight-normal`, `--smrt-typography-weight-semibold`
342
350
  - **z** (7): `--smrt-z-index-dialog`, `--smrt-z-index-dropdown`, `--smrt-z-index-modal`, `--smrt-z-index-overlay`, `--smrt-z-index-sticky`, `--smrt-z-index-toast`, `--smrt-z-index-tooltip`
343
351
 
344
352
  <!-- smrt-required-tokens:end -->
345
353
 
354
+ ## Runtime overrides
355
+
356
+ `ThemeProvider` layers user choices over a theme without touching the theme itself:
357
+ `borderRadius` (control radius tokens), `primaryColor` (full derived primary set, also
358
+ available as `derivePrimaryOverrides(color, scheme)`), `density` and `contrast`. See the
359
+ ThemeProvider props table in the README for the exact behaviour.
360
+
346
361
  ## Best Practices
347
362
 
348
363
  1. **Extend a base theme**: Start with `extend: 'material'` or another base to get sensible defaults
@@ -305,8 +305,29 @@ import '@happyvertical/smrt-ui/themes/styles/happyvertical.css';
305
305
  | `borderRadius` | `'none' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| 'full'` | `'md'` | Border radius scale |
306
306
  | `overrides` | `Record<string, string>` | `{}` | Custom CSS variable overrides |
307
307
  | `persist` | `boolean` | `true` | Persist to localStorage |
308
+ | `density` | `'comfortable' \| 'touch'` | - | Default control target size (`--smrt-control-target-min`); persisted |
309
+ | `contrast` | `'auto' \| 'standard' \| 'more'` | `'auto'` | `auto` follows `prefers-contrast: more`; persisted when explicit |
308
310
  | `storageKey` | `string` | `'smrt-theme'` | localStorage key |
309
311
 
312
+ Behaviour notes (state exposed on context: `contrast`, `isChangingScheme`):
313
+
314
+ - `borderRadius` other than `md` is emitted inline as `--smrt-control-radius` /
315
+ `--smrt-control-radius-small` (one scale step smaller for the small radius);
316
+ `md` leaves the preset's own control radius in place. The generic
317
+ `--smrt-radius-*` scale is not changed.
318
+ - `primaryColor` emits the whole derived set for the resolved scheme through
319
+ `derivePrimaryOverrides(color, scheme)` (also exported): `color-primary`, `on-primary`
320
+ (best of white/black), `primary-container`, `on-primary-container`, `inverse-primary`,
321
+ and the focus ring, accent, caret and selection tokens.
322
+ - After the resolved scheme changes post-mount, `data-smrt-theme-changing` is set on
323
+ the root and `<html>` for one `--smrt-duration-scheme`, so styles can crossfade.
324
+ Never on first paint; skipped under `prefers-reduced-motion: reduce`.
325
+ - Contrast: `data-contrast="standard|more|forced"` on `<html>` and the root;
326
+ `forced-colors: active` also sets `data-forced-colors`. Contract for components in
327
+ forced-colors mode: do not paint over system colours, hairlines become `CanvasText`
328
+ and focus rings `Highlight` (component side: #3867 follow-up).
329
+ - `themeScript()` stamps `data-contrast` and (when persisted) `data-density` pre-paint.
330
+
310
331
  ## Theme Switching
311
332
 
312
333
  Use the context to switch themes programmatically:
@@ -630,6 +651,111 @@ on any custom property outside `--smrt-*` in `packages/*/src`. The one-time rewr
630
651
  was `scripts/codemod-smrt-tokens.mjs`; it is idempotent, so re-run it after a rebase
631
652
  instead of hand-merging renamed names.
632
653
 
654
+ ### Premium baseline token vocabulary
655
+
656
+ Every preset and every `createTheme()` result defines all of these in both schemes
657
+ (sections of `Theme` in `types.ts`; defaults derived in `derive.ts`; emitted by
658
+ `generateThemeVariables`). Themes and presets define them; **components only consume
659
+ them**. Do not redeclare a theme token on a component root (CSS or `style:`): the
660
+ required-tokens scanner (`scripts/smrt-token-scan-lib.mjs`) treats any `--smrt-*` name
661
+ a component defines on its own root as component-private and drops it from the required
662
+ list repo-wide. A per-variant override uses a component-prefixed name that falls back to
663
+ the theme token, for example `background: color-mix(in srgb, currentColor
664
+ calc(var(--smrt-button-state-hover, var(--smrt-state-hover)) * 100%), transparent)`.
665
+
666
+ | Token | Meaning | Consumed by |
667
+ | --- | --- | --- |
668
+ | `--smrt-surface-ground` | Page ground | `body`/shell background |
669
+ | `--smrt-surface-panel` | Cards, sidebars, panels on the ground | `background` of Card, sidebars, header, footer |
670
+ | `--smrt-surface-control` | Input and control fill | `background` of Input, Select, Textarea, Button (outlined/tonal) |
671
+ | `--smrt-surface-raised` | Menus, popovers, dialogs above panels | `background` of overlays |
672
+ | `--smrt-surface-sunken` | Wells and recessed regions | `background` of tables headers, code, inset areas |
673
+ | `--smrt-surface-overlay` | Modal scrim | `background` of Modal/Drawer backdrops |
674
+ | `--smrt-hairline-color` / `-strong-color` | Quiet and strong edge colours | `border-color`, `outline-color`, dividers |
675
+ | `--smrt-hairline` / `--smrt-hairline-strong` | `1px solid` edge shorthands | `border`, `border-top` etc. on panels (quiet) and controls (strong) |
676
+ | `--smrt-inset-highlight` | Top inner light line (`none` on light) | `box-shadow` (layer with an elevation) on raised dark surfaces |
677
+ | `--smrt-state-{hover,pressed,selected,focus,dragged,disabled}` | Unitless state-layer opacities, 0 to 1 | Mix percentage of a state overlay (`calc(var(--smrt-state-hover) * 100%)`); `disabled` as `opacity` |
678
+ | `--smrt-shadow-color` | `rgb(r g b)` shadow base, per scheme | `box-shadow` colour: `rgb(from var(--smrt-shadow-color) r g b / 0.2)` |
679
+ | `--smrt-elevation-0..5` | Existing shadow scale | `box-shadow` |
680
+ | `--smrt-control-radius` / `-radius-small` | Control corner radii | `border-radius` of controls |
681
+ | `--smrt-control-height` / `-height-small` | Control heights | `min-height`/`block-size` of controls |
682
+ | `--smrt-control-weight` | Control label weight | `font-weight` of buttons and inputs |
683
+ | `--smrt-control-border` | Control edge shorthand (`var(--smrt-hairline-strong)`) | `border` of controls |
684
+ | `--smrt-control-target-min` | Density-driven minimum hit size (set by ThemeProvider `density`) | `min-height`/`min-width` of interactive controls |
685
+ | `--smrt-control-hit-size` | Hit area of small icon controls | `inline-size`/`block-size` of icon buttons |
686
+ | `--smrt-focus-ring-{color,width,offset}` | Focus indicator | `outline: var(--smrt-focus-ring-width) solid var(--smrt-focus-ring-color)`, `outline-offset` on `:focus-visible` |
687
+ | `--smrt-accent-color` | Native control accent | `accent-color` (checkbox, radio, range, date pickers) |
688
+ | `--smrt-selection-{background,color}` | Text selection | `::selection` |
689
+ | `--smrt-caret-color` | Text caret | `caret-color` |
690
+ | `--smrt-scrollbar-{thumb,track}` | Scrollbar colours | `scrollbar-color: thumb track` |
691
+ | `--smrt-glass-blur` / `-saturation` | Backdrop filter amounts (`0px`/`100%` outside Glass) | `backdrop-filter: blur(var(--smrt-glass-blur)) saturate(var(--smrt-glass-saturation))` |
692
+ | `--smrt-glass-surface` / `-surface-strong` | Translucent (Glass) or opaque surface fills; opacity is part of the colour | `background` of floating chrome and overlays |
693
+ | `--smrt-glass-border` | Glass edge colour | `border-color` of glass surfaces |
694
+ | `--smrt-duration-scheme` | Scheme crossfade duration | `transition-duration` when the colour scheme changes |
695
+ | `--smrt-font-feature-settings` | OpenType features (`"tnum" 1, "cv11" 1` on Inter presets) | `font-feature-settings` on the root and numeric cells |
696
+
697
+ #### Semantic info colour
698
+
699
+ `ColorPalette` carries an info family beside success, warning and error:
700
+ `--smrt-color-info`, `--smrt-color-on-info`, `--smrt-color-info-container` and
701
+ `--smrt-color-on-info-container`. Presets without a distinct information hue define
702
+ it as their tertiary family (Material, Glass, Studio, Day Shift); **smrt** uses a
703
+ steel blue (`#2c6a96` light, `#6fa8d6` dark) so Information no longer shares amber
704
+ with Warning. `createTheme()` follows a supplied `tertiary` unless `info` is also
705
+ given. Components that show information status (for example `Alert` `variant="info"`)
706
+ should consume these, not tertiary.
707
+
708
+ #### Preset identities on the baseline
709
+
710
+ | Preset | Dark ladder (ground / panel / control / raised) | Depth | State layers (hover / pressed / selected) | Controls |
711
+ | --- | --- | --- | --- | --- |
712
+ | Material | `#121316` / `#1b1c20` / `#26282c` / `#34363a` (M3 tonal, no borders needed; control border is transparent) | contact + ambient; dark adds the inset highlight | .08 / .12 / .12 | radius md / sm, 2.75rem |
713
+ | Glass | `#0b0b0f` ground; panel 72% and popover 88% frosted glass, input a recessed 28% black well; blur 20px, saturation 180% | softest, widest ambient | .10 / .14 / .14 | radius lg / md, 2.75rem |
714
+ | Studio | `#131416` / `#131416` / `#1c1d20` / `#292a2d` (flat, crisp hairlines, panel equals ground) | elevation 1 is the hairline; tight shadows | .06 / .10 / .10 | radius 0.375rem / sm, 2.5rem |
715
+ | smrt | `#0b0c0e` / `#121417` / `#181b1f` / `#1d2126` (instrument dark, amber signal, steel-blue info) | elevation 1 is the hairline; calm two-layer shadows (no far-spread) | .10 / .16 / .14 | radius sm, 2.5rem |
716
+ | Day Shift | enamel ground, faceplate panel, control a hair lighter, raised = faceplate; hairlines are the bezel | elevation 1 is the bezel hairline; minimal | .06 / .10 / .10 | radius md (6px) / sm, 2.5rem |
717
+
718
+ Elevation 2 to 5 are two-layer shadows built with `shadow()` in `shared.ts`: a contact
719
+ layer (`0 1px 2px`) plus an ambient layer (`0 Npx Mpx` with negative spread), both
720
+ `color-mix`ed from `--smrt-shadow-color`. Dark schemes prepend
721
+ `var(--smrt-inset-highlight)`. Weight is 500 on every preset. Native chrome follows the
722
+ baseline: accent and caret are primary, selection is the primary container, and the
723
+ scrollbar thumb is the strong hairline over a transparent track.
724
+
725
+ `--smrt-glass-border-opacity` and `--smrt-glass-background-opacity` are deprecated: still emitted (from `GlassEffects.borderOpacity`/`backgroundOpacity`, `1` when unset) for one release and removed in the next minor; the former
726
+ tokens, and the ThemeProvider-local `--smrt-glass-surface`/`--smrt-glass-border`,
727
+ were folded into the glass surface colours above.
728
+
729
+ ### Typography
730
+
731
+ `themes/styles/base.css` (import once at the app entry) maps headings to type roles
732
+ inside `@layer smrt.structure`, scoped under `body` and `.smrt-theme-root`, so presets'
733
+ display faces reach plain markup. Hosts restyle with unlayered CSS.
734
+
735
+ | Element | Role |
736
+ | --- | --- |
737
+ | `h1` | `display-small` |
738
+ | `h2` | `headline-large` |
739
+ | `h3` | `headline-medium` |
740
+ | `h4` | `headline-small` |
741
+ | `h5` | `title-large` |
742
+ | `h6` | `title-medium` |
743
+
744
+ Each heading takes `font-family`, `font-size`, `line-height`, `font-weight` and
745
+ `letter-spacing` from the role's typography tokens (for example `--smrt-typography-headline-large-size`), and a `margin-block` from
746
+ `--smrt-spacing-*` (first children lose the top margin). Utilities
747
+ `.smrt-{display,headline,title,body,label}-{large,medium,small}` apply the same five
748
+ properties to any element.
749
+
750
+ Tabular figures: the root sets `font-feature-settings: var(--smrt-font-feature-settings)`,
751
+ and `table`, `time`, `[data-smrt-component="DataTable"]`, `[data-smrt-component="Badge"]`
752
+ and `.smrt-tabular` set `font-variant-numeric: tabular-nums`.
753
+
754
+ Preset rhythm: display tracking tightens with size (about -0.02em at display, -0.01em at
755
+ headline, 0 at title), labels weigh 500 (Day Shift keeps its documented bold B612 labels),
756
+ body is 400, and display plus headline roles use the preset's display face where it has one
757
+ (smrt: Space Grotesk; Day Shift: its documented stack).
758
+
633
759
  ## Advanced Usage
634
760
 
635
761
  ### Build-Time Theming
@@ -3,10 +3,13 @@ import type { Snippet } from 'svelte';
3
3
  import { onMount, untrack } from 'svelte';
4
4
  import { SMRT_CASCADE_LAYERS_CSS } from '../styles/layers.js';
5
5
  import { setThemeContext, type ThemeContext } from './context.svelte.js';
6
+ import { derivePrimaryOverrides } from './create-theme.js';
6
7
  import { generateThemeVariables } from './css-generator.js';
7
8
  import { availablePresets, getTheme, isValidPreset } from './registry.js';
8
9
  import type {
9
10
  ColorScheme,
11
+ ContrastPreference,
12
+ ResolvedContrast,
10
13
  Theme,
11
14
  ThemeConfig,
12
15
  ThemePreset,
@@ -16,7 +19,12 @@ import { defaultThemeConfig } from './types.js';
16
19
 
17
20
  interface Props {
18
21
  /** Default control target density for descendants; individual controls may override. */
19
- density?: 'comfortable' | 'touch';
22
+ density?: ThemeConfig['density'];
23
+ /**
24
+ * Contrast preference. `auto` (default) follows `prefers-contrast: more`;
25
+ * `forced-colors: active` always wins. Persisted when set explicitly.
26
+ */
27
+ contrast?: ContrastPreference;
20
28
  /** Theme preset to use */
21
29
  preset?: ThemePreset;
22
30
  /** Color scheme preference */
@@ -63,6 +71,7 @@ let {
63
71
  inlineVariables,
64
72
  children,
65
73
  density,
74
+ contrast = 'auto',
66
75
  }: Props = $props();
67
76
 
68
77
  // Internal state — seed from the props (which already default to
@@ -78,6 +87,8 @@ let config = $state<ThemeConfig>(
78
87
  colorScheme,
79
88
  primaryColor,
80
89
  borderRadius,
90
+ density,
91
+ contrast,
81
92
  overrides,
82
93
  persist,
83
94
  storageKey,
@@ -85,8 +96,33 @@ let config = $state<ThemeConfig>(
85
96
  );
86
97
 
87
98
  let systemPrefersDark = $state(false);
99
+ let systemContrastMore = $state(false);
100
+ let systemForcedColors = $state(false);
101
+ let isChangingScheme = $state(false);
88
102
  let mounted = $state(false);
89
103
 
104
+ /** Control radius per `borderRadius` choice: [control, small]. `md` is the preset default. */
105
+ const CONTROL_RADIUS: Record<
106
+ NonNullable<ThemeConfig['borderRadius']>,
107
+ [string, string]
108
+ > = {
109
+ none: ['var(--smrt-radius-none)', 'var(--smrt-radius-none)'],
110
+ sm: ['var(--smrt-radius-sm)', 'var(--smrt-radius-sm)'],
111
+ md: ['var(--smrt-radius-md)', 'var(--smrt-radius-sm)'],
112
+ lg: ['var(--smrt-radius-lg)', 'var(--smrt-radius-md)'],
113
+ xl: ['var(--smrt-radius-xl)', 'var(--smrt-radius-lg)'],
114
+ full: ['var(--smrt-radius-full)', 'var(--smrt-radius-full)'],
115
+ };
116
+
117
+ const resolvedContrast = $derived<ResolvedContrast>(
118
+ systemForcedColors
119
+ ? 'forced'
120
+ : config.contrast === 'more' ||
121
+ (config.contrast !== 'standard' && systemContrastMore)
122
+ ? 'more'
123
+ : 'standard',
124
+ );
125
+
90
126
  // Derived state
91
127
  const currentTheme = $derived<Theme>(getTheme(config.preset));
92
128
 
@@ -123,15 +159,34 @@ const styleString = $derived.by(() => {
123
159
  }
124
160
  }
125
161
 
126
- // Apply primary color override
162
+ // Border radius: only a non-default choice is emitted, so `md` keeps the
163
+ // preset's own control radius from the static stylesheet.
164
+ if (config.borderRadius && config.borderRadius !== 'md') {
165
+ const [control, small] = CONTROL_RADIUS[config.borderRadius];
166
+ declarations.push(
167
+ `--smrt-control-radius: ${control}`,
168
+ `--smrt-control-radius-small: ${small}`,
169
+ );
170
+ }
171
+
172
+ // Primary colour override: the whole derived set for the resolved scheme so
173
+ // on-primary / container / focus / accent never stay mismatched.
127
174
  if (config.primaryColor) {
128
- declarations.push(`--smrt-color-primary: ${config.primaryColor}`);
129
- // Recalculate dependent colors would go here in a full implementation
175
+ try {
176
+ for (const [key, value] of Object.entries(
177
+ derivePrimaryOverrides(config.primaryColor, resolvedScheme),
178
+ )) {
179
+ declarations.push(`${key}: ${value}`);
180
+ }
181
+ } catch {
182
+ // Unparseable colour (e.g. a named colour): keep the single variable.
183
+ declarations.push(`--smrt-color-primary: ${config.primaryColor}`);
184
+ }
130
185
  }
131
186
 
132
- if (density !== undefined) {
187
+ if (config.density !== undefined) {
133
188
  declarations.push(
134
- `--smrt-control-target-min: ${density === 'touch' ? 'var(--smrt-touch-target-min)' : '0px'}`,
189
+ `--smrt-control-target-min: ${config.density === 'touch' ? 'var(--smrt-touch-target-min)' : '0px'}`,
135
190
  );
136
191
  }
137
192
 
@@ -149,6 +204,8 @@ const themeState = $derived<ThemeStateType>({
149
204
  colorScheme: config.colorScheme,
150
205
  resolvedScheme,
151
206
  isDark,
207
+ contrast: resolvedContrast,
208
+ isChangingScheme,
152
209
  config,
153
210
  theme: currentTheme,
154
211
  });
@@ -183,6 +240,10 @@ function persistConfig(): void {
183
240
  preset: config.preset,
184
241
  colorScheme: config.colorScheme,
185
242
  borderRadius: config.borderRadius,
243
+ ...(config.density ? { density: config.density } : {}),
244
+ ...(config.contrast && config.contrast !== 'auto'
245
+ ? { contrast: config.contrast }
246
+ : {}),
186
247
  });
187
248
  localStorage.setItem(config.storageKey!, data);
188
249
  } catch {
@@ -222,6 +283,15 @@ function loadPersistedConfig(): void {
222
283
  ) {
223
284
  config.borderRadius = persisted.borderRadius;
224
285
  }
286
+ if (
287
+ persisted.density === 'comfortable' ||
288
+ persisted.density === 'touch'
289
+ ) {
290
+ config.density = persisted.density;
291
+ }
292
+ if (persisted.contrast === 'standard' || persisted.contrast === 'more') {
293
+ config.contrast = persisted.contrast;
294
+ }
225
295
  }
226
296
  } catch {
227
297
  // Ignore storage errors (e.g., corrupted data, JSON parse errors)
@@ -253,6 +323,20 @@ onMount(() => {
253
323
  };
254
324
  mediaQuery.addEventListener('change', handler);
255
325
 
326
+ // Contrast preferences (guarded: matchMedia stubs may omit these queries)
327
+ const moreQuery = window.matchMedia('(prefers-contrast: more)');
328
+ const forcedQuery = window.matchMedia('(forced-colors: active)');
329
+ systemContrastMore = !!moreQuery?.matches;
330
+ systemForcedColors = !!forcedQuery?.matches;
331
+ const moreHandler = (e: MediaQueryListEvent) => {
332
+ systemContrastMore = e.matches;
333
+ };
334
+ const forcedHandler = (e: MediaQueryListEvent) => {
335
+ systemForcedColors = e.matches;
336
+ };
337
+ moreQuery?.addEventListener?.('change', moreHandler);
338
+ forcedQuery?.addEventListener?.('change', forcedHandler);
339
+
256
340
  // Load persisted preferences
257
341
  loadPersistedConfig();
258
342
 
@@ -260,6 +344,9 @@ onMount(() => {
260
344
 
261
345
  return () => {
262
346
  mediaQuery.removeEventListener('change', handler);
347
+ moreQuery?.removeEventListener?.('change', moreHandler);
348
+ forcedQuery?.removeEventListener?.('change', forcedHandler);
349
+ if (schemeTimer) clearTimeout(schemeTimer);
263
350
  };
264
351
  }
265
352
  });
@@ -278,9 +365,61 @@ $effect(() => {
278
365
 
279
366
  // Color scheme for browser UI
280
367
  html.style.colorScheme = resolvedScheme;
368
+
369
+ // Contrast / density contract (themeScript stamps the same pre-paint)
370
+ html.setAttribute('data-contrast', resolvedContrast);
371
+ html.toggleAttribute('data-forced-colors', resolvedContrast === 'forced');
372
+ if (config.density) html.setAttribute('data-density', config.density);
373
+ else html.removeAttribute('data-density');
281
374
  }
282
375
  });
283
376
 
377
+ // Scheme crossfade: flag the root for one --smrt-duration-scheme after the
378
+ // resolved scheme CHANGES post-mount (never on first paint, never under
379
+ // prefers-reduced-motion) so base.css/components can transition colours.
380
+ const FALLBACK_SCHEME_MS = 250;
381
+ let schemeTimer: ReturnType<typeof setTimeout> | undefined;
382
+ let lastScheme: 'light' | 'dark' | undefined;
383
+
384
+ function parseDurationMs(value: string): number | undefined {
385
+ const m = /^\s*(\d*\.?\d+)(ms|s)\s*$/.exec(value);
386
+ if (!m) return undefined;
387
+ return Number(m[1]) * (m[2] === 's' ? 1000 : 1);
388
+ }
389
+
390
+ function schemeDurationMs(): number {
391
+ const style = getComputedStyle(rootEl ?? document.documentElement);
392
+ return (
393
+ parseDurationMs(style.getPropertyValue('--smrt-duration-scheme')) ??
394
+ parseDurationMs(style.getPropertyValue('--smrt-duration-medium1')) ??
395
+ FALLBACK_SCHEME_MS
396
+ );
397
+ }
398
+
399
+ $effect(() => {
400
+ const scheme = resolvedScheme;
401
+ if (!mounted || typeof document === 'undefined') return;
402
+ const previous = lastScheme;
403
+ lastScheme = scheme;
404
+ if (previous === undefined || previous === scheme) return;
405
+ if (window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches) return;
406
+ const ms = untrack(schemeDurationMs);
407
+ isChangingScheme = true;
408
+ if (schemeTimer) clearTimeout(schemeTimer);
409
+ schemeTimer = setTimeout(() => {
410
+ isChangingScheme = false;
411
+ schemeTimer = undefined;
412
+ }, ms);
413
+ });
414
+
415
+ $effect(() => {
416
+ if (typeof document === 'undefined' || !mounted) return;
417
+ document.documentElement.toggleAttribute(
418
+ 'data-smrt-theme-changing',
419
+ isChangingScheme,
420
+ );
421
+ });
422
+
284
423
  // Sync props to state — but only on actual prop changes. config is seeded
285
424
  // from the initial props above and onMount applies persisted preferences, so
286
425
  // re-asserting the initial prop values here would clobber the persisted
@@ -289,6 +428,9 @@ let prevPreset = untrack(() => preset);
289
428
  let prevColorScheme = untrack(() => colorScheme);
290
429
  let prevPrimaryColor = untrack(() => primaryColor);
291
430
  let prevBorderRadius = untrack(() => borderRadius);
431
+ let prevDensity = untrack(() => density);
432
+ let prevContrast = untrack(() => contrast);
433
+ let rootEl = $state<HTMLElement | undefined>();
292
434
 
293
435
  $effect(() => {
294
436
  if (preset !== prevPreset) {
@@ -328,6 +470,24 @@ $effect(() => {
328
470
  }
329
471
  });
330
472
 
473
+ $effect(() => {
474
+ if (density !== prevDensity) {
475
+ prevDensity = density;
476
+ untrack(() => {
477
+ config.density = density;
478
+ });
479
+ }
480
+ });
481
+
482
+ $effect(() => {
483
+ if (contrast !== prevContrast) {
484
+ prevContrast = contrast;
485
+ untrack(() => {
486
+ config.contrast = contrast;
487
+ });
488
+ }
489
+ });
490
+
331
491
  // Sync overrides prop to state
332
492
  $effect(() => {
333
493
  config.overrides = overrides;
@@ -348,13 +508,19 @@ $effect(() => {
348
508
  </svelte:head>
349
509
 
350
510
  <div
511
+ bind:this={rootEl}
351
512
  class="smrt-theme-root"
352
513
  class:dark={isDark}
353
514
  class:no-paint={!paintSurface}
354
515
  class:smrt-theme-glass={config.preset === 'glass'}
355
516
  style={styleString || undefined}
356
517
  data-theme={config.preset}
357
- data-color-scheme={resolvedScheme} data-smrt-component="ThemeProvider"
518
+ data-color-scheme={resolvedScheme}
519
+ data-contrast={resolvedContrast}
520
+ data-forced-colors={resolvedContrast === 'forced' ? '' : undefined}
521
+ data-density={config.density}
522
+ data-smrt-theme-changing={isChangingScheme ? '' : undefined}
523
+ data-smrt-component="ThemeProvider"
358
524
  >
359
525
  {@render children()}
360
526
  </div>
@@ -377,12 +543,6 @@ $effect(() => {
377
543
  font-family: inherit;
378
544
  }
379
545
 
380
- /* Glass theme specific base styles */
381
- :global(.smrt-theme-glass) {
382
- --smrt-glass-surface: var(--smrt-color-surface);
383
- --smrt-glass-border: var(--smrt-color-outline);
384
- }
385
-
386
546
  /* HappyVertical ("Day Shift") focus indicator (#2318).
387
547
  * The brand focus ring is part of the preset, so it has to travel on BOTH
388
548
  * delivery paths: this mirrors the identical rule in
@@ -1,8 +1,13 @@
1
1
  import type { Snippet } from 'svelte';
2
- import type { ColorScheme, ThemeConfig, ThemePreset } from './types.js';
2
+ import type { ColorScheme, ContrastPreference, ThemeConfig, ThemePreset } from './types.js';
3
3
  interface Props {
4
4
  /** Default control target density for descendants; individual controls may override. */
5
- density?: 'comfortable' | 'touch';
5
+ density?: ThemeConfig['density'];
6
+ /**
7
+ * Contrast preference. `auto` (default) follows `prefers-contrast: more`;
8
+ * `forced-colors: active` always wins. Persisted when set explicitly.
9
+ */
10
+ contrast?: ContrastPreference;
6
11
  /** Theme preset to use */
7
12
  preset?: ThemePreset;
8
13
  /** Color scheme preference */
@@ -1 +1 @@
1
- {"version":3,"file":"ThemeProvider.svelte.d.ts","sourceRoot":"","sources":["../../src/themes/ThemeProvider.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAMtC,OAAO,KAAK,EACV,WAAW,EAEX,WAAW,EACX,WAAW,EAEZ,MAAM,YAAY,CAAC;AAIpB,UAAU,KAAK;IACb,wFAAwF;IACxF,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC;IAClC,0BAA0B;IAC1B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,8BAA8B;IAC9B,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,oCAAoC;IACpC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,+BAA+B;IAC/B,YAAY,CAAC,EAAE,WAAW,CAAC,cAAc,CAAC,CAAC;IAC3C,oCAAoC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC;;;;OAIG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,0CAA0C;IAC1C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kCAAkC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;;;OAQG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,cAAc;IACd,QAAQ,EAAE,OAAO,CAAC;CACnB;AA4TD,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"ThemeProvider.svelte.d.ts","sourceRoot":"","sources":["../../src/themes/ThemeProvider.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAOtC,OAAO,KAAK,EACV,WAAW,EACX,kBAAkB,EAGlB,WAAW,EACX,WAAW,EAEZ,MAAM,YAAY,CAAC;AAIpB,UAAU,KAAK;IACb,wFAAwF;IACxF,OAAO,CAAC,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC;IACjC;;;OAGG;IACH,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,0BAA0B;IAC1B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,8BAA8B;IAC9B,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,oCAAoC;IACpC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,+BAA+B;IAC/B,YAAY,CAAC,EAAE,WAAW,CAAC,cAAc,CAAC,CAAC;IAC3C,oCAAoC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC;;;;OAIG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,0CAA0C;IAC1C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kCAAkC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;;;OAQG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,cAAc;IACd,QAAQ,EAAE,OAAO,CAAC;CACnB;AAqdD,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
@@ -22,8 +22,18 @@ export interface CreateThemeOptions {
22
22
  elevation?: Partial<ElevationScale>;
23
23
  /** Custom easing curves (optional) */
24
24
  easing?: Partial<EasingScale>;
25
- /** Glass effects (optional) */
25
+ /** Glass effects (optional; defaults to opaque) */
26
26
  glass?: GlassEffects;
27
+ /** Optional overrides for the premium-baseline token families */
28
+ surfaces?: Theme['surfaces'];
29
+ hairlines?: Theme['hairlines'];
30
+ states?: Theme['states'];
31
+ shadowColor?: Theme['shadowColor'];
32
+ controls?: Theme['controls'];
33
+ focus?: Theme['focus'];
34
+ native?: Theme['native'];
35
+ schemeDuration?: string;
36
+ fontFeatureSettings?: string;
27
37
  /** Font family */
28
38
  fontFamily?: string;
29
39
  /** Base theme to extend (copies all tokens as starting point) */
@@ -96,4 +106,15 @@ export declare function isThemeRegistered(id: string): Promise<boolean>;
96
106
  * ```
97
107
  */
98
108
  export declare function createThemeFromColor(primaryColor: string, id: string, name: string, options?: Omit<CreateThemeOptions, 'id' | 'name' | 'light'>): Theme;
109
+ /**
110
+ * Derive the primary-dependent token overrides for a user-chosen primary colour
111
+ * in one resolved scheme, using the same derivation as `createThemeFromColor`
112
+ * (light: the colour itself with a ~12.5% container; dark: the tonal primary
113
+ * pair from `generateTheme`). `on-primary` is the higher-contrast of white and
114
+ * black on the final primary so an override never leaves it mismatched. Focus,
115
+ * accent, caret and selection tokens follow the primary via `var()`.
116
+ *
117
+ * Returns `--smrt-*` declarations; throws on an unparseable colour.
118
+ */
119
+ export declare function derivePrimaryOverrides(color: string, scheme: 'light' | 'dark'): Record<string, string>;
99
120
  //# sourceMappingURL=create-theme.d.ts.map
@@ -1 +1 @@
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;AAsPD;;;;;;;;;;;;;;;;;;;;;;;;;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"}
1
+ {"version":3,"file":"create-theme.d.ts","sourceRoot":"","sources":["../../src/themes/create-theme.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAoBH,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,mDAAmD;IACnD,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,iEAAiE;IACjE,QAAQ,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IAC7B,SAAS,CAAC,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;IAC/B,MAAM,CAAC,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;IACzB,WAAW,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;IACnC,QAAQ,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IAC7B,KAAK,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IACvB,MAAM,CAAC,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kBAAkB;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAkRD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,KAAK,CA8E9D;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;AAID;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,OAAO,GAAG,MAAM,GACvB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAkCxB"}