@fusion.dev/helix-headless 0.143.0 → 0.145.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 (314) hide show
  1. package/custom-elements.json +16990 -1383
  2. package/dist/accordion/item/accordion-item.d.ts +48 -12
  3. package/dist/accordion/item/accordion-item.js +26 -5
  4. package/dist/accordion/root/accordion-root.d.ts +6 -0
  5. package/dist/accordion/root/accordion-root.js +7 -1
  6. package/dist/announcement/root/announcement-root.d.ts +21 -1
  7. package/dist/announcement/root/announcement-root.js +36 -1
  8. package/dist/app-header/root/app-header-root.d.ts +7 -0
  9. package/dist/app-header/root/app-header-root.js +5 -1
  10. package/dist/app-header/sectioning.d.ts +7 -0
  11. package/dist/app-header/sectioning.js +26 -0
  12. package/dist/area-chart/root/area-chart-root.d.ts +12 -4
  13. package/dist/area-chart/root/area-chart-root.js +24 -7
  14. package/dist/avatar/root/avatar-root.d.ts +14 -0
  15. package/dist/avatar/root/avatar-root.js +23 -9
  16. package/dist/badge/root/badge-root.d.ts +11 -7
  17. package/dist/badge/root/badge-root.js +1 -1
  18. package/dist/bar-chart/root/bar-chart-root.d.ts +12 -3
  19. package/dist/bar-chart/root/bar-chart-root.js +17 -10
  20. package/dist/breadcrumbs/root/breadcrumbs-root.d.ts +32 -1
  21. package/dist/breadcrumbs/root/breadcrumbs-root.js +25 -2
  22. package/dist/button/root/button-root.d.ts +47 -1
  23. package/dist/button/root/button-root.js +38 -4
  24. package/dist/callout/root/callout-root.d.ts +18 -1
  25. package/dist/callout/root/callout-root.js +15 -4
  26. package/dist/canvas/root/canvas-root.d.ts +38 -3
  27. package/dist/canvas/root/canvas-root.js +121 -40
  28. package/dist/card/root/card-root.d.ts +30 -1
  29. package/dist/card/root/card-root.js +38 -5
  30. package/dist/carousel/root/carousel-root.d.ts +90 -0
  31. package/dist/carousel/root/carousel-root.js +134 -12
  32. package/dist/chat/composer/chat-composer.d.ts +38 -6
  33. package/dist/chat/composer/chat-composer.js +42 -9
  34. package/dist/chat/message/chat-message.d.ts +32 -1
  35. package/dist/chat/message/chat-message.js +25 -9
  36. package/dist/chat/naming.d.ts +22 -0
  37. package/dist/chat/naming.js +12 -0
  38. package/dist/chat/relative-clock.d.ts +39 -0
  39. package/dist/chat/relative-clock.js +31 -0
  40. package/dist/chat/root/chat-root.d.ts +36 -0
  41. package/dist/chat/root/chat-root.js +24 -1
  42. package/dist/checkbox/root/checkbox-root.js +2 -0
  43. package/dist/checkbox-group/root/checkbox-group-root.d.ts +53 -3
  44. package/dist/checkbox-group/root/checkbox-group-root.js +71 -22
  45. package/dist/code-block/lines.d.ts +13 -0
  46. package/dist/code-block/lines.js +2 -0
  47. package/dist/code-block/root/code-block-root.d.ts +38 -1
  48. package/dist/code-block/root/code-block-root.js +28 -7
  49. package/dist/color-input/root/color-input-root.d.ts +34 -1
  50. package/dist/color-input/root/color-input-root.js +23 -4
  51. package/dist/color-picker/channels.d.ts +26 -0
  52. package/dist/color-picker/channels.js +84 -0
  53. package/dist/color-picker/root/color-picker-root.d.ts +81 -5
  54. package/dist/color-picker/root/color-picker-root.js +103 -10
  55. package/dist/combobox/root/combobox-root.d.ts +38 -3
  56. package/dist/combobox/root/combobox-root.js +64 -2
  57. package/dist/command/root/command-root.d.ts +30 -4
  58. package/dist/command/root/command-root.js +33 -3
  59. package/dist/copy-button/from.d.ts +22 -0
  60. package/dist/copy-button/from.js +28 -0
  61. package/dist/copy-button/root/copy-button-root.d.ts +47 -1
  62. package/dist/copy-button/root/copy-button-root.js +44 -8
  63. package/dist/data-table/root/data-table-root.d.ts +150 -2
  64. package/dist/data-table/root/data-table-root.js +303 -29
  65. package/dist/data-table/tree.d.ts +40 -0
  66. package/dist/data-table/tree.js +55 -0
  67. package/dist/date-input/root/date-input-root.d.ts +73 -4
  68. package/dist/date-input/root/date-input-root.js +93 -43
  69. package/dist/date-picker/root/date-picker-root.d.ts +145 -1
  70. package/dist/date-picker/root/date-picker-root.js +239 -48
  71. package/dist/description-list/item/description-list-item.d.ts +17 -2
  72. package/dist/description-list/item/description-list-item.js +15 -2
  73. package/dist/description-list/root/description-list-root.d.ts +27 -1
  74. package/dist/description-list/root/description-list-root.js +26 -1
  75. package/dist/dialog/description.d.ts +9 -0
  76. package/dist/dialog/description.js +14 -0
  77. package/dist/dialog/root/dialog-root.d.ts +64 -1
  78. package/dist/dialog/root/dialog-root.js +49 -23
  79. package/dist/donut-chart/root/donut-chart-root.d.ts +25 -4
  80. package/dist/donut-chart/root/donut-chart-root.js +21 -6
  81. package/dist/drawer/root/drawer-root.d.ts +63 -2
  82. package/dist/drawer/root/drawer-root.js +95 -19
  83. package/dist/drawer/swipe-start.d.ts +9 -0
  84. package/dist/drawer/swipe-start.js +12 -0
  85. package/dist/drilldown/item/drilldown-item.d.ts +11 -6
  86. package/dist/drilldown/item/drilldown-item.js +8 -5
  87. package/dist/drilldown/root/drilldown-root.d.ts +11 -0
  88. package/dist/drilldown/root/drilldown-root.js +8 -3
  89. package/dist/element-events.generated.js +61 -13
  90. package/dist/empty/root/empty-root.d.ts +1 -1
  91. package/dist/file-input/root/file-input-root.d.ts +47 -0
  92. package/dist/file-input/root/file-input-root.js +60 -22
  93. package/dist/file-input/screen.d.ts +35 -0
  94. package/dist/file-input/screen.js +49 -0
  95. package/dist/filter-input/menu/filter-menu.d.ts +3 -0
  96. package/dist/filter-input/menu/filter-menu.js +8 -4
  97. package/dist/filter-input/root/filter-input-root.d.ts +38 -4
  98. package/dist/filter-input/root/filter-input-root.js +67 -3
  99. package/dist/filter-input/suggest.d.ts +17 -0
  100. package/dist/filter-input/suggest.js +23 -0
  101. package/dist/format-date/options.d.ts +33 -0
  102. package/dist/format-date/options.js +42 -0
  103. package/dist/format-date/root/format-date-root.d.ts +51 -0
  104. package/dist/format-date/root/format-date-root.js +58 -38
  105. package/dist/heatmap/root/heatmap-root.d.ts +23 -6
  106. package/dist/heatmap/root/heatmap-root.js +24 -13
  107. package/dist/icon/index.d.ts +2 -0
  108. package/dist/icon/index.js +2 -0
  109. package/dist/icon/registry.d.ts +37 -0
  110. package/dist/icon/registry.js +83 -0
  111. package/dist/icon/root/icon-root.d.ts +30 -4
  112. package/dist/icon/root/icon-root.js +41 -6
  113. package/dist/icon/root/index.d.ts +2 -0
  114. package/dist/icon/root/index.js +2 -0
  115. package/dist/icon/set.d.ts +1 -0
  116. package/dist/icon/set.js +3 -0
  117. package/dist/image/root/image-root.d.ts +14 -0
  118. package/dist/image/root/image-root.js +23 -2
  119. package/dist/input/root/input-root.d.ts +11 -0
  120. package/dist/input/root/input-root.js +16 -1
  121. package/dist/kbd/root/kbd-root.d.ts +20 -13
  122. package/dist/kbd/root/kbd-root.js +35 -37
  123. package/dist/kbd/shortcut.d.ts +18 -0
  124. package/dist/kbd/shortcut.js +35 -0
  125. package/dist/legend/hidden-series.d.ts +8 -0
  126. package/dist/legend/hidden-series.js +13 -0
  127. package/dist/legend/root/legend-root.d.ts +56 -0
  128. package/dist/legend/root/legend-root.js +54 -5
  129. package/dist/lightbox/item/lightbox-item.d.ts +15 -0
  130. package/dist/lightbox/item/lightbox-item.js +10 -1
  131. package/dist/lightbox/root/lightbox-root.d.ts +61 -1
  132. package/dist/lightbox/root/lightbox-root.js +94 -4
  133. package/dist/line-chart/root/line-chart-root.d.ts +11 -3
  134. package/dist/line-chart/root/line-chart-root.js +19 -5
  135. package/dist/list/item/list-item.d.ts +68 -1
  136. package/dist/list/item/list-item.js +66 -4
  137. package/dist/list/numbering.d.ts +14 -0
  138. package/dist/list/numbering.js +8 -0
  139. package/dist/list/root/list-root.d.ts +106 -9
  140. package/dist/list/root/list-root.js +189 -4
  141. package/dist/list/selection.d.ts +19 -0
  142. package/dist/list/selection.js +21 -0
  143. package/dist/listbox/root/listbox-root.d.ts +74 -3
  144. package/dist/listbox/root/listbox-root.js +86 -9
  145. package/dist/loader/busy.d.ts +17 -0
  146. package/dist/loader/busy.js +24 -0
  147. package/dist/loader/root/loader-root.d.ts +37 -0
  148. package/dist/loader/root/loader-root.js +52 -3
  149. package/dist/map/root/map-root.d.ts +6 -2
  150. package/dist/map/root/map-root.js +5 -2
  151. package/dist/megamenu/root/megamenu-root.d.ts +21 -0
  152. package/dist/megamenu/root/megamenu-root.js +16 -2
  153. package/dist/menu/index.d.ts +1 -0
  154. package/dist/menu/index.js +1 -0
  155. package/dist/menu/item/menu-item.d.ts +38 -11
  156. package/dist/menu/item/menu-item.js +33 -1
  157. package/dist/menu/root/menu-root.d.ts +25 -1
  158. package/dist/menu/root/menu-root.js +63 -6
  159. package/dist/menu/section/menu-section.d.ts +55 -0
  160. package/dist/menu/section/menu-section.js +60 -0
  161. package/dist/meter/root/meter-root.d.ts +7 -0
  162. package/dist/meter/root/meter-root.js +6 -3
  163. package/dist/navigation/root/navigation-root.d.ts +16 -1
  164. package/dist/navigation/root/navigation-root.js +27 -1
  165. package/dist/notifications/feed-keys.d.ts +10 -0
  166. package/dist/notifications/feed-keys.js +14 -0
  167. package/dist/notifications/notification/notification.d.ts +17 -0
  168. package/dist/notifications/notification/notification.js +21 -2
  169. package/dist/notifications/root/notifications-root.d.ts +60 -0
  170. package/dist/notifications/root/notifications-root.js +76 -1
  171. package/dist/number-input/root/number-input-root.d.ts +47 -0
  172. package/dist/number-input/root/number-input-root.js +58 -22
  173. package/dist/object-inspector/root/object-inspector-root.d.ts +98 -16
  174. package/dist/object-inspector/root/object-inspector-root.js +180 -80
  175. package/dist/object-inspector/rows.d.ts +57 -0
  176. package/dist/object-inspector/rows.js +86 -0
  177. package/dist/option/root/option-root.d.ts +54 -5
  178. package/dist/option/root/option-root.js +34 -5
  179. package/dist/outline/levels.d.ts +31 -0
  180. package/dist/outline/levels.js +23 -0
  181. package/dist/outline/root/outline-root.d.ts +68 -3
  182. package/dist/outline/root/outline-root.js +73 -8
  183. package/dist/pagination/size/pagination-size.d.ts +11 -2
  184. package/dist/pagination/size/pagination-size.js +19 -4
  185. package/dist/pin-input/format.d.ts +7 -0
  186. package/dist/pin-input/format.js +16 -0
  187. package/dist/pin-input/root/pin-input-root.d.ts +21 -0
  188. package/dist/pin-input/root/pin-input-root.js +29 -2
  189. package/dist/popover/root/popover-root.d.ts +62 -4
  190. package/dist/popover/root/popover-root.js +53 -5
  191. package/dist/progress/root/progress-root.d.ts +7 -0
  192. package/dist/progress/root/progress-root.js +9 -3
  193. package/dist/qr-code/export.d.ts +31 -0
  194. package/dist/qr-code/export.js +10 -0
  195. package/dist/qr-code/root/qr-code-root.d.ts +26 -0
  196. package/dist/qr-code/root/qr-code-root.js +49 -2
  197. package/dist/radio/group/radio-group.d.ts +17 -0
  198. package/dist/radio/group/radio-group.js +14 -1
  199. package/dist/rating/root/rating-root.d.ts +25 -0
  200. package/dist/rating/root/rating-root.js +46 -4
  201. package/dist/scatter-chart/root/scatter-chart-root.d.ts +17 -3
  202. package/dist/scatter-chart/root/scatter-chart-root.js +25 -7
  203. package/dist/scroll-area/root/scroll-area-root.d.ts +113 -0
  204. package/dist/scroll-area/root/scroll-area-root.js +218 -3
  205. package/dist/scroll-area/scrollbar.d.ts +65 -0
  206. package/dist/scroll-area/scrollbar.js +24 -0
  207. package/dist/select/root/select-root.d.ts +24 -0
  208. package/dist/select/root/select-root.js +21 -7
  209. package/dist/skeleton/root/skeleton-root.d.ts +25 -0
  210. package/dist/skeleton/root/skeleton-root.js +17 -2
  211. package/dist/slider/root/slider-root.d.ts +39 -0
  212. package/dist/slider/root/slider-root.js +45 -9
  213. package/dist/splitter/root/splitter-root.d.ts +13 -0
  214. package/dist/splitter/root/splitter-root.js +20 -2
  215. package/dist/splitter/snap.d.ts +9 -0
  216. package/dist/splitter/snap.js +12 -0
  217. package/dist/stat/root/stat-root.d.ts +2 -0
  218. package/dist/stat/root/stat-root.js +1 -1
  219. package/dist/steps/reach.d.ts +15 -0
  220. package/dist/steps/reach.js +12 -0
  221. package/dist/steps/root/steps-root.d.ts +30 -0
  222. package/dist/steps/root/steps-root.js +53 -0
  223. package/dist/steps/step/step.d.ts +39 -2
  224. package/dist/steps/step/step.js +29 -7
  225. package/dist/switch/root/switch-root.d.ts +12 -2
  226. package/dist/switch/root/switch-root.js +6 -1
  227. package/dist/table/column/table-column.d.ts +11 -0
  228. package/dist/table/column/table-column.js +6 -1
  229. package/dist/table/root/table-root.d.ts +101 -0
  230. package/dist/table/root/table-root.js +166 -5
  231. package/dist/table/row/table-row.d.ts +47 -1
  232. package/dist/table/row/table-row.js +39 -2
  233. package/dist/tabs/panel/tab-panel.d.ts +9 -5
  234. package/dist/tabs/panel/tab-panel.js +3 -4
  235. package/dist/tabs/root/tabs-root.d.ts +25 -1
  236. package/dist/tabs/root/tabs-root.js +21 -3
  237. package/dist/tabs/tab/tab.d.ts +36 -4
  238. package/dist/tabs/tab/tab.js +46 -3
  239. package/dist/tag/group/tag-group.d.ts +107 -0
  240. package/dist/tag/group/tag-group.js +157 -0
  241. package/dist/tag/index.d.ts +1 -0
  242. package/dist/tag/index.js +1 -0
  243. package/dist/tag/root/tag-root.d.ts +60 -0
  244. package/dist/tag/root/tag-root.js +74 -18
  245. package/dist/tag/selection.d.ts +10 -0
  246. package/dist/tag/selection.js +12 -0
  247. package/dist/textarea/grow.d.ts +22 -0
  248. package/dist/textarea/grow.js +20 -0
  249. package/dist/textarea/root/textarea-root.d.ts +9 -0
  250. package/dist/textarea/root/textarea-root.js +9 -1
  251. package/dist/time-input/root/time-input-root.d.ts +44 -5
  252. package/dist/time-input/root/time-input-root.js +69 -39
  253. package/dist/time-picker/root/time-picker-root.d.ts +54 -8
  254. package/dist/time-picker/root/time-picker-root.js +121 -91
  255. package/dist/timeline/group/timeline-group.d.ts +46 -0
  256. package/dist/timeline/group/timeline-group.js +37 -0
  257. package/dist/timeline/index.d.ts +1 -0
  258. package/dist/timeline/index.js +1 -0
  259. package/dist/timeline/root/timeline-root.d.ts +10 -3
  260. package/dist/toast/hotkey.d.ts +22 -0
  261. package/dist/toast/hotkey.js +23 -0
  262. package/dist/toast/item/toast-item.d.ts +26 -18
  263. package/dist/toast/item/toast-item.js +55 -48
  264. package/dist/toast/root/toast-root.d.ts +42 -8
  265. package/dist/toast/root/toast-root.js +64 -4
  266. package/dist/toggle/group/toggle-group.d.ts +57 -15
  267. package/dist/toggle/group/toggle-group.js +69 -9
  268. package/dist/toggle/root/toggle-root.d.ts +12 -1
  269. package/dist/toggle/root/toggle-root.js +11 -3
  270. package/dist/toolbar/root/toolbar-root.d.ts +13 -0
  271. package/dist/toolbar/root/toolbar-root.js +15 -5
  272. package/dist/tooltip/focus-target.d.ts +6 -0
  273. package/dist/tooltip/focus-target.js +9 -0
  274. package/dist/tooltip/root/tooltip-root.d.ts +42 -17
  275. package/dist/tooltip/root/tooltip-root.js +50 -9
  276. package/dist/tour/naming.d.ts +30 -0
  277. package/dist/tour/naming.js +10 -0
  278. package/dist/tour/root/tour-root.d.ts +35 -0
  279. package/dist/tour/root/tour-root.js +56 -5
  280. package/dist/tour/stop/tour-stop.d.ts +53 -8
  281. package/dist/tour/stop/tour-stop.js +45 -15
  282. package/dist/tour/wait.d.ts +11 -0
  283. package/dist/tour/wait.js +19 -0
  284. package/dist/tree/item/tree-item.d.ts +54 -1
  285. package/dist/tree/item/tree-item.js +89 -20
  286. package/dist/tree/root/tree-root.d.ts +37 -0
  287. package/dist/tree/root/tree-root.js +51 -6
  288. package/dist/tree/selection.d.ts +24 -0
  289. package/dist/tree/selection.js +26 -0
  290. package/dist/trend/root/trend-root.d.ts +13 -0
  291. package/dist/trend/root/trend-root.js +14 -9
  292. package/dist/video/media-events.d.ts +49 -0
  293. package/dist/video/media-events.js +39 -0
  294. package/dist/video/root/video-root.d.ts +99 -13
  295. package/dist/video/root/video-root.js +84 -47
  296. package/dist/video-playlist/menu/video-playlist-menu.d.ts +3 -0
  297. package/dist/video-playlist/menu/video-playlist-menu.js +2 -1
  298. package/dist/video-playlist/order.d.ts +41 -0
  299. package/dist/video-playlist/order.js +34 -0
  300. package/dist/video-playlist/root/video-playlist-root.d.ts +70 -2
  301. package/dist/video-playlist/root/video-playlist-root.js +60 -16
  302. package/dist/virtual-list/controls.d.ts +4 -0
  303. package/dist/virtual-list/controls.js +30 -0
  304. package/dist/virtual-list/fill.d.ts +30 -0
  305. package/dist/virtual-list/fill.js +39 -0
  306. package/dist/virtual-list/keys.d.ts +15 -0
  307. package/dist/virtual-list/keys.js +24 -0
  308. package/dist/virtual-list/root/virtual-list-root.d.ts +155 -23
  309. package/dist/virtual-list/root/virtual-list-root.js +175 -34
  310. package/lint.json +1 -1
  311. package/package.json +5 -5
  312. package/types/jsx.d.ts +8871 -1286
  313. package/types/svelte.d.ts +3 -0
  314. package/types/vue.d.ts +1918 -269
