@fusion.dev/helix-headless 0.2.0 → 0.3.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 (127) hide show
  1. package/custom-elements.json +234 -234
  2. package/dist/accordion/item/accordion-item.js +0 -108
  3. package/dist/accordion/root/accordion-root.js +0 -33
  4. package/dist/announcement/root/announcement-root.js +1 -76
  5. package/dist/app-header/root/app-header-root.js +0 -84
  6. package/dist/avatar/group/avatar-group.js +0 -77
  7. package/dist/avatar/root/avatar-root.js +0 -72
  8. package/dist/badge/root/badge-root.js +0 -67
  9. package/dist/bar-chart/root/bar-chart-root.js +3 -263
  10. package/dist/blockquote/root/blockquote-root.js +0 -46
  11. package/dist/breadcrumbs/breadcrumb/breadcrumb.js +0 -72
  12. package/dist/breadcrumbs/root/breadcrumbs-root.js +0 -52
  13. package/dist/button/root/button-root.js +0 -215
  14. package/dist/button-group/root/button-group-root.js +0 -30
  15. package/dist/callout/root/callout-root.js +0 -52
  16. package/dist/card/root/card-root.js +0 -41
  17. package/dist/carousel/root/carousel-root.d.ts +2 -2
  18. package/dist/carousel/root/carousel-root.js +2 -362
  19. package/dist/chat/composer/chat-composer.js +0 -131
  20. package/dist/chat/message/chat-message.js +0 -137
  21. package/dist/chat/root/chat-root.js +0 -193
  22. package/dist/checkbox/root/checkbox-root.js +0 -113
  23. package/dist/checkbox-group/root/checkbox-group-root.js +1 -157
  24. package/dist/code/root/code-root.js +0 -26
  25. package/dist/code-block/root/code-block-root.js +0 -100
  26. package/dist/color-input/root/color-input-root.js +0 -217
  27. package/dist/color-picker/color.js +0 -69
  28. package/dist/color-picker/root/color-picker-root.js +0 -272
  29. package/dist/combobox/root/combobox-root.js +0 -428
  30. package/dist/command/root/command-root.js +0 -177
  31. package/dist/copy-button/root/copy-button-root.js +0 -134
  32. package/dist/data-table/root/data-table-root.js +0 -336
  33. package/dist/data-table/root/order.js +0 -100
  34. package/dist/date-input/root/date-input-root.js +1 -398
  35. package/dist/date-picker/root/date-picker-root.js +0 -358
  36. package/dist/dialog/root/dialog-root.js +0 -189
  37. package/dist/divider/root/divider-root.js +0 -71
  38. package/dist/donut-chart/root/donut-chart-root.js +0 -214
  39. package/dist/drawer/root/drawer-root.js +0 -116
  40. package/dist/drilldown/item/drilldown-item.js +0 -82
  41. package/dist/drilldown/root/drilldown-root.js +0 -91
  42. package/dist/element-events.generated.js +0 -5
  43. package/dist/empty/root/empty-root.js +0 -35
  44. package/dist/file-input/root/file-input-root.js +0 -232
  45. package/dist/format-date/root/format-date-root.js +0 -95
  46. package/dist/heatmap/root/buckets.js +0 -57
  47. package/dist/heatmap/root/heatmap-root.js +0 -313
  48. package/dist/icon/root/icon-root.js +0 -48
  49. package/dist/icon-button/root/icon-button-root.js +0 -30
  50. package/dist/image/root/image-root.js +0 -174
  51. package/dist/index.js +0 -9
  52. package/dist/input/root/input-root.js +0 -228
  53. package/dist/kbd/root/kbd-root.js +0 -117
  54. package/dist/legend/root/legend-root.js +0 -73
  55. package/dist/line-chart/root/line-chart-root.js +0 -172
  56. package/dist/list/item/list-item.js +0 -92
  57. package/dist/list/root/list-root.js +0 -26
  58. package/dist/listbox/root/listbox-root.js +2 -429
  59. package/dist/loader/root/loader-root.js +0 -40
  60. package/dist/megamenu/root/megamenu-root.js +0 -284
  61. package/dist/menu/item/menu-item.js +1 -221
  62. package/dist/menu/root/menu-root.js +1 -171
  63. package/dist/meter/root/meter-root.js +1 -152
  64. package/dist/navigation/root/navigation-root.js +0 -56
  65. package/dist/notifications/notification/notification.js +0 -159
  66. package/dist/notifications/root/notifications-root.js +0 -148
  67. package/dist/number-input/root/number-input-root.js +1 -263
  68. package/dist/option/group/option-group.js +0 -26
  69. package/dist/option/root/option-root.js +0 -157
  70. package/dist/outline/root/outline-root.js +0 -292
  71. package/dist/pagination/root/pagination-root.js +0 -361
  72. package/dist/pagination/size/pagination-size.js +0 -104
  73. package/dist/pagination/summary/pagination-summary.js +0 -90
  74. package/dist/pin-input/root/pin-input-root.js +0 -276
  75. package/dist/popover/root/popover-root.js +0 -103
  76. package/dist/progress/root/progress-root.js +1 -133
  77. package/dist/qr-code/root/qr-code-root.js +0 -96
  78. package/dist/quote/root/quote-root.js +0 -25
  79. package/dist/radio/group/radio-group.js +0 -81
  80. package/dist/radio/root/radio-root.js +0 -85
  81. package/dist/rating/root/rating-root.js +0 -220
  82. package/dist/scroll-area/root/scroll-area-root.js +0 -100
  83. package/dist/select/root/select-root.js +2 -218
  84. package/dist/skeleton/root/skeleton-root.js +0 -20
  85. package/dist/slider/root/slider-root.js +1 -267
  86. package/dist/sparkline/root/sparkline-root.js +3 -164
  87. package/dist/speed-dial/item/speed-dial-item.js +0 -52
  88. package/dist/speed-dial/root/speed-dial-root.js +1 -116
  89. package/dist/splitter/root/splitter-root.js +0 -489
  90. package/dist/stat/group/stat-group.js +0 -18
  91. package/dist/stat/root/stat-root.js +0 -23
  92. package/dist/steps/root/steps-root.js +0 -62
  93. package/dist/steps/step/step.js +0 -131
  94. package/dist/switch/root/switch-root.js +0 -80
  95. package/dist/table/cell/table-cell.js +0 -23
  96. package/dist/table/column/table-column.js +0 -158
  97. package/dist/table/root/table-root.js +0 -82
  98. package/dist/table/row/table-row.js +0 -21
  99. package/dist/tabs/panel/tab-panel.js +0 -49
  100. package/dist/tabs/root/tabs-root.js +0 -98
  101. package/dist/tabs/tab/tab.js +0 -59
  102. package/dist/tag/root/tag-root.js +0 -31
  103. package/dist/textarea/root/textarea-root.js +0 -162
  104. package/dist/time-input/root/time-input-root.js +0 -283
  105. package/dist/time-picker/root/time-picker-root.js +0 -326
  106. package/dist/timeline/item/timeline-item.js +0 -66
  107. package/dist/timeline/root/timeline-root.js +0 -41
  108. package/dist/toast/item/toast-item.js +0 -165
  109. package/dist/toast/root/toast-root.js +0 -51
  110. package/dist/toggle/group/toggle-group.js +0 -150
  111. package/dist/toggle/root/toggle-root.js +0 -78
  112. package/dist/toolbar/root/toolbar-root.js +0 -116
  113. package/dist/tooltip/root/tooltip-root.js +0 -427
  114. package/dist/tour/root/tour-root.js +0 -130
  115. package/dist/tour/stop/tour-stop.js +1 -127
  116. package/dist/tree/item/tree-item.js +0 -198
  117. package/dist/tree/root/tree-root.js +0 -97
  118. package/dist/trend/root/trend-root.js +0 -53
  119. package/dist/video/root/video-root.d.ts +8 -12
  120. package/dist/video/root/video-root.js +5 -756
  121. package/dist/video-playlist/item/video-playlist-item.js +1 -90
  122. package/dist/video-playlist/menu/video-playlist-menu.js +1 -132
  123. package/dist/video-playlist/root/video-playlist-root.js +0 -113
  124. package/dist/virtual-list/root/virtual-list-root.js +0 -105
  125. package/jsx.d.ts +1408 -2576
  126. package/package.json +6 -6
  127. package/vue.d.ts +1 -1
