@happyvertical/smrt-ui 0.55.16 → 0.55.17

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 (138) hide show
  1. package/AGENTS.md +14 -0
  2. package/README.md +68 -0
  3. package/dist/components/calendar/Calendar.svelte +59 -56
  4. package/dist/components/calendar/CalendarView.svelte +79 -76
  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 +10 -7
  8. package/dist/components/chat/TypingIndicator.svelte +12 -9
  9. package/dist/components/data/CollectionList.svelte +10 -7
  10. package/dist/components/data/CollectionToolbar.svelte +5 -2
  11. package/dist/components/data/DataTable.svelte +51 -48
  12. package/dist/components/data/ListSortSelect.svelte +8 -5
  13. package/dist/components/data/SortableHeader.svelte +9 -6
  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 +9 -6
  20. package/dist/components/feedback/ConfirmDialog.svelte +13 -10
  21. package/dist/components/feedback/LoadingOverlay.svelte +35 -32
  22. package/dist/components/feedback/Meter.svelte +4 -1
  23. package/dist/components/feedback/Modal.svelte +20 -17
  24. package/dist/components/feedback/PhoneSheet.svelte +4 -1
  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 +17 -14
  29. package/dist/components/feedback/WorkingStrip.svelte +6 -3
  30. package/dist/components/forms/CameraCapture.svelte +6 -3
  31. package/dist/components/forms/Checkbox.svelte +10 -7
  32. package/dist/components/forms/Combobox.svelte +4 -1
  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 +4 -1
  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 +14 -11
  44. package/dist/components/forms/InputGroup.svelte +5 -2
  45. package/dist/components/forms/Listbox.svelte +4 -1
  46. package/dist/components/forms/MultiSelect.svelte +4 -1
  47. package/dist/components/forms/Radio.svelte +8 -5
  48. package/dist/components/forms/RadioGroup.svelte +5 -2
  49. package/dist/components/forms/RangeSlider.svelte +4 -1
  50. package/dist/components/forms/RelationInput.svelte +7 -4
  51. package/dist/components/forms/SearchInput.svelte +5 -2
  52. package/dist/components/forms/SegmentedControl.svelte +5 -2
  53. package/dist/components/forms/Select.svelte +12 -9
  54. package/dist/components/forms/SignaturePad.svelte +6 -3
  55. package/dist/components/forms/Slider.svelte +4 -1
  56. package/dist/components/forms/StagedControlReview.svelte +15 -12
  57. package/dist/components/forms/Switch.svelte +7 -4
  58. package/dist/components/forms/TagsInput.svelte +4 -1
  59. package/dist/components/forms/Textarea.svelte +13 -10
  60. package/dist/components/forms/TimePicker.svelte +7 -2
  61. package/dist/components/forms/Toggle.svelte +13 -10
  62. package/dist/components/forms/ToggleButton.svelte +4 -1
  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 +10 -7
  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 +28 -24
  77. package/dist/components/permissions/PermissionPicker.svelte +4 -1
  78. package/dist/components/roles/RoleBadge.svelte +8 -5
  79. package/dist/components/roles/RoleSelector.svelte +21 -18
  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 +4 -1
  83. package/dist/components/ui/Avatar.svelte +10 -7
  84. package/dist/components/ui/Badge.svelte +4 -1
  85. package/dist/components/ui/Button.svelte +11 -8
  86. package/dist/components/ui/Card.svelte +7 -4
  87. package/dist/components/ui/Chip.svelte +12 -9
  88. package/dist/components/ui/Disclosure.svelte +4 -1
  89. package/dist/components/ui/Dropdown.svelte +17 -14
  90. package/dist/components/ui/IconToggle.svelte +10 -7
  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 +5 -2
  96. package/dist/components/ui/Skeleton.svelte +10 -7
  97. package/dist/components/ui/Tooltip.svelte +12 -9
  98. package/dist/components/ui/Tree.svelte +10 -7
  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 +25 -0
  121. package/dist/themes/README.md +25 -0
  122. package/dist/themes/ThemeProvider.svelte +11 -2
  123. package/dist/themes/ThemeProvider.svelte.d.ts.map +1 -1
  124. package/dist/themes/components/ColorSchemeToggle.svelte +17 -14
  125. package/dist/themes/components/ThemeSwitcher.svelte +14 -11
  126. package/dist/themes/create-theme.d.ts.map +1 -1
  127. package/dist/themes/create-theme.js +8 -5
  128. package/dist/themes/css-generator.d.ts.map +1 -1
  129. package/dist/themes/css-generator.js +5 -4
  130. package/dist/themes/required-tokens.generated.json +172 -0
  131. package/dist/themes/styles/base.css +4 -1
  132. package/dist/themes/styles/glass.css +4 -1
  133. package/dist/themes/styles/happyvertical.css +7 -3
  134. package/dist/themes/styles/smrt.css +4 -1
  135. package/dist/themes/styles/studio.css +4 -1
  136. package/dist/utils/theme/typography.d.ts +21 -21
  137. package/dist/utils/theme/typography.js +21 -21
  138. package/package.json +8 -2