@@ -4,21 +4,32 @@ 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, DisabledCascadeController, HelixFormElement, hostAria, IndicatorController, KeyboardNavigationController, MixinControllerValue, MixinField, } from '@fusion.dev/helix-core';
7
+ import { commaList, customElement, DisabledCascadeController, HelixFormElement, hostAria, IndicatorController, KeyboardNavigationController, MixinControllerValue, MixinField, rovingStop, toggleValue, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  import { HelixHeadlessToggle } from '../root/toggle-root.js';
11
11
  let HelixHeadlessToggleGroup = class HelixHeadlessToggleGroup extends MixinControllerValue(MixinField(HelixFormElement)) {
12
12
  constructor() {
13
13
  super(...arguments);
14
+ this.lastFocused = undefined;
15
+ this.noteFocus = (event) => {
16
+ const item = this.items.find((one) => one === event.target);
17
+ if (this.multiple && item && item !== this.lastFocused) {
18
+ this.lastFocused = item;
19
+ this.sync();
20
+ }
21
+ };
14
22
  this.focusable = true;
15
23
  this.indicator = new IndicatorController(this, {
16
- active: () => this.items.find((item) => item.value === this.value),
24
+ active: () => this.multiple ? undefined : this.items.find((item) => item.value === this.value),
17
25
  indicator: () => this.partNamed('indicator'),
18
26
  observe: () => this.items,
19
27
  surface: () => this.partNamed('track'),
20
28
  });
21
- this.keys = new KeyboardNavigationController(this, { items: () => this.items });
29
+ this.keys = new KeyboardNavigationController(this, {
30
+ items: () => this.items,
31
+ selects: () => !this.multiple,
32
+ });
22
33
  this.cascade = new DisabledCascadeController(this, {
23
34
  items: () => this.items,
24
35
  });
@@ -27,7 +38,10 @@ let HelixHeadlessToggleGroup = class HelixHeadlessToggleGroup extends MixinContr
27
38
  if (event.defaultPrevented) {
28
39
  return;
29
40
  }
30
- this.requestValue(event.detail.value);
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);
31
45
  });
