@threadlabs/looma 0.22.13 → 0.23.0

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 (257) hide show
  1. package/components/shared/icons.js +6 -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 +23 -4
  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 +6 -0
  25. package/components/ui-icon-button/ui-icon-button.html +1 -1
  26. package/components/ui-input/ui-input.html +9 -4
  27. package/components/ui-input-group/ui-input-group.html +21 -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 +16 -1
  36. package/components/ui-radio/ui-radio.html +2 -2
  37. package/components/ui-scroll-area/ui-scroll-area.html +12 -0
  38. package/components/ui-search-result-row/ui-search-result-row.html +24 -5
  39. package/components/ui-search-shell/ui-search-shell.html +4 -4
  40. package/components/ui-section/ui-section.html +3 -3
  41. package/components/ui-select/ui-select.html +6 -2
  42. package/components/ui-separator/ui-separator.html +2 -2
  43. package/components/ui-sidebar/ui-sidebar.html +3 -3
  44. package/components/ui-stack/ui-stack.html +6 -3
  45. package/components/ui-status-message/ui-status-message.html +1 -1
  46. package/components/ui-switch/ui-switch.html +3 -3
  47. package/components/ui-table/ui-table.html +3 -3
  48. package/components/ui-tabs/ui-tabs.html +7 -7
  49. package/components/ui-text/ui-text.html +12 -0
  50. package/components/ui-textarea/ui-textarea.html +6 -2
  51. package/components/ui-toast/ui-toast.html +2 -2
  52. package/components/ui-toast-region/ui-toast-region.html +5 -5
  53. package/components/ui-tooltip/ui-tooltip.html +15 -15
  54. package/components/ui-top-bar/ui-top-bar.html +1 -1
  55. package/components/ui-tree-item/ui-tree-item.html +27 -8
  56. package/dist/index.js +1 -1
  57. package/editor/chunks/extensions.js +55 -2
  58. package/editor/extensions/index.d.ts +1 -0
  59. package/editor/extensions/index.js +2 -2
  60. package/editor/extensions/preset.d.ts +2 -0
  61. package/editor/extensions/relative-links.d.ts +8 -0
  62. package/editor/icons.d.ts +6 -0
  63. package/editor/index.js +9 -3
  64. package/package.json +1 -1
  65. package/styles/ui-avatar-group.css +1 -1
  66. package/styles/ui-avatar.css +3 -3
  67. package/styles/ui-badge.css +1 -1
  68. package/styles/ui-button.css +3 -3
  69. package/styles/ui-callout.css +3 -2
  70. package/styles/ui-card.css +19 -2
  71. package/styles/ui-checkbox.css +3 -3
  72. package/styles/ui-combobox.css +8 -8
  73. package/styles/ui-context-menu.css +2 -2
  74. package/styles/ui-description-list.css +1 -1
  75. package/styles/ui-dialog.css +3 -3
  76. package/styles/ui-disclosure.css +42 -2
  77. package/styles/ui-editable.css +2 -2
  78. package/styles/ui-editor-insert-table-grid.css +5 -5
  79. package/styles/ui-editor-mention-menu.css +2 -2
  80. package/styles/ui-editor-slash-menu-item.css +1 -1
  81. package/styles/ui-editor-slash-menu.css +6 -6
  82. package/styles/ui-editor-table-context-menu.css +2 -2
  83. package/styles/ui-editor-table-overlay.css +11 -11
  84. package/styles/ui-editor-table-toolbar.css +5 -5
  85. package/styles/ui-editor-toolbar.css +4 -4
  86. package/styles/ui-floating-action-button.css +2 -2
  87. package/styles/ui-icon-button.css +1 -1
  88. package/styles/ui-input-group.css +19 -6
  89. package/styles/ui-input.css +9 -4
  90. package/styles/ui-list-item.css +23 -4
  91. package/styles/ui-list.css +1 -1
  92. package/styles/ui-listbox.css +3 -3
  93. package/styles/ui-menu-item.css +4 -4
  94. package/styles/ui-menu.css +2 -2
  95. package/styles/ui-meter.css +1 -1
  96. package/styles/ui-nav-item.css +46 -12
  97. package/styles/ui-popover.css +14 -1
  98. package/styles/ui-radio.css +2 -2
  99. package/styles/ui-scroll-area.css +10 -0
  100. package/styles/ui-search-result-row.css +25 -5
  101. package/styles/ui-search-shell.css +4 -4
  102. package/styles/ui-section.css +3 -3
  103. package/styles/ui-select.css +6 -2
  104. package/styles/ui-separator.css +2 -2
  105. package/styles/ui-sidebar.css +3 -3
  106. package/styles/ui-stack.css +3 -0
  107. package/styles/ui-status-message.css +1 -1
  108. package/styles/ui-switch.css +3 -3
  109. package/styles/ui-table.css +3 -3
  110. package/styles/ui-tabs.css +7 -7
  111. package/styles/ui-text.css +8 -0
  112. package/styles/ui-textarea.css +6 -2
  113. package/styles/ui-toast-region.css +5 -5
  114. package/styles/ui-toast.css +2 -2
  115. package/styles/ui-tooltip.css +15 -15
  116. package/styles/ui-top-bar.css +1 -1
  117. package/styles/ui-tree-item.css +32 -8
  118. package/tokens.css +30 -9
  119. package/vanilla/UiCard.d.ts +2 -1
  120. package/vanilla/UiCard.js +1 -1
  121. package/vanilla/UiDisclosure.d.ts +1 -0
  122. package/vanilla/UiDisclosure.js +10 -1
  123. package/vanilla/UiIcon.js +1 -1
  124. package/vanilla/UiInputGroup.d.ts +1 -0
  125. package/vanilla/UiInputGroup.js +1 -1
  126. package/vanilla/UiNavItem.d.ts +2 -0
  127. package/vanilla/UiNavItem.js +1 -1
  128. package/vanilla/UiPopover.d.ts +1 -0
  129. package/vanilla/UiPopover.js +1 -1
  130. package/vanilla/UiScrollArea.d.ts +1 -0
  131. package/vanilla/UiScrollArea.js +1 -1
  132. package/vanilla/UiSearchResultRow.d.ts +1 -0
  133. package/vanilla/UiSearchResultRow.js +1 -1
  134. package/vanilla/UiStack.d.ts +1 -1
  135. package/vanilla/UiStack.js +1 -1
  136. package/vanilla/UiText.d.ts +2 -0
  137. package/vanilla/UiText.js +1 -1
  138. package/vue/UiAvatar.vue +3 -3
  139. package/vue/UiAvatarGroup.vue +1 -1
  140. package/vue/UiBadge.vue +2 -1
  141. package/vue/UiButton.vue +3 -3
  142. package/vue/UiCallout.vue +2 -2
  143. package/vue/UiCard.d.ts +12 -3
  144. package/vue/UiCard.vue +51 -7
  145. package/vue/UiCheckbox.vue +3 -3
  146. package/vue/UiCombobox.vue +11 -8
  147. package/vue/UiContextMenu.vue +2 -2
  148. package/vue/UiDescriptionList.vue +1 -1
  149. package/vue/UiDialog.vue +4 -3
  150. package/vue/UiDisclosure.d.ts +13 -2
  151. package/vue/UiDisclosure.vue +61 -4
  152. package/vue/UiEditable.vue +2 -2
  153. package/vue/UiEditorInsertTableGrid.vue +5 -5
  154. package/vue/UiEditorMentionMenu.vue +2 -2
  155. package/vue/UiEditorSlashMenu.vue +6 -6
  156. package/vue/UiEditorSlashMenuItem.vue +1 -1
  157. package/vue/UiEditorTableContextMenu.vue +2 -2
  158. package/vue/UiEditorTableOverlay.vue +11 -11
  159. package/vue/UiEditorTableToolbar.vue +5 -5
  160. package/vue/UiEditorToolbar.vue +6 -4
  161. package/vue/UiFloatingActionButton.vue +2 -2
  162. package/vue/UiIcon.vue +31 -0
  163. package/vue/UiIconButton.vue +1 -1
  164. package/vue/UiInput.vue +8 -4
  165. package/vue/UiInputGroup.d.ts +9 -0
  166. package/vue/UiInputGroup.vue +39 -8
  167. package/vue/UiList.vue +1 -1
  168. package/vue/UiListItem.vue +20 -4
  169. package/vue/UiListbox.vue +3 -3
  170. package/vue/UiMenu.vue +3 -2
  171. package/vue/UiMenuItem.vue +4 -4
  172. package/vue/UiMeter.vue +1 -1
  173. package/vue/UiNavItem.d.ts +18 -0
  174. package/vue/UiNavItem.vue +96 -12
  175. package/vue/UiPopover.d.ts +9 -0
  176. package/vue/UiPopover.vue +45 -3
  177. package/vue/UiRadio.vue +2 -2
  178. package/vue/UiScrollArea.d.ts +9 -0
  179. package/vue/UiScrollArea.vue +35 -2
  180. package/vue/UiSearchResultRow.d.ts +9 -0
  181. package/vue/UiSearchResultRow.vue +51 -6
  182. package/vue/UiSearchShell.vue +4 -4
  183. package/vue/UiSection.vue +3 -3
  184. package/vue/UiSelect.vue +3 -2
  185. package/vue/UiSeparator.vue +4 -2
  186. package/vue/UiSidebar.vue +5 -3
  187. package/vue/UiStack.d.ts +3 -3
  188. package/vue/UiStack.vue +10 -4
  189. package/vue/UiStatusMessage.vue +1 -1
  190. package/vue/UiSwitch.vue +3 -3
  191. package/vue/UiTable.vue +3 -3
  192. package/vue/UiTabs.vue +8 -7
  193. package/vue/UiText.d.ts +18 -0
  194. package/vue/UiText.vue +70 -0
  195. package/vue/UiTextarea.vue +2 -2
  196. package/vue/UiToast.vue +4 -2
  197. package/vue/UiToastRegion.vue +5 -5
  198. package/vue/UiTooltip.vue +19 -15
  199. package/vue/UiTopBar.vue +1 -1
  200. package/vue/UiTreeItem.vue +31 -8
  201. package/vue/chunks/UiAvatar.js +1 -1
  202. package/vue/chunks/UiAvatarGroup.js +1 -1
  203. package/vue/chunks/UiBadge.js +1 -1
  204. package/vue/chunks/UiButton.js +1 -1
  205. package/vue/chunks/UiCallout.js +1 -1
  206. package/vue/chunks/UiCard.js +39 -3
  207. package/vue/chunks/UiCheckbox.js +1 -1
  208. package/vue/chunks/UiCombobox.js +1 -1
  209. package/vue/chunks/UiContextMenu.js +1 -1
  210. package/vue/chunks/UiDescriptionList.js +1 -1
  211. package/vue/chunks/UiDialog.js +1 -1
  212. package/vue/chunks/UiDisclosure.js +27 -4
  213. package/vue/chunks/UiEditable.js +1 -1
  214. package/vue/chunks/UiEditorInsertTableGrid.js +1 -1
  215. package/vue/chunks/UiEditorMentionMenu.js +1 -1
  216. package/vue/chunks/UiEditorSlashMenu.js +1 -1
  217. package/vue/chunks/UiEditorSlashMenuItem.js +1 -1
  218. package/vue/chunks/UiEditorTableContextMenu.js +1 -1
  219. package/vue/chunks/UiEditorTableOverlay.js +1 -1
  220. package/vue/chunks/UiEditorTableToolbar.js +1 -1
  221. package/vue/chunks/UiEditorToolbar.js +1 -1
  222. package/vue/chunks/UiFloatingActionButton.js +1 -1
  223. package/vue/chunks/UiIcon.js +92 -1
  224. package/vue/chunks/UiIconButton.js +1 -1
  225. package/vue/chunks/UiInput.js +1 -1
  226. package/vue/chunks/UiInputGroup.js +59 -22
  227. package/vue/chunks/UiList.js +1 -1
  228. package/vue/chunks/UiListItem.js +1 -1
  229. package/vue/chunks/UiListbox.js +1 -1
  230. package/vue/chunks/UiMenu.js +1 -1
  231. package/vue/chunks/UiMenuItem.js +1 -1
  232. package/vue/chunks/UiMeter.js +1 -1
  233. package/vue/chunks/UiNavItem.js +65 -6
  234. package/vue/chunks/UiPopover.js +37 -5
  235. package/vue/chunks/UiRadio.js +1 -1
  236. package/vue/chunks/UiScrollArea.js +66 -23
  237. package/vue/chunks/UiSearchResultRow.js +28 -2
  238. package/vue/chunks/UiSearchShell.js +1 -1
  239. package/vue/chunks/UiSection.js +1 -1
  240. package/vue/chunks/UiSelect.js +1 -1
  241. package/vue/chunks/UiSeparator.js +1 -1
  242. package/vue/chunks/UiSidebar.js +1 -1
  243. package/vue/chunks/UiStack.js +3 -2
  244. package/vue/chunks/UiStatusMessage.js +1 -1
  245. package/vue/chunks/UiSwitch.js +1 -1
  246. package/vue/chunks/UiTable.js +1 -1
  247. package/vue/chunks/UiTabs.js +1 -1
  248. package/vue/chunks/UiText.js +92 -4
  249. package/vue/chunks/UiTextarea.js +1 -1
  250. package/vue/chunks/UiToast.js +1 -1
  251. package/vue/chunks/UiToastRegion.js +1 -1
  252. package/vue/chunks/UiTooltip.js +1 -1
  253. package/vue/chunks/UiTopBar.js +1 -1
  254. package/vue/chunks/UiTreeItem.js +1 -1
  255. package/vue/components.css +1431 -1429
  256. package/vue/editor/LoomaEditor.d.ts +11 -0
  257. package/vue/editor/index.js +261 -145
