@fusion.dev/helix-headless 0.145.0 → 0.146.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 (278) hide show
  1. package/custom-elements.json +7373 -3849
  2. package/dist/accordion/item/accordion-item.d.ts +14 -0
  3. package/dist/accordion/item/accordion-item.js +16 -6
  4. package/dist/accordion/root/accordion-root.d.ts +8 -5
  5. package/dist/announcement/root/announcement-root.d.ts +3 -2
  6. package/dist/announcement/root/announcement-root.js +3 -1
  7. package/dist/app-header/root/app-header-root.d.ts +7 -2
  8. package/dist/app-header/root/app-header-root.js +9 -1
  9. package/dist/app-header/sectioning.d.ts +3 -1
  10. package/dist/app-header/sectioning.js +3 -2
  11. package/dist/avatar/group/avatar-group.d.ts +20 -14
  12. package/dist/avatar/group/avatar-group.js +17 -19
  13. package/dist/badge/root/badge-root.d.ts +3 -1
  14. package/dist/badge/root/badge-root.js +2 -2
  15. package/dist/bar-chart/root/bar-chart-root.d.ts +18 -1
  16. package/dist/bar-chart/root/bar-chart-root.js +52 -15
  17. package/dist/bar-chart/stack.d.ts +15 -0
  18. package/dist/bar-chart/stack.js +19 -0
  19. package/dist/breadcrumbs/root/breadcrumbs-root.d.ts +25 -0
  20. package/dist/breadcrumbs/root/breadcrumbs-root.js +50 -3
  21. package/dist/button/root/button-root.d.ts +26 -0
  22. package/dist/button/root/button-root.js +38 -18
  23. package/dist/button-group/root/button-group-root.d.ts +2 -1
  24. package/dist/button-group/root/button-group-root.js +5 -1
  25. package/dist/canvas/node/canvas-node.d.ts +4 -3
  26. package/dist/canvas/node/canvas-node.js +3 -2
  27. package/dist/canvas/root/canvas-root.js +2 -2
  28. package/dist/card/root/card-root.d.ts +24 -3
  29. package/dist/card/root/card-root.js +20 -8
  30. package/dist/carousel/root/carousel-root.d.ts +43 -4
  31. package/dist/carousel/root/carousel-root.js +40 -12
  32. package/dist/carousel/stops.d.ts +20 -0
  33. package/dist/carousel/stops.js +30 -0
  34. package/dist/chat/message/chat-message.js +5 -4
  35. package/dist/chat/naming.d.ts +5 -1
  36. package/dist/chat/naming.js +4 -2
  37. package/dist/chat/root/chat-root.d.ts +25 -3
  38. package/dist/chat/root/chat-root.js +22 -5
  39. package/dist/checkbox/root/checkbox-root.d.ts +20 -0
  40. package/dist/checkbox/root/checkbox-root.js +14 -1
  41. package/dist/checkbox-group/root/checkbox-group-root.d.ts +8 -2
  42. package/dist/checkbox-group/root/checkbox-group-root.js +12 -10
  43. package/dist/checkbox-group/select-all.d.ts +27 -0
  44. package/dist/checkbox-group/select-all.js +12 -0
  45. package/dist/code-block/highlight.d.ts +15 -0
  46. package/dist/code-block/highlight.js +4 -0
  47. package/dist/code-block/root/code-block-root.d.ts +49 -1
  48. package/dist/code-block/root/code-block-root.js +58 -3
  49. package/dist/color-input/root/color-input-root.d.ts +3 -0
  50. package/dist/color-input/root/color-input-root.js +6 -3
  51. package/dist/color-picker/root/color-picker-root.d.ts +33 -4
  52. package/dist/color-picker/root/color-picker-root.js +80 -21
  53. package/dist/color-picker/swatches.d.ts +12 -0
  54. package/dist/color-picker/swatches.js +26 -0
  55. package/dist/combobox/root/combobox-root.d.ts +43 -0
  56. package/dist/combobox/root/combobox-root.js +79 -15
  57. package/dist/command/root/command-root.d.ts +16 -1
  58. package/dist/command/root/command-root.js +22 -4
  59. package/dist/copy-button/root/copy-button-root.d.ts +5 -0
  60. package/dist/copy-button/root/copy-button-root.js +10 -3
  61. package/dist/data-table/index.d.ts +1 -1
  62. package/dist/data-table/index.js +1 -1
  63. package/dist/data-table/keys.d.ts +24 -0
  64. package/dist/data-table/keys.js +24 -0
  65. package/dist/data-table/root/data-table-root.d.ts +27 -18
  66. package/dist/data-table/root/data-table-root.js +20 -52
  67. package/dist/data-table/tree.d.ts +16 -1
  68. package/dist/data-table/tree.js +2 -3
  69. package/dist/date-input/root/date-input-root.d.ts +3 -1
  70. package/dist/date-input/root/date-input-root.js +18 -7
  71. package/dist/date-picker/root/date-picker-root.js +12 -6
  72. package/dist/dialog/description.js +15 -1
  73. package/dist/dialog/root/dialog-root.d.ts +8 -2
  74. package/dist/dialog/root/dialog-root.js +16 -1
  75. package/dist/divider/root/divider-root.d.ts +3 -0
  76. package/dist/divider/root/divider-root.js +2 -1
  77. package/dist/donut-chart/root/donut-chart-root.d.ts +31 -0
  78. package/dist/donut-chart/root/donut-chart-root.js +48 -1
  79. package/dist/drawer/reveal.d.ts +8 -0
  80. package/dist/drawer/reveal.js +1 -0
  81. package/dist/drawer/root/drawer-root.d.ts +59 -0
  82. package/dist/drawer/root/drawer-root.js +63 -4
  83. package/dist/drilldown/item/drilldown-item.js +1 -1
  84. package/dist/drilldown/root/drilldown-root.d.ts +21 -3
  85. package/dist/drilldown/root/drilldown-root.js +21 -3
  86. package/dist/element-events.generated.js +31 -5
  87. package/dist/empty/root/empty-root.d.ts +3 -1
  88. package/dist/empty/root/empty-root.js +3 -2
  89. package/dist/file-input/root/file-input-root.d.ts +69 -6
  90. package/dist/file-input/root/file-input-root.js +82 -19
  91. package/dist/file-input/screen.d.ts +2 -1
  92. package/dist/filter-input/menu/filter-menu.d.ts +2 -0
  93. package/dist/filter-input/menu/filter-menu.js +12 -4
  94. package/dist/filter-input/root/filter-input-root.d.ts +30 -6
  95. package/dist/filter-input/root/filter-input-root.js +1 -1
  96. package/dist/filter-input/suggest.d.ts +28 -3
  97. package/dist/filter-input/suggest.js +18 -8
  98. package/dist/format-date/root/format-date-root.d.ts +9 -16
  99. package/dist/format-date/root/format-date-root.js +10 -27
  100. package/dist/heatmap/root/heatmap-root.d.ts +24 -13
  101. package/dist/heatmap/root/heatmap-root.js +34 -12
  102. package/dist/icon/root/icon-root.d.ts +12 -1
  103. package/dist/icon/root/icon-root.js +21 -10
  104. package/dist/icon/settle.d.ts +7 -0
  105. package/dist/icon/settle.js +10 -0
  106. package/dist/icon-button/root/icon-button-root.d.ts +2 -0
  107. package/dist/image/root/image-root.d.ts +5 -1
  108. package/dist/image/root/image-root.js +1 -1
  109. package/dist/input/root/input-root.d.ts +12 -2
  110. package/dist/input/root/input-root.js +11 -2
  111. package/dist/kbd/root/kbd-root.d.ts +35 -1
  112. package/dist/kbd/root/kbd-root.js +23 -4
  113. package/dist/legend/root/legend-root.d.ts +55 -8
  114. package/dist/legend/root/legend-root.js +55 -13
  115. package/dist/lightbox/root/lightbox-root.d.ts +49 -6
  116. package/dist/lightbox/root/lightbox-root.js +86 -19
  117. package/dist/lightbox/zoom-controller.d.ts +59 -0
  118. package/dist/lightbox/zoom-controller.js +128 -0
  119. package/dist/lightbox/zoom.d.ts +22 -0
  120. package/dist/lightbox/zoom.js +16 -0
  121. package/dist/line-chart/root/line-chart-root.d.ts +22 -1
  122. package/dist/line-chart/root/line-chart-root.js +21 -1
  123. package/dist/list/item/list-item.d.ts +2 -0
  124. package/dist/list/item/list-item.js +11 -2
  125. package/dist/list/root/list-root.d.ts +62 -13
  126. package/dist/list/root/list-root.js +70 -13
  127. package/dist/list/selection.d.ts +37 -7
  128. package/dist/list/selection.js +16 -7
  129. package/dist/listbox/root/listbox-root.d.ts +37 -1
  130. package/dist/listbox/root/listbox-root.js +49 -3
  131. package/dist/loader/busy.d.ts +10 -0
  132. package/dist/loader/busy.js +11 -0
  133. package/dist/loader/root/loader-root.d.ts +9 -2
  134. package/dist/loader/root/loader-root.js +23 -3
  135. package/dist/megamenu/root/megamenu-root.d.ts +51 -10
  136. package/dist/megamenu/root/megamenu-root.js +76 -5
  137. package/dist/menu/item/menu-item.d.ts +51 -2
  138. package/dist/menu/item/menu-item.js +47 -23
  139. package/dist/menu/root/menu-root.d.ts +14 -0
  140. package/dist/menu/root/menu-root.js +2 -2
  141. package/dist/meter/root/meter-root.d.ts +20 -1
  142. package/dist/meter/root/meter-root.js +11 -15
  143. package/dist/navigation/root/navigation-root.d.ts +2 -1
  144. package/dist/navigation/root/navigation-root.js +2 -2
  145. package/dist/notifications/root/notifications-root.d.ts +10 -3
  146. package/dist/notifications/root/notifications-root.js +19 -7
  147. package/dist/number-input/root/number-input-root.d.ts +17 -8
  148. package/dist/number-input/root/number-input-root.js +2 -7
  149. package/dist/object-inspector/root/object-inspector-root.d.ts +75 -27
  150. package/dist/object-inspector/root/object-inspector-root.js +117 -76
  151. package/dist/object-inspector/rows.d.ts +18 -0
  152. package/dist/object-inspector/rows.js +33 -0
  153. package/dist/option/root/option-root.d.ts +5 -3
  154. package/dist/outline/root/outline-root.d.ts +18 -0
  155. package/dist/outline/root/outline-root.js +23 -5
  156. package/dist/pagination/size/pagination-size.d.ts +1 -0
  157. package/dist/pagination/size/pagination-size.js +1 -1
  158. package/dist/pin-input/code.d.ts +10 -0
  159. package/dist/pin-input/code.js +13 -0
  160. package/dist/pin-input/root/pin-input-root.d.ts +25 -1
  161. package/dist/pin-input/root/pin-input-root.js +10 -11
  162. package/dist/popover/hide-outside.d.ts +14 -0
  163. package/dist/popover/hide-outside.js +41 -0
  164. package/dist/popover/root/popover-root.d.ts +57 -7
  165. package/dist/popover/root/popover-root.js +58 -3
  166. package/dist/progress/root/progress-root.d.ts +20 -0
  167. package/dist/progress/root/progress-root.js +15 -15
  168. package/dist/qr-code/export.d.ts +26 -1
  169. package/dist/qr-code/export.js +14 -0
  170. package/dist/qr-code/root/qr-code-root.d.ts +19 -5
  171. package/dist/qr-code/root/qr-code-root.js +58 -9
  172. package/dist/quote/marks.d.ts +8 -0
  173. package/dist/quote/marks.js +5 -0
  174. package/dist/quote/root/quote-root.d.ts +14 -2
  175. package/dist/quote/root/quote-root.js +23 -1
  176. package/dist/radio/root/radio-root.d.ts +6 -2
  177. package/dist/radio/root/radio-root.js +6 -1
  178. package/dist/rating/root/rating-root.d.ts +9 -4
  179. package/dist/rating/root/rating-root.js +31 -7
  180. package/dist/rating/steps.d.ts +7 -0
  181. package/dist/rating/steps.js +9 -0
  182. package/dist/scatter-chart/fit.d.ts +15 -0
  183. package/dist/scatter-chart/fit.js +35 -0
  184. package/dist/scatter-chart/root/scatter-chart-root.d.ts +21 -1
  185. package/dist/scatter-chart/root/scatter-chart-root.js +30 -17
  186. package/dist/scroll-area/root/scroll-area-root.d.ts +22 -51
  187. package/dist/scroll-area/root/scroll-area-root.js +57 -173
  188. package/dist/scroll-area/scrollbar-controller.d.ts +111 -0
  189. package/dist/scroll-area/scrollbar-controller.js +172 -0
  190. package/dist/scroll-area/scrollbar.d.ts +27 -0
  191. package/dist/scroll-area/scrollbar.js +9 -0
  192. package/dist/select/root/select-root.d.ts +15 -0
  193. package/dist/select/root/select-root.js +18 -6
  194. package/dist/skeleton/root/skeleton-root.d.ts +3 -0
  195. package/dist/skeleton/root/skeleton-root.js +4 -2
  196. package/dist/skip-link/root/skip-link-root.js +2 -2
  197. package/dist/slider/root/slider-root.d.ts +41 -4
  198. package/dist/slider/root/slider-root.js +23 -3
  199. package/dist/sparkline/readings.d.ts +18 -0
  200. package/dist/sparkline/readings.js +19 -0
  201. package/dist/sparkline/root/sparkline-root.d.ts +78 -7
  202. package/dist/sparkline/root/sparkline-root.js +118 -23
  203. package/dist/speed-dial/root/speed-dial-root.js +2 -2
  204. package/dist/splitter/root/splitter-root.d.ts +7 -12
  205. package/dist/splitter/root/splitter-root.js +209 -204
  206. package/dist/stat/root/stat-root.d.ts +14 -5
  207. package/dist/stat/root/stat-root.js +21 -5
  208. package/dist/steps/reach.d.ts +5 -0
  209. package/dist/steps/reach.js +3 -0
  210. package/dist/steps/root/steps-root.d.ts +49 -3
  211. package/dist/steps/root/steps-root.js +44 -5
  212. package/dist/steps/step/step.d.ts +7 -0
  213. package/dist/steps/step/step.js +7 -2
  214. package/dist/switch/root/switch-root.d.ts +10 -0
  215. package/dist/table/column/table-column.d.ts +0 -10
  216. package/dist/table/column/table-column.js +3 -19
  217. package/dist/table/root/table-root.d.ts +31 -10
  218. package/dist/table/root/table-root.js +2 -13
  219. package/dist/tabs/panel/tab-panel.d.ts +16 -3
  220. package/dist/tabs/panel/tab-panel.js +34 -5
  221. package/dist/tabs/root/tabs-root.d.ts +12 -0
  222. package/dist/tabs/root/tabs-root.js +1 -1
  223. package/dist/tabs/tab/tab.d.ts +5 -0
  224. package/dist/tabs/tab/tab.js +5 -2
  225. package/dist/tag/group/tag-group.d.ts +31 -4
  226. package/dist/tag/group/tag-group.js +46 -7
  227. package/dist/tag/root/tag-root.js +9 -2
  228. package/dist/textarea/root/textarea-root.d.ts +28 -1
  229. package/dist/textarea/root/textarea-root.js +40 -1
  230. package/dist/time-input/root/time-input-root.d.ts +5 -1
  231. package/dist/time-input/root/time-input-root.js +16 -8
  232. package/dist/time-picker/root/time-picker-root.d.ts +18 -4
  233. package/dist/time-picker/root/time-picker-root.js +7 -10
  234. package/dist/time-picker/wheel-name.d.ts +9 -0
  235. package/dist/time-picker/wheel-name.js +15 -0
  236. package/dist/timeline/group/timeline-group.js +2 -2
  237. package/dist/toast/item/toast-item.d.ts +14 -1
  238. package/dist/toast/item/toast-item.js +22 -5
  239. package/dist/toast/root/toast-root.d.ts +6 -0
  240. package/dist/toast/root/toast-root.js +16 -1
  241. package/dist/toggle/group/toggle-group.d.ts +24 -3
  242. package/dist/toggle/group/toggle-group.js +34 -5
  243. package/dist/tooltip/root/tooltip-root.d.ts +13 -2
  244. package/dist/tooltip/root/tooltip-root.js +9 -1
  245. package/dist/tour/back.d.ts +10 -0
  246. package/dist/tour/back.js +8 -0
  247. package/dist/tour/root/tour-root.d.ts +30 -2
  248. package/dist/tour/root/tour-root.js +34 -8
  249. package/dist/tree/item/tree-item.d.ts +7 -3
  250. package/dist/tree/item/tree-item.js +3 -0
  251. package/dist/tree/root/tree-root.d.ts +24 -3
  252. package/dist/tree/root/tree-root.js +32 -13
  253. package/dist/tree/selection.js +2 -15
  254. package/dist/trend/root/trend-root.d.ts +43 -18
  255. package/dist/trend/root/trend-root.js +31 -6
  256. package/dist/video/media-events.d.ts +13 -18
  257. package/dist/video/media-events.js +1 -5
  258. package/dist/video/root/video-root.d.ts +18 -5
  259. package/dist/video/root/video-root.js +19 -10
  260. package/dist/video-playlist/menu/video-playlist-menu.d.ts +2 -0
  261. package/dist/video-playlist/menu/video-playlist-menu.js +2 -1
  262. package/dist/video-playlist/root/video-playlist-root.d.ts +7 -1
  263. package/dist/virtual-list/fill.d.ts +0 -8
  264. package/dist/virtual-list/fill.js +1 -17
  265. package/dist/virtual-list/plan.d.ts +49 -0
  266. package/dist/virtual-list/plan.js +16 -0
  267. package/dist/virtual-list/root/virtual-list-root.d.ts +114 -13
  268. package/dist/virtual-list/root/virtual-list-root.js +183 -40
  269. package/lint.json +1 -1
  270. package/package.json +8 -8
  271. package/types/jsx.d.ts +4577 -835
  272. package/types/vue.d.ts +926 -168
  273. package/dist/chat/relative-clock.d.ts +0 -39
  274. package/dist/chat/relative-clock.js +0 -31
  275. package/dist/data-table/root/order.d.ts +0 -141
  276. package/dist/data-table/root/order.js +0 -76
  277. package/dist/tag/selection.d.ts +0 -10
  278. package/dist/tag/selection.js +0 -12