32
46
  };
33
47
  }
@@ -41,16 +55,28 @@ let HelixHeadlessToggleGroup = class HelixHeadlessToggleGroup extends MixinContr
41
55
  }
42
56
  }
43
57
  sync() {
44
- const active = this.items.find((item) => item.value === this.value);
58
+ const active = this.multiple ? undefined : this.items.find((item) => item.value === this.value);
59
+ const pressed = new Set(this.values);
45
60
  const entry = this.focusable ? this.entry() : undefined;
46
61
  for (const item of this.items) {
47
62
  item.selected = item === active;
63
+ item.pressed = this.multiple ? pressed.has(item.value) : undefined;
48
64
  item.focusable = item === entry;
49
65
  }
50
66
  }
51
67
  entry() {
52
- const active = this.items.find((item) => item.value === this.value);
53
- return active && !active.disabled ? active : this.items.find((item) => !item.disabled);
68
+ const items = this.items;
69
+ if (this.multiple) {
70
+ return rovingStop(items, this.lastFocused, {
71
+ disabled: (item) => item.disabled,
72
+ selected: (item) => item.pressed === true,
73
+ });
74
+ }
75
+ const active = items.find((item) => item.value === this.value);
76
+ return active && !active.disabled ? active : items.find((item) => !item.disabled);
77
+ }
78
+ get multiple() {
79
+ return this.selectionMode === 'multiple';
54
80
  }
