@threadlabs/looma 0.14.7 → 0.15.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 (336) hide show
  1. package/components/shared/icons.js +5 -0
  2. package/components/shared/menu-navigation.js +49 -0
  3. package/components/shared/overlay.js +33 -60
  4. package/components/ui-avatar/ui-avatar.html +19 -2
  5. package/components/ui-avatar/ui-avatar.js +7 -1
  6. package/components/ui-avatar-group/ui-avatar-group.html +8 -3
  7. package/components/ui-avatar-group/ui-avatar-group.js +6 -2
  8. package/components/ui-badge/ui-badge.html +70 -4
  9. package/components/ui-button/ui-button.html +21 -5
  10. package/components/ui-button/ui-button.js +10 -0
  11. package/components/ui-callout/ui-callout.html +13 -5
  12. package/components/ui-checkbox/ui-checkbox.html +11 -0
  13. package/components/ui-combobox/ui-combobox.html +50 -10
  14. package/components/ui-combobox/ui-combobox.js +11 -8
  15. package/components/ui-context-menu/ui-context-menu.html +33 -5
  16. package/components/ui-context-menu/ui-context-menu.js +26 -17
  17. package/components/ui-dialog/ui-dialog.html +51 -8
  18. package/components/ui-dialog/ui-dialog.js +28 -7
  19. package/components/ui-disclosure/ui-disclosure.html +14 -5
  20. package/components/ui-disclosure/ui-disclosure.js +51 -4
  21. package/components/ui-editable/ui-editable.html +17 -3
  22. package/components/ui-editable/ui-editable.js +7 -0
  23. package/components/ui-editor-insert-table-grid/ui-editor-insert-table-grid.html +48 -14
  24. package/components/ui-editor-insert-table-grid/ui-editor-insert-table-grid.js +40 -6
  25. package/components/ui-editor-mention-menu/ui-editor-mention-menu.html +22 -9
  26. package/components/ui-editor-mention-menu/ui-editor-mention-menu.js +33 -11
  27. package/components/ui-editor-mention-menu-item/ui-editor-mention-menu-item.html +67 -0
  28. package/components/ui-editor-slash-menu/ui-editor-slash-menu.html +33 -30
  29. package/components/ui-editor-slash-menu/ui-editor-slash-menu.js +42 -8
  30. package/components/ui-editor-slash-menu-group/ui-editor-slash-menu-group.html +31 -0
  31. package/components/ui-editor-slash-menu-item/ui-editor-slash-menu-item.html +75 -0
  32. package/components/ui-editor-table-context-menu/ui-editor-table-context-menu.html +16 -5
  33. package/components/ui-editor-table-context-menu/ui-editor-table-context-menu.js +10 -2
  34. package/components/ui-editor-table-overlay/ui-editor-table-overlay.html +5 -5
  35. package/components/ui-editor-table-overlay/ui-editor-table-overlay.js +5 -2
  36. package/components/ui-editor-table-toolbar/ui-editor-table-toolbar.html +14 -4
  37. package/components/ui-editor-table-toolbar/ui-editor-table-toolbar.js +2 -2
  38. package/components/ui-editor-toolbar/ui-editor-toolbar.html +1 -1
  39. package/components/ui-editor-toolbar/ui-editor-toolbar.js +46 -0
  40. package/components/ui-floating-action-button/ui-floating-action-button.html +1 -1
  41. package/components/ui-form-field/ui-form-field.html +16 -3
  42. package/components/ui-form-field/ui-form-field.js +1 -1
  43. package/components/ui-grid/ui-grid.html +17 -2
  44. package/components/ui-icon/ui-icon.html +5 -0
  45. package/components/ui-icon-button/ui-icon-button.html +25 -6
  46. package/components/ui-icon-button/ui-icon-button.js +11 -1
  47. package/components/ui-input/ui-input.html +3 -3
  48. package/components/ui-input-group/ui-input-group.html +18 -4
  49. package/components/ui-listbox/ui-listbox.html +81 -0
  50. package/components/ui-listbox/ui-listbox.js +27 -0
  51. package/components/ui-menu/ui-menu.html +33 -5
  52. package/components/ui-menu/ui-menu.js +18 -12
  53. package/components/ui-menu-group/ui-menu-group.html +27 -0
  54. package/components/ui-menu-item/ui-menu-item.html +80 -8
  55. package/components/ui-menu-item/ui-menu-item.js +47 -0
  56. package/components/ui-popover/ui-popover.html +33 -5
  57. package/components/ui-popover/ui-popover.js +9 -1
  58. package/components/ui-radio-group/ui-radio-group.html +13 -1
  59. package/components/ui-search-shell/ui-search-shell.html +6 -6
  60. package/components/ui-search-shell/ui-search-shell.js +21 -5
  61. package/components/ui-select/ui-select.html +14 -4
  62. package/components/ui-switch/ui-switch.html +2 -2
  63. package/components/ui-switcher/ui-switcher.html +8 -1
  64. package/components/ui-switcher/ui-switcher.js +15 -0
  65. package/components/ui-tabs/ui-tabs.html +17 -12
  66. package/components/ui-tabs/ui-tabs.js +43 -7
  67. package/components/ui-textarea/ui-textarea.html +28 -5
  68. package/components/ui-toast/ui-toast.html +88 -0
  69. package/components/ui-toast/ui-toast.js +68 -0
  70. package/components/ui-toast-region/ui-toast-region.html +102 -19
  71. package/components/ui-toast-region/ui-toast-region.js +10 -18
  72. package/components/ui-tooltip/ui-tooltip.html +51 -7
  73. package/components/ui-tooltip/ui-tooltip.js +29 -3
  74. package/components/ui-tree/ui-tree.html +12 -0
  75. package/components/ui-tree/ui-tree.js +191 -12
  76. package/components/ui-tree-item/ui-tree-item.html +87 -8
  77. package/components/ui-tree-item/ui-tree-item.js +3 -1
  78. package/dist/index.js +38 -32
  79. package/editor/chunks/extensions.js +82 -2
  80. package/editor/extensions/table-commands.d.ts +8 -0
  81. package/editor/icons.d.ts +5 -0
  82. package/editor/index.js +7 -2
  83. package/editor/insert-table-grid.d.ts +1 -1
  84. package/editor/mention-menu.d.ts +2 -0
  85. package/editor/slash-menu.d.ts +3 -0
  86. package/editor/table-context-menu.d.ts +1 -1
  87. package/editor/table-overlay.d.ts +2 -2
  88. package/package.json +1 -1
  89. package/styles/ui-avatar-group.css +1 -2
  90. package/styles/ui-avatar.css +11 -1
  91. package/styles/ui-badge.css +73 -1
  92. package/styles/ui-button.css +9 -1
  93. package/styles/ui-callout.css +1 -1
  94. package/styles/ui-checkbox.css +9 -0
  95. package/styles/ui-combobox.css +38 -7
  96. package/styles/ui-context-menu.css +32 -4
  97. package/styles/ui-dialog.css +42 -5
  98. package/styles/ui-editor-insert-table-grid.css +48 -11
  99. package/styles/ui-editor-mention-menu-item.css +57 -0
  100. package/styles/ui-editor-mention-menu.css +11 -1
  101. package/styles/ui-editor-slash-menu-group.css +21 -0
  102. package/styles/ui-editor-slash-menu-item.css +62 -0
  103. package/styles/ui-editor-slash-menu.css +10 -21
  104. package/styles/ui-editor-table-context-menu.css +1 -1
  105. package/styles/ui-floating-action-button.css +1 -1
  106. package/styles/ui-form-field.css +14 -1
  107. package/styles/ui-grid.css +14 -1
  108. package/styles/ui-icon-button.css +17 -5
  109. package/styles/ui-input-group.css +18 -4
  110. package/styles/ui-input.css +3 -3
  111. package/styles/ui-listbox.css +69 -0
  112. package/styles/ui-menu-group.css +19 -0
  113. package/styles/ui-menu-item.css +48 -9
  114. package/styles/ui-menu.css +32 -4
  115. package/styles/ui-popover.css +28 -4
  116. package/styles/ui-radio-group.css +9 -0
  117. package/styles/ui-search-shell.css +5 -5
  118. package/styles/ui-select.css +14 -4
  119. package/styles/ui-switch.css +2 -2
  120. package/styles/ui-switcher.css +4 -0
  121. package/styles/ui-textarea.css +27 -4
  122. package/styles/ui-toast-region.css +88 -4
  123. package/styles/ui-toast.css +74 -0
  124. package/styles/ui-tooltip.css +46 -6
  125. package/styles/ui-tree-item.css +72 -4
  126. package/styles/ui-tree.css +8 -0
  127. package/tokens.css +3 -0
  128. package/vanilla/UiAvatar.d.ts +2 -0
  129. package/vanilla/UiAvatar.js +8 -1
  130. package/vanilla/UiAvatarGroup.d.ts +2 -0
  131. package/vanilla/UiAvatarGroup.js +1 -1
  132. package/vanilla/UiBadge.d.ts +1 -1
  133. package/vanilla/UiBadge.js +1 -1
  134. package/vanilla/UiButton.d.ts +1 -0
  135. package/vanilla/UiButton.js +22 -6
  136. package/vanilla/UiCallout.d.ts +1 -1
  137. package/vanilla/UiCallout.js +1 -1
  138. package/vanilla/UiCheckbox.d.ts +1 -0
  139. package/vanilla/UiCheckbox.js +12 -9
  140. package/vanilla/UiCombobox.d.ts +4 -0
  141. package/vanilla/UiCombobox.js +30 -15
  142. package/vanilla/UiContextMenu.d.ts +1 -1
  143. package/vanilla/UiContextMenu.js +1 -1
  144. package/vanilla/UiDialog.d.ts +5 -2
  145. package/vanilla/UiDialog.js +1 -1
  146. package/vanilla/UiDisclosure.d.ts +2 -0
  147. package/vanilla/UiDisclosure.js +32 -18
  148. package/vanilla/UiEditable.d.ts +5 -0
  149. package/vanilla/UiEditable.js +7 -3
  150. package/vanilla/UiEditorInsertTableGrid.js +1 -6
  151. package/vanilla/UiEditorMentionMenu.d.ts +4 -2
  152. package/vanilla/UiEditorMentionMenu.js +49 -31
  153. package/vanilla/UiEditorMentionMenuItem.d.ts +16 -0
  154. package/vanilla/UiEditorMentionMenuItem.js +56 -0
  155. package/vanilla/UiEditorSlashMenu.d.ts +4 -3
  156. package/vanilla/UiEditorSlashMenu.js +59 -46
  157. package/vanilla/UiEditorSlashMenuGroup.d.ts +14 -0
  158. package/vanilla/UiEditorSlashMenuGroup.js +48 -0
  159. package/vanilla/UiEditorSlashMenuItem.d.ts +18 -0
  160. package/vanilla/UiEditorSlashMenuItem.js +57 -0
  161. package/vanilla/UiEditorTableContextMenu.d.ts +5 -2
  162. package/vanilla/UiEditorTableContextMenu.js +1 -2
  163. package/vanilla/UiEditorTableOverlay.d.ts +1 -1
  164. package/vanilla/UiEditorTableOverlay.js +5 -5
  165. package/vanilla/UiEditorTableToolbar.d.ts +4 -2
  166. package/vanilla/UiEditorTableToolbar.js +1 -2
  167. package/vanilla/UiEditorToolbar.js +2 -0
  168. package/vanilla/UiGrid.d.ts +1 -0
  169. package/vanilla/UiGrid.js +8 -7
  170. package/vanilla/UiIcon.js +1 -1
  171. package/vanilla/UiIconButton.d.ts +1 -0
  172. package/vanilla/UiIconButton.js +11 -2
  173. package/vanilla/UiListbox.d.ts +20 -0
  174. package/vanilla/UiListbox.js +45 -0
  175. package/vanilla/UiMenu.d.ts +1 -1
  176. package/vanilla/UiMenu.js +1 -1
  177. package/vanilla/UiMenuGroup.d.ts +14 -0
  178. package/vanilla/UiMenuGroup.js +35 -0
  179. package/vanilla/UiMenuItem.d.ts +5 -1
  180. package/vanilla/UiMenuItem.js +91 -30
  181. package/vanilla/UiPopover.d.ts +1 -1
  182. package/vanilla/UiPopover.js +1 -1
  183. package/vanilla/UiRadioGroup.d.ts +1 -0
  184. package/vanilla/UiRadioGroup.js +6 -3
  185. package/vanilla/UiSwitcher.d.ts +1 -0
  186. package/vanilla/UiSwitcher.js +7 -9
  187. package/vanilla/UiTabs.d.ts +1 -0
  188. package/vanilla/UiTabs.js +9 -1
  189. package/vanilla/UiTextarea.d.ts +1 -0
  190. package/vanilla/UiTextarea.js +4 -1
  191. package/vanilla/UiToast.d.ts +16 -0
  192. package/vanilla/UiToast.js +53 -0
  193. package/vanilla/UiToastRegion.d.ts +1 -4
  194. package/vanilla/UiToastRegion.js +3 -11
  195. package/vanilla/UiTooltip.d.ts +1 -1
  196. package/vanilla/UiTooltip.js +1 -1
  197. package/vanilla/UiTree.d.ts +2 -0
  198. package/vanilla/UiTree.js +1 -1
  199. package/vanilla/UiTreeItem.d.ts +1 -0
  200. package/vanilla/UiTreeItem.js +45 -31
  201. package/vanilla/index.js +6 -0
  202. package/vue/UiAvatar.d.ts +4 -0
  203. package/vue/UiAvatar.vue +23 -3
  204. package/vue/UiAvatarGroup.d.ts +3 -0
  205. package/vue/UiAvatarGroup.vue +7 -4
  206. package/vue/UiBadge.d.ts +2 -2
  207. package/vue/UiBadge.vue +74 -2
  208. package/vue/UiButton.d.ts +5 -3
  209. package/vue/UiButton.vue +41 -7
  210. package/vue/UiCallout.d.ts +2 -2
  211. package/vue/UiCallout.vue +3 -2
  212. package/vue/UiCheckbox.d.ts +2 -0
  213. package/vue/UiCheckbox.vue +16 -0
  214. package/vue/UiCombobox.d.ts +13 -3
  215. package/vue/UiCombobox.vue +62 -11
  216. package/vue/UiContextMenu.d.ts +2 -0
  217. package/vue/UiContextMenu.vue +47 -6
  218. package/vue/UiDialog.d.ts +18 -4
  219. package/vue/UiDialog.vue +86 -11
  220. package/vue/UiDisclosure.d.ts +8 -2
  221. package/vue/UiDisclosure.vue +38 -14
  222. package/vue/UiEditable.d.ts +10 -0
  223. package/vue/UiEditable.vue +14 -2
  224. package/vue/UiEditorInsertTableGrid.vue +52 -15
  225. package/vue/UiEditorMentionMenu.d.ts +24 -2
  226. package/vue/UiEditorMentionMenu.vue +27 -11
  227. package/vue/UiEditorMentionMenuItem.d.ts +24 -0
  228. package/vue/UiEditorMentionMenuItem.js +2 -0
  229. package/vue/UiEditorMentionMenuItem.vue +88 -0
  230. package/vue/UiEditorSlashMenu.d.ts +24 -1
  231. package/vue/UiEditorSlashMenu.vue +33 -33
  232. package/vue/UiEditorSlashMenuGroup.d.ts +18 -0
  233. package/vue/UiEditorSlashMenuGroup.js +2 -0
  234. package/vue/UiEditorSlashMenuGroup.vue +43 -0
  235. package/vue/UiEditorSlashMenuItem.d.ts +26 -0
  236. package/vue/UiEditorSlashMenuItem.js +2 -0
  237. package/vue/UiEditorSlashMenuItem.vue +101 -0
  238. package/vue/UiEditorTableContextMenu.d.ts +9 -3
  239. package/vue/UiEditorTableContextMenu.vue +53 -3
  240. package/vue/UiEditorTableOverlay.d.ts +2 -2
  241. package/vue/UiEditorTableOverlay.vue +8 -6
  242. package/vue/UiEditorTableToolbar.d.ts +7 -3
  243. package/vue/UiEditorTableToolbar.vue +50 -2
  244. package/vue/UiEditorToolbar.vue +14 -1
  245. package/vue/UiFloatingActionButton.vue +1 -1
  246. package/vue/UiFormField.vue +28 -3
  247. package/vue/UiGrid.d.ts +1 -0
  248. package/vue/UiGrid.vue +26 -2
  249. package/vue/UiIcon.vue +16 -0
  250. package/vue/UiIconButton.d.ts +5 -3
  251. package/vue/UiIconButton.vue +40 -6
  252. package/vue/UiInput.vue +10 -3
  253. package/vue/UiInputGroup.vue +32 -11
  254. package/vue/UiListbox.d.ts +33 -0
  255. package/vue/UiListbox.js +2 -0
  256. package/vue/UiListbox.vue +131 -0
  257. package/vue/UiMenu.d.ts +2 -0
  258. package/vue/UiMenu.vue +43 -6
  259. package/vue/UiMenuGroup.d.ts +18 -0
  260. package/vue/UiMenuGroup.js +2 -0
  261. package/vue/UiMenuGroup.vue +39 -0
  262. package/vue/UiMenuItem.d.ts +34 -2
  263. package/vue/UiMenuItem.vue +145 -9
  264. package/vue/UiPopover.d.ts +2 -2
  265. package/vue/UiPopover.vue +44 -5
  266. package/vue/UiRadioGroup.d.ts +2 -0
  267. package/vue/UiRadioGroup.vue +17 -0
  268. package/vue/UiSearchShell.vue +12 -5
  269. package/vue/UiSelect.vue +18 -4
  270. package/vue/UiSwitch.vue +2 -2
  271. package/vue/UiSwitcher.d.ts +1 -0
  272. package/vue/UiSwitcher.vue +22 -1
  273. package/vue/UiTabs.d.ts +6 -2
  274. package/vue/UiTabs.vue +14 -12
  275. package/vue/UiTextarea.d.ts +2 -0
  276. package/vue/UiTextarea.vue +32 -4
  277. package/vue/UiToast.d.ts +34 -0
  278. package/vue/UiToast.js +2 -0
  279. package/vue/UiToast.vue +173 -0
  280. package/vue/UiToastRegion.d.ts +2 -18
  281. package/vue/UiToastRegion.vue +123 -48
  282. package/vue/UiTooltip.d.ts +2 -2
  283. package/vue/UiTooltip.vue +67 -12
  284. package/vue/UiTree.d.ts +10 -0
  285. package/vue/UiTree.vue +27 -0
  286. package/vue/UiTreeItem.d.ts +2 -0
  287. package/vue/UiTreeItem.vue +96 -4
  288. package/vue/chunks/UiAvatar.js +19 -5
  289. package/vue/chunks/UiAvatarGroup.js +10 -4
  290. package/vue/chunks/UiBadge.js +1 -1
  291. package/vue/chunks/UiButton.js +58 -11
  292. package/vue/chunks/UiCallout.js +2 -2
  293. package/vue/chunks/UiCheckbox.js +12 -2
  294. package/vue/chunks/UiCombobox.js +30 -13
  295. package/vue/chunks/UiContextMenu.js +6 -2
  296. package/vue/chunks/UiDialog.js +25 -8
  297. package/vue/chunks/UiDisclosure.js +36 -12
  298. package/vue/chunks/UiEditable.js +22 -8
  299. package/vue/chunks/UiEditorInsertTableGrid.js +11 -14
  300. package/vue/chunks/UiEditorMentionMenu.js +30 -19
  301. package/vue/chunks/UiEditorMentionMenuItem.js +36 -0
  302. package/vue/chunks/UiEditorSlashMenu.js +33 -35
  303. package/vue/chunks/UiEditorSlashMenuGroup.js +26 -0
  304. package/vue/chunks/UiEditorSlashMenuItem.js +51 -0
  305. package/vue/chunks/UiEditorTableContextMenu.js +39 -5
  306. package/vue/chunks/UiEditorTableOverlay.js +6 -6
  307. package/vue/chunks/UiEditorTableToolbar.js +37 -4
  308. package/vue/chunks/UiEditorToolbar.js +18 -4
  309. package/vue/chunks/UiFloatingActionButton.js +1 -1
  310. package/vue/chunks/UiFormField.js +13 -9
  311. package/vue/chunks/UiGrid.js +6 -2
  312. package/vue/chunks/UiIcon.js +40 -2
  313. package/vue/chunks/UiIconButton.js +35 -4
  314. package/vue/chunks/UiInput.js +1 -1
  315. package/vue/chunks/UiInputGroup.js +1 -1
  316. package/vue/chunks/UiListbox.js +70 -0
  317. package/vue/chunks/UiMenu.js +6 -2
  318. package/vue/chunks/UiMenuGroup.js +25 -0
  319. package/vue/chunks/UiMenuItem.js +128 -7
  320. package/vue/chunks/UiPopover.js +1 -1
  321. package/vue/chunks/UiRadioGroup.js +8 -2
  322. package/vue/chunks/UiSearchShell.js +1 -1
  323. package/vue/chunks/UiSelect.js +1 -1
  324. package/vue/chunks/UiSwitch.js +1 -1
  325. package/vue/chunks/UiSwitcher.js +20 -3
  326. package/vue/chunks/UiTabs.js +4 -3
  327. package/vue/chunks/UiTextarea.js +7 -2
  328. package/vue/chunks/UiToast.js +81 -0
  329. package/vue/chunks/UiToastRegion.js +25 -49
  330. package/vue/chunks/UiTooltip.js +3 -7
  331. package/vue/chunks/UiTree.js +8 -4
  332. package/vue/chunks/UiTreeItem.js +28 -10
  333. package/vue/components.css +1840 -851
  334. package/vue/editor/index.js +263 -2
  335. package/vue/index.d.ts +6 -0
  336. package/vue/index.js +9 -3
