@fusion.dev/helix-headless 0.0.0-dev → 0.0.3

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 (210) hide show
  1. package/custom-elements.json +4513 -4483
  2. package/dist/accordion/item/accordion-item.d.ts +13 -15
  3. package/dist/accordion/item/accordion-item.js +15 -17
  4. package/dist/announcement/root/announcement-root.d.ts +7 -25
  5. package/dist/announcement/root/announcement-root.js +7 -20
  6. package/dist/app-header/root/app-header-root.d.ts +10 -17
  7. package/dist/app-header/root/app-header-root.js +9 -18
  8. package/dist/avatar/group/avatar-group.d.ts +10 -19
  9. package/dist/avatar/group/avatar-group.js +12 -17
  10. package/dist/avatar/root/avatar-root.d.ts +8 -8
  11. package/dist/avatar/root/avatar-root.js +19 -20
  12. package/dist/badge/root/badge-root.d.ts +6 -6
  13. package/dist/badge/root/badge-root.js +13 -13
  14. package/dist/bar-chart/root/bar-chart-root.d.ts +21 -29
  15. package/dist/bar-chart/root/bar-chart-root.js +42 -57
  16. package/dist/breadcrumbs/breadcrumb/breadcrumb.d.ts +12 -18
  17. package/dist/breadcrumbs/breadcrumb/breadcrumb.js +13 -16
  18. package/dist/breadcrumbs/root/breadcrumbs-root.d.ts +9 -28
  19. package/dist/breadcrumbs/root/breadcrumbs-root.js +9 -28
  20. package/dist/button/root/button-root.d.ts +84 -94
  21. package/dist/button/root/button-root.js +82 -92
  22. package/dist/callout/root/callout-root.d.ts +4 -5
  23. package/dist/carousel/root/carousel-root.d.ts +59 -71
  24. package/dist/carousel/root/carousel-root.js +73 -101
  25. package/dist/chat/composer/chat-composer.d.ts +29 -33
  26. package/dist/chat/composer/chat-composer.js +40 -44
  27. package/dist/chat/message/chat-message.d.ts +25 -25
  28. package/dist/chat/message/chat-message.js +26 -26
  29. package/dist/chat/root/chat-root.d.ts +28 -41
  30. package/dist/chat/root/chat-root.js +27 -40
  31. package/dist/checkbox/root/checkbox-root.d.ts +10 -12
  32. package/dist/checkbox/root/checkbox-root.js +29 -35
  33. package/dist/checkbox-group/root/checkbox-group-root.d.ts +32 -45
  34. package/dist/checkbox-group/root/checkbox-group-root.js +53 -79
  35. package/dist/code-block/root/code-block-root.d.ts +3 -4
  36. package/dist/code-block/root/code-block-root.js +8 -12
  37. package/dist/color-input/root/color-input-root.d.ts +36 -37
  38. package/dist/color-input/root/color-input-root.js +29 -34
  39. package/dist/color-picker/color.d.ts +7 -8
  40. package/dist/color-picker/color.js +7 -8
  41. package/dist/color-picker/root/color-picker-root.d.ts +55 -57
  42. package/dist/color-picker/root/color-picker-root.js +60 -64
  43. package/dist/combobox/root/combobox-root.d.ts +53 -58
  44. package/dist/combobox/root/combobox-root.js +85 -94
  45. package/dist/command/root/command-root.d.ts +8 -8
  46. package/dist/command/root/command-root.js +29 -33
  47. package/dist/copy-button/root/copy-button-root.d.ts +21 -24
  48. package/dist/copy-button/root/copy-button-root.js +25 -35
  49. package/dist/data-table/root/data-table-root.d.ts +77 -76
  50. package/dist/data-table/root/data-table-root.js +95 -84
  51. package/dist/data-table/root/order.d.ts +2 -2
  52. package/dist/data-table/root/order.js +1 -3
  53. package/dist/date-input/root/date-input-root.d.ts +88 -93
  54. package/dist/date-input/root/date-input-root.js +147 -158
  55. package/dist/date-picker/root/date-picker-root.d.ts +75 -83
  56. package/dist/date-picker/root/date-picker-root.js +120 -143
  57. package/dist/dialog/root/dialog-root.d.ts +41 -24
  58. package/dist/dialog/root/dialog-root.js +47 -24
  59. package/dist/divider/root/divider-root.d.ts +5 -6
  60. package/dist/divider/root/divider-root.js +4 -5
  61. package/dist/donut-chart/root/donut-chart-root.d.ts +22 -25
  62. package/dist/donut-chart/root/donut-chart-root.js +37 -51
  63. package/dist/drawer/root/drawer-root.d.ts +24 -29
  64. package/dist/drawer/root/drawer-root.js +15 -22
  65. package/dist/drilldown/item/drilldown-item.d.ts +14 -14
  66. package/dist/drilldown/item/drilldown-item.js +24 -26
  67. package/dist/drilldown/root/drilldown-root.d.ts +8 -10
  68. package/dist/drilldown/root/drilldown-root.js +6 -7
  69. package/dist/element-events.generated.js +1 -1
  70. package/dist/file-input/root/file-input-root.d.ts +20 -24
  71. package/dist/file-input/root/file-input-root.js +42 -57
  72. package/dist/format-date/root/format-date-root.js +1 -1
  73. package/dist/heatmap/root/buckets.d.ts +8 -8
  74. package/dist/heatmap/root/buckets.js +8 -8
  75. package/dist/heatmap/root/heatmap-root.d.ts +57 -63
  76. package/dist/heatmap/root/heatmap-root.js +72 -83
  77. package/dist/icon/root/icon-root.d.ts +4 -4
  78. package/dist/icon/root/icon-root.js +9 -9
  79. package/dist/icon-button/root/icon-button-root.d.ts +4 -6
  80. package/dist/icon-button/root/icon-button-root.js +4 -6
  81. package/dist/image/root/image-root.d.ts +49 -59
  82. package/dist/image/root/image-root.js +55 -65
  83. package/dist/index.d.ts +5 -5
  84. package/dist/index.js +5 -5
  85. package/dist/input/root/input-root.d.ts +61 -85
  86. package/dist/input/root/input-root.js +79 -106
  87. package/dist/kbd/root/kbd-root.d.ts +8 -7
  88. package/dist/kbd/root/kbd-root.js +71 -11
  89. package/dist/legend/root/legend-root.d.ts +1 -1
  90. package/dist/legend/root/legend-root.js +4 -5
  91. package/dist/line-chart/root/line-chart-root.d.ts +26 -30
  92. package/dist/line-chart/root/line-chart-root.js +10 -10
  93. package/dist/list/item/list-item.d.ts +14 -18
  94. package/dist/list/item/list-item.js +18 -22
  95. package/dist/list/root/list-root.d.ts +4 -4
  96. package/dist/list/root/list-root.js +3 -3
  97. package/dist/listbox/root/listbox-root.d.ts +31 -58
  98. package/dist/listbox/root/listbox-root.js +66 -109
  99. package/dist/megamenu/root/megamenu-root.d.ts +34 -35
  100. package/dist/megamenu/root/megamenu-root.js +53 -58
  101. package/dist/menu/item/menu-item.d.ts +39 -53
  102. package/dist/menu/item/menu-item.js +50 -64
  103. package/dist/menu/root/menu-root.d.ts +30 -40
  104. package/dist/menu/root/menu-root.js +49 -82
  105. package/dist/meter/root/meter-root.d.ts +49 -53
  106. package/dist/meter/root/meter-root.js +22 -27
  107. package/dist/navigation/root/navigation-root.js +11 -19
  108. package/dist/notifications/notification/notification.d.ts +16 -22
  109. package/dist/notifications/notification/notification.js +21 -27
  110. package/dist/notifications/root/notifications-root.d.ts +31 -39
  111. package/dist/notifications/root/notifications-root.js +41 -52
  112. package/dist/number-input/root/number-input-root.d.ts +31 -33
  113. package/dist/number-input/root/number-input-root.js +37 -39
  114. package/dist/option/root/option-root.d.ts +73 -74
  115. package/dist/option/root/option-root.js +62 -64
  116. package/dist/outline/root/outline-root.d.ts +38 -44
  117. package/dist/outline/root/outline-root.js +66 -82
  118. package/dist/pagination/root/pagination-root.d.ts +87 -92
  119. package/dist/pagination/root/pagination-root.js +116 -123
  120. package/dist/pagination/size/pagination-size.d.ts +31 -50
  121. package/dist/pagination/size/pagination-size.js +35 -55
  122. package/dist/pagination/summary/pagination-summary.d.ts +12 -12
  123. package/dist/pagination/summary/pagination-summary.js +18 -18
  124. package/dist/pin-input/root/pin-input-root.d.ts +53 -70
  125. package/dist/pin-input/root/pin-input-root.js +74 -94
  126. package/dist/popover/root/popover-root.d.ts +15 -20
  127. package/dist/popover/root/popover-root.js +19 -36
  128. package/dist/progress/root/progress-root.d.ts +12 -14
  129. package/dist/progress/root/progress-root.js +22 -25
  130. package/dist/qr-code/root/qr-code-root.d.ts +11 -11
  131. package/dist/qr-code/root/qr-code-root.js +12 -14
  132. package/dist/radio/group/radio-group.js +19 -26
  133. package/dist/radio/root/radio-root.d.ts +12 -13
  134. package/dist/radio/root/radio-root.js +12 -13
  135. package/dist/rating/root/rating-root.d.ts +25 -30
  136. package/dist/rating/root/rating-root.js +43 -50
  137. package/dist/scroll-area/root/scroll-area-root.d.ts +11 -14
  138. package/dist/scroll-area/root/scroll-area-root.js +39 -33
  139. package/dist/select/root/select-root.d.ts +7 -12
  140. package/dist/select/root/select-root.js +40 -55
  141. package/dist/slider/root/slider-root.d.ts +53 -60
  142. package/dist/slider/root/slider-root.js +74 -85
  143. package/dist/sparkline/root/sparkline-root.d.ts +43 -47
  144. package/dist/sparkline/root/sparkline-root.js +23 -32
  145. package/dist/speed-dial/item/speed-dial-item.d.ts +6 -6
  146. package/dist/speed-dial/item/speed-dial-item.js +9 -9
  147. package/dist/speed-dial/root/speed-dial-root.d.ts +21 -25
  148. package/dist/speed-dial/root/speed-dial-root.js +32 -40
  149. package/dist/splitter/root/splitter-root.d.ts +98 -111
  150. package/dist/splitter/root/splitter-root.js +96 -123
  151. package/dist/stat/root/stat-root.d.ts +2 -7
  152. package/dist/stat/root/stat-root.js +0 -5
  153. package/dist/steps/step/step.d.ts +19 -24
  154. package/dist/steps/step/step.js +27 -32
  155. package/dist/switch/root/switch-root.js +2 -2
  156. package/dist/table/column/table-column.d.ts +54 -60
  157. package/dist/table/column/table-column.js +95 -68
  158. package/dist/table/root/table-root.d.ts +5 -6
  159. package/dist/table/root/table-root.js +7 -10
  160. package/dist/tabs/panel/tab-panel.d.ts +21 -25
  161. package/dist/tabs/panel/tab-panel.js +25 -29
  162. package/dist/tabs/root/tabs-root.d.ts +25 -34
  163. package/dist/tabs/root/tabs-root.js +22 -28
  164. package/dist/tabs/tab/tab.d.ts +12 -12
  165. package/dist/tabs/tab/tab.js +19 -20
  166. package/dist/textarea/root/textarea-root.d.ts +36 -36
  167. package/dist/textarea/root/textarea-root.js +43 -47
  168. package/dist/time-input/root/time-input-root.d.ts +58 -68
  169. package/dist/time-input/root/time-input-root.js +62 -77
  170. package/dist/time-picker/root/time-picker-root.d.ts +73 -79
  171. package/dist/time-picker/root/time-picker-root.js +72 -84
  172. package/dist/timeline/item/timeline-item.d.ts +8 -11
  173. package/dist/timeline/item/timeline-item.js +8 -11
  174. package/dist/timeline/root/timeline-root.d.ts +5 -7
  175. package/dist/timeline/root/timeline-root.js +5 -7
  176. package/dist/toast/item/toast-item.d.ts +9 -27
  177. package/dist/toast/item/toast-item.js +19 -41
  178. package/dist/toast/root/toast-root.d.ts +10 -14
  179. package/dist/toast/root/toast-root.js +4 -5
  180. package/dist/toggle/group/toggle-group.d.ts +6 -15
  181. package/dist/toggle/group/toggle-group.js +21 -36
  182. package/dist/toggle/root/toggle-root.d.ts +26 -27
  183. package/dist/toggle/root/toggle-root.js +30 -31
  184. package/dist/toolbar/root/toolbar-root.d.ts +7 -10
  185. package/dist/toolbar/root/toolbar-root.js +10 -14
  186. package/dist/tooltip/root/tooltip-root.d.ts +52 -59
  187. package/dist/tooltip/root/tooltip-root.js +98 -93
  188. package/dist/tour/root/tour-root.d.ts +19 -23
  189. package/dist/tour/root/tour-root.js +31 -41
  190. package/dist/tour/stop/tour-stop.d.ts +14 -15
  191. package/dist/tour/stop/tour-stop.js +31 -34
  192. package/dist/tree/item/tree-item.d.ts +30 -35
  193. package/dist/tree/item/tree-item.js +43 -55
  194. package/dist/tree/root/tree-root.d.ts +7 -10
  195. package/dist/tree/root/tree-root.js +33 -42
  196. package/dist/trend/root/trend-root.d.ts +3 -5
  197. package/dist/trend/root/trend-root.js +5 -9
  198. package/dist/video/root/video-root.d.ts +100 -124
  199. package/dist/video/root/video-root.js +142 -176
  200. package/dist/video-playlist/item/video-playlist-item.d.ts +16 -16
  201. package/dist/video-playlist/item/video-playlist-item.js +24 -24
  202. package/dist/video-playlist/menu/video-playlist-menu.d.ts +5 -8
  203. package/dist/video-playlist/menu/video-playlist-menu.js +20 -29
  204. package/dist/video-playlist/root/video-playlist-root.d.ts +16 -11
  205. package/dist/video-playlist/root/video-playlist-root.js +21 -25
  206. package/dist/virtual-list/root/virtual-list-root.d.ts +8 -12
  207. package/dist/virtual-list/root/virtual-list-root.js +11 -16
  208. package/jsx.d.ts +9797 -12561
  209. package/package.json +6 -6
  210. package/vue.d.ts +2684 -3228
