@threadlabs/looma 0.22.14 → 0.23.1

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 (217) hide show
  1. package/components/shared/icons.js +2 -0
  2. package/components/ui-avatar/ui-avatar.html +3 -3
  3. package/components/ui-avatar-group/ui-avatar-group.html +1 -1
  4. package/components/ui-badge/ui-badge.html +1 -1
  5. package/components/ui-button/ui-button.html +3 -3
  6. package/components/ui-callout/ui-callout.html +3 -2
  7. package/components/ui-card/ui-card.html +2 -2
  8. package/components/ui-checkbox/ui-checkbox.html +3 -3
  9. package/components/ui-combobox/ui-combobox.html +8 -8
  10. package/components/ui-context-menu/ui-context-menu.html +2 -2
  11. package/components/ui-description-list/ui-description-list.html +1 -1
  12. package/components/ui-dialog/ui-dialog.html +3 -3
  13. package/components/ui-disclosure/ui-disclosure.html +43 -3
  14. package/components/ui-editable/ui-editable.html +2 -2
  15. package/components/ui-editor-insert-table-grid/ui-editor-insert-table-grid.html +5 -5
  16. package/components/ui-editor-mention-menu/ui-editor-mention-menu.html +2 -2
  17. package/components/ui-editor-slash-menu/ui-editor-slash-menu.html +6 -6
  18. package/components/ui-editor-slash-menu-item/ui-editor-slash-menu-item.html +1 -1
  19. package/components/ui-editor-table-context-menu/ui-editor-table-context-menu.html +2 -2
  20. package/components/ui-editor-table-overlay/ui-editor-table-overlay.html +11 -11
  21. package/components/ui-editor-table-toolbar/ui-editor-table-toolbar.html +5 -5
  22. package/components/ui-editor-toolbar/ui-editor-toolbar.html +4 -4
  23. package/components/ui-floating-action-button/ui-floating-action-button.html +2 -2
  24. package/components/ui-icon/ui-icon.html +2 -0
  25. package/components/ui-icon-button/ui-icon-button.html +1 -1
  26. package/components/ui-input/ui-input.html +8 -4
  27. package/components/ui-input-group/ui-input-group.html +10 -6
  28. package/components/ui-list/ui-list.html +1 -1
  29. package/components/ui-list-item/ui-list-item.html +20 -4
  30. package/components/ui-listbox/ui-listbox.html +3 -3
  31. package/components/ui-menu/ui-menu.html +2 -2
  32. package/components/ui-menu-item/ui-menu-item.html +4 -4
  33. package/components/ui-meter/ui-meter.html +1 -1
  34. package/components/ui-nav-item/ui-nav-item.html +46 -14
  35. package/components/ui-popover/ui-popover.html +1 -1
  36. package/components/ui-radio/ui-radio.html +2 -2
  37. package/components/ui-search-result-row/ui-search-result-row.html +24 -5
  38. package/components/ui-search-shell/ui-search-shell.html +4 -4
  39. package/components/ui-section/ui-section.html +3 -3
  40. package/components/ui-select/ui-select.html +6 -2
  41. package/components/ui-separator/ui-separator.html +2 -2
  42. package/components/ui-sidebar/ui-sidebar.html +3 -3
  43. package/components/ui-status-message/ui-status-message.html +1 -1
  44. package/components/ui-switch/ui-switch.html +3 -3
  45. package/components/ui-table/ui-table.html +3 -3
  46. package/components/ui-tabs/ui-tabs.html +7 -7
  47. package/components/ui-textarea/ui-textarea.html +6 -2
  48. package/components/ui-toast/ui-toast.html +2 -2
  49. package/components/ui-toast-region/ui-toast-region.html +5 -5
  50. package/components/ui-tooltip/ui-tooltip.html +15 -15
  51. package/components/ui-top-bar/ui-top-bar.html +1 -1
  52. package/components/ui-tree-item/ui-tree-item.html +27 -8
  53. package/dist/index.js +1 -1
  54. package/editor/icons.d.ts +2 -0
  55. package/editor/index.js +3 -1
  56. package/package.json +1 -1
  57. package/styles/ui-avatar-group.css +1 -1
  58. package/styles/ui-avatar.css +3 -3
  59. package/styles/ui-badge.css +1 -1
  60. package/styles/ui-button.css +3 -3
  61. package/styles/ui-callout.css +3 -2
  62. package/styles/ui-card.css +2 -2
  63. package/styles/ui-checkbox.css +3 -3
  64. package/styles/ui-combobox.css +8 -8
  65. package/styles/ui-context-menu.css +2 -2
  66. package/styles/ui-description-list.css +1 -1
  67. package/styles/ui-dialog.css +3 -3
  68. package/styles/ui-disclosure.css +42 -2
  69. package/styles/ui-editable.css +2 -2
  70. package/styles/ui-editor-insert-table-grid.css +5 -5
  71. package/styles/ui-editor-mention-menu.css +2 -2
  72. package/styles/ui-editor-slash-menu-item.css +1 -1
  73. package/styles/ui-editor-slash-menu.css +6 -6
  74. package/styles/ui-editor-table-context-menu.css +2 -2
  75. package/styles/ui-editor-table-overlay.css +11 -11
  76. package/styles/ui-editor-table-toolbar.css +5 -5
  77. package/styles/ui-editor-toolbar.css +4 -4
  78. package/styles/ui-floating-action-button.css +2 -2
  79. package/styles/ui-icon-button.css +1 -1
  80. package/styles/ui-input-group.css +10 -6
  81. package/styles/ui-input.css +8 -4
  82. package/styles/ui-list-item.css +23 -4
  83. package/styles/ui-list.css +1 -1
  84. package/styles/ui-listbox.css +3 -3
  85. package/styles/ui-menu-item.css +4 -4
  86. package/styles/ui-menu.css +2 -2
  87. package/styles/ui-meter.css +1 -1
  88. package/styles/ui-nav-item.css +46 -12
  89. package/styles/ui-popover.css +1 -1
  90. package/styles/ui-radio.css +2 -2
  91. package/styles/ui-search-result-row.css +29 -5
  92. package/styles/ui-search-shell.css +4 -4
  93. package/styles/ui-section.css +3 -3
  94. package/styles/ui-select.css +6 -2
  95. package/styles/ui-separator.css +2 -2
  96. package/styles/ui-sidebar.css +3 -3
  97. package/styles/ui-status-message.css +1 -1
  98. package/styles/ui-switch.css +3 -3
  99. package/styles/ui-table.css +3 -3
  100. package/styles/ui-tabs.css +7 -7
  101. package/styles/ui-textarea.css +6 -2
  102. package/styles/ui-toast-region.css +5 -5
  103. package/styles/ui-toast.css +2 -2
  104. package/styles/ui-tooltip.css +15 -15
  105. package/styles/ui-top-bar.css +1 -1
  106. package/styles/ui-tree-item.css +32 -8
  107. package/tokens.css +30 -9
  108. package/vanilla/UiDisclosure.d.ts +1 -0
  109. package/vanilla/UiDisclosure.js +10 -1
  110. package/vanilla/UiIcon.js +1 -1
  111. package/vanilla/UiNavItem.d.ts +2 -0
  112. package/vanilla/UiNavItem.js +1 -1
  113. package/vue/UiAvatar.vue +3 -3
  114. package/vue/UiAvatarGroup.vue +1 -1
  115. package/vue/UiBadge.vue +2 -1
  116. package/vue/UiButton.vue +3 -3
  117. package/vue/UiCallout.vue +2 -2
  118. package/vue/UiCard.vue +2 -2
  119. package/vue/UiCheckbox.vue +3 -3
  120. package/vue/UiCombobox.vue +11 -8
  121. package/vue/UiContextMenu.vue +2 -2
  122. package/vue/UiDescriptionList.vue +1 -1
  123. package/vue/UiDialog.vue +4 -3
  124. package/vue/UiDisclosure.d.ts +13 -2
  125. package/vue/UiDisclosure.vue +61 -4
  126. package/vue/UiEditable.vue +2 -2
  127. package/vue/UiEditorInsertTableGrid.vue +5 -5
  128. package/vue/UiEditorMentionMenu.vue +2 -2
  129. package/vue/UiEditorSlashMenu.vue +6 -6
  130. package/vue/UiEditorSlashMenuItem.vue +1 -1
  131. package/vue/UiEditorTableContextMenu.vue +2 -2
  132. package/vue/UiEditorTableOverlay.vue +11 -11
  133. package/vue/UiEditorTableToolbar.vue +5 -5
  134. package/vue/UiEditorToolbar.vue +6 -4
  135. package/vue/UiFloatingActionButton.vue +2 -2
  136. package/vue/UiIcon.vue +8 -0
  137. package/vue/UiIconButton.vue +1 -1
  138. package/vue/UiInput.vue +7 -4
  139. package/vue/UiInputGroup.vue +6 -6
  140. package/vue/UiList.vue +1 -1
  141. package/vue/UiListItem.vue +20 -4
  142. package/vue/UiListbox.vue +3 -3
  143. package/vue/UiMenu.vue +3 -2
  144. package/vue/UiMenuItem.vue +4 -4
  145. package/vue/UiMeter.vue +1 -1
  146. package/vue/UiNavItem.d.ts +18 -0
  147. package/vue/UiNavItem.vue +96 -12
  148. package/vue/UiPopover.vue +2 -1
  149. package/vue/UiRadio.vue +2 -2
  150. package/vue/UiSearchResultRow.vue +25 -5
  151. package/vue/UiSearchShell.vue +4 -4
  152. package/vue/UiSection.vue +3 -3
  153. package/vue/UiSelect.vue +3 -2
  154. package/vue/UiSeparator.vue +4 -2
  155. package/vue/UiSidebar.vue +5 -3
  156. package/vue/UiStatusMessage.vue +1 -1
  157. package/vue/UiSwitch.vue +3 -3
  158. package/vue/UiTable.vue +3 -3
  159. package/vue/UiTabs.vue +8 -7
  160. package/vue/UiTextarea.vue +2 -2
  161. package/vue/UiToast.vue +4 -2
  162. package/vue/UiToastRegion.vue +5 -5
  163. package/vue/UiTooltip.vue +19 -15
  164. package/vue/UiTopBar.vue +1 -1
  165. package/vue/UiTreeItem.vue +31 -8
  166. package/vue/chunks/UiAvatar.js +1 -1
  167. package/vue/chunks/UiAvatarGroup.js +1 -1
  168. package/vue/chunks/UiBadge.js +1 -1
  169. package/vue/chunks/UiButton.js +1 -1
  170. package/vue/chunks/UiCallout.js +1 -1
  171. package/vue/chunks/UiCard.js +1 -1
  172. package/vue/chunks/UiCheckbox.js +1 -1
  173. package/vue/chunks/UiCombobox.js +1 -1
  174. package/vue/chunks/UiContextMenu.js +1 -1
  175. package/vue/chunks/UiDescriptionList.js +1 -1
  176. package/vue/chunks/UiDialog.js +1 -1
  177. package/vue/chunks/UiDisclosure.js +27 -4
  178. package/vue/chunks/UiEditable.js +1 -1
  179. package/vue/chunks/UiEditorInsertTableGrid.js +1 -1
  180. package/vue/chunks/UiEditorMentionMenu.js +1 -1
  181. package/vue/chunks/UiEditorSlashMenu.js +1 -1
  182. package/vue/chunks/UiEditorSlashMenuItem.js +1 -1
  183. package/vue/chunks/UiEditorTableContextMenu.js +1 -1
  184. package/vue/chunks/UiEditorTableOverlay.js +1 -1
  185. package/vue/chunks/UiEditorTableToolbar.js +1 -1
  186. package/vue/chunks/UiEditorToolbar.js +1 -1
  187. package/vue/chunks/UiFloatingActionButton.js +1 -1
  188. package/vue/chunks/UiIcon.js +24 -1
  189. package/vue/chunks/UiIconButton.js +1 -1
  190. package/vue/chunks/UiInput.js +1 -1
  191. package/vue/chunks/UiInputGroup.js +1 -1
  192. package/vue/chunks/UiList.js +1 -1
  193. package/vue/chunks/UiListItem.js +1 -1
  194. package/vue/chunks/UiListbox.js +1 -1
  195. package/vue/chunks/UiMenu.js +1 -1
  196. package/vue/chunks/UiMenuItem.js +1 -1
  197. package/vue/chunks/UiMeter.js +1 -1
  198. package/vue/chunks/UiNavItem.js +65 -6
  199. package/vue/chunks/UiPopover.js +1 -1
  200. package/vue/chunks/UiRadio.js +1 -1
  201. package/vue/chunks/UiSearchResultRow.js +1 -1
  202. package/vue/chunks/UiSearchShell.js +1 -1
  203. package/vue/chunks/UiSection.js +1 -1
  204. package/vue/chunks/UiSelect.js +1 -1
  205. package/vue/chunks/UiSeparator.js +1 -1
  206. package/vue/chunks/UiSidebar.js +1 -1
  207. package/vue/chunks/UiStatusMessage.js +1 -1
  208. package/vue/chunks/UiSwitch.js +1 -1
  209. package/vue/chunks/UiTable.js +1 -1
  210. package/vue/chunks/UiTabs.js +1 -1
  211. package/vue/chunks/UiTextarea.js +1 -1
  212. package/vue/chunks/UiToast.js +1 -1
  213. package/vue/chunks/UiToastRegion.js +1 -1
  214. package/vue/chunks/UiTooltip.js +1 -1
  215. package/vue/chunks/UiTopBar.js +1 -1
  216. package/vue/chunks/UiTreeItem.js +1 -1
  217. package/vue/components.css +1348 -1208
