@happyvertical/smrt-ui 0.55.16 → 0.55.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/AGENTS.md +14 -0
  2. package/README.md +75 -0
  3. package/dist/components/calendar/Calendar.svelte +59 -56
  4. package/dist/components/calendar/CalendarView.svelte +80 -77
  5. package/dist/components/calendar/DayView.svelte +39 -36
  6. package/dist/components/chat/MessageBubble.svelte +26 -23
  7. package/dist/components/chat/ReactionPicker.svelte +12 -9
  8. package/dist/components/chat/TypingIndicator.svelte +12 -9
  9. package/dist/components/data/CollectionList.svelte +11 -8
  10. package/dist/components/data/CollectionToolbar.svelte +5 -2
  11. package/dist/components/data/DataTable.svelte +74 -59
  12. package/dist/components/data/ListSortSelect.svelte +8 -5
  13. package/dist/components/data/SortableHeader.svelte +10 -7
  14. package/dist/components/display/ConfidenceBadge.svelte +11 -8
  15. package/dist/components/display/CurrencyDisplay.svelte +7 -4
  16. package/dist/components/display/DateDisplay.svelte +6 -3
  17. package/dist/components/display/Icon.svelte +4 -1
  18. package/dist/components/display/StatusBadge.svelte +9 -6
  19. package/dist/components/feedback/Alert.svelte +12 -8
  20. package/dist/components/feedback/ConfirmDialog.svelte +13 -10
  21. package/dist/components/feedback/LoadingOverlay.svelte +37 -34
  22. package/dist/components/feedback/Meter.svelte +4 -1
  23. package/dist/components/feedback/Modal.svelte +26 -21
  24. package/dist/components/feedback/PhoneSheet.svelte +6 -3
  25. package/dist/components/feedback/Progress.svelte +8 -5
  26. package/dist/components/feedback/ProgressBar.svelte +13 -10
  27. package/dist/components/feedback/Spinner.svelte +4 -1
  28. package/dist/components/feedback/ToastViewport.svelte +18 -15
  29. package/dist/components/feedback/WorkingStrip.svelte +6 -3
  30. package/dist/components/forms/CameraCapture.svelte +8 -5
  31. package/dist/components/forms/Checkbox.svelte +11 -8
  32. package/dist/components/forms/Combobox.svelte +5 -2
  33. package/dist/components/forms/DatePicker.svelte +7 -2
  34. package/dist/components/forms/DictationButton.svelte +17 -14
  35. package/dist/components/forms/DictationStatus.svelte +12 -9
  36. package/dist/components/forms/ErrorSummary.svelte +4 -1
  37. package/dist/components/forms/FieldLabel.svelte +9 -6
  38. package/dist/components/forms/Fieldset.svelte +4 -1
  39. package/dist/components/forms/FilePicker.svelte +5 -2
  40. package/dist/components/forms/Form.svelte +1 -1
  41. package/dist/components/forms/FormActionBar.svelte +4 -1
  42. package/dist/components/forms/FormGroup.svelte +6 -3
  43. package/dist/components/forms/Input.svelte +22 -15
  44. package/dist/components/forms/InputGroup.svelte +6 -3
  45. package/dist/components/forms/Listbox.svelte +5 -2
  46. package/dist/components/forms/MultiSelect.svelte +4 -1
  47. package/dist/components/forms/Radio.svelte +9 -6
  48. package/dist/components/forms/RadioGroup.svelte +5 -2
  49. package/dist/components/forms/RangeSlider.svelte +5 -2
  50. package/dist/components/forms/RelationInput.svelte +7 -4
  51. package/dist/components/forms/SearchInput.svelte +7 -4
  52. package/dist/components/forms/SegmentedControl.svelte +6 -3
  53. package/dist/components/forms/Select.svelte +20 -13
  54. package/dist/components/forms/SignaturePad.svelte +8 -5
  55. package/dist/components/forms/Slider.svelte +5 -2
  56. package/dist/components/forms/StagedControlReview.svelte +15 -12
  57. package/dist/components/forms/Switch.svelte +8 -5
  58. package/dist/components/forms/TagsInput.svelte +5 -2
  59. package/dist/components/forms/Textarea.svelte +20 -13
  60. package/dist/components/forms/TimePicker.svelte +7 -2
  61. package/dist/components/forms/Toggle.svelte +14 -11
  62. package/dist/components/forms/ToggleButton.svelte +5 -2
  63. package/dist/components/layout/ActionGroup.svelte +4 -1
  64. package/dist/components/layout/Container.svelte +4 -1
  65. package/dist/components/layout/EmptyState.svelte +11 -8
  66. package/dist/components/layout/Footer.svelte +4 -1
  67. package/dist/components/layout/Grid.svelte +13 -10
  68. package/dist/components/layout/Header.svelte +4 -1
  69. package/dist/components/layout/Masthead.svelte +4 -1
  70. package/dist/components/layout/PageHeader.svelte +12 -9
  71. package/dist/components/layout/PageLayout.svelte +3 -0
  72. package/dist/components/layout/SummaryCard.svelte +9 -6
  73. package/dist/components/memberships/MembershipCard.svelte +5 -2
  74. package/dist/components/memberships/MembershipList.svelte +5 -2
  75. package/dist/components/nav/FilterChips.svelte +13 -10
  76. package/dist/components/nav/Tabs.svelte +75 -41
  77. package/dist/components/permissions/PermissionPicker.svelte +5 -2
  78. package/dist/components/roles/RoleBadge.svelte +8 -5
  79. package/dist/components/roles/RoleSelector.svelte +22 -19
  80. package/dist/components/theme/ThemeProvider.svelte +7 -4
  81. package/dist/components/ui/Accordion.svelte +4 -1
  82. package/dist/components/ui/AccordionItem.svelte +5 -2
  83. package/dist/components/ui/Avatar.svelte +10 -7
  84. package/dist/components/ui/Badge.svelte +6 -2
  85. package/dist/components/ui/Button.svelte +49 -37
  86. package/dist/components/ui/Card.svelte +18 -14
  87. package/dist/components/ui/Chip.svelte +41 -14
  88. package/dist/components/ui/Disclosure.svelte +5 -2
  89. package/dist/components/ui/Dropdown.svelte +33 -25
  90. package/dist/components/ui/IconToggle.svelte +12 -9
  91. package/dist/components/ui/IconToggle.svelte.d.ts +1 -1
  92. package/dist/components/ui/IconToggle.svelte.d.ts.map +1 -1
  93. package/dist/components/ui/IconToggleGroup.svelte +8 -5
  94. package/dist/components/ui/Pagination.svelte +11 -8
  95. package/dist/components/ui/Popover.svelte +9 -6
  96. package/dist/components/ui/Skeleton.svelte +10 -7
  97. package/dist/components/ui/Tooltip.svelte +13 -9
  98. package/dist/components/ui/Tree.svelte +12 -9
  99. package/dist/styles/layers.css +8 -0
  100. package/dist/styles/layers.d.ts +9 -0
  101. package/dist/styles/layers.d.ts.map +1 -0
  102. package/dist/styles/layers.js +8 -0
  103. package/dist/styles/tokens.css +27 -69
  104. package/dist/styles/unstyled.css +65 -0
  105. package/dist/svelte/playground/BaseControlsPreview.svelte +3 -0
  106. package/dist/svelte/playground/CalendarPreview.svelte +5 -2
  107. package/dist/svelte/playground/CapturePreview.svelte +3 -0
  108. package/dist/svelte/playground/CodeSelectorsPreview.svelte +3 -0
  109. package/dist/svelte/playground/CollectionsPreview.svelte +3 -0
  110. package/dist/svelte/playground/DataTablePreview.svelte +3 -0
  111. package/dist/svelte/playground/FeedbackPreview.svelte +3 -0
  112. package/dist/svelte/playground/InteractiveControlsPreview.svelte +3 -0
  113. package/dist/svelte/playground/StylelessPreview.svelte +110 -0
  114. package/dist/svelte/playground/StylelessPreview.svelte.d.ts +4 -0
  115. package/dist/svelte/playground/StylelessPreview.svelte.d.ts.map +1 -0
  116. package/dist/svelte/playground.d.ts.map +1 -1
  117. package/dist/svelte/playground.js +10 -0
  118. package/dist/test-support/setup.js +9 -0
  119. package/dist/theme/ThemeProvider.svelte +3 -0
  120. package/dist/themes/CUSTOM_THEME_GUIDE.md +40 -0
  121. package/dist/themes/README.md +151 -0
  122. package/dist/themes/ThemeProvider.svelte +181 -12
  123. package/dist/themes/ThemeProvider.svelte.d.ts +7 -2
  124. package/dist/themes/ThemeProvider.svelte.d.ts.map +1 -1
  125. package/dist/themes/components/ColorSchemeToggle.svelte +17 -14
  126. package/dist/themes/components/ThemeSwitcher.svelte +14 -11
  127. package/dist/themes/create-theme.d.ts +22 -1
  128. package/dist/themes/create-theme.d.ts.map +1 -1
  129. package/dist/themes/create-theme.js +100 -9
  130. package/dist/themes/css-generator.d.ts.map +1 -1
  131. package/dist/themes/css-generator.js +20 -20
  132. package/dist/themes/derive.d.ts +31 -0
  133. package/dist/themes/derive.d.ts.map +1 -0
  134. package/dist/themes/derive.js +183 -0
  135. package/dist/themes/glass/index.d.ts.map +1 -1
  136. package/dist/themes/glass/index.js +127 -14
  137. package/dist/themes/happyvertical/index.d.ts.map +1 -1
  138. package/dist/themes/happyvertical/index.js +101 -20
  139. package/dist/themes/index.d.ts +2 -2
  140. package/dist/themes/index.d.ts.map +1 -1
  141. package/dist/themes/index.js +1 -1
  142. package/dist/themes/material/index.d.ts.map +1 -1
  143. package/dist/themes/material/index.js +129 -28
  144. package/dist/themes/required-tokens.generated.json +201 -0
  145. package/dist/themes/shared.d.ts +35 -0
  146. package/dist/themes/shared.d.ts.map +1 -1
  147. package/dist/themes/shared.js +32 -0
  148. package/dist/themes/smrt/index.d.ts +3 -0
  149. package/dist/themes/smrt/index.d.ts.map +1 -1
  150. package/dist/themes/smrt/index.js +108 -26
  151. package/dist/themes/studio/index.d.ts.map +1 -1
  152. package/dist/themes/studio/index.js +116 -40
  153. package/dist/themes/styles/base.css +229 -1
  154. package/dist/themes/styles/glass.css +107 -20
  155. package/dist/themes/styles/happyvertical.css +111 -15
  156. package/dist/themes/styles/material.css +127 -35
  157. package/dist/themes/styles/smrt.css +126 -31
  158. package/dist/themes/styles/studio.css +132 -37
  159. package/dist/themes/theme-script.d.ts +3 -1
  160. package/dist/themes/theme-script.d.ts.map +1 -1
  161. package/dist/themes/theme-script.js +7 -2
  162. package/dist/themes/types.d.ts +118 -6
  163. package/dist/themes/types.d.ts.map +1 -1
  164. package/dist/utils/theme/typography.d.ts +21 -21
  165. package/dist/utils/theme/typography.js +21 -21
  166. package/package.json +8 -2
