@fusion.dev/helix-headless 0.156.0 → 0.158.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 (47) hide show
  1. package/custom-elements.json +1085 -66
  2. package/dist/command/root/command-root.d.ts +19 -5
  3. package/dist/command/root/command-root.js +14 -4
  4. package/dist/data-table/root/data-table-root.js +5 -2
  5. package/dist/drilldown/item/drilldown-item.d.ts +49 -1
  6. package/dist/drilldown/item/drilldown-item.js +77 -17
  7. package/dist/drilldown/root/drilldown-root.d.ts +45 -5
  8. package/dist/drilldown/root/drilldown-root.js +51 -5
  9. package/dist/element-events.generated.js +1 -1
  10. package/dist/filter-input/menu/filter-menu.d.ts +1 -0
  11. package/dist/filter-input/menu/filter-menu.js +4 -4
  12. package/dist/list/item/list-item.d.ts +15 -1
  13. package/dist/list/item/list-item.js +20 -1
  14. package/dist/list/root/list-root.d.ts +29 -1
  15. package/dist/list/root/list-root.js +44 -10
  16. package/dist/listbox/root/listbox-root.d.ts +19 -2
  17. package/dist/listbox/root/listbox-root.js +16 -8
  18. package/dist/map/root/map-root.d.ts +4 -0
  19. package/dist/map/root/map-root.js +1 -1
  20. package/dist/megamenu/root/megamenu-root.d.ts +50 -13
  21. package/dist/megamenu/root/megamenu-root.js +44 -6
  22. package/dist/menu/item/menu-item.d.ts +24 -1
  23. package/dist/menu/item/menu-item.js +42 -2
  24. package/dist/menu/root/menu-root.d.ts +126 -11
  25. package/dist/menu/root/menu-root.js +336 -26
  26. package/dist/notifications/root/notifications-root.d.ts +20 -0
  27. package/dist/notifications/root/notifications-root.js +32 -1
  28. package/dist/number-input/root/number-input-root.d.ts +7 -4
  29. package/dist/number-input/root/number-input-root.js +3 -3
  30. package/dist/progress/root/progress-root.js +1 -1
  31. package/dist/select/root/select-root.d.ts +8 -1
  32. package/dist/select/root/select-root.js +8 -5
  33. package/dist/skeleton/root/skeleton-root.d.ts +2 -1
  34. package/dist/skeleton/root/skeleton-root.js +1 -1
  35. package/dist/toast/item/toast-item.d.ts +26 -0
  36. package/dist/toast/item/toast-item.js +55 -3
  37. package/dist/toast/root/toast-root.d.ts +32 -0
  38. package/dist/toast/root/toast-root.js +47 -2
  39. package/dist/video/root/video-root.d.ts +1 -0
  40. package/dist/video/root/video-root.js +9 -9
  41. package/dist/video-playlist/menu/video-playlist-menu.d.ts +1 -0
  42. package/dist/video-playlist/menu/video-playlist-menu.js +1 -1
  43. package/lint.json +1 -1
  44. package/package.json +5 -5
  45. package/types/jsx.d.ts +653 -74
  46. package/types/vue.d.ts +133 -16
  47. package/types/words.d.ts +14 -0
@@ -13,8 +13,11 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
13
13
  constructor() {
14
14
  super(...arguments);
15
15
  this.bandBelow = 640;
16
+ this.closeDelay = 220;
16
17
  this.label = '';
18
+ this.openDelay = 120;
17
19
  this.openOn = 'hover';
20
+ this.placement = 'bottom-start';
18
21
  this.narrow = false;
19
22
  this.handleWidth = (event) => {
20
23
  this.narrow = event.matches;
@@ -25,9 +28,10 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
25
28
  onDismiss: () => this.close(),
26
29
  open: () => this.open,
27
30
  place: () => !this.banded,
28
- placement: () => 'bottom-start',
31
+ placement: () => this.placement,
29
32
  surface: () => this.partNamed('surface'),
30
33
  });
