@fusion.dev/helix-headless 0.155.0 → 0.157.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 (74) hide show
  1. package/custom-elements.json +2695 -137
  2. package/dist/canvas/node/canvas-node.js +1 -1
  3. package/dist/canvas/root/canvas-root.d.ts +17 -2
  4. package/dist/canvas/root/canvas-root.js +23 -6
  5. package/dist/chat/composer/chat-composer.js +1 -1
  6. package/dist/chat/root/chat-root.d.ts +14 -1
  7. package/dist/chat/root/chat-root.js +14 -4
  8. package/dist/combobox/root/combobox-root.d.ts +10 -0
  9. package/dist/combobox/root/combobox-root.js +18 -3
  10. package/dist/command/root/command-root.d.ts +116 -1
  11. package/dist/command/root/command-root.js +124 -15
  12. package/dist/data-table/root/data-table-root.d.ts +25 -0
  13. package/dist/data-table/root/data-table-root.js +13 -3
  14. package/dist/date-input/root/date-input-root.d.ts +32 -1
  15. package/dist/date-input/root/date-input-root.js +7 -1
  16. package/dist/drilldown/item/drilldown-item.d.ts +49 -1
  17. package/dist/drilldown/item/drilldown-item.js +77 -17
  18. package/dist/drilldown/root/drilldown-root.d.ts +45 -5
  19. package/dist/drilldown/root/drilldown-root.js +51 -5
  20. package/dist/element-events.generated.js +2 -1
  21. package/dist/filter-input/menu/filter-menu.d.ts +1 -0
  22. package/dist/filter-input/menu/filter-menu.js +4 -4
  23. package/dist/graph/root/graph-root.d.ts +13 -0
  24. package/dist/graph/root/graph-root.js +16 -0
  25. package/dist/list/root/list-root.d.ts +74 -1
  26. package/dist/list/root/list-root.js +92 -4
  27. package/dist/list/selection.js +3 -3
  28. package/dist/map/root/map-root.d.ts +6 -0
  29. package/dist/map/root/map-root.js +8 -1
  30. package/dist/megamenu/root/megamenu-root.d.ts +50 -13
  31. package/dist/megamenu/root/megamenu-root.js +44 -6
  32. package/dist/menu/item/menu-item.d.ts +24 -1
  33. package/dist/menu/item/menu-item.js +42 -2
  34. package/dist/menu/root/menu-root.d.ts +120 -9
  35. package/dist/menu/root/menu-root.js +329 -26
  36. package/dist/notifications/root/notifications-root.d.ts +20 -0
  37. package/dist/notifications/root/notifications-root.js +32 -1
  38. package/dist/number-input/root/number-input-root.d.ts +63 -6
  39. package/dist/number-input/root/number-input-root.js +96 -11
  40. package/dist/object-inspector/root/object-inspector-root.d.ts +36 -1
  41. package/dist/object-inspector/root/object-inspector-root.js +26 -2
  42. package/dist/option/root/option-root.d.ts +32 -0
  43. package/dist/option/root/option-root.js +44 -5
  44. package/dist/outline/root/outline-root.d.ts +6 -2
  45. package/dist/outline/root/outline-root.js +7 -3
  46. package/dist/table/root/table-root.d.ts +20 -0
  47. package/dist/table/root/table-root.js +13 -3
  48. package/dist/time-input/root/time-input-root.d.ts +32 -1
  49. package/dist/time-input/root/time-input-root.js +9 -2
  50. package/dist/time-picker/root/time-picker-root.d.ts +63 -1
  51. package/dist/time-picker/root/time-picker-root.js +58 -7
  52. package/dist/timeline/item/timeline-item.d.ts +25 -0
  53. package/dist/timeline/item/timeline-item.js +27 -2
  54. package/dist/toast/item/toast-item.d.ts +26 -0
  55. package/dist/toast/item/toast-item.js +55 -3
  56. package/dist/toast/root/toast-root.d.ts +32 -0
  57. package/dist/toast/root/toast-root.js +47 -2
  58. package/dist/video/root/keys.d.ts +6 -2
  59. package/dist/video/root/keys.js +18 -1
  60. package/dist/video/root/video-root.d.ts +61 -1
  61. package/dist/video/root/video-root.js +79 -11
  62. package/dist/video-playlist/item/video-playlist-item.d.ts +15 -1
  63. package/dist/video-playlist/item/video-playlist-item.js +7 -0
  64. package/dist/video-playlist/menu/video-playlist-menu.d.ts +1 -0
  65. package/dist/video-playlist/menu/video-playlist-menu.js +1 -1
  66. package/dist/video-playlist/root/video-playlist-root.d.ts +43 -4
  67. package/dist/video-playlist/root/video-playlist-root.js +69 -2
  68. package/dist/virtual-list/root/virtual-list-root.d.ts +8 -0
  69. package/dist/virtual-list/root/virtual-list-root.js +3 -0
  70. package/lint.json +1 -1
  71. package/package.json +5 -5
  72. package/types/jsx.d.ts +3346 -266
  73. package/types/vue.d.ts +910 -66
  74. package/types/words.d.ts +124 -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'), '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