@@ -6,8 +6,10 @@
6
6
  <prop name="min" type="sm | md | lg"
7
7
  >The narrowest a column may get before the grid drops to fewer columns: sm is 12rem (also the default), md is 16rem, and lg is 20rem. The grid fits as many
8
8
  equal columns as its width allows, and a single column never overflows a narrower container.</prop>
9
+ <prop name="columns" type="integer"
10
+ >Maximum columns when there is room. Omit for no cap. The grid still drops columns as its container narrows; it never forces a fixed column count.</prop>
9
11
  </defs>
10
- <div><slot></slot></div>
12
+ <div style:--_grid-column-cap="columns"><slot></slot></div>
11
13
  <style>
12
14
  /* This component's hooks style the element they are set on, and never a nested one. */
13
15
  @property --ui-grid-gap {
@@ -24,7 +26,20 @@
24
26
  --_grid-min: 12rem;
25
27
  display: grid;
26
28
  gap: var(--ui-grid-gap, var(--_grid-gap));
27
- grid-template-columns: repeat(auto-fit, minmax(min(var(--ui-grid-min, var(--_grid-min)), 100%), 1fr));
29
+ grid-template-columns:
30
+ repeat(
31
+ auto-fit,
32
+ minmax(
33
+ min(
34
+ max(
35
+ var(--ui-grid-min, var(--_grid-min)),
36
+ calc((100% - (max(1, var(--_grid-column-cap, 9999)) - 1) * var(--ui-grid-gap, var(--_grid-gap))) / max(1, var(--_grid-column-cap, 9999)))
37
+ ),
38
+ 100%
39
+ ),
40
+ 1fr
41
+ )
42
+ );
28
43
  min-inline-size: 0;
29
44
  max-inline-size: 100%;
30
45
  margin: 0;
@@ -15,10 +15,12 @@
15
15
  'book-user': [{ tag: 'path', d: 'M15 13a3 3 0 1 0-6 0' }, { tag: 'path', d: 'M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20' }, { tag: 'circle', cx: '12', cy: '8', r: '2' }],
16
16
  'braces': [{ tag: 'path', d: 'M8 3H7a2 2 0 0 0-2 2v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5c0 1.1.9 2 2 2h1' }, { tag: 'path', d: 'M16 21h1a2 2 0 0 0 2-2v-5c0-1.1.9-2 2-2a2 2 0 0 1-2-2V5a2 2 0 0 0-2-2h-1' }],
17
17
  'calculator': [{ tag: 'rect', width: '16', height: '20', x: '4', y: '2', rx: '2' }, { tag: 'line', x1: '8', x2: '16', y1: '6', y2: '6' }, { tag: 'line', x1: '16', x2: '16', y1: '14', y2: '18' }, { tag: 'path', d: 'M16 10h.01' }, { tag: 'path', d: 'M12 10h.01' }, { tag: 'path', d: 'M8 10h.01' }, { tag: 'path', d: 'M12 14h.01' }, { tag: 'path', d: 'M8 14h.01' }, { tag: 'path', d: 'M12 18h.01' }, { tag: 'path', d: 'M8 18h.01' }],
18
+ 'check': [{ tag: 'path', d: 'M20 6 9 17l-5-5' }],
18
19
  'chevron-down': [{ tag: 'path', d: 'm6 9 6 6 6-6' }],
19
20
  'chevron-left': [{ tag: 'path', d: 'm15 18-6-6 6-6' }],
20
21
  'chevron-right': [{ tag: 'path', d: 'm9 18 6-6-6-6' }],
21
22
  'circle-check': [{ tag: 'circle', cx: '12', cy: '12', r: '10' }, { tag: 'path', d: 'm9 12 2 2 4-4' }],
23
+ 'circle-dot': [{ tag: 'circle', cx: '12', cy: '12', r: '10' }, { tag: 'circle', cx: '12', cy: '12', r: '1' }],
22
24
  'circle-x': [{ tag: 'circle', cx: '12', cy: '12', r: '10' }, { tag: 'path', d: 'm15 9-6 6' }, { tag: 'path', d: 'm9 9 6 6' }],
23
25
  'code-xml': [{ tag: 'path', d: 'm18 16 4-4-4-4' }, { tag: 'path', d: 'm6 8-4 4 4 4' }, { tag: 'path', d: 'm14.5 4-5 16' }],
24
26
  'columns': [{ tag: 'rect', width: '18', height: '18', x: '3', y: '3', rx: '2' }, { tag: 'path', d: 'M9 3v18' }, { tag: 'path', d: 'M15 3v18' }],
@@ -38,6 +40,7 @@
38
40
  'info': [{ tag: 'circle', cx: '12', cy: '12', r: '10' }, { tag: 'path', d: 'M12 16v-4' }, { tag: 'path', d: 'M12 8h.01' }],
39
41
  'italic': [{ tag: 'line', x1: '19', x2: '10', y1: '4', y2: '4' }, { tag: 'line', x1: '14', x2: '5', y1: '20', y2: '20' }, { tag: 'line', x1: '15', x2: '9', y1: '4', y2: '20' }],
40
42
  'layout-dashboard': [{ tag: 'rect', width: '7', height: '9', x: '3', y: '3', rx: '1' }, { tag: 'rect', width: '7', height: '5', x: '14', y: '3', rx: '1' }, { tag: 'rect', width: '7', height: '9', x: '14', y: '12', rx: '1' }, { tag: 'rect', width: '7', height: '5', x: '3', y: '16', rx: '1' }],
43
+ 'link': [{ tag: 'path', d: 'M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71' }, { tag: 'path', d: 'M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71' }],
41
44
  'list': [{ tag: 'path', d: 'M3 5h.01' }, { tag: 'path', d: 'M3 12h.01' }, { tag: 'path', d: 'M3 19h.01' }, { tag: 'path', d: 'M8 5h13' }, { tag: 'path', d: 'M8 12h13' }, { tag: 'path', d: 'M8 19h13' }],
42
45
  'list-ordered': [{ tag: 'path', d: 'M11 5h10' }, { tag: 'path', d: 'M11 12h10' }, { tag: 'path', d: 'M11 19h10' }, { tag: 'path', d: 'M4 4h1v5' }, { tag: 'path', d: 'M4 9h2' }, { tag: 'path', d: 'M6.5 20H3.4c0-1 2.6-1.925 2.6-3.5a1.5 1.5 0 0 0-2.6-1.02' }],
43
46
  'list-todo': [{ tag: 'path', d: 'M13 5h8' }, { tag: 'path', d: 'M13 12h8' }, { tag: 'path', d: 'M13 19h8' }, { tag: 'path', d: 'm3 17 2 2 4-4' }, { tag: 'rect', x: '3', y: '4', width: '6', height: '6', rx: '1' }],
@@ -65,7 +68,9 @@
65
68
  'truck': [{ tag: 'path', d: 'M14 18V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v11a1 1 0 0 0 1 1h2' }, { tag: 'path', d: 'M15 18H9' }, { tag: 'path', d: 'M19 18h2a1 1 0 0 0 1-1v-3.65a1 1 0 0 0-.22-.624l-3.48-4.35A1 1 0 0 0 17.52 8H14' }, { tag: 'circle', cx: '17', cy: '18', r: '2' }, { tag: 'circle', cx: '7', cy: '18', r: '2' }],
66
69
  'underline': [{ tag: 'path', d: 'M6 4v6a6 6 0 0 0 12 0V4' }, { tag: 'line', x1: '4', x2: '20', y1: '20', y2: '20' }],
67
70
  'undo': [{ tag: 'path', d: 'M9 14 4 9l5-5' }, { tag: 'path', d: 'M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11' }],
71
+ 'user-round': [{ tag: 'circle', cx: '12', cy: '8', r: '5' }, { tag: 'path', d: 'M20 21a8 8 0 0 0-16 0' }],
68
72
  'users': [{ tag: 'path', d: 'M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2' }, { tag: 'path', d: 'M16 3.128a4 4 0 0 1 0 7.744' }, { tag: 'path', d: 'M22 21v-2a4 4 0 0 0-3-3.87' }, { tag: 'circle', cx: '9', cy: '7', r: '4' }],
73
+ 'x': [{ tag: 'path', d: 'M18 6 6 18' }, { tag: 'path', d: 'm6 6 12 12' }],
69
74
  }[name]"
