@fusion.dev/helix-headless 0.144.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 (319) hide show
  1. package/custom-elements.json +15195 -4240
  2. package/dist/accordion/item/accordion-item.d.ts +62 -12
  3. package/dist/accordion/item/accordion-item.js +41 -10
  4. package/dist/accordion/root/accordion-root.d.ts +14 -5
  5. package/dist/accordion/root/accordion-root.js +7 -1
  6. package/dist/announcement/root/announcement-root.d.ts +24 -3
  7. package/dist/announcement/root/announcement-root.js +39 -2
  8. package/dist/app-header/root/app-header-root.d.ts +12 -0
  9. package/dist/app-header/root/app-header-root.js +13 -1
  10. package/dist/app-header/sectioning.d.ts +9 -0
  11. package/dist/app-header/sectioning.js +27 -0
  12. package/dist/avatar/group/avatar-group.d.ts +20 -14
  13. package/dist/avatar/group/avatar-group.js +17 -19
  14. package/dist/badge/root/badge-root.d.ts +3 -1
  15. package/dist/badge/root/badge-root.js +2 -2
  16. package/dist/bar-chart/root/bar-chart-root.d.ts +18 -1
  17. package/dist/bar-chart/root/bar-chart-root.js +52 -15
  18. package/dist/bar-chart/stack.d.ts +15 -0
  19. package/dist/bar-chart/stack.js +19 -0
  20. package/dist/breadcrumbs/root/breadcrumbs-root.d.ts +57 -1
  21. package/dist/breadcrumbs/root/breadcrumbs-root.js +73 -3
  22. package/dist/button/root/button-root.d.ts +26 -0
  23. package/dist/button/root/button-root.js +38 -18
  24. package/dist/button-group/root/button-group-root.d.ts +2 -1
  25. package/dist/button-group/root/button-group-root.js +5 -1
  26. package/dist/canvas/node/canvas-node.d.ts +4 -3
  27. package/dist/canvas/node/canvas-node.js +3 -2
  28. package/dist/canvas/root/canvas-root.d.ts +38 -3
  29. package/dist/canvas/root/canvas-root.js +123 -42
  30. package/dist/card/root/card-root.d.ts +24 -3
  31. package/dist/card/root/card-root.js +20 -8
  32. package/dist/carousel/root/carousel-root.d.ts +133 -4
  33. package/dist/carousel/root/carousel-root.js +174 -24
  34. package/dist/carousel/stops.d.ts +20 -0
  35. package/dist/carousel/stops.js +30 -0
  36. package/dist/chat/composer/chat-composer.d.ts +38 -6
  37. package/dist/chat/composer/chat-composer.js +42 -9
  38. package/dist/chat/message/chat-message.d.ts +32 -1
  39. package/dist/chat/message/chat-message.js +27 -10
  40. package/dist/chat/naming.d.ts +26 -0
  41. package/dist/chat/naming.js +14 -0
  42. package/dist/chat/root/chat-root.d.ts +58 -0
  43. package/dist/chat/root/chat-root.js +43 -3
  44. package/dist/checkbox/root/checkbox-root.d.ts +20 -0
  45. package/dist/checkbox/root/checkbox-root.js +14 -1
  46. package/dist/checkbox-group/root/checkbox-group-root.d.ts +8 -2
  47. package/dist/checkbox-group/root/checkbox-group-root.js +12 -10
  48. package/dist/checkbox-group/select-all.d.ts +27 -0
  49. package/dist/checkbox-group/select-all.js +12 -0
  50. package/dist/code-block/highlight.d.ts +15 -0
  51. package/dist/code-block/highlight.js +4 -0
  52. package/dist/code-block/root/code-block-root.d.ts +57 -2
  53. package/dist/code-block/root/code-block-root.js +63 -8
  54. package/dist/color-input/root/color-input-root.d.ts +3 -0
  55. package/dist/color-input/root/color-input-root.js +6 -3
  56. package/dist/color-picker/root/color-picker-root.d.ts +33 -4
  57. package/dist/color-picker/root/color-picker-root.js +80 -21
  58. package/dist/color-picker/swatches.d.ts +12 -0
  59. package/dist/color-picker/swatches.js +26 -0
  60. package/dist/combobox/root/combobox-root.d.ts +43 -0
  61. package/dist/combobox/root/combobox-root.js +79 -15
  62. package/dist/command/root/command-root.d.ts +16 -1
  63. package/dist/command/root/command-root.js +22 -4
  64. package/dist/copy-button/root/copy-button-root.d.ts +26 -1
  65. package/dist/copy-button/root/copy-button-root.js +12 -5
  66. package/dist/data-table/index.d.ts +1 -1
  67. package/dist/data-table/index.js +1 -1
  68. package/dist/data-table/keys.d.ts +24 -0
  69. package/dist/data-table/keys.js +24 -0
  70. package/dist/data-table/root/data-table-root.d.ts +170 -13
  71. package/dist/data-table/root/data-table-root.js +294 -52
  72. package/dist/data-table/tree.d.ts +55 -0
  73. package/dist/data-table/tree.js +54 -0
  74. package/dist/date-input/root/date-input-root.d.ts +3 -1
  75. package/dist/date-input/root/date-input-root.js +18 -7
  76. package/dist/date-picker/root/date-picker-root.js +12 -6
  77. package/dist/description-list/item/description-list-item.d.ts +17 -2
  78. package/dist/description-list/item/description-list-item.js +15 -2
  79. package/dist/description-list/root/description-list-root.d.ts +27 -1
  80. package/dist/description-list/root/description-list-root.js +26 -1
  81. package/dist/dialog/description.js +15 -1
  82. package/dist/dialog/root/dialog-root.d.ts +8 -2
  83. package/dist/dialog/root/dialog-root.js +16 -1
  84. package/dist/divider/root/divider-root.d.ts +3 -0
  85. package/dist/divider/root/divider-root.js +2 -1
  86. package/dist/donut-chart/root/donut-chart-root.d.ts +31 -0
  87. package/dist/donut-chart/root/donut-chart-root.js +48 -1
  88. package/dist/drawer/reveal.d.ts +8 -0
  89. package/dist/drawer/reveal.js +1 -0
  90. package/dist/drawer/root/drawer-root.d.ts +59 -0
  91. package/dist/drawer/root/drawer-root.js +63 -4
  92. package/dist/drilldown/item/drilldown-item.d.ts +11 -6
  93. package/dist/drilldown/item/drilldown-item.js +8 -5
  94. package/dist/drilldown/root/drilldown-root.d.ts +29 -0
  95. package/dist/drilldown/root/drilldown-root.js +28 -5
  96. package/dist/element-events.generated.js +83 -12
  97. package/dist/empty/root/empty-root.d.ts +3 -1
  98. package/dist/empty/root/empty-root.js +3 -2
  99. package/dist/file-input/root/file-input-root.d.ts +69 -6
  100. package/dist/file-input/root/file-input-root.js +82 -19
  101. package/dist/file-input/screen.d.ts +2 -1
  102. package/dist/filter-input/menu/filter-menu.d.ts +5 -0
  103. package/dist/filter-input/menu/filter-menu.js +16 -4
  104. package/dist/filter-input/root/filter-input-root.d.ts +30 -6
  105. package/dist/filter-input/root/filter-input-root.js +1 -1
  106. package/dist/filter-input/suggest.d.ts +28 -3
  107. package/dist/filter-input/suggest.js +18 -8
  108. package/dist/format-date/root/format-date-root.d.ts +9 -16
  109. package/dist/format-date/root/format-date-root.js +10 -27
  110. package/dist/heatmap/root/heatmap-root.d.ts +24 -13
  111. package/dist/heatmap/root/heatmap-root.js +34 -12
  112. package/dist/icon/root/icon-root.d.ts +12 -1
  113. package/dist/icon/root/icon-root.js +21 -10
  114. package/dist/icon/settle.d.ts +7 -0
  115. package/dist/icon/settle.js +10 -0
  116. package/dist/icon-button/root/icon-button-root.d.ts +2 -0
  117. package/dist/image/root/image-root.d.ts +5 -1
  118. package/dist/image/root/image-root.js +10 -2
  119. package/dist/input/root/input-root.d.ts +13 -3
  120. package/dist/input/root/input-root.js +12 -3
  121. package/dist/kbd/root/kbd-root.d.ts +35 -1
  122. package/dist/kbd/root/kbd-root.js +23 -4
  123. package/dist/legend/root/legend-root.d.ts +55 -8
  124. package/dist/legend/root/legend-root.js +55 -13
  125. package/dist/lightbox/root/lightbox-root.d.ts +49 -6
  126. package/dist/lightbox/root/lightbox-root.js +86 -19
  127. package/dist/lightbox/zoom-controller.d.ts +59 -0
  128. package/dist/lightbox/zoom-controller.js +128 -0
  129. package/dist/lightbox/zoom.d.ts +22 -0
  130. package/dist/lightbox/zoom.js +16 -0
  131. package/dist/line-chart/root/line-chart-root.d.ts +22 -1
  132. package/dist/line-chart/root/line-chart-root.js +21 -1
  133. package/dist/list/item/list-item.d.ts +70 -1
  134. package/dist/list/item/list-item.js +77 -6
  135. package/dist/list/numbering.d.ts +14 -0
  136. package/dist/list/numbering.js +8 -0
  137. package/dist/list/root/list-root.d.ts +155 -9
  138. package/dist/list/root/list-root.js +246 -4
  139. package/dist/list/selection.d.ts +49 -0
  140. package/dist/list/selection.js +30 -0
  141. package/dist/listbox/root/listbox-root.d.ts +37 -1
  142. package/dist/listbox/root/listbox-root.js +49 -4
  143. package/dist/loader/busy.d.ts +10 -0
  144. package/dist/loader/busy.js +11 -0
  145. package/dist/loader/root/loader-root.d.ts +9 -2
  146. package/dist/loader/root/loader-root.js +23 -3
  147. package/dist/map/root/map-root.d.ts +6 -2
  148. package/dist/map/root/map-root.js +5 -2
  149. package/dist/megamenu/root/megamenu-root.d.ts +62 -0
  150. package/dist/megamenu/root/megamenu-root.js +90 -5
  151. package/dist/menu/index.d.ts +1 -0
  152. package/dist/menu/index.js +1 -0
  153. package/dist/menu/item/menu-item.d.ts +87 -11
  154. package/dist/menu/item/menu-item.js +78 -22
  155. package/dist/menu/root/menu-root.d.ts +39 -1
  156. package/dist/menu/root/menu-root.js +65 -8
  157. package/dist/menu/section/menu-section.d.ts +55 -0
  158. package/dist/menu/section/menu-section.js +60 -0
  159. package/dist/meter/root/meter-root.d.ts +20 -1
  160. package/dist/meter/root/meter-root.js +11 -15
  161. package/dist/navigation/root/navigation-root.d.ts +18 -2
  162. package/dist/navigation/root/navigation-root.js +29 -3
  163. package/dist/notifications/root/notifications-root.d.ts +25 -0
  164. package/dist/notifications/root/notifications-root.js +33 -5
  165. package/dist/number-input/root/number-input-root.d.ts +17 -8
  166. package/dist/number-input/root/number-input-root.js +2 -7
  167. package/dist/object-inspector/root/object-inspector-root.d.ts +161 -32
  168. package/dist/object-inspector/root/object-inspector-root.js +264 -123
  169. package/dist/object-inspector/rows.d.ts +75 -0
  170. package/dist/object-inspector/rows.js +119 -0
  171. package/dist/option/root/option-root.d.ts +5 -3
  172. package/dist/outline/levels.d.ts +31 -0
  173. package/dist/outline/levels.js +23 -0
  174. package/dist/outline/root/outline-root.d.ts +86 -3
  175. package/dist/outline/root/outline-root.js +95 -12
  176. package/dist/pagination/size/pagination-size.d.ts +12 -2
  177. package/dist/pagination/size/pagination-size.js +20 -5
  178. package/dist/pin-input/code.d.ts +10 -0
  179. package/dist/pin-input/code.js +13 -0
  180. package/dist/pin-input/root/pin-input-root.d.ts +25 -1
  181. package/dist/pin-input/root/pin-input-root.js +10 -11
  182. package/dist/popover/hide-outside.d.ts +14 -0
  183. package/dist/popover/hide-outside.js +41 -0
  184. package/dist/popover/root/popover-root.d.ts +57 -7
  185. package/dist/popover/root/popover-root.js +58 -3
  186. package/dist/progress/root/progress-root.d.ts +20 -0
  187. package/dist/progress/root/progress-root.js +15 -15
  188. package/dist/qr-code/export.d.ts +26 -1
  189. package/dist/qr-code/export.js +14 -0
  190. package/dist/qr-code/root/qr-code-root.d.ts +19 -5
  191. package/dist/qr-code/root/qr-code-root.js +58 -9
  192. package/dist/quote/marks.d.ts +8 -0
  193. package/dist/quote/marks.js +5 -0
  194. package/dist/quote/root/quote-root.d.ts +14 -2
  195. package/dist/quote/root/quote-root.js +23 -1
  196. package/dist/radio/group/radio-group.d.ts +4 -10
  197. package/dist/radio/group/radio-group.js +5 -10
  198. package/dist/radio/root/radio-root.d.ts +6 -2
  199. package/dist/radio/root/radio-root.js +6 -1
  200. package/dist/rating/root/rating-root.d.ts +10 -5
  201. package/dist/rating/root/rating-root.js +32 -8
  202. package/dist/rating/steps.d.ts +7 -0
  203. package/dist/rating/steps.js +9 -0
  204. package/dist/scatter-chart/fit.d.ts +15 -0
  205. package/dist/scatter-chart/fit.js +35 -0
  206. package/dist/scatter-chart/root/scatter-chart-root.d.ts +21 -1
  207. package/dist/scatter-chart/root/scatter-chart-root.js +30 -17
  208. package/dist/scroll-area/root/scroll-area-root.d.ts +84 -0
  209. package/dist/scroll-area/root/scroll-area-root.js +106 -7
  210. package/dist/scroll-area/scrollbar-controller.d.ts +111 -0
  211. package/dist/scroll-area/scrollbar-controller.js +172 -0
  212. package/dist/scroll-area/scrollbar.d.ts +92 -0
  213. package/dist/scroll-area/scrollbar.js +33 -0
  214. package/dist/select/root/select-root.d.ts +15 -0
  215. package/dist/select/root/select-root.js +18 -6
  216. package/dist/skeleton/root/skeleton-root.d.ts +3 -0
  217. package/dist/skeleton/root/skeleton-root.js +4 -2
  218. package/dist/skip-link/root/skip-link-root.js +2 -2
  219. package/dist/slider/root/slider-root.d.ts +41 -4
  220. package/dist/slider/root/slider-root.js +23 -3
  221. package/dist/sparkline/readings.d.ts +18 -0
  222. package/dist/sparkline/readings.js +19 -0
  223. package/dist/sparkline/root/sparkline-root.d.ts +78 -7
  224. package/dist/sparkline/root/sparkline-root.js +118 -23
  225. package/dist/speed-dial/root/speed-dial-root.js +2 -2
  226. package/dist/splitter/root/splitter-root.d.ts +20 -12
  227. package/dist/splitter/root/splitter-root.js +226 -203
  228. package/dist/splitter/snap.d.ts +9 -0
  229. package/dist/splitter/snap.js +12 -0
  230. package/dist/stat/root/stat-root.d.ts +14 -5
  231. package/dist/stat/root/stat-root.js +21 -5
  232. package/dist/steps/reach.d.ts +20 -0
  233. package/dist/steps/reach.js +15 -0
  234. package/dist/steps/root/steps-root.d.ts +76 -0
  235. package/dist/steps/root/steps-root.js +93 -1
  236. package/dist/steps/step/step.d.ts +45 -2
  237. package/dist/steps/step/step.js +33 -6
  238. package/dist/switch/root/switch-root.d.ts +10 -0
  239. package/dist/table/column/table-column.d.ts +11 -10
  240. package/dist/table/column/table-column.js +8 -19
  241. package/dist/table/root/table-root.d.ts +126 -4
  242. package/dist/table/root/table-root.js +156 -6
  243. package/dist/table/row/table-row.d.ts +47 -1
  244. package/dist/table/row/table-row.js +39 -2
  245. package/dist/tabs/panel/tab-panel.d.ts +22 -5
  246. package/dist/tabs/panel/tab-panel.js +34 -6
  247. package/dist/tabs/root/tabs-root.d.ts +37 -1
  248. package/dist/tabs/root/tabs-root.js +22 -4
  249. package/dist/tabs/tab/tab.d.ts +41 -4
  250. package/dist/tabs/tab/tab.js +49 -3
  251. package/dist/tag/group/tag-group.d.ts +31 -4
  252. package/dist/tag/group/tag-group.js +46 -7
  253. package/dist/tag/root/tag-root.js +9 -2
  254. package/dist/textarea/root/textarea-root.d.ts +28 -1
  255. package/dist/textarea/root/textarea-root.js +40 -1
  256. package/dist/time-input/root/time-input-root.d.ts +5 -1
  257. package/dist/time-input/root/time-input-root.js +16 -8
  258. package/dist/time-picker/root/time-picker-root.d.ts +18 -4
  259. package/dist/time-picker/root/time-picker-root.js +7 -10
  260. package/dist/time-picker/wheel-name.d.ts +9 -0
  261. package/dist/time-picker/wheel-name.js +15 -0
  262. package/dist/timeline/group/timeline-group.d.ts +46 -0
  263. package/dist/timeline/group/timeline-group.js +37 -0
  264. package/dist/timeline/index.d.ts +1 -0
  265. package/dist/timeline/index.js +1 -0
  266. package/dist/timeline/root/timeline-root.d.ts +10 -3
  267. package/dist/toast/item/toast-item.d.ts +24 -18
  268. package/dist/toast/item/toast-item.js +36 -40
  269. package/dist/toast/root/toast-root.d.ts +6 -0
  270. package/dist/toast/root/toast-root.js +16 -1
  271. package/dist/toggle/group/toggle-group.d.ts +24 -3
  272. package/dist/toggle/group/toggle-group.js +34 -5
  273. package/dist/toolbar/root/toolbar-root.d.ts +13 -0
  274. package/dist/toolbar/root/toolbar-root.js +15 -5
  275. package/dist/tooltip/root/tooltip-root.d.ts +13 -2
  276. package/dist/tooltip/root/tooltip-root.js +9 -1
  277. package/dist/tour/back.d.ts +10 -0
  278. package/dist/tour/back.js +8 -0
  279. package/dist/tour/root/tour-root.d.ts +30 -2
  280. package/dist/tour/root/tour-root.js +34 -8
  281. package/dist/tree/item/tree-item.d.ts +61 -4
  282. package/dist/tree/item/tree-item.js +92 -20
  283. package/dist/tree/root/tree-root.d.ts +58 -0
  284. package/dist/tree/root/tree-root.js +80 -16
  285. package/dist/tree/selection.d.ts +24 -0
  286. package/dist/tree/selection.js +13 -0
  287. package/dist/trend/root/trend-root.d.ts +43 -18
  288. package/dist/trend/root/trend-root.js +31 -6
  289. package/dist/video/media-events.d.ts +44 -0
  290. package/dist/video/media-events.js +35 -0
  291. package/dist/video/root/video-root.d.ts +117 -18
  292. package/dist/video/root/video-root.js +93 -47
  293. package/dist/video-playlist/menu/video-playlist-menu.d.ts +5 -0
  294. package/dist/video-playlist/menu/video-playlist-menu.js +3 -1
  295. package/dist/video-playlist/order.d.ts +41 -0
  296. package/dist/video-playlist/order.js +34 -0
  297. package/dist/video-playlist/root/video-playlist-root.d.ts +77 -3
  298. package/dist/video-playlist/root/video-playlist-root.js +60 -16
  299. package/dist/virtual-list/controls.d.ts +4 -0
  300. package/dist/virtual-list/controls.js +30 -0
  301. package/dist/virtual-list/fill.d.ts +22 -0
  302. package/dist/virtual-list/fill.js +23 -0
  303. package/dist/virtual-list/keys.d.ts +15 -0
  304. package/dist/virtual-list/keys.js +24 -0
  305. package/dist/virtual-list/plan.d.ts +49 -0
  306. package/dist/virtual-list/plan.js +16 -0
  307. package/dist/virtual-list/root/virtual-list-root.d.ts +259 -26
  308. package/dist/virtual-list/root/virtual-list-root.js +333 -49
  309. package/lint.json +1 -1
  310. package/package.json +8 -8
  311. package/types/jsx.d.ts +8113 -1394
  312. package/types/svelte.d.ts +2 -0
  313. package/types/vue.d.ts +1678 -353
  314. package/dist/data-table/root/order.d.ts +0 -141
  315. package/dist/data-table/root/order.js +0 -76
  316. package/dist/listbox/range.d.ts +0 -17
  317. package/dist/listbox/range.js +0 -5
  318. package/dist/tag/selection.d.ts +0 -10
  319. package/dist/tag/selection.js +0 -12
