@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.
- package/custom-elements.json +1085 -66
- package/dist/command/root/command-root.d.ts +19 -5
- package/dist/command/root/command-root.js +14 -4
- package/dist/data-table/root/data-table-root.js +5 -2
- 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/list/item/list-item.d.ts +15 -1
- package/dist/list/item/list-item.js +20 -1
- package/dist/list/root/list-root.d.ts +29 -1
- package/dist/list/root/list-root.js +44 -10
- package/dist/listbox/root/listbox-root.d.ts +19 -2
- package/dist/listbox/root/listbox-root.js +16 -8
- package/dist/map/root/map-root.d.ts +4 -0
- package/dist/map/root/map-root.js +1 -1
- 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 +126 -11
- package/dist/menu/root/menu-root.js +336 -26
- package/dist/notifications/root/notifications-root.d.ts +20 -0
- package/dist/notifications/root/notifications-root.js +32 -1
- package/dist/number-input/root/number-input-root.d.ts +7 -4
- package/dist/number-input/root/number-input-root.js +3 -3
- package/dist/progress/root/progress-root.js +1 -1
- package/dist/select/root/select-root.d.ts +8 -1
- package/dist/select/root/select-root.js +8 -5
- package/dist/skeleton/root/skeleton-root.d.ts +2 -1
- package/dist/skeleton/root/skeleton-root.js +1 -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 +653 -74
- package/types/vue.d.ts +133 -16
- package/types/words.d.ts +14 -0
|
@@ -39,8 +39,9 @@ declare const HelixHeadlessCommand_base: (new (...args: any[]) => import("@fusio
|
|
|
39
39
|
*
|
|
40
40
|
* Choosing the opener says `helix-select` with its value, and canceling it
|
|
41
41
|
* keeps the page that is shown. The list then shows the page, named by the
|
|
42
|
-
* opener's words, and
|
|
43
|
-
*
|
|
42
|
+
* opener's words, and a screen reader hears those words. Backspace in an empty
|
|
43
|
+
* filter goes back, and a screen reader hears the name of the page it goes
|
|
44
|
+
* back to. `page` says which page is shown.
|
|
44
45
|
*
|
|
45
46
|
* The filter is cleared when the palette closes, so each opening starts from
|
|
46
47
|
* the full list. Combobox and Select do the same. As typing narrows the list, a
|
|
@@ -62,9 +63,10 @@ declare const HelixHeadlessCommand_base: (new (...args: any[]) => import("@fusio
|
|
|
62
63
|
* | Tab | Stays in the palette, which is modal. With `inline`, moves on through the page. |
|
|
63
64
|
*
|
|
64
65
|
* **ARIA.** The palette is a modal `dialog` named by `label`, or with
|
|
65
|
-
* `inline` no dialog at all. Inside it the filter field is a
|
|
66
|
-
* `aria-
|
|
67
|
-
* `
|
|
66
|
+
* `inline` no dialog at all. Inside it the filter field is a text field with
|
|
67
|
+
* `aria-autocomplete="list"`, `aria-controls` on the list, and
|
|
68
|
+
* `aria-activedescendant` on the highlighted command, as the field of React
|
|
69
|
+
* Aria's `Autocomplete` is. The commands are a `listbox` of `option`s. How many commands typing left, the empty text and
|
|
68
70
|
* the loading text are said in a polite live region.
|
|
69
71
|
*
|
|
70
72
|
* @since 0.1.0
|
|
@@ -109,6 +111,7 @@ declare const HelixHeadlessCommand_base: (new (...args: any[]) => import("@fusio
|
|
|
109
111
|
* @csspart listbox-loading - The text shown while the commands are loading.
|
|
110
112
|
* @csspart listbox-search - The region that holds the filter field.
|
|
111
113
|
* @csspart listbox-search-field - The filter field.
|
|
114
|
+
* @csspart live - The region a screen reader announces the page shown from, by the words of the command that opens it, drawn once there is one to say. Visually hidden in the styled tier.
|
|
112
115
|
*
|
|
113
116
|
* @cssstate empty - The list shows no commands: there are none, or typing hid every one.
|
|
114
117
|
* @cssstate error - While `load-error` is set.
|
|
@@ -132,6 +135,15 @@ export declare class HelixHeadlessCommand extends HelixHeadlessCommand_base {
|
|
|
132
135
|
* `0` filters on every keystroke. See Listbox's `filter-delay`.
|
|
133
136
|
*/
|
|
134
137
|
filterDelay: number;
|
|
138
|
+
/**
|
|
139
|
+
* Whether a command is kept by the filter: a function given the command's
|
|
140
|
+
* words and the filter, trimmed, that returns `true` to keep it showing, as
|
|
141
|
+
* React Aria's `Autocomplete` takes `filter`. See Listbox's `filterMatch`.
|
|
142
|
+
*
|
|
143
|
+
* Absent, a command is kept where its words hold the filter, in any case. A
|
|
144
|
+
* property only, there being no way to write a function in markup.
|
|
145
|
+
*/
|
|
146
|
+
filterMatch?: ((text: string, filter: string) => boolean) | undefined;
|
|
135
147
|
/**
|
|
136
148
|
* The key that opens the palette, written as `mod+k`, `shift+p`, or `k`,
|
|
137
149
|
* in Kbd's chord spelling: `mod` is Command or Control, `ctrl`, `cmd`,
|
|
@@ -270,6 +282,8 @@ export declare class HelixHeadlessCommand extends HelixHeadlessCommand_base {
|
|
|
270
282
|
* filter starts empty, as it does on opening, and the field keeps focus.
|
|
271
283
|
*/
|
|
272
284
|
private goTo;
|
|
285
|
+
/** The region the page shown is said from as it changes. */
|
|
286
|
+
private readonly status;
|
|
273
287
|
/** Backspace in an empty filter, on a page, goes back to the page before. */
|
|
274
288
|
private readonly handleBack;
|
|
275
289
|
/**
|
|
@@ -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 { customElement, HelixElement, HelixSelectEvent, MixinControllerOpen, matchesChord, words, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { customElement, drawStatus, HelixElement, HelixSelectEvent, MixinControllerOpen, matchesChord, StatusController, words, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { nothing } from 'lit';
|
|
9
9
|
import { property, state } from 'lit/decorators.js';
|
|
10
10
|
import { html } from 'lit/static-html.js';
|
|
@@ -61,6 +61,7 @@ let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpe
|
|
|
61
61
|
}
|
|
62
62
|
this.requestOpen(false);
|
|
63
63
|
};
|
|
64
|
+
this.status = new StatusController(this);
|
|
64
65
|
this.handleBack = (event) => {
|
|
65
66
|
if (event.key !== 'Backspace' ||
|
|
66
67
|
this.page === '' ||
|
|
@@ -124,7 +125,12 @@ let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpe
|
|
|
124
125
|
this.trail = trail;
|
|
125
126
|
this.page = page;
|
|
126
127
|
this.listbox?.requestFilter('');
|
|
127
|
-
void this.updateComplete.then(() =>
|
|
128
|
+
void this.updateComplete.then(() => {
|
|
129
|
+
this.searchField?.focus();
|
|
130
|
+
if (this.listName !== '') {
|
|
131
|
+
this.status.say(this.listName);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
128
134
|
}
|
|
129
135
|
get listName() {
|
|
130
136
|
if (this.page === '') {
|
|
@@ -210,7 +216,7 @@ let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpe
|
|
|
210
216
|
}
|
|
211
217
|
}}
|
|
212
218
|
>
|
|
213
|
-
${this.renderListbox()}
|
|
219
|
+
${this.renderListbox()}${drawStatus(this.status)}
|
|
214
220
|
<!-- Handed on only while something fills it, as the list's slots are,
|
|
215
221
|
so an empty header draws nothing in the dialog. -->
|
|
216
222
|
<slot
|
|
@@ -231,6 +237,7 @@ let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpe
|
|
|
231
237
|
aria-label=${this.listName}
|
|
232
238
|
empty-text=${this.emptyText}
|
|
233
239
|
filter-delay=${this.filterDelay}
|
|
240
|
+
.filterMatch=${this.filterMatch}
|
|
234
241
|
filter-label=${this.placeholder}
|
|
235
242
|
load-error=${this.loadError}
|
|
236
243
|
loading-text=${this.loadingText}
|
|
@@ -277,7 +284,7 @@ let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpe
|
|
|
277
284
|
}
|
|
278
285
|
render() {
|
|
279
286
|
if (this.inline) {
|
|
280
|
-
return html `<slot name="header"></slot>${this.renderListbox()}${this.renderFooter(nothing)}`;
|
|
287
|
+
return html `<slot name="header"></slot>${this.renderListbox()}${drawStatus(this.status)}${this.renderFooter(nothing)}`;
|
|
281
288
|
}
|
|
282
289
|
return html `${this.renderDialog()}`;
|
|
283
290
|
}
|
|
@@ -289,6 +296,9 @@ __decorate([
|
|
|
289
296
|
__decorate([
|
|
290
297
|
property({ attribute: 'filter-delay', type: Number })
|
|
291
298
|
], HelixHeadlessCommand.prototype, "filterDelay", void 0);
|
|
299
|
+
__decorate([
|
|
300
|
+
property({ attribute: false })
|
|
301
|
+
], HelixHeadlessCommand.prototype, "filterMatch", void 0);
|
|
292
302
|
__decorate([
|
|
293
303
|
property()
|
|
294
304
|
], HelixHeadlessCommand.prototype, "hotkey", void 0);
|
|
@@ -1015,7 +1015,7 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
|
|
|
1015
1015
|
: '';
|
|
1016
1016
|
}
|
|
1017
1017
|
}
|
|
1018
|
-
renderCell(column, row, picked, place, stop, header) {
|
|
1018
|
+
renderCell(column, row, picked, place, stop, header, id) {
|
|
1019
1019
|
const edge = edgeOf(column.textAlign);
|
|
1020
1020
|
const placed = header && this.childField ? this.tree().placed.get(row) : undefined;
|
|
1021
1021
|
const style = [
|
|
@@ -1027,6 +1027,7 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
|
|
|
1027
1027
|
return html `<span
|
|
1028
1028
|
part="cell${picked ? ' cell-selected' : ''}${header ? ' cell-header' : ''}"
|
|
1029
1029
|
role=${header ? 'rowheader' : 'gridcell'}
|
|
1030
|
+
id=${id ?? nothing}
|
|
1030
1031
|
tabindex=${stop ? '0' : '-1'}
|
|
1031
1032
|
data-column=${place}
|
|
1032
1033
|
style=${style || nothing}
|
|
@@ -1083,10 +1084,12 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
|
|
|
1083
1084
|
const stop = (place) => index === stopRow && place === stopColumn;
|
|
1084
1085
|
const moveKey = grips ? this.moveKeyOf(row) : '';
|
|
1085
1086
|
const marked = mark?.key === moveKey ? mark.edge : undefined;
|
|
1087
|
+
const header = named ? `row-header-${index}` : undefined;
|
|
1086
1088
|
return html `<div
|
|
1087
1089
|
part="row${held?.key === moveKey ? ' row-dragging' : ''}${marked === 'before' ? ' row-drop-before' : ''}${marked === 'after' ? ' row-drop-after' : ''}"
|
|
1088
1090
|
role="row"
|
|
1089
1091
|
aria-rowindex=${this.rowIndex(index)}
|
|
1092
|
+
aria-labelledby=${header ?? nothing}
|
|
1090
1093
|
aria-selected=${chosen ? String(picked) : nothing}
|
|
1091
1094
|
aria-level=${placed ? placed.level : nothing}
|
|
1092
1095
|
aria-posinset=${placed ? placed.position : nothing}
|
|
@@ -1103,7 +1106,7 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
|
|
|
1103
1106
|
data-column=${this.selectAt}
|
|
1104
1107
|
>${this.renderCheckbox(formatWords(this, 'selectRowLabel', { row: this.rowName(row, key) }), picked, false, (event) => this.toggleRow(key, event))}</span
|
|
1105
1108
|
>`
|
|
1106
|
-
: nothing}${columns.map((column, place) => this.renderCell(column, row, picked, place + offset, stop(place + offset), column === named))}
|
|
1109
|
+
: nothing}${columns.map((column, place) => this.renderCell(column, row, picked, place + offset, stop(place + offset), column === named, column === named ? header : undefined))}
|
|
1107
1110
|
</div>`;
|
|
1108
1111
|
})}`;
|
|
1109
1112
|
}
|
|
@@ -25,6 +25,16 @@ import { nothing, type PropertyValues, type TemplateResult } from 'lit';
|
|
|
25
25
|
* `aria-haspopup`, the same way `Menu` marks a row with a submenu. The server
|
|
26
26
|
* render leaves both out, because the server cannot see the children.
|
|
27
27
|
*
|
|
28
|
+
* **A row with an `href` is a link.** The row itself renders as an `<a>`
|
|
29
|
+
* carrying `menuitem`, as `Menu.Item` does, so a middle click, a modified
|
|
30
|
+
* click and the status bar keep working. Enter follows it, and Space does too.
|
|
31
|
+
* Choosing it fires `helix-select` first: cancel the event and the link is not
|
|
32
|
+
* followed, so a router can take the navigation. A row that opens a page, or a
|
|
33
|
+
* disabled row, renders no link.
|
|
34
|
+
*
|
|
35
|
+
* A development build warns when a row has no words to be named by, such as a
|
|
36
|
+
* row holding only an icon with no label.
|
|
37
|
+
*
|
|
28
38
|
* The row is named by its label alone. Words in `description`, a line under
|
|
29
39
|
* the label, and in `end` describe it instead, through `aria-describedby`, as
|
|
30
40
|
* `Menu.Item` does: a reader hears "Theme", then "Dark".
|
|
@@ -43,7 +53,7 @@ import { nothing, type PropertyValues, type TemplateResult } from 'lit';
|
|
|
43
53
|
*
|
|
44
54
|
* @event {HelixSelectEvent} helix-select - Fired when the row is chosen. Cancelable: canceling it stops the change and leaves the decision to you.
|
|
45
55
|
*
|
|
46
|
-
* @csspart item - The row itself.
|
|
56
|
+
* @csspart item - The row itself, which is an `<a>` where `href` is set.
|
|
47
57
|
* @csspart label - The row's label.
|
|
48
58
|
* @csspart mark - The caret at the end of a row that opens a page, after the `end` slot. The caret points, so a stylesheet mirrors it in a right-to-left document.
|
|
49
59
|
*/
|
|
@@ -52,6 +62,11 @@ export declare class HelixHeadlessDrilldownItem extends HelixElement {
|
|
|
52
62
|
* Whether the row can be reached at all.
|
|
53
63
|
*/
|
|
54
64
|
disabled: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Whether the link downloads the file when the row is chosen. Only
|
|
67
|
+
* meaningful beside an `href`, and only for a file served from this origin.
|
|
68
|
+
*/
|
|
69
|
+
download: boolean;
|
|
55
70
|
/**
|
|
56
71
|
* Whether this row holds the region's tab stop. Set by the drill-down.
|
|
57
72
|
*
|
|
@@ -66,6 +81,16 @@ export declare class HelixHeadlessDrilldownItem extends HelixElement {
|
|
|
66
81
|
* lands inside this shadow root instead.
|
|
67
82
|
*/
|
|
68
83
|
focusable: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* The URL the row links to. Setting it renders the row as a link, unless the
|
|
86
|
+
* row opens a page or is disabled.
|
|
87
|
+
*/
|
|
88
|
+
href: string;
|
|
89
|
+
/**
|
|
90
|
+
* @internal Whether the row is a list item of a drilldown with
|
|
91
|
+
* `navigation`. Written by the stack.
|
|
92
|
+
*/
|
|
93
|
+
listed: boolean;
|
|
69
94
|
/**
|
|
70
95
|
* @internal Read by a parent to identify its children across the light DOM,
|
|
71
96
|
* which is cheaper than `instanceof` and is why this is public rather than
|
|
@@ -83,11 +108,19 @@ export declare class HelixHeadlessDrilldownItem extends HelixElement {
|
|
|
83
108
|
* level. Written by the stack.
|
|
84
109
|
*/
|
|
85
110
|
onPath: boolean;
|
|
111
|
+
/**
|
|
112
|
+
* The link's `rel`. Set it whenever you set `target="_blank"`.
|
|
113
|
+
*/
|
|
114
|
+
rel: string;
|
|
86
115
|
/**
|
|
87
116
|
* @internal Whether this row's own page is the one showing. Written by the
|
|
88
117
|
* stack.
|
|
89
118
|
*/
|
|
90
119
|
showingPage: boolean;
|
|
120
|
+
/**
|
|
121
|
+
* Where the link opens, for rows that should open outside the current page.
|
|
122
|
+
*/
|
|
123
|
+
target: string;
|
|
91
124
|
/**
|
|
92
125
|
* Identifies the row to your event listeners.
|
|
93
126
|
*/
|
|
@@ -113,6 +146,7 @@ export declare class HelixHeadlessDrilldownItem extends HelixElement {
|
|
|
113
146
|
* that skipped.
|
|
114
147
|
*/
|
|
115
148
|
protected willUpdate(changed: PropertyValues): void;
|
|
149
|
+
protected updated(changed: PropertyValues): void;
|
|
116
150
|
protected firstUpdated(changed: PropertyValues): void;
|
|
117
151
|
/**
|
|
118
152
|
* Re-reads whether a page is slotted, from whichever `page` slot is current.
|
|
@@ -154,6 +188,20 @@ export declare class HelixHeadlessDrilldownItem extends HelixElement {
|
|
|
154
188
|
get selected(): boolean;
|
|
155
189
|
/** Fires `helix-select` for this row. */
|
|
156
190
|
select(): void;
|
|
191
|
+
/** Fires `helix-select`, and returns whether the choice stood. */
|
|
192
|
+
private choose;
|
|
193
|
+
/**
|
|
194
|
+
* A press on the row. On a link, a refused `helix-select` stops the link
|
|
195
|
+
* from being followed, so a router can take the navigation.
|
|
196
|
+
*/
|
|
197
|
+
private readonly handleClick;
|
|
198
|
+
/**
|
|
199
|
+
* Space on a link presses it, which an anchor does not do on its own. Enter
|
|
200
|
+
* is the anchor's, which fires a click and follows the link.
|
|
201
|
+
*/
|
|
202
|
+
private readonly handleLinkKeydown;
|
|
203
|
+
/** Whether the row renders as a link. */
|
|
204
|
+
private get linked();
|
|
157
205
|
/**
|
|
158
206
|
* The caret of a row that opens a page, as the `chevron` slot's fallback, so
|
|
159
207
|
* an icon of your own replaces it by being slotted.
|
|
@@ -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 { customElement, deepActiveElement, HelixElement, HelixSelectEvent, slotFilled, slotNamed, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { auditName, customElement, deepActiveElement, HelixElement, HelixSelectEvent, hostAria, slotFilled, slotNamed, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { caretRight } from '@fusion.dev/helix-icons';
|
|
9
9
|
import { html, nothing } from 'lit';
|
|
10
10
|
import { property, state } from 'lit/decorators.js';
|
|
@@ -13,11 +13,16 @@ let HelixHeadlessDrilldownItem = class HelixHeadlessDrilldownItem extends HelixE
|
|
|
13
13
|
constructor() {
|
|
14
14
|
super(...arguments);
|
|
15
15
|
this.disabled = false;
|
|
16
|
+
this.download = false;
|
|
16
17
|
this.focusable = false;
|
|
18
|
+
this.href = '';
|
|
19
|
+
this.listed = false;
|
|
17
20
|
this.isDrilldownItem = true;
|
|
18
21
|
this.onPage = true;
|
|
19
22
|
this.onPath = false;
|
|
23
|
+
this.rel = '';
|
|
20
24
|
this.showingPage = false;
|
|
25
|
+
this.target = '';
|
|
21
26
|
this.value = '';
|
|
22
27
|
this.page = false;
|
|
23
28
|
this.description = false;
|
|
@@ -30,9 +35,23 @@ let HelixHeadlessDrilldownItem = class HelixHeadlessDrilldownItem extends HelixE
|
|
|
30
35
|
this.description = slotFilled(slotNamed(this.renderRoot, 'description'));
|
|
31
36
|
this.end = slotFilled(slotNamed(this.renderRoot, 'end'));
|
|
32
37
|
};
|
|
38
|
+
this.handleClick = (event) => {
|
|
39
|
+
if (!this.choose() && this.linked) {
|
|
40
|
+
event.preventDefault();
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
this.handleLinkKeydown = (event) => {
|
|
44
|
+
if (event.key !== ' ') {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
event.preventDefault();
|
|
48
|
+
event.currentTarget.click();
|
|
49
|
+
};
|
|
33
50
|
}
|
|
34
51
|
willUpdate(changed) {
|
|
35
52
|
super.willUpdate(changed);
|
|
53
|
+
const item = this.listed && this.onPage && !this.showingPage && !this.onPath;
|
|
54
|
+
hostAria(this.internals, { role: item ? 'listitem' : '' });
|
|
36
55
|
if (this.childrenKnown) {
|
|
37
56
|
const named = (name) => this.knownChildren.some((child) => child.slot === name);
|
|
38
57
|
this.page = named('page');
|
|
@@ -40,6 +59,12 @@ let HelixHeadlessDrilldownItem = class HelixHeadlessDrilldownItem extends HelixE
|
|
|
40
59
|
this.end = named('end');
|
|
41
60
|
}
|
|
42
61
|
}
|
|
62
|
+
updated(changed) {
|
|
63
|
+
super.updated(changed);
|
|
64
|
+
if (this.partNamed('item')) {
|
|
65
|
+
auditName(this, () => this.partNamed('item'), 'Give the row words in its default slot, or a label on the icon it holds.');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
43
68
|
firstUpdated(changed) {
|
|
44
69
|
super.firstUpdated(changed);
|
|
45
70
|
this.finishHydration();
|
|
@@ -61,10 +86,16 @@ let HelixHeadlessDrilldownItem = class HelixHeadlessDrilldownItem extends HelixE
|
|
|
61
86
|
return this.active;
|
|
62
87
|
}
|
|
63
88
|
select() {
|
|
89
|
+
this.choose();
|
|
90
|
+
}
|
|
91
|
+
choose() {
|
|
64
92
|
if (this.disabled) {
|
|
65
|
-
return;
|
|
93
|
+
return false;
|
|
66
94
|
}
|
|
67
|
-
this.dispatchEvent(new HelixSelectEvent({ value: this.value }));
|
|
95
|
+
return this.dispatchEvent(new HelixSelectEvent({ value: this.value }));
|
|
96
|
+
}
|
|
97
|
+
get linked() {
|
|
98
|
+
return this.href !== '' && !this.disabled && !this.page;
|
|
68
99
|
}
|
|
69
100
|
renderMark() {
|
|
70
101
|
return html `<slot name="chevron" aria-hidden="true"
|
|
@@ -80,18 +111,7 @@ let HelixHeadlessDrilldownItem = class HelixHeadlessDrilldownItem extends HelixE
|
|
|
80
111
|
if (!this.onPage) {
|
|
81
112
|
return nothing;
|
|
82
113
|
}
|
|
83
|
-
|
|
84
|
-
part="item"
|
|
85
|
-
type="button"
|
|
86
|
-
role="menuitem"
|
|
87
|
-
aria-describedby=${this.describedBy}
|
|
88
|
-
aria-disabled=${this.disabled ? 'true' : nothing}
|
|
89
|
-
aria-haspopup=${this.page ? 'menu' : nothing}
|
|
90
|
-
tabindex=${this.focusable ? '0' : '-1'}
|
|
91
|
-
?data-described=${this.description}
|
|
92
|
-
@click=${() => this.select()}
|
|
93
|
-
>
|
|
94
|
-
<slot name="start"></slot>
|
|
114
|
+
const inside = html `<slot name="start"></slot>
|
|
95
115
|
<span part="label"><slot></slot></span>
|
|
96
116
|
<slot
|
|
97
117
|
id="description"
|
|
@@ -101,26 +121,66 @@ let HelixHeadlessDrilldownItem = class HelixHeadlessDrilldownItem extends HelixE
|
|
|
101
121
|
@slotchange=${this.readWords}
|
|
102
122
|
></slot>
|
|
103
123
|
<slot id="end" name="end" aria-hidden="true" @slotchange=${this.readWords}></slot>
|
|
104
|
-
${this.page ? this.renderMark() : nothing}
|
|
105
|
-
|
|
124
|
+
${this.page ? this.renderMark() : nothing}`;
|
|
125
|
+
const row = this.linked
|
|
126
|
+
? html `<a
|
|
127
|
+
part="item"
|
|
128
|
+
href=${this.href}
|
|
129
|
+
target=${this.target || nothing}
|
|
130
|
+
rel=${this.rel || nothing}
|
|
131
|
+
?download=${this.download}
|
|
132
|
+
role=${this.listed ? nothing : 'menuitem'}
|
|
133
|
+
aria-describedby=${this.describedBy}
|
|
134
|
+
tabindex=${this.focusable ? '0' : '-1'}
|
|
135
|
+
?data-described=${this.description}
|
|
136
|
+
@click=${this.handleClick}
|
|
137
|
+
@keydown=${this.handleLinkKeydown}
|
|
138
|
+
>${inside}</a>`
|
|
139
|
+
: html `<button
|
|
140
|
+
part="item"
|
|
141
|
+
type="button"
|
|
142
|
+
role=${this.listed ? nothing : 'menuitem'}
|
|
143
|
+
aria-describedby=${this.describedBy}
|
|
144
|
+
aria-disabled=${this.disabled ? 'true' : nothing}
|
|
145
|
+
aria-haspopup=${this.page && !this.listed ? 'menu' : nothing}
|
|
146
|
+
tabindex=${this.focusable ? '0' : '-1'}
|
|
147
|
+
?data-described=${this.description}
|
|
148
|
+
@click=${this.handleClick}
|
|
149
|
+
>${inside}</button>`;
|
|
150
|
+
return html `${row}
|
|
106
151
|
<div hidden><slot name="page" @slotchange=${this.readPage}></slot></div>`;
|
|
107
152
|
}
|
|
108
153
|
};
|
|
109
154
|
__decorate([
|
|
110
155
|
property({ reflect: true, type: Boolean })
|
|
111
156
|
], HelixHeadlessDrilldownItem.prototype, "disabled", void 0);
|
|
157
|
+
__decorate([
|
|
158
|
+
property({ type: Boolean })
|
|
159
|
+
], HelixHeadlessDrilldownItem.prototype, "download", void 0);
|
|
112
160
|
__decorate([
|
|
113
161
|
property({ attribute: false })
|
|
114
162
|
], HelixHeadlessDrilldownItem.prototype, "focusable", void 0);
|
|
163
|
+
__decorate([
|
|
164
|
+
property()
|
|
165
|
+
], HelixHeadlessDrilldownItem.prototype, "href", void 0);
|
|
166
|
+
__decorate([
|
|
167
|
+
property({ attribute: false })
|
|
168
|
+
], HelixHeadlessDrilldownItem.prototype, "listed", void 0);
|
|
115
169
|
__decorate([
|
|
116
170
|
property({ attribute: false })
|
|
117
171
|
], HelixHeadlessDrilldownItem.prototype, "onPage", void 0);
|
|
118
172
|
__decorate([
|
|
119
173
|
property({ attribute: false })
|
|
120
174
|
], HelixHeadlessDrilldownItem.prototype, "onPath", void 0);
|
|
175
|
+
__decorate([
|
|
176
|
+
property()
|
|
177
|
+
], HelixHeadlessDrilldownItem.prototype, "rel", void 0);
|
|
121
178
|
__decorate([
|
|
122
179
|
property({ attribute: false })
|
|
123
180
|
], HelixHeadlessDrilldownItem.prototype, "showingPage", void 0);
|
|
181
|
+
__decorate([
|
|
182
|
+
property()
|
|
183
|
+
], HelixHeadlessDrilldownItem.prototype, "target", void 0);
|
|
124
184
|
__decorate([
|
|
125
185
|
property()
|
|
126
186
|
], HelixHeadlessDrilldownItem.prototype, "value", void 0);
|
|
@@ -20,6 +20,11 @@ declare const HelixHeadlessDrilldown_base: (new (...args: any[]) => import("@fus
|
|
|
20
20
|
* and reads the rest from the DOM. The behavior lives in `DrilldownController`,
|
|
21
21
|
* which `Menu` shares.
|
|
22
22
|
*
|
|
23
|
+
* **`page` is the page showing**, as the `value` of the row that opens it,
|
|
24
|
+
* and empty on the top page. Set it to show a page, such as one a link to a
|
|
25
|
+
* settings screen names. A reader's move updates it, and `helix-navigate`
|
|
26
|
+
* comes first, so you can refuse the move or take it yourself.
|
|
27
|
+
*
|
|
23
28
|
* After each move the host carries `data-direction`, `forward` into a page or
|
|
24
29
|
* `back` out of one, and records `:state(forward)` or `:state(back)`, so a
|
|
25
30
|
* stylesheet can slide the new page in from the side the reader is going. Both
|
|
@@ -27,13 +32,25 @@ declare const HelixHeadlessDrilldown_base: (new (...args: any[]) => import("@fus
|
|
|
27
32
|
* page, two moves the same way included. The styled tier slides the page and
|
|
28
33
|
* swaps it at once under `prefers-reduced-motion`.
|
|
29
34
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
35
|
+
* **Keyboard.** The drilldown is one tab stop, on the row last focused.
|
|
36
|
+
*
|
|
37
|
+
* | Key | What it does |
|
|
38
|
+
* |---|---|
|
|
39
|
+
* | Down Arrow, Up Arrow | Moves to the next or the previous row. |
|
|
40
|
+
* | Home, End | Moves to the first or the last row. |
|
|
41
|
+
* | A letter | Moves to the next row whose name starts with it. |
|
|
42
|
+
* | Enter, Space | Chooses the row: opens its page, follows its link, or sends `helix-select`. |
|
|
43
|
+
* | Right Arrow | Opens the row's page. Left Arrow on a right-to-left page. |
|
|
44
|
+
* | Left Arrow, Escape | Goes back one page, with focus on the row that opened it. Right Arrow on a right-to-left page. |
|
|
34
45
|
*
|
|
35
46
|
* The rows of the page showing are a `menu`, named by `label` on the top page
|
|
36
|
-
* and by the page's title below it.
|
|
47
|
+
* and by the page's title below it.
|
|
48
|
+
*
|
|
49
|
+
* **`navigation` makes it a site's navigation.** The host is a `navigation`
|
|
50
|
+
* landmark named by `label`, each page is a list, and each row is a list item
|
|
51
|
+
* holding a link or a button, as the APG's disclosure navigation is built,
|
|
52
|
+
* so a reader meets places to go in place of a menu of commands. The keys stay
|
|
53
|
+
* the same. The back button and the title sit above
|
|
37
54
|
* the menu. Disabled rows stay reachable with the arrow keys, and typing a
|
|
38
55
|
* letter moves to the next row whose name starts with it.
|
|
39
56
|
*
|
|
@@ -46,6 +63,7 @@ declare const HelixHeadlessDrilldown_base: (new (...args: any[]) => import("@fus
|
|
|
46
63
|
* @slot - The rows of the top page.
|
|
47
64
|
* @slot back-icon - Replaces the icon on the back button.
|
|
48
65
|
*
|
|
66
|
+
* @event {HelixNavigateEvent} helix-navigate - Fired before a reader moves into a page or back out of one. `detail.value` is the `value` of the row whose page is about to show, empty for the top page. Cancelable: cancel it to stay on the page showing.
|
|
49
67
|
* @event {HelixSelectEvent} helix-select - Fired when a row with no page is chosen. Cancelable: canceling it stops the change and leaves the decision to you.
|
|
50
68
|
*
|
|
51
69
|
* @csspart back - The button that goes back one page.
|
|
@@ -79,6 +97,18 @@ export declare class HelixHeadlessDrilldown extends HelixHeadlessDrilldown_base
|
|
|
79
97
|
* The text of the back button.
|
|
80
98
|
*/
|
|
81
99
|
backLabel: string;
|
|
100
|
+
/**
|
|
101
|
+
* Whether the drilldown is a site's navigation: a `navigation` landmark
|
|
102
|
+
* named by `label`, whose pages are lists of links. Without it, each page is
|
|
103
|
+
* a `menu`.
|
|
104
|
+
*/
|
|
105
|
+
navigation: boolean;
|
|
106
|
+
/**
|
|
107
|
+
* The page showing, as the `value` of the row that opens it. Empty is the
|
|
108
|
+
* top page. Setting it shows that page, and a value no row with a page has
|
|
109
|
+
* shows the top page.
|
|
110
|
+
*/
|
|
111
|
+
page: string;
|
|
82
112
|
/**
|
|
83
113
|
* Which way the last move went, for whatever animates the change.
|
|
84
114
|
*
|
|
@@ -138,6 +168,13 @@ export declare class HelixHeadlessDrilldown extends HelixHeadlessDrilldown_base
|
|
|
138
168
|
* actually showing.
|
|
139
169
|
*/
|
|
140
170
|
private placeTabStop;
|
|
171
|
+
/**
|
|
172
|
+
* Asks before a reader's move to a page, the row's or the top one, and
|
|
173
|
+
* returns whether the move may go ahead.
|
|
174
|
+
*/
|
|
175
|
+
private navigate;
|
|
176
|
+
/** The row whose page holds a row, or null for the top page. */
|
|
177
|
+
private holderOf;
|
|
141
178
|
/** Shows a row's page, and puts focus on the first row of it. */
|
|
142
179
|
private enter;
|
|
143
180
|
/**
|
|
@@ -149,11 +186,14 @@ export declare class HelixHeadlessDrilldown extends HelixHeadlessDrilldown_base
|
|
|
149
186
|
* list they have already read.
|
|
150
187
|
*/
|
|
151
188
|
back(): boolean;
|
|
189
|
+
/** Shows the page of the row with a value, or the top page where no row with a page has it. */
|
|
190
|
+
private showPage;
|
|
152
191
|
private readonly handleFocusIn;
|
|
153
192
|
private readonly handleSelect;
|
|
154
193
|
private readonly handleKeydown;
|
|
155
194
|
connectedCallback(): void;
|
|
156
195
|
disconnectedCallback(): void;
|
|
196
|
+
protected willUpdate(changed: PropertyValues): void;
|
|
157
197
|
protected updated(changed: PropertyValues): void;
|
|
158
198
|
/**
|
|
159
199
|
* Writes which way the last move went. After a move, the marks come off and
|
|
@@ -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 { AriaForwardController, customElement, DrilldownController, HelixElement, inlineStep, KeyboardNavigationController, keyClaimed, MixinCollection, slottedWords, typeaheadByWords, words, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { AriaForwardController, customElement, DrilldownController, HelixElement, HelixNavigateEvent, hostAria, inlineStep, KeyboardNavigationController, keyClaimed, MixinCollection, slottedWords, typeaheadByWords, words, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { caretLeft } from '@fusion.dev/helix-icons';
|
|
9
9
|
import { html, nothing } from 'lit';
|
|
10
10
|
import { property, state } from 'lit/decorators.js';
|
|
@@ -15,6 +15,8 @@ let HelixHeadlessDrilldown = class HelixHeadlessDrilldown extends MixinCollectio
|
|
|
15
15
|
super(...arguments);
|
|
16
16
|
this.label = 'Navigation';
|
|
17
17
|
this.backLabel = 'Back';
|
|
18
|
+
this.navigation = false;
|
|
19
|
+
this.page = '';
|
|
18
20
|
this.moved = false;
|
|
19
21
|
this.drilldown = new DrilldownController(this, {
|
|
20
22
|
isRow: (element) => 'isDrilldownItem' in element && element.isDrilldownItem === true,
|
|
@@ -97,24 +99,50 @@ let HelixHeadlessDrilldown = class HelixHeadlessDrilldown extends MixinCollectio
|
|
|
97
99
|
item.focusable = item === stop;
|
|
98
100
|
}
|
|
99
101
|
}
|
|
102
|
+
navigate(to) {
|
|
103
|
+
return this.dispatchEvent(new HelixNavigateEvent({ value: to?.value ?? '' }));
|
|
104
|
+
}
|
|
105
|
+
holderOf(row) {
|
|
106
|
+
const parent = row.parentElement;
|
|
107
|
+
return row.slot === 'page' && parent instanceof HelixHeadlessDrilldownItem ? parent : null;
|
|
108
|
+
}
|
|
100
109
|
enter(item) {
|
|
101
|
-
if (!this.drilldown.enter(item)) {
|
|
110
|
+
if (!this.drilldown.hasPage(item) || !this.navigate(item) || !this.drilldown.enter(item)) {
|
|
102
111
|
return;
|
|
103
112
|
}
|
|
113
|
+
this.page = item.value;
|
|
104
114
|
this.direction = 'forward';
|
|
105
115
|
this.moved = true;
|
|
106
116
|
this.updateComplete.then(() => this.items[0]?.focusItem());
|
|
107
117
|
}
|
|
108
118
|
back() {
|
|
109
119
|
const leaving = this.drilldown.page;
|
|
110
|
-
if (
|
|
120
|
+
if (leaving === null) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
const to = this.holderOf(leaving);
|
|
124
|
+
if (!this.navigate(to) || !this.drilldown.back()) {
|
|
111
125
|
return false;
|
|
112
126
|
}
|
|
127
|
+
this.page = to?.value ?? '';
|
|
113
128
|
this.direction = 'back';
|
|
114
129
|
this.moved = true;
|
|
115
130
|
this.updateComplete.then(() => leaving?.focusItem());
|
|
116
131
|
return true;
|
|
117
132
|
}
|
|
133
|
+
showPage(value) {
|
|
134
|
+
const row = value === ''
|
|
135
|
+
? undefined
|
|
136
|
+
: [...this.querySelectorAll('*')].find((one) => one instanceof HelixHeadlessDrilldownItem &&
|
|
137
|
+
one.value === value &&
|
|
138
|
+
this.drilldown.hasPage(one));
|
|
139
|
+
if (row) {
|
|
140
|
+
this.drilldown.enter(row);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
this.drilldown.reset();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
118
146
|
connectedCallback() {
|
|
119
147
|
super.connectedCallback();
|
|
120
148
|
this.addEventListener('helix-select', this.handleSelect, true);
|
|
@@ -127,6 +155,17 @@ let HelixHeadlessDrilldown = class HelixHeadlessDrilldown extends MixinCollectio
|
|
|
127
155
|
this.removeEventListener('keydown', this.handleKeydown);
|
|
128
156
|
this.removeEventListener('focusin', this.handleFocusIn);
|
|
129
157
|
}
|
|
158
|
+
willUpdate(changed) {
|
|
159
|
+
super.willUpdate(changed);
|
|
160
|
+
if (changed.has('page') && (this.drilldown.page?.value ?? '') !== this.page) {
|
|
161
|
+
this.showPage(this.page);
|
|
162
|
+
this.page = this.drilldown.page?.value ?? '';
|
|
163
|
+
}
|
|
164
|
+
hostAria(this.internals, {
|
|
165
|
+
label: this.navigation ? this.label : '',
|
|
166
|
+
role: this.navigation ? 'navigation' : '',
|
|
167
|
+
});
|
|
168
|
+
}
|
|
130
169
|
updated(changed) {
|
|
131
170
|
super.updated(changed);
|
|
132
171
|
const page = this.drilldown.page;
|
|
@@ -162,6 +201,7 @@ let HelixHeadlessDrilldown = class HelixHeadlessDrilldown extends MixinCollectio
|
|
|
162
201
|
}
|
|
163
202
|
mark(item, page, onPage) {
|
|
164
203
|
item.onPage = onPage;
|
|
204
|
+
item.listed = this.navigation;
|
|
165
205
|
item.showingPage = item === page;
|
|
166
206
|
item.onPath = page !== null && item !== page && item.contains(page);
|
|
167
207
|
for (const child of item.children) {
|
|
@@ -188,10 +228,10 @@ let HelixHeadlessDrilldown = class HelixHeadlessDrilldown extends MixinCollectio
|
|
|
188
228
|
</div>`}
|
|
189
229
|
<div
|
|
190
230
|
aria-busy=${this.loading ? 'true' : nothing}
|
|
191
|
-
aria-label=${this.drilldown.atRoot ? this.label || nothing : nothing}
|
|
231
|
+
aria-label=${this.drilldown.atRoot && !this.navigation ? this.label || nothing : nothing}
|
|
192
232
|
aria-labelledby=${this.drilldown.atRoot ? nothing : 'title'}
|
|
193
233
|
part="page"
|
|
194
|
-
role
|
|
234
|
+
role=${this.navigation ? 'list' : 'menu'}
|
|
195
235
|
>
|
|
196
236
|
<slot @slotchange=${(event) => this.countRows(event)}></slot>
|
|
197
237
|
${this.renderStatus(this.hasRows)}
|
|
@@ -206,6 +246,12 @@ __decorate([
|
|
|
206
246
|
words,
|
|
207
247
|
property({ attribute: 'back-label' })
|
|
208
248
|
], HelixHeadlessDrilldown.prototype, "backLabel", void 0);
|
|
249
|
+
__decorate([
|
|
250
|
+
property({ type: Boolean })
|
|
251
|
+
], HelixHeadlessDrilldown.prototype, "navigation", void 0);
|
|
252
|
+
__decorate([
|
|
253
|
+
property()
|
|
254
|
+
], HelixHeadlessDrilldown.prototype, "page", void 0);
|
|
209
255
|
__decorate([
|
|
210
256
|
state()
|
|
211
257
|
], HelixHeadlessDrilldown.prototype, "direction", void 0);
|
|
@@ -93,7 +93,7 @@ registerElementEvents({
|
|
|
93
93
|
'helix-headless-date-picker': ['change', 'helix-before-change', 'helix-navigate', 'input'],
|
|
94
94
|
'helix-headless-dialog': ['close', 'helix-before-close', 'helix-before-open', 'toggle'],
|
|
95
95
|
'helix-headless-drawer': ['close', 'helix-before-close', 'helix-before-open', 'toggle'],
|
|
96
|
-
'helix-headless-drilldown': ['helix-select'],
|
|
96
|
+
'helix-headless-drilldown': ['helix-navigate', 'helix-select'],
|
|
97
97
|
'helix-headless-drilldown-item': ['helix-select'],
|
|
98
98
|
'helix-headless-file-input': ['change', 'helix-before-change', 'helix-reject', 'input'],
|
|
99
99
|
'helix-headless-filter-chip': ['helix-dismiss'],
|