@threadlabs/looma 0.14.8 → 0.15.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 (343) 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-cluster/ui-cluster.html +7 -0
  14. package/components/ui-combobox/ui-combobox.html +46 -10
  15. package/components/ui-combobox/ui-combobox.js +5 -5
  16. package/components/ui-context-menu/ui-context-menu.html +33 -5
  17. package/components/ui-context-menu/ui-context-menu.js +26 -17
  18. package/components/ui-dialog/ui-dialog.html +51 -8
  19. package/components/ui-dialog/ui-dialog.js +28 -7
  20. package/components/ui-disclosure/ui-disclosure.html +14 -5
  21. package/components/ui-disclosure/ui-disclosure.js +51 -4
  22. package/components/ui-editable/ui-editable.html +17 -3
  23. package/components/ui-editable/ui-editable.js +7 -0
  24. package/components/ui-editor-insert-table-grid/ui-editor-insert-table-grid.html +48 -14
  25. package/components/ui-editor-insert-table-grid/ui-editor-insert-table-grid.js +40 -6
  26. package/components/ui-editor-mention-menu/ui-editor-mention-menu.html +22 -9
  27. package/components/ui-editor-mention-menu/ui-editor-mention-menu.js +33 -11
  28. package/components/ui-editor-mention-menu-item/ui-editor-mention-menu-item.html +67 -0
  29. package/components/ui-editor-slash-menu/ui-editor-slash-menu.html +33 -30
  30. package/components/ui-editor-slash-menu/ui-editor-slash-menu.js +42 -8
  31. package/components/ui-editor-slash-menu-group/ui-editor-slash-menu-group.html +31 -0
  32. package/components/ui-editor-slash-menu-item/ui-editor-slash-menu-item.html +75 -0
  33. package/components/ui-editor-table-context-menu/ui-editor-table-context-menu.html +16 -5
  34. package/components/ui-editor-table-context-menu/ui-editor-table-context-menu.js +10 -2
  35. package/components/ui-editor-table-overlay/ui-editor-table-overlay.html +5 -5
  36. package/components/ui-editor-table-overlay/ui-editor-table-overlay.js +5 -2
  37. package/components/ui-editor-table-toolbar/ui-editor-table-toolbar.html +14 -4
  38. package/components/ui-editor-table-toolbar/ui-editor-table-toolbar.js +2 -2
  39. package/components/ui-editor-toolbar/ui-editor-toolbar.html +1 -1
  40. package/components/ui-editor-toolbar/ui-editor-toolbar.js +46 -0
  41. package/components/ui-floating-action-button/ui-floating-action-button.html +1 -1
  42. package/components/ui-form-field/ui-form-field.html +16 -3
  43. package/components/ui-form-field/ui-form-field.js +1 -1
  44. package/components/ui-grid/ui-grid.html +17 -2
  45. package/components/ui-icon/ui-icon.html +5 -0
  46. package/components/ui-icon-button/ui-icon-button.html +25 -6
  47. package/components/ui-icon-button/ui-icon-button.js +11 -1
  48. package/components/ui-input/ui-input.html +3 -3
  49. package/components/ui-input-group/ui-input-group.html +18 -4
  50. package/components/ui-listbox/ui-listbox.html +81 -0
  51. package/components/ui-listbox/ui-listbox.js +27 -0
  52. package/components/ui-menu/ui-menu.html +33 -5
  53. package/components/ui-menu/ui-menu.js +18 -12
  54. package/components/ui-menu-group/ui-menu-group.html +27 -0
  55. package/components/ui-menu-item/ui-menu-item.html +80 -8
  56. package/components/ui-menu-item/ui-menu-item.js +47 -0
  57. package/components/ui-popover/ui-popover.html +33 -5
  58. package/components/ui-popover/ui-popover.js +9 -1
  59. package/components/ui-radio-group/ui-radio-group.html +13 -1
  60. package/components/ui-search-shell/ui-search-shell.html +6 -6
  61. package/components/ui-search-shell/ui-search-shell.js +21 -5
  62. package/components/ui-select/ui-select.html +14 -4
  63. package/components/ui-switch/ui-switch.html +2 -2
  64. package/components/ui-switcher/ui-switcher.html +8 -1
  65. package/components/ui-switcher/ui-switcher.js +15 -0
  66. package/components/ui-tabs/ui-tabs.html +17 -12
  67. package/components/ui-tabs/ui-tabs.js +43 -7
  68. package/components/ui-textarea/ui-textarea.html +28 -5
  69. package/components/ui-toast/ui-toast.html +88 -0
  70. package/components/ui-toast/ui-toast.js +68 -0
  71. package/components/ui-toast-region/ui-toast-region.html +102 -19
  72. package/components/ui-toast-region/ui-toast-region.js +10 -18
  73. package/components/ui-tooltip/ui-tooltip.html +51 -7
  74. package/components/ui-tooltip/ui-tooltip.js +29 -3
  75. package/components/ui-tree/ui-tree.html +12 -0
  76. package/components/ui-tree/ui-tree.js +191 -12
  77. package/components/ui-tree-item/ui-tree-item.html +87 -8
  78. package/components/ui-tree-item/ui-tree-item.js +3 -1
  79. package/dist/index.js +38 -32
  80. package/editor/chunks/extensions.js +82 -2
  81. package/editor/extensions/table-commands.d.ts +8 -0
  82. package/editor/icons.d.ts +5 -0
  83. package/editor/index.js +7 -2
  84. package/editor/insert-table-grid.d.ts +1 -1
  85. package/editor/mention-menu.d.ts +2 -0
  86. package/editor/slash-menu.d.ts +3 -0
  87. package/editor/table-context-menu.d.ts +1 -1
  88. package/editor/table-overlay.d.ts +2 -2
  89. package/package.json +1 -1
  90. package/styles/ui-avatar-group.css +1 -2
  91. package/styles/ui-avatar.css +11 -1
  92. package/styles/ui-badge.css +73 -1
  93. package/styles/ui-button.css +9 -1
  94. package/styles/ui-callout.css +1 -1
  95. package/styles/ui-checkbox.css +9 -0
  96. package/styles/ui-cluster.css +3 -0
  97. package/styles/ui-combobox.css +38 -7
  98. package/styles/ui-context-menu.css +32 -4
  99. package/styles/ui-dialog.css +42 -5
  100. package/styles/ui-editor-insert-table-grid.css +48 -11
  101. package/styles/ui-editor-mention-menu-item.css +57 -0
  102. package/styles/ui-editor-mention-menu.css +11 -1
  103. package/styles/ui-editor-slash-menu-group.css +21 -0
  104. package/styles/ui-editor-slash-menu-item.css +62 -0
  105. package/styles/ui-editor-slash-menu.css +10 -21
  106. package/styles/ui-editor-table-context-menu.css +1 -1
  107. package/styles/ui-floating-action-button.css +1 -1
  108. package/styles/ui-form-field.css +14 -1
  109. package/styles/ui-grid.css +14 -1
  110. package/styles/ui-icon-button.css +17 -5
  111. package/styles/ui-input-group.css +18 -4
  112. package/styles/ui-input.css +3 -3
  113. package/styles/ui-listbox.css +69 -0
  114. package/styles/ui-menu-group.css +19 -0
  115. package/styles/ui-menu-item.css +48 -9
  116. package/styles/ui-menu.css +32 -4
  117. package/styles/ui-popover.css +28 -4
  118. package/styles/ui-radio-group.css +9 -0
  119. package/styles/ui-search-shell.css +5 -5
  120. package/styles/ui-select.css +14 -4
  121. package/styles/ui-switch.css +2 -2
  122. package/styles/ui-switcher.css +4 -0
  123. package/styles/ui-textarea.css +27 -4
  124. package/styles/ui-toast-region.css +88 -4
  125. package/styles/ui-toast.css +74 -0
  126. package/styles/ui-tooltip.css +46 -6
  127. package/styles/ui-tree-item.css +72 -4
  128. package/styles/ui-tree.css +8 -0
  129. package/tokens.css +3 -0
  130. package/vanilla/UiAvatar.d.ts +2 -0
  131. package/vanilla/UiAvatar.js +8 -1
  132. package/vanilla/UiAvatarGroup.d.ts +2 -0
  133. package/vanilla/UiAvatarGroup.js +1 -1
  134. package/vanilla/UiBadge.d.ts +1 -1
  135. package/vanilla/UiBadge.js +1 -1
  136. package/vanilla/UiButton.d.ts +1 -0
  137. package/vanilla/UiButton.js +22 -6
  138. package/vanilla/UiCallout.d.ts +1 -1
  139. package/vanilla/UiCallout.js +1 -1
  140. package/vanilla/UiCheckbox.d.ts +1 -0
  141. package/vanilla/UiCheckbox.js +12 -9
  142. package/vanilla/UiCluster.d.ts +1 -0
  143. package/vanilla/UiCluster.js +2 -0
  144. package/vanilla/UiCombobox.d.ts +3 -0
  145. package/vanilla/UiCombobox.js +30 -15
  146. package/vanilla/UiContextMenu.d.ts +1 -1
  147. package/vanilla/UiContextMenu.js +1 -1
  148. package/vanilla/UiDialog.d.ts +5 -2
  149. package/vanilla/UiDialog.js +1 -1
  150. package/vanilla/UiDisclosure.d.ts +2 -0
  151. package/vanilla/UiDisclosure.js +32 -18
  152. package/vanilla/UiEditable.d.ts +5 -0
  153. package/vanilla/UiEditable.js +7 -3
  154. package/vanilla/UiEditorInsertTableGrid.js +1 -6
  155. package/vanilla/UiEditorMentionMenu.d.ts +4 -2
  156. package/vanilla/UiEditorMentionMenu.js +49 -31
  157. package/vanilla/UiEditorMentionMenuItem.d.ts +16 -0
  158. package/vanilla/UiEditorMentionMenuItem.js +56 -0
  159. package/vanilla/UiEditorSlashMenu.d.ts +4 -3
  160. package/vanilla/UiEditorSlashMenu.js +59 -46
  161. package/vanilla/UiEditorSlashMenuGroup.d.ts +14 -0
  162. package/vanilla/UiEditorSlashMenuGroup.js +48 -0
  163. package/vanilla/UiEditorSlashMenuItem.d.ts +18 -0
  164. package/vanilla/UiEditorSlashMenuItem.js +57 -0
  165. package/vanilla/UiEditorTableContextMenu.d.ts +5 -2
  166. package/vanilla/UiEditorTableContextMenu.js +1 -2
  167. package/vanilla/UiEditorTableOverlay.d.ts +1 -1
  168. package/vanilla/UiEditorTableOverlay.js +5 -5
  169. package/vanilla/UiEditorTableToolbar.d.ts +4 -2
  170. package/vanilla/UiEditorTableToolbar.js +1 -2
  171. package/vanilla/UiEditorToolbar.js +2 -0
  172. package/vanilla/UiGrid.d.ts +1 -0
  173. package/vanilla/UiGrid.js +8 -7
  174. package/vanilla/UiIcon.js +1 -1
  175. package/vanilla/UiIconButton.d.ts +1 -0
  176. package/vanilla/UiIconButton.js +11 -2
  177. package/vanilla/UiListbox.d.ts +20 -0
  178. package/vanilla/UiListbox.js +45 -0
  179. package/vanilla/UiMenu.d.ts +1 -1
  180. package/vanilla/UiMenu.js +1 -1
  181. package/vanilla/UiMenuGroup.d.ts +14 -0
  182. package/vanilla/UiMenuGroup.js +35 -0
  183. package/vanilla/UiMenuItem.d.ts +5 -1
  184. package/vanilla/UiMenuItem.js +91 -30
  185. package/vanilla/UiPopover.d.ts +1 -1
  186. package/vanilla/UiPopover.js +1 -1
  187. package/vanilla/UiRadioGroup.d.ts +1 -0
  188. package/vanilla/UiRadioGroup.js +6 -3
  189. package/vanilla/UiSwitcher.d.ts +1 -0
  190. package/vanilla/UiSwitcher.js +7 -9
  191. package/vanilla/UiTabs.d.ts +1 -0
  192. package/vanilla/UiTabs.js +9 -1
  193. package/vanilla/UiTextarea.d.ts +1 -0
  194. package/vanilla/UiTextarea.js +4 -1
  195. package/vanilla/UiToast.d.ts +16 -0
  196. package/vanilla/UiToast.js +53 -0
  197. package/vanilla/UiToastRegion.d.ts +1 -4
  198. package/vanilla/UiToastRegion.js +3 -11
  199. package/vanilla/UiTooltip.d.ts +1 -1
  200. package/vanilla/UiTooltip.js +1 -1
  201. package/vanilla/UiTree.d.ts +2 -0
  202. package/vanilla/UiTree.js +1 -1
  203. package/vanilla/UiTreeItem.d.ts +1 -0
  204. package/vanilla/UiTreeItem.js +45 -31
  205. package/vanilla/index.js +6 -0
  206. package/vue/UiAvatar.d.ts +4 -0
  207. package/vue/UiAvatar.vue +23 -3
  208. package/vue/UiAvatarGroup.d.ts +3 -0
  209. package/vue/UiAvatarGroup.vue +7 -4
  210. package/vue/UiBadge.d.ts +2 -2
  211. package/vue/UiBadge.vue +74 -2
  212. package/vue/UiButton.d.ts +5 -3
  213. package/vue/UiButton.vue +41 -7
  214. package/vue/UiCallout.d.ts +2 -2
  215. package/vue/UiCallout.vue +3 -2
  216. package/vue/UiCheckbox.d.ts +2 -0
  217. package/vue/UiCheckbox.vue +16 -0
  218. package/vue/UiCluster.d.ts +1 -0
  219. package/vue/UiCluster.vue +5 -0
  220. package/vue/UiCombobox.d.ts +11 -3
  221. package/vue/UiCombobox.vue +60 -11
  222. package/vue/UiContextMenu.d.ts +2 -0
  223. package/vue/UiContextMenu.vue +47 -6
  224. package/vue/UiDialog.d.ts +18 -4
  225. package/vue/UiDialog.vue +86 -11
  226. package/vue/UiDisclosure.d.ts +8 -2
  227. package/vue/UiDisclosure.vue +38 -14
  228. package/vue/UiEditable.d.ts +10 -0
  229. package/vue/UiEditable.vue +14 -2
  230. package/vue/UiEditorInsertTableGrid.vue +52 -15
  231. package/vue/UiEditorMentionMenu.d.ts +24 -2
  232. package/vue/UiEditorMentionMenu.vue +27 -11
  233. package/vue/UiEditorMentionMenuItem.d.ts +24 -0
  234. package/vue/UiEditorMentionMenuItem.js +2 -0
  235. package/vue/UiEditorMentionMenuItem.vue +88 -0
  236. package/vue/UiEditorSlashMenu.d.ts +24 -1
  237. package/vue/UiEditorSlashMenu.vue +33 -33
  238. package/vue/UiEditorSlashMenuGroup.d.ts +18 -0
  239. package/vue/UiEditorSlashMenuGroup.js +2 -0
  240. package/vue/UiEditorSlashMenuGroup.vue +43 -0
  241. package/vue/UiEditorSlashMenuItem.d.ts +26 -0
  242. package/vue/UiEditorSlashMenuItem.js +2 -0
  243. package/vue/UiEditorSlashMenuItem.vue +101 -0
  244. package/vue/UiEditorTableContextMenu.d.ts +9 -3
  245. package/vue/UiEditorTableContextMenu.vue +53 -3
  246. package/vue/UiEditorTableOverlay.d.ts +2 -2
  247. package/vue/UiEditorTableOverlay.vue +8 -6
  248. package/vue/UiEditorTableToolbar.d.ts +7 -3
  249. package/vue/UiEditorTableToolbar.vue +50 -2
  250. package/vue/UiEditorToolbar.vue +14 -1
  251. package/vue/UiFloatingActionButton.vue +1 -1
  252. package/vue/UiFormField.vue +28 -3
  253. package/vue/UiGrid.d.ts +1 -0
  254. package/vue/UiGrid.vue +26 -2
  255. package/vue/UiIcon.vue +16 -0
  256. package/vue/UiIconButton.d.ts +5 -3
  257. package/vue/UiIconButton.vue +40 -6
  258. package/vue/UiInput.vue +10 -3
  259. package/vue/UiInputGroup.vue +32 -11
  260. package/vue/UiListbox.d.ts +33 -0
  261. package/vue/UiListbox.js +2 -0
  262. package/vue/UiListbox.vue +131 -0
  263. package/vue/UiMenu.d.ts +2 -0
  264. package/vue/UiMenu.vue +43 -6
  265. package/vue/UiMenuGroup.d.ts +18 -0
  266. package/vue/UiMenuGroup.js +2 -0
  267. package/vue/UiMenuGroup.vue +39 -0
  268. package/vue/UiMenuItem.d.ts +34 -2
  269. package/vue/UiMenuItem.vue +145 -9
  270. package/vue/UiPopover.d.ts +2 -2
  271. package/vue/UiPopover.vue +44 -5
  272. package/vue/UiRadioGroup.d.ts +2 -0
  273. package/vue/UiRadioGroup.vue +17 -0
  274. package/vue/UiSearchShell.vue +12 -5
  275. package/vue/UiSelect.vue +18 -4
  276. package/vue/UiSwitch.vue +2 -2
  277. package/vue/UiSwitcher.d.ts +1 -0
  278. package/vue/UiSwitcher.vue +22 -1
  279. package/vue/UiTabs.d.ts +6 -2
  280. package/vue/UiTabs.vue +14 -12
  281. package/vue/UiTextarea.d.ts +2 -0
  282. package/vue/UiTextarea.vue +32 -4
  283. package/vue/UiToast.d.ts +34 -0
  284. package/vue/UiToast.js +2 -0
  285. package/vue/UiToast.vue +173 -0
  286. package/vue/UiToastRegion.d.ts +2 -18
  287. package/vue/UiToastRegion.vue +123 -48
  288. package/vue/UiTooltip.d.ts +2 -2
  289. package/vue/UiTooltip.vue +67 -12
  290. package/vue/UiTree.d.ts +10 -0
  291. package/vue/UiTree.vue +27 -0
  292. package/vue/UiTreeItem.d.ts +2 -0
  293. package/vue/UiTreeItem.vue +96 -4
  294. package/vue/chunks/UiAvatar.js +19 -5
  295. package/vue/chunks/UiAvatarGroup.js +10 -4
  296. package/vue/chunks/UiBadge.js +1 -1
  297. package/vue/chunks/UiButton.js +58 -11
  298. package/vue/chunks/UiCallout.js +2 -2
  299. package/vue/chunks/UiCheckbox.js +12 -2
  300. package/vue/chunks/UiCluster.js +5 -3
  301. package/vue/chunks/UiCombobox.js +29 -13
  302. package/vue/chunks/UiContextMenu.js +6 -2
  303. package/vue/chunks/UiDialog.js +25 -8
  304. package/vue/chunks/UiDisclosure.js +36 -12
  305. package/vue/chunks/UiEditable.js +22 -8
  306. package/vue/chunks/UiEditorInsertTableGrid.js +11 -14
  307. package/vue/chunks/UiEditorMentionMenu.js +30 -19
  308. package/vue/chunks/UiEditorMentionMenuItem.js +36 -0
  309. package/vue/chunks/UiEditorSlashMenu.js +33 -35
  310. package/vue/chunks/UiEditorSlashMenuGroup.js +26 -0
  311. package/vue/chunks/UiEditorSlashMenuItem.js +51 -0
  312. package/vue/chunks/UiEditorTableContextMenu.js +39 -5
  313. package/vue/chunks/UiEditorTableOverlay.js +6 -6
  314. package/vue/chunks/UiEditorTableToolbar.js +37 -4
  315. package/vue/chunks/UiEditorToolbar.js +18 -4
  316. package/vue/chunks/UiFloatingActionButton.js +1 -1
  317. package/vue/chunks/UiFormField.js +13 -9
  318. package/vue/chunks/UiGrid.js +6 -2
  319. package/vue/chunks/UiIcon.js +40 -2
  320. package/vue/chunks/UiIconButton.js +35 -4
  321. package/vue/chunks/UiInput.js +1 -1
  322. package/vue/chunks/UiInputGroup.js +1 -1
  323. package/vue/chunks/UiListbox.js +70 -0
  324. package/vue/chunks/UiMenu.js +6 -2
  325. package/vue/chunks/UiMenuGroup.js +25 -0
  326. package/vue/chunks/UiMenuItem.js +128 -7
  327. package/vue/chunks/UiPopover.js +1 -1
  328. package/vue/chunks/UiRadioGroup.js +8 -2
  329. package/vue/chunks/UiSearchShell.js +1 -1
  330. package/vue/chunks/UiSelect.js +1 -1
  331. package/vue/chunks/UiSwitch.js +1 -1
  332. package/vue/chunks/UiSwitcher.js +20 -3
  333. package/vue/chunks/UiTabs.js +4 -3
  334. package/vue/chunks/UiTextarea.js +7 -2
  335. package/vue/chunks/UiToast.js +81 -0
  336. package/vue/chunks/UiToastRegion.js +25 -49
  337. package/vue/chunks/UiTooltip.js +3 -7
  338. package/vue/chunks/UiTree.js +8 -4
  339. package/vue/chunks/UiTreeItem.js +28 -10
  340. package/vue/components.css +1856 -864
  341. package/vue/editor/index.js +263 -2
  342. package/vue/index.d.ts +6 -0
  343. package/vue/index.js +9 -3