@@ -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,16 +111,19 @@ 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); }
122
124
  .file-picker.disabled { opacity: .5; cursor: not-allowed; } input { position: absolute; width: 1px; height: 1px; opacity: 0; }
123
- input:focus-visible ~ .label { outline: 2px solid var(--smrt-color-primary); outline-offset: 3px; }
125
+ input:focus-visible ~ .label { outline: var(--smrt-focus-ring-width) solid var(--smrt-focus-ring-color); outline-offset: var(--smrt-focus-ring-offset); }
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
@@ -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,46 +218,52 @@ 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;
228
230
  width: 100%;
229
231
  padding: 0.5rem 0.75rem;
230
232
  font-size: var(--smrt-typography-body-medium-size, 0.875rem);
233
+ font-weight: var(--smrt-control-weight);
231
234
  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);
235
- border-radius: var(--smrt-radius-small, 0.375rem);
235
+ color: var(--smrt-color-on-surface);
236
+ background-color: var(--smrt-surface-control);
237
+ border: var(--smrt-hairline-strong);
238
+ border-radius: var(--smrt-control-radius);
236
239
  transition:
237
240
  border-color var(--smrt-duration-short2, 150ms) var(--smrt-easing-standard, ease-in-out),
238
241
  box-shadow var(--smrt-duration-short2, 150ms) var(--smrt-easing-standard, ease-in-out);
