@fusion.dev/helix-headless 0.165.0 → 0.166.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 +578 -54
- package/dist/callout/root/callout-root.d.ts +8 -2
- package/dist/callout/root/callout-root.js +14 -1
- package/dist/code-block/root/code-block-root.d.ts +63 -0
- package/dist/code-block/root/code-block-root.js +117 -6
- package/dist/element-events.generated.js +15 -2
- package/dist/filter-input/menu/filter-menu.d.ts +1 -0
- package/dist/filter-input/menu/filter-menu.js +1 -1
- package/dist/listbox/root/listbox-root.js +2 -5
- package/dist/menu/item/menu-item.d.ts +20 -0
- package/dist/menu/item/menu-item.js +53 -20
- package/dist/menu/root/menu-root.d.ts +84 -1
- package/dist/menu/root/menu-root.js +187 -44
- package/dist/object-inspector/root/object-inspector-root.d.ts +5 -0
- package/dist/object-inspector/root/object-inspector-root.js +5 -0
- package/dist/steps/root/steps-root.d.ts +7 -0
- package/dist/steps/root/steps-root.js +7 -0
- package/dist/video/root/video-root.d.ts +2 -0
- package/dist/video/root/video-root.js +4 -2
- package/dist/video-playlist/menu/video-playlist-menu.d.ts +1 -0
- package/dist/video-playlist/menu/video-playlist-menu.js +1 -1
- package/dist/video-playlist/root/video-playlist-root.js +15 -1
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +262 -20
- package/types/vue.d.ts +59 -5
- package/types/words.d.ts +18 -0
|
@@ -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, auditName, customElement, deepActiveElement, HelixBeforeToggleEvent, HelixElement, HelixSelectEvent, isRtl, onApple, onUnlessFalse, PopoverController, requestChange, slotFilled, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { ariaChord, auditName, customElement, deepActiveElement, followLink, HelixBeforeToggleEvent, HelixElement, HelixSelectEvent, hostAria, isRtl, onApple, onUnlessFalse, 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';
|
|
@@ -21,6 +21,7 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
|
|
|
21
21
|
this.exclusive = false;
|
|
22
22
|
this.expanded = false;
|
|
23
23
|
this.flyout = false;
|
|
24
|
+
this.highlighted = false;
|
|
24
25
|
this.href = '';
|
|
25
26
|
this.onPage = true;
|
|
26
27
|
this.rel = '';
|
|
@@ -28,6 +29,7 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
|
|
|
28
29
|
this.showingSubmenu = false;
|
|
29
30
|
this.target = '';
|
|
30
31
|
this.value = '';
|
|
32
|
+
this.virtual = false;
|
|
31
33
|
this.isMenuItem = true;
|
|
32
34
|
this.keepsOpen = false;
|
|
33
35
|
this.submenu = false;
|
|
@@ -90,6 +92,7 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
|
|
|
90
92
|
willUpdate(changed) {
|
|
91
93
|
super.willUpdate(changed);
|
|
92
94
|
this.toggleState('checked', this.checked);
|
|
95
|
+
this.toggleState('highlighted', this.virtual && this.highlighted);
|
|
93
96
|
if (!this.childrenKnown) {
|
|
94
97
|
return;
|
|
95
98
|
}
|
|
@@ -109,6 +112,11 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
|
|
|
109
112
|
}
|
|
110
113
|
updated(changed) {
|
|
111
114
|
super.updated(changed);
|
|
115
|
+
hostAria(this.internals, {
|
|
116
|
+
describedBy: this.onHost
|
|
117
|
+
? [...this.renderRoot.querySelectorAll('#description, #end-words')].filter((slot) => slot.id === 'description' ? this.description : this.end)
|
|
118
|
+
: [],
|
|
119
|
+
});
|
|
112
120
|
if (this.partNamed('item')) {
|
|
113
121
|
auditName(this, () => this.partNamed('item') ?? undefined, 'Give the row words in its default slot, or a label on the icon it holds.');
|
|
114
122
|
}
|
|
@@ -126,7 +134,7 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
|
|
|
126
134
|
const ids = [this.description ? 'description' : '', this.end ? 'end-words' : '']
|
|
127
135
|
.filter((id) => id !== '')
|
|
128
136
|
.join(' ');
|
|
129
|
-
return ids === '' ?
|
|
137
|
+
return ids === '' ? null : ids;
|
|
130
138
|
}
|
|
131
139
|
renderEnd() {
|
|
132
140
|
const kbd = this.tag('kbd');
|
|
@@ -151,6 +159,9 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
|
|
|
151
159
|
this.choose();
|
|
152
160
|
}
|
|
153
161
|
get active() {
|
|
162
|
+
if (this.virtual) {
|
|
163
|
+
return this.highlighted;
|
|
164
|
+
}
|
|
154
165
|
const active = deepActiveElement();
|
|
155
166
|
return active !== null && active.getRootNode() === this.renderRoot;
|
|
156
167
|
}
|
|
@@ -201,7 +212,11 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
|
|
|
201
212
|
const spaceKeeps = space && this.choice && !this.hasSubmenu;
|
|
202
213
|
this.keepsOpen = this.closeOnSelect === undefined ? spaceKeeps : !this.closeOnSelect;
|
|
203
214
|
this.tick();
|
|
204
|
-
this.dispatchEvent(new HelixSelectEvent({ value: this.value }))
|
|
215
|
+
if (this.dispatchEvent(new HelixSelectEvent({ value: this.value })) &&
|
|
216
|
+
this.virtual &&
|
|
217
|
+
this.href !== '') {
|
|
218
|
+
followLink(this.ownerDocument, this);
|
|
219
|
+
}
|
|
205
220
|
}
|
|
206
221
|
renderMark(submenu) {
|
|
207
222
|
if (submenu) {
|
|
@@ -220,19 +235,31 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
|
|
|
220
235
|
}
|
|
221
236
|
return nothing;
|
|
222
237
|
}
|
|
238
|
+
get onHost() {
|
|
239
|
+
return this.virtual && this.onPage && !this.showingSubmenu;
|
|
240
|
+
}
|
|
223
241
|
render() {
|
|
242
|
+
const submenu = this.hasSubmenu;
|
|
243
|
+
const ticks = this.choice;
|
|
244
|
+
const role = ticks ? (this.exclusive ? 'menuitemradio' : 'menuitemcheckbox') : 'menuitem';
|
|
245
|
+
const checkedState = ticks ? (this.checked ? 'true' : 'false') : null;
|
|
246
|
+
const keys = this.shortcut === '' ? null : ariaChord(this.shortcut, !this.hydrating && onApple());
|
|
247
|
+
const host = this.onHost;
|
|
248
|
+
const internals = this.internals;
|
|
249
|
+
internals.role = host ? role : null;
|
|
250
|
+
internals.ariaChecked = host ? checkedState : null;
|
|
251
|
+
internals.ariaDisabled = host && this.disabled ? 'true' : null;
|
|
252
|
+
internals.ariaHasPopup = host && submenu ? 'menu' : null;
|
|
253
|
+
internals.ariaExpanded = host && this.flyout && submenu ? String(this.expanded) : null;
|
|
254
|
+
internals.ariaKeyShortcuts = host ? keys : null;
|
|
255
|
+
const own = (value) => (host || value === null ? nothing : value);
|
|
224
256
|
if (this.showingSubmenu) {
|
|
225
257
|
return html `<slot name="submenu" @slotchange=${this.readSubmenu}></slot>`;
|
|
226
258
|
}
|
|
227
259
|
if (!this.onPage) {
|
|
228
260
|
return nothing;
|
|
229
261
|
}
|
|
230
|
-
const
|
|
231
|
-
const linked = this.href !== '' && !this.disabled;
|
|
232
|
-
const ticks = this.choice;
|
|
233
|
-
const role = ticks ? (this.exclusive ? 'menuitemradio' : 'menuitemcheckbox') : 'menuitem';
|
|
234
|
-
const checkedState = ticks ? (this.checked ? 'true' : 'false') : nothing;
|
|
235
|
-
const keys = this.shortcut === '' ? nothing : ariaChord(this.shortcut, !this.hydrating && onApple());
|
|
262
|
+
const linked = this.href !== '' && !this.disabled && !this.virtual;
|
|
236
263
|
const inside = html `<span part="start" ?data-empty=${!this.start}
|
|
237
264
|
><slot name="start" @slotchange=${this.readStart}></slot
|
|
238
265
|
></span>
|
|
@@ -249,11 +276,11 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
|
|
|
249
276
|
rel=${this.rel || nothing}
|
|
250
277
|
?download=${this.download}
|
|
251
278
|
role=${role}
|
|
252
|
-
aria-checked=${checkedState}
|
|
253
|
-
aria-describedby=${this.describedBy}
|
|
279
|
+
aria-checked=${own(checkedState)}
|
|
280
|
+
aria-describedby=${own(this.describedBy)}
|
|
254
281
|
aria-haspopup=${submenu ? 'menu' : nothing}
|
|
255
282
|
aria-expanded=${this.flyout && submenu ? String(this.expanded) : nothing}
|
|
256
|
-
aria-keyshortcuts=${keys}
|
|
283
|
+
aria-keyshortcuts=${own(keys)}
|
|
257
284
|
tabindex="-1"
|
|
258
285
|
@click=${this.handleClick}
|
|
259
286
|
@keydown=${this.handleKeydown}
|
|
@@ -261,14 +288,14 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
|
|
|
261
288
|
: html `<div
|
|
262
289
|
part="item"
|
|
263
290
|
id=${this.flyout ? 'row' : nothing}
|
|
264
|
-
role=${role}
|
|
265
|
-
aria-checked=${checkedState}
|
|
266
|
-
aria-describedby=${this.describedBy}
|
|
267
|
-
aria-disabled=${this.disabled ? 'true' :
|
|
268
|
-
aria-haspopup=${submenu ? 'menu' :
|
|
269
|
-
aria-expanded=${this.flyout && submenu ? String(this.expanded) :
|
|
270
|
-
aria-keyshortcuts=${keys}
|
|
271
|
-
tabindex=${this.disabled ?
|
|
291
|
+
role=${own(role)}
|
|
292
|
+
aria-checked=${own(checkedState)}
|
|
293
|
+
aria-describedby=${own(this.describedBy)}
|
|
294
|
+
aria-disabled=${own(this.disabled ? 'true' : null)}
|
|
295
|
+
aria-haspopup=${own(submenu ? 'menu' : null)}
|
|
296
|
+
aria-expanded=${own(this.flyout && submenu ? String(this.expanded) : null)}
|
|
297
|
+
aria-keyshortcuts=${own(keys)}
|
|
298
|
+
tabindex=${own(this.disabled ? null : '-1')}
|
|
272
299
|
@click=${this.handleClick}
|
|
273
300
|
@keydown=${this.handleKeydown}
|
|
274
301
|
>${inside}</div>`}
|
|
@@ -307,6 +334,9 @@ __decorate([
|
|
|
307
334
|
__decorate([
|
|
308
335
|
property({ attribute: false })
|
|
309
336
|
], HelixHeadlessMenuItem.prototype, "flyout", void 0);
|
|
337
|
+
__decorate([
|
|
338
|
+
property({ attribute: false })
|
|
339
|
+
], HelixHeadlessMenuItem.prototype, "highlighted", void 0);
|
|
310
340
|
__decorate([
|
|
311
341
|
property()
|
|
312
342
|
], HelixHeadlessMenuItem.prototype, "href", void 0);
|
|
@@ -328,6 +358,9 @@ __decorate([
|
|
|
328
358
|
__decorate([
|
|
329
359
|
property()
|
|
330
360
|
], HelixHeadlessMenuItem.prototype, "value", void 0);
|
|
361
|
+
__decorate([
|
|
362
|
+
property({ attribute: false })
|
|
363
|
+
], HelixHeadlessMenuItem.prototype, "virtual", void 0);
|
|
331
364
|
__decorate([
|
|
332
365
|
state()
|
|
333
366
|
], HelixHeadlessMenuItem.prototype, "submenu", void 0);
|
|
@@ -65,6 +65,21 @@ declare const HelixHeadlessMenu_base: (new (...args: any[]) => import("@fusion.d
|
|
|
65
65
|
* opens it from the keyboard, and the trigger is described by
|
|
66
66
|
* `long-press-label`, which says so.
|
|
67
67
|
*
|
|
68
|
+
* **A filter field.** With `filterable`, the menu opens with focus in a field
|
|
69
|
+
* above its rows, and typing narrows the rows of the page the keys move in,
|
|
70
|
+
* as React Aria's `Autocomplete` around a `Menu` does. A row is kept where its
|
|
71
|
+
* words hold the filter, in any case, or where `filterMatch` says so. Each
|
|
72
|
+
* page starts unfiltered, and the filter is cleared as the menu closes. Where
|
|
73
|
+
* typing keeps no row, the menu shows `no-results-text`. Focus stays in the
|
|
74
|
+
* field: Up and Down Arrow move the highlight through the rows that are left,
|
|
75
|
+
* Enter chooses the highlighted row, Right Arrow opens its submenu, Left Arrow
|
|
76
|
+
* on a submenu goes back, and Escape clears the filter and then closes the
|
|
77
|
+
* menu. Home, End and the other keys move the caret and type. The field names
|
|
78
|
+
* the highlighted row with `aria-activedescendant`, and the rows are a `menu`
|
|
79
|
+
* after the field. Cancel `helix-filter` to keep every row
|
|
80
|
+
* showing and narrow the rows yourself, such as by replacing them with rows
|
|
81
|
+
* from a server.
|
|
82
|
+
*
|
|
68
83
|
* @since 0.1.0
|
|
69
84
|
* @status stable
|
|
70
85
|
* @category overlays
|
|
@@ -72,10 +87,12 @@ declare const HelixHeadlessMenu_base: (new (...args: any[]) => import("@fusion.d
|
|
|
72
87
|
* @documentation https://fusion.dev/helix/components/menu
|
|
73
88
|
*
|
|
74
89
|
* @slot - The items.
|
|
90
|
+
* @slot no-results - What the menu shows when the filter keeps no rows, for content richer than `no-results-text`.
|
|
75
91
|
* @slot trigger - The control that opens the menu, or with `context` the area a context menu opens over.
|
|
76
92
|
*
|
|
77
93
|
* @event {HelixBeforeCloseEvent} helix-before-close - The menu is about to close. Cancelable: cancel it to keep it open.
|
|
78
94
|
* @event {HelixBeforeOpenEvent} helix-before-open - The menu is about to open. Cancelable: cancel it to keep it closed.
|
|
95
|
+
* @event {HelixFilterEvent} helix-filter - The text in the filter field changed, with the new text in `detail.value`. Cancelable: cancel it to keep every row showing and narrow the rows yourself.
|
|
79
96
|
* @event {HelixSelectEvent} helix-select - An item was chosen. Cancelable: cancel it to keep the menu open.
|
|
80
97
|
* @event {ToggleEvent} toggle - The menu opened or closed. The event carries `newState` and `oldState`.
|
|
81
98
|
*
|
|
@@ -85,7 +102,8 @@ declare const HelixHeadlessMenu_base: (new (...args: any[]) => import("@fusion.d
|
|
|
85
102
|
* @csspart header - The bar above a submenu, holding the way back and its name.
|
|
86
103
|
* @csspart live - A live region that repeats the empty and loading text, so screen readers announce it when it changes. Visually hidden in the styled tier.
|
|
87
104
|
* @csspart loading - The text shown while the items are loading.
|
|
88
|
-
* @csspart
|
|
105
|
+
* @csspart search-field - With `filterable`, the field that narrows the rows.
|
|
106
|
+
* @csspart surface - The floating panel the menu is drawn on. It is the `menu` itself, and with `filterable` it holds the filter field and then the `menu`.
|
|
89
107
|
* @csspart title - The name of the submenu that is showing.
|
|
90
108
|
*
|
|
91
109
|
* @cssproperty --floating-anchor-inline-size - The trigger's width, written on the panel while it is open, as in `min-inline-size: var(--floating-anchor-inline-size)`. Read it; a value set here is written over.
|
|
@@ -123,6 +141,31 @@ export declare class HelixHeadlessMenu extends HelixHeadlessMenu_base {
|
|
|
123
141
|
* asks for one, with a right-click, a long press, Shift+F10 or the menu key.
|
|
124
142
|
*/
|
|
125
143
|
context: boolean;
|
|
144
|
+
/**
|
|
145
|
+
* The text the rows are filtered by, which the filter field holds.
|
|
146
|
+
*
|
|
147
|
+
* Cleared as the menu closes and as a submenu opens or closes, so each page
|
|
148
|
+
* starts with every row showing.
|
|
149
|
+
*/
|
|
150
|
+
filter: string;
|
|
151
|
+
/**
|
|
152
|
+
* Whether the menu shows a field above its rows that narrows them as the
|
|
153
|
+
* reader types, with focus staying in the field.
|
|
154
|
+
*/
|
|
155
|
+
filterable: boolean;
|
|
156
|
+
/**
|
|
157
|
+
* The filter field's name and placeholder.
|
|
158
|
+
*/
|
|
159
|
+
filterLabel: string;
|
|
160
|
+
/**
|
|
161
|
+
* Whether a row is kept by the filter: a function given the row's words and
|
|
162
|
+
* the filter, trimmed, that returns `true` to keep it showing, as React
|
|
163
|
+
* Aria's `Autocomplete` takes `filter`. See Listbox's `filterMatch`.
|
|
164
|
+
*
|
|
165
|
+
* Absent, a row is kept where its words hold the filter, in any case. A
|
|
166
|
+
* property only, there being no way to write a function in markup.
|
|
167
|
+
*/
|
|
168
|
+
filterMatch?: ((text: string, filter: string) => boolean) | undefined;
|
|
126
169
|
/**
|
|
127
170
|
* Accessible name for the surface.
|
|
128
171
|
*
|
|
@@ -153,6 +196,10 @@ export declare class HelixHeadlessMenu extends HelixHeadlessMenu_base {
|
|
|
153
196
|
* to open the menu. Translate it with the rest of your interface.
|
|
154
197
|
*/
|
|
155
198
|
longPressLabel: string;
|
|
199
|
+
/**
|
|
200
|
+
* The text shown when the filter keeps no rows.
|
|
201
|
+
*/
|
|
202
|
+
noResultsText: string;
|
|
156
203
|
/**
|
|
157
204
|
* Space between the trigger and the surface, in pixels.
|
|
158
205
|
*/
|
|
@@ -234,6 +281,35 @@ export declare class HelixHeadlessMenu extends HelixHeadlessMenu_base {
|
|
|
234
281
|
private get trigger();
|
|
235
282
|
/** The items on the page that is showing, which is what the keys move over. */
|
|
236
283
|
private get items();
|
|
284
|
+
/** The element that is the `menu`: the surface, or with `filterable` the box after the field. */
|
|
285
|
+
private get menuBox();
|
|
286
|
+
/** The filter field, with `filterable`. */
|
|
287
|
+
private get field();
|
|
288
|
+
/** The submenu the filter narrows, or null for the top page. */
|
|
289
|
+
private filterPage;
|
|
290
|
+
/** The filter a listener refused, which narrows nothing. */
|
|
291
|
+
private refused;
|
|
292
|
+
/** Whether the filter is narrowing the rows. */
|
|
293
|
+
private get filtering();
|
|
294
|
+
/** Whether the filter keeps a row: one of another page, or one whose words it matches. */
|
|
295
|
+
private kept;
|
|
296
|
+
/**
|
|
297
|
+
* Asks to filter by new text, which the field and `filter` then hold. A
|
|
298
|
+
* refusal keeps every row showing, for a page that narrows them itself.
|
|
299
|
+
*/
|
|
300
|
+
private requestFilter;
|
|
301
|
+
private readonly handleFilterInput;
|
|
302
|
+
/** Filters by what the reader typed, and highlights the first row kept. */
|
|
303
|
+
private filterBy;
|
|
304
|
+
/** The row the keys reached while focus stays in the filter field. */
|
|
305
|
+
private highlighted;
|
|
306
|
+
/**
|
|
307
|
+
* Moves the reader to a row: focus, or with `filterable` the highlight,
|
|
308
|
+
* which the field names with `aria-activedescendant`. An element reference,
|
|
309
|
+
* because the field is in this shadow root and the row in the tree around
|
|
310
|
+
* it, the one direction a reference crosses.
|
|
311
|
+
*/
|
|
312
|
+
private reach;
|
|
237
313
|
/** Opens the menu. */
|
|
238
314
|
show(): void;
|
|
239
315
|
/** Opens the menu with focus on its first or its last row. */
|
|
@@ -260,6 +336,8 @@ export declare class HelixHeadlessMenu extends HelixHeadlessMenu_base {
|
|
|
260
336
|
* below the fold of a scrolled panel.
|
|
261
337
|
*/
|
|
262
338
|
private readonly handleFocusIn;
|
|
339
|
+
/** Scrolls the panel that holds a row to show it. */
|
|
340
|
+
private revealRow;
|
|
263
341
|
private readonly handleKeydown;
|
|
264
342
|
/**
|
|
265
343
|
* Down and Up on the trigger open the menu on its first and its last row.
|
|
@@ -276,6 +354,11 @@ export declare class HelixHeadlessMenu extends HelixHeadlessMenu_base {
|
|
|
276
354
|
* `open-on="long-press"`, or a touch or a pen held on a context menu's area.
|
|
277
355
|
*/
|
|
278
356
|
private readonly handlePointerDown;
|
|
357
|
+
/**
|
|
358
|
+
* Keeps focus in the filter field through a press on a row or the way
|
|
359
|
+
* back, as React Aria's virtual focus does: the press still chooses.
|
|
360
|
+
*/
|
|
361
|
+
private readonly handleMouseDown;
|
|
279
362
|
private readonly handlePointerMove;
|
|
280
363
|
private readonly handlePointerUp;
|
|
281
364
|
private readonly handlePointerCancel;
|