@@ -3,6 +3,7 @@
3
3
  <prop name="checked" type="boolean" default="false"
4
4
  >Whether the checkbox is checked initially or when set externally. A form reset returns it to this.</prop>
5
5
  <prop name="disabled" type="boolean" default="false">Disables the native checkbox, so it cannot be toggled or focused.</prop>
6
+ <prop name="invalid" type="boolean" default="false">Exposes an externally validated error and styles the checkbox with the field danger colour.</prop>
6
7
  <prop name="indeterminate" type="boolean" default="false"
7
8
  >Shows the mixed state, for a checkbox that summarises a group whose items are partly checked. It sets the native indeterminate property, which is
8
9
  independent of checked, so update it as the group changes.</prop>
@@ -28,6 +29,7 @@
28
29
  .checked="internalChecked"
29
30
  .indeterminate="indeterminate"
30
31
  :disabled="disabled"
32
+ :aria-invalid="invalid"
31
33
  :required="required"
32
34
  :name="name"
33
35
  :value="value"
@@ -43,6 +45,10 @@
43
45
  syntax: "*";
44
46
  inherits: false;
45
47
  }
48
+ @property --ui-checkbox-invalid-border {
49
+ syntax: "*";
50
+ inherits: false;
51
+ }
46
52
  @property --ui-checkbox-size {
47
53
  syntax: "*";
48
54
  inherits: false;
@@ -51,6 +57,7 @@
51
57
  :host {
52
58
  /* Hooks do not inherit, so the parts below read them from the root. */
53
59
  --_ui-checkbox-size: var(--ui-checkbox-size);
60
+ --_ui-checkbox-invalid-border: var(--ui-checkbox-invalid-border, var(--ui-field-danger, var(--ui-danger-solid)));
54
61
 
55
62
  display: inline-flex;
56
63
  align-items: flex-start;
@@ -148,6 +155,10 @@
148
155
  outline-offset: 2px;
149
156
  }
150
157
 
158
+ :host-state([invalid]) input {
159
+ border-color: var(--_ui-checkbox-invalid-border);
160
+ }
161
+
151
162
  :host-state([disabled]) {
152
163
  color: var(--ui-disabled-text);
153
164
  cursor: not-allowed;
@@ -9,6 +9,10 @@
9
9
  <prop name="justify" type="start | center | end | between"
10
10
  >Where items sit along each row: start (the default), center, end, or between, which pushes the first and last items to the ends of the row and spreads the
11
11
  spare space between them, as for a title with its actions.</prop>
12
+ <prop name="wrap" type="wrap | nowrap"
13
+ >Whether items move onto a new row when they run out of space: wrap (the default) or nowrap, which keeps them on one row at their own sizes. Use nowrap for
14
+ a small group that must read as one unit, such as a meter and its value in a table cell, so the cell grows to fit it; a whole row of actions should keep
15
+ wrapping so it fits narrow screens.</prop>
12
16
  </defs>
13
17
  <div><slot></slot></div>
14
18
  <style>
@@ -62,6 +66,9 @@
62
66
  :host-state([justify="between"]) {
63
67
  justify-content: space-between;
64
68
  }
69
+ :host-state([wrap="nowrap"]) {
70
+ flex-wrap: nowrap;
71
+ }
65
72
 
66
73
  /* A component that sets its display still honours the hidden attribute on its root. */
67
74
  :host[hidden] {
@@ -50,6 +50,11 @@
50
50
  <prop name="required" type="boolean" default="false"
51
51
  >Requires a value: adds an asterisk to the label, marks the input required for native form validation, and makes validation report "A value is required."
52
52
  when the field is left empty. In multiple mode a chosen item satisfies it, whatever is left in the input.</prop>
53
+ <prop name="invalid" type="boolean" default="false"
54
+ >Exposes an externally validated error on the input and field. It remains until the consumer clears it, independently of native validation.</prop>
55
+ <prop name="openOnFocus" type="boolean" default="false">Opens suggestions when the input receives focus, before typing.</prop>
56
+ <prop name="loading" type="boolean" default="false"
57
+ >Marks the suggestion list busy while the consumer loads options. It announces loading and suppresses the empty state and create offer until cleared.</prop>
53
58
  <prop name="size" type="sm | md | lg" default="md"
54
59
  >Field height, padding, and type size, matching Button's sizes so a field and a button of one size share a row. sm is the small control height (2rem by
55
60
  default) with smaller text, for compact rows such as a table row or a filter bar; md is the standard control height; lg is taller with roomier padding.
@@ -81,7 +86,6 @@
81
86
  :value="[]"
82
87
  ></state>
83
88
  <state name="active" :value="-1"></state>
84
- <state name="loading" :value="false"></state>
85
89
  <state name="helpOpen" :value="false"></state>
86
90
  <state name="lookupError" :value="''"></state>
87
91
  <state
@@ -163,8 +167,8 @@
163
167
  autocomplete="off"
164
168
  :aria-controls="format('%s-listbox', uid)"
165
169
  :aria-expanded="expanded"
166
- :aria-invalid="validationStatus = 'error'"
167
- :aria-busy="validationStatus = 'pending'"
170
+ :aria-invalid="invalid or validationStatus = 'error'"
171
+ :aria-busy="loading or validationStatus = 'pending'"
168
172
  .value="display"
169
173
  :placeholder="placeholder"
170
174
  :disabled="disabled"
@@ -262,6 +266,7 @@
262
266
  >
263
267
  </div>
264
268
  <div $if="message" class="message" $value="message"></div>
269
+ <div $if="not loading and not rows[0] and not creatable and not lookupError" class="message"><slot name="empty">No suggestions.</slot></div>
265
270
  </div>
266
271
  <slot name="footer"></slot>
267
272
  </div>
@@ -313,6 +318,30 @@
313
318
  syntax: "*";
314
319
  inherits: false;
315
320
  }
321
+ @property --ui-combobox-radius {
322
+ syntax: "*";
323
+ inherits: false;
324
+ }
325
+ @property --ui-combobox-invalid-border {
326
+ syntax: "*";
327
+ inherits: false;
328
+ }
329
+ @property --ui-combobox-popup-surface {
330
+ syntax: "*";
331
+ inherits: false;
332
+ }
333
+ @property --ui-combobox-popup-border {
334
+ syntax: "*";
335
+ inherits: false;
336
+ }
337
+ @property --ui-combobox-popup-radius {
338
+ syntax: "*";
339
+ inherits: false;
340
+ }
341
+ @property --ui-combobox-popup-shadow {
342
+ syntax: "*";
343
+ inherits: false;
344
+ }
316
345
 
317
346
  :host {
318
347
  /* Hooks do not inherit, so the parts below read them from the root. */
@@ -326,6 +355,12 @@
326
355
  --_ui-combobox-item-min-block-size: var(--ui-combobox-item-min-block-size);
327
356
  --_ui-combobox-item-input-min-inline-size: var(--ui-combobox-item-input-min-inline-size);
328
357
  --_ui-combobox-item-item-max-inline-size: var(--ui-combobox-item-item-max-inline-size);
358
+ --_ui-combobox-radius: var(--ui-combobox-radius);
359
+ --_ui-combobox-invalid-border: var(--ui-combobox-invalid-border);
360
+ --_ui-combobox-popup-surface: var(--ui-combobox-popup-surface);
361
+ --_ui-combobox-popup-border: var(--ui-combobox-popup-border);
362
+ --_ui-combobox-popup-radius: var(--ui-combobox-popup-radius);
363
+ --_ui-combobox-popup-shadow: var(--ui-combobox-popup-shadow);
329
364
 
330
365
  display: grid;
331
366
  gap: var(--ui-space-1);
@@ -352,7 +387,7 @@
352
387
  display: flex;
353
388
  min-inline-size: 0;
354
389
  border: 1px solid var(--ui-control-border, var(--ui-border-default));
355
- border-radius: var(--ui-radius-md);
390
+ border-radius: var(--_ui-combobox-radius, var(--ui-field-radius, var(--ui-radius-md)));
356
391
  background: var(--ui-control-surface, var(--ui-surface-default));
357
392
  box-shadow: var(--ui-control-inset);
358
393
  transition: border-color var(--ui-motion-fast) var(--ui-motion-ease), box-shadow var(--ui-motion-fast) var(--ui-motion-ease);
@@ -470,8 +505,9 @@
470
505
  .field:has(input:disabled) {
471
506
  background: var(--ui-control-surface-disabled, var(--ui-surface-muted));
472
507
  }
473
- :host-state([validationStatus="error"]) .field {
474
- border-color: var(--ui-danger-solid, var(--ui-accent-active));
508
+ :host-state([validationStatus="error"]) .field,
509
+ :host-state([invalid]) .field {
510
+ border-color: var(--_ui-combobox-invalid-border, var(--ui-field-danger, var(--ui-danger-solid, var(--ui-accent-active))));
475
511
  }
476
512
  .popup {
477
513
  box-sizing: border-box;
@@ -481,11 +517,11 @@
481
517
  overflow: auto;
482
518
  margin: 0;
483
519
  padding: var(--ui-space-1);
484
- border: 1px solid var(--ui-border-default);
485
- border-radius: var(--ui-radius-md);
486
- background: var(--ui-surface-elevated);
520
+ border: 1px solid var(--_ui-combobox-popup-border, var(--ui-overlay-border, var(--ui-border-default)));
521
+ border-radius: var(--_ui-combobox-popup-radius, var(--ui-overlay-radius, var(--ui-radius-md)));
522
+ background: var(--_ui-combobox-popup-surface, var(--ui-overlay-surface, var(--ui-surface-elevated)));
487
523
  color: var(--ui-text-primary);
488
- box-shadow: var(--ui-elevation-overlay, var(--ui-shadow-lg));
524
+ box-shadow: var(--_ui-combobox-popup-shadow, var(--ui-overlay-shadow, var(--ui-elevation-overlay, var(--ui-shadow-lg))));
489
525
  }
490
526
  [hidden] {
491
527
  display: none;
@@ -110,7 +110,6 @@ export default function controller(host) {
110
110
  const cancelLookup = () => {
111
111
  lookup?.abort();
112
112
  lookup = undefined;
113
- host.state.loading = false;
114
113
  };
115
114
  const close = () => {
116
115
  host.state.expanded = false;
@@ -167,7 +166,6 @@ export default function controller(host) {
167
166
  // the group-name list below. Concatenating a Map iterator produces one
168
167
  // iterator row instead of the option objects it contains.
169
168
  host.state.rows = Array.from(groups.values()).flat();
170
- host.state.loading = false;
171
169
  // When nothing matches what was typed, the offer to create it is the only choice, so it is
172
170
  // the one Enter makes, and it is highlighted as such.
173
171
  if (query && !host.state.rows.some((row) => !row.disabled) && canCreate()) host.state.active = host.state.rows.length;
@@ -385,8 +383,7 @@ export default function controller(host) {
385
383
  host.state.createIndex = rows.length;
386
384
  // What a named combobox submits in single mode: the value, never the label the field shows.
387
385
  host.state.submitted = String(host.state.selected ?? (host.state.allowFreeText ? host.state.raw ?? "" : ""));
388
- host.state.message = host.state.loading ? "Loading suggestions…"
389
- : host.state.lookupError || (!rows.length && !host.state.creatable ? "No suggestions." : "");
386
+ host.state.message = host.state.loading ? "Loading suggestions…" : host.state.lookupError || "";
390
387
  const validation = host.state.validation ?? { status: "pristine", issues: [] };
391
388
  host.state.validationStatus = validation.status;
392
389
  const status = host.state.loading ? "Loading suggestions…" : host.state.lookupError || (host.state.expanded ? `${rows.length} suggestions available.` : "");
@@ -455,6 +452,9 @@ export default function controller(host) {
455
452
  if (!host.state.multiple && config().allowFreeText && host.state.selected === null) commit(null, host.state.raw, null, "free-entry", "keyboard");
456
453
  void validateCurrent();
457
454
  };
455
+ const onFocusin = (event) => {
456
+ if (event.target === input && host.state.openOnFocus && !host.state.expanded) open();
457
+ };
458
458
  const onCompositionstart = (event) => { if (event.target === input) composing = true; };
459
459
  const onCompositionend = (event) => { if (event.target === input) { composing = false; onInput(new InputEvent("input")); } };
460
460
  const onTooltipOpen = (event) => { if (event.target.closest?.('[role="tooltip"]')) host.state.helpOpen = true; };
@@ -482,7 +482,7 @@ export default function controller(host) {
482
482
 
483
483
  const api = { get input() { return input; }, validate: validateCurrent };
484
484
  instances.set(element, api);
485
- const listeners = { input: onInput, keydown: onKeydown, click: onClick, pointerdown: onPointerdown, focusout: onFocusout, compositionstart: onCompositionstart, compositionend: onCompositionend, open: onTooltipOpen, close: onTooltipClose };
485
+ const listeners = { input: onInput, keydown: onKeydown, click: onClick, pointerdown: onPointerdown, focusin: onFocusin, focusout: onFocusout, compositionstart: onCompositionstart, compositionend: onCompositionend, open: onTooltipOpen, close: onTooltipClose };
486
486
  for (const [name, listener] of Object.entries(listeners)) element.addEventListener(name, listener);
487
487
  // Authored options can change after mount (renamed, replaced, or arriving late). A selected value
488
488
  // then shows its current label, unless the user is editing the text.
@@ -11,11 +11,27 @@
11
11
  name="close"
12
12
  type="object({ open: boolean, reason: action | programmatic | light-dismiss | escape, trigger: keyboard | pointer | programmatic })"
13
13
  ></event>
14
- <event name="select" type="object({ value: string, trigger: keyboard | pointer | programmatic })"></event>
14
+ <event name="select" type="object({ value: string, checked?: boolean, trigger: keyboard | pointer | programmatic })"></event>
15
15
  </defs>
16
16
  <span><div class="menu" $ref="menu" role="menu" aria-orientation="vertical"><slot></slot></div></span>
17
17
  <style>
18
18
  /* This component's hooks style the element they are set on, and never a nested one. */
19
+ @property --ui-context-menu-surface {
20
+ syntax: "*";
21
+ inherits: false;
22
+ }
23
+ @property --ui-context-menu-border {
24
+ syntax: "*";
25
+ inherits: false;
26
+ }
27
+ @property --ui-context-menu-radius {
28
+ syntax: "*";
29
+ inherits: false;
30
+ }
31
+ @property --ui-context-menu-shadow {
32
+ syntax: "*";
33
+ inherits: false;
34
+ }
19
35
  @property --ui-context-menu-z-index {
20
36
  syntax: "*";
21
37
  inherits: false;
@@ -23,6 +39,10 @@
23
39
 
24
40
  :host {
25
41
  /* Hooks do not inherit, so the parts below read them from the root. */
42
+ --_ui-context-menu-surface: var(--ui-context-menu-surface);
43
+ --_ui-context-menu-border: var(--ui-context-menu-border);
44
+ --_ui-context-menu-radius: var(--ui-context-menu-radius);
45
+ --_ui-context-menu-shadow: var(--ui-context-menu-shadow);
26
46
  --_ui-context-menu-z-index: var(--ui-context-menu-z-index);
27
47
 
28
48
  display: contents;
@@ -36,10 +56,10 @@
36
56
  flex-direction: column;
37
57
  box-sizing: border-box;
38
58
  padding: var(--ui-space-1);
39
- background: var(--ui-surface-elevated);
40
- border: 1px solid var(--ui-border-default);
41
- border-radius: var(--ui-radius-lg);
42
- box-shadow: var(--ui-elevation-overlay, var(--ui-shadow-lg));
59
+ background: var(--_ui-context-menu-surface, var(--ui-overlay-surface, var(--ui-surface-elevated)));
60
+ border: 1px solid var(--_ui-context-menu-border, var(--ui-overlay-border, var(--ui-border-default)));
61
+ border-radius: var(--_ui-context-menu-radius, var(--ui-overlay-radius, var(--ui-radius-lg)));
62
+ box-shadow: var(--_ui-context-menu-shadow, var(--ui-overlay-shadow, var(--ui-elevation-overlay, var(--ui-shadow-lg))));
43
63
  outline: 0;
44
64
  overflow: auto;
45
65
  z-index: var(--_ui-context-menu-z-index, 1000);
@@ -53,6 +73,14 @@
53
73
  overlay var(--ui-motion-fast) allow-discrete;
54
74
  }
55
75
 
76
+ .menu hr {
77
+ align-self: stretch;
78
+ block-size: 0;
79
+ margin: var(--ui-space-1) 0;
80
+ border: 0;
81
+ border-block-start: 1px solid var(--ui-border-default);
82
+ }
83
+
56
84
  .menu:popover-open {
57
85
  opacity: 1;
58
86
  transform: translateY(0) scale(1);
@@ -1,4 +1,5 @@
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 disabled(item) {
4
5
  return item.getAttribute("aria-disabled") === "true" || item.hasAttribute("disabled") || item.getAttribute("disabled") === "true";
@@ -17,7 +18,7 @@ export default function controller(host) {
17
18
  let lastOpenProp = Boolean(host.state.open);
18
19
  host.state.internalOpen = lastOpenProp;
19
20
 
20
- const items = () => Array.from(element.querySelectorAll('[role="menuitem"]')).filter((item) => !disabled(item));
21
+ const items = () => menuItems(element);
21
22
  const focusFirst = () => requestAnimationFrame(() => items()[0]?.focus());
22
23
  const targetEvents = { contextmenu: onContextMenu, keydown: onTargetKeydown, pointerdown: onPointerdown, pointerup: endPress, pointercancel: endPress };
23
24
  const detach = () => {
@@ -101,33 +102,41 @@ export default function controller(host) {
101
102
  clearTimeout(press?.timer);
102
103
  press = null;
103
104
  }
105
+ const select = (item, trigger) => {
106
+ if (!item || disabled(item)) return;
107
+ const value = item.getAttribute("data-value") ?? item.getAttribute("value") ?? "";
108
+ if (item.getAttribute("role") !== "menuitem") {
109
+ const detail = { trigger, checked: undefined };
110
+ item.dispatchEvent(new CustomEvent("ui-menu-item-activate", { detail }));
111
+ host.dispatch("select", { value, checked: detail.checked, trigger });
112
+ return;
113
+ }
114
+ host.dispatch("select", { value, trigger });
115
+ close("action", trigger);
116
+ };
104
117
  const onKeydown = (event) => {
105
118
  if (!host.state.internalOpen) return;
106
119
  if (event.key === "Escape") {
107
120
  event.preventDefault();
108
121
  requestTopOverlayClose(document, "escape", "keyboard");
109
122
  } else if (["Enter", " "].includes(event.key)) {
110
- const item = event.target.closest?.('[role="menuitem"]');
123
+ const item = menuItemFrom(event.target);
111
124
  if (!item || disabled(item)) return;
112
- event.preventDefault();
113
- host.dispatch("select", { value: item.getAttribute("data-value") ?? item.getAttribute("value") ?? "", trigger: "keyboard" });
114
- close("action", "keyboard");
115
- } else if (event.key === "ArrowDown" || event.key === "ArrowUp") {
116
- const enabled = items();
117
- if (!enabled.length) return;
118
- event.preventDefault();
119
- const index = enabled.indexOf(event.target.closest?.('[role="menuitem"]'));
120
- const next = event.key === "ArrowDown"
121
- ? (index < 0 ? 0 : Math.min(index + 1, enabled.length - 1))
122
- : (index <= 0 ? enabled.length - 1 : index - 1);
123
- enabled[next].focus();
125
+ if (item.localName === "a") {
126
+ if (event.key === " ") { event.preventDefault(); item.click(); }
127
+ } else {
128
+ event.preventDefault();
129
+ select(item, "keyboard");
130
+ }
131
+ } else {
132
+ navigateMenu(event, element);
124
133
  }
125
134
  };
126
135
  const onClick = (event) => {
127
- const item = event.target.closest?.('[role="menuitem"]');
136
+ const item = menuItemFrom(event.target);
128
137
  if (!item || disabled(item)) return;
129
- host.dispatch("select", { value: item.getAttribute("data-value") ?? item.getAttribute("value") ?? "", trigger: "pointer" });
130
- close("action", "pointer");
138
+ const trigger = event.detail === 0 ? "keyboard" : "pointer";
139
+ select(item, trigger);
131
140
  };
132
141
  resolveTargets();
133
142
  if (menuSurface) surface = createAnchoredSurface(menuSurface, { anchor: target, placement: "bottom-start" });
@@ -1,13 +1,19 @@
1
1
  <template component="ui-dialog" controller="./ui-dialog.js">
2
2
  <defs>
3
3
  <prop name="open" type="boolean" default="false">Whether the dialog is open initially or when set externally.</prop>
4
- <prop name="modal" type="boolean" default="false">Open in the top layer with a backdrop and make the rest of the page inert.</prop>
5
- <prop name="dismissible" type="boolean" default="false"
6
- >Also close on Escape and on a pointer press outside the dialog. The header close button and actions always close it.</prop>
4
+ <prop name="modeless" type="boolean" default="false">Open without a backdrop or making the rest of the page inert; dialogs are modal by default.</prop>
5
+ <prop name="closedby" type="any | closerequest | none" default="closerequest"
6
+ >Which user close requests work: any includes outside presses, closerequest includes Escape, and none requires an action.</prop>
7
+ <prop name="alert" type="boolean" default="false">Use alert dialog semantics and require an explicit action instead of the header close button.</prop>
8
+ <prop name="size" type="sm | md | lg | fullscreen" default="md">Choose a compact, standard, wide, or viewport-filling dialog.</prop>
7
9
  <prop name="label" type="string">Visible title in the dialog header; also its accessible name.</prop>
8
10
  <prop name="for" type="string" default="">ID of the control that opens the dialog.</prop>
9
11
  <state name="internalOpen" :value="false"></state>
10
12
  <state name="accessibleLabel" :value="'Dialog'"></state>
13
+ <event
14
+ name="open"
15
+ type="object({ open: boolean, reason: action | programmatic, trigger: keyboard | pointer | programmatic })"
16
+ ></event>
11
17
  <event
12
18
  name="close"
13
19
  type="object({ open: boolean, reason: action | programmatic | light-dismiss | escape, trigger: keyboard | pointer | programmatic })"
@@ -27,6 +33,22 @@
27
33
  syntax: "*";
28
34
  inherits: false;
29
35
  }
36
+ @property --ui-dialog-surface {
37
+ syntax: "*";
38
+ inherits: false;
39
+ }
40
+ @property --ui-dialog-border {
41
+ syntax: "*";
42
+ inherits: false;
43
+ }
44
+ @property --ui-dialog-radius {
45
+ syntax: "*";
46
+ inherits: false;
47
+ }
48
+ @property --ui-dialog-shadow {
49
+ syntax: "*";
50
+ inherits: false;
51
+ }
30
52
  @property --ui-dialog-max-width {
31
53
  syntax: "*";
32
54
  inherits: false;
@@ -45,7 +67,7 @@
45
67
  box-sizing: border-box;
46
68
  inline-size: min(
47
69
  calc(100% - var(--ui-dialog-viewport-gap, var(--ui-space-4)) * 2),
48
- var(--ui-dialog-max-width, 440px)
70
+ var(--ui-dialog-max-width, var(--_ui-dialog-size, 440px))
49
71
  );
50
72
  max-inline-size: none;
51
73
  max-block-size: calc(100dvh - var(--ui-dialog-viewport-gap, var(--ui-space-4)) * 2);
@@ -53,10 +75,10 @@
53
75
  padding: 0;
54
76
  overflow: hidden;
55
77
  flex-direction: column;
56
- background: var(--ui-surface-elevated);
57
- border: 1px solid var(--ui-border-default);
58
- border-radius: var(--ui-radius-dialog);
59
- box-shadow: var(--ui-elevation-dialog, var(--ui-shadow-xl));
78
+ background: var(--ui-dialog-surface, var(--ui-overlay-surface, var(--ui-surface-elevated)));
79
+ border: 1px solid var(--ui-dialog-border, var(--ui-overlay-border, var(--ui-border-default)));
80
+ border-radius: var(--ui-dialog-radius, var(--ui-overlay-radius, var(--ui-radius-dialog)));
81
+ box-shadow: var(--ui-dialog-shadow, var(--ui-overlay-shadow, var(--ui-elevation-dialog, var(--ui-shadow-xl))));
60
82
  visibility: hidden;
61
83
  pointer-events: none;
62
84
  opacity: 0;
@@ -78,6 +100,27 @@
78
100
  animation: ui-dialog-enter var(--ui-dialog-duration, var(--ui-motion-base)) var(--ui-motion-ease);
79
101
  }
80
102
 
103
+ :host-state([size="sm"]) {
104
+ --_ui-dialog-size: 360px;
105
+ }
106
+
107
+ :host-state([size="lg"]) {
108
+ --_ui-dialog-size: 640px;
109
+ }
110
+
111
+ :host-state([size="fullscreen"]) {
112
+ inset: 0;
113
+ inline-size: 100%;
114
+ block-size: 100dvh;
115
+ max-block-size: none;
116
+ margin: 0;
117
+ border-radius: var(--ui-dialog-radius, 0);
118
+ }
119
+
120
+ :host-state([alert]) .close {
121
+ display: none;
122
+ }
123
+
81
124
  /* Header and actions stay pinned; only the body scrolls when content is long. */
82
125
  header {
83
126
  display: flex;
@@ -13,16 +13,21 @@ export default function controller(host) {
13
13
  let trigger = null;
14
14
  let lastFor = "";
15
15
  let lastExternalOpen = Boolean(host.state.open);
16
+ let lastOpen;
16
17
  host.state.internalOpen = lastExternalOpen;
17
18
 
18
19
  const requestClose = (reason, input) => {
19
20
  host.state.internalOpen = false;
21
+ lastOpen = false;
20
22
  host.dispatch("close", { open: false, reason, trigger: input });
21
23
  };
22
24
  const closeButton = host.refs.close;
23
25
  const onCloseClick = (event) => requestClose("action", event.detail === 0 ? "keyboard" : "pointer");
24
- const onTriggerClick = () => {
26
+ const onTriggerClick = (event) => {
27
+ if (host.state.internalOpen) return;
25
28
  host.state.internalOpen = true;
29
+ lastOpen = true;
30
+ host.dispatch("open", { open: true, reason: "action", trigger: event.detail === 0 ? "keyboard" : "pointer" });
26
31
  };
27
32
  const ids = createIdResolver(document, () => {
28
33
  lastFor = null;
@@ -49,30 +54,46 @@ export default function controller(host) {
49
54
  }
50
55
  host.state.accessibleLabel = inferredLabel(element, host.state.label);
51
56
  dialog?.setAttribute("aria-label", String(host.state.accessibleLabel));
57
+ if (host.state.alert) dialog?.setAttribute("role", "alertdialog");
58
+ else dialog?.removeAttribute("role");
52
59
  syncTrigger();
53
60
  const open = Boolean(host.state.internalOpen);
61
+ const modal = !host.state.modeless;
62
+ const requestedClosedBy = String(host.state.closedby ?? "closerequest");
63
+ const closedBy = host.state.alert && requestedClosedBy === "any" ? "closerequest" : requestedClosedBy;
54
64
  trigger?.setAttribute("aria-expanded", String(open));
55
65
  if (!dialog) return;
66
+ dialog.setAttribute("closedby", closedBy);
56
67
  if (open) {
68
+ if (dialog.open && dialog.matches(":modal") !== modal) dialog.close();
57
69
  if (!dialog.open) {
58
- if (host.state.modal) dialog.showModal();
70
+ if (modal) dialog.showModal();
59
71
  else dialog.show();
60
72
  }
61
- openOverlay({ id: overlayId, modal: Boolean(host.state.modal), element, dismissible: Boolean(host.state.dismissible), requestClose });
73
+ openOverlay({ id: overlayId, modal, element, dismissible: true, canClose: (reason) => reason === "light-dismiss" ? closedBy === "any" : reason !== "escape" || closedBy !== "none", requestClose });
62
74
  } else {
63
75
  if (dialog.open) dialog.close();
64
76
  closeOverlay(document, overlayId);
65
77
  }
78
+ if (lastOpen !== undefined && lastOpen !== open) {
79
+ host.dispatch(open ? "open" : "close", { open, reason: "programmatic", trigger: "programmatic" });
80
+ }
81
+ lastOpen = open;
66
82
  };
67
- const onClose = () => {
68
- if (!host.state.internalOpen) return;
83
+ const onClose = (event) => {
84
+ if (event instanceof CustomEvent) return;
85
+ // Consumers receive one Looma close event with a reason, not an additional native close event.
86
+ event.stopImmediatePropagation();
87
+ if (dialog.open || !host.state.internalOpen) return;
69
88
  host.state.internalOpen = false;
89
+ lastOpen = false;
70
90
  closeOverlay(document, overlayId);
71
91
  host.dispatch("close", { open: false, reason: "programmatic", trigger: "programmatic" });
72
92
  };
73
93
  const onKeydown = (event) => {
74
94
  if (event.key === "Escape") {
75
95
  event.preventDefault();
96
+ event.stopPropagation();
76
97
  requestTopOverlayClose(document, "escape", "keyboard");
77
98
  }
78
99
  };
@@ -85,7 +106,7 @@ export default function controller(host) {
85
106
  if (label !== host.state.accessibleLabel) host.state.accessibleLabel = label;
86
107
  });
87
108
  observer.observe(element, { childList: true, subtree: true, characterData: true });
88
- dialog?.addEventListener("close", onClose);
109
+ dialog?.addEventListener("close", onClose, true);
89
110
  dialog?.addEventListener("cancel", onCancel);
90
111
  closeButton?.addEventListener("click", onCloseClick);
91
112
  element.addEventListener("keydown", onKeydown);
@@ -95,7 +116,7 @@ export default function controller(host) {
95
116
  stop();
96
117
  ids.stop();
97
118
  observer.disconnect();
98
- dialog?.removeEventListener("close", onClose);
119
+ dialog?.removeEventListener("close", onClose, true);
99
120
  dialog?.removeEventListener("cancel", onCancel);
100
121
  closeButton?.removeEventListener("click", onCloseClick);
101
122
  trigger?.removeEventListener("click", onTriggerClick);
@@ -7,8 +7,15 @@
7
7
  >Whether the disclosure is open initially or when set externally. The trigger toggles it from there, reporting each change as an open or close event.</prop>
8
8
  <prop name="disabled" type="boolean" default="false"
9
9
  >Disables the trigger and mutes its text, so the disclosure keeps its current state and the user cannot toggle it.</prop>
10
+ <prop name="name" type="string" default=""
11
+ >Disclosures with the same nonempty name form an exclusive group in one document: opening one closes the others. An empty name allows multiple open
12
+ items.</prop>
13
+ <prop name="headingLevel" type="integer" default="0"
14
+ >Adds heading semantics around the trigger at level 2 through 6. Zero leaves it a plain disclosure; choose a level that matches the page outline.</prop>
10
15
  <state name="internalOpen" :value="false"></state>
11
16
  <state name="contentId" :value="''"></state>
17
+ <state name="headingRole" :value="null"></state>
18
+ <state name="headingAriaLevel" :value="null"></state>
12
19
  <event
13
20
  name="open"
14
21
  type="object({ open: boolean, reason: action | programmatic | light-dismiss | escape, trigger: keyboard | pointer | programmatic })"
@@ -19,11 +26,13 @@
19
26
  ></event>
20
27
  </defs>
21
28
  <div>
22
- <button class="trigger" type="button" $ref="trigger" :aria-controls="contentId" :aria-expanded="internalOpen" :disabled="disabled">
23
- <span $value="summary"></span>
24
- <span class="chevron" aria-hidden="true"></span>
25
- </button>
26
- <div class="panel" :id="contentId" :inert="not internalOpen"><div class="panel-inner"><slot></slot></div></div>
29
+ <div class="heading" :role="headingRole" :aria-level="headingAriaLevel">
30
+ <button class="trigger" type="button" $ref="trigger" :aria-controls="contentId" :aria-expanded="internalOpen" :disabled="disabled">
31
+ <span class="summary"><slot name="summary"><template $value="summary"></template></slot></span>
32
+ <span class="chevron" aria-hidden="true"></span>
33
+ </button>
34
+ </div>
35
+ <div class="panel" $ref="panel" :id="contentId"><div class="panel-inner"><slot></slot></div></div>
27
36
  </div>
28
37
  <style>
29
38
  /* This component's hooks style the element they are set on, and never a nested one. */