@@ -17,12 +17,22 @@ const propValidityContract = {
17
17
  'Fades the group\'s framing to match a disabled input inside it. Disable the input itself too.',
18
18
  default: false,
19
19
  },
20
+ prefixKind: {
21
+ type: { kind: 'terminal', name: 'keyword' },
22
+ required: false,
23
+ target: { attribute: 'prefixkind' },
24
+ description:
25
+ 'Text affixes have their own framing. An icon prefix shares the field surface and uses one compact icon-to-value gap.',
26
+ values: ['text', 'icon'],
27
+ default: 'text',
28
+ },
20
29
  },
21
30
  } as const
22
31
  const props = defineProps({
23
32
  disabled: { type: null as unknown as PropType<boolean | null>, default: false },
33
+ prefixKind: { type: null as unknown as PropType<'text' | 'icon' | null>, default: 'text' },
24
34
  })
25
- const acceptedProps: Record<string, unknown> = { disabled: false }
35
+ const acceptedProps: Record<string, unknown> = { disabled: false, prefixKind: 'text' }
26
36
  const checkedProps = computed(() => ({
27
37
  disabled: checkedProp<boolean>(
28
38
  props['disabled'],
@@ -31,6 +41,13 @@ const checkedProps = computed(() => ({
31
41
  'disabled',
32
42
  acceptedProps,
33
43
  ) as boolean,
44
+ prefixKind: checkedProp<'text' | 'icon'>(
45
+ props['prefixKind'],
46
+ { kind: 'terminal', name: 'keyword' },
47
+ false,
48
+ 'prefixKind',
49
+ acceptedProps,
50
+ ) as 'text' | 'icon',
34
51
  }))
35
52
  void checkedProps.value
36
53
  watchSyncEffect(() => {
@@ -92,6 +109,9 @@ if (
92
109
  const hostState = computed(() =>
93
110
  [
94
111
  checkedProps.value.disabled && 'disabled',
112
+ checkedProps.value.prefixKind && 'prefixKind',
113
+ checkedProps.value.prefixKind != null
114
+ && `prefixKind=${encodeURIComponent(checkedProps.value.prefixKind)}`,
95
115
  ].filter(Boolean).join(' ')
96
116
  )
97
117
 
@@ -112,7 +132,7 @@ useComponentHost(() => import('../components/ui-input-group/ui-input-group.js'),
112
132
  ? (incoming[name] ?? incoming[kebab] ?? null)
113
133
  : null
114
134
  },
115
- propNames: ['disabled'],
135
+ propNames: ['disabled', 'prefixKind'],
116
136
  propValidity: (name: string) =>
117
137
  propValidityState({ contract: propValidityContract, values: { ...props } }, name),
118
138
  })
@@ -124,6 +144,7 @@ useComponentHost(() => import('../components/ui-input-group/ui-input-group.js'),
124
144
  v-bind="nativeAttrs($attrs)"
125
145
  v-prop-validity="{ contract: propValidityContract, values: { ...props } }"
126
146
  :data-disabled="reflectedProp('disabled', 'disabled', checkedProps.disabled, undefined, false)"
147
+ :data-prefix-kind="reflectedProp('prefixKind', 'prefix-kind', checkedProps.prefixKind, undefined, false)"
127
148
  :data-ui-input-group-state="hostState || undefined"
128
149
  ref="root"
129
150
  >
@@ -178,7 +199,7 @@ useComponentHost(() => import('../components/ui-input-group/ui-input-group.js'),
178
199
  min-inline-size: 0;
179
200
  min-block-size: var(--ui-control-size-md);
180
201
  /* The frame reads the shared control tokens with a lone Input's fallbacks, so the two match. */
181
- border: 1px solid var(--ui-control-border, var(--ui-border-strong));
202
+ border: var(--ui-border-width) solid var(--ui-control-border, var(--ui-border-strong));
182
203
  border-radius: var(--ui-input-group-radius, var(--ui-field-radius, var(--ui-radius-md)));
183
204
  background: var(--ui-control-surface, var(--ui-surface-elevated));
184
205
  box-shadow: var(--ui-control-inset);
@@ -229,7 +250,7 @@ useComponentHost(() => import('../components/ui-input-group/ui-input-group.js'),
229
250
  box-shadow: var(
230
251
  --ui-input-group-invalid-focus-shadow,
231
252
  var(--ui-control-inset),
232
- 0 0 0 3px color-mix(
253
+ 0 0 0 calc(var(--ui-focus-width) * 1.5) color-mix(
233
254
  in srgb,
234
255
  var(--ui-input-group-invalid-border, var(--ui-field-danger, var(--ui-danger-solid))) 22%,
235
256
  transparent
@@ -240,8 +261,8 @@ useComponentHost(() => import('../components/ui-input-group/ui-input-group.js'),
240
261
  /* Forced colors drop box shadows, so the focus ring becomes an outline. */
241
262
  @media (forced-colors: active) {
242
263
  [data-component~="ui-input-group"]:has(input:focus-visible) {
243
- outline: 2px solid Highlight;
244
- outline-offset: 1px;
264
+ outline: var(--ui-focus-width) solid Highlight;
265
+ outline-offset: var(--ui-border-width);
245
266
  }
246
267
  }
247
268
 
@@ -276,11 +297,21 @@ useComponentHost(() => import('../components/ui-input-group/ui-input-group.js'),
276
297
  }
277
298
 
278
299
  .affix:has([slot="prefix"]) {
279
- border-inline-end: 1px solid var(--ui-border-divider);
300
+ border-inline-end: var(--ui-border-width) solid var(--ui-border-divider);
280
301
  }
281
302
 
282
303
  .affix:has([slot="suffix"]) {
283
- border-inline-start: 1px solid var(--ui-border-divider);
304
+ border-inline-start: var(--ui-border-width) solid var(--ui-border-divider);
305
+ }
306
+
307
+ [data-component~="ui-input-group"][data-ui-input-group-state~="prefixKind=icon"] {
308
+ --_ui-default-input-padding-inline-start: var(--ui-space-2);
309
+ }
310
+ [data-component~="ui-input-group"][data-ui-input-group-state~="prefixKind=icon"]
311
+ .affix:first-child {
312
+ padding-inline-end: 0;
313
+ border-inline-end: 0;
314
+ background: transparent;
284
315
  }
285
316
 
286
317
  /* Centred rather than padded above and below: the group is exactly a lone Input's height, so a
package/vue/UiList.vue CHANGED
@@ -250,7 +250,7 @@ const hostState = computed(() =>
250
250
  [data-component~="ui-list"] > :slotted(li.card) {
251
251
  padding: var(--_ui-list-native-padding-block, var(--ui-space-3))
252
252
  var(--_ui-list-native-padding-inline, var(--ui-space-4));
253
- border: 1px solid var(--ui-border-strong);
253
+ border: var(--ui-border-width) solid var(--ui-border-strong);
254
254
  border-radius: var(--ui-radius-lg);
255
255
  background: var(--ui-surface-raised);
256
256
  list-style: none;
@@ -178,7 +178,7 @@ const hostState = computed(() =>
178
178
  --ui-list-item-padding-inline,
179
179
  var(--_ui-list-item-padding-inline, var(--ui-space-3))
180
180
  );
181
- border-radius: var(--ui-radius-md);
181
+ border-radius: var(--ui-selection-radius);
182
182
  color: var(--ui-text-primary);
183
183
  transition:
184
184
  background-color var(--ui-motion-fast) var(--ui-motion-ease),
@@ -188,7 +188,7 @@ const hostState = computed(() =>
188
188
  [data-component~="ui-list-item"][data-ui-list-item-state~="variant=card"] {
189
189
  padding-block: var(--ui-list-item-padding-block, var(--ui-space-3));
190
190
  padding-inline: var(--ui-list-item-padding-inline, var(--ui-space-4));
191
- border: 1px solid var(--ui-border-strong);
191
+ border: var(--ui-border-width) solid var(--ui-border-strong);
192
192
  border-radius: var(--ui-radius-lg);
193
193
  background: var(--ui-surface-raised);
194
194
  }
@@ -203,11 +203,12 @@ const hostState = computed(() =>
203
203
  }
204
204
 
205
205
  [data-component~="ui-list-item"][data-ui-list-item-state~="current"] {
206
- background: var(--ui-surface-hover);
206
+ background: var(--ui-selection-surface);
207
+ color: var(--ui-selection-text);
207
208
  }
208
209
 
209
210
  [data-component~="ui-list-item"]:has(.title a[href]:focus-visible) {
210
- box-shadow: 0 0 0 2px var(--ui-focus-ring);
211
+ box-shadow: 0 0 0 var(--ui-focus-width) var(--ui-focus-ring);
211
212
  }
212
213
 
213
214
  .leading,
@@ -223,6 +224,10 @@ const hostState = computed(() =>
223
224
  font-size: var(--_ui-list-item-icon-size, var(--ui-font-size-lg));
224
225
  }
225
226
 
227
+ [data-component~="ui-list-item"][data-ui-list-item-state~="current"] .leading {
228
+ color: var(--ui-selection-text);
229
+ }
230
+
226
231
  /* Trailing controls take their own clicks, above the title link's stretched hit area. */
227
232
  .trailing {
228
233
  position: relative;
@@ -284,6 +289,17 @@ const hostState = computed(() =>
284
289
  display: none;
285
290
  }
286
291
 
292
+ @media (forced-colors: active) {
293
+ [data-component~="ui-list-item"]:has(a:focus-visible) {
294
+ outline: var(--ui-focus-width) solid Highlight;
295
+ outline-offset: calc(-1 * var(--ui-focus-width));
296
+ }
297
+ [data-component~="ui-list-item"][data-ui-list-item-state~="current"]:not(:has(a:focus-visible)) {
298
+ outline: var(--ui-border-width) solid Highlight;
299
+ outline-offset: calc(-1 * var(--ui-border-width));
300
+ }
301
+ }
302
+
287
303
  /* A component that sets its display still honours the hidden attribute on its root. */
288
304
  [data-component~="ui-list-item"][hidden] {
289
305
  display: none;
package/vue/UiListbox.vue CHANGED
@@ -471,7 +471,7 @@ function uniqueKeys<T>(
471
471
  min-inline-size: 0;
472
472
  margin: 0;
473
473
  padding: var(--ui-space-1);
474
- border: 1px solid var(--ui-listbox-border, var(--ui-control-border));
474
+ border: var(--ui-border-width) solid var(--ui-listbox-border, var(--ui-control-border));
475
475
  border-radius: var(--ui-listbox-radius, var(--ui-field-radius, var(--ui-radius-md)));
476
476
  background: var(--ui-listbox-surface, var(--ui-control-surface));
477
477
  color: var(--ui-text-primary);
@@ -515,8 +515,8 @@ function uniqueKeys<T>(
515
515
 
516
516
  [data-component~="ui-listbox"]:focus-visible {
517
517
  border-color: var(--ui-control-focus);
518
- outline: 2px solid var(--ui-focus-ring);
519
- outline-offset: 2px;
518
+ outline: var(--ui-focus-width) solid var(--ui-focus-ring);
519
+ outline-offset: var(--ui-focus-width);
520
520
  box-shadow: var(--ui-listbox-focus-shadow, var(--ui-focus-halo));
521
521
  }
522
522
 
package/vue/UiMenu.vue CHANGED
@@ -379,7 +379,8 @@ useComponentHost(() => import('../components/ui-menu/ui-menu.js'), {
379
379
  max-inline-size: inherit;
380
380
  padding: var(--ui-space-1);
381
381
  background: var(--_ui-menu-surface, var(--ui-overlay-surface, var(--ui-surface-elevated)));
382
- border: 1px solid var(--_ui-menu-border, var(--ui-overlay-border, var(--ui-border-strong)));
382
+ border: var(--ui-border-width) solid
383
+ var(--_ui-menu-border, var(--ui-overlay-border, var(--ui-border-strong)));
383
384
  border-radius: var(--_ui-menu-radius, var(--ui-overlay-radius, var(--ui-radius-lg)));
384
385
  box-shadow: var(
385
386
  --_ui-menu-shadow,
@@ -394,7 +395,7 @@ useComponentHost(() => import('../components/ui-menu/ui-menu.js'), {
394
395
  block-size: 0;
395
396
  margin: var(--ui-space-1) 0;
396
397
  border: 0;
397
- border-block-start: 1px solid var(--ui-border-divider);
398
+ border-block-start: var(--ui-border-width) solid var(--ui-border-divider);
398
399
  }
399
400
 
400
401
  [data-component~="ui-menu"]:not([data-component~="ui-menu"][data-ui-menu-state~="internalOpen"]):not([data-component~="ui-menu"][data-ui-menu-state~="inline"]) {
@@ -369,7 +369,7 @@ function attribute(value: unknown): any {
369
369
  text-align: left;
370
370
  border-radius: var(
371
371
  --ui-menu-item-radius,
372
- var(--_ui-default-menu-item-radius, var(--ui-radius-md))
372
+ var(--_ui-default-menu-item-radius, var(--ui-selection-radius))
373
373
  );
374
374
  color: var(--ui-text-primary);
375
375
  font-size: var(
@@ -401,7 +401,7 @@ function attribute(value: unknown): any {
401
401
  box-sizing: border-box;
402
402
  inline-size: var(--_ui-default-menu-indicator-size, var(--ui-icon-size-sm));
403
403
  block-size: var(--_ui-default-menu-indicator-size, var(--ui-icon-size-sm));
404
- border: 1.5px solid var(--ui-control-border);
404
+ border: var(--ui-border-width) solid var(--ui-control-border);
405
405
  border-radius: var(--ui-radius-sm);
406
406
  color: var(--ui-surface);
407
407
  --_ui-default-icon-size: 0.75rem;
@@ -457,8 +457,8 @@ function attribute(value: unknown): any {
457
457
  }
458
458
 
459
459
  [data-component~="ui-menu-item"]:focus-visible {
460
- outline: 2px solid var(--ui-focus-ring);
461
- outline-offset: -2px;
460
+ outline: var(--ui-focus-width) solid var(--ui-focus-ring);
461
+ outline-offset: calc(-1 * var(--ui-focus-width));
462
462
  }
463
463
 
464
464
  [data-component~="ui-menu-item"][aria-disabled="true"] {
package/vue/UiMeter.vue CHANGED
@@ -388,7 +388,7 @@ function number(value: unknown): number | undefined {
388
388
  background: var(--ui-border);
389
389
  box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.1), inset 0 -1px rgb(255 255 255 / 0.22);
390
390
  /* Unseen until forced colours draw it, where the track's own colour is gone. */
391
- outline: 1px solid transparent;
391
+ outline: var(--ui-border-width) solid transparent;
392
392
  }
393
393
 
394
394
  [data-component~="ui-meter"][data-ui-meter-state~="size=sm"] {
@@ -22,6 +22,10 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
22
22
  type: PropType<"true" | "false" | "page" | "step" | "location" | null>;
23
23
  default: string;
24
24
  };
25
+ density: {
26
+ type: PropType<"comfortable" | "compact" | null>;
27
+ default: string;
28
+ };
25
29
  href: {
26
30
  type: PropType<string | null>;
27
31
  default: null;
@@ -34,6 +38,10 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
34
38
  type: PropType<string | null>;
35
39
  default: null;
36
40
  };
41
+ variant: {
42
+ type: PropType<"surface" | "line" | null>;
43
+ default: string;
44
+ };
37
45
  }>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
38
46
  as: {
39
47
  type: PropType<"button" | "a" | null>;
@@ -43,6 +51,10 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
43
51
  type: PropType<"true" | "false" | "page" | "step" | "location" | null>;
44
52
  default: string;
45
53
  };
54
+ density: {
55
+ type: PropType<"comfortable" | "compact" | null>;
56
+ default: string;
57
+ };
46
58
  href: {
47
59
  type: PropType<string | null>;
48
60
  default: null;
@@ -55,12 +67,18 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
55
67
  type: PropType<string | null>;
56
68
  default: null;
57
69
  };
70
+ variant: {
71
+ type: PropType<"surface" | "line" | null>;
72
+ default: string;
73
+ };
58
74
  }>> & Readonly<{}>, {
75
+ variant: "line" | "surface" | null;
59
76
  current: "true" | "false" | "page" | "step" | "location" | null;
60
77
  as: "a" | "button" | null;
61
78
  href: string | null;
62
79
  rel: string | null;
63
80
  target: string | null;
81
+ density: "comfortable" | "compact" | null;
64
82
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
65
83
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
66
84
  declare const _default: typeof __VLS_export;
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 {
@@ -16,6 +16,10 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
16
16
  type: PropType<"top" | "top-start" | "top-end" | "bottom" | "bottom-start" | "bottom-end" | "left" | "left-start" | "left-end" | "right" | "right-start" | "right-end" | null>;
17
17
  default: string;
18
18
  };
19
+ size: {
20
+ type: PropType<"content" | "sm" | "md" | "lg" | null>;
21
+ default: string;
22
+ };
19
23
  }>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
20
24
  open: (event: CustomEvent<{
21
25
  readonly open: boolean;
@@ -41,6 +45,10 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
41
45
  type: PropType<"top" | "top-start" | "top-end" | "bottom" | "bottom-start" | "bottom-end" | "left" | "left-start" | "left-end" | "right" | "right-start" | "right-end" | null>;
42
46
  default: string;
43
47
  };
48
+ size: {
49
+ type: PropType<"content" | "sm" | "md" | "lg" | null>;
50
+ default: string;
51
+ };
44
52
  }>> & Readonly<{
45
53
  onOpen?: ((event: CustomEvent<{
46
54
  readonly open: boolean;
@@ -55,6 +63,7 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
55
63
  }>) => any) | undefined;
56
64
  }>, {
57
65
  open: boolean | null;
66
+ size: "sm" | "md" | "content" | "lg" | null;
58
67
  for: string | null;
59
68
  placement: "left" | "right" | "top" | "top-start" | "top-end" | "bottom" | "bottom-start" | "bottom-end" | "left-start" | "left-end" | "right-start" | "right-end" | null;
60
69
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;