@@ -89,7 +89,7 @@ function onkeydown(event: KeyboardEvent): void {
89
89
  tabindex="-1"
90
90
  data-escape-scope
91
91
  data-testid="phone-sheet"
92
- {onkeydown}
92
+ {onkeydown} data-smrt-component="PhoneSheet"
93
93
  >
94
94
  <header
95
95
  class="phone-sheet__header"
@@ -129,6 +129,8 @@ function onkeydown(event: KeyboardEvent): void {
129
129
  </div>
130
130
 
131
131
  <style>
132
+ @layer smrt.reset, smrt.structure, smrt.theme;
133
+ @layer smrt.theme {
132
134
  .phone-sheet {
133
135
  position: absolute;
134
136
  inset: 0;
@@ -221,4 +223,5 @@ function onkeydown(event: KeyboardEvent): void {
221
223
  transition: none;
222
224
  }
223
225
  }
226
+ }
224
227
  </style>
@@ -39,18 +39,20 @@ const displayValue = $derived(
39
39
  </script>
40
40
  {#if variant === 'circular'}
41
41
  <div class="progress progress--circular progress--{size} {className}" class:indeterminate role="progressbar" aria-label={label}
42
- aria-valuemin={indeterminate ? undefined : min} aria-valuemax={indeterminate ? undefined : max} aria-valuenow={value}>
43
- <svg viewBox="0 0 44 44" aria-hidden="true"><circle class="track" cx="22" cy="22" r="18"></circle><circle class="indicator" cx="22" cy="22" r="18" style={`--progress: ${normalized}`}></circle></svg>
42
+ aria-valuemin={indeterminate ? undefined : min} aria-valuemax={indeterminate ? undefined : max} aria-valuenow={value} data-smrt-component="Progress">
43
+ <svg viewBox="0 0 44 44" aria-hidden="true"><circle class="track" cx="22" cy="22" r="18"></circle><circle class="indicator" cx="22" cy="22" r="18" style={`--smrt-progress-value: ${normalized}`}></circle></svg>
44
44
  {#if showValue && !indeterminate}<span>{displayValue}</span>{/if}
45
45
  </div>
46
46
  {:else}
47
47
  <div class="progress progress--linear progress--{size} {className}" class:indeterminate role="progressbar" aria-label={label}
48
- aria-valuemin={indeterminate ? undefined : min} aria-valuemax={indeterminate ? undefined : max} aria-valuenow={value}>
48
+ aria-valuemin={indeterminate ? undefined : min} aria-valuemax={indeterminate ? undefined : max} aria-valuenow={value} data-smrt-component="Progress">
49
49
  <span class="track"><span class="indicator" style:width={indeterminate ? undefined : `${normalized}%`}></span></span>
50
50
  {#if showValue && !indeterminate}<span class="value">{displayValue}</span>{/if}
51
51
  </div>
52
52
  {/if}
53
53
  <style>
54
+ @layer smrt.reset, smrt.structure, smrt.theme;
55
+ @layer smrt.theme {
54
56
  .progress--linear { display: flex; align-items: center; gap: var(--smrt-spacing-2); width: 100%; }
55
57
  .progress--linear .track { position: relative; display: block; flex: 1; height: 4px; overflow: hidden; border-radius: var(--smrt-radius-full); background: var(--smrt-color-surface-container-highest); }
56
58
  .progress--linear.progress--sm .track { height: 2px; } .progress--linear.progress--lg .track { height: 8px; }
@@ -61,9 +63,10 @@ const displayValue = $derived(
61
63
  .progress--circular.progress--sm { width: 1.75rem; height: 1.75rem; } .progress--circular.progress--lg { width: 4rem; height: 4rem; }
62
64
  .progress--circular svg { width: 100%; height: 100%; transform: rotate(-90deg); }
63
65
  .progress--circular circle { fill: none; stroke-width: 4; } .progress--circular .track { stroke: var(--smrt-color-surface-container-highest); }
64
- .progress--circular .indicator { stroke: var(--smrt-color-primary); stroke-linecap: round; stroke-dasharray: 113.1; stroke-dashoffset: calc(113.1 - (113.1 * var(--progress)) / 100); transition: stroke-dashoffset var(--smrt-duration-short4); }
66
+ .progress--circular .indicator { stroke: var(--smrt-color-primary); stroke-linecap: round; stroke-dasharray: 113.1; stroke-dashoffset: calc(113.1 - (113.1 * var(--smrt-progress-value)) / 100); transition: stroke-dashoffset var(--smrt-duration-short4); }
65
67
  .progress--circular.indeterminate svg { animation: spin 1s linear infinite; } .progress--circular.indeterminate .indicator { stroke-dasharray: 70 113.1; }
66
68
  .progress--circular > span { position: absolute; font: var(--smrt-typography-label-small-font); }
67
69
  @keyframes linear-progress { from { left: -40%; } to { left: 105%; } } @keyframes spin { to { transform: rotate(270deg); } }
68
- @media (prefers-reduced-motion: reduce) { .indicator { transition: none !important; animation-duration: 2.5s !important; } }
70
+ @media (prefers-reduced-motion: reduce) { .progress--linear .indicator, .progress--circular .indicator { transition: none; } .progress--linear.indeterminate .indicator { animation-duration: 2.5s; } }
71
+ }
69
72
  </style>
@@ -76,7 +76,7 @@ const displayLabel = $derived.by(() => {
76
76
  });
77
77
  </script>
78
78
 
79
- <div class="progress-container" class:sm={size === 'sm'} class:lg={size === 'lg'}>
79
+ <div class="progress-container" class:sm={size === 'sm'} class:lg={size === 'lg'} data-smrt-component="ProgressBar">
80
80
  {#if showLabel || showValue}
81
81
  <div class="progress-header">
82
82
  <span class="progress-label">{displayLabel}</span>
@@ -102,6 +102,8 @@ const displayLabel = $derived.by(() => {
102
102
  </div>
103
103
 
104
104
  <style>
105
+ @layer smrt.reset, smrt.structure, smrt.theme;
106
+ @layer smrt.theme {
105
107
  .progress-container {
106
108
  width: 100%;
107
109
  }
@@ -110,12 +112,12 @@ const displayLabel = $derived.by(() => {
110
112
  display: flex;
111
113
  justify-content: space-between;
112
114
  align-items: center;
113
- margin-bottom: var(--smrt-spacing-2, 8px);
115
+ margin-bottom: var(--smrt-spacing-2);
114
116
  }
115
117
 
116
118
  .progress-label {
117
119
  font: var(--smrt-typography-label-large-font);
118
- color: var(--smrt-color-on-surface-variant, #43474e);
120
+ color: var(--smrt-color-on-surface-variant);
119
121
  }
120
122
 
121
123
  .sm .progress-label {
@@ -125,14 +127,14 @@ const displayLabel = $derived.by(() => {
125
127
  .over-badge {
126
128
  font: var(--smrt-typography-label-small-font);
127
129
  font-weight: var(--smrt-typography-weight-semibold, 600);
128
- color: var(--smrt-color-error, #ba1a1a);
130
+ color: var(--smrt-color-error);
129
131
  }
130
132
 
131
133
  .progress-track {
132
134
  width: 100%;
133
135
  height: 4px;
134
- background-color: var(--smrt-color-surface-container-highest, #e0e2ec);
135
- border-radius: var(--smrt-radius-sm, 4px);
136
+ background-color: var(--smrt-color-surface-container-highest);
137
+ border-radius: var(--smrt-radius-sm);
136
138
  overflow: hidden;
137
139
  }
138
140
 
@@ -142,7 +144,7 @@ const displayLabel = $derived.by(() => {
142
144
 
143
145
  .lg .progress-track {
144
146
  height: 8px;
145
- border-radius: var(--smrt-radius-sm, 4px);
147
+ border-radius: var(--smrt-radius-sm);
146
148
  }
147
149
 
148
150
  .progress-bar {
@@ -156,7 +158,8 @@ const displayLabel = $derived.by(() => {
156
158
  }
157
159
  }
158
160
 
159
- .color-primary { background-color: var(--smrt-color-primary, #005ac1); }
160
- .color-tertiary { background-color: var(--smrt-color-tertiary, #6b5778); }
161
- .color-error { background-color: var(--smrt-color-error, #ba1a1a); }
161
+ .color-primary { background-color: var(--smrt-color-primary); }
162
+ .color-tertiary { background-color: var(--smrt-color-tertiary); }
163
+ .color-error { background-color: var(--smrt-color-error); }
164
+ }
162
165
  </style>
@@ -9,12 +9,15 @@ export interface Props {
9
9
  }
10
10
  let { label = 'Loading', size = 'md', class: className = '' }: Props = $props();
11
11
  </script>
12
- <span class="spinner spinner--{size} {className}" role="status"><span class="spinner__ring" aria-hidden="true"></span><span class="visually-hidden">{label}</span></span>
12
+ <span class="spinner spinner--{size} {className}" role="status" data-smrt-component="Spinner"><span class="spinner__ring" aria-hidden="true"></span><span class="visually-hidden">{label}</span></span>
13
13
  <style>
14
+ @layer smrt.reset, smrt.structure, smrt.theme;
15
+ @layer smrt.theme {
14
16
  .spinner { display: inline-flex; }
15
17
  .spinner__ring { width: 1.5rem; height: 1.5rem; border: 2px solid var(--smrt-color-surface-container-highest); border-top-color: var(--smrt-color-primary); border-radius: 50%; animation: spin .75s linear infinite; }
16
18
  .spinner--sm .spinner__ring { width: 1rem; height: 1rem; } .spinner--lg .spinner__ring { width: 2.5rem; height: 2.5rem; border-width: 3px; }
17
19
  .visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
18
20
  @keyframes spin { to { transform: rotate(360deg); } }
19
21
  @media (prefers-reduced-motion: reduce) { .spinner__ring { animation-duration: 1.5s; } }
22
+ }
20
23
  </style>
@@ -161,12 +161,12 @@ async function runAction(toast: Toast) {
161
161
  </script>
162
162
 
163
163
  <section use:followModal class="viewport viewport--{position} {className}" aria-label="Notifications" aria-live="polite"
164
- style:--toast-inset={inset}
165
- style:--toast-top={bounds ? `${bounds.top}px` : undefined}
166
- style:--toast-right={bounds ? `${bounds.right}px` : undefined}
167
- style:--toast-bottom={bounds ? `${bounds.bottom}px` : undefined}
168
- style:--toast-left={bounds ? `${bounds.left}px` : undefined}
169
- style:--toast-region-width={bounds ? `${bounds.width}px` : undefined}
164
+ style:--smrt-toast-viewport-inset={inset}
165
+ style:--smrt-toast-viewport-top={bounds ? `${bounds.top}px` : undefined}
166
+ style:--smrt-toast-viewport-right={bounds ? `${bounds.right}px` : undefined}
167
+ style:--smrt-toast-viewport-bottom={bounds ? `${bounds.bottom}px` : undefined}
168
+ style:--smrt-toast-viewport-left={bounds ? `${bounds.left}px` : undefined}
169
+ style:--smrt-toast-viewport-region-width={bounds ? `${bounds.width}px` : undefined} data-smrt-component="ToastViewport"
170
170
  >
171
171
  {#each toasts as toast (toast.id)}
172
172
  <div role="group" aria-label={toast.title ?? 'Notification'}
@@ -186,14 +186,17 @@ async function runAction(toast: Toast) {
186
186
  </section>
187
187
 
188
188
  <style>
189
- .viewport { position: fixed; z-index: var(--smrt-z-index-toast, 1200); display: grid; width: min(24rem, calc(var(--toast-region-width, 100vw) - 2 * var(--toast-inset))); gap: var(--smrt-spacing-2); pointer-events: none; }
189
+ @layer smrt.reset, smrt.structure, smrt.theme;
190
+ @layer smrt.theme {
191
+ .viewport { position: fixed; z-index: var(--smrt-z-index-toast, 1200); display: grid; width: min(24rem, calc(var(--smrt-toast-viewport-region-width, 100vw) - 2 * var(--smrt-toast-viewport-inset))); gap: var(--smrt-spacing-2); pointer-events: none; }
190
192
  .viewport :global(.alert) { pointer-events: auto; box-shadow: var(--smrt-elevation-3); }
191
- .viewport--top-start { top: calc(var(--toast-top, 0px) + var(--toast-inset)); left: calc(var(--toast-left, 0px) + var(--toast-inset)); }
192
- .viewport--top-end { top: calc(var(--toast-top, 0px) + var(--toast-inset)); right: calc(var(--toast-right, 0px) + var(--toast-inset)); }
193
- .viewport--bottom-start { bottom: calc(var(--toast-bottom, 0px) + var(--toast-inset)); left: calc(var(--toast-left, 0px) + var(--toast-inset)); }
194
- .viewport--bottom-end { right: calc(var(--toast-right, 0px) + var(--toast-inset)); bottom: calc(var(--toast-bottom, 0px) + var(--toast-inset)); }
195
- .viewport--top-center, .viewport--bottom-center { left: calc(var(--toast-left, 0px) + var(--toast-region-width, 100vw) / 2); transform: translateX(-50%); }
196
- .viewport--top-center { top: calc(var(--toast-top, 0px) + var(--toast-inset)); }
197
- .viewport--bottom-center { bottom: calc(var(--toast-bottom, 0px) + var(--toast-inset)); }
193
+ .viewport--top-start { top: calc(var(--smrt-toast-viewport-top, 0px) + var(--smrt-toast-viewport-inset)); left: calc(var(--smrt-toast-viewport-left, 0px) + var(--smrt-toast-viewport-inset)); }
194
+ .viewport--top-end { top: calc(var(--smrt-toast-viewport-top, 0px) + var(--smrt-toast-viewport-inset)); right: calc(var(--smrt-toast-viewport-right, 0px) + var(--smrt-toast-viewport-inset)); }
195
+ .viewport--bottom-start { bottom: calc(var(--smrt-toast-viewport-bottom, 0px) + var(--smrt-toast-viewport-inset)); left: calc(var(--smrt-toast-viewport-left, 0px) + var(--smrt-toast-viewport-inset)); }
196
+ .viewport--bottom-end { right: calc(var(--smrt-toast-viewport-right, 0px) + var(--smrt-toast-viewport-inset)); bottom: calc(var(--smrt-toast-viewport-bottom, 0px) + var(--smrt-toast-viewport-inset)); }
197
+ .viewport--top-center, .viewport--bottom-center { left: calc(var(--smrt-toast-viewport-left, 0px) + var(--smrt-toast-viewport-region-width, 100vw) / 2); transform: translateX(-50%); }
198
+ .viewport--top-center { top: calc(var(--smrt-toast-viewport-top, 0px) + var(--smrt-toast-viewport-inset)); }
199
+ .viewport--bottom-center { bottom: calc(var(--smrt-toast-viewport-bottom, 0px) + var(--smrt-toast-viewport-inset)); }
198
200
  button { padding: var(--smrt-spacing-1) var(--smrt-spacing-2); border: 1px solid currentColor; border-radius: var(--smrt-radius-small); background: transparent; color: inherit; font: inherit; cursor: pointer; }
201
+ }
199
202
  </style>
@@ -144,10 +144,10 @@ function handleKeydown(event: KeyboardEvent) {
144
144
  }
145
145
  </script>
146
146
 
147
- <div class="working-strip__live" aria-live="polite" aria-atomic="true">
147
+ <div class="working-strip__live" aria-live="polite" aria-atomic="true" data-smrt-component="WorkingStrip">
148
148
  {politeText}
149
149
  </div>
150
- <div class="working-strip__live" aria-live="assertive" aria-atomic="true">
150
+ <div class="working-strip__live" aria-live="assertive" aria-atomic="true" data-smrt-component="WorkingStrip">
151
151
  {visible && assertive ? accessibleSummary : ''}
152
152
  </div>
153
153
 
@@ -157,7 +157,7 @@ function handleKeydown(event: KeyboardEvent) {
157
157
  class="working-strip working-strip--{variant}"
158
158
  data-phase={phase}
159
159
  data-testid="working-strip"
160
- onkeydown={handleKeydown}
160
+ onkeydown={handleKeydown} data-smrt-component="WorkingStrip"
161
161
  >
162
162
  {#snippet content()}
163
163
  <span class="working-strip__icon" aria-hidden="true">
@@ -243,6 +243,8 @@ function handleKeydown(event: KeyboardEvent) {
243
243
  {/if}
244
244
 
245
245
  <style>
246
+ @layer smrt.reset, smrt.structure, smrt.theme;
247
+ @layer smrt.theme {
246
248
  .working-strip__live,
247
249
  .working-strip__sr {
248
250
  position: absolute;
@@ -423,4 +425,5 @@ function handleKeydown(event: KeyboardEvent) {
423
425
  border-inline-start-color: currentColor;
424
426
  }
425
427
  }
428
+ }
426
429
  </style>
@@ -451,7 +451,7 @@ useControlRegistration(() => {
451
451
  data-smrt-control={controlId}
452
452
  data-smrt-form={interactionContext?.formId}
453
453
  data-smrt-subject-type={interaction === false ? undefined : interaction?.subject?.type}
454
- data-smrt-subject-id={interaction === false ? undefined : interaction?.subject?.id}
454
+ data-smrt-subject-id={interaction === false ? undefined : interaction?.subject?.id} data-smrt-component="CameraCapture"
455
455
  >
456
456
  <div class="stage" class:empty={cameraState !== 'streaming' && !previewUrl}>
457
457
  <video
@@ -524,6 +524,8 @@ useControlRegistration(() => {
524
524
  </div>
525
525
 
526
526
  <style>
527
+ @layer smrt.reset, smrt.structure, smrt.theme;
528
+ @layer smrt.theme {
527
529
  .camera-capture {
528
530
  display: grid;
529
531
  gap: var(--smrt-spacing-3);
@@ -578,8 +580,8 @@ useControlRegistration(() => {
578
580
  justify-content: center;
579
581
  /* Capture is a touch flow: its actions are touch targets at every
580
582
  density, on the shared scale (smrt#3252). */
581
- min-width: var(--smrt-touch-target-min, 48px);
582
- min-height: var(--smrt-touch-target-min, 48px);
583
+ min-width: var(--smrt-touch-target-min);
584
+ min-height: var(--smrt-touch-target-min);
583
585
  padding: 0 var(--smrt-spacing-4);
584
586
  border: 1px solid var(--smrt-color-outline);
585
587
  border-radius: var(--smrt-radius-small);
@@ -616,4 +618,5 @@ useControlRegistration(() => {
616
618
  outline: 3px solid var(--smrt-color-tertiary);
617
619
  outline-offset: 4px;
618
620
  }
621
+ }
619
622
  </style>
@@ -130,7 +130,7 @@ useControlRegistration(() => {
130
130
  data-smrt-control={controlId}
131
131
  data-smrt-form={interactionContext?.formId}
132
132
  data-smrt-subject-type={resolvedInteraction === false ? undefined : resolvedInteraction.subject?.type}
133
- data-smrt-subject-id={resolvedInteraction === false ? undefined : resolvedInteraction.subject?.id}
133
+ data-smrt-subject-id={resolvedInteraction === false ? undefined : resolvedInteraction.subject?.id} data-smrt-component="Checkbox"
134
134
  >
135
135
  <input
136
136
  bind:this={inputEl}
@@ -154,6 +154,8 @@ useControlRegistration(() => {
154
154
  </label>
155
155
 
156
156
  <style>
157
+ @layer smrt.reset, smrt.structure, smrt.theme;
158
+ @layer smrt.theme {
157
159
  /* The visible box stays 18px. A transparent 44x44 hit area (WCAG 2.5.8 / M3 touch target) is
158
160
  centred on it with ::before, so clicks nearby still land on the wrapping <label>. It adds no
159
161
  layout. The box and the label text sit above every hit area (z-index 1 over 0), so one
@@ -162,22 +164,22 @@ useControlRegistration(() => {
162
164
  checkbox stands alone in its parent. Beside anything else (a row title, a link, other text)
163
165
  it keeps to the box's own column and grows vertically only. Resize it with
164
166
  --smrt-control-hit-size (0px turns it off). */
165
- .checkbox { --_box: 1.125rem; --_hit: max(var(--smrt-control-hit-size, 2.75rem), var(--_box)); position: relative; display: inline-flex; align-items: center; gap: var(--smrt-spacing-2, .5rem); cursor: pointer; color: var(--smrt-color-on-surface); }
166
- .checkbox::before { content: ''; position: absolute; z-index: 0; inset-block-start: 50%; inset-inline-start: calc(var(--_box) / 2); inline-size: var(--_hit); block-size: var(--_hit); margin-block-start: calc(var(--_hit) / -2); margin-inline-start: calc(var(--_hit) / -2); }
167
+ .checkbox { --smrt-checkbox-box: 1.125rem; --smrt-checkbox-hit: max(var(--smrt-control-hit-size, 2.75rem), var(--smrt-checkbox-box)); position: relative; display: inline-flex; align-items: center; gap: var(--smrt-spacing-2, .5rem); cursor: pointer; color: var(--smrt-color-on-surface); }
168
+ .checkbox::before { content: ''; position: absolute; z-index: 0; inset-block-start: 50%; inset-inline-start: calc(var(--smrt-checkbox-box) / 2); inline-size: var(--smrt-checkbox-hit); block-size: var(--smrt-checkbox-hit); margin-block-start: calc(var(--smrt-checkbox-hit) / -2); margin-inline-start: calc(var(--smrt-checkbox-hit) / -2); }
167
169
  .checkbox--disabled { opacity: .5; cursor: not-allowed; }
168
170
  input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
169
- .checkbox__box { position: relative; z-index: 1; width: 1.125rem; height: 1.125rem; display: grid; place-items: center; border: 2px solid var(--smrt-color-outline); border-radius: var(--smrt-radius-extra-small, 3px); background: var(--smrt-color-surface); transition: background var(--smrt-duration-short2), border-color var(--smrt-duration-short2); }
171
+ .checkbox__box { position: relative; z-index: 1; width: 1.125rem; height: 1.125rem; display: grid; place-items: center; border: 2px solid var(--smrt-color-outline); border-radius: var(--smrt-radius-extra-small); background: var(--smrt-color-surface); transition: background var(--smrt-duration-short2), border-color var(--smrt-duration-short2); }
170
172
  .checkbox__label { position: relative; z-index: 1; }
171
173
  input:checked + .checkbox__box, input:indeterminate + .checkbox__box { background: var(--smrt-color-primary); border-color: var(--smrt-color-primary); color: var(--smrt-color-on-primary); }
172
174
  input:focus-visible + .checkbox__box { outline: 2px solid var(--smrt-color-primary); outline-offset: 3px; }
173
175
  .checkbox__check { font-size: .8rem; font-weight: 800; line-height: 1; }
174
176
  .checkbox__dash { width: .6rem; height: 2px; background: currentColor; }
175
- .checkbox:not(:only-child)::before { inline-size: var(--_box); margin-inline-start: calc(var(--_box) / -2); }
177
+ .checkbox:not(:only-child)::before { inline-size: var(--smrt-checkbox-box); margin-inline-start: calc(var(--smrt-checkbox-box) / -2); }
176
178
  .checkbox[data-smrt-highlighted='true'] { outline: 3px solid var(--smrt-color-tertiary); outline-offset: 4px; border-radius: var(--smrt-radius-small); }
177
179
 
178
180
  /* Table cells: neighbouring cells hold links and other controls, so the hit area never leaves
179
181
  the cell's column. It grows vertically only (box wide)... */
180
- :global(:is(td, th)) .checkbox::before { inline-size: var(--_box); margin-inline-start: calc(var(--_box) / -2); }
182
+ :global(:is(td, th)) .checkbox::before { inline-size: var(--smrt-checkbox-box); margin-inline-start: calc(var(--smrt-checkbox-box) / -2); }
181
183
  /* ...unless the checkbox is the cell's only content: then the whole cell, clipped to its own
182
184
  edges, is the hit area (the 44px target the header and every row share). The cell only needs
183
185
  a containing block, so :where() keeps that rule at zero specificity: a sticky header or a
@@ -188,7 +190,8 @@ useControlRegistration(() => {
188
190
 
189
191
  @media (prefers-reduced-motion: reduce) { .checkbox__box { transition: none; } }
190
192
 
191
- .checkbox[data-density='touch'] { --smrt-control-target-min: var(--smrt-touch-target-min, 48px); }
193
+ .checkbox[data-density='touch'] { --smrt-control-target-min: var(--smrt-touch-target-min); }
192
194
  .checkbox[data-density='comfortable'] { --smrt-control-target-min: 0px; }
193
195
  .checkbox { min-height: var(--smrt-control-target-min, 0px); min-width: var(--smrt-control-target-min, 0px); }
196
+ }
194
197
  </style>
@@ -334,7 +334,7 @@ useControlRegistration(() => {
334
334
  </script>
335
335
  <div bind:this={rootEl} class="combobox {className}" data-smrt-control={controlId} data-smrt-form={interactionContext?.formId}
336
336
  data-smrt-subject-type={interaction === false ? undefined : interaction?.subject?.type}
337
- data-smrt-subject-id={interaction === false ? undefined : interaction?.subject?.id}>
337
+ data-smrt-subject-id={interaction === false ? undefined : interaction?.subject?.id} data-smrt-component="Combobox">
338
338
  <FieldLabel for={inputId} {label} {required} />
339
339
  <!-- The form posts the committed value (an option id), never the label the person sees. -->
340
340
  {#if name}<input type="hidden" {name} {value} {disabled} />{/if}
@@ -347,6 +347,8 @@ useControlRegistration(() => {
347
347
  {#if status !== undefined}<div class="sr-status" role="status" aria-live="polite">{open ? status : ''}</div>{/if}
348
348
  </div>
349
349
  <style>
350
+ @layer smrt.reset, smrt.structure, smrt.theme;
351
+ @layer smrt.theme {
350
352
  .combobox { position: relative; display: grid; gap: 0; color: var(--smrt-color-on-surface); }
351
353
  input { box-sizing: border-box; width: 100%; padding: var(--smrt-spacing-2) var(--smrt-spacing-3); border: 1px solid var(--smrt-color-outline); border-radius: var(--smrt-radius-small); background: var(--smrt-color-surface); color: inherit; }
352
354
  input:focus { outline: 2px solid var(--smrt-color-primary); outline-offset: 1px; } .options { position: absolute; z-index: var(--smrt-z-index-dropdown); top: 100%; left: 0; right: 0; display: grid; padding: var(--smrt-spacing-1); border: 1px solid var(--smrt-color-outline-variant); border-radius: var(--smrt-radius-small); background: var(--smrt-color-surface-container); box-shadow: var(--smrt-elevation-2); }
@@ -354,4 +356,5 @@ useControlRegistration(() => {
354
356
  .options.empty { padding: var(--smrt-spacing-2) var(--smrt-spacing-3); color: var(--smrt-color-on-surface-variant); }
355
357
  .sr-status { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
356
358
  :global(.combobox[data-smrt-highlighted='true']) { outline: 3px solid var(--smrt-color-tertiary); outline-offset: 4px; }
359
+ }
357
360
  </style>
@@ -25,5 +25,10 @@ let {
25
25
  const instanceId = $props.id();
26
26
  const resolvedId = $derived(id ?? `smrt-date-${instanceId}`);
27
27
  </script>
28
- <label class="picker {className}" for={resolvedId}><span>{label}</span><Input id={resolvedId} {name} type="date" aria-label={label} {interaction} bind:value {...rest} /></label>
29
- <style>.picker { display: grid; gap: var(--smrt-spacing-1); color: var(--smrt-color-on-surface); font: var(--smrt-typography-label-large-font); }</style>
28
+ <label class="picker {className}" for={resolvedId} data-smrt-component="DatePicker"><span>{label}</span><Input id={resolvedId} {name} type="date" aria-label={label} {interaction} bind:value {...rest} /></label>
29
+ <style>
30
+ @layer smrt.reset, smrt.structure, smrt.theme;
31
+ @layer smrt.theme {
32
+ .picker { display: grid; gap: var(--smrt-spacing-1); color: var(--smrt-color-on-surface); font: var(--smrt-typography-label-large-font); }
33
+ }
34
+ </style>
@@ -118,10 +118,12 @@ function handleClick() {
118
118
  {/if}
119
119
  </Button>
120
120
  {#if hint !== name}
121
- <span id={helpId} class="smrt-dictation-help">{hint}</span>
121
+ <span id={helpId} class="smrt-dictation-help" data-smrt-component="DictationButton">{hint}</span>
122
122
  {/if}
123
123
 
124
124
  <style>
125
+ @layer smrt.reset, smrt.structure, smrt.theme;
126
+ @layer smrt.theme {
125
127
  :global(.smrt-dictation-button) {
126
128
  position: relative;
127
129
  flex-shrink: 0;
@@ -132,13 +134,13 @@ function handleClick() {
132
134
  block-size: 44px;
133
135
  min-inline-size: 44px;
134
136
  padding: 0;
135
- border-radius: var(--smrt-radius-full, 9999px);
136
- color: var(--smrt-color-on-surface-variant, #43474e);
137
+ border-radius: var(--smrt-radius-full);
138
+ color: var(--smrt-color-on-surface-variant);
137
139
  }
138
140
 
139
141
  :global(.smrt-dictation-button--listening) {
140
- background: var(--smrt-color-error-container, #ffdad6);
141
- color: var(--smrt-color-on-error-container, #410002);
142
+ background: var(--smrt-color-error-container);
143
+ color: var(--smrt-color-on-error-container);
142
144
  }
143
145
 
144
146
  /* Read out with the button's name; never shown. */
@@ -158,22 +160,22 @@ function handleClick() {
158
160
  inline-size: 10px;
159
161
  block-size: 10px;
160
162
  border-radius: 50%;
161
- background: var(--smrt-color-error, #ba1a1a);
163
+ background: var(--smrt-color-error);
162
164
  animation: smrt-dictation-pulse 1.2s ease-in-out infinite;
163
165
  }
164
166
 
165
167
  /* Hands-free: a steady ring while listening ... */
166
168
  :global(.smrt-dictation-button--hands-free) {
167
- box-shadow: 0 0 0 2px var(--smrt-color-error, #ba1a1a);
169
+ box-shadow: 0 0 0 2px var(--smrt-color-error);
168
170
  }
169
171
 
170
172
  /* ... and a halo that swells with the voice while someone is speaking. */
171
173
  :global(.smrt-dictation-button--speaking) {
172
174
  box-shadow:
173
- 0 0 0 3px var(--smrt-color-error, #ba1a1a),
175
+ 0 0 0 3px var(--smrt-color-error),
174
176
  0 0 0
175
177
  calc(4px + 10px * var(--smrt-dictation-level, 0))
176
- color-mix(in srgb, var(--smrt-color-error, #ba1a1a) 28%, transparent);
178
+ color-mix(in srgb, var(--smrt-color-error) 28%, transparent);
177
179
  transition: box-shadow 80ms linear;
178
180
  }
179
181
 
@@ -181,7 +183,7 @@ function handleClick() {
181
183
  :global(.smrt-dictation-button--paused) {
182
184
  opacity: 0.6;
183
185
  box-shadow: none;
184
- outline: 2px dashed var(--smrt-color-outline, #73777f);
186
+ outline: 2px dashed var(--smrt-color-outline);
185
187
  outline-offset: -2px;
186
188
  }
187
189
 
@@ -198,9 +200,9 @@ function handleClick() {
198
200
  inline-size: 10px;
199
201
  block-size: 10px;
200
202
  border-radius: 50%;
201
- border: 2px solid var(--smrt-color-outline-variant, #c4c6cf);
202
- border-block-start-color: var(--smrt-color-primary, #2f5ea8);
203
- background: var(--smrt-color-surface, #fff);
203
+ border: 2px solid var(--smrt-color-outline-variant);
204
+ border-block-start-color: var(--smrt-color-primary);
205
+ background: var(--smrt-color-surface);
204
206
  animation: smrt-dictation-spin 0.9s linear infinite;
205
207
  }
206
208
 
@@ -229,7 +231,8 @@ function handleClick() {
229
231
  /* Still ringed, but the halo no longer animates with the voice. */
230
232
  :global(.smrt-dictation-button--speaking) {
231
233
  transition: none;
232
- box-shadow: 0 0 0 4px var(--smrt-color-error, #ba1a1a);
234
+ box-shadow: 0 0 0 4px var(--smrt-color-error);
233
235
  }
234
236
  }
237
+ }
235
238
  </style>
@@ -71,7 +71,7 @@ function errorText(kind: DictationErrorKind | null): string {
71
71
  }
72
72
  </script>
73
73
 
74
- <div class={`smrt-dictation-status ${className}`.trim()}>
74
+ <div class={`smrt-dictation-status ${className}`.trim()} data-smrt-component="DictationStatus">
75
75
  <p
76
76
  class="smrt-dictation-status-line"
77
77
  class:smrt-dictation-status--empty={!showLine || announceOnly}
@@ -118,6 +118,8 @@ function errorText(kind: DictationErrorKind | null): string {
118
118
  </div>
119
119
 
120
120
  <style>
121
+ @layer smrt.reset, smrt.structure, smrt.theme;
122
+ @layer smrt.theme {
121
123
  .smrt-dictation-status {
122
124
  display: contents;
123
125
  }
@@ -135,7 +137,7 @@ function errorText(kind: DictationErrorKind | null): string {
135
137
  align-items: center;
136
138
  gap: 0.4rem;
137
139
  padding: 0.25rem 0;
138
- color: var(--smrt-color-on-surface, #1a1c1e);
140
+ color: var(--smrt-color-on-surface);
139
141
  }
140
142
 
141
143
  /* Kept mounted while empty (live regions must exist before they speak),
@@ -154,15 +156,15 @@ function errorText(kind: DictationErrorKind | null): string {
154
156
  inline-size: 8px;
155
157
  block-size: 8px;
156
158
  border-radius: 50%;
157
- background: var(--smrt-color-error, #ba1a1a);
159
+ background: var(--smrt-color-error);
158
160
  }
159
161
 
160
162
  .smrt-dictation-status-spinner {
161
163
  inline-size: 12px;
162
164
  block-size: 12px;
163
165
  border-radius: 50%;
164
- border: 2px solid var(--smrt-color-outline-variant, #c4c6cf);
165
- border-block-start-color: var(--smrt-color-primary, #2f5ea8);
166
+ border: 2px solid var(--smrt-color-outline-variant);
167
+ border-block-start-color: var(--smrt-color-primary);
166
168
  animation: smrt-dictation-spin 0.9s linear infinite;
167
169
  }
168
170
 
@@ -179,14 +181,15 @@ function errorText(kind: DictationErrorKind | null): string {
179
181
  }
180
182
 
181
183
  .smrt-dictation-status-interim {
182
- color: var(--smrt-color-on-surface-variant, #43474e);
184
+ color: var(--smrt-color-on-surface-variant);
183
185
  font-style: italic;
184
186
  }
185
187
 
186
188
  .smrt-dictation-status-error {
187
189
  padding: 0.4rem 0.6rem;
188
- border-radius: var(--smrt-radius-medium, 8px);
189
- background: var(--smrt-color-error-container, #ffdad6);
190
- color: var(--smrt-color-on-error-container, #410002);
190
+ border-radius: var(--smrt-radius-medium);
191
+ background: var(--smrt-color-error-container);
192
+ color: var(--smrt-color-on-error-container);
191
193
  }
194
+ }
192
195
  </style>
@@ -28,9 +28,12 @@ function select(error: FormError) {
28
28
  onselect?.(error.controlId);
29
29
  }
30
30
  </script>
31
- {#if errors.length > 0}<section class="error-summary {className}" role="alert" aria-labelledby={titleId}><h2 id={titleId}>{title}</h2><ul>{#each errors as error}<li><button type="button" onclick={() => select(error)}>{error.label ? `${error.label}: ` : ''}{error.message}</button></li>{/each}</ul></section>{/if}
31
+ {#if errors.length > 0}<section class="error-summary {className}" role="alert" aria-labelledby={titleId} data-smrt-component="ErrorSummary"><h2 id={titleId}>{title}</h2><ul>{#each errors as error}<li><button type="button" onclick={() => select(error)}>{error.label ? `${error.label}: ` : ''}{error.message}</button></li>{/each}</ul></section>{/if}
32
32
  <style>
33
+ @layer smrt.reset, smrt.structure, smrt.theme;
34
+ @layer smrt.theme {
33
35
  .error-summary { padding: var(--smrt-spacing-4); border-inline-start: 4px solid var(--smrt-color-error); border-radius: var(--smrt-radius-small); background: var(--smrt-color-error-container); color: var(--smrt-color-on-error-container); }
34
36
  h2 { margin: 0 0 var(--smrt-spacing-2); font: var(--smrt-typography-title-small-font); } ul { margin: 0; padding-inline-start: var(--smrt-spacing-5); }
35
37
  button { padding: 0; border: 0; background: transparent; color: inherit; text-decoration: underline; cursor: pointer; }
38
+ }
36
39
  </style>
@@ -19,26 +19,29 @@ let { label, for: forId, id, required = false }: Props = $props();
19
19
  </script>
20
20
 
21
21
  {#if forId}
22
- <label {id} for={forId} class="smrt-field-label">{label}{#if required}<span class="required" aria-hidden="true">*</span>{/if}</label>
22
+ <label {id} for={forId} class="smrt-field-label" data-smrt-component="FieldLabel">{label}{#if required}<span class="required" aria-hidden="true">*</span>{/if}</label>
23
23
  {:else}
24
- <span {id} class="smrt-field-label">{label}{#if required}<span class="required" aria-hidden="true">*</span>{/if}</span>
24
+ <span {id} class="smrt-field-label" data-smrt-component="FieldLabel">{label}{#if required}<span class="required" aria-hidden="true">*</span>{/if}</span>
25
25
  {/if}
26
26
 
27
27
  <style>
28
+ @layer smrt.reset, smrt.structure, smrt.theme;
29
+ @layer smrt.theme {
28
30
  .smrt-field-label {
29
31
  display: block;
30
- margin-bottom: var(--smrt-spacing-1, 4px);
32
+ margin-bottom: var(--smrt-spacing-1);
31
33
  font-size: var(--smrt-typography-label-large-size, 0.875rem);
32
34
  font-weight: var(--smrt-typography-weight-medium, 500);
33
35
  line-height: var(--smrt-typography-label-large-line-height, 1.4);
34
36
  letter-spacing: normal;
35
37
  text-transform: none;
36
38
  font-family: inherit;
37
- color: var(--smrt-color-on-surface, #374151);
39
+ color: var(--smrt-color-on-surface);
38
40
  }
39
41
 
40
42
  .required {
41
- color: var(--smrt-color-error, #ba1a1a);
42
- margin-left: var(--smrt-spacing-1, 4px);
43
+ color: var(--smrt-color-error);
44
+ margin-left: var(--smrt-spacing-1);
43
45
  }
46
+ }
44
47
  </style>
@@ -30,12 +30,15 @@ const descriptionId = $derived(
30
30
  );
31
31
  const errorId = $derived(error ? `fieldset-${instanceId}-error` : undefined);
32
32
  </script>
33
- <fieldset class="fieldset {className}" aria-describedby={[descriptionId, errorId].filter(Boolean).join(' ') || undefined} {...rest}>
33
+ <fieldset class="fieldset {className}" aria-describedby={[descriptionId, errorId].filter(Boolean).join(' ') || undefined} {...rest} data-smrt-component="Fieldset">
34
34
  <legend>{legend}</legend>{#if description}<p id={descriptionId} class="description">{description}</p>{/if}<div class="content" class:content--stack={stack}>{@render children()}</div>{#if error}<p id={errorId} class="error" role="alert">{error}</p>{/if}
35
35
  </fieldset>
36
36
  <style>
37
+ @layer smrt.reset, smrt.structure, smrt.theme;
38
+ @layer smrt.theme {
37
39
  .fieldset { margin: 0; padding: var(--smrt-spacing-4); border: 1px solid var(--smrt-color-outline-variant); border-radius: var(--smrt-radius-medium); }
38
40
  legend { padding: 0 var(--smrt-spacing-2); color: var(--smrt-color-on-surface); font: var(--smrt-typography-title-small-font); }
39
41
  .content--stack { display: grid; gap: var(--smrt-spacing-4); }
40
42
  .description, .error { margin: 0 0 var(--smrt-spacing-3); font: var(--smrt-typography-body-small-font); color: var(--smrt-color-on-surface-variant); } .error { margin: var(--smrt-spacing-3) 0 0; color: var(--smrt-color-error); }
43
+ }
41
44
  </style>
@@ -111,11 +111,13 @@ useControlRegistration(() => {
111
111
  data-smrt-subject-type={interaction === false ? undefined : interaction?.subject?.type}
112
112
  data-smrt-subject-id={interaction === false ? undefined : interaction?.subject?.id}
113
113
  ondragenter={(event) => { event.preventDefault(); dragging = true; }}
114
- ondragover={(event) => event.preventDefault()} ondragleave={() => dragging = false} ondrop={handleDrop}>
114
+ ondragover={(event) => event.preventDefault()} ondragleave={() => dragging = false} ondrop={handleDrop} data-smrt-component="FilePicker">
115
115
  <input bind:this={inputEl} id={resolvedId} type="file" {name} {accept} {multiple} {disabled} {required} onchange={handleChange} {...rest} />
116
116
  <span class="label">{label}</span><span class="description">{files.length ? `${files.length} file${files.length === 1 ? '' : 's'} selected` : description}</span>
117
117
  </label>
118
118
  <style>
119
+ @layer smrt.reset, smrt.structure, smrt.theme;
120
+ @layer smrt.theme {
119
121
  .file-picker { display: inline-flex; flex-direction: column; gap: var(--smrt-spacing-1); padding: var(--smrt-spacing-3) var(--smrt-spacing-4); border: 1px solid var(--smrt-color-outline); border-radius: var(--smrt-radius-small); background: var(--smrt-color-surface); color: var(--smrt-color-on-surface); cursor: pointer; }
120
122
  .file-picker.dropzone { width: 100%; min-height: 8rem; align-items: center; justify-content: center; border-style: dashed; text-align: center; }
121
123
  .file-picker.dragging { border-color: var(--smrt-color-primary); background: var(--smrt-color-primary-container); }
@@ -123,4 +125,5 @@ useControlRegistration(() => {
123
125
  input:focus-visible ~ .label { outline: 2px solid var(--smrt-color-primary); outline-offset: 3px; }
124
126
  .label { font: var(--smrt-typography-label-large-font); } .description { font: var(--smrt-typography-body-small-font); color: var(--smrt-color-on-surface-variant); }
125
127
  .file-picker[data-smrt-highlighted='true'] { outline: 3px solid var(--smrt-color-tertiary); outline-offset: 4px; }
128
+ }
126
129
  </style>
@@ -152,7 +152,7 @@ function handleChange(event: Event & { currentTarget: HTMLFormElement }) {
152
152
  {onclick}
153
153
  oninput={handleInput}
154
154
  onchange={handleChange}
155
- {...rest}
155
+ {...rest} data-smrt-component="Form"
156
156
  >
157
157
  {@render children()}
158
158
  <StagedControlReview