package/vue/UiNavItem.vue CHANGED
@@ -23,10 +23,19 @@ const propValidityContract = {
23
23
  required: false,
24
24
  target: { attribute: 'current' },
25
25
  description:
26
- 'Marks the item as where you are: it takes the selected surface, a stronger label, and a bar on its start edge, and states aria-current, which assistive\n technology announces. "true" means the current page (aria-current="page"); "step" marks the current step of a process, and "location" the current place in\n a set, such as a section of a record. Set it on one item at a time.',
26
+ 'Marks the item as where you are: it takes the selected surface, the selected text, and the chosen surface or line treatment, and states aria-current, which\n assistive technology announces. "true" means the current page (aria-current="page"); "step" marks the current step of a process, and "location" the\n current place in a set, such as a section of a record. Set it on one item at a time.',
27
27
  values: ['true', 'false', 'page', 'step', 'location'],
28
28
  default: 'false',
29
29
  },
30
+ density: {
31
+ type: { kind: 'terminal', name: 'keyword' },
32
+ required: false,
33
+ target: { attribute: 'density' },
34
+ description:
35
+ 'Compact uses smaller type and row spacing to sit alongside a compact tree; comfortable keeps the regular navigation row.',
36
+ values: ['comfortable', 'compact'],
37
+ default: 'comfortable',
38
+ },
30
39
  href: {
31
40
  type: { kind: 'terminal', name: 'string' },
32
41
  required: false,
@@ -47,6 +56,15 @@ const propValidityContract = {
47
56
  target: { attribute: 'target' },
48
57
  description: 'Where a link opens, as on a native link. Only meaningful with as="a".',
49
58
  },
59
+ variant: {
60
+ type: { kind: 'terminal', name: 'keyword' },
61
+ required: false,
62
+ target: { attribute: 'variant' },
63
+ description:
64
+ 'surface marks the current row with a quiet filled surface and shared corners. line uses square corners and a continuous logical start edge. Both retain the\n resting label weight and expose aria-current.',
65
+ values: ['surface', 'line'],
66
+ default: 'surface',
67
+ },
50
68
  },