55
81
  get focusTarget() {
56
82
  return (super.focusTarget ??
@@ -64,6 +90,9 @@ let HelixHeadlessToggleGroup = class HelixHeadlessToggleGroup extends MixinContr
64
90
  if (this.value === '') {
65
91
  return false;
66
92
  }
93
+ if (this.multiple) {
94
+ return this.children === undefined || this.values.length > 0;
95
+ }
67
96
  if (this.children === undefined) {
68
97
  return true;
69
98
  }
@@ -73,10 +102,12 @@ let HelixHeadlessToggleGroup = class HelixHeadlessToggleGroup extends MixinContr
73
102
  connectedCallback() {
74
103
  super.connectedCallback();
75
104
  this.addEventListener('helix-select', this.handleSelection);
105
+ this.addEventListener('focusin', this.noteFocus);
76
106
  }
77
107
  disconnectedCallback() {
78
108
  super.disconnectedCallback();
79
109
  this.removeEventListener('helix-select', this.handleSelection);
110
+ this.removeEventListener('focusin', this.noteFocus);
80
111
  }
81
112
  firstUpdated(changed) {
82
113
  super.firstUpdated(changed);
@@ -92,12 +123,32 @@ let HelixHeadlessToggleGroup = class HelixHeadlessToggleGroup extends MixinContr
92
123
  willUpdate(changed) {
93
124
  super.willUpdate(changed);
94
125
  this.adopt();
95
- hostAria(this.internals, { label: this.label, role: 'radiogroup' });
96
- this.internals.setFormValue(this.answered ? this.value : null);
126
+ hostAria(this.internals, { label: this.label, role: this.multiple ? 'toolbar' : 'radiogroup' });
127
+ this.internals.ariaOrientation = this.orientation ?? null;
128
+ this.internals.setFormValue(this.formValue());
129
+ }
130
+ formValue() {
131
+ if (!this.answered) {
132
+ return null;
133
+ }
134
+ if (!this.multiple || this.name === '') {
135
+ return this.value;
136
+ }
137
+ const data = new FormData();
138
+ for (const value of this.values) {
139
+ data.append(this.name, value);
140
+ }
141
+ return data;
97
142
  }
98
143
  validate() {
99
144
  this.reportRequired(!this.answered, 'Choose one to continue.', 'track');
100
145
  }
146
+ get values() {
147
+ return this.multiple ? commaList(this.value) : this.value === '' ? [] : [this.value];
148
+ }
149
+ set values(next) {
150
+ this.value = next.join(',');
151
+ }
101
152
  render() {
102
153
  const inside = this.labelPlacement === 'start';
103
154
  const label = this.renderGroupLabel();
@@ -127,6 +178,15 @@ __decorate([
127
178
  __decorate([
128
179
  property({ attribute: 'label-placement', reflect: true })
129
180
  ], HelixHeadlessToggleGroup.prototype, "labelPlacement", void 0);
181
+ __decorate([
182
+ property({ reflect: true })
183
+ ], HelixHeadlessToggleGroup.prototype, "orientation", void 0);
184
+ __decorate([
185
+ property({ attribute: 'selection-mode', reflect: true })
186
+ ], HelixHeadlessToggleGroup.prototype, "selectionMode", void 0);
187
+ __decorate([
188
+ property({ attribute: false })
189
+ ], HelixHeadlessToggleGroup.prototype, "values", null);
130
190
  HelixHeadlessToggleGroup = __decorate([
131
191
  customElement('helix-headless-toggle-group')
132
192
  ], HelixHeadlessToggleGroup);
@@ -5,7 +5,9 @@ declare const HelixHeadlessToggle_base: (new (...args: any[]) => import("@fusion
5
5
  * @summary One option in a toggle group.
6
6
  *
7
7
  * @description
8
- * Renders a `<button role="radio">` for use as a form control.
8
+ * Renders a `<button role="radio">` for use as a form control. In a group with
9
+ * `selection-mode="multiple"` it is a `<button>` that says whether it is
10
+ * pressed with `aria-pressed`.
9
11
  *
10
12
  * Do not use it for navigation. For navigation, use links marked with
11
13
  * `aria-current` and styled by the `data-navigation-item` recipe in the base
@@ -23,6 +25,7 @@ declare const HelixHeadlessToggle_base: (new (...args: any[]) => import("@fusion
23
25
  *
24
26
  * @csspart item - The radio button.
25
27
  *
28
+ * @cssstate pressed - The item is pressed, in a group where several can be.
26
29
  * @cssstate selected - The item is chosen.
27
30
  */
28
31
  export declare class HelixHeadlessToggle extends HelixHeadlessToggle_base {
@@ -69,6 +72,14 @@ export declare class HelixHeadlessToggle extends HelixHeadlessToggle_base {
69
72
  * React consumer's hydration reports and does not patch up.
70
73
  */
71
74
  focusable?: boolean;
75
+ /**
76
+ * Whether the item is pressed, in a group where several can be, and
77
+ * `undefined` in a group where one is chosen.
78
+ *
79
+ * Written by the group, like `selected`, so no attribute.
80
+ */
81
+ pressed?: boolean | undefined;
82
+ protected willUpdate(changed: PropertyValues): void;
72
83
  /**
73
84
  * Asks for the render that follows a hydration, so the stop stops being every
74
85
  * choice's.
@@ -5,7 +5,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
7
  import { customElement, HelixElement, HostClickController, MixinSelectableItem, } from '@fusion.dev/helix-core';
8
- import { html } from 'lit';
8
+ import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  let HelixHeadlessToggle = class HelixHeadlessToggle extends MixinSelectableItem(HelixElement) {
11
11
  constructor() {
@@ -18,6 +18,10 @@ let HelixHeadlessToggle = class HelixHeadlessToggle extends MixinSelectableItem(
18
18
  set defaultSelected(next) {
19
19
  this.toggleAttribute('selected', next);
20
20
  }
21
+ willUpdate(changed) {
22
+ super.willUpdate(changed);
23
+ this.toggleState('pressed', this.pressed === true);
24
+ }
21
25
  firstUpdated(changed) {
22
26
  super.firstUpdated(changed);
23
27
  this.finishHydration();
@@ -26,8 +30,9 @@ let HelixHeadlessToggle = class HelixHeadlessToggle extends MixinSelectableItem(
26
30
  return html `<button
27
31
  part="item"
28
32
  type="button"
29
- role="radio"
30
- aria-checked=${this.selected ? 'true' : 'false'}
33
+ role=${this.pressed === undefined ? 'radio' : nothing}
34
+ aria-checked=${this.pressed === undefined ? (this.selected ? 'true' : 'false') : nothing}
35
+ aria-pressed=${this.pressed === undefined ? nothing : String(this.pressed)}
31
36
  ?disabled=${this.disabled}
32
37
  tabindex=${(this.focusable ?? this.selected) || !this.childrenKnown ? '0' : '-1'}
33
38
  @click=${() => this.select()}
@@ -39,6 +44,9 @@ let HelixHeadlessToggle = class HelixHeadlessToggle extends MixinSelectableItem(
39
44
  __decorate([
40
45
  property({ attribute: false })
41
46
  ], HelixHeadlessToggle.prototype, "focusable", void 0);
47
+ __decorate([
48
+ property({ attribute: false })
49
+ ], HelixHeadlessToggle.prototype, "pressed", void 0);
42
50
  HelixHeadlessToggle = __decorate([
43
51
  customElement('helix-headless-toggle')
44
52
  ], HelixHeadlessToggle);
@@ -12,6 +12,14 @@ import { type PropertyValues, type TemplateResult } from 'lit';
12
12
  * focus counts as a control, including your own elements. A child that takes no
13
13
  * focus, such as a divider, is skipped. A menu is reached at its trigger.
14
14
  *
15
+ * Controls inside a plain wrapper, such as a `Button Group`, are each a stop
16
+ * of the toolbar's own, as React Aria's toolbar flattens a group: the arrows
17
+ * walk through the group's buttons and on past it, and the group takes no tab
18
+ * stop of its own.
19
+ *
20
+ * The arrows wrap from the last control to the first. Set `stop-at-ends` to
21
+ * keep them at the ends, as Radix's `loop` set to false does.
22
+ *
15
23
  * A nested composite, such as a toggle group, is one stop on the toolbar's
16
24
  * axis: the toolbar's arrows move past it, and the arrows across the toolbar
17
25
  * move inside it. A composite with a `focusable` property, as Toggle Group
@@ -49,6 +57,11 @@ export declare class HelixHeadlessToolbar extends HelixElement {
49
57
  * The direction the arrow keys move focus in.
50
58
  */
51
59
  orientation?: 'horizontal' | 'vertical';
60
+ /**
61
+ * Whether the arrow keys stop at the first and the last control. Unset, they
62
+ * wrap around from one end to the other.
63
+ */
64
+ stopAtEnds: boolean;
52
65
  private watcher;
53
66
  /**
54
67
  * The children that take part in the toolbar, in document order, each with
@@ -31,6 +31,7 @@ let HelixHeadlessToolbar = class HelixHeadlessToolbar extends HelixElement {
31
31
  target: () => this.partNamed('toolbar'),
32
32
  });
33
33
  this.wroteForeign = false;
34
+ this.stopAtEnds = false;
34
35
  this.handleFocusIn = (event) => {
35
36
  const control = this.origin(event);
36
37
  if (control) {
@@ -75,11 +76,14 @@ let HelixHeadlessToolbar = class HelixHeadlessToolbar extends HelixElement {
75
76
  return;
76
77
  }
77
78
  let next = current;
79
+ const last = controls.length - 1;
78
80
  if (forward) {
79
- next = (current + 1) % controls.length;
81
+ next = this.stopAtEnds ? Math.min(current + 1, last) : (current + 1) % controls.length;
80
82
  }
81
83
  else if (back) {
82
- next = (current - 1 + controls.length) % controls.length;
84
+ next = this.stopAtEnds
85
+ ? Math.max(current - 1, 0)
86
+ : (current - 1 + controls.length) % controls.length;
83
87
  }
84
88
  else if (event.key === 'Home') {
85
89
  next = 0;
@@ -110,9 +114,12 @@ let HelixHeadlessToolbar = class HelixHeadlessToolbar extends HelixElement {
110
114
  child.getAttribute('aria-hidden') === 'true') {
111
115
  continue;
112
116
  }
113
- const stops = takesFocus(child) ? this.inside(child) : this.within(child);
114
- if (stops.length > 0) {
115
- controls.push({ child, stops });
117
+ if (takesFocus(child)) {
118
+ controls.push({ child, stops: this.inside(child) });
119
+ continue;
120
+ }
121
+ for (const stop of this.within(child)) {
122
+ controls.push({ child: stop, stops: [stop] });
116
123
  }
117
124
  }
118
125
  return controls;
@@ -204,6 +211,9 @@ __decorate([
204
211
  __decorate([
205
212
  property({ reflect: true })
206
213
  ], HelixHeadlessToolbar.prototype, "orientation", void 0);
214
+ __decorate([
215
+ property({ attribute: 'stop-at-ends', type: Boolean })
216
+ ], HelixHeadlessToolbar.prototype, "stopAtEnds", void 0);
207
217
  HelixHeadlessToolbar = __decorate([
208
218
  customElement('helix-headless-toolbar')
209
219
  ], HelixHeadlessToolbar);
@@ -0,0 +1,6 @@
1
+ /**
2
+ * The element focus lands on when a trigger is focused: the trigger itself,
3
+ * or, for a component that delegates focus, the control inside its shadow root,
4
+ * followed down through any component nested there.
5
+ */
6
+ export declare const focusTarget: (trigger: Element) => Element;
@@ -0,0 +1,9 @@
1
+ const CONTROL = 'a[href], button, input, select, textarea, [contenteditable], [tabindex]:not([tabindex="-1"])';
2
+ export const focusTarget = (trigger) => {
3
+ const root = trigger.shadowRoot;
4
+ if (!root?.delegatesFocus) {
5
+ return trigger;
6
+ }
7
+ const inner = root.querySelector(CONTROL);
8
+ return inner ? focusTarget(inner) : trigger;
9
+ };
@@ -10,29 +10,32 @@ declare const HelixHeadlessTooltip_base: (new (...args: any[]) => import("@fusio
10
10
  * does not clip it and no `z-index` is needed. `FloatingController` positions
11
11
  * it and flips it to another side when the preferred side does not fit.
12
12
  *
13
- * Wrapping the trigger is the convenient form. Use it where the trigger already
14
- * has an accessible name, because the tooltip cannot describe a slotted
15
- * trigger: an IDREF cannot cross a shadow boundary.
16
- *
17
- * To add a description, place the tooltip beside the trigger and name the
18
- * trigger with `for`:
13
+ * **The tooltip describes its trigger itself, in both forms.** Wrap the
14
+ * trigger, or place the tooltip beside it and name the trigger with `for`:
19
15
  *
20
16
  * ```html
21
- * <helix-headless-button id="save" aria-describedby="save-tip">Save</helix-headless-button>
22
- * <helix-headless-tooltip id="save-tip" for="save" content="Save your work"></helix-headless-tooltip>
17
+ * <helix-headless-button id="save">Save</helix-headless-button>
18
+ * <helix-headless-tooltip for="save" content="Save your work"></helix-headless-tooltip>
23
19
  * ```
24
20
  *
25
- * Write the `aria-describedby` yourself, pointing at the tooltip host. Setting
26
- * it at runtime causes a hydration mismatch that React does not fix. With
27
- * `for`, the tooltip warns once on first open if the attribute is missing.
21
+ * The link is an element reference on the control that takes focus, and it is
22
+ * never written into your markup before a reader has acted. A trigger that is a
23
+ * Helix component is described at once, inside its own shadow root. A plain
24
+ * element in your markup is described when a reader first points at it or
25
+ * focuses it, which is after any hydration, and only where you have written no
26
+ * `aria-describedby` or `aria-labelledby` of your own. Write
27
+ * `aria-describedby` pointing at the tooltip yourself to have the link in the
28
+ * server's markup. Where the browser has no element references, write it
29
+ * yourself; the tooltip warns once on first open if nothing links the two.
28
30
  *
29
31
  * The tooltip shows on hover and on focus. It hides on pointer leave, on blur,
30
32
  * on Escape, and when the trigger is activated. The pointer can move from the
31
33
  * trigger onto the bubble and the bubble stays.
32
34
  *
33
35
  * Once one tooltip is showing, moving to another trigger shows its tooltip
34
- * with no `delay`, and the first one hides. The same holds for 300
35
- * milliseconds after a tooltip hides.
36
+ * with no `delay`, and the first one hides. The same holds for `skip-delay`
37
+ * milliseconds after a tooltip hides, 300 unless you set it. `show-on="focus"`
38
+ * shows the tooltip on keyboard focus only, for a hint a pointer does not need.
36
39
  *
37
40
  * @since 0.1.0
38
41
  * @status stable
@@ -40,9 +43,8 @@ declare const HelixHeadlessTooltip_base: (new (...args: any[]) => import("@fusio
40
43
  * @aliases Hint, Popup label
41
44
  * @documentation https://fusion.dev/helix/components/tooltip
42
45
  *
43
- * @slot - The trigger, in the wrapping form. A trigger here gets no
44
- * description, so use it only where the trigger's name says what the tooltip
45
- * says, and use `for` otherwise.
46
+ * @slot - The trigger, in the wrapping form. The tooltip describes it, as it
47
+ * describes the element `for` names.
46
48
  * @slot content - Rich content for the bubble. It replaces the `content`
47
49
  * property.
48
50
  *
@@ -149,6 +151,18 @@ export declare class HelixHeadlessTooltip extends HelixHeadlessTooltip_base {
149
151
  * It moves if that side does not fit.
150
152
  */
151
153
  placement: Placement;
154
+ /**
155
+ * What shows the bubble. Unset, it is a pointer resting on the trigger and
156
+ * keyboard focus. `focus` is keyboard focus alone, for a hint a pointer does
157
+ * not need, such as a shortcut a mouse reader has no use for.
158
+ */
159
+ showOn?: 'focus';
160
+ /**
161
+ * How long after a tooltip hides the next one still shows at once, with no
162
+ * `delay`, in milliseconds. The same skip delay Radix uses. Zero makes every
163
+ * tooltip wait.
164
+ */
165
+ skipDelay: number;
152
166
  /**
153
167
  * The id of a box to keep the bubble inside. Defaults to the viewport.
154
168
  *
@@ -260,7 +274,7 @@ export declare class HelixHeadlessTooltip extends HelixHeadlessTooltip_base {
260
274
  * Shows after the pointer has rested for `delay`, or at once while the
261
275
  * tooltips are warm.
262
276
  *
263
- * **Warm is another tooltip showing, or one that hid within `SKIP_DELAY`.**
277
+ * **Warm is another tooltip showing, or one that hid within `skipDelay`.**
264
278
  * A reader scanning a toolbar has already waited once, and waiting again on
265
279
  * every control turns the scan into a series of pauses.
266
280
  */
@@ -344,6 +358,17 @@ export declare class HelixHeadlessTooltip extends HelixHeadlessTooltip_base {
344
358
  * have upgraded, or existed, when this one connected.
345
359
  */
346
360
  private bind;
361
+ /** The control this tooltip has described, so the link can be taken back. */
362
+ private linked;
363
+ /**
364
+ * Describes the control that takes the trigger's focus by this tooltip.
365
+ *
366
+ * `light` says a reader has just acted, which is what allows a write onto a
367
+ * plain element in the consumer's markup. See `linkDescription`.
368
+ */
369
+ private link;
370
+ /** A reader arriving on the trigger, which is late enough to write the link. */
371
+ private readonly linkOnArrival;
347
372
  private unbind;
348
373
  /** Said once per element, however many times it opens. */
349
374
  private audited;
@@ -4,11 +4,11 @@ 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, customElement, elementById, HelixElement, hostAria, MixinControllerOpen, PopoverController, reportRuntime, } from '@fusion.dev/helix-core';
7
+ import { anchorElement, arrowReach, customElement, elementById, HelixElement, hostAria, linkDescription, MixinControllerOpen, 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
+ import { focusTarget } from '../focus-target.js';
10
11
  const GENERIC = new Set(['div', 'p', 'section', 'span']);
11
- const SKIP_DELAY = 300;
12
12
  const WARMTH = Symbol.for('fusion.helix.tooltip.warmth');
13
13
  const warmth = () => {
14
14
  const globals = globalThis;
@@ -26,22 +26,20 @@ let HelixHeadlessTooltip = class HelixHeadlessTooltip extends MixinControllerOpe
26
26
  this.offset = 8;
27
27
  this.persist = false;
28
28
  this.placement = 'top';
29
+ this.skipDelay = 300;
29
30
  this.within = '';
30
31
  this.holds = new Set();
31
32
  this.bubbleLayer = new PopoverController(this, {
32
33
  anchor: () => this.trigger,
33
34
  arrow: () => this.partNamed('arrow'),
34
35
  boundary: () => [this, this.named],
35
- offset: () => {
36
- const point = this.partNamed('arrow');
37
- return this.offset + (point?.offsetWidth ?? 0) / Math.SQRT2;
38
- },
36
+ offset: () => this.offset + arrowReach(this.partNamed('arrow')),
39
37
  onDismiss: () => {
40
38
  this.holds.clear();
41
39
  this.hide();
42
40
  },
43
41
  onPlace: (placement) => {
44
- const side = placement.split('-')[0];
42
+ const side = sideOf(placement);
45
43
  this.toggleState('placed-bottom', side === 'bottom');
46
44
  this.toggleState('placed-left', side === 'left');
47
45
  this.toggleState('placed-right', side === 'right');
@@ -56,11 +54,14 @@ let HelixHeadlessTooltip = class HelixHeadlessTooltip extends MixinControllerOpe
56
54
  this.requestUpdate();
57
55
  };
58
56
  this.showLater = () => {
57
+ if (this.showOn === 'focus') {
58
+ return;
59
+ }
59
60
  this.holds.add('pointer');
60
61
  clearTimeout(this.timer);
61
62
  const warm = warmth();
62
63
  const others = [...warm.showing].some((one) => one !== this);
63
- if (others || performance.now() - warm.hidAt < SKIP_DELAY) {
64
+ if (others || performance.now() - warm.hidAt < this.skipDelay) {
64
65
  this.show();
65
66
  return;
66
67
  }
@@ -102,6 +103,10 @@ let HelixHeadlessTooltip = class HelixHeadlessTooltip extends MixinControllerOpe
102
103
  this.holds.clear();
103
104
  this.hide();
104
105
  };
106
+ this.linkOnArrival = (event) => {
107
+ const first = event.composedPath()[0];
108
+ this.link(true, event.type === 'focusin' && first instanceof Element ? first : undefined);
109
+ };
105
110
  this.audited = false;
106
111
  }
107
112
  get trigger() {
@@ -179,14 +184,41 @@ let HelixHeadlessTooltip = class HelixHeadlessTooltip extends MixinControllerOpe
179
184
  return;
180
185
  }
181
186
  this.unbind();
187
+ target.addEventListener('pointerenter', this.linkOnArrival);
188
+ target.addEventListener('focusin', this.linkOnArrival);
182
189
  target.addEventListener('pointerenter', this.showLater);
183
190
  target.addEventListener('pointerleave', this.hideLater);
184
191
  target.addEventListener('focusin', this.showNow);
185
192
  target.addEventListener('focusout', this.releaseFocus);
186
193
  target.addEventListener('click', this.handleActivate);
187
194
  this.bound = target;
195
+ this.link(false);
196
+ const drawn = this.trigger.updateComplete;
197
+ void drawn?.then(() => {
198
+ if (this.bound === target) {
199
+ this.link(false);
200
+ }
201
+ });
202
+ }
203
+ link(light, target = focusTarget(this.trigger)) {
204
+ if (this.linked === target) {
205
+ return;
206
+ }
207
+ if (this.linked) {
208
+ unlinkDescription(this.linked, this);
209
+ this.linked = undefined;
210
+ }
211
+ if (linkDescription(target, this, light)) {
212
+ this.linked = target;
213
+ }
188
214
  }
189
215
  unbind() {
216
+ if (this.linked) {
217
+ unlinkDescription(this.linked, this);
218
+ this.linked = undefined;
219
+ }
220
+ this.bound?.removeEventListener('pointerenter', this.linkOnArrival);
221
+ this.bound?.removeEventListener('focusin', this.linkOnArrival);
190
222
  this.bound?.removeEventListener('pointerenter', this.showLater);
191
223
  this.bound?.removeEventListener('pointerleave', this.hideLater);
192
224
  this.bound?.removeEventListener('click', this.handleActivate);
@@ -199,6 +231,9 @@ let HelixHeadlessTooltip = class HelixHeadlessTooltip extends MixinControllerOpe
199
231
  return;
200
232
  }
201
233
  this.audited = true;
234
+ if (this.linked) {
235
+ return;
236
+ }
202
237
  if (this.for === '') {
203
238
  if (this.triggerSays(this.describedText)) {
204
239
  return;
@@ -293,7 +328,7 @@ let HelixHeadlessTooltip = class HelixHeadlessTooltip extends MixinControllerOpe
293
328
  }
294
329
  hostAria(this.internals, {
295
330
  describedBy: this.hasContent ? [bubble] : [],
296
- label: this.for === '' ? '' : this.describedText,
331
+ label: this.describedText,
297
332
  });
298
333
  }
299
334
  render() {
@@ -339,6 +374,12 @@ __decorate([
339
374
  __decorate([
340
375
  property()
341
376
  ], HelixHeadlessTooltip.prototype, "placement", void 0);
377
+ __decorate([
378
+ property({ attribute: 'show-on' })
379
+ ], HelixHeadlessTooltip.prototype, "showOn", void 0);
380
+ __decorate([
381
+ property({ attribute: 'skip-delay', type: Number })
382
+ ], HelixHeadlessTooltip.prototype, "skipDelay", void 0);
342
383
  __decorate([
343
384
  property()
344
385
  ], HelixHeadlessTooltip.prototype, "within", void 0);
@@ -0,0 +1,30 @@
1
+ /** What names and describes a stop's panel. */
2
+ export interface StopNaming {
3
+ /** The elements that describe the panel, in reading order. */
4
+ describedBy: Element[];
5
+ /** The elements that name the panel, or none where a label names it. */
6
+ labelledBy: Element[];
7
+ }
8
+ /** Where a stop's words are. */
9
+ export interface StopWords {
10
+ /** The elements in the stop's default slot. */
11
+ content: readonly Element[];
12
+ /** The elements in its `description` slot. */
13
+ description: readonly Element[];
14
+ /** The elements in its `heading` slot. */
15
+ heading: readonly Element[];
16
+ /** Whether the stop has a `label` of its own. */
17
+ labeled: boolean;
18
+ /** The element that says the stop's place in the tour, such as "2 of 5". */
19
+ progress: Element | null;
20
+ }
21
+ /**
22
+ * What names a tour stop's panel and what describes it.
23
+ *
24
+ * Named by the `heading` slot, then by the first heading in the content, then
25
+ * by the stop's own `label`, and where there is none of those, by its place in
26
+ * the tour, so no stop is an unnamed group. Described by the `description`
27
+ * slot and by its place, which is how a screen reader hears where it is as
28
+ * focus moves from one stop to the next.
29
+ */
30
+ export declare const stopNaming: ({ content, description, heading, labeled, progress, }: StopWords) => StopNaming;
@@ -0,0 +1,10 @@
1
+ import { headingIn } from '@fusion.dev/helix-core';
2
+ export const stopNaming = ({ content, description, heading, labeled, progress, }) => {
3
+ const first = heading.length > 0 ? null : headingIn(content);
4
+ const found = heading.length > 0 ? [...heading] : first ? [first] : [];
5
+ const place = progress ? [progress] : [];
6
+ if (found.length === 0 && !labeled) {
7
+ return { describedBy: [...description], labelledBy: place };
8
+ }
9
+ return { describedBy: [...description, ...place], labelledBy: found };
10
+ };