@@ -7,49 +7,9 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  import { customElement, HelixElement, hostAria } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
- /**
11
- * @summary Says that something indeterminate is in progress.
12
- *
13
- * @description
14
- * Two concentric circles rather than one arc: the track is what makes the
15
- * moving part read as a proportion of a whole rather than as a shape drifting
16
- * on its own, and it costs one element.
17
- *
18
- * **The center is a slot and is empty by default.** A loader with a mark in the
19
- * middle is a brand's loader, and this library does not know whose. The slot is
20
- * the capability; the logo is the application's, put in once wherever the app
21
- * wraps it. Nothing is rendered when nothing is slotted, so an unbranded loader
22
- * is still two circles and no extra box.
23
- *
24
- * What is slotted does not turn. Only the arc moves, because a mark rotating
25
- * with it stops reading as a mark and becomes part of the animation, and a
26
- * wordmark upside down twice a second is worse than no wordmark.
27
- *
28
- * The semantics are on the host through `ElementInternals`, not on an inner
29
- * span. A `role` written into the shadow root announces correctly but leaves
30
- * the host itself anonymous to anything walking the light DOM, and a `role`
31
- * written onto the host as an attribute is one a React consumer's server markup
32
- * never had.
33
- *
34
- * @since 0.1.0
35
- * @status stable
36
- * @category feedback
37
- * @aliases Spinner, Busy, Activity
38
- * @documentation https://helix.fusion.dev/components/loader
39
- *
40
- * @slot - A mark to sit still at the center, usually a logo. Empty by default.
41
- *
42
- * @csspart indicator - The arc that turns.
43
- * @csspart mark - The region holding what was slotted at the center.
44
- * @csspart svg - The rotating circle pair.
45
- * @csspart track - The full circle behind the indicator.
46
- */
47
10
  let HelixHeadlessLoader = class HelixHeadlessLoader extends HelixElement {
48
11
  constructor() {
49
12
  super(...arguments);
50
- /**
51
- * Accessible name, announced while the work is outstanding.
52
- */
53
13
  this.label = 'Loading';
54
14
  }
55
15
  willUpdate(changed) {
@@ -8,123 +8,12 @@ var HelixHeadlessMegamenu_1;
8
8
  import { anchorElement, customElement, HelixElement, MixinControllerOpen, PopoverController, tellDisclosure, } from '@fusion.dev/helix-core';
9
9
  import { html, nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
11
- /**
12
- * The panel showing now, so opening one closes the last.
13
- *
14
- * A page has one of these open at a time: a nav bar whose Products panel is
15
- * still down while Solutions opens is a nav bar with two panels over the page.
16
- * Module scope rather than a coordinating parent, because there is nothing to
17
- * coordinate through: these sit in whatever container the consumer used, which
18
- * is usually `helix-navigation` and is not required to be. `Video` holds the
19
- * media session the same way and for the same reason.
20
- */
21
11
  let showing;
22
- /**
23
- * @summary A section of a navigation that opens a panel of destinations.
24
- *
25
- * @description
26
- * **A disclosure, not a menu, and that is the whole design.** `role="menu"` and
27
- * `menuitem` are for an application's commands: they take over the arrow keys,
28
- * announce "menu" to a reader who asked for navigation, and put every link
29
- * inside a role that is not a link. What this is, is a button that shows and
30
- * hides a region of the consumer's own anchors, which is the pattern the APG
31
- * gives for site navigation and the one that leaves a middle click, a modified
32
- * click and the status bar working. `Menu` remains right for a row of commands.
33
- *
34
- * **It renders no links**, for the reason `Navigation` gives: a router renders
35
- * its own anchor and cannot render one inside this shadow root. The panel is a
36
- * slot, and what goes in it is lists and headings the consumer wrote.
37
- *
38
- * **It opens on hover, and a press is always the fallback.** `open-on` says
39
- * which, and hover is the resting value because a nav bar is the one place a
40
- * reader expects to sweep along a row and see each section: making them click,
41
- * read, click again to compare two is the interaction hover exists to save.
42
- *
43
- * Hover is not the only way in, and the code is mostly about that. There is no
44
- * hover on a touch screen, so a tap opens the panel there whatever `open-on`
45
- * says, which is why the pointer type is checked rather than assumed. Focus
46
- * still opens nothing, for the same reason a tab through a nav bar should not
47
- * unfold it, but Enter on the trigger does, and a keyboard press arrives as a
48
- * click with no pointer behind it.
49
- *
50
- * A mouse click on a trigger the pointer is already sitting on is the case
51
- * worth naming: toggling there would close the panel and the pointer, still on
52
- * the trigger, would open it again, so the trigger would flicker and never
53
- * close. In hover mode the mouse's own clicks are left alone and hover governs
54
- * on its own. Escape and a click away still close it.
55
- *
56
- * The delays are intent, not decoration. Opening waits, so a pointer crossing
57
- * the bar on its way somewhere else opens nothing; closing waits longer, so the
58
- * diagonal from the trigger down into the panel does not pass through a gap and
59
- * shut it. Moving along a bar where a panel is already down opens the next one
60
- * at once, because at that point the reader is reading the bar, not crossing it.
61
- *
62
- * **Focus is not trapped and not moved.** A disclosure leaves the reader on the
63
- * trigger and the panel follows in the tab order, which is what the markup
64
- * already says, so Tab walks into the links and out the far side without
65
- * anything managing it. Escape closes and puts focus back, because Escape is
66
- * the one key that has to return the reader to where they were.
67
- *
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
- * and CSS cannot, which is what lets a stylesheet on this tier draw the thing
71
- * without measuring anything itself.
72
- *
73
- * @since 0.1.0
74
- * @status experimental
75
- * @category navigation
76
- * @aliases Mega menu, Flyout, Nav panel
77
- * @documentation https://helix.fusion.dev/components/megamenu
78
- *
79
- * @slot - The panel's contents: the consumer's own lists and headings.
80
- * @slot trigger - The control that opens it. Falls back to a button named by `label`.
81
- *
82
- * @event {HelixBeforeCloseEvent} helix-before-close - Fired before it closes, and cancelable.
83
- * @event {HelixBeforeOpenEvent} helix-before-open - Fired before it opens, and cancelable.
84
- * @event {Event} toggle - Says the state moved, carrying `newState` and `oldState`.
85
- *
86
- * @csspart surface - The floating panel, which is what opens.
87
- * @csspart trigger - The default trigger, where none was slotted.
88
- *
89
- * @cssproperty --megamenu-top - Where the panel starts, measured from the bar above it.
90
- */
91
12
  let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerOpen(HelixElement) {
92
13
  constructor() {
93
14
  super(...arguments);
94
- /**
95
- * The width, in pixels, under which a card becomes a band. Zero never does.
96
- *
97
- * A card is a panel hung off one word, and there is no room to hang anything
98
- * off a word on a phone: the columns do not shrink past
99
- * `--megamenu-column-size`, so four of them stay four of them and the panel
100
- * runs off the side of the screen rather than reflowing. Below this it takes
101
- * the whole width and its columns stack, which is the only arrangement that
102
- * fits.
103
- *
104
- * **In script rather than a media query, and only once.** Whether the panel is
105
- * a band is not a question of paint: a band is not placed against the trigger,
106
- * so the controller has to be told not to measure, and that decision is here
107
- * whatever the stylesheet says. Writing the width in both places would be one
108
- * number in two languages, drifting the first time either moved. This sets
109
- * `data-narrow` on the host and the stylesheet arranges itself off that.
110
- */
111
15
  this.fullBelow = 640;
112
- /**
113
- * The section's name, which names the default trigger and the panel.
114
- */
115
16
  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
17
  this.openOn = 'hover';
129
18
  this.narrow = false;
130
19
  this.handleWidth = (event) => {
@@ -132,34 +21,12 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
132
21
  };
133
22
  this.panel = new PopoverController(this, {
134
23
  anchor: () => this.trigger,
135
- /* The host, not the panel. A press on the trigger is a press on this
136
- element, and counting it as outside would close the panel on the way to
137
- the click that toggles it. `Menu` says the same about its own. */
138
24
  boundary: () => this,
139
25
  onDismiss: () => this.close(),
140
26
  place: () => !this.banded,
141
27
  placement: () => 'bottom-start',
142
28
  surface: () => this.renderRoot?.querySelector('[part="surface"]'),
143
29
  });
144
- /**
145
- * Keeps a band under its bar while the page moves, and closes it when the bar
146
- * goes.
147
- *
148
- * The band is fixed and its offset is measured once, when it opens. That is
149
- * fine until the page scrolls: a header that scrolls away leaves the band
150
- * pinned where the header used to be, open, in the middle of the page with
151
- * nothing above it. Measured at a scroll of 300: header bottom at -243 and the
152
- * band still sitting at 57.
153
- *
154
- * The card has none of this, because the floating controller follows its
155
- * anchor. The band turns that controller off, having nothing to be placed
156
- * against, and this is the half of it still wanted.
157
- *
158
- * A sticky bar does not move, so re-measuring returns the same number and the
159
- * band stays where it is. `requestOpen` rather than `close`, because closing
160
- * puts focus back on the trigger and a reader who has scrolled away should not
161
- * be dragged back up to it.
162
- */
163
30
  this.handleScroll = () => {
164
31
  if (!this.open || !this.banded) {
165
32
  return;
@@ -174,14 +41,9 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
174
41
  this.pointer = event.pointerType;
175
42
  };
176
43
  this.handlePointerEnter = (event) => {
177
- /* Mouse only. A touch screen synthesizes an enter on tap, and acting on it
178
- would open the panel and then let the click that follows close it again,
179
- so a tap would do nothing at all. */
180
44
  if (this.openOn !== 'hover' || event.pointerType !== 'mouse') {
181
45
  return;
182
46
  }
183
- /* No wait when a panel is already down: the bar is open, and the reader
184
- moving along it is choosing, not passing through. */
185
47
  this.schedule(showing && showing !== this ? 0 : HelixHeadlessMegamenu_1.OPEN_DELAY, () => {
186
48
  this.requestOpen(true);
187
49
  });
@@ -191,9 +53,6 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
191
53
  return;
192
54
  }
193
55
  this.schedule(HelixHeadlessMegamenu_1.CLOSE_DELAY, () => {
194
- /* Not `close()`: leaving with the pointer should not pull focus back to
195
- the trigger, which would scroll the page to a nav bar the reader has
196
- moved on from. Escape is the one that returns them. */
197
56
  this.requestOpen(false);
198
57
  });
199
58
  };
@@ -203,39 +62,10 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
203
62
  this.requestOpen(!this.open);
204
63
  return;
205
64
  }
206
- /* In hover mode the mouse's own clicks are hover's business. Toggling here
207
- would close a panel the pointer is still standing on, and the pointer
208
- would reopen it. A keyboard press arrives with no clicks behind it, and a
209
- tap arrives with a pointer that is not a mouse: both are the only way in
210
- for the people making them, so both toggle. */
211
65
  if (event.detail === 0 || (this.pointer !== undefined && this.pointer !== 'mouse')) {
212
66
  this.requestOpen(!this.open);
213
67
  }
214
68
  };
215
- /**
216
- * Shuts the panel when a reader takes one of the destinations in it.
217
- *
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.
221
- *
222
- * **A modified click leaves it open**, which is the same position this
223
- * element already takes on middle clicks and the status bar: `cmd` on a link
224
- * opens a tab behind this page rather than navigating it, so the reader is
225
- * still standing in the menu and collecting, and shutting it under them would
226
- * make the second choice cost a second open.
227
- *
228
- * `requestOpen(false)` rather than `close()`, for the reason `pointerleave`
229
- * gives: the trigger is not where the reader is going, and pulling focus back
230
- * to a nav bar they have just left is a scroll they did not ask for.
231
- *
232
- * **On the host rather than on the panel**, which is the same reason the
233
- * pointer handlers give and one more. A row is slotted light DOM and the
234
- * panel is in the shadow root, so a click only reaches the panel through the
235
- * flattened tree: real ones do, and a DOM that does not implement slot
236
- * propagation never delivers it, which makes every case here pass by never
237
- * running this at all. The host is the row's own ancestor in both.
238
- */
239
69
  this.handleContentClick = (event) => {
240
70
  if (!this.open || event.defaultPrevented || event.button !== 0) {
241
71
  return;
@@ -243,14 +73,8 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
243
73
  if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
244
74
  return;
245
75
  }
246
- /* Asking for the destination rather than for any click, so the headings,
247
- the gaps between rows and a reader's stray click on the panel's own
248
- padding leave it where it is. */
249
76
  const target = event.target;
250
77
  const chosen = target?.closest?.('a[href], button:not([disabled]), [role="menuitem"]');
251
- /* Inside this element and not the trigger. A press on the trigger is the
252
- trigger's to answer, and a shadow-rendered one retargets to the host, so
253
- `closest` there climbs past this element entirely. */
254
78
  if (!chosen || !this.contains(chosen) || chosen.closest('[slot="trigger"]')) {
255
79
  return;
256
80
  }
@@ -258,12 +82,9 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
258
82
  };
259
83
  }
260
84
  static { HelixHeadlessMegamenu_1 = this; }
261
- /** A band, whether because it was asked for or because there is no room. */
262
85
  get banded() {
263
86
  return this.variant === 'full' || this.narrow;
264
87
  }
265
- /* Rebuilt rather than kept, because the width is a property and a query
266
- cannot be edited once it is made. */
267
88
  watchWidth() {
268
89
  this.query?.removeEventListener('change', this.handleWidth);
269
90
  this.query = undefined;
@@ -275,69 +96,31 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
275
96
  this.query.addEventListener('change', this.handleWidth);
276
97
  this.handleWidth(this.query);
277
98
  }
278
- /** Tells a slotted trigger the disclosure state, by property. See `tellDisclosure`. */
279
99
  tellTrigger() {
280
100
  const slotted = this.slotted;
281
101
  if (slotted) {
282
102
  tellDisclosure(slotted, this.open);
283
103
  }
284
104
  }
285
- /** What the consumer slotted, if they slotted anything. */
286
105
  get slotted() {
287
106
  const slot = this.renderRoot?.querySelector('slot[name="trigger"]');
288
107
  return slot?.assignedElements({ flatten: false })[0];
289
108
  }
290
- /** Whatever the consumer slotted, or the button rendered in its place. */
291
109
  get trigger() {
292
110
  return anchorElement(this.renderRoot?.querySelector('slot[name="trigger"]'), this.renderRoot?.querySelector('[part="trigger"]') ?? this);
293
111
  }
294
- /** Shuts it and puts the reader back where they were. */
295
112
  close() {
296
113
  if (!this.requestOpen(false)) {
297
114
  return;
298
115
  }
299
116
  this.trigger.focus();
300
117
  }
301
- /**
302
- * Where a full-bleed band starts, which is the only number it needs.
303
- *
304
- * The placing pass is off for that appearance, so nothing else is measuring:
305
- * a band spans the viewport and has only one degree of freedom, how far down
306
- * it sits, and that is the bottom of the trigger in viewport coordinates
307
- * because the panel is fixed. A header that scrolls takes the band with it,
308
- * which is what a panel hanging off a header should do.
309
- *
310
- * Written as a custom property rather than an inline `top`, so the stylesheet
311
- * keeps the arrangement and this contributes the one measurement it cannot
312
- * make. Nothing to do for the card, which the controller places.
313
- */
314
118
  band() {
315
119
  if (!this.banded) {
316
120
  return;
317
121
  }
318
122
  this.style.setProperty('--megamenu-top', `${Math.round(this.bar())}px`);
319
123
  }
320
- /**
321
- * The bottom edge of the bar this hangs from.
322
- *
323
- * The trigger's own bottom is the obvious answer and is the wrong one: a
324
- * header is taller than the word inside it, so a band starting at the word
325
- * starts partway up the header, over its lower padding and its border.
326
- * Measured: trigger bottom 44, header bottom 57, thirteen pixels of header
327
- * with a band drawn across it.
328
- *
329
- * **The first ancestor as wide as the window, which is a fact and not a tag
330
- * name.** A band spans the viewport, so what it should hang from is whatever
331
- * else does: that is what makes something a bar. Asking for `helix-app-header`
332
- * by name would make this element know about that one, which is the coupling
333
- * `Menu` is careful not to have with `Drilldown`, and would leave a consumer
334
- * who wrote their own header with a band across it.
335
- *
336
- * `body` ends the walk. It is as wide as the window and is not a bar, and its
337
- * bottom is the foot of the page: without the guard a megamenu on a plain page
338
- * would put its band down there. Nothing found means nothing to hang from, and
339
- * the trigger is the honest answer again.
340
- */
341
124
  bar() {
342
125
  const wide = document.documentElement.clientWidth;
343
126
  let node = this.parentElement;
@@ -350,14 +133,6 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
350
133
  }
351
134
  return this.trigger.getBoundingClientRect().bottom;
352
135
  }
353
- /**
354
- * How long a pointer has to mean it, in milliseconds.
355
- *
356
- * Opening is the shorter wait: too long and the panel feels stuck, too short
357
- * and crossing the bar opens things. Closing is longer because the pointer
358
- * has to get from the trigger to the panel, and the straight line between
359
- * them is not the line anybody moves along.
360
- */
361
136
  static { this.OPEN_DELAY = 120; }
362
137
  static { this.CLOSE_DELAY = 220; }
363
138
  schedule(delay, then) {
@@ -370,15 +145,9 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
370
145
  this.timer = undefined;
371
146
  }
372
147
  }