239
242
  }
240
243
 
241
244
  .input:focus {
242
- 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
+ }
247
+
248
+ .input:focus-visible {
249
+ outline: var(--smrt-focus-ring-width) solid var(--smrt-focus-ring-color);
250
+ outline-offset: var(--smrt-focus-ring-offset);
245
251
  }
246
252
 
247
253
  .input[data-smrt-highlighted='true'] {
248
- outline: 3px solid var(--smrt-color-tertiary, #7d5260);
254
+ outline: 3px solid var(--smrt-color-tertiary);
249
255
  outline-offset: 3px;
250
- box-shadow: 0 0 0 6px color-mix(in srgb, var(--smrt-color-tertiary, #7d5260) 20%, transparent);
256
+ box-shadow: 0 0 0 6px color-mix(in srgb, var(--smrt-color-tertiary) 20%, transparent);
251
257
  }
252
258
 
253
259
  .input:disabled {
254
- background-color: var(--smrt-color-surface-container-high, #f3f4f6);
260
+ background-color: var(--smrt-color-surface-container-high);
255
261
  cursor: not-allowed;
256
- opacity: 0.7;
262
+ opacity: var(--smrt-state-disabled);
257
263
  }
258
264
 
259
265
  .input::placeholder {
260
- color: var(--smrt-color-on-surface-variant, #9ca3af);
266
+ color: var(--smrt-color-on-surface-variant);
261
267
  }
262
268
 
263
269
  /* Phones: 16px text so iOS Safari doesn't zoom the page when the field
@@ -275,7 +281,8 @@ export function getElement(): HTMLInputElement | null {
275
281
  }
276
282
  }
277
283
 
278
- .input[data-density='touch'] { --smrt-control-target-min: var(--smrt-touch-target-min, 48px); }
284
+ .input[data-density='touch'] { --smrt-control-target-min: var(--smrt-touch-target-min); }
279
285
  .input[data-density='comfortable'] { --smrt-control-target-min: 0px; }
280
- .input { min-height: var(--smrt-control-target-min, 0px); }
286
+ .input { min-height: max(var(--smrt-control-height), var(--smrt-control-target-min, 0px)); }
287
+ }
281
288
  </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; }
20
- .input-group:focus-within { outline: 2px solid var(--smrt-color-primary); outline-offset: 1px; }
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; }
22
+ .input-group:focus-within { outline: var(--smrt-focus-ring-width) solid var(--smrt-focus-ring-color); outline-offset: var(--smrt-focus-ring-offset); }
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
- 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); }
170
+ button[aria-selected='true'] { background: var(--smrt-color-secondary-container); color: var(--smrt-color-on-secondary-container); } button:focus-visible { outline: var(--smrt-focus-ring-width) solid var(--smrt-focus-ring-color); outline-offset: var(--smrt-focus-ring-offset); }
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; }
@@ -41,9 +43,10 @@ function handleChange(event: Event & { currentTarget: HTMLInputElement }) {
41
43
  .radio__mark::after { content: ''; width: .55rem; height: .55rem; border-radius: 50%; background: var(--smrt-color-primary); transform: scale(0); transition: transform var(--smrt-duration-short2); }
42
44
  input:checked + .radio__mark { border-color: var(--smrt-color-primary); }
43
45
  input:checked + .radio__mark::after { transform: scale(1); }
44
- input:focus-visible + .radio__mark { outline: 2px solid var(--smrt-color-primary); outline-offset: 3px; }
46
+ input:focus-visible + .radio__mark { outline: var(--smrt-focus-ring-width) solid var(--smrt-focus-ring-color); outline-offset: var(--smrt-focus-ring-offset); }
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); }
@@ -200,6 +202,7 @@ useControlRegistration(() => {
200
202
  .range-slider__inputs { display: flex; gap: var(--smrt-spacing-3); }
201
203
  .range-slider__inputs label { display: grid; gap: var(--smrt-spacing-1); flex: 1; font: var(--smrt-typography-label-small-font); color: var(--smrt-color-on-surface-variant); }
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
- input:focus-visible { outline: 2px solid var(--smrt-color-primary); outline-offset: 3px; }
205
+ input:focus-visible { outline: var(--smrt-focus-ring-width) solid var(--smrt-focus-ring-color); outline-offset: var(--smrt-focus-ring-offset); }
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;
@@ -194,8 +196,8 @@ onDestroy(cancel);
194
196
  color: var(--smrt-color-on-surface);
195
197
  }
196
198
  .search-input__clear:focus-visible {
197
- outline: 2px solid var(--smrt-color-primary);
198
- outline-offset: -2px;
199
+ outline: var(--smrt-focus-ring-width) solid var(--smrt-focus-ring-color);
200
+ outline-offset: calc(var(--smrt-focus-ring-width) * -1);
199
201
  }
200
202
  /* Phones: full width. */
201
203
  @media (max-width: 48rem) {
@@ -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,15 +250,18 @@ 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; }
256
258
  label:last-child { border-right: 0; } label.selected { background: var(--smrt-color-secondary-container); color: var(--smrt-color-on-secondary-container); }
257
- 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; }
259
+ label:has(input:focus-visible) { outline: var(--smrt-focus-ring-width) solid var(--smrt-focus-ring-color); outline-offset: calc(var(--smrt-focus-ring-width) * -1); } 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>