@fusion.dev/helix-headless 0.156.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.
- package/custom-elements.json +909 -46
- package/dist/drilldown/item/drilldown-item.d.ts +49 -1
- package/dist/drilldown/item/drilldown-item.js +77 -17
- package/dist/drilldown/root/drilldown-root.d.ts +45 -5
- package/dist/drilldown/root/drilldown-root.js +51 -5
- package/dist/element-events.generated.js +1 -1
- package/dist/filter-input/menu/filter-menu.d.ts +1 -0
- package/dist/filter-input/menu/filter-menu.js +4 -4
- package/dist/megamenu/root/megamenu-root.d.ts +50 -13
- package/dist/megamenu/root/megamenu-root.js +44 -6
- package/dist/menu/item/menu-item.d.ts +24 -1
- package/dist/menu/item/menu-item.js +42 -2
- package/dist/menu/root/menu-root.d.ts +120 -9
- package/dist/menu/root/menu-root.js +329 -26
- package/dist/notifications/root/notifications-root.d.ts +20 -0
- package/dist/notifications/root/notifications-root.js +32 -1
- package/dist/toast/item/toast-item.d.ts +26 -0
- package/dist/toast/item/toast-item.js +55 -3
- package/dist/toast/root/toast-root.d.ts +32 -0
- package/dist/toast/root/toast-root.js +47 -2
- package/dist/video/root/video-root.d.ts +1 -0
- package/dist/video/root/video-root.js +9 -9
- package/dist/video-playlist/menu/video-playlist-menu.d.ts +1 -0
- package/dist/video-playlist/menu/video-playlist-menu.js +1 -1
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +445 -34
- package/types/vue.d.ts +86 -8
- package/types/words.d.ts +14 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HelixElement, KeyboardNavigationController, PopoverController } from '@fusion.dev/helix-core';
|
|
1
|
+
import { HelixElement, KeyboardNavigationController, type Placement, PopoverController } from '@fusion.dev/helix-core';
|
|
2
2
|
import { nothing, type PropertyValues, type TemplateResult } from 'lit';
|
|
3
3
|
declare const HelixHeadlessMegamenu_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerOpenInterface) & typeof HelixElement;
|
|
4
4
|
/**
|
|
@@ -18,7 +18,10 @@ declare const HelixHeadlessMegamenu_base: (new (...args: any[]) => import("@fusi
|
|
|
18
18
|
*
|
|
19
19
|
* It opens on hover by default, and a press always opens it too. `open-on` sets
|
|
20
20
|
* which. Hover is the default because in a navigation bar people expect to move
|
|
21
|
-
* along the row and see each section.
|
|
21
|
+
* along the row and see each section. A pointer resting on the trigger opens
|
|
22
|
+
* the panel after `open-delay`, and leaving closes it after `close-delay`. Moving
|
|
23
|
+
* from one open panel's trigger to another's opens the next at once, as Radix's
|
|
24
|
+
* `skipDelayDuration` does.
|
|
22
25
|
*
|
|
23
26
|
* When one panel opens in place of another, each says which way the reader
|
|
24
27
|
* moved, as Radix's `data-motion` does. Moving toward the end of the bar, the
|
|
@@ -36,6 +39,10 @@ declare const HelixHeadlessMegamenu_base: (new (...args: any[]) => import("@fusi
|
|
|
36
39
|
* navigation menu publishes the same pair from its shared viewport, so a
|
|
37
40
|
* stylesheet can grow or shrink one panel into the next.
|
|
38
41
|
*
|
|
42
|
+
* While a link in the panel carries `aria-current="page"`, the host has
|
|
43
|
+
* `:state(current)`, so the trigger of the section holding the page you are
|
|
44
|
+
* on can say so, as Radix's navigation menu marks its active link.
|
|
45
|
+
*
|
|
39
46
|
* The panel's default slot is the `columns` part and draws no box, so a sheet
|
|
40
47
|
* can give the columns one of their own. While a panel is open, an
|
|
41
48
|
* `indicator` slot is rendered beside its trigger, as Radix's indicator
|
|
@@ -52,6 +59,12 @@ declare const HelixHeadlessMegamenu_base: (new (...args: any[]) => import("@fusi
|
|
|
52
59
|
* way out. The trigger says `aria-expanded`, and the panel is a group named by
|
|
53
60
|
* `label`.
|
|
54
61
|
*
|
|
62
|
+
* A Helix trigger, such as `helix-button`, is told whether the panel is open
|
|
63
|
+
* inside its own shadow root. On a plain element, such as a `<button>`, the
|
|
64
|
+
* megamenu writes `aria-expanded`, from a frame after its own first render, so
|
|
65
|
+
* a server's markup is never changed while a framework hydrates it. It never
|
|
66
|
+
* writes it where you wrote `aria-expanded` yourself.
|
|
67
|
+
*
|
|
55
68
|
* @since 0.1.0
|
|
56
69
|
* @status stable
|
|
57
70
|
* @category navigation
|
|
@@ -77,6 +90,7 @@ declare const HelixHeadlessMegamenu_base: (new (...args: any[]) => import("@fusi
|
|
|
77
90
|
* @cssproperty --megamenu-surface-inline-size - The panel's own width, written on the surface while it is open.
|
|
78
91
|
* @cssproperty --megamenu-top - Where the panel starts, measured from the bar above it.
|
|
79
92
|
*
|
|
93
|
+
* @cssstate current - A link in the panel carries `aria-current="page"`, so the trigger can show the section you are in.
|
|
80
94
|
* @cssstate from-end - The panel opened in place of one earlier in the bar, so it can enter from the end side.
|
|
81
95
|
* @cssstate from-start - The panel opened in place of one later in the bar, so it can enter from the start side.
|
|
82
96
|
* @cssstate narrow - The page is narrower than `band-below`, so the panel is shown as a band whatever its variant.
|
|
@@ -114,10 +128,22 @@ export declare class HelixHeadlessMegamenu extends HelixHeadlessMegamenu_base {
|
|
|
114
128
|
* paint is the opposite case. See `MixinControllerOpen` on `open`.
|
|
115
129
|
*/
|
|
116
130
|
bandBelow: number;
|
|
131
|
+
/**
|
|
132
|
+
* How long a pointer leaving the trigger and the panel waits before the panel
|
|
133
|
+
* closes, in milliseconds. The pointer needs the time to cross from the
|
|
134
|
+
* trigger to the panel.
|
|
135
|
+
*/
|
|
136
|
+
closeDelay: number;
|
|
117
137
|
/**
|
|
118
138
|
* The section's name, used for the default trigger and the panel.
|
|
119
139
|
*/
|
|
120
140
|
label: string;
|
|
141
|
+
/**
|
|
142
|
+
* How long a pointer rests on the trigger before the panel opens, in
|
|
143
|
+
* milliseconds, under `open-on="hover"`. A pointer crossing the bar on its
|
|
144
|
+
* way somewhere else opens nothing.
|
|
145
|
+
*/
|
|
146
|
+
openDelay: number;
|
|
121
147
|
/**
|
|
122
148
|
* Whether hovering over the trigger opens the panel (`hover`), or only a press
|
|
123
149
|
* does (`click`).
|
|
@@ -128,6 +154,12 @@ export declare class HelixHeadlessMegamenu extends HelixHeadlessMegamenu_base {
|
|
|
128
154
|
* heavy enough that opening it on the way past is rude.
|
|
129
155
|
*/
|
|
130
156
|
openOn: 'click' | 'hover';
|
|
157
|
+
/**
|
|
158
|
+
* The side of the trigger a card panel opens on, when there is room. It
|
|
159
|
+
* moves if that side does not fit. A band ignores it, since a band spans the
|
|
160
|
+
* page under its bar.
|
|
161
|
+
*/
|
|
162
|
+
placement: Placement;
|
|
131
163
|
/**
|
|
132
164
|
* Whether the panel is a card under the trigger or a band across the page.
|
|
133
165
|
*
|
|
@@ -158,8 +190,19 @@ export declare class HelixHeadlessMegamenu extends HelixHeadlessMegamenu_base {
|
|
|
158
190
|
private readonly handleWidth;
|
|
159
191
|
private watchWidth;
|
|
160
192
|
protected readonly panel: PopoverController;
|
|
161
|
-
/**
|
|
193
|
+
/**
|
|
194
|
+
* Tells a slotted trigger the disclosure state.
|
|
195
|
+
*
|
|
196
|
+
* A Helix trigger by property, through `tellDisclosure`. A plain element,
|
|
197
|
+
* which has no shadow root to keep the state out of the markup, by
|
|
198
|
+
* `aria-expanded`, from a frame after the first render, as `Popover` writes
|
|
199
|
+
* it. One that already carries `aria-expanded` is the consumer's to keep.
|
|
200
|
+
*/
|
|
162
201
|
private tellTrigger;
|
|
202
|
+
/** Whether a frame has passed since the first render, after which a plain trigger may be told. */
|
|
203
|
+
private settledOnce;
|
|
204
|
+
/** The plain trigger this megamenu writes `aria-expanded` on. */
|
|
205
|
+
private told;
|
|
163
206
|
/** What the consumer slotted, if they slotted anything. */
|
|
164
207
|
private get slotted();
|
|
165
208
|
/** Whatever the consumer slotted, or the button rendered in its place. */
|
|
@@ -185,6 +228,10 @@ export declare class HelixHeadlessMegamenu extends HelixHeadlessMegamenu_base {
|
|
|
185
228
|
* link or control, as Radix's navigation menu does.
|
|
186
229
|
*/
|
|
187
230
|
private readonly handleKeyDown;
|
|
231
|
+
/** Watches the panel's links for the one that is the page you are on. */
|
|
232
|
+
private currentWatch;
|
|
233
|
+
/** Records whether a link in the panel is the page you are on. */
|
|
234
|
+
private markCurrent;
|
|
188
235
|
/** Shuts it and puts the reader back where they were. */
|
|
189
236
|
private close;
|
|
190
237
|
/**
|
|
@@ -223,16 +270,6 @@ export declare class HelixHeadlessMegamenu extends HelixHeadlessMegamenu_base {
|
|
|
223
270
|
* the trigger is the honest answer again.
|
|
224
271
|
*/
|
|
225
272
|
private bar;
|
|
226
|
-
/**
|
|
227
|
-
* How long a pointer has to mean it, in milliseconds.
|
|
228
|
-
*
|
|
229
|
-
* Opening is the shorter wait: too long and the panel feels stuck, too short
|
|
230
|
-
* and crossing the bar opens things. Closing is longer because the pointer
|
|
231
|
-
* has to get from the trigger to the panel, and the straight line between
|
|
232
|
-
* them is not the line anybody moves along.
|
|
233
|
-
*/
|
|
234
|
-
private static readonly OPEN_DELAY;
|
|
235
|
-
private static readonly CLOSE_DELAY;
|
|
236
273
|
private timer;
|
|
237
274
|
private pointer;
|
|
238
275
|
private schedule;
|
|
@@ -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: () =>
|
|
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 :
|
|
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(
|
|
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'), '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
|
-
|
|
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
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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
|
|
19
|
-
* returns to the previous page. One panel
|
|
20
|
-
*
|
|
21
|
-
*
|
|
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;
|