@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
@@ -33,12 +33,14 @@ const { t } = useI18n();
33
33
  role="group"
34
34
  aria-label={label ?? t(M['ui.form_action_bar.label'])}
35
35
  data-form-action-bar
36
- data-testid="form-action-bar"
36
+ data-testid="form-action-bar" data-smrt-component="FormActionBar"
37
37
  >
38
38
  {@render children()}
39
39
  </div>
40
40
 
41
41
  <style>
42
+ @layer smrt.reset, smrt.structure, smrt.theme;
43
+ @layer smrt.theme {
42
44
  .form-action-bar {
43
45
  display: flex;
44
46
  flex-wrap: wrap;
@@ -81,4 +83,5 @@ const { t } = useI18n();
81
83
  display: none;
82
84
  }
83
85
  }
86
+ }
84
87
  </style>
@@ -62,7 +62,7 @@ setFormGroupContext(
62
62
  );
63
63
  </script>
64
64
 
65
- <div class="form-group">
65
+ <div class="form-group" data-smrt-component="FormGroup">
66
66
  <FieldLabel for={fieldId} {label} {required} />
67
67
  {@render children()}
68
68
  {#if hintId}
@@ -74,6 +74,8 @@ setFormGroupContext(
74
74
  </div>
75
75
 
76
76
  <style>
77
+ @layer smrt.reset, smrt.structure, smrt.theme;
78
+ @layer smrt.theme {
77
79
  .form-group {
78
80
  margin-bottom: 1rem;
79
81
  }
@@ -83,12 +85,13 @@ setFormGroupContext(
83
85
  .form-hint {
84
86
  margin: 0.25rem 0 0;
85
87
  font-size: var(--smrt-typography-body-small-size, 0.75rem);
86
- color: var(--smrt-color-on-surface-variant, #6b7280);
88
+ color: var(--smrt-color-on-surface-variant);
87
89
  }
88
90
 
89
91
  .form-error {
90
92
  margin: 0.25rem 0 0;
91
93
  font-size: var(--smrt-typography-body-small-size, 0.75rem);
92
- color: var(--smrt-color-error, #ba1a1a);
94
+ color: var(--smrt-color-error);
93
95
  }
96
+ }
94
97
  </style>
@@ -218,10 +218,12 @@ export function getElement(): HTMLInputElement | null {
218
218
  data-smrt-form={controlInteraction?.formId}
219
219
  data-smrt-subject-type={resolvedInteraction === false ? undefined : resolvedInteraction.subject?.type}
220
220
  data-smrt-subject-id={resolvedInteraction === false ? undefined : resolvedInteraction.subject?.id}
221
- {...rest}
221
+ {...rest} data-smrt-component="Input"
222
222
  />
223
223
 
224
224
  <style>
225
+ @layer smrt.reset, smrt.structure, smrt.theme;
226
+ @layer smrt.theme {
225
227
  .input {
226
228
  box-sizing: border-box;
227
229
  display: block;
@@ -229,9 +231,9 @@ export function getElement(): HTMLInputElement | null {
229
231
  padding: 0.5rem 0.75rem;
230
232
  font-size: var(--smrt-typography-body-medium-size, 0.875rem);
231
233
  line-height: var(--smrt-typography-body-medium-line-height, 1.5);
232
- color: var(--smrt-color-on-surface, #1f2937);
233
- background-color: var(--smrt-color-surface, #fff);
234
- border: 1px solid var(--smrt-color-outline, #6b7280);
234
+ color: var(--smrt-color-on-surface);
235
+ background-color: var(--smrt-color-surface);
236
+ border: 1px solid var(--smrt-color-outline);
235
237
  border-radius: var(--smrt-radius-small, 0.375rem);
236
238
  transition:
237
239
  border-color var(--smrt-duration-short2, 150ms) var(--smrt-easing-standard, ease-in-out),
@@ -240,24 +242,24 @@ export function getElement(): HTMLInputElement | null {
240
242
 
241
243
  .input:focus {
242
244
  outline: none;
243
- border-color: var(--smrt-color-primary, #005ac1);
244
- box-shadow: 0 0 0 3px color-mix(in srgb, var(--smrt-color-primary, #005ac1) 10%, transparent);
245
+ border-color: var(--smrt-color-primary);
246
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--smrt-color-primary) 10%, transparent);
245
247
  }
246
248
 
247
249
  .input[data-smrt-highlighted='true'] {
248
- outline: 3px solid var(--smrt-color-tertiary, #7d5260);
250
+ outline: 3px solid var(--smrt-color-tertiary);
249
251
  outline-offset: 3px;
250
- box-shadow: 0 0 0 6px color-mix(in srgb, var(--smrt-color-tertiary, #7d5260) 20%, transparent);
252
+ box-shadow: 0 0 0 6px color-mix(in srgb, var(--smrt-color-tertiary) 20%, transparent);
251
253
  }
252
254
 
253
255
  .input:disabled {
254
- background-color: var(--smrt-color-surface-container-high, #f3f4f6);
256
+ background-color: var(--smrt-color-surface-container-high);
255
257
  cursor: not-allowed;
256
258
  opacity: 0.7;
257
259
  }
258
260
 
259
261
  .input::placeholder {
260
- color: var(--smrt-color-on-surface-variant, #9ca3af);
262
+ color: var(--smrt-color-on-surface-variant);
261
263
  }
262
264
 
263
265
  /* Phones: 16px text so iOS Safari doesn't zoom the page when the field
@@ -275,7 +277,8 @@ export function getElement(): HTMLInputElement | null {
275
277
  }
276
278
  }
277
279
 
278
- .input[data-density='touch'] { --smrt-control-target-min: var(--smrt-touch-target-min, 48px); }
280
+ .input[data-density='touch'] { --smrt-control-target-min: var(--smrt-touch-target-min); }
279
281
  .input[data-density='comfortable'] { --smrt-control-target-min: 0px; }
280
282
  .input { min-height: var(--smrt-control-target-min, 0px); }
283
+ }
281
284
  </style>
@@ -12,10 +12,13 @@ export interface Props {
12
12
  }
13
13
  let { prefix, suffix, children, class: className = '' }: Props = $props();
14
14
  </script>
15
- <div class="input-group {className}">{#if prefix}<span class="adornment">{@render prefix()}</span>{/if}<div class="control">{@render children()}</div>{#if suffix}<span class="adornment">{@render suffix()}</span>{/if}</div>
15
+ <div class="input-group {className}" data-smrt-component="InputGroup">{#if prefix}<span class="adornment">{@render prefix()}</span>{/if}<div class="control">{@render children()}</div>{#if suffix}<span class="adornment">{@render suffix()}</span>{/if}</div>
16
16
  <style>
17
+ @layer smrt.reset, smrt.structure, smrt.theme;
18
+ @layer smrt.theme {
17
19
  .input-group { display: flex; align-items: stretch; width: 100%; border: 1px solid var(--smrt-color-outline); border-radius: var(--smrt-radius-small); background: var(--smrt-color-surface); overflow: hidden; }
18
20
  .adornment { display: inline-flex; align-items: center; padding: 0 var(--smrt-spacing-3); background: var(--smrt-color-surface-container-low); color: var(--smrt-color-on-surface-variant); }
19
- .control { flex: 1; min-width: 0; } .control :global(input), .control :global(select), .control :global(textarea) { border: 0 !important; border-radius: 0 !important; box-shadow: none !important; }
21
+ .control { flex: 1; min-width: 0; } .input-group .control :global(input), .input-group .control :global(select), .input-group .control :global(textarea) { border: 0; border-radius: 0; box-shadow: none; }
20
22
  .input-group:focus-within { outline: 2px solid var(--smrt-color-primary); outline-offset: 1px; }
23
+ }
21
24
  </style>
@@ -156,15 +156,18 @@ useControlRegistration(() => {
156
156
  </script>
157
157
  <div bind:this={rootEl} class="listbox {className}" role="listbox" aria-label={label} aria-disabled={disabled} data-smrt-control={controlId} data-smrt-form={interactionContext?.formId}
158
158
  data-smrt-subject-type={interaction === false ? undefined : interaction?.subject?.type}
159
- data-smrt-subject-id={interaction === false ? undefined : interaction?.subject?.id}>
159
+ data-smrt-subject-id={interaction === false ? undefined : interaction?.subject?.id} data-smrt-component="Listbox">
160
160
  {#if name}<input type="hidden" {name} value={value ?? ''} disabled={disabled || matchingOption(options, value)?.disabled} />{/if}
161
161
  {#each options as option, index (option.value)}<button bind:this={optionEls[index]} type="button" role="option" aria-selected={value === option.value}
162
162
  disabled={disabled || option.disabled} tabindex={value === option.value || (value === undefined && index === 0) ? 0 : -1}
163
163
  onkeydown={(event) => move(event, index)} onclick={() => select(option.value, true)}>{option.label}</button>{/each}
164
164
  </div>
165
165
  <style>
166
+ @layer smrt.reset, smrt.structure, smrt.theme;
167
+ @layer smrt.theme {
166
168
  .listbox { display: grid; padding: var(--smrt-spacing-1); border: 1px solid var(--smrt-color-outline); border-radius: var(--smrt-radius-small); background: var(--smrt-color-surface); }
167
169
  button { padding: var(--smrt-spacing-2) var(--smrt-spacing-3); border: 0; border-radius: var(--smrt-radius-extra-small); background: transparent; color: var(--smrt-color-on-surface); text-align: left; cursor: pointer; }
168
170
  button[aria-selected='true'] { background: var(--smrt-color-secondary-container); color: var(--smrt-color-on-secondary-container); } button:focus-visible { outline: 2px solid var(--smrt-color-primary); }
169
171
  button:disabled { opacity: .5; cursor: not-allowed; } :global(.listbox[data-smrt-highlighted='true']) { outline: 3px solid var(--smrt-color-tertiary); outline-offset: 4px; }
172
+ }
170
173
  </style>
@@ -216,15 +216,18 @@ useControlRegistration(() => {
216
216
  </script>
217
217
  <div bind:this={rootEl} class="multi-select {className}" data-smrt-control={controlId} data-smrt-form={interactionContext?.formId}
218
218
  data-smrt-subject-type={interaction === false ? undefined : interaction?.subject?.type}
219
- data-smrt-subject-id={interaction === false ? undefined : interaction?.subject?.id}>
219
+ data-smrt-subject-id={interaction === false ? undefined : interaction?.subject?.id} data-smrt-component="MultiSelect">
220
220
  {#if name}{#each canonicalValues as value}<input type="hidden" {name} value={String(value)} disabled={disabled || options.some((option) => Object.is(option.value, value) && option.disabled)} />{/each}{/if}
221
221
  <span class="label" id={`${listId}-label`}>{label}</span><button bind:this={triggerEl} id={triggerId} type="button" class="trigger" {disabled} aria-haspopup="listbox" aria-expanded={open} aria-controls={listId} aria-labelledby={`${listId}-label ${triggerId}`} onclick={() => open ? closeOptions() : openOptions()} onkeydown={handleTriggerKeydown}>{selectedLabels.length ? selectedLabels.join(', ') : placeholder}</button>
222
222
  {#if open}<div id={listId} class="options" role="listbox" tabindex="-1" aria-multiselectable="true" aria-labelledby={`${listId}-label`} onkeydown={handleOptionsKeydown}>{#each options as option, index (option.value)}<button bind:this={optionEls[index]} type="button" role="option" tabindex="-1" aria-selected={canonicalValues.some((value) => Object.is(value, option.value))} disabled={option.disabled} onclick={() => toggle(option)}><span aria-hidden="true">{canonicalValues.some((value) => Object.is(value, option.value)) ? '✓' : ''}</span>{option.label}</button>{/each}</div>{/if}
223
223
  </div>
224
224
  <style>
225
+ @layer smrt.reset, smrt.structure, smrt.theme;
226
+ @layer smrt.theme {
225
227
  .multi-select { position: relative; display: grid; gap: var(--smrt-spacing-1); color: var(--smrt-color-on-surface); } .label { font: var(--smrt-typography-label-large-font); }
226
228
  .trigger { width: 100%; min-height: 2.5rem; 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; text-align: left; }
227
229
  .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); background: var(--smrt-color-surface-container); box-shadow: var(--smrt-elevation-2); }
228
230
  .options button { display: grid; grid-template-columns: 1.25rem 1fr; gap: var(--smrt-spacing-2); padding: var(--smrt-spacing-2); border: 0; background: transparent; color: inherit; text-align: left; } .options button[aria-selected='true'] { background: var(--smrt-color-secondary-container); }
229
231
  :global(.multi-select[data-smrt-highlighted='true']) { outline: 3px solid var(--smrt-color-tertiary); outline-offset: 4px; }
232
+ }
230
233
  </style>
@@ -20,20 +20,22 @@ function handleChange(event: Event & { currentTarget: HTMLInputElement }) {
20
20
  onchange?.(event);
21
21
  }
22
22
  </script>
23
- <label class="radio" class:disabled={disabled || group.disabled} data-smrt-hit-target>
23
+ <label class="radio" class:disabled={disabled || group.disabled} data-smrt-hit-target data-smrt-component="Radio">
24
24
  <input id={resolvedId} type="radio" name={group.name} {value} disabled={disabled || group.disabled} required={group.required}
25
25
  checked={group.value === value} onchange={handleChange} {...rest} />
26
26
  <span class="radio__mark" aria-hidden="true"></span><span class="radio__label">{label}</span>
27
27
  </label>
28
28
  <style>
29
+ @layer smrt.reset, smrt.structure, smrt.theme;
30
+ @layer smrt.theme {
29
31
  /* The visible mark stays 18px; a transparent 44x44 ::before hit area (see Checkbox) makes the
30
32
  touch target. Mark and label text sit above every hit area, so neighbours' hit areas never
31
33
  steal their clicks. Beside other content (a group's other options, text, a link) it keeps to
32
34
  the mark's column and grows vertically only. --smrt-control-hit-size resizes it (0px turns it
33
35
  off). */
34
- .radio { --_box: 1.15rem; --_hit: max(var(--smrt-control-hit-size, 2.75rem), var(--_box)); position: relative; display: inline-flex; align-items: center; gap: var(--smrt-spacing-2); cursor: pointer; }
35
- .radio::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); }
36
- .radio:not(:only-child)::before { inline-size: var(--_box); margin-inline-start: calc(var(--_box) / -2); }
36
+ .radio { --smrt-radio-box: 1.15rem; --smrt-radio-hit: max(var(--smrt-control-hit-size, 2.75rem), var(--smrt-radio-box)); position: relative; display: inline-flex; align-items: center; gap: var(--smrt-spacing-2); cursor: pointer; }
37
+ .radio::before { content: ''; position: absolute; z-index: 0; inset-block-start: 50%; inset-inline-start: calc(var(--smrt-radio-box) / 2); inline-size: var(--smrt-radio-hit); block-size: var(--smrt-radio-hit); margin-block-start: calc(var(--smrt-radio-hit) / -2); margin-inline-start: calc(var(--smrt-radio-hit) / -2); }
38
+ .radio:not(:only-child)::before { inline-size: var(--smrt-radio-box); margin-inline-start: calc(var(--smrt-radio-box) / -2); }
37
39
  .radio.disabled { opacity: .5; cursor: not-allowed; }
38
40
  input { position: absolute; opacity: 0; width: 1px; height: 1px; }
39
41
  .radio__mark { position: relative; z-index: 1; width: 1.15rem; height: 1.15rem; border: 2px solid var(--smrt-color-outline); border-radius: 50%; display: grid; place-items: center; }
@@ -45,5 +47,6 @@ function handleChange(event: Event & { currentTarget: HTMLInputElement }) {
45
47
  .radio { min-height: var(--smrt-control-target-min, 0px); min-width: var(--smrt-control-target-min, 0px); }
46
48
 
47
49
  /* Table cells: grow vertically only, never into a neighbouring cell. */
48
- :global(:is(td, th)) .radio::before { inline-size: var(--_box); margin-inline-start: calc(var(--_box) / -2); }
50
+ :global(:is(td, th)) .radio::before { inline-size: var(--smrt-radio-box); margin-inline-start: calc(var(--smrt-radio-box) / -2); }
51
+ }
49
52
  </style>
@@ -139,13 +139,15 @@ useControlRegistration(() => {
139
139
  <fieldset bind:this={fieldsetEl} data-density={density} class="radio-group {className}" {disabled}
140
140
  aria-describedby={description ? `${name}-description` : undefined} data-smrt-control={controlId} data-smrt-form={interactionContext?.formId}
141
141
  data-smrt-subject-type={interaction === false ? undefined : interaction?.subject?.type}
142
- data-smrt-subject-id={interaction === false ? undefined : interaction?.subject?.id}>
142
+ data-smrt-subject-id={interaction === false ? undefined : interaction?.subject?.id} data-smrt-component="RadioGroup">
143
143
  {#if label}<legend>{label}{#if required}<span aria-hidden="true"> *</span>{/if}</legend>{/if}
144
144
  {#if description}<p id={`${name}-description`} class="description">{description}</p>{/if}
145
145
  <div class="options">{@render children()}</div>
146
146
  {#if error}<p class="error" role="alert">{error}</p>{/if}
147
147
  </fieldset>
148
148
  <style>
149
+ @layer smrt.reset, smrt.structure, smrt.theme;
150
+ @layer smrt.theme {
149
151
  .radio-group { margin: 0; padding: 0; border: 0; color: var(--smrt-color-on-surface); }
150
152
  legend { margin-bottom: var(--smrt-spacing-2); font: var(--smrt-typography-label-large-font); }
151
153
  .description, .error { margin: var(--smrt-spacing-1) 0 var(--smrt-spacing-2); font: var(--smrt-typography-body-small-font); color: var(--smrt-color-on-surface-variant); }
@@ -153,6 +155,7 @@ useControlRegistration(() => {
153
155
  .options { display: flex; flex-wrap: wrap; gap: var(--smrt-spacing-3); }
154
156
  :global(.radio-group[data-smrt-highlighted='true']) { outline: 3px solid var(--smrt-color-tertiary); outline-offset: 4px; border-radius: var(--smrt-radius-small); }
155
157
 
156
- .radio-group[data-density='touch'] { --smrt-control-target-min: var(--smrt-touch-target-min, 48px); }
158
+ .radio-group[data-density='touch'] { --smrt-control-target-min: var(--smrt-touch-target-min); }
157
159
  .radio-group[data-density='comfortable'] { --smrt-control-target-min: 0px; }
160
+ }
158
161
  </style>
@@ -175,7 +175,7 @@ useControlRegistration(() => {
175
175
  </script>
176
176
  <div bind:this={rootEl} class="range-slider {className}" class:disabled data-smrt-control={controlId} data-smrt-form={interactionContext?.formId}
177
177
  data-smrt-subject-type={resolvedInteraction === false ? undefined : resolvedInteraction.subject?.type}
178
- data-smrt-subject-id={resolvedInteraction === false ? undefined : resolvedInteraction.subject?.id} role="group" aria-label={label ?? formGroup?.().label ?? 'Range'}>
178
+ data-smrt-subject-id={resolvedInteraction === false ? undefined : resolvedInteraction.subject?.id} role="group" aria-label={label ?? formGroup?.().label ?? 'Range'} data-smrt-component="RangeSlider">
179
179
  {#if label}<div class="range-slider__header"><span>{label}</span><output>{value.min}{unit ?? ''} – {value.max}{unit ?? ''}</output></div>{/if}
180
180
  <div class="range-slider__track">
181
181
  <input bind:this={minEl} id={minId} type="range" name={name ? `${name}[min]` : undefined} {min} max={value.max} {step} {disabled} value={value.min}
@@ -189,6 +189,8 @@ useControlRegistration(() => {
189
189
  </div>
190
190
  </div>
191
191
  <style>
192
+ @layer smrt.reset, smrt.structure, smrt.theme;
193
+ @layer smrt.theme {
192
194
  .range-slider { display: grid; gap: var(--smrt-spacing-3); color: var(--smrt-color-on-surface); }
193
195
  .range-slider.disabled { opacity: .5; }
194
196
  .range-slider__header { display: flex; justify-content: space-between; font: var(--smrt-typography-label-large-font); }
@@ -202,4 +204,5 @@ useControlRegistration(() => {
202
204
  .range-slider__inputs input { width: 100%; padding: var(--smrt-spacing-2); border: 1px solid var(--smrt-color-outline); border-radius: var(--smrt-radius-small); background: var(--smrt-color-surface); color: var(--smrt-color-on-surface); }
203
205
  input:focus-visible { outline: 2px solid var(--smrt-color-primary); outline-offset: 3px; }
204
206
  :global(.range-slider[data-smrt-highlighted='true']) { outline: 3px solid var(--smrt-color-tertiary); outline-offset: 4px; border-radius: var(--smrt-radius-small); }
207
+ }
205
208
  </style>
@@ -288,7 +288,7 @@ $effect(() => {
288
288
  $effect(() => () => clearTimeout(timer));
289
289
  </script>
290
290
 
291
- <div class="relation-input" bind:this={rootEl}>
291
+ <div class="relation-input" bind:this={rootEl} data-smrt-component="RelationInput">
292
292
  <div class="field">
293
293
  <Combobox
294
294
  {name}
@@ -343,9 +343,11 @@ $effect(() => () => clearTimeout(timer));
343
343
  </div>
344
344
 
345
345
  <style>
346
+ @layer smrt.reset, smrt.structure, smrt.theme;
347
+ @layer smrt.theme {
346
348
  .relation-input {
347
349
  display: grid;
348
- gap: var(--smrt-spacing-1, 4px);
350
+ gap: var(--smrt-spacing-1);
349
351
  min-width: 0;
350
352
  }
351
353
  .field {
@@ -353,8 +355,8 @@ $effect(() => () => clearTimeout(timer));
353
355
  }
354
356
  .field :global(.clear) {
355
357
  position: absolute;
356
- right: var(--smrt-spacing-1, 4px);
357
- bottom: var(--smrt-spacing-1, 4px);
358
+ right: var(--smrt-spacing-1);
359
+ bottom: var(--smrt-spacing-1);
358
360
  }
359
361
  .option-label,
360
362
  .option-detail {
@@ -375,4 +377,5 @@ $effect(() => () => clearTimeout(timer));
375
377
  .error {
376
378
  color: var(--smrt-color-error);
377
379
  }
380
+ }
378
381
  </style>
@@ -112,7 +112,7 @@ function clear() {
112
112
  onDestroy(cancel);
113
113
  </script>
114
114
 
115
- <div class="search-input {className}" role="search">
115
+ <div class="search-input {className}" role="search" data-smrt-component="SearchInput">
116
116
  <label class="search-input__label" for={inputId}>{label}</label>
117
117
  <span class="search-input__icon" aria-hidden="true">
118
118
  <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="7" /><path d="m20 20-3.5-3.5" /></svg>
@@ -137,6 +137,8 @@ onDestroy(cancel);
137
137
  </div>
138
138
 
139
139
  <style>
140
+ @layer smrt.reset, smrt.structure, smrt.theme;
141
+ @layer smrt.theme {
140
142
  .search-input {
141
143
  position: relative;
142
144
  display: flex;
@@ -185,7 +187,7 @@ onDestroy(cancel);
185
187
  block-size: 2.75rem;
186
188
  padding: 0;
187
189
  border: none;
188
- border-radius: var(--smrt-radius-full, 999px);
190
+ border-radius: var(--smrt-radius-full);
189
191
  background: transparent;
190
192
  color: var(--smrt-color-on-surface-variant);
191
193
  cursor: pointer;
@@ -204,4 +206,5 @@ onDestroy(cancel);
204
206
  max-inline-size: none;
205
207
  }
206
208
  }
209
+ }
207
210
  </style>
@@ -236,7 +236,7 @@ useControlRegistration(() => {
236
236
  </script>
237
237
  <div bind:this={rootEl} data-density={density} class="segmented {className}" class:full-width={fullWidth} role="radiogroup" tabindex="-1" aria-label={label} aria-required={required} onkeydown={handleKeydown} data-smrt-control={controlId} data-smrt-form={interactionContext?.formId}
238
238
  data-smrt-subject-type={interaction === false ? undefined : interaction?.subject?.type}
239
- data-smrt-subject-id={interaction === false ? undefined : interaction?.subject?.id}>
239
+ data-smrt-subject-id={interaction === false ? undefined : interaction?.subject?.id} data-smrt-component="SegmentedControl">
240
240
  {#each options as option, index (option.value)}
241
241
  <label class:selected={selectedOption === option} class:disabled={disabled || option.disabled}>
242
242
  <input type="radio" {name} value={option.value} disabled={disabled || option.disabled}
@@ -250,6 +250,8 @@ useControlRegistration(() => {
250
250
  {/each}
251
251
  </div>
252
252
  <style>
253
+ @layer smrt.reset, smrt.structure, smrt.theme;
254
+ @layer smrt.theme {
253
255
  .segmented { display: inline-flex; border: 1px solid var(--smrt-color-outline); border-radius: var(--smrt-radius-full); overflow: hidden; }
254
256
  .segmented.full-width { display: flex; width: 100%; }
255
257
  label { position: relative; display: inline-flex; align-items: center; justify-content: center; flex: 1; min-height: 2.5rem; padding: 0 var(--smrt-spacing-4); border: 0; border-right: 1px solid var(--smrt-color-outline); background: var(--smrt-color-surface); color: var(--smrt-color-on-surface); cursor: pointer; }
@@ -257,8 +259,9 @@ useControlRegistration(() => {
257
259
  label:has(input:focus-visible) { outline: 2px solid var(--smrt-color-primary); outline-offset: -3px; } label.disabled, label:has(input:disabled) { opacity: .5; cursor: not-allowed; }
258
260
  :global(.segmented[data-smrt-highlighted='true']) { outline: 3px solid var(--smrt-color-tertiary); outline-offset: 4px; }
259
261
 
260
- .segmented[data-density='touch'] { --smrt-control-target-min: var(--smrt-touch-target-min, 48px); }
262
+ .segmented[data-density='touch'] { --smrt-control-target-min: var(--smrt-touch-target-min); }
261
263
  .segmented[data-density='comfortable'] { --smrt-control-target-min: 0px; }
262
264
  label { min-height: max(2.5rem, var(--smrt-control-target-min, 0px)); min-width: var(--smrt-control-target-min, 0px); }
263
265
  input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: inherit; }
266
+ }
264
267
  </style>
@@ -148,12 +148,14 @@ export function getElement(): HTMLSelectElement | null {
148
148
  data-smrt-form={controlInteraction?.formId}
149
149
  data-smrt-subject-type={resolvedInteraction === false ? undefined : resolvedInteraction.subject?.type}
150
150
  data-smrt-subject-id={resolvedInteraction === false ? undefined : resolvedInteraction.subject?.id}
151
- {...rest}
151
+ {...rest} data-smrt-component="Select"
152
152
  >
153
153
  {@render children()}
154
154
  </select>
155
155
 
156
156
  <style>
157
+ @layer smrt.reset, smrt.structure, smrt.theme;
158
+ @layer smrt.theme {
157
159
  .select {
158
160
  box-sizing: border-box;
159
161
  display: block;
@@ -161,13 +163,13 @@ export function getElement(): HTMLSelectElement | null {
161
163
  padding: 0.5rem 2rem 0.5rem 0.75rem;
162
164
  font-size: var(--smrt-typography-body-medium-size, 0.875rem);
163
165
  line-height: var(--smrt-typography-body-medium-line-height, 1.5);
164
- color: var(--smrt-color-on-surface, #1f2937);
165
- background-color: var(--smrt-color-surface, #fff);
166
+ color: var(--smrt-color-on-surface);
167
+ background-color: var(--smrt-color-surface);
166
168
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%2379747e' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
167
169
  background-position: right 0.5rem center;
168
170
  background-repeat: no-repeat;
169
171
  background-size: 1.5em 1.5em;
170
- border: 1px solid var(--smrt-color-outline, #6b7280);
172
+ border: 1px solid var(--smrt-color-outline);
171
173
  border-radius: 0.375rem;
172
174
  appearance: none;
173
175
  cursor: pointer;
@@ -178,17 +180,17 @@ export function getElement(): HTMLSelectElement | null {
178
180
 
179
181
  .select:focus {
180
182
  outline: none;
181
- border-color: var(--smrt-color-primary, #005ac1);
182
- box-shadow: 0 0 0 3px color-mix(in srgb, var(--smrt-color-primary, #005ac1) 10%, transparent);
183
+ border-color: var(--smrt-color-primary);
184
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--smrt-color-primary) 10%, transparent);
183
185
  }
184
186
 
185
187
  .select[data-smrt-highlighted='true'] {
186
- outline: 3px solid var(--smrt-color-tertiary, #7d5260);
188
+ outline: 3px solid var(--smrt-color-tertiary);
187
189
  outline-offset: 3px;
188
190
  }
189
191
 
190
192
  .select:disabled {
191
- background-color: var(--smrt-color-surface-container-high, #f3f4f6);
193
+ background-color: var(--smrt-color-surface-container-high);
192
194
  cursor: not-allowed;
193
195
  opacity: 0.7;
194
196
  }
@@ -208,7 +210,8 @@ export function getElement(): HTMLSelectElement | null {
208
210
  }
209
211
  }
210
212
 
211
- .select[data-density='touch'] { --smrt-control-target-min: var(--smrt-touch-target-min, 48px); }
213
+ .select[data-density='touch'] { --smrt-control-target-min: var(--smrt-touch-target-min); }
212
214
  .select[data-density='comfortable'] { --smrt-control-target-min: 0px; }
213
215
  .select { min-height: var(--smrt-control-target-min, 0px); }
216
+ }
214
217
  </style>
@@ -313,7 +313,7 @@ useControlRegistration(() => {
313
313
  data-smrt-control={controlId}
314
314
  data-smrt-form={interactionContext?.formId}
315
315
  data-smrt-subject-type={interaction === false ? undefined : interaction?.subject?.type}
316
- data-smrt-subject-id={interaction === false ? undefined : interaction?.subject?.id}
316
+ data-smrt-subject-id={interaction === false ? undefined : interaction?.subject?.id} data-smrt-component="SignaturePad"
317
317
  >
318
318
  <canvas
319
319
  bind:this={canvasEl}
@@ -358,6 +358,8 @@ useControlRegistration(() => {
358
358
  </div>
359
359
 
360
360
  <style>
361
+ @layer smrt.reset, smrt.structure, smrt.theme;
362
+ @layer smrt.theme {
361
363
  .signature-pad {
362
364
  display: grid;
363
365
  gap: var(--smrt-spacing-3);
@@ -405,8 +407,8 @@ useControlRegistration(() => {
405
407
  flex: 1 1 auto;
406
408
  /* Capture is a touch flow: its actions are touch targets at every
407
409
  density, on the shared scale (smrt#3252). */
408
- min-width: var(--smrt-touch-target-min, 48px);
409
- min-height: var(--smrt-touch-target-min, 48px);
410
+ min-width: var(--smrt-touch-target-min);
411
+ min-height: var(--smrt-touch-target-min);
410
412
  padding: 0 var(--smrt-spacing-4);
411
413
  border: 1px solid var(--smrt-color-outline);
412
414
  border-radius: var(--smrt-radius-small);
@@ -433,4 +435,5 @@ useControlRegistration(() => {
433
435
  outline: 3px solid var(--smrt-color-tertiary);
434
436
  outline-offset: 4px;
435
437
  }
438
+ }
436
439
  </style>
@@ -139,7 +139,7 @@ useControlRegistration(() => {
139
139
  </script>
140
140
  <div class="slider {className}" data-smrt-control={controlId} data-smrt-form={interactionContext?.formId}
141
141
  data-smrt-subject-type={resolvedInteraction === false ? undefined : resolvedInteraction.subject?.type}
142
- data-smrt-subject-id={resolvedInteraction === false ? undefined : resolvedInteraction.subject?.id}>
142
+ data-smrt-subject-id={resolvedInteraction === false ? undefined : resolvedInteraction.subject?.id} data-smrt-component="Slider">
143
143
  {#if label}<div class="slider__header"><label for={resolvedId}>{label}</label><output for={resolvedId}>{formatValue(value)}</output></div>{/if}
144
144
  <div class="slider__controls">
145
145
  <input bind:this={rangeEl} id={resolvedId} type="range" {name} {min} {max} {step} {disabled} value={value}
@@ -150,6 +150,8 @@ useControlRegistration(() => {
150
150
  </div>
151
151
  </div>
152
152
  <style>
153
+ @layer smrt.reset, smrt.structure, smrt.theme;
154
+ @layer smrt.theme {
153
155
  .slider { display: grid; gap: var(--smrt-spacing-2); color: var(--smrt-color-on-surface); }
154
156
  .slider__header, .slider__controls { display: flex; align-items: center; gap: var(--smrt-spacing-3); }
155
157
  .slider__header { justify-content: space-between; font: var(--smrt-typography-label-large-font); }
@@ -158,4 +160,5 @@ useControlRegistration(() => {
158
160
  .slider__number { width: 5.5rem; padding: var(--smrt-spacing-2); border: 1px solid var(--smrt-color-outline); border-radius: var(--smrt-radius-small); background: var(--smrt-color-surface); color: var(--smrt-color-on-surface); }
159
161
  input:focus-visible { outline: 2px solid var(--smrt-color-primary); outline-offset: 3px; }
160
162
  .slider[data-smrt-highlighted='true'] { outline: 3px solid var(--smrt-color-tertiary); outline-offset: 4px; border-radius: var(--smrt-radius-small); }
163
+ }
161
164
  </style>
@@ -536,7 +536,7 @@ async function discardAll(event: MouseEvent): Promise<void> {
536
536
  bind:this={reviewElement}
537
537
  class="staged-review"
538
538
  data-staged-control-review
539
- aria-label={text.region}
539
+ aria-label={text.region} data-smrt-component="StagedControlReview"
540
540
  >
541
541
  <header>
542
542
  <div>
@@ -623,31 +623,34 @@ async function discardAll(event: MouseEvent): Promise<void> {
623
623
  {/if}
624
624
 
625
625
  {#if summary && reviewActivated}
626
- <p class="status visually-hidden" role="status" aria-live="polite" aria-atomic="true">{status}</p>
626
+ <p class="status visually-hidden" role="status" aria-live="polite" aria-atomic="true" data-smrt-component="StagedControlReview">{status}</p>
627
627
  {/if}
628
628
 
629
629
  <style>
630
- .staged-review { margin-block-start: var(--smrt-spacing-5, 1.25rem); padding: var(--smrt-spacing-4, 1rem); color: var(--smrt-color-on-surface, #1f2937); background: var(--smrt-color-surface-container-low, #f8fafc); border: 1px solid var(--smrt-color-outline-variant, #cbd5e1); border-inline-start: 4px solid var(--smrt-color-tertiary, #7d5260); border-radius: var(--smrt-radius-medium, .5rem); }
630
+ @layer smrt.reset, smrt.structure, smrt.theme;
631
+ @layer smrt.theme {
632
+ .staged-review { margin-block-start: var(--smrt-spacing-5, 1.25rem); padding: var(--smrt-spacing-4, 1rem); color: var(--smrt-color-on-surface); background: var(--smrt-color-surface-container-low); border: 1px solid var(--smrt-color-outline-variant); border-inline-start: 4px solid var(--smrt-color-tertiary); border-radius: var(--smrt-radius-medium, .5rem); }
631
633
  header, .proposal-heading, .batch-actions, .item-actions { display: flex; align-items: center; gap: var(--smrt-spacing-2, .5rem); }
632
634
  header { justify-content: space-between; align-items: flex-start; gap: var(--smrt-spacing-4, 1rem); }
633
635
  h2, p { margin: 0; } h2 { font: var(--smrt-typography-title-medium-font, 600 1rem/1.4 system-ui); }
634
- header p, .proposal-heading span, dt { color: var(--smrt-color-on-surface-variant, #475569); font-size: .875rem; }
636
+ header p, .proposal-heading span, dt { color: var(--smrt-color-on-surface-variant); font-size: .875rem; }
635
637
  .proposal-heading strong, .proposal-heading .subject { display: block; }
636
638
  ul { display: grid; gap: var(--smrt-spacing-3, .75rem); padding: 0; list-style: none; }
637
- li { padding: var(--smrt-spacing-3, .75rem); background: var(--smrt-color-surface, #fff); border: 1px solid var(--smrt-color-outline-variant, #cbd5e1); border-radius: var(--smrt-radius-small, .375rem); }
638
- li.stale, li.invalid { border-color: var(--smrt-color-error, #b3261e); }
639
+ li { padding: var(--smrt-spacing-3, .75rem); background: var(--smrt-color-surface); border: 1px solid var(--smrt-color-outline-variant); border-radius: var(--smrt-radius-small, .375rem); }
640
+ li.stale, li.invalid { border-color: var(--smrt-color-error); }
639
641
  .proposal-heading { justify-content: space-between; }
640
642
  dl { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--smrt-spacing-3, .75rem); }
641
643
  dt { font-weight: 600; } dd { margin: .25rem 0 0; overflow-wrap: anywhere; }
642
- input, textarea { width: 100%; box-sizing: border-box; padding: .4rem .5rem; color: inherit; background: var(--smrt-color-surface, #fff); border: 1px solid var(--smrt-color-outline, #64748b); border-radius: var(--smrt-radius-small, .375rem); }
644
+ input, textarea { width: 100%; box-sizing: border-box; padding: .4rem .5rem; color: inherit; background: var(--smrt-color-surface); border: 1px solid var(--smrt-color-outline); border-radius: var(--smrt-radius-small, .375rem); }
643
645
  input[type="checkbox"] { width: auto; }
644
- button { min-height: 2.25rem; padding: .4rem .75rem; color: var(--smrt-color-on-primary, #fff); background: var(--smrt-color-primary, #005ac1); border: 1px solid transparent; border-radius: var(--smrt-radius-small, .375rem); cursor: pointer; }
645
- button.secondary { color: var(--smrt-color-primary, #005ac1); background: transparent; border-color: var(--smrt-color-outline, #64748b); }
646
- button:focus-visible, input:focus-visible, textarea:focus-visible { outline: 2px solid var(--smrt-color-primary, #005ac1); outline-offset: 2px; }
646
+ button { min-height: 2.25rem; padding: .4rem .75rem; color: var(--smrt-color-on-primary); background: var(--smrt-color-primary); border: 1px solid transparent; border-radius: var(--smrt-radius-small, .375rem); cursor: pointer; }
647
+ button.secondary { color: var(--smrt-color-primary); background: transparent; border-color: var(--smrt-color-outline); }
648
+ button:focus-visible, input:focus-visible, textarea:focus-visible { outline: 2px solid var(--smrt-color-primary); outline-offset: 2px; }
647
649
  button:disabled { cursor: not-allowed; opacity: .55; }
648
650
  .item-actions { margin-block-start: var(--smrt-spacing-3, .75rem); }
649
- .problem { margin-block-start: var(--smrt-spacing-2, .5rem); color: var(--smrt-color-error, #b3261e); }
651
+ .problem { margin-block-start: var(--smrt-spacing-2, .5rem); color: var(--smrt-color-error); }
650
652
  .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; }
651
- :global([data-smrt-staged='true']) { outline: 3px solid var(--smrt-color-tertiary, #7d5260); outline-offset: 3px; }
653
+ :global([data-smrt-staged='true']) { outline: 3px solid var(--smrt-color-tertiary); outline-offset: 3px; }
652
654
  @media (max-width: 40rem) { header, .proposal-heading { align-items: stretch; flex-direction: column; } dl { grid-template-columns: 1fr; } }
655
+ }
653
656
  </style>
@@ -118,7 +118,7 @@ useControlRegistration(() => {
118
118
 
119
119
  <label data-density={density} class="switch switch--{size} {className}" class:switch--disabled={disabled} data-smrt-hit-target data-smrt-control={controlId} data-smrt-form={interactionContext?.formId}
120
120
  data-smrt-subject-type={resolvedInteraction === false ? undefined : resolvedInteraction.subject?.type}
121
- data-smrt-subject-id={resolvedInteraction === false ? undefined : resolvedInteraction.subject?.id}>
121
+ data-smrt-subject-id={resolvedInteraction === false ? undefined : resolvedInteraction.subject?.id} data-smrt-component="Switch">
122
122
  {#if label && labelPosition === 'left'}<span class="switch__label">{label}</span>{/if}
123
123
  <span class="switch__control">
124
124
  <input bind:this={inputEl} id={resolvedId} type="checkbox" role="switch" {name} {value} {disabled} {required} checked={checked}
@@ -130,14 +130,16 @@ useControlRegistration(() => {
130
130
  </label>
131
131
 
132
132
  <style>
133
+ @layer smrt.reset, smrt.structure, smrt.theme;
134
+ @layer smrt.theme {
133
135
  .switch { display: inline-flex; align-items: center; gap: var(--smrt-spacing-2, .5rem); cursor: pointer; color: var(--smrt-color-on-surface); }
134
136
  .switch--disabled { opacity: .5; cursor: not-allowed; }
135
137
  /* Phones: the whole row (track and label) is at least a 44px touch target. */
136
138
  @media (max-width: 48rem) { .switch { min-block-size: 2.75rem; } }
137
139
  /* The 2.75rem-wide track gets a transparent 44px-tall ::before hit area (see Checkbox); the
138
140
  track and label sit above every hit area. --smrt-control-hit-size resizes it (0px = off). */
139
- .switch__control { --_hit: max(var(--smrt-control-hit-size, 2.75rem), 1.5rem); position: relative; display: inline-flex; }
140
- .switch__control::before { content: ''; position: absolute; z-index: 0; inset-block-start: 50%; inset-inline: calc((100% - var(--_hit)) / 2); block-size: var(--_hit); margin-block-start: calc(var(--_hit) / -2); }
141
+ .switch__control { --smrt-switch-hit: max(var(--smrt-control-hit-size, 2.75rem), 1.5rem); position: relative; display: inline-flex; }
142
+ .switch__control::before { content: ''; position: absolute; z-index: 0; inset-block-start: 50%; inset-inline: calc((100% - var(--smrt-switch-hit)) / 2); block-size: var(--smrt-switch-hit); margin-block-start: calc(var(--smrt-switch-hit) / -2); }
141
143
  .switch__label { position: relative; z-index: 1; }
142
144
  input { position: absolute; width: 1px; height: 1px; opacity: 0; }
143
145
  .switch__track { position: relative; z-index: 1; width: 2.75rem; height: 1.5rem; padding: 2px; border-radius: var(--smrt-radius-full); background: var(--smrt-color-surface-container-highest); border: 1px solid var(--smrt-color-outline); transition: background var(--smrt-duration-short2); }
@@ -150,7 +152,8 @@ useControlRegistration(() => {
150
152
  .switch[data-smrt-highlighted='true'] { outline: 3px solid var(--smrt-color-tertiary); outline-offset: 4px; border-radius: var(--smrt-radius-full); }
151
153
  @media (prefers-reduced-motion: reduce) { .switch__track, .switch__thumb { transition: none; } }
152
154
 
153
- .switch[data-density='touch'] { --smrt-control-target-min: var(--smrt-touch-target-min, 48px); }
155
+ .switch[data-density='touch'] { --smrt-control-target-min: var(--smrt-touch-target-min); }
154
156
  .switch[data-density='comfortable'] { --smrt-control-target-min: 0px; }
155
157
  .switch { min-height: var(--smrt-control-target-min, 0px); min-width: var(--smrt-control-target-min, 0px); }
158
+ }
156
159
  </style>
@@ -129,13 +129,15 @@ useControlRegistration(() => {
129
129
  };
130
130
  });
131
131
  </script>
132
- <div class="tags-field {className}"><label for={inputId}>{label}</label><div bind:this={rootEl} class="tags" data-smrt-control={controlId} data-smrt-form={interactionContext?.formId}
132
+ <div class="tags-field {className}" data-smrt-component="TagsInput"><label for={inputId}>{label}</label><div bind:this={rootEl} class="tags" data-smrt-control={controlId} data-smrt-form={interactionContext?.formId}
133
133
  data-smrt-subject-type={interaction === false ? undefined : interaction?.subject?.type}
134
134
  data-smrt-subject-id={interaction === false ? undefined : interaction?.subject?.id}>
135
135
  {#each values as tag, index}<span class="tag">{tag}<button type="button" disabled={disabled} aria-label={`Remove ${tag}`} onclick={() => remove(index)}>×</button><input type="hidden" name={name ? `${name}[]` : undefined} value={tag} /></span>{/each}
136
136
  <input bind:this={inputEl} id={inputId} {placeholder} {disabled} bind:value={draft} onkeydown={handleKeydown} onblur={() => commit(draft)} />
137
137
  </div></div>
138
138
  <style>
139
+ @layer smrt.reset, smrt.structure, smrt.theme;
140
+ @layer smrt.theme {
139
141
  .tags-field { display: grid; gap: var(--smrt-spacing-1); color: var(--smrt-color-on-surface); } label { font: var(--smrt-typography-label-large-font); }
140
142
  .tags { display: flex; flex-wrap: wrap; gap: var(--smrt-spacing-1); min-height: 2.5rem; padding: var(--smrt-spacing-1); border: 1px solid var(--smrt-color-outline); border-radius: var(--smrt-radius-small); background: var(--smrt-color-surface); }
141
143
  .tag { display: inline-flex; align-items: center; gap: var(--smrt-spacing-1); padding: var(--smrt-spacing-1) var(--smrt-spacing-2); border-radius: var(--smrt-radius-full); background: var(--smrt-color-secondary-container); color: var(--smrt-color-on-secondary-container); }
@@ -148,4 +150,5 @@ useControlRegistration(() => {
148
150
  .tag button:focus-visible { outline: 2px solid var(--smrt-color-primary); outline-offset: -2px; border-radius: var(--smrt-radius-full); }
149
151
  .tags > input { flex: 1; min-width: 8rem; border: 0; outline: 0; background: transparent; color: inherit; }
150
152
  :global(.tags[data-smrt-highlighted='true']) { outline: 3px solid var(--smrt-color-tertiary); outline-offset: 4px; }
153
+ }
151
154
  </style>