373
- /* On the host, and here rather than in the template, because Lit templates
374
- bind inside a shadow root and these have to hear the panel too. Not the
375
- constructor: the server runs that and has no events. */
376
148
  connectedCallback() {
377
149
  super.connectedCallback();
378
150
  this.watchWidth();
379
- /* On the window and capturing, so a band over a scrolling panel hears that
380
- too rather than only the page's own scrolling. Passive: this measures and
381
- never prevents. */
382
151
  addEventListener('scroll', this.handleScroll, { capture: true, passive: true });
383
152
  this.addEventListener('pointerdown', this.handlePointerDown);
384
153
  this.addEventListener('click', this.handleContentClick);
@@ -404,51 +173,9 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
404
173
  if (changed.has('fullBelow')) {
405
174
  this.watchWidth();
406
175
  }
407
- /* The one thing the stylesheet cannot work out for itself, since the
408
- breakpoint is declared up here. */
409
176
  this.toggleAttribute('data-narrow', this.narrow);
410
- /* A slotted trigger is told the disclosure state, by property.
411
-
412
- `render` writes `aria-expanded` onto the button it renders itself, and a
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.
417
-
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.
426
-
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.
432
-
433
- **What it cannot reach is a trigger that is not a Helix element.** A plain
434
- `<button slot="trigger">` has no shadow root to hide the state in and no
435
- property to take it, and a React consumer cannot mirror it themselves
436
- because the open state is this element's. That case is warned about rather
437
- than written to, on the same argument `Tooltip` makes about a link it
438
- will not write for the consumer.
439
-
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
445
- `aria-controls` is worse than none, an audit tool reporting it satisfied
446
- and a screen reader following it nowhere. */
447
177
  this.tellTrigger();
448
178
  if (this.open) {
449
- /* Before showing this one, so the two are never both up for a frame. The
450
- other closes through `requestOpen`, so a consumer holding it open is
451
- asked rather than overruled. */
452
179
  if (showing && showing !== this) {
453
180
  showing.requestOpen(false);
454
181
  }
@@ -463,13 +190,6 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
463
190
  this.panel.hide();
464
191
  }
465
192
  }
466
- /**
467
- * The control that opens it, behind a seam the styled tier can move.
468
- *
469
- * The same case `SpeedDial` records: a bare `<button>` is what this tier can
470
- * write, `helix-button` lives a tier above, and a render method is how a tier
471
- * that has one reaches this without reimplementing the panel below it.
472
- */
473
193
  renderTrigger() {
474
194
  return html `<slot name="trigger" @click=${this.handleTriggerClick}>
475
195
  <button
@@ -483,10 +203,6 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
483
203
  </slot>`;
484
204
  }
485
205
  render() {
486
- /* The listeners sit on the host rather than the trigger, so the panel counts
487
- as inside it: the top layer changes where the panel is painted and not
488
- where it is in the tree, so a pointer moving off the trigger and onto the
489
- panel has not left this element and fires no leave. */
490
206
  return html `${this.renderTrigger()}
491
207
  <div
492
208
  part="surface"