@@ -8,6 +8,13 @@ import { type PropertyValues, type TemplateResult } from 'lit';
8
8
  * still there when the person comes back, and the content of every panel is in
9
9
  * the server-rendered markup for crawlers, whichever tab is open.
10
10
  *
11
+ * The host is the `tabpanel` while its tab is selected, through
12
+ * `ElementInternals`, so the tab's `aria-controls` can reach it from the tab's
13
+ * shadow root. The box inside is a tab stop only while the panel holds nothing
14
+ * focusable, as the ARIA tabs pattern and React Aria's tab panel do: Tab from
15
+ * the tab list lands on the panel's first link or control where there is one,
16
+ * and on the panel itself where there is none.
17
+ *
11
18
  * @since 0.1.0
12
19
  * @status stable
13
20
  * @aliases TabPanel
@@ -15,7 +22,7 @@ import { type PropertyValues, type TemplateResult } from 'lit';
15
22
  *
16
23
  * @slot - The panel's content.
17
24
  *
18
- * @csspart panel - The panel container.
25
+ * @csspart panel - The panel container, which takes the focus while it holds nothing focusable.
19
26
  *
20
27
  * @cssstate selected - While its tab is the one selected.
21
28
  */
@@ -28,10 +35,9 @@ export declare class HelixHeadlessTabPanel extends HelixElement {
28
35
  * writes: the tab already says it.
29
36
  *
30
37
  * A string and not a reference: an IDREF resolves only in the tree it is
31
- * written in, and an element reference cannot reach a sibling shadow tree
32
- * either, so `aria-labelledby` to the tab's own id leaves the panel with no
33
- * accessible name. `aria-label` on a `tabpanel` is how the pattern names one
34
- * anyway.
38
+ * written in, and the tab's control is inside the tab's shadow root, which no
39
+ * reference from the light DOM can reach. `aria-label` on a `tabpanel` is how
40
+ * the pattern names one anyway.
35
41
  */
36
42
  label: string;
37
43
  /**
@@ -42,6 +48,17 @@ export declare class HelixHeadlessTabPanel extends HelixElement {
42
48
  * Pairs the panel with the tab of the same value.
43
49
  */
44
50
  value: string;
51
+ /**
52
+ * Whether the content holds a tab stop of its own, so the panel needs none.
53
+ * False on a server, which serves the panel as a stop: it cannot look.
54
+ */
55
+ private holdsStop;
56
+ /** Re-reads the content when it changes in a way that adds or removes a stop. */
57
+ private readonly watcher;
58
+ private readonly readStops;
59
+ connectedCallback(): void;
60
+ disconnectedCallback(): void;
61
+ protected updated(changed: PropertyValues): void;
45
62
  /**
46
63
  * Mirrors the chosen state where a stylesheet can select on it.
47
64
  *
@@ -4,29 +4,54 @@ 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, HelixElement } from '@fusion.dev/helix-core';
7
+ import { customElement, focusableElements, HelixElement, hostAria } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
- import { property } from 'lit/decorators.js';
9
+ import { property, state } from 'lit/decorators.js';
10
10
  let HelixHeadlessTabPanel = class HelixHeadlessTabPanel extends HelixElement {
11
11
  constructor() {
12
12
  super(...arguments);
13
13
  this.label = '';
14
14
  this.selected = false;
15
15
  this.value = '';
16
+ this.holdsStop = false;
17
+ this.watcher = typeof MutationObserver === 'undefined'
18
+ ? undefined
19
+ : new MutationObserver(() => this.readStops());
20
+ this.readStops = () => {
21
+ this.holdsStop = this.selected && focusableElements(this).length > 0;
22
+ };
23
+ }
24
+ connectedCallback() {
25
+ super.connectedCallback();
26
+ this.watcher?.observe(this, {
27
+ attributeFilter: ['contenteditable', 'disabled', 'hidden', 'href', 'tabindex'],
28
+ attributes: true,
29
+ childList: true,
30
+ subtree: true,
31
+ });
32
+ }
33
+ disconnectedCallback() {
34
+ super.disconnectedCallback();
35
+ this.watcher?.disconnect();
36
+ }
37
+ updated(changed) {
38
+ super.updated(changed);
39
+ if (changed.has('selected')) {
40
+ this.readStops();
41
+ }
16
42
  }
17
43
  willUpdate(changed) {
18
44
  super.willUpdate(changed);
19
45
  this.toggleState('selected', this.selected);
46
+ hostAria(this.internals, { label: this.label, role: this.selected ? 'tabpanel' : '' });
20
47
  }
21
48
  render() {
22
49
  return html `<div
23
50
  part="panel"
24
- role="tabpanel"
25
- aria-label=${this.label || nothing}
26
51
  ?hidden=${!this.selected}
27
- tabindex="0"
52
+ tabindex=${this.holdsStop ? nothing : '0'}
28
53
  >
29
- <slot></slot>
54
+ <slot @slotchange=${this.readStops}></slot>
30
55
  </div>`;
31
56
  }
32
57
  };
@@ -39,6 +64,9 @@ __decorate([
39
64
  __decorate([
40
65
  property()
41
66
  ], HelixHeadlessTabPanel.prototype, "value", void 0);
67
+ __decorate([
68
+ state()
69
+ ], HelixHeadlessTabPanel.prototype, "holdsStop", void 0);
42
70
  HelixHeadlessTabPanel = __decorate([
43
71
  customElement('helix-headless-tab-panel')
44
72
  ], HelixHeadlessTabPanel);
@@ -16,6 +16,29 @@ declare const HelixHeadlessTabs_base: (new (...args: any[]) => import("@fusion.d
16
16
  * panel is cheap. Set `manual` when showing a panel is expensive, so focus can
17
17
  * move without selecting.
18
18
  *
19
+ * `orientation="vertical"` sets the tabs in a column beside the panels, and the
20
+ * tab list says `aria-orientation="vertical"`. The arrow keys of both axes move
21
+ * between tabs either way.
22
+ *
23
+ * A tab with an `href` is a link, for tabs that are pages of a site. Pressing
24
+ * it follows the link, and the arrow keys move focus without choosing, so
25
+ * arrowing past a tab never leaves the page. Mark the tab of the page you are
26
+ * on with `selected`, or set `active` to its value.
27
+ *
28
+ * Each tab says which panel it controls through `aria-controls`, set as an
29
+ * element reference to the panel's host, where the engine has reflection.
30
+ *
31
+ * Keys, on the tab list: Left and Right Arrow move between tabs, and so do Up
32
+ * and Down; Home and End go to the first and the last. The horizontal arrows
33
+ * follow the reading direction, so on a right-to-left page Left moves on.
34
+ * Disabled tabs are passed over. Tab leaves the list for the selected panel,
35
+ * which is a tab stop only while it holds nothing focusable.
36
+ *
37
+ * The `indicator` part moves to the selected tab and carries its place and
38
+ * size, so a mark of your own can be drawn there: style the part, or slot an
39
+ * element into `indicator`, which sits inside the part and reads the same
40
+ * properties.
41
+ *
19
42
  * @since 0.1.0
20
43
  * @status stable
21
44
  * @category navigation
@@ -23,13 +46,17 @@ declare const HelixHeadlessTabs_base: (new (...args: any[]) => import("@fusion.d
23
46
  * @documentation https://fusion.dev/helix/components/tabs
24
47
  *
25
48
  * @slot - The tabs, as direct children of this element.
49
+ * @slot indicator - A mark of your own, drawn inside the `indicator` part, where `--indicator-x`, `--indicator-y`, `--indicator-inline-size` and `--indicator-block-size` reach it.
26
50
  * @slot panels - The panels, as direct children of this element. Panels inside a wrapper are not paired with tabs, because pairing reads only the direct children.
27
51
  *
28
52
  * @event {HelixActivateEvent} helix-activate - Fired after the active tab changes, so the element already holds the new value.
29
53
  * @event {HelixBeforeActivateEvent} helix-before-activate - Fired before the active tab changes, and cancelable. Canceling it is the only way to block a change.
30
54
  * @event {HelixSelectEvent} helix-select - Fired when a tab is chosen. The tab sends it and the root handles it, so you can listen on the root. Cancelable: canceling it stops the change and leaves the decision to you.
31
55
  *
32
- * @csspart indicator - The marker that moves to the selected tab.
56
+ * @csspart indicator - The marker that moves to the selected tab. It carries the selected tab's
57
+ * place and size as `--indicator-x`, `--indicator-y`, `--indicator-inline-size` and
58
+ * `--indicator-block-size`, and `data-placed` once it has been placed, for a mark you draw
59
+ * and animate yourself.
33
60
  * @csspart panels - The panel container.
34
61
  * @csspart tabs - The tab list.
35
62
  */
@@ -54,6 +81,13 @@ export declare class HelixHeadlessTabs extends HelixHeadlessTabs_base {
54
81
  * panels on the way.
55
82
  */
56
83
  manual: boolean;
84
+ /**
85
+ * The direction the tabs run. `vertical` sets them in a column beside the
86
+ * panels and tells a screen reader the list is vertical.
87
+ *
88
+ * Optional, so the resting row writes no attribute onto the host.
89
+ */
90
+ orientation?: 'horizontal' | 'vertical';
57
91
  /**
58
92
  * How the row of tabs is drawn.
59
93
  *
@@ -80,6 +114,8 @@ export declare class HelixHeadlessTabs extends HelixHeadlessTabs_base {
80
114
  */
81
115
  variant?: 'segmented' | 'underlined';
82
116
  private get tabs();
117
+ /** Whether any tab is a link, so arriving on one chooses nothing. */
118
+ private get linked();
83
119
  private get panels();
84
120
  private readonly handleSelection;
85
121
  /** Pushes the current value down to whichever tab and panel own it. */
@@ -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 { AriaForwardController, customElement, HelixElement, IndicatorController, KeyboardNavigationController, MixinControllerActive, whenHydrated, } 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
  import { HelixHeadlessTabPanel } from '../panel/tab-panel.js';
11
11
  import { HelixHeadlessTab } from '../tab/tab.js';
@@ -15,7 +15,7 @@ let HelixHeadlessTabs = class HelixHeadlessTabs extends MixinControllerActive(He
15
15
  this.keys = new KeyboardNavigationController(this, {
16
16
  items: () => this.tabs,
17
17
  origin: 'focus',
18
- selects: () => !this.manual,
18
+ selects: () => !this.manual && !this.linked,
19
19
  });
20
20
  this.indicator = new IndicatorController(this, {
21
21
  active: () => this.tabs.find((tab) => tab.selected),
@@ -43,6 +43,9 @@ let HelixHeadlessTabs = class HelixHeadlessTabs extends MixinControllerActive(He
43
43
  get tabs() {
44
44
  return [...this.children].filter((child) => child instanceof HelixHeadlessTab);
45
45
  }
46
+ get linked() {
47
+ return this.tabs.some((tab) => (tab.href ?? '') !== '');
48
+ }
46
49
  get panels() {
47
50
  return [...this.children].filter((child) => child instanceof HelixHeadlessTabPanel);
48
51
  }
@@ -57,6 +60,13 @@ let HelixHeadlessTabs = class HelixHeadlessTabs extends MixinControllerActive(He
57
60
  });
58
61
  }
59
62
  const names = new Map(tabs.map((tab) => [tab.value, (tab.textContent ?? '').trim()]));
63
+ const panels = new Map(this.panels.map((panel) => [panel.value, panel]));
64
+ for (const tab of tabs) {
65
+ const panel = panels.get(tab.value);
66
+ whenHydrated(tab, () => {
67
+ tab.controls = panel;
68
+ });
69
+ }
60
70
  for (const panel of this.panels) {
61
71
  const selected = panel.value === value;
62
72
  const label = names.get(panel.value) ?? '';
@@ -88,9 +98,14 @@ let HelixHeadlessTabs = class HelixHeadlessTabs extends MixinControllerActive(He
88
98
  this.indicator.place();
89
99
  }
90
100
  render() {
91
- return html `<div part="tabs" role="tablist" aria-label=${this.label}>
101
+ return html `<div
102
+ part="tabs"
103
+ role="tablist"
104
+ aria-label=${this.label}
105
+ aria-orientation=${this.orientation === 'vertical' ? 'vertical' : nothing}
106
+ >
92
107
  <slot @slotchange=${this.handleSlotChange}></slot>
93
- <span part="indicator" aria-hidden="true"></span>
108
+ <span part="indicator" aria-hidden="true"><slot name="indicator"></slot></span>
94
109
  </div>
95
110
  <div part="panels"><slot name="panels" @slotchange=${this.handleSlotChange}></slot></div>`;
96
111
  }
@@ -101,6 +116,9 @@ __decorate([
101
116
  __decorate([
102
117
  property({ reflect: true, type: Boolean })
103
118
  ], HelixHeadlessTabs.prototype, "manual", void 0);
119
+ __decorate([
120
+ property({ reflect: true })
121
+ ], HelixHeadlessTabs.prototype, "orientation", void 0);
104
122
  __decorate([
105
123
  property({ reflect: true })
106
124
  ], HelixHeadlessTabs.prototype, "variant", void 0);
@@ -7,10 +7,10 @@ declare const HelixHeadlessTab_base: (new (...args: any[]) => import("@fusion.de
7
7
  * @description
8
8
  * A tab is an element, because it manages selection and its own tab stop.
9
9
  *
10
- * Its id and the id it controls both come from `value`, so the link between a
11
- * tab and its panel works with server rendering. A generated id would differ
12
- * between the server render and the client render, and `aria-controls` would
13
- * point at nothing.
10
+ * With an `href`, the tab is a link: `role="tab"` on an `<a>`, which a press,
11
+ * Enter and the browser's own link gestures follow. `helix-select` is sent
12
+ * first, and canceling it keeps the browser from following, so a client-side
13
+ * router follows it instead.
14
14
  *
15
15
  * @since 0.1.0
16
16
  * @status stable
@@ -27,6 +27,43 @@ declare const HelixHeadlessTab_base: (new (...args: any[]) => import("@fusion.de
27
27
  */
28
28
  export declare class HelixHeadlessTab extends HelixHeadlessTab_base {
29
29
  protected itemPart: string;
30
+ /**
31
+ * @internal The panel this tab shows, written by the tab set. A property,
32
+ * because it is an element, and it reaches the tab's `aria-controls` as an
33
+ * element reference: the panel is in the light DOM, which a reference from
34
+ * inside this shadow root can reach.
35
+ */
36
+ controls: Element | undefined;
37
+ /**
38
+ * The URL the tab links to. Setting it renders the tab as a link, for a set
39
+ * of tabs that are pages of a site.
40
+ */
41
+ href?: string;
42
+ /**
43
+ * The link's `rel`, for a tab with an `href`.
44
+ */
45
+ rel?: string;
46
+ /**
47
+ * Where the link opens, such as `_blank`, for a tab with an `href`.
48
+ */
49
+ target?: string;
50
+ /**
51
+ * Moves focus to the tab's control, the button or the link inside it, so a
52
+ * script can focus the tab the way it focuses a native one.
53
+ */
54
+ focus(options?: FocusOptions): void;
55
+ /**
56
+ * Chooses the tab before the browser follows its link, and keeps the browser
57
+ * from following where the choice was refused. A press with a modifier, or
58
+ * with a button other than the main one, opens the link where the browser
59
+ * opens it and chooses nothing here.
60
+ */
61
+ private readonly handleLinkClick;
62
+ /**
63
+ * Points the control's `aria-controls` at the panel, where the engine can.
64
+ * The control is this tab's own, so the reference is always its to clear.
65
+ */
66
+ protected updated(changed: PropertyValues): void;
30
67
  /**
31
68
  * Asks for the render that follows a hydration, so the stop stops being every
32
69
  * tab's.
@@ -4,31 +4,77 @@ 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, HelixElement, MixinSelectableItem } from '@fusion.dev/helix-core';
8
- import { html } from 'lit';
7
+ import { customElement, HelixElement, MixinSelectableItem, plainClick, reflectElements, } from '@fusion.dev/helix-core';
8
+ import { html, nothing } from 'lit';
9
+ import { property } from 'lit/decorators.js';
9
10
  let HelixHeadlessTab = class HelixHeadlessTab extends MixinSelectableItem(HelixElement) {
10
11
  constructor() {
11
12
  super(...arguments);
12
13
  this.itemPart = 'tab';
14
+ this.controls = undefined;
15
+ this.handleLinkClick = (event) => {
16
+ if (!plainClick(event)) {
17
+ return;
18
+ }
19
+ if (!this.select()) {
20
+ event.preventDefault();
21
+ }
22
+ };
23
+ }
24
+ focus(options) {
25
+ this.partNamed('tab')?.focus(options);
26
+ }
27
+ updated(changed) {
28
+ super.updated(changed);
29
+ const control = this.renderRoot.querySelector('[part="tab"]');
30
+ reflectElements(control, 'ariaControlsElements', this.controls ? [this.controls] : [], true);
13
31
  }
14
32
  firstUpdated(changed) {
15
33
  super.firstUpdated(changed);
16
34
  this.finishHydration();
17
35
  }
18
36
  render() {
37
+ const tabindex = this.selected || !this.childrenKnown ? '0' : '-1';
38
+ if ((this.href ?? '') !== '') {
39
+ return html `<a
40
+ part="tab"
41
+ role="tab"
42
+ href=${this.disabled ? nothing : (this.href ?? nothing)}
43
+ target=${this.target || nothing}
44
+ rel=${this.rel || nothing}
45
+ aria-selected=${this.selected ? 'true' : 'false'}
46
+ aria-disabled=${this.disabled ? 'true' : nothing}
47
+ tabindex=${this.disabled ? nothing : tabindex}
48
+ @click=${this.handleLinkClick}
49
+ >
50
+ <slot></slot>
51
+ </a>`;
52
+ }
19
53
  return html `<button
20
54
  part="tab"
21
55
  role="tab"
22
56
  type="button"
23
57
  aria-selected=${this.selected ? 'true' : 'false'}
24
58
  ?disabled=${this.disabled}
25
- tabindex=${this.selected || !this.childrenKnown ? '0' : '-1'}
59
+ tabindex=${tabindex}
26
60
  @click=${() => this.select()}
27
61
  >
28
62
  <slot></slot>
29
63
  </button>`;
30
64
  }
31
65
  };
66
+ __decorate([
67
+ property({ attribute: false })
68
+ ], HelixHeadlessTab.prototype, "controls", void 0);
69
+ __decorate([
70
+ property()
71
+ ], HelixHeadlessTab.prototype, "href", void 0);
72
+ __decorate([
73
+ property()
74
+ ], HelixHeadlessTab.prototype, "rel", void 0);
75
+ __decorate([
76
+ property()
77
+ ], HelixHeadlessTab.prototype, "target", void 0);
32
78
  HelixHeadlessTab = __decorate([
33
79
  customElement('helix-headless-tab')
34
80
  ], HelixHeadlessTab);
@@ -1,7 +1,8 @@
1
- import { HelixElement, KeyboardNavigationController } from '@fusion.dev/helix-core';
1
+ import { HelixElement, KeyboardNavigationController, type SelectionMode } from '@fusion.dev/helix-core';
2
2
  import { type PropertyValues, type TemplateResult } from 'lit';
3
3
  import { HelixHeadlessTag } from '../root/tag-root.js';
4
- import { type TagSelectionMode } from '../selection.js';
4
+ /** How many tags a group lets a reader select: core's `SelectionMode`. */
5
+ export type TagSelectionMode = SelectionMode;
5
6
  /**
6
7
  * @summary A set of tags that the arrow keys move through, and that can be removed and selected from the keyboard.
7
8
  *
@@ -17,9 +18,16 @@ import { type TagSelectionMode } from '../selection.js';
17
18
  * `single` selects one at a time and `multiple` any number. `value` holds the
18
19
  * selected tags' values, separated by commas, and a change asks with
19
20
  * `helix-before-change` and tells with `change`. Enter on a tag with an
20
- * `href` follows its link. A `disabled` tag is passed over.
21
+ * `href` follows its link. A `disabled` tag is passed over. A selected tag
22
+ * that is removed leaves `value`, which then sends `change`.
21
23
  *
22
- * Name the set with `label`.
24
+ * Name the set with `label`. Each tag is named by its words alone, without
25
+ * its remove button.
26
+ *
27
+ * **Removing the last tag keeps focus in the group.** With no tags left the
28
+ * set is a `group`, and focus moves to its `empty` part,
29
+ * which holds the `empty` slot: put words there such as "No filters" for a
30
+ * reader to hear.
23
31
  *
24
32
  * @since 0.1.0
25
33
  * @status stable
@@ -27,10 +35,13 @@ import { type TagSelectionMode } from '../selection.js';
27
35
  * @documentation https://fusion.dev/helix/components/tag
28
36
  *
29
37
  * @slot - The tags, in the order they are read.
38
+ * @slot empty - Shown in place of the tags while there are none, such as "No filters".
30
39
  *
31
40
  * @event {Event} change - Fired once a press has changed which tags are selected.
32
41
  * @event {HelixBeforeChangeEvent} helix-before-change - Fired before a press changes which tags are selected, with the `value` it would leave. Cancelable.
33
42
  * @event {HelixDismissEvent} helix-dismiss - A tag's remove button was pressed, or Delete or Backspace on the focused tag. Bubbles from that tag, which is its `target`; remove the tag in response. Cancelable.
43
+ *
44
+ * @csspart empty - Holds the `empty` slot while the group has no tags, rendered only then. Takes focus when the last tag is removed.
34
45
  */
35
46
  export declare class HelixHeadlessTagGroup extends HelixElement {
36
47
  /**
@@ -93,9 +104,25 @@ export declare class HelixHeadlessTagGroup extends HelixElement {
93
104
  protected press(tag: HelixHeadlessTag): void;
94
105
  private readonly handleKeydown;
95
106
  private readonly handleClick;
107
+ /**
108
+ * What happens once a tag has asked to be removed and the page has had its
109
+ * turn: a selected tag that left takes its value out of `value`, and focus
110
+ * that the last tag took with it comes to the group's `empty` part.
111
+ *
112
+ * Asked after the event and again a frame later, as `MixinDismissible`
113
+ * asks, because a framework removes the tag on its next render, after the
114
+ * handler. A canceled dismissal changes nothing.
115
+ */
116
+ private readonly handleDismiss;
117
+ private readonly handleSlotchange;
96
118
  private readonly handleFocusin;
97
119
  connectedCallback(): void;
98
120
  disconnectedCallback(): void;
121
+ /**
122
+ * Whether the group is known to hold no tags, which a server and a
123
+ * hydrating render cannot know: there it holds whatever it was given.
124
+ */
125
+ private get empty();
99
126
  protected willUpdate(changed: PropertyValues<this>): void;
100
127
  protected updated(changed: PropertyValues<this>): void;
101
128
  protected render(): TemplateResult;
@@ -4,11 +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 { commaList, customElement, HelixBeforeChangeEvent, HelixElement, hostAria, KeyboardNavigationController, requestChange, rovingStop, slottedWords, whenHydrated, } from '@fusion.dev/helix-core';
8
- import { html } from 'lit';
7
+ import { commaList, customElement, deepActiveElement, deepContains, HelixBeforeChangeEvent, HelixElement, hostAria, KeyboardNavigationController, pressedValue, prunedValue, requestChange, rovingStop, slottedWords, typeaheadBy, whenHydrated, } from '@fusion.dev/helix-core';
8
+ import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  import { HelixHeadlessTag } from '../root/tag-root.js';
11
- import { pressedValue } from '../selection.js';
12
11
  let HelixHeadlessTagGroup = class HelixHeadlessTagGroup extends HelixElement {
13
12
  constructor() {
14
13
  super(...arguments);
@@ -18,7 +17,7 @@ let HelixHeadlessTagGroup = class HelixHeadlessTagGroup extends HelixElement {
18
17
  items: () => this.reachable.map((tag) => this.navigable(tag)),
19
18
  origin: 'focus',
20
19
  selects: () => false,
21
- typeahead: (item) => item.words,
20
+ typeahead: typeaheadBy((item) => item.words),
22
21
  wrap: false,
23
22
  });
24
23
  this.handleKeydown = (event) => {
@@ -53,6 +52,37 @@ let HelixHeadlessTagGroup = class HelixHeadlessTagGroup extends HelixElement {
53
52
  this.press(tag);
54
53
  }
55
54
  };
55
+ this.handleDismiss = (event) => {
56
+ const tag = this.tagOf(event);
57
+ if (!tag) {
58
+ return;
59
+ }
60
+ const had = deepContains(this, deepActiveElement());
61
+ const settle = async () => {
62
+ if (event.defaultPrevented || tag.isConnected) {
63
+ return;
64
+ }
65
+ const left = this.tags.map((one) => this.valueFor(one));
66
+ const pruned = prunedValue(this.value, this.valueFor(tag), left);
67
+ if (pruned !== this.value) {
68
+ this.value = pruned;
69
+ this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
70
+ }
71
+ const active = document.activeElement;
72
+ if (!had || this.tags.length > 0 || (active !== null && active !== document.body)) {
73
+ return;
74
+ }
75
+ this.requestUpdate();
76
+ await this.updateComplete;
77
+ this.renderRoot.querySelector('[part~="empty"]')?.focus();
78
+ };
79
+ queueMicrotask(() => void settle());
80
+ requestAnimationFrame(() => void settle());
81
+ };
82
+ this.handleSlotchange = () => {
83
+ this.sync();
84
+ this.requestUpdate();
85
+ };
56
86
  this.handleFocusin = (event) => {
57
87
  const tag = this.tagOf(event);
58
88
  if (tag && tag !== this.last) {
@@ -122,24 +152,33 @@ let HelixHeadlessTagGroup = class HelixHeadlessTagGroup extends HelixElement {
122
152
  this.addEventListener('keydown', this.handleKeydown);
123
153
  this.addEventListener('click', this.handleClick);
124
154
  this.addEventListener('focusin', this.handleFocusin);
155
+ this.addEventListener('helix-dismiss', this.handleDismiss);
125
156
  }
126
157
  disconnectedCallback() {
127
158
  super.disconnectedCallback();
128
159
  this.removeEventListener('keydown', this.handleKeydown);
129
160
  this.removeEventListener('click', this.handleClick);
130
161
  this.removeEventListener('focusin', this.handleFocusin);
162
+ this.removeEventListener('helix-dismiss', this.handleDismiss);
163
+ }
164
+ get empty() {
165
+ return this.childrenKnown && this.tags.length === 0;
131
166
  }
132
167
  willUpdate(changed) {
133
168
  super.willUpdate(changed);
134
- hostAria(this.internals, { label: this.label, role: 'grid' });
135
- this.internals.ariaMultiSelectable = this.selectionMode === 'multiple' ? 'true' : null;
169
+ const empty = this.empty;
170
+ hostAria(this.internals, { label: this.label, role: empty ? 'group' : 'grid' });
171
+ this.internals.ariaMultiSelectable = this.selectionMode === 'multiple' && !empty ? 'true' : null;
136
172
  }
137
173
  updated(changed) {
138
174
  super.updated(changed);
139
175
  this.sync();
140
176
  }
141
177
  render() {
142
- return html `<slot @slotchange=${() => this.sync()}></slot>`;
178
+ const empty = this.empty
179
+ ? html `<div part="empty" tabindex="-1"><slot name="empty"></slot></div>`
180
+ : nothing;
181
+ return html `<slot @slotchange=${this.handleSlotchange}></slot>${empty}`;
143
182
  }
144
183
  };
145
184
  __decorate([
@@ -36,7 +36,10 @@ let HelixHeadlessTag = class HelixHeadlessTag extends MixinDismissible(HelixElem
36
36
  }
37
37
  willUpdate(changed) {
38
38
  super.willUpdate(changed);
39
- hostAria(this.internals, { role: this.grouped ? 'row' : '' });
39
+ hostAria(this.internals, {
40
+ label: this.grouped ? this.words : '',
41
+ role: this.grouped ? 'row' : '',
42
+ });
40
43
  this.internals.ariaSelected = this.selected === undefined ? null : String(this.selected);
41
44
  this.internals.ariaDisabled = this.disabled ? 'true' : null;
42
45
  this.toggleState('selected', this.selected === true);
@@ -58,7 +61,11 @@ let HelixHeadlessTag = class HelixHeadlessTag extends MixinDismissible(HelixElem
58
61
  render() {
59
62
  const content = html `<slot name="start" part="start"></slot>${this.renderLabel()}${this.dismissible ? this.renderDismiss() : nothing}`;
60
63
  return this.grouped
61
- ? html `<span part="cell" role="gridcell" tabindex=${this.focusable ? '0' : '-1'}
64
+ ? html `<span
65
+ aria-label=${this.words || nothing}
66
+ part="cell"
67
+ role="gridcell"
68
+ tabindex=${this.focusable ? '0' : '-1'}
62
69
  >${content}</span
63
70
  >`
64
71
  : content;