70
75
  ></computed>
71
76
  </defs>
@@ -1,7 +1,11 @@
1
+ <link rel="component" href="../ui-spinner/ui-spinner.html">
1
2
  <template component="ui-icon-button" controller="./ui-icon-button.js">
2
3
  <defs>
3
4
  <prop name="disabled" type="boolean" default="false"
4
5
  >Disables the button, so it cannot be pressed or focused, and washes it out: the same shape, with less colour and contrast (--ui-disabled-filter).</prop>
6
+ <prop name="pending" type="boolean" default="false"
7
+ >Keeps the button focusable and named while work is in progress. It shows a spinner, states aria-disabled and aria-busy, and prevents activation until
8
+ cleared.</prop>
5
9
  <prop name="label" type="string">Accessible name for the icon.</prop>
6
10
  <prop name="size" type="sm | md | lg" default="md"
7
11
  >Button and icon size together: sm is 1.75rem with a small icon, md is 2rem, and lg is 2.5rem with a large icon. On touch, every size gets an invisible hit
@@ -14,7 +18,10 @@
14
18
  the pointer comes near inside a ui-affordance-scope. Touch devices and the solid variant always show it.</prop>
15
19
  <prop name="round" type="boolean" default="false">Use a fully circular shape.</prop>
16
20
  </defs>