@@ -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
@@ -102,16 +118,58 @@ export declare class HelixHeadlessMenu extends HelixHeadlessMenu_base {
102
118
  * `aria-labelledby` on the host names it too, and wins over both.
103
119
  */
104
120
  label: string;
121
+ /** Whether submenus open beside their rows. */
122
+ private get flies();
123
+ /** The rows of the top page, read through each section. */
124
+ private get topRows();
125
+ /** The rows of a submenu, or of the top page for null. */
126
+ private rowsOf;
127
+ /** The row whose submenu holds a row, or null for a row of the top page. */
128
+ private holderOf;
129
+ /** The submenus open, from the top page down to the deepest. */
130
+ private get openPages();
131
+ /**
132
+ * The submenu the keys move in under `flyout`: the one holding the row with
133
+ * focus, or the deepest open one.
134
+ */
135
+ private get keyPage();
136
+ /** Opens a submenu and closes the ones below it, or closes them all for null. */
137
+ private openPage;
138
+ /**
139
+ * The description a trigger gives under `open-on="long-press"`, saying how
140
+ * to open the menu. Translate it with the rest of your interface.
141
+ */
142
+ longPressLabel: string;
105
143
  /**
106
144
  * Space between the trigger and the surface, in pixels.
107
145
  */
108
146
  offset: number;
147
+ /** Whether the trigger opens the menu only on a held press. */
148
+ private get pressHeld();
149
+ /**
150
+ * Watches the trigger for a held press: under `open-on="long-press"`, and
151
+ * for a touch or a pen on a context menu's area.
152
+ */
153
+ private readonly longPress;
154
+ /** Where a held press on a context menu's area is, to open the menu there. */
155
+ private heldAt;
156
+ /**
157
+ * What opens the menu from its trigger. `long-press` leaves a press to the
158
+ * trigger's own action and opens the menu on a press held for half a second,
159
+ * or on Alt+Down or Alt+Up.
160
+ */
161
+ openOn: 'click' | 'long-press';
109
162
  /**
110
163
  * The side the menu opens on, when there is room.
111
164
  *
112
165
  * It moves if that side does not fit.
113
166
  */
114
167
  placement: Placement;
168
+ /**
169
+ * How a submenu opens. `drill` shows it in place of the page showing, with
170
+ * a way back. `flyout` opens it in a panel of its own beside its row.
171
+ */
172
+ submenus: 'drill' | 'flyout';
115
173
  /**
116
174
  * Which page is showing, and what is on it.
117
175
  *
@@ -155,6 +213,10 @@ export declare class HelixHeadlessMenu extends HelixHeadlessMenu_base {
155
213
  * hydration mismatch waiting to happen.
156
214
  */
157
215
  private tellTrigger;
216
+ /** Whether a frame has passed since the first render, after which a plain trigger may be told. */
217
+ private settledOnce;
218
+ /** The plain trigger this menu writes `aria-expanded` on. */
219
+ private told;
158
220
  /** The element the surface opens against. */
159
221
  private get trigger();
160
222
  /** The items on the page that is showing, which is what the keys move over. */
@@ -170,6 +232,8 @@ export declare class HelixHeadlessMenu extends HelixHeadlessMenu_base {
170
232
  * the area's corner where the keyboard asked and the event has no position.
171
233
  */
172
234
  private readonly handleContextMenu;
235
+ /** Opens a context menu at a point in the viewport, or moves an open one there. */
236
+ private openContextAt;
173
237
  /** Shows an item's submenu in place of the current page. */
174
238
  private enter;
175
239
  /** Returns to the page above, or closes when there is none. */
@@ -192,6 +256,53 @@ export declare class HelixHeadlessMenu extends HelixHeadlessMenu_base {
192
256
  */
193
257
  private handleClosedKeydown;
194
258
  private readonly handleTriggerClick;
259
+ /** Whether an event came from the slotted trigger, or the area of a context menu. */
260
+ private fromTrigger;
261
+ /**
262
+ * A press on the trigger starting: the start of a held one under
263
+ * `open-on="long-press"`, or a touch or a pen held on a context menu's area.
264
+ */
265
+ private readonly handlePointerDown;
266
+ private readonly handlePointerMove;
267
+ private readonly handlePointerUp;
268
+ private readonly handlePointerCancel;
269
+ /**
270
+ * Takes the click a held press ends in, ahead of the trigger's own
271
+ * listeners, so a press that opened the menu does not also do the trigger's
272
+ * action. In capture, on the host, which is above a slotted trigger.
273
+ */
274
+ private readonly handleHeldClick;
275
+ /** The control the menu has described, so the description can be taken back. */
276
+ private described;
277
+ /**
278
+ * Describes the trigger by `long-press-label` under `open-on="long-press"`,
279
+ * or takes it back, by the host, which carries the words as its label, as
280
+ * `SpeedDial` does. On the consumer's own element only once a reader has
281
+ * reached it, which is after hydration.
282
+ */
283
+ private describeTrigger;
284
+ /** A reader reaching the trigger, which is late enough to describe a plain one. */
285
+ private readonly handleTriggerFocusIn;
286
+ /** The row the pointer last rested on, under `flyout`. */
287
+ private hovered;
288
+ /** The wait before a rested-on row opens or closes a submenu. */
289
+ private hoverTimer;
290
+ /**
291
+ * The triangle from where the pointer left a row with an open submenu to the
292
+ * near edge of that submenu, and until when it holds.
293
+ */
294
+ private grace;
295
+ private stopHover;
296
+ /**
297
+ * A mouse resting on a row, under `flyout`: focus follows it, and after a
298
+ * moment the row's submenu opens, or the submenus below the row's own page
299
+ * close. Not while the pointer is crossing toward an open submenu.
300
+ */
301
+ private readonly handleHover;
302
+ /** The pointer leaving a row whose submenu is open, which starts the grace area. */
303
+ private readonly handleRowOut;
304
+ /** Whether the pointer is inside the grace area, while it holds. */
305
+ private inGrace;
195
306
  connectedCallback(): void;
196
307
  disconnectedCallback(): void;
197
308
  protected updated(changed: PropertyValues): void;