@@ -44,11 +44,9 @@ const HIGHLIGHT = 'helix-filter-match';
44
44
  /**
45
45
  * Whether this environment can paint a highlight at all.
46
46
  *
47
- * Asked before the ranges are built rather than after. Walking every shown
48
- * option's text nodes to hand the result to an API that is not there is work
49
- * with no output, and it is not free: a server, a test environment and an older
50
- * browser all take that path, and the DOM implementations they use are not the
51
- * one this was measured against.
47
+ * Asked before the ranges are built rather than after: a server, a test
48
+ * environment and an older browser all take that path, and walking every shown
49
+ * option's text nodes for an API that is not there is work with no output.
52
50
  */
53
51
  const paintable = () => typeof CSS !== 'undefined' && 'highlights' in CSS && typeof Highlight !== 'undefined';
54
52
  const paintMatches = () => {
@@ -122,25 +120,14 @@ const rangeFor = (root, needle) => {
122
120
  * chooses, because a reader passing over four options on the way to the fifth
123
121
  * did not mean to pick all five.
124
122
  *
125
- * **This does not use `KeyboardNavigationController`, and the reason given here
126
- * was the wrong one.** It said the controller moves and chooses in one act by
127
- * design, so half of this component needed that and half the opposite. The
128
- * controller does not work that way: it takes `origin: 'focus' | 'selection'`,
129
- * and `focus` exists precisely so a reader can arrow without choosing, which is
130
- * what `Tabs` uses in `manual` mode. `focusItem()` and `select()` are separate
131
- * members of `NavigableItem`. The incompatibility named above is one the
132
- * controller had already solved.
133
- *
134
- * The real one is where focus lives. `NavigableItem.focusItem()` moves DOM
135
- * focus to the item, because the controller's model is a roving tab stop. A
136
- * filterable list cannot do that: focus has to stay in the field or typing
137
- * stops, so what moves is a virtual cursor and `aria-activedescendant` is what
138
- * says where it is. Those two disagree about the one thing neither can
139
- * abstract, and no flag reconciles them.
140
- *
141
- * Worth keeping the distinction sharp, because the old wording invites the
142
- * wrong fix: someone reading it would add `origin` to the controller, find it
143
- * already there, and conclude this component could adopt it.
123
+ * **This does not use `KeyboardNavigationController`, and the reason is where
124
+ * focus lives rather than whether moving chooses.** The controller already
125
+ * separates those, taking `origin: 'focus' | 'selection'`. What it cannot do is
126
+ * leave focus alone: `NavigableItem.focusItem()` moves DOM focus to the item,
127
+ * its model being a roving tab stop. A filterable list cannot have that, focus
128
+ * having to stay in the field or typing stops, so what moves is a virtual
129
+ * cursor and `aria-activedescendant` is what says where it is. No flag
130
+ * reconciles the two.
144
131
  *
145
132
  * **Filtering hides rather than removes.** An option the filter has hidden is
146
133
  * still the consumer's element in the consumer's order, so a filter that clears
@@ -201,15 +188,22 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
201
188
  this.defaultFilter = '';
202
189
  /** What the list is narrowed to, whoever set it. */
203
190
  this.filterState = undefined;
191
+ /**
192
+ * What is shown when a filter has hidden every option.
193
+ *
194
+ * From `MixinCollection`, which every collection in the library now shares,
195
+ * and re-declared here only to keep this list's own wording: "No matches" is
196
+ * about a filter, and the mixin's "Nothing to show" is about a list that was
197
+ * never given anything.
198
+ */
199
+ this.emptyText = 'No matches';
204
200
  /**
205
201
  * Whether the list renders a field for narrowing itself.
206
202
  *
207
203
  * The field lives here rather than in whatever frames the list, because the
208
204
  * filtering already does: `filter`, the empty state and the keyboard are all
209
- * this component's. A select that drew its own search box above a listbox
210
- * would be writing the same field again, and so would a combobox, and so
211
- * would a command palette, which is the fault the three-family split exists
212
- * to prevent.
205
+ * this component's. A select, a combobox and a command palette drawing their
206
+ * own search boxes would be three copies of one field.
213
207
  */
214
208
  this.filterable = false;
215
209
  /**
@@ -220,15 +214,6 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
220
214
  * Whether more than one option can be the answer.
221
215
  */
222
216
  this.multiple = false;
223
- /**
224
- * What is shown when a filter has hidden every option.
225
- *
226
- * From `MixinCollection`, which every collection in the library now shares,
227
- * and re-declared here only to keep this list's own wording: "No matches" is
228
- * about a filter, and the mixin's "Nothing to show" is about a list that was
229
- * never given anything.
230
- */
231
- this.emptyText = 'No matches';
232
217
  /**
233
218
  * Which option the keyboard is on, which is not what is chosen.
234
219
  */
@@ -247,10 +232,9 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
247
232
  *
248
233
  * Counting during render reads the hiding from the *previous* pass, because
249
234
  * hiding happens in `updated` against options that are the consumer's
250
- * elements rather than this component's markup. So a filter matching nothing
251
- * rendered the full list, hid every option, and left an empty box with no
252
- * message in it: the count was right one pass too late. Written to state
253
- * instead, which schedules the one further render that shows it.
235
+ * elements rather than this component's markup, so a filter matching nothing
236
+ * leaves an empty box with no message in it. Written to state instead, which
237
+ * schedules the one further render that shows it.
254
238
  */
255
239
  this.hasMatches = true;
256
240
  /**
@@ -264,9 +248,8 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
264
248
  */
265
249
  this.counted = false;
266
250
  /* Stated once, and the group never thinks about it again. See the
267
- controller: the cascade was four hand calls from four different
268
- methods, so which paths reached it differed by component and a fifth
269
- group would have got none at all. */
251
+ controller: cascading by hand from each method that changes the set
252
+ leaves the paths that reached it differing by component. */
270
253
  this.cascade = new DisabledCascadeController(this, {
271
254
  items: () => this.options,
272
255
  });
@@ -340,8 +323,7 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
340
323
  *
341
324
  * `helix-filter` and not `helix-change`, because nothing about the value
342
325
  * changes: the options are hidden rather than removed, and telling a consumer
343
- * their answer moved when it did not is worse than telling them nothing. It
344
- * told them nothing before this.
326
+ * their answer moved when it did not is worse than telling them nothing.
345
327
  */
346
328
  this.handleFilter = (event) => {
347
329
  // Stopped for the reason `Input.handleInput` gives: the inner control's own
@@ -388,15 +370,12 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
388
370
  arrows move the highlight and Enter takes it, which is what Enter already
389
371
  does by bubbling to the host handler.
390
372
 
391
- **What was here instead moved focus to `[part="items"]` and re-dispatched
392
- the key.** Both halves of that were wrong, and measured on a bare
393
- `<helix-listbox filterable>` one Down produced: two `helix-change` events,
394
- `a` then `b`, and a highlight on the *second* option. Focus on the list
395
- meant moving chose, so a single press committed a value the reader was
396
- arrowing past; and the original press went on bubbling to the same host
397
- handler the re-dispatch had already reached, so every key was handled
398
- twice and the list advanced two rows for one press. In `Command` that
399
- reads as a palette that navigates away the moment a reader presses Down.
373
+ **Never move focus to `[part="items"]` and re-dispatch the key instead.**
374
+ Both halves fail: focus on the list means moving chooses, so a single
375
+ press commits a value the reader was arrowing past, and the original
376
+ press goes on bubbling to the same host handler the re-dispatch already
377
+ reached. Measured on a bare `<helix-listbox filterable>`, one Down
378
+ produced two `helix-change` events and a highlight two rows down.
400
379
 
401
380
  `stopPropagation` is what closes the second half: the field handled the
402
381
  key, so it must not arrive at the host as well. The move keys are the
@@ -412,8 +391,6 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
412
391
  * the list is still the consumer's own elements in their own order. Reading
413
392
  * gives what is being filtered on; writing declares it theirs, and
414
393
  * `default-filter` seeds one the list owns.
415
- *
416
- *
417
394
  */
418
395
  get filter() {
419
396
  return this.filterState ?? this.defaultFilter;
@@ -464,17 +441,11 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
464
441
  /**
465
442
  * Walks the tree, and walks *through* a slot when it meets one.
466
443
  *
467
- * Descendants rather than children, because a group puts a level between the
468
- * list and its options and the keyboard has to move through a grouped list
469
- * exactly as it moves through a flat one.
470
- *
471
444
  * Through a slot because that is how this gets composed. `Select` renders a
472
445
  * listbox in its own shadow root with a slot inside it, so the options are
473
446
  * the *select's* light DOM and the listbox's only child is the slot: a plain
474
447
  * `querySelectorAll` finds the slot and no options at all, and the whole
475
- * component comes up empty. Found the first time the two were put together,
476
- * which is the argument for building the pieces separately rather than
477
- * discovering it inside a finished Select.
448
+ * component comes up empty.
478
449
  */
479
450
  collect(nodes) {
480
451
  const found = [];
@@ -535,10 +506,9 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
535
506
  if (joined === this.value) {
536
507
  return;
537
508
  }
538
- /* Through the mixin rather than hand-rolled. It did the same two steps in
539
- the same order, and doing them here meant the control could not be
540
- owned: writing `value` is how a consumer says it is theirs, so a listbox
541
- writing its own would take ownership away from them on every change. */
509
+ /* Through the mixin rather than hand-rolled: writing `value` is how a
510
+ consumer says it is theirs, so a listbox writing its own would take
511
+ ownership away from them on every change. */
542
512
  this.requestValue(joined);
543
513
  }
544
514
  /**
@@ -546,9 +516,8 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
546
516
  *
547
517
  * A combobox keeps focus in its text field, because that is the whole of what
548
518
  * a combobox is, and still has to move this list with the arrows. Without
549
- * this it would have to reimplement the moving, the wrapping, the skipping of
550
- * disabled options and the seeding from the current value, and the second
551
- * copy would drift from this one the first time either changed.
519
+ * this it would reimplement the moving, the wrapping, the skipping of
520
+ * disabled options and the seeding from the current value.
552
521
  *
553
522
  * Public and taking the event rather than a direction, so a caller forwards
554
523
  * what it received and this decides what the key means. `preventDefault` is
@@ -577,8 +546,8 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
577
546
  * A method rather than something this does on its own, because a listbox
578
547
  * opening on its answer usually wants it merely *visible* and the browser
579
548
  * already manages that: centering is what a caller wants when the list is being
580
- * read as a dial rather than scanned as a list, which is what Time Picker's wheels
581
- * does with three of these.
549
+ * read as a dial rather than scanned as a list, which is what Time Picker's
550
+ * wheels do with three of these.
582
551
  *
583
552
  * A delta from the two boxes rather than `offsetTop`. An option is a slotted
584
553
  * light DOM child, so its `offsetParent` is whatever is positioned above it,
@@ -693,30 +662,6 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
693
662
  hostAria(this.internals, { label: this.label, role: 'listbox' });
694
663
  this.internals.ariaMultiSelectable = this.multiple ? 'true' : null;
695
664
  }
696
- /*
697
- * Points the focused list at the option the keyboard is on.
698
- *
699
- * **The gap this closes.** Focus stays on `[part="items"]` while a highlight
700
- * moves between options, which is the half of the ARIA listbox pattern we
701
- * had. The other half is saying *which* option, and nothing did: a screen
702
- * reader heard the list and its selection and never heard the item being
703
- * moved over. Neither `aria-activedescendant` nor a roving `tabindex` was
704
- * wired, and the pattern requires one of the two.
705
- *
706
- * **An element reference and not an id, because an IDREF cannot do this.**
707
- * The options are the consumer's light DOM and the element that takes focus
708
- * is in this shadow root, so `aria-activedescendant="whatever"` resolves
709
- * against the shadow tree, finds nothing, and is dropped in silence. Element
710
- * references are the platform's answer to exactly that, and they resolve
711
- * outward: a shadow-tree element may point at a light-DOM one, which is the
712
- * direction this needs.
713
- *
714
- * Feature-tested rather than assumed, for the reason `hostAria` gives:
715
- * assigning to a property an engine does not implement is a no-op rather than
716
- * an error, so the guard is what makes the absence visible to anyone reading
717
- * this. Where it is missing the list still works and still announces its
718
- * selection; what is lost is the running commentary as the keys move.
719
- */
720
665
  /**
721
666
  * Where the reader is, and failing that where the answer is.
722
667
  *
@@ -726,16 +671,28 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
726
671
  * Seeding from the value means the first press moves from what the reader can
727
672
  * see is selected.
728
673
  *
729
- * **The keyboard knew this and the announcement did not.** `linkActive` asked
730
- * for `activeValue` alone, so a list that had been given a value and not yet
731
- * arrowed through pointed `aria-activedescendant` at nothing: a screen reader
732
- * was told the list and its selection, and nothing about where the cursor
733
- * was, until a key moved it. Both read this now, so the thing announced and
734
- * the thing the arrows move from cannot disagree.
674
+ * Read by `linkActive` as well as by the keys, so the option announced and
675
+ * the option the arrows move from cannot disagree: asking for `activeValue`
676
+ * alone points `aria-activedescendant` at nothing until a key has moved it.
735
677
  */
736
678
  get currentValue() {
737
679
  return this.activeValue || (this.value.split(',')[0] ?? '');
738
680
  }
681
+ /*
682
+ * Points the focused list at the option the keyboard is on.
683
+ *
684
+ * **An element reference and not an id, because an IDREF cannot do this.**
685
+ * The options are the consumer's light DOM and the element that takes focus
686
+ * is in this shadow root, so `aria-activedescendant="whatever"` resolves
687
+ * against the shadow tree, finds nothing, and is dropped in silence. Element
688
+ * references resolve outward: a shadow-tree element may point at a light-DOM
689
+ * one, which is the direction this needs.
690
+ *
691
+ * Feature-tested rather than assumed, for the reason `hostAria` gives:
692
+ * assigning to a property an engine does not implement is a no-op rather than
693
+ * an error. Where it is missing the list still works and still announces its
694
+ * selection; what is lost is the running commentary as the keys move.
695
+ */
739
696
  linkActive() {
740
697
  /* Whichever element the reader is actually in. `aria-activedescendant` is
741
698
  read from the focused element, so pointing the list at the active option
@@ -833,9 +790,9 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
833
790
  id="items"
834
791
  aria-busy=${this.loading ? 'true' : nothing}
835
792
  ${
836
- /* `isDisabled`, matching the filter input above it. The two lines
837
- disagreed, so a listbox in a disabled fieldset kept its tab stop
838
- and took arrow keys while its own filter was correctly out. */
793
+ /* `isDisabled`, matching the filter input above it: on `disabled`
794
+ alone, a listbox in a disabled fieldset keeps its tab stop and
795
+ takes arrow keys while its own filter is correctly out. */
839
796
  ''}
840
797
  tabindex=${this.isDisabled ? -1 : 0}
841
798
  >
@@ -863,6 +820,9 @@ __decorate([
863
820
  __decorate([
864
821
  property({ attribute: 'default-filter' })
865
822
  ], HelixHeadlessListbox.prototype, "defaultFilter", void 0);
823
+ __decorate([
824
+ property({ attribute: 'empty-text' })
825
+ ], HelixHeadlessListbox.prototype, "emptyText", void 0);
866
826
  __decorate([
867
827
  property({ attribute: 'filterable', reflect: true, type: Boolean })
868
828
  ], HelixHeadlessListbox.prototype, "filterable", void 0);
@@ -872,9 +832,6 @@ __decorate([
872
832
  __decorate([
873
833
  property({ reflect: true, type: Boolean })
874
834
  ], HelixHeadlessListbox.prototype, "multiple", void 0);
875
- __decorate([
876
- property({ attribute: 'empty-text' })
877
- ], HelixHeadlessListbox.prototype, "emptyText", void 0);
878
835
  __decorate([
879
836
  state()
880
837
  ], HelixHeadlessListbox.prototype, "activeValue", void 0);
@@ -47,8 +47,8 @@ declare const HelixHeadlessMegamenu_base: (new (...args: any[]) => import("@fusi
47
47
  * anything managing it. Escape closes and puts focus back, because Escape is
48
48
  * the one key that has to return the reader to where they were.
49
49
  *
50
- * The properties below are written by this component and read by whoever draws
51
- * it. A consumer sets none of them: they carry a number this element computed
50
+ * The custom property below is written by this component and read by whoever
51
+ * draws it. A consumer never sets it: it carries a number this element computed
52
52
  * and CSS cannot, which is what lets a stylesheet on this tier draw the thing
53
53
  * without measuring anything itself.
54
54
  *
@@ -71,10 +71,37 @@ declare const HelixHeadlessMegamenu_base: (new (...args: any[]) => import("@fusi
71
71
  * @cssproperty --megamenu-top - Where the panel starts, measured from the bar above it.
72
72
  */
73
73
  export declare class HelixHeadlessMegamenu extends HelixHeadlessMegamenu_base {
74
+ /**
75
+ * The width, in pixels, under which a card becomes a band. Zero never does.
76
+ *
77
+ * A card is a panel hung off one word, and there is no room to hang anything
78
+ * off a word on a phone: the columns do not shrink past
79
+ * `--megamenu-column-size`, so four of them stay four of them and the panel
80
+ * runs off the side of the screen rather than reflowing. Below this it takes
81
+ * the whole width and its columns stack, which is the only arrangement that
82
+ * fits.
83
+ *
84
+ * **In script rather than a media query, and only once.** Whether the panel is
85
+ * a band is not a question of paint: a band is not placed against the trigger,
86
+ * so the controller has to be told not to measure, and that decision is here
87
+ * whatever the stylesheet says. Writing the width in both places would be one
88
+ * number in two languages, drifting the first time either moved. This sets
89
+ * `data-narrow` on the host and the stylesheet arranges itself off that.
90
+ */
91
+ fullBelow: number;
74
92
  /**
75
93
  * The section's name, which names the default trigger and the panel.
76
94
  */
77
95
  label: string;
96
+ /**
97
+ * Whether hovering the trigger opens the panel, or only a press does.
98
+ *
99
+ * A press always works. This says whether a mouse also opens it by resting on
100
+ * the trigger, and it is the default because that is what a navigation bar
101
+ * does. `click` is for a trigger that does something else as well, or a panel
102
+ * heavy enough that opening it on the way past is rude.
103
+ */
104
+ openOn: 'click' | 'hover';
78
105
  /**
79
106
  * Whether the panel is a card under the trigger or a band across the page.
80
107
  *
@@ -91,33 +118,6 @@ export declare class HelixHeadlessMegamenu extends HelixHeadlessMegamenu_base {
91
118
  * the dismissal, is exactly what such a band needs.
92
119
  */
93
120
  variant?: 'full';
94
- /**
95
- * Whether hovering the trigger opens the panel, or only a press does.
96
- *
97
- * A press always works. This says whether a mouse also opens it by resting on
98
- * the trigger, and it is the default because that is what a navigation bar
99
- * does. `click` is for a trigger that does something else as well, or a panel
100
- * heavy enough that opening it on the way past is rude.
101
- */
102
- openOn: 'click' | 'hover';
103
- /**
104
- * The width, in pixels, under which a card becomes a band. Zero never does.
105
- *
106
- * A card is a panel hung off one word, and there is no room to hang anything
107
- * off a word on a phone: the columns do not shrink past
108
- * `--megamenu-column-size`, so four of them stay four of them and the panel
109
- * runs off the side of the screen rather than reflowing. Below this it takes
110
- * the whole width and its columns stack, which is the only arrangement that
111
- * fits.
112
- *
113
- * **In script rather than a media query, and only once.** Whether the panel is
114
- * a band is not a question of paint: a band is not placed against the trigger,
115
- * so the controller has to be told not to measure, and that decision is here
116
- * whatever the stylesheet says. Writing the width in both places would be one
117
- * number in two languages, drifting the first time either moved. This sets
118
- * `data-narrow` on the host and the stylesheet arranges itself off that.
119
- */
120
- fullBelow: number;
121
121
  private narrow;
122
122
  private query;
123
123
  /** A band, whether because it was asked for or because there is no room. */
@@ -194,9 +194,8 @@ export declare class HelixHeadlessMegamenu extends HelixHeadlessMegamenu_base {
194
194
  * band still sitting at 57.
195
195
  *
196
196
  * The card has none of this, because the floating controller follows its
197
- * anchor. Turning that off for the band, which has nothing to be placed
198
- * against, took the scroll handling with it, and this is the half that was
199
- * still wanted.
197
+ * anchor. The band turns that controller off, having nothing to be placed
198
+ * against, and this is the half of it still wanted.
200
199
  *
201
200
  * A sticky bar does not move, so re-measuring returns the same number and the
202
201
  * band stays where it is. `requestOpen` rather than `close`, because closing
@@ -211,9 +210,9 @@ export declare class HelixHeadlessMegamenu extends HelixHeadlessMegamenu_base {
211
210
  /**
212
211
  * Shuts the panel when a reader takes one of the destinations in it.
213
212
  *
214
- * Nothing closed it before, so a click navigated and left the panel hanging
215
- * over wherever the reader landed. With a router that navigates in place, and
216
- * this is one, that is a panel covering the page it was asked to go to.
213
+ * Without this a click navigates and leaves the panel hanging over wherever
214
+ * the reader landed. With a router that navigates in place, that is a panel
215
+ * covering the page it was asked to go to.
217
216
  *
218
217
  * **A modified click leaves it open**, which is the same position this
219
218
  * element already takes on middle clicks and the status bar: `cmd` on a link
@@ -65,8 +65,8 @@ let showing;
65
65
  * anything managing it. Escape closes and puts focus back, because Escape is
66
66
  * the one key that has to return the reader to where they were.
67
67
  *
68
- * The properties below are written by this component and read by whoever draws
69
- * it. A consumer sets none of them: they carry a number this element computed
68
+ * The custom property below is written by this component and read by whoever
69
+ * draws it. A consumer never sets it: it carries a number this element computed
70
70
  * and CSS cannot, which is what lets a stylesheet on this tier draw the thing
71
71
  * without measuring anything itself.
72
72
  *
@@ -91,23 +91,6 @@ let showing;
91
91
  let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerOpen(HelixElement) {
92
92
  constructor() {
93
93
  super(...arguments);
94
- /**
95
- * The section's name, which names the default trigger and the panel.
96
- */
97
- this.label = '';
98
- /**
99
- * Whether hovering the trigger opens the panel, or only a press does.
100
- *
101
- * A press always works. This says whether a mouse also opens it by resting on
102
- * the trigger, and it is the default because that is what a navigation bar
103
- * does. `click` is for a trigger that does something else as well, or a panel
104
- * heavy enough that opening it on the way past is rude.
105
- */
106
- /* Read, never written back. It has a default, and a reflected default puts an
107
- attribute on the host that the consumer did not write, which the server
108
- then ships: the SSR spec for this element checks exactly that and caught
109
- it. Nothing selects on it, so there is nothing to reflect for. */
110
- this.openOn = 'hover';
111
94
  /**
112
95
  * The width, in pixels, under which a card becomes a band. Zero never does.
113
96
  *
@@ -126,6 +109,23 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
126
109
  * `data-narrow` on the host and the stylesheet arranges itself off that.
127
110
  */
128
111
  this.fullBelow = 640;
112
+ /**
113
+ * The section's name, which names the default trigger and the panel.
114
+ */
115
+ this.label = '';
116
+ /**
117
+ * Whether hovering the trigger opens the panel, or only a press does.
118
+ *
119
+ * A press always works. This says whether a mouse also opens it by resting on
120
+ * the trigger, and it is the default because that is what a navigation bar
121
+ * does. `click` is for a trigger that does something else as well, or a panel
122
+ * heavy enough that opening it on the way past is rude.
123
+ */
124
+ /* Read, never written back. It has a default, and a reflected default puts an
125
+ attribute on the host that the consumer did not write, which the server
126
+ then ships: the SSR spec for this element checks exactly that. Nothing
127
+ selects on it, so there is nothing to reflect for. */
128
+ this.openOn = 'hover';
129
129
  this.narrow = false;
130
130
  this.handleWidth = (event) => {
131
131
  this.narrow = event.matches;
@@ -152,9 +152,8 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
152
152
  * band still sitting at 57.
153
153
  *
154
154
  * The card has none of this, because the floating controller follows its
155
- * anchor. Turning that off for the band, which has nothing to be placed
156
- * against, took the scroll handling with it, and this is the half that was
157
- * still wanted.
155
+ * anchor. The band turns that controller off, having nothing to be placed
156
+ * against, and this is the half of it still wanted.
158
157
  *
159
158
  * A sticky bar does not move, so re-measuring returns the same number and the
160
159
  * band stays where it is. `requestOpen` rather than `close`, because closing
@@ -175,7 +174,7 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
175
174
  this.pointer = event.pointerType;
176
175
  };
177
176
  this.handlePointerEnter = (event) => {
178
- /* Mouse only. A touch screen synthesises an enter on tap, and acting on it
177
+ /* Mouse only. A touch screen synthesizes an enter on tap, and acting on it
179
178
  would open the panel and then let the click that follows close it again,
180
179
  so a tap would do nothing at all. */
181
180
  if (this.openOn !== 'hover' || event.pointerType !== 'mouse') {
@@ -216,9 +215,9 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
216
215
  /**
217
216
  * Shuts the panel when a reader takes one of the destinations in it.
218
217
  *
219
- * Nothing closed it before, so a click navigated and left the panel hanging
220
- * over wherever the reader landed. With a router that navigates in place, and
221
- * this is one, that is a panel covering the page it was asked to go to.
218
+ * Without this a click navigates and leaves the panel hanging over wherever
219
+ * the reader landed. With a router that navigates in place, that is a panel
220
+ * covering the page it was asked to go to.
222
221
  *
223
222
  * **A modified click leaves it open**, which is the same position this
224
223
  * element already takes on middle clicks and the status bar: `cmd` on a link
@@ -411,29 +410,25 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
411
410
  /* A slotted trigger is told the disclosure state, by property.
412
411
 
413
412
  `render` writes `aria-expanded` onto the button it renders itself, and a
414
- consumer who takes the documented `trigger` slot replaces that button: the
415
- panel opened and closed and nothing announced it, so a reader was told the
416
- control was a plain button forever. Silent, because the slot works in
417
- every other respect and the failure is only audible.
413
+ consumer who takes the documented `trigger` slot replaces that button, so
414
+ without this the panel opens and closes and nothing announces it. Silent,
415
+ because the slot works in every other respect and the failure is only
416
+ audible.
418
417
 
419
- **It was `setAttribute` and that really was a hydration mismatch**, which
420
- is what `Popover`'s class note has said since the day it was written.
421
- *Measured 28 Aug 2026 against React 19.2.8*, four families server-rendered
422
- and hydrated with the definitions loaded either side of `hydrateRoot`:
423
- this one and `SpeedDial` produced "some attributes of the server rendered
424
- HTML didn't match the client properties. This won't be patched up" in both
425
- orders, and `Popover` and `Menu`, which write nothing, were clean in both.
426
- React answers a mismatch by discarding the server's HTML for that subtree
427
- and rebuilding it, which is the expensive half rather than the console
428
- line.
418
+ **Never `setAttribute` here: that is a hydration mismatch.** Measured
419
+ against React 19.2.8, four families server-rendered and hydrated with the
420
+ definitions loaded either side of `hydrateRoot`: this one and `SpeedDial`
421
+ produced "some attributes of the server rendered HTML didn't match the
422
+ client properties. This won't be patched up" in both orders, and
423
+ `Popover` and `Menu`, which write nothing, were clean in both. React
424
+ answers a mismatch by discarding the server's HTML for that subtree and
425
+ rebuilding it, which is the expensive half rather than the console line.
429
426
 
430
- **The property is the way through, and it is this library's own answer
431
- already.** `Menu` tells its items by property for exactly this reason and
432
- says so. React diffs the attributes it rendered and never looks at a
433
- property, so `expanded` travels to the trigger, lands as `aria-expanded`
434
- inside that element's shadow root, and the consumer's markup is left as
435
- they served it. Both notes get what they were protecting: nothing is
436
- written onto their element, and the reader is still told.
427
+ **The property is the way through**, as `Menu` already does for its items.
428
+ React diffs the attributes it rendered and never looks at a property, so
429
+ `expanded` travels to the trigger, lands as `aria-expanded` inside that
430
+ element's shadow root, and the consumer's markup is left as they served
431
+ it.
437
432
 
438
433
  **What it cannot reach is a trigger that is not a Helix element.** A plain
439
434
  `<button slot="trigger">` has no shadow root to hide the state in and no
@@ -442,13 +437,13 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
442
437
  than written to, on the same argument `Tooltip` makes about a link it
443
438
  will not write for the consumer.
444
439
 
445
- **`aria-controls` is not written here and cannot be.** It named `panel`,
446
- an id inside this shadow root, and an IDREF resolves only within the tree
447
- that holds it: from a light-DOM trigger looking in, the reference was to
448
- nothing. Measured, `document.querySelector('#panel')` came back `null`
449
- while the same id resolved inside `megamenu.shadowRoot`. An inert
440
+ **`aria-controls` is not written here and cannot be.** It would name
441
+ `panel`, an id inside this shadow root, and an IDREF resolves only within
442
+ the tree that holds it: from a light-DOM trigger looking in, the reference
443
+ is to nothing. Measured, `document.querySelector('#panel')` came back
444
+ `null` while the same id resolved inside `megamenu.shadowRoot`. An inert
450
445
  `aria-controls` is worse than none, an audit tool reporting it satisfied
451
- and a screen reader following it nowhere. See #322. */
446
+ and a screen reader following it nowhere. */
452
447
  this.tellTrigger();
453
448
  if (this.open) {
454
449
  /* Before showing this one, so the two are never both up for a frame. The
@@ -505,18 +500,18 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
505
500
  </div>`;
506
501
  }
507
502
  };
503
+ __decorate([
504
+ property({ attribute: 'full-below', type: Number })
505
+ ], HelixHeadlessMegamenu.prototype, "fullBelow", void 0);
508
506
  __decorate([
509
507
  property()
510
508
  ], HelixHeadlessMegamenu.prototype, "label", void 0);
511
- __decorate([
512
- property({ reflect: true })
513
- ], HelixHeadlessMegamenu.prototype, "variant", void 0);
514
509
  __decorate([
515
510
  property({ attribute: 'open-on' })
516
511
  ], HelixHeadlessMegamenu.prototype, "openOn", void 0);
517
512
  __decorate([
518
- property({ attribute: 'full-below', type: Number })
519
- ], HelixHeadlessMegamenu.prototype, "fullBelow", void 0);
513
+ property({ reflect: true })
514
+ ], HelixHeadlessMegamenu.prototype, "variant", void 0);
520
515
  __decorate([
521
516
  state()
522
517
  ], HelixHeadlessMegamenu.prototype, "narrow", void 0);