@happyvertical/smrt-ui 0.55.16 → 0.55.18

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 (166) hide show
  1. package/AGENTS.md +14 -0
  2. package/README.md +75 -0
  3. package/dist/components/calendar/Calendar.svelte +59 -56
  4. package/dist/components/calendar/CalendarView.svelte +80 -77
  5. package/dist/components/calendar/DayView.svelte +39 -36
  6. package/dist/components/chat/MessageBubble.svelte +26 -23
  7. package/dist/components/chat/ReactionPicker.svelte +12 -9
  8. package/dist/components/chat/TypingIndicator.svelte +12 -9
  9. package/dist/components/data/CollectionList.svelte +11 -8
  10. package/dist/components/data/CollectionToolbar.svelte +5 -2
  11. package/dist/components/data/DataTable.svelte +74 -59
  12. package/dist/components/data/ListSortSelect.svelte +8 -5
  13. package/dist/components/data/SortableHeader.svelte +10 -7
  14. package/dist/components/display/ConfidenceBadge.svelte +11 -8
  15. package/dist/components/display/CurrencyDisplay.svelte +7 -4
  16. package/dist/components/display/DateDisplay.svelte +6 -3
  17. package/dist/components/display/Icon.svelte +4 -1
  18. package/dist/components/display/StatusBadge.svelte +9 -6
  19. package/dist/components/feedback/Alert.svelte +12 -8
  20. package/dist/components/feedback/ConfirmDialog.svelte +13 -10
  21. package/dist/components/feedback/LoadingOverlay.svelte +37 -34
  22. package/dist/components/feedback/Meter.svelte +4 -1
  23. package/dist/components/feedback/Modal.svelte +26 -21
  24. package/dist/components/feedback/PhoneSheet.svelte +6 -3
  25. package/dist/components/feedback/Progress.svelte +8 -5
  26. package/dist/components/feedback/ProgressBar.svelte +13 -10
  27. package/dist/components/feedback/Spinner.svelte +4 -1
  28. package/dist/components/feedback/ToastViewport.svelte +18 -15
  29. package/dist/components/feedback/WorkingStrip.svelte +6 -3
  30. package/dist/components/forms/CameraCapture.svelte +8 -5
  31. package/dist/components/forms/Checkbox.svelte +11 -8
  32. package/dist/components/forms/Combobox.svelte +5 -2
  33. package/dist/components/forms/DatePicker.svelte +7 -2
  34. package/dist/components/forms/DictationButton.svelte +17 -14
  35. package/dist/components/forms/DictationStatus.svelte +12 -9
  36. package/dist/components/forms/ErrorSummary.svelte +4 -1
  37. package/dist/components/forms/FieldLabel.svelte +9 -6
  38. package/dist/components/forms/Fieldset.svelte +4 -1
  39. package/dist/components/forms/FilePicker.svelte +5 -2
  40. package/dist/components/forms/Form.svelte +1 -1
  41. package/dist/components/forms/FormActionBar.svelte +4 -1
  42. package/dist/components/forms/FormGroup.svelte +6 -3
  43. package/dist/components/forms/Input.svelte +22 -15
  44. package/dist/components/forms/InputGroup.svelte +6 -3
  45. package/dist/components/forms/Listbox.svelte +5 -2
  46. package/dist/components/forms/MultiSelect.svelte +4 -1
  47. package/dist/components/forms/Radio.svelte +9 -6
  48. package/dist/components/forms/RadioGroup.svelte +5 -2
  49. package/dist/components/forms/RangeSlider.svelte +5 -2
  50. package/dist/components/forms/RelationInput.svelte +7 -4
  51. package/dist/components/forms/SearchInput.svelte +7 -4
  52. package/dist/components/forms/SegmentedControl.svelte +6 -3
  53. package/dist/components/forms/Select.svelte +20 -13
  54. package/dist/components/forms/SignaturePad.svelte +8 -5
  55. package/dist/components/forms/Slider.svelte +5 -2
  56. package/dist/components/forms/StagedControlReview.svelte +15 -12
  57. package/dist/components/forms/Switch.svelte +8 -5
  58. package/dist/components/forms/TagsInput.svelte +5 -2
  59. package/dist/components/forms/Textarea.svelte +20 -13
  60. package/dist/components/forms/TimePicker.svelte +7 -2
  61. package/dist/components/forms/Toggle.svelte +14 -11
  62. package/dist/components/forms/ToggleButton.svelte +5 -2
  63. package/dist/components/layout/ActionGroup.svelte +4 -1
  64. package/dist/components/layout/Container.svelte +4 -1
  65. package/dist/components/layout/EmptyState.svelte +11 -8
  66. package/dist/components/layout/Footer.svelte +4 -1
  67. package/dist/components/layout/Grid.svelte +13 -10
  68. package/dist/components/layout/Header.svelte +4 -1
  69. package/dist/components/layout/Masthead.svelte +4 -1
  70. package/dist/components/layout/PageHeader.svelte +12 -9
  71. package/dist/components/layout/PageLayout.svelte +3 -0
  72. package/dist/components/layout/SummaryCard.svelte +9 -6
  73. package/dist/components/memberships/MembershipCard.svelte +5 -2
  74. package/dist/components/memberships/MembershipList.svelte +5 -2
  75. package/dist/components/nav/FilterChips.svelte +13 -10
  76. package/dist/components/nav/Tabs.svelte +75 -41
  77. package/dist/components/permissions/PermissionPicker.svelte +5 -2
  78. package/dist/components/roles/RoleBadge.svelte +8 -5
  79. package/dist/components/roles/RoleSelector.svelte +22 -19
  80. package/dist/components/theme/ThemeProvider.svelte +7 -4
  81. package/dist/components/ui/Accordion.svelte +4 -1
  82. package/dist/components/ui/AccordionItem.svelte +5 -2
  83. package/dist/components/ui/Avatar.svelte +10 -7
  84. package/dist/components/ui/Badge.svelte +6 -2
  85. package/dist/components/ui/Button.svelte +49 -37
  86. package/dist/components/ui/Card.svelte +18 -14
  87. package/dist/components/ui/Chip.svelte +41 -14
  88. package/dist/components/ui/Disclosure.svelte +5 -2
  89. package/dist/components/ui/Dropdown.svelte +33 -25
  90. package/dist/components/ui/IconToggle.svelte +12 -9
  91. package/dist/components/ui/IconToggle.svelte.d.ts +1 -1
  92. package/dist/components/ui/IconToggle.svelte.d.ts.map +1 -1
  93. package/dist/components/ui/IconToggleGroup.svelte +8 -5
  94. package/dist/components/ui/Pagination.svelte +11 -8
  95. package/dist/components/ui/Popover.svelte +9 -6
  96. package/dist/components/ui/Skeleton.svelte +10 -7
  97. package/dist/components/ui/Tooltip.svelte +13 -9
  98. package/dist/components/ui/Tree.svelte +12 -9
  99. package/dist/styles/layers.css +8 -0
  100. package/dist/styles/layers.d.ts +9 -0
  101. package/dist/styles/layers.d.ts.map +1 -0
  102. package/dist/styles/layers.js +8 -0
  103. package/dist/styles/tokens.css +27 -69
  104. package/dist/styles/unstyled.css +65 -0
  105. package/dist/svelte/playground/BaseControlsPreview.svelte +3 -0
  106. package/dist/svelte/playground/CalendarPreview.svelte +5 -2
  107. package/dist/svelte/playground/CapturePreview.svelte +3 -0
  108. package/dist/svelte/playground/CodeSelectorsPreview.svelte +3 -0
  109. package/dist/svelte/playground/CollectionsPreview.svelte +3 -0
  110. package/dist/svelte/playground/DataTablePreview.svelte +3 -0
  111. package/dist/svelte/playground/FeedbackPreview.svelte +3 -0
  112. package/dist/svelte/playground/InteractiveControlsPreview.svelte +3 -0
  113. package/dist/svelte/playground/StylelessPreview.svelte +110 -0
  114. package/dist/svelte/playground/StylelessPreview.svelte.d.ts +4 -0
  115. package/dist/svelte/playground/StylelessPreview.svelte.d.ts.map +1 -0
  116. package/dist/svelte/playground.d.ts.map +1 -1
  117. package/dist/svelte/playground.js +10 -0
  118. package/dist/test-support/setup.js +9 -0
  119. package/dist/theme/ThemeProvider.svelte +3 -0
  120. package/dist/themes/CUSTOM_THEME_GUIDE.md +40 -0
  121. package/dist/themes/README.md +151 -0
  122. package/dist/themes/ThemeProvider.svelte +181 -12
  123. package/dist/themes/ThemeProvider.svelte.d.ts +7 -2
  124. package/dist/themes/ThemeProvider.svelte.d.ts.map +1 -1
  125. package/dist/themes/components/ColorSchemeToggle.svelte +17 -14
  126. package/dist/themes/components/ThemeSwitcher.svelte +14 -11
  127. package/dist/themes/create-theme.d.ts +22 -1
  128. package/dist/themes/create-theme.d.ts.map +1 -1
  129. package/dist/themes/create-theme.js +100 -9
  130. package/dist/themes/css-generator.d.ts.map +1 -1
  131. package/dist/themes/css-generator.js +20 -20
  132. package/dist/themes/derive.d.ts +31 -0
  133. package/dist/themes/derive.d.ts.map +1 -0
  134. package/dist/themes/derive.js +183 -0
  135. package/dist/themes/glass/index.d.ts.map +1 -1
  136. package/dist/themes/glass/index.js +127 -14
  137. package/dist/themes/happyvertical/index.d.ts.map +1 -1
  138. package/dist/themes/happyvertical/index.js +101 -20
  139. package/dist/themes/index.d.ts +2 -2
  140. package/dist/themes/index.d.ts.map +1 -1
  141. package/dist/themes/index.js +1 -1
  142. package/dist/themes/material/index.d.ts.map +1 -1
  143. package/dist/themes/material/index.js +129 -28
  144. package/dist/themes/required-tokens.generated.json +201 -0
  145. package/dist/themes/shared.d.ts +35 -0
  146. package/dist/themes/shared.d.ts.map +1 -1
  147. package/dist/themes/shared.js +32 -0
  148. package/dist/themes/smrt/index.d.ts +3 -0
  149. package/dist/themes/smrt/index.d.ts.map +1 -1
  150. package/dist/themes/smrt/index.js +108 -26
  151. package/dist/themes/studio/index.d.ts.map +1 -1
  152. package/dist/themes/studio/index.js +116 -40
  153. package/dist/themes/styles/base.css +229 -1
  154. package/dist/themes/styles/glass.css +107 -20
  155. package/dist/themes/styles/happyvertical.css +111 -15
  156. package/dist/themes/styles/material.css +127 -35
  157. package/dist/themes/styles/smrt.css +126 -31
  158. package/dist/themes/styles/studio.css +132 -37
  159. package/dist/themes/theme-script.d.ts +3 -1
  160. package/dist/themes/theme-script.d.ts.map +1 -1
  161. package/dist/themes/theme-script.js +7 -2
  162. package/dist/themes/types.d.ts +118 -6
  163. package/dist/themes/types.d.ts.map +1 -1
  164. package/dist/utils/theme/typography.d.ts +21 -21
  165. package/dist/utils/theme/typography.js +21 -21
  166. package/package.json +8 -2