17
- <button :disabled="disabled" type="button" :aria-label="label"><slot></slot></button>
21
+ <button :disabled="disabled" type="button" :aria-label="label" :aria-disabled="{ true: 'true', false: null }[format('%s', pending)]" :aria-busy="pending">
22
+ <ui-spinner $if="pending" size="sm"></ui-spinner>
23
+ <span class="content"><slot></slot></span>
24
+ </button>
18
25
  <style>
19
26
  /* This component's hooks style the element they are set on, and never a nested one. */
20
27
  @property --ui-icon-button-active-scale {
@@ -145,7 +152,7 @@
145
152
  padding: 0;
146
153
  margin: 0;
147
154
  border: 1px solid transparent;
148
- border-radius: var(--ui-icon-button-radius, var(--ui-radius-md));
155
+ border-radius: var(--ui-icon-button-radius, var(--ui-action-radius, var(--ui-radius-md)));
149
156
  background: var(--ui-icon-button-surface, transparent);
150
157
  color: inherit;
151
158
  display: inline-flex;
@@ -231,24 +238,36 @@
231
238
  );
232
239
  }
233
240
 
234
- :host:hover:enabled {
241
+ :host:hover:enabled:not([aria-disabled="true"]) {
235
242
  /* Derived from the text colour so the ghost hover shows on any background. */
236
243
  background: var(--ui-icon-button-hover-surface, color-mix(in srgb, currentColor 8%, transparent));
237
244
  color: var(--ui-icon-button-hover-text, var(--ui-text-primary));
238
245
  }
239
246
 
240
- :host-state([variant="outline"]):hover:enabled {
247
+ :host-state([variant="outline"]):hover:enabled:not([aria-disabled="true"]) {
241
248
  border-color: var(--ui-icon-button-hover-border, var(--ui-border-strong));
242
249
  }
243
250
 
244
- :host-state([variant="solid"]):hover:enabled {
251
+ :host-state([variant="solid"]):hover:enabled:not([aria-disabled="true"]) {
245
252
  background: var(--ui-icon-button-solid-hover-surface, var(--ui-accent-hover));
246
253
  }
247
254
 
248
- :host:active:enabled {
255
+ :host:active:enabled:not([aria-disabled="true"]) {
249
256
  transform: scale(var(--ui-icon-button-active-scale, 0.96));
250
257
  }
251
258
 
259
+ :host-state([pending]) {
260
+ cursor: progress;
261
+ }
262
+
263
+ .content {
264
+ display: contents;
265
+ }
266
+
267
+ :host-state([pending]) .content {
268
+ display: none;
269
+ }
270
+
252
271
  :host:focus-visible {
253
272
  outline: none;
254
273
  box-shadow: var(--ui-focus-halo);
@@ -1,7 +1,17 @@
1
1
  // Marks an anticipatory button for the enclosing ui-affordance-scope, which reveals it on proximity.
2
2
  export default function controller(host) {
3
- return host.effect(() => {
3
+ const stop = host.effect(() => {
4
4
  if (host.state.anticipatory) host.element.setAttribute("data-ui-affordance", "button");
5
5
  else host.element.removeAttribute("data-ui-affordance");
6
6
  });
7
+ const onClick = (event) => {
8
+ if (!host.state.pending) return;
9
+ event.preventDefault();
10
+ event.stopImmediatePropagation();
11
+ };
12
+ host.element.addEventListener("click", onClick, true);
13
+ return () => {
14
+ stop();
15
+ host.element.removeEventListener("click", onClick, true);
16
+ };
7
17
  }
@@ -77,7 +77,7 @@
77
77
  appearance: none;
78
78
  border: var(--ui-input-border-width, var(--_ui-default-input-border-width, 1px)) solid
79
79
  var(--ui-input-border, var(--_ui-default-input-border, var(--ui-control-border, var(--ui-border-strong))));
80
- border-radius: var(--ui-input-radius, var(--_ui-default-input-radius, var(--ui-radius-md)));
80
+ border-radius: var(--ui-input-radius, var(--_ui-default-input-radius, var(--ui-field-radius, var(--ui-radius-md))));
81
81
  background: var(--ui-input-surface, var(--_ui-default-input-surface, var(--ui-control-surface, var(--ui-surface-elevated))));
82
82
  color: var(--ui-text-primary);
83
83
  font: inherit;
@@ -135,7 +135,7 @@
135
135
  /* The app's invalid flag, or the browser's own verdict once the user has left an invalid field. */
136
136
  :host[aria-invalid="true"],
137
137
  :host:user-invalid {
138
- border-color: var(--ui-input-invalid-border, var(--_ui-default-input-invalid-border, var(--ui-danger-solid)));
138
+ border-color: var(--ui-input-invalid-border, var(--_ui-default-input-invalid-border, var(--ui-field-danger, var(--ui-danger-solid))));
139
139
  }
140
140
 
141
141
  :host:is([aria-invalid="true"], :user-invalid):focus-visible {
@@ -144,7 +144,7 @@
144
144
  var(
145
145
  --_ui-default-input-invalid-focus-shadow,
146
146
  var(--ui-control-inset),
147
- 0 0 0 3px color-mix(in srgb, var(--ui-danger-solid) 22%, transparent)
147
+ 0 0 0 3px color-mix(in srgb, var(--ui-input-invalid-border, var(--ui-field-danger, var(--ui-danger-solid))) 22%, transparent)
148
148
  )
149
149
  );
150
150
  }
@@ -9,6 +9,18 @@
9
9
  <span class="action"><slot name="action"></slot></span>
10
10
  </div>
11
11
  <style>
12
+ @property --ui-input-group-radius {
13
+ syntax: "*";
14
+ inherits: false;
15
+ }
16
+ @property --ui-input-group-invalid-border {
17
+ syntax: "*";
18
+ inherits: false;
19
+ }
20
+ @property --ui-input-group-invalid-focus-shadow {
21
+ syntax: "*";
22
+ inherits: false;
23
+ }
12
24
  /* An input with fixed text before or after it, such as a domain after a site's name
13
25
  ("acme" + ".example.com") or slashes around a path. The group draws the field's border and
14
26
  focus ring around the input and its affixes together; the input inside drops its own through
@@ -41,7 +53,7 @@
41
53
  min-block-size: var(--ui-control-size-md);
42
54
  /* The frame reads the shared control tokens with a lone Input's fallbacks, so the two match. */
43
55
  border: 1px solid var(--ui-control-border, var(--ui-border-strong));
44
- border-radius: var(--ui-radius-md);
56
+ border-radius: var(--ui-input-group-radius, var(--ui-field-radius, var(--ui-radius-md)));
45
57
  background: var(--ui-control-surface, var(--ui-surface-elevated));
46
58
  box-shadow: var(--ui-control-inset);
47
59
  overflow: hidden;
@@ -79,13 +91,15 @@
79
91
  }
80
92
 
81
93
  :host:has([aria-invalid="true"], :user-invalid) {
82
- border-color: var(--ui-danger-solid);
94
+ border-color: var(--ui-input-group-invalid-border, var(--ui-field-danger, var(--ui-danger-solid)));
83
95
  }
84
96
 
85
97
  :host:has(input:is([aria-invalid="true"], :user-invalid):focus-visible) {
86
- box-shadow:
98
+ box-shadow: var(
99
+ --ui-input-group-invalid-focus-shadow,
87
100
  var(--ui-control-inset),
88
- 0 0 0 3px color-mix(in srgb, var(--ui-danger-solid) 22%, transparent);
101
+ 0 0 0 3px color-mix(in srgb, var(--ui-input-group-invalid-border, var(--ui-field-danger, var(--ui-danger-solid))) 22%, transparent)
102
+ );
89
103
  }
90
104
 
91
105
  /* Forced colors drop box shadows, so the focus ring becomes an outline. */
@@ -0,0 +1,81 @@
1
+ <template component="ui-listbox" controller="./ui-listbox.js">
2
+ <defs>
3
+ <prop name="rows" type="number" default="5">The number of option rows visible at once. Use at least two.</prop>
4
+ <prop name="multiple" type="boolean" default="false">Allows several options to be selected with native keyboard and pointer behavior.</prop>
5
+ <prop name="value" type="string">The initial or externally set single selection. Authored selected options work when it is omitted.</prop>
6
+ <prop name="values" type="list(string)">The initial or externally set selections in multiple mode. Authored selected options work when it is omitted.</prop>
7
+ <prop name="disabled" type="boolean" default="false">Prevents selection and form submission.</prop>
8
+ <prop name="required" type="boolean" default="false">Requires at least one selection for form validation.</prop>
9
+ <prop name="invalid" type="boolean" default="false">Exposes invalid state to assistive technology and styling.</prop>
10
+ </defs>
11
+ <select :size="rows" :multiple="multiple" :disabled="disabled" :required="required" :aria-invalid="invalid"><slot></slot></select>
12
+ <style>
13
+ @property --ui-listbox-radius {
14
+ syntax: "*";
15
+ inherits: false;
16
+ }
17
+ @property --ui-listbox-border {
18
+ syntax: "*";
19
+ inherits: false;
20
+ }
21
+ @property --ui-listbox-surface {
22
+ syntax: "*";
23
+ inherits: false;
24
+ }
25
+ @property --ui-listbox-focus-shadow {
26
+ syntax: "*";
27
+ inherits: false;
28
+ }
29
+ @property --ui-listbox-invalid-border {
30
+ syntax: "*";
31
+ inherits: false;
32
+ }
33
+
34
+ :host {
35
+ box-sizing: border-box;
36
+ display: block;
37
+ inline-size: 100%;
38
+ min-inline-size: 0;
39
+ min-block-size: var(--ui-control-min-block-size);
40
+ margin: 0;
41
+ padding: var(--ui-space-1);
42
+ border: 1px solid var(--ui-listbox-border, var(--ui-control-border));
43
+ border-radius: var(--ui-listbox-radius, var(--ui-field-radius, var(--ui-radius-md)));
44
+ background: var(--ui-listbox-surface, var(--ui-control-surface));
45
+ color: var(--ui-text-primary);
46
+ color-scheme: light dark;
47
+ font: inherit;
48
+ line-height: var(--ui-line-height-md);
49
+ box-shadow: var(--ui-control-inset);
50
+ }
51
+
52
+ :host option,
53
+ :host optgroup {
54
+ padding: var(--ui-space-2);
55
+ font: inherit;
56
+ }
57
+
58
+ :host:focus-visible {
59
+ border-color: var(--ui-control-focus);
60
+ outline: 2px solid var(--ui-focus-ring);
61
+ outline-offset: 2px;
62
+ box-shadow: var(--ui-listbox-focus-shadow, var(--ui-focus-halo));
63
+ }
64
+
65
+ :host[aria-invalid="true"],
66
+ :host:user-invalid {
67
+ border-color: var(--ui-listbox-invalid-border, var(--ui-field-danger, var(--ui-danger)));
68
+ }
69
+
70
+ :host:disabled {
71
+ background: var(--ui-control-surface-disabled);
72
+ color: var(--ui-disabled-text);
73
+ box-shadow: none;
74
+ cursor: not-allowed;
75
+ }
76
+
77
+ :host[hidden] {
78
+ display: none;
79
+ }
80
+ </style>
81
+ </template>
@@ -0,0 +1,27 @@
1
+ // Keep externally supplied selections in sync when options arrive after the listbox upgrades.
2
+ // Without value or values, the native select owns selection, keyboard behavior, and form reset.
3
+ export default function controller(host) {
4
+ const select = host.element;
5
+ const apply = () => {
6
+ if (host.state.multiple) {
7
+ if (host.state.values === undefined || host.state.values === null) return;
8
+ const values = new Set(host.state.values.map(String));
9
+ for (const option of select.options) {
10
+ option.defaultSelected = values.has(option.value);
11
+ option.selected = values.has(option.value);
12
+ }
13
+ return;
14
+ }
15
+ if (host.state.value === undefined || host.state.value === null) return;
16
+ const value = String(host.state.value);
17
+ for (const option of select.options) option.defaultSelected = option.value === value;
18
+ if (select.value !== value) select.value = value;
19
+ };
20
+ const observer = new MutationObserver(apply);
21
+ observer.observe(select, { childList: true, subtree: true });
22
+ const stop = host.effect(apply);
23
+ return () => {
24
+ stop();
25
+ observer.disconnect();
26
+ };
27
+ }
@@ -5,7 +5,7 @@
5
5
  <prop name="for" type="string" default="">ID of the control that toggles and anchors the menu.</prop>
6
6
  <prop name="placement" type="string" default="bottom-start">Positions an associated menu relative to its trigger.</prop>
7
7
  <state name="internalOpen" :value="false"></state>
8
- <event name="select" type="object({ value: string, trigger: keyboard | pointer | programmatic })"></event>
8
+ <event name="select" type="object({ value: string, checked?: boolean, trigger: keyboard | pointer | programmatic })"></event>
9
9
  <event name="open" type="object({ open: boolean, reason: action | programmatic, trigger: keyboard | pointer | programmatic })"></event>
10
10
  <event
11
11
  name="close"
@@ -15,6 +15,22 @@
15
15
  <div role="menu" aria-orientation="vertical"><div class="surface"><slot></slot></div></div>
16
16
  <style>
17
17
  /* This component's hooks style the element they are set on, and never a nested one. */
18
+ @property --ui-menu-surface {
19
+ syntax: "*";
20
+ inherits: false;
21
+ }
22
+ @property --ui-menu-border {
23
+ syntax: "*";
24
+ inherits: false;
25
+ }
26
+ @property --ui-menu-radius {
27
+ syntax: "*";
28
+ inherits: false;
29
+ }
30
+ @property --ui-menu-shadow {
31
+ syntax: "*";
32
+ inherits: false;
33
+ }
18
34
  @property --ui-menu-viewport-gap {
19
35
  syntax: "*";
20
36
  inherits: false;
@@ -32,6 +48,10 @@
32
48
  }
33
49
 
34
50
  :host {
51
+ --_ui-menu-surface: var(--ui-menu-surface);
52
+ --_ui-menu-border: var(--ui-menu-border);
53
+ --_ui-menu-radius: var(--ui-menu-radius);
54
+ --_ui-menu-shadow: var(--ui-menu-shadow);
35
55
  /* A menu nested in a compact one starts again from its own density. */
36
56
  --_ui-default-menu-item-padding-block: initial;
37
57
  --_ui-default-menu-item-padding-inline: initial;
@@ -58,14 +78,22 @@
58
78
  box-sizing: border-box;
59
79
  max-inline-size: inherit;
60
80
  padding: var(--ui-space-1);
61
- background: var(--ui-surface-elevated);
62
- border: 1px solid var(--ui-border-default);
63
- border-radius: var(--ui-radius-lg);
64
- box-shadow: var(--ui-elevation-overlay, var(--ui-shadow-lg));
81
+ background: var(--_ui-menu-surface, var(--ui-overlay-surface, var(--ui-surface-elevated)));
82
+ border: 1px solid var(--_ui-menu-border, var(--ui-overlay-border, var(--ui-border-default)));
83
+ border-radius: var(--_ui-menu-radius, var(--ui-overlay-radius, var(--ui-radius-lg)));
84
+ box-shadow: var(--_ui-menu-shadow, var(--ui-overlay-shadow, var(--ui-elevation-overlay, var(--ui-shadow-lg))));
65
85
  max-block-size: inherit;
66
86
  overflow: auto;
67
87
  }
68
88
 
89
+ .surface hr {
90
+ align-self: stretch;
91
+ block-size: 0;
92
+ margin: var(--ui-space-1) 0;
93
+ border: 0;
94
+ border-block-start: 1px solid var(--ui-border-default);
95
+ }
96
+
69
97
  :host:not(:host-state([internalOpen])) {
70
98
  display: none;
71
99
  }
@@ -1,8 +1,9 @@
1
1
  import { closeOverlay, createAnchoredSurface, createIdResolver, openOverlay, requestTopOverlayClose } from "../shared/overlay.js";
2
+ import { menuItemFrom, menuItems, navigateMenu } from "../shared/menu-navigation.js";
2
3
 
3
4
  function triggerFor(event) {
4
5
  if (event instanceof KeyboardEvent) return "keyboard";
5
- if (event instanceof MouseEvent || event instanceof PointerEvent) return "pointer";
6
+ if (event instanceof MouseEvent || event instanceof PointerEvent) return event.detail === 0 ? "keyboard" : "pointer";
6
7
  return "programmatic";
7
8
  }
8
9
 
@@ -14,7 +15,7 @@ export default function controller(host) {
14
15
  const element = host.element;
15
16
  const document = element.ownerDocument;
16
17
  const overlayId = `ui-menu-${Math.random().toString(36).slice(2, 11)}`;
17
- const items = () => Array.from(element.querySelectorAll('[role="menuitem"]'));
18
+ const items = () => menuItems(element);
18
19
  let anchor = null;
19
20
  let surface = null;
20
21
  let lastFor;
@@ -28,7 +29,7 @@ export default function controller(host) {
28
29
  else {
29
30
  host.state.internalOpen = true;
30
31
  host.dispatch("open", { open: true, reason: "action", trigger });
31
- requestAnimationFrame(() => items().find((item) => !disabled(item))?.focus());
32
+ requestAnimationFrame(() => items()[0]?.focus());
32
33
  }
33
34
  };
34
35
  const close = (reason, trigger) => {
@@ -73,29 +74,34 @@ export default function controller(host) {
73
74
  const select = (item, trigger) => {
74
75
  if (!item || disabled(item)) return;
75
76
  const value = item.getAttribute("data-value") ?? item.getAttribute("value") ?? "";
77
+ if (item.getAttribute("role") !== "menuitem") {
78
+ const detail = { trigger, checked: undefined };
79
+ item.dispatchEvent(new CustomEvent("ui-menu-item-activate", { detail }));
80
+ host.dispatch("select", { value, checked: detail.checked, trigger });
81
+ return;
82
+ }
76
83
  host.dispatch("select", { value, trigger });
77
84
  host.dispatch("close", { open: false, reason: "action", trigger });
78
85
  host.state.internalOpen = false;
79
86
  };
80
- const onClick = (event) => select(event.target.closest?.('[role="menuitem"]'), triggerFor(event));
87
+ const onClick = (event) => select(menuItemFrom(event.target), triggerFor(event));
81
88
  const onKeydown = (event) => {
82
89
  if (event.key === "Escape") {
83
90
  event.preventDefault();
84
91
  requestTopOverlayClose(document, "escape", "keyboard");
85
92
  return;
86
93
  }
87
- const enabled = items().filter((item) => !disabled(item));
88
- const target = event.target.closest?.('[role="menuitem"]');
89
- const index = target ? enabled.indexOf(target) : -1;
94
+ const target = menuItemFrom(event.target);
90
95
  if (["Enter", " "].includes(event.key)) {
91
- if (index >= 0) {
96
+ if (!target || disabled(target)) return;
97
+ if (target.localName === "a") {
98
+ if (event.key === " ") { event.preventDefault(); target.click(); }
99
+ } else {
92
100
  event.preventDefault();
93
101
  select(target, "keyboard");
94
102
  }
95
- } else if (["ArrowDown", "ArrowUp"].includes(event.key) && enabled.length) {
96
- event.preventDefault();
97
- const next = event.key === "ArrowDown" ? (index < 0 ? 0 : Math.min(index + 1, enabled.length - 1)) : (index <= 0 ? enabled.length - 1 : index - 1);
98
- enabled[next]?.focus();
103
+ } else {
104
+ navigateMenu(event, element);
99
105
  }
100
106
  };
101
107
  element.addEventListener("click", onClick);
@@ -0,0 +1,27 @@
1
+ <template component="ui-menu-group">
2
+ <defs>
3
+ <prop name="label" type="string" default="">Visible heading and accessible name for this group of menu items.</prop>
4
+ </defs>
5
+ <div role="group" :aria-label="label">
6
+ <div class="heading" $if="label" $value="label"></div>
7
+ <slot></slot>
8
+ </div>
9
+ <style>
10
+ :host {
11
+ display: flex;
12
+ flex-direction: column;
13
+ min-inline-size: 0;
14
+ }
15
+
16
+ .heading {
17
+ padding: var(--ui-space-2) var(--ui-space-3) var(--ui-space-1);
18
+ color: var(--ui-text-muted);
19
+ font-size: var(--ui-font-size-sm);
20
+ font-weight: var(--ui-font-semibold);
21
+ }
22
+
23
+ :host[hidden] {
24
+ display: none;
25
+ }
26
+ </style>
27
+ </template>
@@ -1,12 +1,46 @@
1
- <template component="ui-menu-item">
1
+ <link rel="component" href="../ui-icon/ui-icon.html">
2
+ <template component="ui-menu-item" controller="./ui-menu-item.js">
2
3
  <defs>
3
4
  <prop name="disabled" type="boolean" default="false"
4
5
  >Makes the item unavailable: it is faded, skipped by the arrow keys, and choosing it does nothing. It stays visible and is announced as disabled.</prop>
5
6
  <prop name="value" type="string" default=""
6
7
  >The value the enclosing menu reports in its select event when this item is chosen. Give each item a distinct value so one handler can tell them
7
8
  apart.</prop>
9
+ <prop name="href" type="string" default="">A destination makes an action item a real link, with native navigation and link context actions.</prop>
10
+ <prop name="type" type="action | checkbox | radio" default="action"
11
+ >An action closes its menu; a checkbox toggles, and a radio chooses one item within its nearest Menu Group. Checkable items keep the menu open.</prop>
12
+ <prop name="checked" type="boolean" default="false">Initial or externally controlled checked state of a checkbox or radio item.</prop>
13
+ <state name="internalChecked" :value="false"></state>
14
+ <event name="change" type="object({ checked: boolean, value: string, type: checkbox | radio, trigger: keyboard | pointer | programmatic })"></event>
8
15
  </defs>
9
- <div role="menuitem" tabindex="-1" :aria-disabled="disabled" :data-value="value"><slot></slot></div>
16
+ <template $match>
17
+ <a
18
+ $when="href and type = 'action'"
19
+ role="menuitem"
20
+ tabindex="-1"
21
+ :aria-disabled="disabled"
22
+ :data-value="value"
23
+ :href="{ true: null, false: href }[format('%s', disabled)]"
24
+ >
25
+ <span class="label"><slot></slot></span>
26
+ <span class="shortcut" aria-hidden="true"><slot name="shortcut"></slot></span>
27
+ </a>
28
+ <div
29
+ $else
30
+ :role="{ action: 'menuitem', checkbox: 'menuitemcheckbox', radio: 'menuitemradio' }[type]"
31
+ :aria-checked="{ action: null, checkbox: internalChecked, radio: internalChecked }[type]"
32
+ tabindex="-1"
33
+ :aria-disabled="disabled"
34
+ :data-value="value"
35
+ >
36
+ <span class="indicator" aria-hidden="true">
37
+ <ui-icon $if="type = 'checkbox' and internalChecked" name="check"></ui-icon>
38
+ <ui-icon $if="type = 'radio' and internalChecked" name="circle-dot"></ui-icon>
39
+ </span>
40
+ <span class="label"><slot></slot></span>
41
+ <span class="shortcut" aria-hidden="true"><slot name="shortcut"></slot></span>
42
+ </div>
43
+ </template>
10
44
  <style>
11
45
  /* This component's hooks style the element they are set on, and never a nested one. */
12
46
  @property --ui-menu-item-font-size {
@@ -35,7 +69,9 @@
35
69
  }
36
70
 
37
71
  :host {
38
- display: block;
72
+ display: flex;
73
+ align-items: center;
74
+ gap: var(--ui-space-5);
39
75
  box-sizing: border-box;
40
76
  width: 100%;
41
77
  padding: var(--ui-menu-item-padding-block, var(--_ui-default-menu-item-padding-block, var(--ui-space-2)))
@@ -45,19 +81,44 @@
45
81
  color: var(--ui-text-primary);
46
82
  font-size: var(--ui-menu-item-font-size, var(--_ui-default-menu-item-font-size, var(--ui-font-size-md)));
47
83
  font-weight: var(--ui-font-medium);
84
+ text-decoration: none;
48
85
  cursor: pointer;
49
86
  outline: none;
50
87
  transition: background-color var(--ui-motion-fast) var(--ui-motion-ease), color var(--ui-motion-fast) var(--ui-motion-ease);
51
88
  }
52
89
 
53
- /* Centre the glyphs, not the line box: a label without descenders otherwise reads high. */
54
- /* Centre the glyphs, not the line box: a label without descenders otherwise reads high.
55
- * Trimming applies to block containers, so the box is a block with centred content. */
90
+ .label {
91
+ flex: 1;
92
+ min-inline-size: 0;
93
+ }
94
+
95
+ .indicator {
96
+ display: inline-flex;
97
+ place-items: center;
98
+ flex: none;
99
+ inline-size: var(--ui-icon-size-md);
100
+ color: var(--ui-accent-solid);
101
+ }
102
+
103
+ :host-state([type="action"]) .indicator {
104
+ display: none;
105
+ }
106
+
107
+ .shortcut {
108
+ margin-inline-start: auto;
109
+ color: var(--ui-text-secondary);
110
+ font-size: var(--ui-font-size-sm);
111
+ font-weight: var(--ui-font-normal);
112
+ }
113
+
114
+ .shortcut:empty {
115
+ display: none;
116
+ }
117
+
118
+ /* Keep the text row at the shared control height where cap trimming is available. */
56
119
  @supports (text-box-trim: trim-both) {
57
120
  :host {
58
- display: block;
59
121
  min-block-size: var(--ui-menu-item-min-block-size, var(--ui-control-size-md, 2.5rem));
60
- align-content: center;
61
122
  text-box-trim: trim-both;
62
123
  text-box-edge: cap alphabetic;
63
124
  }
@@ -67,11 +128,22 @@
67
128
  background: var(--ui-menu-item-hover-surface, var(--ui-surface-subtle));
68
129
  }
69
130
 
131
+ :host:focus-visible {
132
+ outline: 2px solid var(--ui-focus-ring);
133
+ outline-offset: -2px;
134
+ }
135
+
70
136
  :host[aria-disabled="true"] {
71
137
  opacity: 0.6;
72
138
  cursor: not-allowed;
73
139
  }
74
140
 
141
+ @media (forced-colors: active) {
142
+ :host:focus-visible {
143
+ outline-color: Highlight;
144
+ }
145
+ }
146
+
75
147
  /* A component that sets its display still honours the hidden attribute on its root. */
76
148
  :host[hidden] {
77
149
  display: none;