@@ -109,10 +109,16 @@ export declare class HelixHeadlessToast extends HelixElement {
109
109
  * stack, holding `message` as its text. The region removes it once it has
110
110
  * been dismissed and has finished leaving, which is the one case where it
111
111
  * removes anything: a message you render stays yours.
112
+ *
113
+ * `heading` gives the message a title, which names it, and `message` becomes
114
+ * the line of detail under it. `action` is an element for the message's
115
+ * `actions` slot, such as an Undo button you have wired up.
112
116
  */
113
117
  add(message: string, options?: {
118
+ action?: Element;
114
119
  dismissible?: boolean;
115
120
  duration?: number;
121
+ heading?: string;
116
122
  tone?: 'danger' | 'info' | 'success' | 'warning';
117
123
  }): HTMLElement;
118
124
  /** Removes a message `add()` made, once it has hidden and finished leaving. */
@@ -92,7 +92,16 @@ let HelixHeadlessToast = class HelixHeadlessToast extends HelixElement {
92
92
  add(message, options = {}) {
93
93
  const tier = this.constructor.tier;
94
94
  const item = document.createElement(`${tier}toast-item`);
95
- item.textContent = message;
95
+ if (options.heading === undefined) {
96
+ item.textContent = message;
97
+ }
98
+ else {
99
+ item.append(slotted('heading', options.heading), slotted('description', message));
100
+ }
101
+ if (options.action) {
102
+ options.action.setAttribute('slot', 'actions');
103
+ item.append(options.action);
104
+ }
96
105
  if (options.tone) {
97
106
  item.setAttribute('tone', options.tone);
98
107
  }
@@ -238,3 +247,9 @@ const popoverOpen = (region) => {
238
247
  const focusTarget = (item) => item.shadowRoot?.querySelector('[part="dismiss"]') ??
239
248
  focusableElements(item)[0] ??
240
249
  null;
250
+ const slotted = (slot, words) => {
251
+ const element = document.createElement('div');
252
+ element.slot = slot;
253
+ element.textContent = words;
254
+ return element;
255
+ };
@@ -18,9 +18,12 @@ declare const HelixHeadlessToggleGroup_base: (new (...args: any[]) => import("@f
18
18
  * say whether they are pressed with `aria-pressed`, as React Aria's
19
19
  * ToggleButtonGroup and Radix's ToggleGroup do. A press presses or releases
20
20
  * one item, the arrow keys move focus without pressing, and `value` is the
21
- * pressed values, comma separated, which `values` also gives as a list. The
22
- * form receives one entry per pressed value. The indicator stays hidden, and
23
- * each pressed item is marked with `:state(pressed)`.
21
+ * pressed values, comma separated, which `values` also gives as a list. Use
22
+ * `values` where an item's value holds a comma. The form receives one entry
23
+ * per pressed value, and a page the browser brings back presses the same
24
+ * items again. The indicator stays hidden, and each pressed item is marked
25
+ * with `:state(pressed)`. Inside a toolbar it is a named group of that
26
+ * toolbar's controls instead, so a reader meets one toolbar.
24
27
  *
25
28
  * `orientation="vertical"` runs the items down a column, says so to the
26
29
  * accessibility tree, and the up and down arrows move between them.
@@ -178,6 +181,16 @@ export declare class HelixHeadlessToggleGroup extends HelixHeadlessToggleGroup_b
178
181
  disconnectedCallback(): void;
179
182
  protected firstUpdated(changed: PropertyValues): void;
180
183
  protected updated(changed: PropertyValues): void;
184
+ /**
185
+ * The group's role: a radio group, a toolbar of pressed buttons, or, for
186
+ * pressed buttons inside a toolbar, a group.
187
+ *
188
+ * **A toolbar inside a toolbar is a reader's dead end**, so inside one the
189
+ * pressed buttons are a named group of the outer toolbar, as React Aria's
190
+ * ToggleButtonGroup demotes itself. Read by tag and by role, since the
191
+ * outer toolbar is a Helix one or the page's own `role="toolbar"`.
192
+ */
193
+ private get groupRole();
181
194
  protected willUpdate(changed: PropertyValues): void;
182
195
  /**
183
196
  * What the form receives: the value, or under `selection-mode="multiple"`
@@ -203,6 +216,14 @@ export declare class HelixHeadlessToggleGroup extends HelixHeadlessToggleGroup_b
203
216
  */
204
217
  get values(): string[];
205
218
  set values(next: string[]);
219
+ /** The pressed values as a list last written, which `values` reads. */
220
+ private chosen;
221
+ /**
222
+ * Presses the items a page the browser brings back held. Under
223
+ * `selection-mode="multiple"` the saved state is the form's entries, one per
224
+ * pressed value, which the shared restore leaves alone.
225
+ */
226
+ formStateRestoreCallback(state: string | File | FormData | null): void;
206
227
  /**
207
228
  * The row the track sits in, so something can sit beside it.
208
229
  *
@@ -39,11 +39,19 @@ let HelixHeadlessToggleGroup = class HelixHeadlessToggleGroup extends MixinContr
39
39
  return;
40
40
  }
41
41
  const value = event.detail.value;
42
- this.requestValue(this.multiple
43
- ? toggleValue(this.values, value, this.items.map((item) => item.value)).join(',')
44
- : value);
42
+ if (!this.multiple) {
43
+ this.requestValue(value);
44
+ return;
45
+ }
46
+ const next = toggleValue(this.values, value, this.items.map((item) => item.value));
47
+ const was = this.chosen;
48
+ this.chosen = next;
49
+ if (!this.requestValue(next.join(','))) {
50
+ this.chosen = was;
51
+ }
45
52
  });
46
53
  };
54
+ this.chosen = undefined;
47
55
  }
48
56
  adopt() {
49
57
  if (this.value !== '' || this.children === undefined) {
@@ -120,10 +128,18 @@ let HelixHeadlessToggleGroup = class HelixHeadlessToggleGroup extends MixinContr
120
128
  this.validate();
121
129
  this.linkHost();
122
130
  }
131
+ get groupRole() {
132
+ if (!this.multiple) {
133
+ return 'radiogroup';
134
+ }
135
+ return this.parentElement?.closest('[role="toolbar"], helix-toolbar, helix-headless-toolbar')
136
+ ? 'group'
137
+ : 'toolbar';
138
+ }
123
139
  willUpdate(changed) {
124
140
  super.willUpdate(changed);
125
141
  this.adopt();
126
- hostAria(this.internals, { label: this.label, role: this.multiple ? 'toolbar' : 'radiogroup' });
142
+ hostAria(this.internals, { label: this.label, role: this.groupRole });
127
143
  this.internals.ariaOrientation = this.orientation ?? null;
128
144
  this.internals.setFormValue(this.formValue());
129
145
  }
@@ -144,11 +160,24 @@ let HelixHeadlessToggleGroup = class HelixHeadlessToggleGroup extends MixinContr
144
160
  this.reportRequired(!this.answered, 'Choose one to continue.', 'track');
145
161
  }
146
162
  get values() {
147
- return this.multiple ? commaList(this.value) : this.value === '' ? [] : [this.value];
163
+ if (!this.multiple) {
164
+ return this.value === '' ? [] : [this.value];
165
+ }
166
+ return this.chosen && this.chosen.join(',') === this.value
167
+ ? [...this.chosen]
168
+ : commaList(this.value);
148
169
  }
149
170
  set values(next) {
171
+ this.chosen = [...next];
150
172
  this.value = next.join(',');
151
173
  }
174
+ formStateRestoreCallback(state) {
175
+ if (!(state instanceof FormData) || !this.multiple) {
176
+ super.formStateRestoreCallback(state);
177
+ return;
178
+ }
179
+ this.values = state.getAll(this.name).filter((one) => typeof one === 'string');
180
+ }
152
181
  render() {
153
182
  const inside = this.labelPlacement === 'start';
154
183
  const label = this.renderGroupLabel();
@@ -30,7 +30,9 @@ declare const HelixHeadlessTooltip_base: (new (...args: any[]) => import("@fusio
30
30
  *
31
31
  * The tooltip shows on hover and on focus. It hides on pointer leave, on blur,
32
32
  * on Escape, and when the trigger is activated. The pointer can move from the
33
- * trigger onto the bubble and the bubble stays.
33
+ * trigger onto the bubble and the bubble stays, unless you set
34
+ * `hoverable="false"`. `delay` is how long the pointer rests before it shows,
35
+ * and `delay-out` how long it stays once the pointer leaves.
34
36
  *
35
37
  * Once one tooltip is showing, moving to another trigger shows its tooltip
36
38
  * with no `delay`, and the first one hides. The same holds for `skip-delay`
@@ -87,7 +89,9 @@ export declare class HelixHeadlessTooltip extends HelixHeadlessTooltip_base {
87
89
  */
88
90
  delay: number;
89
91
  /**
90
- * How long the bubble stays after the pointer leaves, in milliseconds.
92
+ * How long the bubble stays after the pointer leaves the trigger or the
93
+ * bubble, in milliseconds: the close delay. It is the time a reader has to
94
+ * cross the gap onto the bubble.
91
95
  */
92
96
  delayOut: number;
93
97
  /**
@@ -115,6 +119,13 @@ export declare class HelixHeadlessTooltip extends HelixHeadlessTooltip_base {
115
119
  * why this does not reflect and nothing styles on it.
116
120
  */
117
121
  for: string;
122
+ /**
123
+ * Whether the pointer can move onto the bubble and the bubble stays, so a
124
+ * reader can read it under a magnifier or select its words, as WCAG 1.4.13
125
+ * asks. `hoverable="false"` hides the bubble once the pointer reaches it,
126
+ * for a bubble that covers something a reader moves on to.
127
+ */
128
+ hoverable: boolean;
118
129
  /**
119
130
  * The space between the trigger and the tip of the bubble, in pixels.
120
131
  *
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { anchorElement, arrowReach, customElement, elementById, HelixElement, hostAria, linkDescription, MixinControllerOpen, PopoverController, reportRuntime, sideOf, unlinkDescription, } from '@fusion.dev/helix-core';
7
+ import { anchorElement, arrowReach, customElement, elementById, HelixElement, hostAria, linkDescription, MixinControllerOpen, onUnlessFalse, PopoverController, reportRuntime, sideOf, unlinkDescription, } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
9
  import { property, state } from 'lit/decorators.js';
10
10
  import { focusTarget } from '../focus-target.js';
@@ -23,6 +23,7 @@ let HelixHeadlessTooltip = class HelixHeadlessTooltip extends MixinControllerOpe
23
23
  this.delayOut = 100;
24
24
  this.disabled = false;
25
25
  this.for = '';
26
+ this.hoverable = true;
26
27
  this.offset = 8;
27
28
  this.persist = false;
28
29
  this.placement = 'top';
@@ -68,6 +69,10 @@ let HelixHeadlessTooltip = class HelixHeadlessTooltip extends MixinControllerOpe
68
69
  this.timer = setTimeout(() => this.show(), this.delay);
69
70
  };
70
71
  this.holdOnBubble = () => {
72
+ if (!this.hoverable) {
73
+ this.hideLater();
74
+ return;
75
+ }
71
76
  if (!this.named) {
72
77
  return;
73
78
  }
@@ -365,6 +370,9 @@ __decorate([
365
370
  __decorate([
366
371
  property()
367
372
  ], HelixHeadlessTooltip.prototype, "for", void 0);
373
+ __decorate([
374
+ property({ converter: onUnlessFalse })
375
+ ], HelixHeadlessTooltip.prototype, "hoverable", void 0);
368
376
  __decorate([
369
377
  property({ type: Number })
370
378
  ], HelixHeadlessTooltip.prototype, "offset", void 0);
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The stop Back goes to from `index`: the nearest stop before it that is not a
3
+ * `wait` stop, or `undefined` where there is none.
4
+ *
5
+ * A `wait` stop shows nothing and moves on by itself once its target is on the
6
+ * page, so going back onto one either moves straight on again or leaves the
7
+ * reader at a stop with nothing in it. Back passes over it to the stop that
8
+ * told the reader what to do.
9
+ */
10
+ export declare const stopBefore: (types: readonly (string | undefined)[], index: number) => number | undefined;
@@ -0,0 +1,8 @@
1
+ export const stopBefore = (types, index) => {
2
+ for (let at = Math.min(index, types.length) - 1; at >= 0; at -= 1) {
3
+ if (types[at] !== 'wait') {
4
+ return at;
5
+ }
6
+ }
7
+ return undefined;
8
+ };
@@ -22,6 +22,11 @@ declare const HelixHeadlessTour_base: (new (...args: any[]) => import("@fusion.d
22
22
  * `toggle` work as they do on `Dialog`. Setting `open` starts the run, and
23
23
  * `skip()` and `requestOpen(false)` end it early.
24
24
  *
25
+ * The box the light falls on is on the `spotlight` part as
26
+ * `--tour-spotlight-x`, `--tour-spotlight-y`, `--tour-spotlight-width` and
27
+ * `--tour-spotlight-height`, for a sheet that draws its own light, such as a
28
+ * ring around the target.
29
+ *
25
30
  * Each stop is told its place in the run, from `progress-label`, such as "2 of
26
31
  * 5". It describes the stop's panel, so a screen reader says it at every stop,
27
32
  * and `progress` draws it in the panel as well. A dialog or a floating stop,
@@ -50,12 +55,19 @@ declare const HelixHeadlessTour_base: (new (...args: any[]) => import("@fusion.d
50
55
  * @event {HelixCompleteEvent} helix-complete - Fired when the last stop is
51
56
  * finished.
52
57
  * @event {HelixTourAskEvent} helix-tour-ask - Fired when a stop's control is pressed to move the tour. The stop sends it, and the tour handles it.
58
+ * @event {HelixTourStatusEvent} helix-tour-status - Fired when the tour starts waiting at a `wait` stop, is skipped, or is completed, with the stop's index and `detail.status` saying which.
53
59
  * @event {ToggleEvent} toggle - Sent when the tour starts or ends, with `newState` and `oldState`.
54
60
  *
55
61
  * @csspart backdrop - Behind a dialog or a floating stop, which lights nothing. It draws no box until you give it one.
56
62
  * @csspart spotlight - The box over the current target. It also draws the dimmed area around the target.
57
63
  *
64
+ * @cssproperty --tour-spotlight-height - The lit box's height, in pixels, padding included. Written on the spotlight while it lights a target.
65
+ * @cssproperty --tour-spotlight-width - The lit box's width, in pixels, padding included. Written on the spotlight while it lights a target.
66
+ * @cssproperty --tour-spotlight-x - The lit box's left edge, from the window's left edge, in pixels. Written on the spotlight while it lights a target.
67
+ * @cssproperty --tour-spotlight-y - The lit box's top edge, from the window's top edge, in pixels. Written on the spotlight while it lights a target.
68
+ *
58
69
  * @cssstate open - While the tour is running.
70
+ * @cssstate waiting - While the tour waits at a `wait` stop for its target to be on the page.
59
71
  */
60
72
  export declare class HelixHeadlessTour extends HelixHeadlessTour_base {
61
73
  /**
@@ -131,7 +143,10 @@ export declare class HelixHeadlessTour extends HelixHeadlessTour_base {
131
143
  get length(): number;
132
144
  /** Goes to the next stop, and finishes the tour on the last stop. */
133
145
  next(): void;
134
- /** Goes to the previous stop. It does nothing on the first stop. */
146
+ /**
147
+ * Goes to the previous stop, passing over a `wait` stop, which shows nothing.
148
+ * It does nothing on the first stop, or where only `wait` stops come before.
149
+ */
135
150
  back(): void;
136
151
  /**
137
152
  * Ends the tour early, as a close. `helix-complete` is not fired.
@@ -164,7 +179,20 @@ export declare class HelixHeadlessTour extends HelixHeadlessTour_base {
164
179
  * where to be.
165
180
  */
166
181
  private follow;
167
- /** Focuses the current stop's panel once the stop has shown it. */
182
+ /**
183
+ * Where focus was put while a `wait` stop waits, so the move on from it still
184
+ * counts as a move from inside the tour while focus has stayed there.
185
+ */
186
+ private parked;
187
+ /**
188
+ * Focuses the current stop's panel once the stop has shown it.
189
+ *
190
+ * A `wait` stop shows no panel, and focus would fall to the body. It goes to
191
+ * the target of the stop before instead, which is usually the control the
192
+ * reader is asked to use, such as the button that opens the menu the stop
193
+ * waits for. Where that target holds no control, it goes back to where it
194
+ * was when the tour opened.
195
+ */
168
196
  private focusCurrent;
169
197
  /**
170
198
  * Tells each stop where it is in the run.
@@ -4,9 +4,10 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { customElement, deepActiveElement, deepContains, fillTemplate, HelixActivateEvent, HelixBeforeActivateEvent, HelixCompleteEvent, HelixElement, hostAria, MixinControllerOpen, requestChange, whenHydrated, } from '@fusion.dev/helix-core';
7
+ import { controlsIn, customElement, deepActiveElement, deepContains, fillTemplate, HelixActivateEvent, HelixBeforeActivateEvent, HelixCompleteEvent, HelixElement, HelixTourStatusEvent, hostAria, MixinControllerOpen, requestChange, whenHydrated, } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
9
  import { property, queryAssignedElements, state } from 'lit/decorators.js';
10
+ import { stopBefore } from '../back.js';
10
11
  import { onPage, waitFor } from '../wait.js';
11
12
  let HelixHeadlessTour = class HelixHeadlessTour extends MixinControllerOpen(HelixElement) {
12
13
  constructor() {
@@ -69,16 +70,20 @@ let HelixHeadlessTour = class HelixHeadlessTour extends MixinControllerOpen(Heli
69
70
  this.move(this.index + 1);
70
71
  }
71
72
  back() {
72
- if (this.index <= 0) {
73
- return;
73
+ const to = stopBefore(this.stops.map((stop) => stop.type), this.index);
74
+ if (to !== undefined) {
75
+ this.move(to);
74
76
  }
75
- this.move(this.index - 1);
76
77
  }
77
78
  skip() {
78
- this.requestOpen(false);
79
+ const at = this.index;
80
+ if (this.open && this.requestOpen(false)) {
81
+ this.dispatchEvent(new HelixTourStatusEvent({ index: at, status: 'skipped' }));
82
+ }
79
83
  }
80
84
  finish() {
81
85
  this.dispatchEvent(new HelixCompleteEvent({ value: String(this.index) }));
86
+ this.dispatchEvent(new HelixTourStatusEvent({ index: this.index, status: 'completed' }));
82
87
  this.open = false;
83
88
  }
84
89
  move(to) {
@@ -115,6 +120,7 @@ let HelixHeadlessTour = class HelixHeadlessTour extends MixinControllerOpen(Heli
115
120
  this.unwait?.();
116
121
  this.unwait = undefined;
117
122
  this.waiting = undefined;
123
+ this.toggleState('waiting', false);
118
124
  }
119
125
  willUpdate(changed) {
120
126
  super.willUpdate(changed);
@@ -138,7 +144,13 @@ let HelixHeadlessTour = class HelixHeadlessTour extends MixinControllerOpen(Heli
138
144
  const opened = changed.has('open') && this.open && changed.get('open') === false;
139
145
  const closed = changed.has('open') && !this.open && changed.get('open') === true;
140
146
  const active = deepActiveElement();
141
- const inside = active === null || active === document.body || deepContains(this, active);
147
+ const inside = active === null ||
148
+ active === document.body ||
149
+ deepContains(this, active) ||
150
+ (this.parked !== undefined && active === this.parked);
151
+ if (opened || closed || changed.has('index')) {
152
+ this.parked = undefined;
153
+ }
142
154
  if (opened) {
143
155
  if (active instanceof HTMLElement && !deepContains(this, active)) {
144
156
  this.returnFocus = active;
@@ -158,10 +170,21 @@ let HelixHeadlessTour = class HelixHeadlessTour extends MixinControllerOpen(Heli
158
170
  }
159
171
  focusCurrent() {
160
172
  const stops = this.stops;
161
- const stop = stops[Math.max(0, Math.min(this.index, stops.length - 1))];
173
+ const at = Math.max(0, Math.min(this.index, stops.length - 1));
174
+ const stop = stops[at];
162
175
  if (!stop) {
163
176
  return;
164
177
  }
178
+ if (stop.type === 'wait') {
179
+ const before = stopBefore(stops.map((one) => one.type), at);
180
+ const target = before === undefined ? undefined : stops[before]?.target;
181
+ const park = (target ? controlsIn([target])[0] : undefined) ?? this.returnFocus;
182
+ if (park?.isConnected) {
183
+ park.focus();
184
+ this.parked = park;
185
+ }
186
+ return;
187
+ }
165
188
  void stop.updateComplete.then(() => {
166
189
  if (this.open && stop.current) {
167
190
  stop.focus();
@@ -213,12 +236,15 @@ let HelixHeadlessTour = class HelixHeadlessTour extends MixinControllerOpen(Heli
213
236
  this.unwait?.();
214
237
  this.unwait = undefined;
215
238
  this.waiting = next;
239
+ this.toggleState('waiting', next !== undefined);
216
240
  if (!next) {
217
241
  return;
218
242
  }
243
+ this.dispatchEvent(new HelixTourStatusEvent({ index: this.index, status: 'waiting' }));
219
244
  this.unwait = waitFor(next.getRootNode(), () => onPage(next.target), () => {
220
245
  this.waiting = undefined;
221
246
  this.unwait = undefined;
247
+ this.toggleState('waiting', false);
222
248
  queueMicrotask(() => {
223
249
  if (this.open && this.current === next) {
224
250
  this.next();
@@ -274,7 +300,7 @@ let HelixHeadlessTour = class HelixHeadlessTour extends MixinControllerOpen(Heli
274
300
  : html `<div
275
301
  part="spotlight"
276
302
  aria-hidden="true"
277
- style=${`left: ${spot.left}px; top: ${spot.top}px; block-size: ${spot.height}px; inline-size: ${spot.width}px`}
303
+ style=${`left: ${spot.left}px; top: ${spot.top}px; block-size: ${spot.height}px; inline-size: ${spot.width}px; --tour-spotlight-x: ${spot.left}px; --tour-spotlight-y: ${spot.top}px; --tour-spotlight-width: ${spot.width}px; --tour-spotlight-height: ${spot.height}px`}
278
304
  ></div>`}
279
305
  <slot></slot>`;
280
306
  }
@@ -21,7 +21,8 @@ declare const HelixHeadlessTreeItem_base: (new (...args: any[]) => import("@fusi
21
21
  *
22
22
  * Pressing a branch, or Enter or Space on it, opens or closes it. A branch with
23
23
  * a `value` is chosen as well and sends `helix-select`, unless the tree has
24
- * `nesting="heading"`, where a branch names the list under it.
24
+ * `nesting="heading"`, where a branch names the list under it, or
25
+ * `selection-mode="leaf"`, where only the items at the ends are chosen.
25
26
  *
26
27
  * A `disabled` item is reached by the arrow keys and announced as unavailable,
27
28
  * and a press, Enter or Space on it does nothing.
@@ -58,7 +59,9 @@ declare const HelixHeadlessTreeItem_base: (new (...args: any[]) => import("@fusi
58
59
  *
59
60
  * @cssproperty --tree-level - How deep this row sits, counting from one as `aria-level` does, for the indent.
60
61
  *
62
+ * @cssstate checked - While it is checked, in a tree with `selection-mode="multiple"`.
61
63
  * @cssstate loading - While a `lazy` item is open and its children have not arrived.
64
+ * @cssstate mixed - While some of a branch's items are checked, in a tree with `selection-mode="multiple"`.
62
65
  * @cssstate open - While its children are shown.
63
66
  * @cssstate selected - While it is selected.
64
67
  */
@@ -80,8 +83,9 @@ export declare class HelixHeadlessTreeItem extends HelixHeadlessTreeItem_base {
80
83
  disabled: boolean;
81
84
  /**
82
85
  * @internal Set by the tree: whether a branch heads the list under it, as
83
- * under `nesting="heading"`. Such a branch only opens and closes when it is
84
- * pressed, because it names a list and is not a destination of its own.
86
+ * under `nesting="heading"`, or is not chosen at all, as under
87
+ * `selection-mode="leaf"`. Such a branch only opens and closes when it is
88
+ * pressed.
85
89
  */
86
90
  heads: boolean;
87
91
  /**
@@ -110,6 +110,7 @@ let HelixHeadlessTreeItem = class HelixHeadlessTreeItem extends MixinControllerO
110
110
  renderToggle() {
111
111
  return html `<slot
112
112
  name="chevron"
113
+ aria-hidden="true"
113
114
  @click=${this.href === '' ? nothing : this.handleToggleClick}
114
115
  ><svg part="toggle" fill="currentColor" viewBox=${caretDown.viewBox} aria-hidden="true">
115
116
  ${unsafeSVG(caretDown.content)}
@@ -140,6 +141,8 @@ let HelixHeadlessTreeItem = class HelixHeadlessTreeItem extends MixinControllerO
140
141
  willUpdate(changed) {
141
142
  super.willUpdate(changed);
142
143
  this.toggleState('selected', this.selected);
144
+ this.toggleState('checked', this.checkable && this.checked === true);
145
+ this.toggleState('mixed', this.checkable && this.checked === 'mixed');
143
146
  this.toggleState('loading', this.open && this.lazy);
144
147
  if (this.childrenKnown) {
145
148
  this.branch = this.knownChildren.some((child) => child.slot === 'children');
@@ -24,6 +24,18 @@ declare const HelixHeadlessTree_base: (new (...args: any[]) => import("@fusion.d
24
24
  * checked values, comma separated, which `values` also gives as a list, and
25
25
  * the tree says `aria-multiselectable`.
26
26
  *
27
+ * `selection-mode="leaf"` chooses one item at a time and only the items at the
28
+ * ends: a press, Enter or Space on a branch opens or closes it and chooses
29
+ * nothing, as Web Awesome's `leaf` mode does.
30
+ *
31
+ * Keys: Up and Down Arrow move between the items a reader can see; Right
32
+ * Arrow opens a shut branch and then moves into it, and Left Arrow shuts an
33
+ * open one and then moves to its parent, the two swapping on a right-to-left
34
+ * page. Home and End go to the first and the last item, Enter and Space choose
35
+ * or open, and the tree's one tab stop stays on the item last reached. In a
36
+ * tree of check marks, Control+A (Command+A on an Apple keyboard) checks every
37
+ * item or clears them all, and Shift with Up or Down Arrow moves and checks.
38
+ *
27
39
  * @since 0.1.0
28
40
  * @status stable
29
41
  * @category navigation
@@ -91,12 +103,12 @@ export declare class HelixHeadlessTree extends HelixHeadlessTree_base {
91
103
  */
92
104
  nesting?: 'heading' | 'peer' | 'subordinate';
93
105
  /**
94
- * Whether a reader can check many items. Unset, one item is chosen at a
95
- * time.
106
+ * Whether a reader can check many items, or choose only the items at the
107
+ * ends. Unset, one item is chosen at a time, branches included.
96
108
  *
97
109
  * Optional, so a tree of one choice writes no attribute onto the host.
98
110
  */
99
- selectionMode?: 'multiple';
111
+ selectionMode?: 'leaf' | 'multiple';
100
112
  /**
101
113
  * The checked values, as a list, under `selection-mode="multiple"`, and the
102
114
  * chosen value alone otherwise. Writing it sets `active`.
@@ -154,6 +166,15 @@ export declare class HelixHeadlessTree extends HelixHeadlessTree_base {
154
166
  * Returns whether the press was one of those.
155
167
  */
156
168
  private handleTyping;
169
+ /**
170
+ * The keys of a tree of check marks, as the tree pattern gives them:
171
+ * Control+A (Command+A on an Apple keyboard) checks every item, or clears
172
+ * them all where every one is checked, and Shift with Up or Down Arrow moves
173
+ * to the item before or after and checks or unchecks it.
174
+ *
175
+ * Returns whether the press was one of those.
176
+ */
177
+ private handleMultiple;
157
178
  private move;
158
179
  private focusOn;
159
180
  private readonly handleKeydown;
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { AriaForwardController, closestInstance, commaList, customElement, HelixElement, inlineStep, keyClaimed, MixinCollection, MixinControllerActive, scrollItemIntoView, slottedWords, Typeahead, typesCharacter, whenHydrated, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, closestInstance, commaList, customElement, HelixElement, inlineStep, keyClaimed, MixinCollection, MixinControllerActive, scrollItemIntoView, slottedWords, Typeahead, treeArrow, typesCharacter, whenHydrated, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  import { HelixHeadlessTreeItem } from '../item/tree-item.js';
@@ -45,6 +45,10 @@ let HelixHeadlessTree = class HelixHeadlessTree extends MixinCollection(MixinCon
45
45
  if (!current || !this.items.includes(current)) {
46
46
  return;
47
47
  }
48
+ if (this.multiple && this.handleMultiple(current, event)) {
49
+ event.preventDefault();
50
+ return;
51
+ }
48
52
  if (this.handleTyping(current, event)) {
49
53
  event.preventDefault();
50
54
  return;
@@ -71,21 +75,16 @@ let HelixHeadlessTree = class HelixHeadlessTree extends MixinCollection(MixinCon
71
75
  else if (event.key === 'End') {
72
76
  this.focusOn(this.visibleItems.at(-1));
73
77
  }
74
- else if (along === 1) {
75
- if (current.isBranch && !current.open && !current.disabled) {
76
- current.requestOpen(true);
78
+ else if (along !== 0) {
79
+ const arrow = treeArrow({ branch: current.isBranch, disabled: current.disabled, open: current.open }, along);
80
+ if (arrow === 'open' || arrow === 'close') {
81
+ current.requestOpen(arrow === 'open');
77
82
  this.syncItems();
78
83
  }
79
- else if (current.isBranch) {
84
+ else if (arrow === 'child') {
80
85
  this.move(current, 1);
81
86
  }
82
- }
83
- else if (along === -1) {
84
- if (current.isBranch && current.open && !current.disabled) {
85
- current.requestOpen(false);
86
- this.syncItems();
87
- }
88
- else {
87
+ else if (arrow === 'parent') {
89
88
  const parent = closestInstance(current.parentElement, HelixHeadlessTreeItem);
90
89
  this.focusOn(parent instanceof HelixHeadlessTreeItem ? parent : undefined);
91
90
  }
@@ -157,7 +156,7 @@ let HelixHeadlessTree = class HelixHeadlessTree extends MixinCollection(MixinCon
157
156
  const held = visible.find((item) => item.focusable) ??
158
157
  this.visibleAncestor(this.items.find((item) => item.focusable));
159
158
  const stop = held ?? chosen;
160
- const heads = this.nesting === 'heading';
159
+ const heads = this.nesting === 'heading' || this.selectionMode === 'leaf';
161
160
  const multiple = this.multiple;
162
161
  const checked = new Set(this.values);
163
162
  for (const item of this.items) {
@@ -217,6 +216,26 @@ let HelixHeadlessTree = class HelixHeadlessTree extends MixinCollection(MixinCon
217
216
  }
218
217
  return true;
219
218
  }
219
+ handleMultiple(current, event) {
220
+ if ((event.ctrlKey || event.metaKey) && !event.altKey && event.key.toLowerCase() === 'a') {
221
+ const leaves = this.items
222
+ .filter((item) => !this.holds(item) && !item.disabled && item.value !== '')
223
+ .map((item) => item.value);
224
+ this.requestActive(pressRow({ branch: true, disabled: false, leaves, value: '' }, this.values, this.order).join(','));
225
+ this.syncItems();
226
+ return true;
227
+ }
228
+ if (event.shiftKey && (event.key === 'ArrowDown' || event.key === 'ArrowUp')) {
229
+ const visible = this.visibleItems;
230
+ const next = visible[visible.indexOf(current) + (event.key === 'ArrowDown' ? 1 : -1)];
231
+ if (next) {
232
+ this.focusOn(next);
233
+ next.select();
234
+ }
235
+ return true;
236
+ }
237
+ return false;
238
+ }
220
239
  move(from, delta) {
221
240
  const visible = this.visibleItems;
222
241
  const index = visible.indexOf(from);
@@ -1,3 +1,4 @@
1
+ import { toggleEvery } from '@fusion.dev/helix-core';
1
2
  const valuesOf = (row) => row.branch && row.leaves.length > 0 ? [...row.leaves] : row.value === '' ? [] : [row.value];
2
3
  export const checkedState = (row, chosen) => {
3
4
  const values = valuesOf(row);
@@ -8,19 +9,5 @@ export const checkedState = (row, chosen) => {
8
9
  return count === values.length ? true : 'mixed';
9
10
  };
10
11
  export const pressRow = (row, chosen, order) => {
11
- if (row.disabled) {
12
- return [...chosen];
13
- }
14
- const values = valuesOf(row);
15
- const all = values.every((value) => chosen.includes(value));
16
- const next = new Set(chosen);
17
- for (const value of values) {
18
- if (all) {
19
- next.delete(value);
20
- }
21
- else {
22
- next.add(value);
23
- }
24
- }
25
- return order.filter((value) => next.has(value));
12
+ return row.disabled ? [...chosen] : toggleEvery(chosen, valuesOf(row), order);
26
13
  };