@@ -1,11 +1,15 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
3
  import { onMount, untrack } from 'svelte';
4
+ import { SMRT_CASCADE_LAYERS_CSS } from '../styles/layers.js';
4
5
  import { setThemeContext, type ThemeContext } from './context.svelte.js';
6
+ import { derivePrimaryOverrides } from './create-theme.js';
5
7
  import { generateThemeVariables } from './css-generator.js';
6
8
  import { availablePresets, getTheme, isValidPreset } from './registry.js';
7
9
  import type {
8
10
  ColorScheme,
11
+ ContrastPreference,
12
+ ResolvedContrast,
9
13
  Theme,
10
14
  ThemeConfig,
11
15
  ThemePreset,
@@ -15,7 +19,12 @@ import { defaultThemeConfig } from './types.js';
15
19
 
16
20
  interface Props {
17
21
  /** Default control target density for descendants; individual controls may override. */
18
- 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;
19
28
  /** Theme preset to use */
20
29
  preset?: ThemePreset;
21
30
  /** Color scheme preference */
@@ -62,6 +71,7 @@ let {
62
71
  inlineVariables,
63
72
  children,
64
73
  density,
74
+ contrast = 'auto',
65
75
  }: Props = $props();
66
76
 
67
77
  // Internal state — seed from the props (which already default to
@@ -77,6 +87,8 @@ let config = $state<ThemeConfig>(
77
87
  colorScheme,
78
88
  primaryColor,
79
89
  borderRadius,
90
+ density,
91
+ contrast,
80
92
  overrides,
81
93
  persist,
82
94
  storageKey,
@@ -84,8 +96,33 @@ let config = $state<ThemeConfig>(
84
96
  );
85
97
 
86
98
  let systemPrefersDark = $state(false);
99
+ let systemContrastMore = $state(false);
100
+ let systemForcedColors = $state(false);
101
+ let isChangingScheme = $state(false);
87
102
  let mounted = $state(false);
88
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
+
89
126
  // Derived state
90
127
  const currentTheme = $derived<Theme>(getTheme(config.preset));
91
128
 
@@ -122,15 +159,34 @@ const styleString = $derived.by(() => {
122
159
  }
123
160
  }
124
161
 
125
- // 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.
126
174
  if (config.primaryColor) {
127
- declarations.push(`--smrt-color-primary: ${config.primaryColor}`);
128
- // 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
+ }
129
185
  }
130
186
 
131
- if (density !== undefined) {
187
+ if (config.density !== undefined) {
132
188
  declarations.push(
133
- `--smrt-control-target-min: ${density === 'touch' ? 'var(--smrt-touch-target-min, 48px)' : '0px'}`,
189
+ `--smrt-control-target-min: ${config.density === 'touch' ? 'var(--smrt-touch-target-min)' : '0px'}`,
134
190
  );
135
191
  }
136
192
 
@@ -148,6 +204,8 @@ const themeState = $derived<ThemeStateType>({
148
204
  colorScheme: config.colorScheme,
149
205
  resolvedScheme,
150
206
  isDark,
207
+ contrast: resolvedContrast,
208
+ isChangingScheme,
151
209
  config,
152
210
  theme: currentTheme,
153
211
  });
@@ -182,6 +240,10 @@ function persistConfig(): void {
182
240
  preset: config.preset,
183
241
  colorScheme: config.colorScheme,
184
242
  borderRadius: config.borderRadius,
243
+ ...(config.density ? { density: config.density } : {}),
244
+ ...(config.contrast && config.contrast !== 'auto'
245
+ ? { contrast: config.contrast }
246
+ : {}),
185
247
  });
186
248
  localStorage.setItem(config.storageKey!, data);
187
249
  } catch {
@@ -221,6 +283,15 @@ function loadPersistedConfig(): void {
221
283
  ) {
222
284
  config.borderRadius = persisted.borderRadius;
223
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
+ }
224
295
  }
225
296
  } catch {
226
297
  // Ignore storage errors (e.g., corrupted data, JSON parse errors)
@@ -252,6 +323,20 @@ onMount(() => {
252
323
  };
253
324
  mediaQuery.addEventListener('change', handler);
254
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
+
255
340
  // Load persisted preferences
256
341
  loadPersistedConfig();
257
342
 
@@ -259,6 +344,9 @@ onMount(() => {
259
344
 
260
345
  return () => {
261
346
  mediaQuery.removeEventListener('change', handler);
347
+ moreQuery?.removeEventListener?.('change', moreHandler);
348
+ forcedQuery?.removeEventListener?.('change', forcedHandler);
349
+ if (schemeTimer) clearTimeout(schemeTimer);
262
350
  };
263
351
  }
264
352
  });
@@ -277,9 +365,61 @@ $effect(() => {
277
365
 
278
366
  // Color scheme for browser UI
279
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');
280
374
  }
281
375
  });
