@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
@@ -29,10 +29,19 @@ export default function controller(host) {
29
29
  let tabStop = null;
30
30
  let hoverTimer = null;
31
31
  let hoverKey = null;
32
+ let typed = "";
33
+ let lastTyped = 0;
34
+ let moveMode = false;
35
+ const announcer = document.createElement("span");
36
+ announcer.setAttribute("role", "status");
37
+ announcer.style.cssText = "position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap";
38
+ document.body.append(announcer);
39
+ const announce = (message) => { announcer.textContent = message; };
32
40
 
33
41
  const allItems = () => Array.from(element.querySelectorAll(itemSelector));
34
42
  const visibleItems = () => allItems().filter((item) => item.getClientRects().length > 0 && item.getAttribute("aria-disabled") !== "true");
35
43
  const rowFor = (item) => item.querySelector(":scope > .row");
44
+ const selectionMode = () => String(host.state.selection || "none");
36
45
  // Only a branch has aria-expanded.
37
46
  const acceptsChildren = (item) => item.hasAttribute("aria-expanded") && item.getAttribute("aria-disabled") !== "true";
38
47
  const metadata = (item) => ({
@@ -91,6 +100,8 @@ export default function controller(host) {
91
100
  cancelHover();
92
101
  };
93
102
  const finish = () => {
103
+ moveMode = false;
104
+ element.removeAttribute("data-move-mode");
94
105
  source?.removeAttribute("data-dragging");
95
106
  source = null;
96
107
  clearTarget();
@@ -100,15 +111,12 @@ export default function controller(host) {
100
111
  };
101
112
  const itemFromEvent = (event) => event.composedPath().find((node) => node instanceof HTMLElement && node.matches?.(itemSelector)) ?? null;
102
113
  const itemAtPoint = (x, y) => document.elementFromPoint(x, y)?.closest?.(itemSelector) ?? null;
103
- const updateTarget = (nextTarget, y, transfer) => {
114
+ const chooseTarget = (nextTarget, nextPosition, transfer) => {
104
115
  if (!source || !nextTarget || nextTarget === source) {
105
116
  clearTarget();
106
117
  rejection = rejectedTarget = rejectedPosition = null;
107
118
  return false;
108
119
  }
109
- const row = rowFor(nextTarget);
110
- if (!row) return false;
111
- const nextPosition = classify(row.getBoundingClientRect(), y, acceptsChildren(nextTarget));
112
120
  const nextRejection = rejected(source, nextTarget, nextPosition);
113
121
  if (nextRejection) {
114
122
  if (transfer) transfer.dropEffect = "none";
@@ -132,21 +140,126 @@ export default function controller(host) {
132
140
  else cancelHover();
133
141
  return true;
134
142
  };
135
- const dispatchReorder = (from, to, nextPosition) => {
143
+ const updateTarget = (nextTarget, y, transfer) => {
144
+ const row = nextTarget && rowFor(nextTarget);
145
+ return row ? chooseTarget(nextTarget, classify(row.getBoundingClientRect(), y, acceptsChildren(nextTarget)), transfer) : false;
146
+ };
147
+ const dispatchReorder = (from, to, nextPosition, how = "pointer") => {
136
148
  const sourceId = itemId(from);
137
149
  const targetId = itemId(to);
138
150
  if (!sourceId || !targetId) return;
139
151
  if (nextPosition === "inside") expandTarget(targetId);
140
152
  const fromMeta = metadata(from);
141
153
  const toMeta = metadata(to);
142
- host.dispatch("reorder", { sourceId, targetId, position: nextPosition, sourceType: fromMeta.type, targetType: toMeta.type, sourceScope: fromMeta.scope, targetScope: toMeta.scope, trigger: "pointer" });
154
+ host.dispatch("reorder", { sourceId, targetId, position: nextPosition, sourceType: fromMeta.type, targetType: toMeta.type, sourceScope: fromMeta.scope, targetScope: toMeta.scope, trigger: how });
143
155
  };
144
- const dispatchRejected = (from, to, nextPosition, detail) => {
156
+ const dispatchRejected = (from, to, nextPosition, detail, how = "pointer") => {
145
157
  const sourceId = itemId(from);
146
158
  const targetId = itemId(to);
147
- if (sourceId && targetId) host.dispatch("reorder-rejected", { sourceId, targetId, position: nextPosition, reason: "max-depth", maxDepth: detail.maxDepth, resultingDepth: detail.resultingDepth, trigger: "pointer" });
159
+ if (sourceId && targetId) host.dispatch("reorder-rejected", { sourceId, targetId, position: nextPosition, reason: "max-depth", maxDepth: detail.maxDepth, resultingDepth: detail.resultingDepth, trigger: how });
160
+ };
161
+ const nameOf = (item) => item.getAttribute("aria-label") || itemId(item) || "item";
162
+ const beginMove = (item) => {
163
+ if (!itemId(item) || item.getAttribute("aria-disabled") === "true") return;
164
+ finish();
165
+ moveMode = true;
166
+ source = item;
167
+ source.setAttribute("data-dragging", "true");
168
+ element.setAttribute("data-move-mode", "");
169
+ announce(`Moving ${nameOf(item)}. Choose a destination by click or the arrow keys. Press Enter to place or Escape to cancel.`);
170
+ };
171
+ const commitMove = (how) => {
172
+ if (!source) return;
173
+ if (target && position) {
174
+ const message = `Move requested: ${nameOf(source)} ${position} ${nameOf(target)}.`;
175
+ dispatchReorder(source, target, position, how);
176
+ finish();
177
+ announce(message);
178
+ } else if (rejection && rejectedTarget && rejectedPosition) {
179
+ dispatchRejected(source, rejectedTarget, rejectedPosition, rejection, how);
180
+ finish();
181
+ announce(`Cannot move ${nameOf(source)} there: maximum depth exceeded.`);
182
+ } else announce("Choose another destination row.");
183
+ };
184
+ const onMoveClick = (event) => {
185
+ const handle = event.composedPath().find((node) => node instanceof HTMLElement && node.classList.contains("drag-handle"));
186
+ if (handle) {
187
+ event.preventDefault();
188
+ event.stopPropagation();
189
+ const item = itemFromEvent(event);
190
+ if (item) beginMove(item);
191
+ return;
192
+ }
193
+ if (!moveMode) {
194
+ if (selectionMode() === "none") return;
195
+ const item = itemFromEvent(event);
196
+ if (!item || item.getAttribute("aria-disabled") === "true") return;
197
+ const path = event.composedPath();
198
+ const checkbox = path.some((node) => node instanceof HTMLElement && node.classList.contains("selection-checkbox"));
199
+ const control = path.some((node) => node instanceof HTMLElement && node !== item && node.matches?.('a, button, input, select, textarea, [role="button"], [role="link"]'));
200
+ if (control && !checkbox) return;
201
+ event.preventDefault();
202
+ event.stopPropagation();
203
+ selectItem(item, "pointer");
204
+ focusItem(item);
205
+ return;
206
+ }
207
+ const item = itemFromEvent(event);
208
+ if (!item) return;
209
+ event.preventDefault();
210
+ event.stopPropagation();
211
+ if (updateTarget(item, event.clientY)) commitMove("pointer");
212
+ else if (rejection) commitMove("pointer");
213
+ else announce("Choose another destination row.");
214
+ };
215
+ const onMovePointer = (event) => {
216
+ if (!moveMode) return;
217
+ const item = itemFromEvent(event);
218
+ if (item && item !== source) updateTarget(item, event.clientY);
219
+ };
220
+ const onMoveKeydown = (event) => {
221
+ if (!moveMode) return false;
222
+ if (event.key === "Escape") {
223
+ event.preventDefault();
224
+ finish();
225
+ announce("Move cancelled.");
226
+ return true;
227
+ }
228
+ if (event.key === "Enter" || event.key === " ") {
229
+ event.preventDefault();
230
+ commitMove("keyboard");
231
+ return true;
232
+ }
233
+ if (event.key === "ArrowRight" && target) {
234
+ event.preventDefault();
235
+ if (chooseTarget(target, "inside")) announce(`Move ${nameOf(source)} inside ${nameOf(target)}.`);
236
+ else announce("That item cannot contain this one.");
237
+ return true;
238
+ }
239
+ if (event.key === "ArrowLeft" && target) {
240
+ event.preventDefault();
241
+ const side = allItems().indexOf(target) < allItems().indexOf(source) ? "before" : "after";
242
+ if (chooseTarget(target, side)) announce(`Move ${nameOf(source)} ${side} ${nameOf(target)}.`);
243
+ return true;
244
+ }
245
+ if (event.key !== "ArrowDown" && event.key !== "ArrowUp") return false;
246
+ event.preventDefault();
247
+ const items = visibleItems();
248
+ const step = event.key === "ArrowDown" ? 1 : -1;
249
+ let index = items.indexOf(target ?? source);
250
+ while (index + step >= 0 && index + step < items.length) {
251
+ index += step;
252
+ const candidate = items[index];
253
+ const side = step < 0 ? "before" : "after";
254
+ if (candidate !== source && chooseTarget(candidate, side)) {
255
+ announce(`Move ${nameOf(source)} ${side} ${nameOf(candidate)}. Press Enter to place.`);
256
+ break;
257
+ }
258
+ }
259
+ return true;
148
260
  };
149
261
  const onDragStart = (event) => {
262
+ if (moveMode) finish();
150
263
  const item = itemFromEvent(event);
151
264
  // An item renders its drag handle only while it is sortable and enabled.
152
265
  const fromHandle = event.composedPath().some((node) => node instanceof HTMLElement && node.classList.contains("drag-handle"));
@@ -204,9 +317,52 @@ export default function controller(host) {
204
317
  tabStop = next;
205
318
  for (const item of items) item.dispatchEvent(new CustomEvent("ui-tree-roving-tab-stop", { detail: { active: item === next } }));
206
319
  };
320
+ const syncSelection = () => {
321
+ const mode = selectionMode();
322
+ if (mode === "multiple") element.setAttribute("aria-multiselectable", "true");
323
+ else element.removeAttribute("aria-multiselectable");
324
+ for (const item of allItems()) {
325
+ if (mode === "multiple") item.setAttribute("data-selection-mode", "multiple");
326
+ else item.removeAttribute("data-selection-mode");
327
+ const checkbox = rowFor(item)?.querySelector(".selection-checkbox");
328
+ if (checkbox) {
329
+ const descendants = Array.from(item.querySelectorAll(itemSelector));
330
+ checkbox.indeterminate = mode === "multiple" && descendants.some((child) => child.getAttribute("aria-selected") === "true")
331
+ && (item.getAttribute("aria-selected") !== "true" || descendants.some((child) => child.getAttribute("aria-selected") !== "true"));
332
+ }
333
+ }
334
+ };
335
+ const selectItem = (item, how) => {
336
+ const mode = selectionMode();
337
+ const id = itemId(item);
338
+ if (mode === "none" || !id) return;
339
+ if (mode === "single") {
340
+ if (item.getAttribute("aria-selected") !== "true") host.dispatch("select", { ids: [id], trigger: how });
341
+ return;
342
+ }
343
+ const items = allItems();
344
+ const selected = new Set(items.filter((candidate) => candidate.getAttribute("aria-selected") === "true").map(itemId).filter(Boolean));
345
+ const subtree = [item, ...item.querySelectorAll(itemSelector)].filter((candidate) => candidate.getAttribute("aria-disabled") !== "true");
346
+ const adding = !selected.has(id);
347
+ for (const candidate of subtree) {
348
+ const candidateId = itemId(candidate);
349
+ if (!candidateId) continue;
350
+ if (adding) selected.add(candidateId);
351
+ else selected.delete(candidateId);
352
+ }
353
+ let ancestor = parentItem(item, element);
354
+ while (ancestor) {
355
+ const children = items.filter((candidate) => parentItem(candidate, element) === ancestor);
356
+ if (children.length && children.every((child) => selected.has(itemId(child)))) selected.add(itemId(ancestor));
357
+ else selected.delete(itemId(ancestor));
358
+ ancestor = parentItem(ancestor, element);
359
+ }
360
+ host.dispatch("select", { ids: items.map(itemId).filter((candidate) => candidate && selected.has(candidate)), trigger: how });
361
+ };
207
362
  const syncStructure = () => {
208
363
  for (const item of allItems()) item.dispatchEvent(new CustomEvent("ui-tree-structure-sync"));
209
364
  syncTabStop();
365
+ syncSelection();
210
366
  };
211
367
  const interactive = (event) => {
212
368
  for (const node of event.composedPath()) {
@@ -219,9 +375,15 @@ export default function controller(host) {
219
375
  const onFocusin = (event) => { if (!interactive(event)) { const item = itemFromEvent(event); if (item) syncTabStop(item); } };
220
376
  const onExpansion = () => requestAnimationFrame(() => syncTabStop());
221
377
  const onKeydown = (event) => {
378
+ if (onMoveKeydown(event)) return;
222
379
  if (interactive(event)) return;
223
380
  const current = itemFromEvent(event);
224
381
  if (!current) return;
382
+ if (event.key === " " && selectionMode() !== "none") {
383
+ event.preventDefault();
384
+ selectItem(current, "keyboard");
385
+ return;
386
+ }
225
387
  const items = visibleItems();
226
388
  const index = items.indexOf(current);
227
389
  if (index < 0) return;
@@ -234,17 +396,34 @@ export default function controller(host) {
234
396
  if (current.getAttribute("aria-expanded") === "false") { event.preventDefault(); current.dispatchEvent(new CustomEvent("ui-tree-request-expanded", { detail: { expanded: true, trigger: "keyboard" } })); }
235
397
  else if (current.getAttribute("aria-expanded") === "true") { const child = items.find((item) => parentItem(item, element) === current); if (child) { event.preventDefault(); focusItem(child); } }
236
398
  }
399
+ if (event.key.length === 1 && /^[\p{L}\p{N}]$/u.test(event.key) && !event.altKey && !event.ctrlKey && !event.metaKey) {
400
+ const now = Date.now();
401
+ typed = `${now - lastTyped < 700 ? typed : ""}${event.key.toLocaleLowerCase()}`;
402
+ lastTyped = now;
403
+ const candidates = [...items.slice(index + 1), ...items.slice(0, index + 1)];
404
+ const match = candidates.find((item) => (item.getAttribute("aria-label") || item.textContent || "").trim().toLocaleLowerCase().startsWith(typed));
405
+ if (match) { event.preventDefault(); focusItem(match); }
406
+ }
237
407
  };
238
- const listeners = { dragstart: onDragStart, drag: onDrag, dragenter: onDragOver, dragover: onDragOver, dragleave: onDragLeave, drop: onDrop, dragend: onDragEnd, keydown: onKeydown, focusin: onFocusin, "ui-tree-expansion-change": onExpansion };
408
+ const listeners = { dragstart: onDragStart, drag: onDrag, dragenter: onDragOver, dragover: onDragOver, dragleave: onDragLeave, drop: onDrop, dragend: onDragEnd, keydown: onKeydown, focusin: onFocusin, pointermove: onMovePointer, "ui-tree-expansion-change": onExpansion };
239
409
  for (const [name, listener] of Object.entries(listeners)) element.addEventListener(name, listener);
240
- const observer = new MutationObserver(syncStructure);
241
- observer.observe(element, { childList: true, subtree: true });
242
- const stop = host.effect(() => element.setAttribute("aria-label", String(host.state.label || "Tree")));
410
+ element.addEventListener("click", onMoveClick, true);
411
+ const observer = new MutationObserver((records) => {
412
+ if (records.some((record) => record.type === "childList")) syncStructure();
413
+ else syncSelection();
414
+ });
415
+ observer.observe(element, { childList: true, subtree: true, attributes: true, attributeFilter: ["aria-selected"] });
416
+ const stop = host.effect(() => {
417
+ element.setAttribute("aria-label", String(host.state.label || "Tree"));
418
+ syncSelection();
419
+ });
243
420
  syncStructure();
244
421
  return () => {
245
422
  stop();
246
423
  observer.disconnect();
247
424
  cancelHover();
425
+ announcer.remove();
248
426
  for (const [name, listener] of Object.entries(listeners)) element.removeEventListener(name, listener);
427
+ element.removeEventListener("click", onMoveClick, true);
249
428
  };
250
429
  }
@@ -13,9 +13,12 @@
13
13
  <prop name="container" type="boolean" default="false"
14
14
  >Makes the item a branch even before it has children: it gets the expand button and aria-expanded, and accepts drops inside it. An item with nested items is
15
15
  a branch without it.</prop>
16
+ <prop name="lazy" type="boolean" default="false"
17
+ >Keeps an empty item expandable while its children are supplied later. Expanding emits expand; the item is aria-busy until child Tree Items are appended.
18
+ Clear lazy if loading finishes with no children.</prop>
16
19
  <prop name="sortable" type="boolean" default="false"
17
- >Gives the item a drag handle, shown on hover, so it can be dragged to a new place in the tree. The tree reports the drop as a reorder event and leaves
18
- moving the item to you. Needs itemId; a disabled item, and any item under touch input, shows no handle.</prop>
20
+ >Gives the item a move handle. Drag it, or activate it and choose a target by a second click or the keyboard. The tree reports reorder and leaves moving the
21
+ item to you. Needs itemId; disabled items show no handle.</prop>
19
22
  <prop name="dragType" type="string" default="item"
20
23
  >The item's kind, for drag rules: it can be dropped before or after only items of the same type, and inside a branch only if that branch's accepts allows
21
24
  it. Reported as sourceType and targetType.</prop>
@@ -29,8 +32,8 @@
29
32
  >Whether the branch shows its children, initially or when set externally. The expand button, a click on the row, and the arrow keys toggle it from there,
30
33
  each reported as an expand event. Only meaningful for a branch.</prop>
31
34
  <prop name="selected" type="boolean" default="false"
32
- >Marks the item selected: it sets aria-selected and gives the row the highlighted surface. The tree does not select on click, so drive it from your own
33
- state.</prop>
35
+ >Marks the item selected: it sets aria-selected and highlights the row. This is controlled state; respond to the parent Tree's select request by updating
36
+ it.</prop>
34
37
  <prop name="marquee" type="boolean" default="false"
35
38
  >Scrolls a name too long for its row while the row is hovered or focused, then puts it back. Off by default: a tree of short names should not move, and a
36
39
  product decides whether reading the whole name matters more than a still list.</prop>
@@ -85,6 +88,14 @@
85
88
  <button class="disclosure" $ref="disclosure" type="button" :disabled="disabled" :hidden="not isContainer">
86
89
  <span class="disclosure-icon" aria-hidden="true"></span>
87
90
  </button>
91
+ <label class="selection-hit"><input
92
+ class="selection-checkbox"
93
+ type="checkbox"
94
+ tabindex="-1"
95
+ :checked="selected"
96
+ :disabled="disabled"
97
+ :aria-label="format('Select %s', label)"
98
+ ></label>
88
99
  <span class="leading" $ref="leading"><slot name="leading"></slot></span>
89
100
  <span class="label" $ref="label"><span class="label-text" $ref="labelText"><slot name="label"><template $value="label"></template></slot></span></span>
90
101
  <span class="actions" $ref="actions"><slot name="actions"></slot></span>
@@ -204,6 +215,35 @@
204
215
  display: none;
205
216
  }
206
217
 
218
+ .selection-hit {
219
+ display: none;
220
+ grid-column: 2;
221
+ grid-row: 1;
222
+ place-items: center;
223
+ inline-size: 1.25rem;
224
+ block-size: var(--_ui-tree-item-min-block-size, var(--_tree-row-min-height));
225
+ cursor: pointer;
226
+ }
227
+ .selection-checkbox {
228
+ inline-size: 1rem;
229
+ block-size: 1rem;
230
+ margin: 0;
231
+ accent-color: var(--ui-accent-solid);
232
+ }
233
+
234
+ :host[data-selection-mode="multiple"] > .row {
235
+ grid-template-columns: var(--_tree-disclosure-size) 1.25rem auto minmax(0, 1fr);
236
+ }
237
+ :host[data-selection-mode="multiple"] > .row > .selection-hit {
238
+ display: grid;
239
+ }
240
+ :host[data-selection-mode="multiple"] > .row > .leading {
241
+ grid-column: 3;
242
+ }
243
+ :host[data-selection-mode="multiple"] > .row > .label {
244
+ grid-column: 4;
245
+ }
246
+
207
247
  .disclosure:hover {
208
248
  color: var(--ui-text-primary);
209
249
  background: var(--ui-surface-elevated);
@@ -472,7 +512,7 @@
472
512
  inset-inline: 1px;
473
513
  display: none;
474
514
  block-size: 2px;
475
- border-radius: 999px;
515
+ border-radius: var(--ui-radius-round);
476
516
  background: var(--_ui-tree-drop-color, var(--ui-accent-solid));
477
517
  pointer-events: none;
478
518
  }
@@ -485,7 +525,7 @@
485
525
  inline-size: 7px;
486
526
  block-size: 7px;
487
527
  border: 2px solid var(--_ui-tree-drop-color, var(--ui-accent-solid));
488
- border-radius: 999px;
528
+ border-radius: var(--ui-radius-round);
489
529
  background: var(--ui-surface-elevated);
490
530
  transform: translateY(-50%);
491
531
  }
@@ -515,8 +555,47 @@
515
555
  --_tree-row-min-height: 44px;
516
556
  }
517
557
 
518
- html[data-ui-input-modality="touch"] :host .drag-handle {
519
- display: none;
558
+ html[data-ui-input-modality="touch"] :host .row {
559
+ grid-template-columns: 44px var(--_tree-disclosure-size) auto minmax(0, 1fr);
560
+ }
561
+
562
+ html[data-ui-input-modality="touch"] :host .drag-handle,
563
+ html[data-ui-input-modality="touch"] :host-state([isContainer]) > .row > .drag-handle {
564
+ position: relative;
565
+ grid-column: 1;
566
+ grid-row: 1;
567
+ inset: auto;
568
+ inline-size: 44px;
569
+ block-size: 44px;
570
+ padding: 14px;
571
+ opacity: 1;
572
+ transform: none;
573
+ cursor: pointer;
574
+ }
575
+
576
+ html[data-ui-input-modality="touch"] :host .disclosure {
577
+ grid-column: 2;
578
+ }
579
+ html[data-ui-input-modality="touch"] :host .leading {
580
+ grid-column: 3;
581
+ }
582
+ html[data-ui-input-modality="touch"] :host .label {
583
+ grid-column: 4;
584
+ }
585
+
586
+ html[data-ui-input-modality="touch"] :host[data-selection-mode="multiple"] > .row {
587
+ grid-template-columns: 44px var(--_tree-disclosure-size) 44px auto minmax(0, 1fr);
588
+ }
589
+ html[data-ui-input-modality="touch"] :host[data-selection-mode="multiple"] > .row > .selection-hit {
590
+ grid-column: 3;
591
+ inline-size: 44px;
592
+ block-size: 44px;
593
+ }
594
+ html[data-ui-input-modality="touch"] :host[data-selection-mode="multiple"] > .row > .leading {
595
+ grid-column: 4;
596
+ }
597
+ html[data-ui-input-modality="touch"] :host[data-selection-mode="multiple"] > .row > .label {
598
+ grid-column: 5;
520
599
  }
521
600
 
522
601
  @media (prefers-reduced-motion: reduce) {
@@ -27,7 +27,7 @@ export default function controller(host) {
27
27
  let lastExternalExpanded = Boolean(host.state.expanded);
28
28
  host.state.internalExpanded = lastExternalExpanded;
29
29
 
30
- const isContainer = () => Boolean(host.state.container) || childItems().length > 0;
30
+ const isContainer = () => Boolean(host.state.container) || Boolean(host.state.lazy) || childItems().length > 0;
31
31
  const updateLevel = () => {
32
32
  const tree = element.closest('[role="tree"]');
33
33
  let ancestor = parentItem(element);
@@ -52,6 +52,8 @@ export default function controller(host) {
52
52
  // A leaf has no aria-expanded at all; "false" would announce it as a collapsed branch.
53
53
  if (container) element.setAttribute("aria-expanded", String(expanded));
54
54
  else element.removeAttribute("aria-expanded");
55
+ if (host.state.lazy && expanded && childItems().length === 0) element.setAttribute("aria-busy", "true");
56
+ else element.removeAttribute("aria-busy");
55
57
  element.tabIndex = host.state.disabled || !host.state.tabStop ? -1 : 0;
56
58
  element.style.setProperty("--ui-tree-item-depth", String(Number(host.state.structuralLevel ?? 1) - 1));
57
59
  disclosure.setAttribute("aria-expanded", String(expanded));