34
+ this.settledOnce = false;
31
35
  this.keys = new KeyboardNavigationController(this, {
32
36
  items: () => !this.parentElement?.shadowRoot
33
37
  ? HelixHeadlessMegamenu_1.bar
@@ -72,7 +76,7 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
72
76
  return;
73
77
  }
74
78
  const showing = HelixHeadlessMegamenu_1.bar.showing;
75
- this.schedule(showing && showing !== this ? 0 : HelixHeadlessMegamenu_1.OPEN_DELAY, () => {
79
+ this.schedule(showing && showing !== this ? 0 : this.openDelay, () => {
76
80
  this.requestOpen(true);
77
81
  });
78
82
  };
@@ -80,7 +84,7 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
80
84
  if (this.openOn !== 'hover' || event.pointerType !== 'mouse') {
81
85
  return;
82
86
  }
83
- this.schedule(HelixHeadlessMegamenu_1.CLOSE_DELAY, () => {
87
+ this.schedule(this.closeDelay, () => {
84
88
  this.requestOpen(false);
85
89
  });
86
90
  };
@@ -142,9 +146,17 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
142
146
  }
143
147
  tellTrigger() {
144
148
  const slotted = this.slotted;
145
- if (slotted) {
149
+ if (!slotted || slotted.localName.includes('-')) {
146
150
  tellDisclosure(slotted, this.open);
151
+ return;
152
+ }
153
+ if (slotted !== this.told) {
154
+ if (!this.settledOnce || slotted.hasAttribute('aria-expanded')) {
155
+ return;
156
+ }
157
+ this.told = slotted;
147
158
  }
159
+ slotted.setAttribute('aria-expanded', String(this.open));
148
160
  }
149
161
  get slotted() {
150
162
  const slot = this.renderRoot?.querySelector('slot[name="trigger"]');
@@ -156,6 +168,10 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
156
168
  get navigationControl() {
157
169
  return this.trigger;
158
170
  }
171
+ markCurrent() {
172
+ const current = [...this.querySelectorAll('[aria-current="page"]')].some((link) => !link.closest('[slot="trigger"]'));
173
+ this.toggleState('current', current);
174
+ }
159
175
  close() {
160
176
  if (!this.requestOpen(false)) {
161
177
  return;
@@ -180,8 +196,6 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
180
196
  }
181
197
  return this.trigger.getBoundingClientRect().bottom;
182
198
  }
183
- static { this.OPEN_DELAY = 120; }
184
- static { this.CLOSE_DELAY = 220; }
185
199
  schedule(delay, then) {
186
200
  this.stop();
187
201
  this.timer = setTimeout(then, delay);
@@ -203,6 +217,19 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
203
217
  this.addEventListener('pointerleave', this.handlePointerLeave);
204
218
  this.addEventListener('focusout', this.handleFocusOut);
205
219
  this.addEventListener('keydown', this.handleKeyDown);
220
+ this.markCurrent();
221
+ if (typeof MutationObserver === 'function') {
222
+ this.currentWatch = new MutationObserver(() => this.markCurrent());
223
+ this.currentWatch.observe(this, {
224
+ attributeFilter: ['aria-current'],
225
+ childList: true,
226
+ subtree: true,
227
+ });
228
+ }
229
+ void this.whenSettled.then(() => {
230
+ this.settledOnce = true;
231
+ this.tellTrigger();
232
+ });
206
233
  }
207
234
  disconnectedCallback() {
208
235
  super.disconnectedCallback();
@@ -215,6 +242,8 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
215
242
  this.removeEventListener('pointerleave', this.handlePointerLeave);
216
243
  this.removeEventListener('focusout', this.handleFocusOut);
217
244
  this.removeEventListener('keydown', this.handleKeyDown);
245
+ this.currentWatch?.disconnect();
246
+ this.currentWatch = undefined;
218
247
  HelixHeadlessMegamenu_1.bar.remove(this);
219
248
  this.stop();
220
249
  }
@@ -297,12 +326,21 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
297
326
  __decorate([
298
327
  property({ attribute: 'band-below', type: Number })
299
328
  ], HelixHeadlessMegamenu.prototype, "bandBelow", void 0);
329
+ __decorate([
330
+ property({ attribute: 'close-delay', type: Number })
331
+ ], HelixHeadlessMegamenu.prototype, "closeDelay", void 0);
300
332
  __decorate([
301
333
  property()
302
334
  ], HelixHeadlessMegamenu.prototype, "label", void 0);
335
+ __decorate([
336
+ property({ attribute: 'open-delay', type: Number })
337
+ ], HelixHeadlessMegamenu.prototype, "openDelay", void 0);
303
338
  __decorate([
304
339
  property({ attribute: 'open-on' })
305
340
  ], HelixHeadlessMegamenu.prototype, "openOn", void 0);
341
+ __decorate([
342
+ property()
343
+ ], HelixHeadlessMegamenu.prototype, "placement", void 0);
306
344
  __decorate([
307
345
  property({ reflect: true })
308
346
  ], HelixHeadlessMegamenu.prototype, "variant", void 0);
@@ -1,4 +1,4 @@
1
- import { HelixElement } from '@fusion.dev/helix-core';
1
+ import { HelixElement, PopoverController } from '@fusion.dev/helix-core';
2
2
  import { nothing, type PropertyValues, type TemplateResult } from 'lit';
3
3
  import '../../kbd/index.js';
4
4
  /**
@@ -34,6 +34,9 @@ import '../../kbd/index.js';
34
34
  * React Aria's menu item does with its description: a reader hears "Copy",
35
35
  * then "Copies the selection".
36
36
  *
37
+ * A development build warns when a row has no words to be named by, such as a
38
+ * row holding only an icon with no label.
39
+ *
37
40
  * The check mark and the caret are the fallbacks of the `check` and `chevron`
38
41
  * slots, so a mark of your own replaces either by being slotted. Neither is
39
42
  * read as part of the row's name.
@@ -63,8 +66,11 @@ import '../../kbd/index.js';
63
66
  * @csspart shortcut-key - One key of the shortcut's chord, such as `Ctrl`.
64
67
  * @csspart shortcut-separator - The `+` between two keys of the shortcut, where the platform writes one.
65
68
  * @csspart start - The leading column that holds the `start` slot. It is always rendered.
69
+ * @csspart surface - The panel a submenu opens in beside the row, under the menu's `submenus="flyout"`.
66
70
  *
67
71
  * @cssstate checked - The row is checked.
72
+ * @cssstate submenu-mark - Under `submenus="flyout"`, a row in this row's submenu can be ticked or opens a submenu, so the submenu keeps its trailing column.
73
+ * @cssstate submenu-start - Under `submenus="flyout"`, a row in this row's submenu has content before its label, so the submenu keeps its leading column.
68
74
  */
69
75
  export declare class HelixHeadlessMenuItem extends HelixElement {
70
76
  /**
@@ -114,6 +120,16 @@ export declare class HelixHeadlessMenuItem extends HelixElement {
114
120
  * one of a set of anything.
115
121
  */
116
122
  exclusive: boolean;
123
+ /**
124
+ * @internal Whether this row's submenu is open beside it, under
125
+ * `submenus="flyout"`. Written by the menu.
126
+ */
127
+ expanded: boolean;
128
+ /**
129
+ * @internal Whether the menu opens submenus beside their rows, under
130
+ * `submenus="flyout"`. Written by the menu.
131
+ */
132
+ flyout: boolean;
117
133
  /**
118
134
  * The URL the row links to. Setting it renders the row as a link.
119
135
  *
@@ -228,6 +244,7 @@ export declare class HelixHeadlessMenuItem extends HelixElement {
228
244
  */
229
245
  protected willUpdate(changed: PropertyValues): void;
230
246
  protected firstUpdated(changed: PropertyValues): void;
247
+ protected updated(changed: PropertyValues): void;
231
248
  /**
232
249
  * Re-reads whether anything is before the label.
233
250
  *
@@ -291,6 +308,12 @@ export declare class HelixHeadlessMenuItem extends HelixElement {
291
308
  * on the row, which is the whole of what moving here means.
292
309
  */
293
310
  focusItem(): void;
311
+ /**
312
+ * The panel a flyout submenu opens in, beside the row and in the top layer.
313
+ * The menu around it says when, and answers a press outside and Escape, so
314
+ * its own dismissal does nothing.
315
+ */
316
+ protected readonly flyoutPanel: PopoverController;
294
317
  /** Chooses the row, the same as pressing it. */
295
318
  select(): void;
296
319
  /**
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { ariaChord, customElement, deepActiveElement, HelixBeforeToggleEvent, HelixElement, HelixSelectEvent, onApple, requestChange, slotFilled, } from '@fusion.dev/helix-core';
7
+ import { ariaChord, auditName, customElement, deepActiveElement, HelixBeforeToggleEvent, HelixElement, HelixSelectEvent, isRtl, onApple, PopoverController, requestChange, slotFilled, } from '@fusion.dev/helix-core';
8
8
  import { caretRight, check } from '@fusion.dev/helix-icons';
9
9
  import { html, nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
@@ -19,6 +19,8 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
19
19
  this.disabled = false;
20
20
  this.download = false;
21
21
  this.exclusive = false;
22
+ this.expanded = false;
23
+ this.flyout = false;
22
24
  this.href = '';
23
25
  this.onPage = true;
24
26
  this.rel = '';
@@ -47,6 +49,14 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
47
49
  const slot = this.renderRoot.querySelector('slot[name="submenu"]');
48
50
  this.submenu = (slot?.assignedElements().length ?? 0) > 0;
49
51
  };
52
+ this.flyoutPanel = new PopoverController(this, {
53
+ anchor: () => this.partNamed('item'),
54
+ boundary: () => this,
55
+ onDismiss: () => { },
56
+ open: () => this.flyout && this.expanded && this.onPage && this.submenu,
57
+ placement: () => (isRtl(this) ? 'left-start' : 'right-start'),
58
+ surface: () => (this.flyout ? this.partNamed('surface') : null),
59
+ });
50
60
  this.handleClick = () => {
51
61
  this.choose();
52
62
  };
@@ -80,11 +90,23 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
80
90
  this.start = named('start');
81
91
  this.end = named('end');
82
92
  this.description = named('description');
93
+ const rows = this.knownChildren.filter((child) => child.slot === 'submenu' && child.isMenuItem);
94
+ this.toggleState('submenu-start', this.flyout && rows.some((row) => row.querySelector(':scope > [slot="start"]') !== null));
95
+ this.toggleState('submenu-mark', this.flyout &&
96
+ rows.some((row) => row.checkable ||
97
+ row.checked ||
98
+ [...row.children].some((child) => child.slot === 'submenu')));
83
99
  }
84
100
  firstUpdated(changed) {
85
101
  super.firstUpdated(changed);
86
102
  this.finishHydration();
87
103
  }
104
+ updated(changed) {
105
+ super.updated(changed);
106
+ if (this.partNamed('item')) {
107
+ auditName(this, () => this.partNamed('item') ?? undefined, 'Give the row words in its default slot, or a label on the icon it holds.');
108
+ }
109
+ }
88
110
  renderDescription() {
89
111
  return html `<slot
90
112
  id="description"
@@ -213,6 +235,7 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
213
235
  return html `${linked
214
236
  ? html `<a
215
237
  part="item"
238
+ id=${this.flyout ? 'row' : nothing}
216
239
  href=${this.href}
217
240
  target=${this.target || nothing}
218
241
  rel=${this.rel || nothing}
@@ -221,6 +244,7 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
221
244
  aria-checked=${checkedState}
222
245
  aria-describedby=${this.describedBy}
223
246
  aria-haspopup=${submenu ? 'menu' : nothing}
247
+ aria-expanded=${this.flyout && submenu ? String(this.expanded) : nothing}
224
248
  aria-keyshortcuts=${keys}
225
249
  tabindex="-1"
226
250
  @click=${this.handleClick}
@@ -228,17 +252,27 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
228
252
  >${inside}</a>`
229
253
  : html `<div
230
254
  part="item"
255
+ id=${this.flyout ? 'row' : nothing}
231
256
  role=${role}
232
257
  aria-checked=${checkedState}
233
258
  aria-describedby=${this.describedBy}
234
259
  aria-disabled=${this.disabled ? 'true' : nothing}
235
260
  aria-haspopup=${submenu ? 'menu' : nothing}
261
+ aria-expanded=${this.flyout && submenu ? String(this.expanded) : nothing}
236
262
  aria-keyshortcuts=${keys}
237
263
  tabindex=${this.disabled ? nothing : '-1'}
238
264
  @click=${this.handleClick}
239
265
  @keydown=${this.handleKeydown}
240
266
  >${inside}</div>`}
241
- <div hidden><slot name="submenu" @slotchange=${this.readSubmenu}></slot></div>`;
267
+ ${this.flyout
268
+ ? html `<div
269
+ part="surface"
270
+ popover="manual"
271
+ role="menu"
272
+ aria-labelledby="row"
273
+ ?hidden=${!(this.expanded && submenu)}
274
+ ><slot name="submenu" @slotchange=${this.readSubmenu}></slot></div>`
275
+ : html `<div hidden><slot name="submenu" @slotchange=${this.readSubmenu}></slot></div>`}`;
242
276
  }
243
277
  };
244
278
  __decorate([
@@ -256,6 +290,12 @@ __decorate([
256
290
  __decorate([
257
291
  property({ reflect: true, type: Boolean })
258
292
  ], HelixHeadlessMenuItem.prototype, "exclusive", void 0);
293
+ __decorate([
294
+ property({ attribute: false })
295
+ ], HelixHeadlessMenuItem.prototype, "expanded", void 0);
296
+ __decorate([
297
+ property({ attribute: false })
298
+ ], HelixHeadlessMenuItem.prototype, "flyout", void 0);
259
299
  __decorate([
260
300
  property()
261
301
  ], HelixHeadlessMenuItem.prototype, "href", void 0);
@@ -10,15 +10,24 @@ declare const HelixHeadlessMenu_base: (new (...args: any[]) => import("@fusion.d
10
10
  * ancestor with `overflow: hidden` does not clip it and it needs no `z-index`.
11
11
  *
12
12
  * Slot a `helix-button` or `helix-icon-button` as the trigger, and the menu
13
- * tells it that it opens a menu and whether the menu is open. A plain
14
- * `<button>` can only be told by writing attributes onto your markup, which
15
- * the menu does not do, so give it `aria-haspopup="menu"` and set its
16
- * `aria-expanded` from the `toggle` event.
13
+ * tells it that it opens a menu and whether the menu is open, inside its own
14
+ * shadow root. On a plain element, such as a `<button>`, the menu writes
15
+ * `aria-haspopup="menu"` and `aria-expanded`, from a frame after its own first
16
+ * render, so a server's markup is never changed while a framework hydrates it.
17
+ * It never writes `aria-expanded` where you wrote it yourself.
17
18
  *
18
- * Submenus drill down. A submenu replaces the panel's content, and a back row
19
- * returns to the previous page. One panel avoids the diagonal pointer path that
20
- * side-by-side panels need, stays inside a narrow window, and works on touch
21
- * screens, which have no hover.
19
+ * Submenus drill down by default. A submenu replaces the panel's content, and a
20
+ * back row returns to the previous page. One panel stays inside a narrow
21
+ * window, and works on touch screens, which have no hover.
22
+ *
23
+ * **`submenus="flyout"` opens each submenu in a panel of its own beside its
24
+ * row**, as React Aria's `SubmenuTrigger` and Radix's `Sub` do. Resting the
25
+ * pointer on the row opens it, and a pointer crossing toward the open panel
26
+ * over the rows between keeps it open, as Radix's grace area does. Right Arrow
27
+ * opens a row's submenu on its first row, and Left Arrow or Escape closes it
28
+ * and returns focus to the row, the two arrows swapping on a right-to-left
29
+ * page. The row says `aria-expanded`, and the panel is a `menu` named by the
30
+ * row.
22
31
  *
23
32
  * The root holds which page is open, because only one page shows at a time. It
24
33
  * sets this on items as a property, never as an attribute. An attribute written
@@ -33,8 +42,8 @@ declare const HelixHeadlessMenu_base: (new (...args: any[]) => import("@fusion.d
33
42
  * rows, Home and End go to the first and the last, a typed letter moves to
34
43
  * the next row whose name starts with it, Enter and Space choose, Right Arrow
35
44
  * opens a row's submenu and Left Arrow goes back, the two swapping on a
36
- * right-to-left page, and Escape closes the menu and returns focus to the
37
- * trigger. Tab closes it and moves on.
45
+ * right-to-left page. Left Arrow on the top page does nothing. Escape closes
46
+ * the menu and returns focus to the trigger. Tab closes it and moves on.
38
47
  *
39
48
  * While it is open, the surface carries the trigger's width as
40
49
  * `--floating-anchor-inline-size` and the room left below it as
@@ -46,7 +55,14 @@ declare const HelixHeadlessMenu_base: (new (...args: any[]) => import("@fusion.d
46
55
  * right-click, a long press, Shift+F10 or the menu key over the area opens it
47
56
  * at the pointer, or at the area's corner from the keyboard, as Radix's context
48
57
  * menu does. A press on the area does not open it, and closing it returns focus
49
- * to where it was.
58
+ * to where it was. A touch or a pen held still on the area opens it too, on
59
+ * every browser, since iOS sends no `contextmenu` for one.
60
+ *
61
+ * **A trigger with its own action.** With `open-on="long-press"`, a press on
62
+ * the trigger is the trigger's own, and a press held for half a second opens
63
+ * the menu, as React Aria's `trigger="longPress"` does. Alt+Down or Alt+Up
64
+ * opens it from the keyboard, and the trigger is described by
65
+ * `long-press-label`, which says so.
50
66
  *
51
67
  * @since 0.1.0
52
68
  * @status stable
@@ -77,6 +93,10 @@ declare const HelixHeadlessMenu_base: (new (...args: any[]) => import("@fusion.d
77
93
  * @cssstate drilled - A submenu's page is showing, so content that is not a row can hide with the top page.
78
94
  * @cssstate mark - A row on the current page has a checkbox or a link, so every row keeps the trailing column.
79
95
  * @cssstate open - While the menu is open.
96
+ * @cssstate placed-bottom - While the open panel sits below its trigger.
97
+ * @cssstate placed-left - While the open panel sits to the left of its trigger.
98
+ * @cssstate placed-right - While the open panel sits to the right of its trigger.
99
+ * @cssstate placed-top - While the open panel sits above its trigger.
80
100
  * @cssstate start - A row on the current page has content in its leading column, so every row keeps that column.
81
101
  */
82
102
  export declare class HelixHeadlessMenu extends HelixHeadlessMenu_base {
@@ -102,16 +122,58 @@ export declare class HelixHeadlessMenu extends HelixHeadlessMenu_base {
102
122
  * `aria-labelledby` on the host names it too, and wins over both.
103
123
  */
104
124
  label: string;
125
+ /** Whether submenus open beside their rows. */
126
+ private get flies();
127
+ /** The rows of the top page, read through each section. */
128
+ private get topRows();
129
+ /** The rows of a submenu, or of the top page for null. */
130
+ private rowsOf;
131
+ /** The row whose submenu holds a row, or null for a row of the top page. */
132
+ private holderOf;
133
+ /** The submenus open, from the top page down to the deepest. */
134
+ private get openPages();
135
+ /**
136
+ * The submenu the keys move in under `flyout`: the one holding the row with
137
+ * focus, or the deepest open one.
138
+ */
139
+ private get keyPage();
140
+ /** Opens a submenu and closes the ones below it, or closes them all for null. */
141
+ private openPage;
142
+ /**
143
+ * The description a trigger gives under `open-on="long-press"`, saying how
144
+ * to open the menu. Translate it with the rest of your interface.
145
+ */
146
+ longPressLabel: string;
105
147
  /**
106
148
  * Space between the trigger and the surface, in pixels.
107
149
  */
108
150
  offset: number;
151
+ /** Whether the trigger opens the menu only on a held press. */
152
+ private get pressHeld();
153
+ /**
154
+ * Watches the trigger for a held press: under `open-on="long-press"`, and
155
+ * for a touch or a pen on a context menu's area.
156
+ */
157
+ private readonly longPress;
158
+ /** Where a held press on a context menu's area is, to open the menu there. */
159
+ private heldAt;
160
+ /**
161
+ * What opens the menu from its trigger. `long-press` leaves a press to the
162
+ * trigger's own action and opens the menu on a press held for half a second,
163
+ * or on Alt+Down or Alt+Up.
164
+ */
165
+ openOn: 'click' | 'long-press';
109
166
  /**
110
167
  * The side the menu opens on, when there is room.
111
168
  *
112
169
  * It moves if that side does not fit.
113
170
  */
114
171
  placement: Placement;
172
+ /**
173
+ * How a submenu opens. `drill` shows it in place of the page showing, with
174
+ * a way back. `flyout` opens it in a panel of its own beside its row.
175
+ */
176
+ submenus: 'drill' | 'flyout';
115
177
  /**
116
178
  * Which page is showing, and what is on it.
117
179
  *
@@ -155,6 +217,10 @@ export declare class HelixHeadlessMenu extends HelixHeadlessMenu_base {
155
217
  * hydration mismatch waiting to happen.
156
218
  */
157
219
  private tellTrigger;
220
+ /** Whether a frame has passed since the first render, after which a plain trigger may be told. */
221
+ private settledOnce;
222
+ /** The plain trigger this menu writes `aria-expanded` on. */
223
+ private told;
158
224
  /** The element the surface opens against. */
159
225
  private get trigger();
160
226
  /** The items on the page that is showing, which is what the keys move over. */
@@ -170,6 +236,8 @@ export declare class HelixHeadlessMenu extends HelixHeadlessMenu_base {
170
236
  * the area's corner where the keyboard asked and the event has no position.
171
237
  */
172
238
  private readonly handleContextMenu;
239
+ /** Opens a context menu at a point in the viewport, or moves an open one there. */
240
+ private openContextAt;
173
241
  /** Shows an item's submenu in place of the current page. */
174
242
  private enter;
175
243
  /** Returns to the page above, or closes when there is none. */
@@ -192,6 +260,53 @@ export declare class HelixHeadlessMenu extends HelixHeadlessMenu_base {
192
260
  */
193
261
  private handleClosedKeydown;
194
262
  private readonly handleTriggerClick;
263
+ /** Whether an event came from the slotted trigger, or the area of a context menu. */
264
+ private fromTrigger;
265
+ /**
266
+ * A press on the trigger starting: the start of a held one under
267
+ * `open-on="long-press"`, or a touch or a pen held on a context menu's area.
268
+ */
269
+ private readonly handlePointerDown;
270
+ private readonly handlePointerMove;
271
+ private readonly handlePointerUp;
272
+ private readonly handlePointerCancel;
273
+ /**
274
+ * Takes the click a held press ends in, ahead of the trigger's own
275
+ * listeners, so a press that opened the menu does not also do the trigger's
276
+ * action. In capture, on the host, which is above a slotted trigger.
277
+ */
278
+ private readonly handleHeldClick;
279
+ /** The control the menu has described, so the description can be taken back. */
280
+ private described;
281
+ /**
282
+ * Describes the trigger by `long-press-label` under `open-on="long-press"`,
283
+ * or takes it back, by the host, which carries the words as its label, as
284
+ * `SpeedDial` does. On the consumer's own element only once a reader has
285
+ * reached it, which is after hydration.
286
+ */
287
+ private describeTrigger;
288
+ /** A reader reaching the trigger, which is late enough to describe a plain one. */
289
+ private readonly handleTriggerFocusIn;
290
+ /** The row the pointer last rested on, under `flyout`. */
291
+ private hovered;
292
+ /** The wait before a rested-on row opens or closes a submenu. */
293
+ private hoverTimer;
294
+ /**
295
+ * The triangle from where the pointer left a row with an open submenu to the
296
+ * near edge of that submenu, and until when it holds.
297
+ */
298
+ private grace;
299
+ private stopHover;
300
+ /**
301
+ * A mouse resting on a row, under `flyout`: focus follows it, and after a
302
+ * moment the row's submenu opens, or the submenus below the row's own page
303
+ * close. Not while the pointer is crossing toward an open submenu.
304
+ */
305
+ private readonly handleHover;
306
+ /** The pointer leaving a row whose submenu is open, which starts the grace area. */
307
+ private readonly handleRowOut;
308
+ /** Whether the pointer is inside the grace area, while it holds. */
309
+ private inGrace;
195
310
  connectedCallback(): void;
196
311
  disconnectedCallback(): void;
197
312
  protected updated(changed: PropertyValues): void;