51
69
  } as const
52
70
  const props = defineProps({
@@ -55,16 +73,23 @@ const props = defineProps({
55
73
  type: null as unknown as PropType<'true' | 'false' | 'page' | 'step' | 'location' | null>,
56
74
  default: 'false',
57
75
  },
76
+ density: {
77
+ type: null as unknown as PropType<'comfortable' | 'compact' | null>,
78
+ default: 'comfortable',
79
+ },
58
80
  href: { type: null as unknown as PropType<string | null>, default: null },
59
81
  rel: { type: null as unknown as PropType<string | null>, default: null },
60
82
  target: { type: null as unknown as PropType<string | null>, default: null },
83
+ variant: { type: null as unknown as PropType<'surface' | 'line' | null>, default: 'surface' },
61
84
  })
62
85
  const acceptedProps: Record<string, unknown> = {
63
86
  as: 'button',
64
87
  current: 'false',
88
+ density: 'comfortable',
65
89
  href: null,
66
90
  rel: null,
67
91
  target: null,
92
+ variant: 'surface',
68
93
  }
69
94
  const checkedProps = computed(() => ({
70
95
  as: checkedProp<'button' | 'a'>(
@@ -81,6 +106,13 @@ const checkedProps = computed(() => ({
81
106
  'current',
82
107
  acceptedProps,
83
108
  ) as 'true' | 'false' | 'page' | 'step' | 'location',
109
+ density: checkedProp<'comfortable' | 'compact'>(
110
+ props['density'],
111
+ { kind: 'terminal', name: 'keyword' },
112
+ false,
113
+ 'density',
114
+ acceptedProps,
115
+ ) as 'comfortable' | 'compact',
84
116
  href: checkedProp<string>(
85
117
  props['href'],
86
118
  { kind: 'terminal', name: 'string' },
@@ -102,6 +134,13 @@ const checkedProps = computed(() => ({
102
134
  'target',
103
135
  acceptedProps,
104
136
  ),
137
+ variant: checkedProp<'surface' | 'line'>(
138
+ props['variant'],
139
+ { kind: 'terminal', name: 'keyword' },
140
+ false,
141
+ 'variant',
142
+ acceptedProps,
143
+ ) as 'surface' | 'line',
105
144
  }))
106
145
  void checkedProps.value
107
146
  watchSyncEffect(() => {
@@ -162,6 +201,18 @@ if (
162
201
  )
163
202
  }
164
203
 
204
+ /** The values the styles' :host-state() rules test. */
205
+ const hostState = computed(() =>
206
+ [
207
+ checkedProps.value.variant && 'variant',
208
+ checkedProps.value.variant != null
209
+ && `variant=${encodeURIComponent(checkedProps.value.variant)}`,
210
+ checkedProps.value.density && 'density',
211
+ checkedProps.value.density != null
212
+ && `density=${encodeURIComponent(checkedProps.value.density)}`,
213
+ ].filter(Boolean).join(' ')
214
+ )
215
+
165
216
  function text(value: unknown): string {
166
217
  if (Array.isArray(value)) return value.map(text).join(' ')
167
218
  return value === null || value === undefined || typeof value === 'object' ? '' : String(value)
@@ -206,9 +257,12 @@ function attribute(value: unknown): any {
206
257
  v-prop-validity="{ contract: propValidityContract, values: { ...props } }"
207
258
  :data-as="reflectedProp('as', 'as', checkedProps.as, undefined, false)"
208
259
  :data-current="reflectedProp('current', 'current', checkedProps.current, undefined, false)"
260
+ :data-density="reflectedProp('density', 'density', checkedProps.density, undefined, false)"
209
261
  :data-href="reflectedProp('href', 'href', checkedProps.href, undefined, false)"
210
262
  :data-rel="reflectedProp('rel', 'rel', checkedProps.rel, undefined, false)"
211
263
  :data-target="reflectedProp('target', 'target', checkedProps.target, undefined, false)"
264
+ :data-variant="reflectedProp('variant', 'variant', checkedProps.variant, undefined, false)"
265
+ :data-ui-nav-item-state="hostState || undefined"
212
266
  ref="@html-next/root"
213
267
  ><span class="indicator" aria-hidden="true"></span>
214
268
  <span class="leading"><slot name="leading" /></span>
@@ -241,9 +295,12 @@ function attribute(value: unknown): any {
241
295
  v-prop-validity="{ contract: propValidityContract, values: { ...props } }"
242
296
  :data-as="reflectedProp('as', 'as', checkedProps.as, undefined, false)"
243
297
  :data-current="reflectedProp('current', 'current', checkedProps.current, undefined, false)"
298
+ :data-density="reflectedProp('density', 'density', checkedProps.density, undefined, false)"
244
299
  :data-href="reflectedProp('href', 'href', checkedProps.href, undefined, false)"
245
300
  :data-rel="reflectedProp('rel', 'rel', checkedProps.rel, undefined, false)"
246
301
  :data-target="reflectedProp('target', 'target', checkedProps.target, undefined, false)"
302
+ :data-variant="reflectedProp('variant', 'variant', checkedProps.variant, undefined, false)"
303
+ :data-ui-nav-item-state="hostState || undefined"
247
304
  ref="@html-next/root"
248
305
  >
249
306
  <span class="indicator" aria-hidden="true"></span>
@@ -277,7 +334,7 @@ function attribute(value: unknown): any {
277
334
  belongs to. Label and description are one line each and end in an ellipsis, so every item in a
278
335
  navigation has the same height and the rail never grows sideways. */
279
336
  [data-component~="ui-nav-item"] {
280
- --_indicator-width: var(--ui-nav-item-indicator-width, 3px);
337
+ --_indicator-width: var(--ui-nav-item-indicator-width, var(--ui-accent-line-width));
281
338
  --_indicator-color: var(--ui-nav-item-indicator-color, var(--ui-accent));
282
339
  position: relative;
283
340
  box-sizing: border-box;
@@ -291,11 +348,11 @@ function attribute(value: unknown): any {
291
348
  padding-block: var(--ui-space-2);
292
349
  padding-inline: var(--ui-space-3);
293
350
  border: 0;
294
- border-radius: var(--ui-radius-md);
351
+ border-radius: var(--ui-selection-radius);
295
352
  background: transparent;
296
353
  color: var(--ui-text-secondary);
297
354
  font: inherit;
298
- font-weight: var(--ui-font-medium);
355
+ font-weight: var(--ui-font-normal);
299
356
  text-align: start;
300
357
  text-decoration: none;
301
358
  cursor: pointer;
@@ -313,32 +370,43 @@ function attribute(value: unknown): any {
313
370
  /* The ring is an outline, drawn inside the item: forced colors keep an outline where they drop a
314
371
  box shadow, and a scrolling rail cannot clip a ring that stays within the item. */
315
372
  [data-component~="ui-nav-item"]:focus-visible {
316
- outline: 2px solid var(--ui-focus-ring);
317
- outline-offset: -2px;
373
+ outline: var(--ui-focus-width) solid var(--ui-focus-ring);
374
+ outline-offset: calc(-1 * var(--ui-focus-width));
318
375
  }
319
376
 
320
377
  [data-component~="ui-nav-item"][aria-current] {
321
- background: var(--ui-nav-item-current-surface, var(--ui-accent-soft));
322
- color: var(--ui-accent-subtle-text, var(--ui-text-primary));
323
- font-weight: var(--ui-font-semibold);
378
+ background: var(--ui-nav-item-current-surface, var(--ui-selection-surface));
379
+ color: var(--ui-selection-text);
324
380
  }
325
381
 
326
- /* The bar on the start edge is a border, not a fill: forced colors replace backgrounds but keep a
382
+ [data-component~="ui-nav-item"][data-ui-nav-item-state~="variant=line"] {
383
+ border-radius: 0;
384
+ }
385
+
386
+ /* A line treatment has straight corners and a continuous edge. Surface treatment has
387
+ shared rounded corners and no edge marker; never combine the two geometries.
388
+ The line is a border, not a fill: forced colors replace backgrounds but keep a
327
389
  border, so the current item stays marked in high contrast. Logical edges mirror it in a
328
390
  right-to-left page. */
329
391
  .indicator {
330
392
  position: absolute;
331
- inset-block: var(--ui-space-2);
393
+ inset-block: 0;
332
394
  inset-inline-start: 0;
333
395
  display: none;
334
396
  border-inline-start: var(--_indicator-width) solid var(--_indicator-color);
335
397
  }
336
398
 
337
- [data-component~="ui-nav-item"][aria-current] .indicator {
399
+ [data-component~="ui-nav-item"][data-ui-nav-item-state~="variant=line"][aria-current] .indicator {
338
400
  display: block;
339
401
  }
340
402
 
341
403
  @media (forced-colors: active) {
404
+ /* System colors may erase the selected fill. Trace the complete surface, without
405
+ creating an edge stripe; the stronger keyboard focus outline keeps precedence. */
406
+ [data-component~="ui-nav-item"][data-ui-nav-item-state~="variant=surface"][aria-current]:not(:focus-visible) {
407
+ outline: var(--ui-border-width) solid Highlight;
408
+ outline-offset: calc(-1 * var(--ui-border-width));
409
+ }
342
410
  .indicator {
343
411
  border-inline-start-color: Highlight;
344
412
  }
@@ -381,6 +449,22 @@ function attribute(value: unknown): any {
381
449
  line-height: var(--ui-line-height-tight);
382
450
  }
383
451
 
452
+ [data-component~="ui-nav-item"][data-ui-nav-item-state~="density=compact"] {
453
+ min-block-size: var(--ui-control-size-sm);
454
+ padding-block: var(--ui-space-1);
455
+ padding-inline: var(--ui-space-2);
456
+ }
457
+ [data-component~="ui-nav-item"][data-ui-nav-item-state~="density=compact"] .leading,
458
+ [data-component~="ui-nav-item"][data-ui-nav-item-state~="density=compact"] .label {
459
+ font-size: var(--ui-font-size-sm);
460
+ }
461
+
462
+ @media (pointer: coarse) {
463
+ [data-component~="ui-nav-item"][data-ui-nav-item-state~="density=compact"] {
464
+ min-block-size: var(--ui-control-min-block-size);
465
+ }
466
+ }
467
+
384
468
  /* An unused region takes no space. */
385
469
  .leading:empty,
386
470
  .description:empty {
package/vue/UiPopover.vue CHANGED
@@ -339,7 +339,8 @@ useComponentHost(() => import('../components/ui-popover/ui-popover.js'), {
339
339
  overflow: auto;
340
340
  padding: var(--_ui-popover-padding, var(--ui-space-3));
341
341
  background: var(--_ui-popover-surface, var(--ui-overlay-surface, var(--ui-surface-elevated)));
342
- border: 1px solid var(--_ui-popover-border, var(--ui-overlay-border, var(--ui-border-strong)));
342
+ border: var(--ui-border-width) solid
343
+ var(--_ui-popover-border, var(--ui-overlay-border, var(--ui-border-strong)));
343
344
  border-radius: var(--_ui-popover-radius, var(--ui-overlay-radius, var(--ui-radius-lg)));
344
345
  box-shadow: var(
345
346
  --_ui-popover-shadow,
package/vue/UiRadio.vue CHANGED
@@ -328,8 +328,8 @@ input {
328
328
  }
329
329
 
330
330
  input:focus-visible {
331
- outline: 2px solid var(--ui-focus-ring, var(--ui-accent-solid));
332
- outline-offset: 2px;
331
+ outline: var(--ui-focus-width) solid var(--ui-focus-ring, var(--ui-accent-solid));
332
+ outline-offset: var(--ui-focus-width);
333
333
  }
334
334
 
335
335
  [data-component~="ui-radio"][data-ui-radio-state~="disabled"] {
@@ -204,8 +204,8 @@ const hostState = computed(() =>
204
204
  align-items: flex-start;
205
205
  gap: var(--ui-search-result-row-gap, var(--ui-space-3));
206
206
  background: var(--ui-search-result-row-surface, transparent);
207
- border: 1px solid var(--ui-search-result-row-border, transparent);
208
- border-radius: var(--ui-radius-md);
207
+ border: var(--ui-border-width) solid var(--ui-search-result-row-border, transparent);
208
+ border-radius: var(--ui-selection-radius);
209
209
  color: inherit;
210
210
  cursor: pointer;
211
211
  text-align: left;
@@ -221,6 +221,18 @@ const hostState = computed(() =>
221
221
  font-family: var(--ui-font-family-sans, sans-serif);
222
222
  }
223
223
 
224
+ /* Slot wrappers own their line boxes even when an app makes buttons inherit body typography. */
225
+ [data-component~="ui-search-result-row"][data-ui-search-result-row-state~="density=compact"]
226
+ .title {
227
+ font-size: var(--ui-font-size-sm);
228
+ line-height: var(--ui-line-height);
229
+ }
230
+
231
+ [data-component~="ui-search-result-row"][data-ui-search-result-row-state~="density=compact"] .meta {
232
+ font-size: var(--ui-font-size-xs);
233
+ line-height: var(--ui-line-height);
234
+ }
235
+
224
236
  [data-component~="ui-search-result-row"]:hover:enabled {
225
237
  background: var(
226
238
  --ui-search-result-row-hover-surface,
@@ -231,13 +243,14 @@ const hostState = computed(() =>
231
243
  [data-component~="ui-search-result-row"][data-ui-search-result-row-state~="selected"] {
232
244
  background: var(
233
245
  --ui-search-result-row-selected-bg,
234
- var(--ui-search-result-row-hover-surface, color-mix(in srgb, var(--ui-surface) 85%, black 3%))
246
+ var(--ui-selection-surface)
235
247
  );
248
+ color: var(--ui-selection-text);
236
249
  }
237
250
 
238
251
  [data-component~="ui-search-result-row"]:focus-visible {
239
- outline: 2px solid var(--ui-focus-ring);
240
- outline-offset: -2px;
252
+ outline: var(--ui-focus-width) solid var(--ui-focus-ring);
253
+ outline-offset: calc(-1 * var(--ui-focus-width));
241
254
  }
242
255
 
243
256
  [data-component~="ui-search-result-row"]:disabled {
@@ -282,6 +295,13 @@ const hostState = computed(() =>
282
295
  margin: 0;
283
296
  }
284
297
 
298
+ @media (forced-colors: active) {
299
+ [data-component~="ui-search-result-row"][data-ui-search-result-row-state~="selected"]:not(:focus-visible):not(:disabled) {
300
+ outline: var(--ui-border-width) solid Highlight;
301
+ outline-offset: calc(-1 * var(--ui-border-width));
302
+ }
303
+ }
304
+
285
305
  /* A component that sets its display still honours the hidden attribute on its root. */
286
306
  [data-component~="ui-search-result-row"][hidden] {
287
307
  display: none;
@@ -318,7 +318,7 @@ dialog[data-ui-backdrop-hidden]::backdrop {
318
318
  --_ui-search-shell-surface,
319
319
  var(--ui-overlay-surface, var(--ui-surface-elevated))
320
320
  );
321
- border: 1px solid
321
+ border: var(--ui-border-width) solid
322
322
  var(--_ui-search-shell-border, var(--ui-overlay-border, var(--ui-border-strong)));
323
323
  border-radius: var(--_ui-search-shell-radius, var(--ui-overlay-radius, var(--ui-radius-dialog)));
324
324
  box-shadow: var(
@@ -336,7 +336,7 @@ dialog[data-ui-backdrop-hidden]::backdrop {
336
336
  inline-size: 100%;
337
337
  min-block-size: var(--ui-control-min-block-size);
338
338
  padding: var(--ui-space-2) var(--ui-space-3);
339
- border: 1px solid var(--ui-control-border);
339
+ border: var(--ui-border-width) solid var(--ui-control-border);
340
340
  border-radius: var(--ui-field-radius, var(--ui-radius-md));
341
341
  background: var(--ui-control-surface);
342
342
  color: var(--ui-text-primary);
@@ -346,8 +346,8 @@ dialog[data-ui-backdrop-hidden]::backdrop {
346
346
 
347
347
  :slotted(input[slot="search"]:focus-visible) {
348
348
  border-color: var(--_ui-search-shell-focus-color, var(--ui-control-focus));
349
- outline: 2px solid var(--ui-focus-ring);
350
- outline-offset: 2px;
349
+ outline: var(--ui-focus-width) solid var(--ui-focus-ring);
350
+ outline-offset: var(--ui-focus-width);
351
351
  }
352
352
 
353
353
  .status,
package/vue/UiSection.vue CHANGED
@@ -166,19 +166,19 @@ const hostState = computed(() =>
166
166
 
167
167
  [data-component~="ui-section"][data-ui-section-state~="variant=card"] {
168
168
  padding: var(--ui-space-5);
169
- border: 1px solid var(--ui-border-strong);
169
+ border: var(--ui-border-width) solid var(--ui-border-strong);
170
170
  border-radius: var(--ui-radius-lg);
171
171
  background: var(--ui-surface-raised);
172
172
  }
173
173
 
174
174
  [data-component~="ui-section"][data-ui-section-state~="variant=divided"] {
175
175
  padding-block-start: var(--ui-space-6);
176
- border-block-start: 1px solid var(--ui-border-divider);
176
+ border-block-start: var(--ui-border-width) solid var(--ui-border-divider);
177
177
  }
178
178
 
179
179
  [data-component~="ui-section"][data-ui-section-state~="variant=card"][data-ui-section-state~="tone=danger"] {
180
180
  border-color: color-mix(in srgb, var(--ui-danger) 45%, var(--ui-border-default));
181
- border-inline-start: 4px solid var(--ui-danger);
181
+ border-inline-start: var(--ui-accent-line-width) solid var(--ui-danger);
182
182
  background: var(--ui-danger-soft);
183
183
  }
184
184
 
package/vue/UiSelect.vue CHANGED
@@ -278,7 +278,8 @@ useComponentHost(() => import('../components/ui-select/ui-select.js'), {
278
278
  min-block-size: var(--ui-control-size-md);
279
279
  margin: 0;
280
280
  padding: var(--ui-space-2) var(--ui-space-3);
281
- border: 1px solid var(--ui-select-border, var(--ui-control-border, var(--ui-border-strong)));
281
+ border: var(--ui-border-width) solid
282
+ var(--ui-select-border, var(--ui-control-border, var(--ui-border-strong)));
282
283
  border-radius: var(--ui-select-radius, var(--ui-field-radius, var(--ui-radius-md)));
283
284
  background-color: var(--ui-select-surface, var(--ui-control-surface, var(--ui-surface-elevated)));
284
285
  color: var(--ui-text-primary);
@@ -333,7 +334,7 @@ useComponentHost(() => import('../components/ui-select/ui-select.js'), {
333
334
  box-shadow: var(
334
335
  --ui-select-invalid-focus-shadow,
335
336
  var(--ui-control-inset),
336
- 0 0 0 3px color-mix(
337
+ 0 0 0 calc(var(--ui-focus-width) * 1.5) color-mix(
337
338
  in srgb,
338
339
  var(--ui-select-invalid-border, var(--ui-field-danger, var(--ui-danger-solid))) 22%,
339
340
  transparent
@@ -114,12 +114,14 @@ if (
114
114
  align-self: stretch;
115
115
  margin: 0;
116
116
  border: 0;
117
- border-block-start: 1px solid var(--ui-separator-color, var(--ui-border-divider));
117
+ border-block-start: var(--ui-border-width) solid
118
+ var(--ui-separator-color, var(--ui-border-divider));
118
119
  }
119
120
 
120
121
  [data-component~="ui-separator"][aria-orientation="vertical"] {
121
122
  block-size: auto;
122
123
  border-block-start: 0;
123
- border-inline-start: 1px solid var(--ui-separator-color, var(--ui-border-divider));
124
+ border-inline-start: var(--ui-border-width) solid
125
+ var(--ui-separator-color, var(--ui-border-divider));
124
126
  }
125
127
  </style>
package/vue/UiSidebar.vue CHANGED
@@ -383,12 +383,14 @@ useComponentHost(() => import('../components/ui-sidebar/ui-sidebar.js'), {
383
383
  max-inline-size: 100%;
384
384
  min-block-size: 0;
385
385
  background: var(--ui-sidebar-surface, var(--ui-surface-default));
386
- border-inline-end: 1px solid var(--ui-sidebar-border, var(--ui-border-divider));
386
+ border-inline-end: var(--ui-border-width) solid
387
+ var(--ui-sidebar-border, var(--ui-border-divider));
387
388
  }
388
389
 
389
390
  [data-component~="ui-sidebar"][data-ui-sidebar-state~="side=end"] {
390
391
  border-inline-end: 0;
391
- border-inline-start: 1px solid var(--ui-sidebar-border, var(--ui-border-divider));
392
+ border-inline-start: var(--ui-border-width) solid
393
+ var(--ui-sidebar-border, var(--ui-border-divider));
392
394
  }
393
395
 
394
396
  /* The panel's content scrolls, and each edge that hides content fades: the scroll fade every
@@ -471,7 +473,7 @@ useComponentHost(() => import('../components/ui-sidebar/ui-sidebar.js'), {
471
473
  inset-inline-start: 50%;
472
474
  inline-size: 6px;
473
475
  block-size: 22px;
474
- border: 1px solid var(--ui-border-strong);
476
+ border: var(--ui-border-width) solid var(--ui-border-strong);
475
477
  border-radius: var(--ui-radius-round);
476
478
  background: var(--ui-surface-elevated);
477
479
  opacity: 0;
@@ -192,7 +192,7 @@ function attribute(value: unknown): any {
192
192
 
193
193
  [data-component~="ui-status-message"][data-ui-status-message-state~="layout=panel"] {
194
194
  padding: var(--ui-space-6);
195
- border: 1px dashed var(--ui-border-strong);
195
+ border: var(--ui-border-width) dashed var(--ui-border-strong);
196
196
  border-radius: var(--ui-radius-lg);
197
197
  }
198
198
 
package/vue/UiSwitch.vue CHANGED
@@ -331,7 +331,7 @@ input {
331
331
  width: var(--_ui-switch-track-width, 2.5rem);
332
332
  height: var(--_ui-switch-track-height, 1.5rem);
333
333
  padding: 0;
334
- border: 1px solid var(--ui-control-border, var(--ui-border-strong));
334
+ border: var(--ui-border-width) solid var(--ui-control-border, var(--ui-border-strong));
335
335
  border-radius: var(--ui-radius-round);
336
336
  background-color: var(--ui-control-surface, var(--ui-surface-default));
337
337
  box-shadow: var(--ui-control-inset);
@@ -368,8 +368,8 @@ input:checked::after {
368
368
  }
369
369
 
370
370
  input:focus-visible {
371
- outline: 2px solid var(--ui-focus-ring, var(--ui-accent-solid));
372
- outline-offset: 2px;
371
+ outline: var(--ui-focus-width) solid var(--ui-focus-ring, var(--ui-accent-solid));
372
+ outline-offset: var(--ui-focus-width);
373
373
  }
374
374
 
375
375
  [data-component~="ui-switch"][data-ui-switch-state~="disabled"] {
package/vue/UiTable.vue CHANGED
@@ -198,8 +198,8 @@ useComponentHost(() => import('../components/ui-table/ui-table.js'), {
198
198
  }
199
199
 
200
200
  [data-component~="ui-table"]:focus-visible {
201
- outline: 2px solid var(--ui-focus-ring);
202
- outline-offset: -2px;
201
+ outline: var(--ui-focus-width) solid var(--ui-focus-ring);
202
+ outline-offset: calc(-1 * var(--ui-focus-width));
203
203
  }
204
204
 
205
205
  [data-component~="ui-table"][data-ui-table-state~="density=compact"] {
@@ -232,7 +232,7 @@ useComponentHost(() => import('../components/ui-table/ui-table.js'), {
232
232
  :slotted(td) {
233
233
  block-size: var(--_row-size);
234
234
  padding: var(--_pad-block) var(--_pad-inline);
235
- border-block-end: 1px solid var(--ui-border-subtle);
235
+ border-block-end: var(--ui-border-width) solid var(--ui-border-subtle);
236
236
  text-align: start;
237
237
  vertical-align: middle;
238
238
  }
package/vue/UiTabs.vue CHANGED
@@ -410,17 +410,17 @@ function uniqueKeys<T>(
410
410
  overflow-y: hidden;
411
411
  overscroll-behavior: contain;
412
412
  scrollbar-width: thin;
413
- border-block-end: 1px solid var(--ui-border-divider);
413
+ border-block-end: var(--ui-border-width) solid var(--ui-border-divider);
414
414
  }
415
415
 
416
416
  .list button,
417
417
  .list > :slotted(button[slot="tab"]) {
418
418
  min-block-size: var(--_ui-tabs-tab-min-block-size, var(--_tab-min-block-size));
419
- margin: 0 0 -1px;
419
+ margin: 0 0 calc(-1 * var(--ui-border-width));
420
420
  padding: var(--_ui-tabs-tab-padding-block, var(--_tab-padding-block))
421
421
  var(--_ui-tabs-tab-padding-inline, var(--_tab-padding-inline));
422
422
  border: 0;
423
- border-block-end: 2px solid transparent;
423
+ border-block-end: var(--ui-accent-line-width) solid transparent;
424
424
  background: transparent;
425
425
  color: var(--ui-text-secondary);
426
426
  font: inherit;
@@ -470,7 +470,8 @@ function uniqueKeys<T>(
470
470
  .list > :slotted(button[slot="tab"]:focus-visible) {
471
471
  position: relative;
472
472
  outline: none;
473
- box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--ui-focus-ring) 45%, transparent);
473
+ box-shadow: inset 0 0 0 var(--ui-focus-width)
474
+ color-mix(in srgb, var(--ui-focus-ring) 45%, transparent);
474
475
  border-radius: var(--ui-radius-sm);
475
476
  }
476
477
 
@@ -490,16 +491,16 @@ function uniqueKeys<T>(
490
491
  grid-auto-flow: row;
491
492
  grid-auto-columns: auto;
492
493
  border-block-end: 0;
493
- border-inline-end: 1px solid var(--ui-border-divider);
494
+ border-inline-end: var(--ui-border-width) solid var(--ui-border-divider);
494
495
  }
495
496
 
496
497
  [data-component~="ui-tabs"][data-ui-tabs-state~="orientation=vertical"] .list button,
497
498
  [data-component~="ui-tabs"][data-ui-tabs-state~="orientation=vertical"]
498
499
  .list
499
500
  > :slotted(button[slot="tab"]) {
500
- margin: 0 -1px 0 0;
501
+ margin: 0 calc(-1 * var(--ui-border-width)) 0 0;
501
502
  border-block-end: 0;
502
- border-inline-end: 2px solid transparent;
503
+ border-inline-end: var(--ui-accent-line-width) solid transparent;
503
504
  text-align: start;
504
505
  }
505
506
 
@@ -314,7 +314,7 @@ useComponentHost(() => import('../components/ui-textarea/ui-textarea.js'), {
314
314
  padding: var(--ui-space-2) var(--ui-space-3);
315
315
  appearance: none;
316
316
  resize: vertical;
317
- border: 1px solid var(--ui-control-border, var(--ui-border-strong));
317
+ border: var(--ui-border-width) solid var(--ui-control-border, var(--ui-border-strong));
318
318
  border-radius: var(--ui-textarea-radius, var(--ui-field-radius, var(--ui-radius-md)));
319
319
  background: var(--ui-control-surface, var(--ui-surface-elevated));
320
320
  color: var(--ui-text-primary);
@@ -362,7 +362,7 @@ useComponentHost(() => import('../components/ui-textarea/ui-textarea.js'), {
362
362
  box-shadow: var(
363
363
  --ui-textarea-invalid-focus-shadow,
364
364
  var(--ui-control-inset),
365
- 0 0 0 3px color-mix(
365
+ 0 0 0 calc(var(--ui-focus-width) * 1.5) color-mix(
366
366
  in srgb,
367
367
  var(--ui-textarea-invalid-border, var(--ui-field-danger, var(--ui-danger-solid))) 22%,
368
368
  transparent
package/vue/UiToast.vue CHANGED
@@ -241,8 +241,10 @@ function attribute(value: unknown): any {
241
241
  inline-size: 100%;
242
242
  margin: 0;
243
243
  padding: var(--ui-space-3) var(--ui-space-4);
244
- border: 1px solid var(--ui-toast-border, var(--ui-overlay-border, var(--ui-border-strong)));
245
- border-inline-start: 3px solid var(--ui-toast-accent, var(--_ui-toast-accent));
244
+ border: var(--ui-border-width) solid
245
+ var(--ui-toast-border, var(--ui-overlay-border, var(--ui-border-strong)));
246
+ border-inline-start: var(--ui-accent-line-width) solid
247
+ var(--ui-toast-accent, var(--_ui-toast-accent));
246
248
  border-radius: var(--ui-toast-radius, var(--ui-overlay-radius, var(--ui-radius-lg)));
247
249
  background: var(--ui-toast-surface, var(--ui-overlay-surface, var(--ui-surface-elevated)));
248
250
  color: var(--ui-text-primary);
@@ -401,7 +401,7 @@ function uniqueKeys<T>(
401
401
  grid-template-columns: 1fr auto;
402
402
  gap: var(--ui-space-2);
403
403
  align-items: start;
404
- border: 1px solid
404
+ border: var(--ui-border-width) solid
405
405
  var(--_ui-toast-region-border, var(--ui-overlay-border, var(--ui-border-strong)));
406
406
  border-radius: var(--_ui-toast-region-radius, var(--ui-overlay-radius, var(--ui-radius-lg)));
407
407
  padding: var(--ui-space-3) var(--ui-space-4);
@@ -419,16 +419,16 @@ function uniqueKeys<T>(
419
419
  }
420
420
 
421
421
  .toast[data-tone="info"] {
422
- border-inline-start: 3px solid var(--ui-info-solid);
422
+ border-inline-start: var(--ui-accent-line-width) solid var(--ui-info-solid);
423
423
  }
424
424
  .toast[data-tone="success"] {
425
- border-inline-start: 3px solid var(--ui-success-solid);
425
+ border-inline-start: var(--ui-accent-line-width) solid var(--ui-success-solid);
426
426
  }
427
427
  .toast[data-tone="warning"] {
428
- border-inline-start: 3px solid var(--ui-warning-solid);
428
+ border-inline-start: var(--ui-accent-line-width) solid var(--ui-warning-solid);
429
429
  }
430
430
  .toast[data-tone="danger"] {
431
- border-inline-start: 3px solid var(--ui-danger-solid);
431
+ border-inline-start: var(--ui-accent-line-width) solid var(--ui-danger-solid);
432
432
  }
433
433
 
434
434
  /* Leaving slides on down and fades; the controller removes the toast when this ends. */