282
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
+
283
423
  // Sync props to state — but only on actual prop changes. config is seeded
284
424
  // from the initial props above and onMount applies persisted preferences, so
285
425
  // re-asserting the initial prop values here would clobber the persisted
@@ -288,6 +428,9 @@ let prevPreset = untrack(() => preset);
288
428
  let prevColorScheme = untrack(() => colorScheme);
289
429
  let prevPrimaryColor = untrack(() => primaryColor);
290
430
  let prevBorderRadius = untrack(() => borderRadius);
431
+ let prevDensity = untrack(() => density);
432
+ let prevContrast = untrack(() => contrast);
433
+ let rootEl = $state<HTMLElement | undefined>();
291
434
 
292
435
  $effect(() => {
293
436
  if (preset !== prevPreset) {
@@ -327,6 +470,24 @@ $effect(() => {
327
470
  }
328
471
  });
329
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
+
330
491
  // Sync overrides prop to state
331
492
  $effect(() => {
332
493
  config.overrides = overrides;
@@ -341,7 +502,13 @@ $effect(() => {
341
502
  });
342
503
  </script>
343
504
 
505
+ <svelte:head>
506
+ <!-- Declares the smrt cascade layer order before any layered component rule (#3823). -->
507
+ <svelte:element this={'style'} id="smrt-cascade-layers">{SMRT_CASCADE_LAYERS_CSS}</svelte:element>
508
+ </svelte:head>
509
+
344
510
  <div
511
+ bind:this={rootEl}
345
512
  class="smrt-theme-root"
346
513
  class:dark={isDark}
347
514
  class:no-paint={!paintSurface}
@@ -349,11 +516,18 @@ $effect(() => {
349
516
  style={styleString || undefined}
350
517
  data-theme={config.preset}
351
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"
352
524
  >
353
525
  {@render children()}
354
526
  </div>
355
527
 
356
528
  <style>
529
+ @layer smrt.reset, smrt.structure, smrt.theme;
530
+ @layer smrt.theme {
357
531
  .smrt-theme-root {
358
532
  /* Ensure theme variables cascade properly */
359
533
  /* Note: Using block display instead of contents so styles apply correctly */
@@ -369,12 +543,6 @@ $effect(() => {
369
543
  font-family: inherit;
370
544
  }
371
545
 
372
- /* Glass theme specific base styles */
373
- :global(.smrt-theme-glass) {
374
- --smrt-glass-surface: var(--smrt-color-surface);
375
- --smrt-glass-border: var(--smrt-color-outline);
376
- }
377
-
378
546
  /* HappyVertical ("Day Shift") focus indicator (#2318).
379
547
  * The brand focus ring is part of the preset, so it has to travel on BOTH
380
548
  * delivery paths: this mirrors the identical rule in
@@ -385,4 +553,5 @@ $effect(() => {
385
553
  outline: 2px solid var(--smrt-color-primary);
386
554
  outline-offset: 2px;
387
555
  }
556
+ }
388
557
  </style>
@@ -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;AAKtC,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;AAsTD,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"}
@@ -44,7 +44,7 @@ const labels = {
44
44
  };
45
45
  </script>
46
46
 
47
- <div class="smrt-color-scheme-toggle {variant} {className}">
47
+ <div class="smrt-color-scheme-toggle {variant} {className}" data-smrt-component="ColorSchemeToggle">
48
48
  {#if variant === 'switch'}
49
49
  <button
50
50
  class="smrt-color-scheme-toggle__switch"
@@ -110,6 +110,8 @@ const labels = {
110
110
  </div>
111
111
 
112
112
  <style>
113
+ @layer smrt.reset, smrt.structure, smrt.theme;
114
+ @layer smrt.theme {
113
115
  .smrt-color-scheme-toggle {
114
116
  display: inline-flex;
115
117
  align-items: center;
@@ -120,9 +122,9 @@ const labels = {
120
122
  align-items: center;
121
123
  gap: var(--smrt-spacing-2, 0.5rem);
122
124
  padding: var(--smrt-spacing-2, 0.5rem) var(--smrt-spacing-3, 0.75rem);
123
- border: 1px solid var(--smrt-color-outline, #ccc);
124
- border-radius: var(--smrt-radius-full, 9999px);
125
- background: var(--smrt-color-surface-container-low, #f5f5f5);
125
+ border: 1px solid var(--smrt-color-outline);
126
+ border-radius: var(--smrt-radius-full);
127
+ background: var(--smrt-color-surface-container-low);
126
128
  color: var(--smrt-color-on-surface, inherit);
127
129
  font-size: var(--smrt-typography-body-medium-size, 0.875rem);
128
130
  cursor: pointer;
@@ -130,7 +132,7 @@ const labels = {
130
132
  }
131
133
 
132
134
  .smrt-color-scheme-toggle__switch:hover {
133
- background: var(--smrt-color-surface-container, #eee);
135
+ background: var(--smrt-color-surface-container);
134
136
  }
135
137
 
136
138
  .smrt-color-scheme-toggle__buttons {
@@ -143,9 +145,9 @@ const labels = {
143
145
  align-items: center;
144
146
  gap: var(--smrt-spacing-1, 0.25rem);
145
147
  padding: var(--smrt-spacing-1_5, 0.375rem) var(--smrt-spacing-3, 0.75rem);
146
- border: 1px solid var(--smrt-color-outline, #ccc);
148
+ border: 1px solid var(--smrt-color-outline);
147
149
  border-radius: var(--smrt-radius-md, 0.5rem);
148
- background: var(--smrt-color-surface-container-low, #f5f5f5);
150
+ background: var(--smrt-color-surface-container-low);
149
151
  color: var(--smrt-color-on-surface, inherit);
150
152
  font-size: var(--smrt-typography-body-medium-size, 0.875rem);
151
153
  cursor: pointer;
@@ -153,19 +155,19 @@ const labels = {
153
155
  }
154
156
 
155
157
  .smrt-color-scheme-toggle__button:hover {
156
- background: var(--smrt-color-surface-container, #eee);
158
+ background: var(--smrt-color-surface-container);
157
159
  }
158
160
 
159
161
  .smrt-color-scheme-toggle__button.active {
160
- background: var(--smrt-color-primary-container, #e3f2fd);
161
- color: var(--smrt-color-on-primary-container, #1565c0);
162
- border-color: var(--smrt-color-primary, #2196f3);
162
+ background: var(--smrt-color-primary-container);
163
+ color: var(--smrt-color-on-primary-container);
164
+ border-color: var(--smrt-color-primary);
163
165
  }
164
166
 
165
167
  .smrt-color-scheme-toggle__segmented {
166
168
  display: inline-flex;
167
- background: var(--smrt-color-surface-container-lowest, #fafafa);
168
- border: 1px solid var(--smrt-color-outline, #ccc);
169
+ background: var(--smrt-color-surface-container-lowest);
170
+ border: 1px solid var(--smrt-color-outline);
169
171
  border-radius: var(--smrt-radius-lg, 0.75rem);
170
172
  padding: var(--smrt-spacing-0_5, 0.125rem);
171
173
  }
@@ -178,7 +180,7 @@ const labels = {
178
180
  border: none;
179
181
  border-radius: var(--smrt-radius-md, 0.5rem);
180
182
  background: transparent;
181
- color: var(--smrt-color-on-surface-variant, #666);
183
+ color: var(--smrt-color-on-surface-variant);
182
184
  font-size: var(--smrt-typography-body-medium-size, 0.875rem);
183
185
  cursor: pointer;
184
186
  transition: all 200ms ease;
@@ -202,4 +204,5 @@ const labels = {
202
204
  .smrt-color-scheme-toggle__label {
203
205
  font-size: inherit;
204
206
  }
207
+ }
205
208
  </style>
@@ -43,7 +43,7 @@ const themeIcons: Record<ThemePreset, string> = {
43
43
  };
44
44
  </script>
45
45
 
46
- <div class="smrt-theme-switcher {variant} {className}">
46
+ <div class="smrt-theme-switcher {variant} {className}" data-smrt-component="ThemeSwitcher">
47
47
  {#if label}
48
48
  <span class="smrt-theme-switcher__label">{label}</span>
49
49
  {/if}
@@ -101,6 +101,8 @@ const themeIcons: Record<ThemePreset, string> = {
101
101
  </div>
102
102
 
103
103
  <style>
104
+ @layer smrt.reset, smrt.structure, smrt.theme;
105
+ @layer smrt.theme {
104
106
  .smrt-theme-switcher {
105
107
  display: flex;
106
108
  align-items: center;
@@ -116,7 +118,7 @@ const themeIcons: Record<ThemePreset, string> = {
116
118
  .smrt-theme-switcher__select {
117
119
  padding: var(--smrt-spacing-1_5, 0.375rem) var(--smrt-spacing-3, 0.75rem);
118
120
  border-radius: var(--smrt-radius-md, 0.5rem);
119
- border: 1px solid var(--smrt-color-outline, #ccc);
121
+ border: 1px solid var(--smrt-color-outline);
120
122
  background: var(--smrt-color-surface, white);
121
123
  color: var(--smrt-color-on-surface, inherit);
122
124
  font-size: var(--smrt-typography-body-medium-size, 0.875rem);
@@ -133,9 +135,9 @@ const themeIcons: Record<ThemePreset, string> = {
133
135
  align-items: center;
134
136
  gap: var(--smrt-spacing-1, 0.25rem);
135
137
  padding: var(--smrt-spacing-1_5, 0.375rem) var(--smrt-spacing-3, 0.75rem);
136
- border: 1px solid var(--smrt-color-outline, #ccc);
138
+ border: 1px solid var(--smrt-color-outline);
137
139
  border-radius: var(--smrt-radius-md, 0.5rem);
138
- background: var(--smrt-color-surface-container-low, #f5f5f5);
140
+ background: var(--smrt-color-surface-container-low);
139
141
  color: var(--smrt-color-on-surface, inherit);
140
142
  font-size: var(--smrt-typography-body-medium-size, 0.875rem);
141
143
  cursor: pointer;
@@ -143,19 +145,19 @@ const themeIcons: Record<ThemePreset, string> = {
143
145
  }
144
146
 
145
147
  .smrt-theme-switcher__button:hover {
146
- background: var(--smrt-color-surface-container, #eee);
148
+ background: var(--smrt-color-surface-container);
147
149
  }
148
150
 
149
151
  .smrt-theme-switcher__button.active {
150
- background: var(--smrt-color-primary-container, #e3f2fd);
151
- color: var(--smrt-color-on-primary-container, #1565c0);
152
- border-color: var(--smrt-color-primary, #2196f3);
152
+ background: var(--smrt-color-primary-container);
153
+ color: var(--smrt-color-on-primary-container);
154
+ border-color: var(--smrt-color-primary);
153
155
  }
154
156
 
155
157
  .smrt-theme-switcher__segmented {
156
158
  display: inline-flex;
157
- background: var(--smrt-color-surface-container-lowest, #fafafa);
158
- border: 1px solid var(--smrt-color-outline, #ccc);
159
+ background: var(--smrt-color-surface-container-lowest);
160
+ border: 1px solid var(--smrt-color-outline);
159
161
  border-radius: var(--smrt-radius-lg, 0.75rem);
160
162
  padding: var(--smrt-spacing-0_5, 0.125rem);
161
163
  }
@@ -168,7 +170,7 @@ const themeIcons: Record<ThemePreset, string> = {
168
170
  border: none;
169
171
  border-radius: var(--smrt-radius-md, 0.5rem);
170
172
  background: transparent;
171
- color: var(--smrt-color-on-surface-variant, #666);
173
+ color: var(--smrt-color-on-surface-variant);
172
174
  font-size: var(--smrt-typography-body-medium-size, 0.875rem);
173
175
  cursor: pointer;
174
176
  transition: all 200ms ease;
@@ -187,4 +189,5 @@ const themeIcons: Record<ThemePreset, string> = {
187
189
  .smrt-theme-switcher__icon {
188
190
  font-size: var(--smrt-typography-body-large-size, 1rem);
189
191
  }
192
+ }
190
193
  </style>
@@ -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;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"}
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"}