@fusion.dev/helix-headless 0.155.0 → 0.157.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/custom-elements.json +2695 -137
- package/dist/canvas/node/canvas-node.js +1 -1
- package/dist/canvas/root/canvas-root.d.ts +17 -2
- package/dist/canvas/root/canvas-root.js +23 -6
- package/dist/chat/composer/chat-composer.js +1 -1
- package/dist/chat/root/chat-root.d.ts +14 -1
- package/dist/chat/root/chat-root.js +14 -4
- package/dist/combobox/root/combobox-root.d.ts +10 -0
- package/dist/combobox/root/combobox-root.js +18 -3
- package/dist/command/root/command-root.d.ts +116 -1
- package/dist/command/root/command-root.js +124 -15
- package/dist/data-table/root/data-table-root.d.ts +25 -0
- package/dist/data-table/root/data-table-root.js +13 -3
- package/dist/date-input/root/date-input-root.d.ts +32 -1
- package/dist/date-input/root/date-input-root.js +7 -1
- 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 +2 -1
- package/dist/filter-input/menu/filter-menu.d.ts +1 -0
- package/dist/filter-input/menu/filter-menu.js +4 -4
- package/dist/graph/root/graph-root.d.ts +13 -0
- package/dist/graph/root/graph-root.js +16 -0
- package/dist/list/root/list-root.d.ts +74 -1
- package/dist/list/root/list-root.js +92 -4
- package/dist/list/selection.js +3 -3
- package/dist/map/root/map-root.d.ts +6 -0
- package/dist/map/root/map-root.js +8 -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 +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/number-input/root/number-input-root.d.ts +63 -6
- package/dist/number-input/root/number-input-root.js +96 -11
- package/dist/object-inspector/root/object-inspector-root.d.ts +36 -1
- package/dist/object-inspector/root/object-inspector-root.js +26 -2
- package/dist/option/root/option-root.d.ts +32 -0
- package/dist/option/root/option-root.js +44 -5
- package/dist/outline/root/outline-root.d.ts +6 -2
- package/dist/outline/root/outline-root.js +7 -3
- package/dist/table/root/table-root.d.ts +20 -0
- package/dist/table/root/table-root.js +13 -3
- package/dist/time-input/root/time-input-root.d.ts +32 -1
- package/dist/time-input/root/time-input-root.js +9 -2
- package/dist/time-picker/root/time-picker-root.d.ts +63 -1
- package/dist/time-picker/root/time-picker-root.js +58 -7
- package/dist/timeline/item/timeline-item.d.ts +25 -0
- package/dist/timeline/item/timeline-item.js +27 -2
- 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/keys.d.ts +6 -2
- package/dist/video/root/keys.js +18 -1
- package/dist/video/root/video-root.d.ts +61 -1
- package/dist/video/root/video-root.js +79 -11
- package/dist/video-playlist/item/video-playlist-item.d.ts +15 -1
- package/dist/video-playlist/item/video-playlist-item.js +7 -0
- 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.d.ts +43 -4
- package/dist/video-playlist/root/video-playlist-root.js +69 -2
- package/dist/virtual-list/root/virtual-list-root.d.ts +8 -0
- package/dist/virtual-list/root/virtual-list-root.js +3 -0
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +3346 -266
- package/types/vue.d.ts +910 -66
- package/types/words.d.ts +124 -0
|
@@ -69,6 +69,23 @@ import { type ListSelectionBehavior, type ListSelectionMode } from '../selection
|
|
|
69
69
|
* no `start` counts down from the number of rows, which a server cannot know,
|
|
70
70
|
* so served markup counts from one until the list runs.
|
|
71
71
|
*
|
|
72
|
+
* **An empty list.** Set `empty-label`, or put your own content in the `empty`
|
|
73
|
+
* slot, for a row shown while the list has no rows and none are loading, such
|
|
74
|
+
* as "No files yet" and a link to add one. Without either, an empty list shows
|
|
75
|
+
* nothing. The list takes the `empty` state while it has no rows, whatever it
|
|
76
|
+
* shows.
|
|
77
|
+
*
|
|
78
|
+
* **Loading more.** `load-more` sends `helix-load-more` once the end of the
|
|
79
|
+
* list comes within a screen of view, in the box that scrolls it or the page,
|
|
80
|
+
* once for each number of rows: add rows and it can send it again. While you
|
|
81
|
+
* fetch, set `loading`: the list is busy, and a row after the last says
|
|
82
|
+
* "Loading more" (`loading-label`, or your own content in the `loading`
|
|
83
|
+
* slot). The list asks for nothing while `loading` is set.
|
|
84
|
+
*
|
|
85
|
+
* An interactive list with no name is reported once in a development build,
|
|
86
|
+
* since a screen reader announces it as a grid and nothing more. Name it with
|
|
87
|
+
* `label` or `aria-label`.
|
|
88
|
+
*
|
|
72
89
|
* @since 0.1.0
|
|
73
90
|
* @status stable
|
|
74
91
|
* @category data
|
|
@@ -76,18 +93,26 @@ import { type ListSelectionBehavior, type ListSelectionMode } from '../selection
|
|
|
76
93
|
* @documentation https://fusion.dev/helix/components/list
|
|
77
94
|
*
|
|
78
95
|
* @slot - The rows.
|
|
96
|
+
* @slot empty - Content for the row shown while the list has no rows and none are loading. Falls back to `empty-label`.
|
|
97
|
+
* @slot loading - Content for the row after the last while `loading` is set. Falls back to `loading-label`.
|
|
79
98
|
*
|
|
80
99
|
* @event {Event} change - Fired once a press has changed which rows are selected.
|
|
81
100
|
* @event {HelixActionEvent} helix-action - Sent from a row when the reader acts on it: Enter, or a press in a list that selects nothing. Read the row from `target`. A row with `href` follows its link instead.
|
|
82
101
|
* @event {HelixBeforeChangeEvent} helix-before-change - Fired before a press changes which rows are selected, with the `value` it would leave. Cancelable.
|
|
83
102
|
* @event {HelixBeforeReorderEvent} helix-before-reorder - Fired when a row is dropped at a new place, in a `reorderable` list, with every row's value in the new order. Cancelable: cancel it to keep the row where it is.
|
|
103
|
+
* @event {HelixLoadMoreEvent} helix-load-more - Fired, in a list with `load-more`, when the end of the list comes within a screen of view, once for each number of rows.
|
|
84
104
|
* @event {HelixReorderEvent} helix-reorder - Fired after `helix-before-reorder` when nobody canceled it. Move your rows to the order in `keys`. Cancelable: cancel it to say the move did not happen.
|
|
85
105
|
*
|
|
86
106
|
* @csspart drag-preview - A copy of the row a pointer drags, which follows the pointer in the top layer until the row is let go, in a `reorderable` list. Hidden from assistive technology.
|
|
107
|
+
* @csspart empty - The row shown while the list has no rows and none are loading, where `empty-label` or the `empty` slot gives it content. A grid row with one cell in a list that selects.
|
|
87
108
|
* @csspart live - The region that says each step of a move to screen readers, in a `reorderable` list. Drawn from the first move. Visually hidden in the styled tier.
|
|
109
|
+
* @csspart loading - The row after the last while `loading` is set. A grid row with one cell in a list that selects.
|
|
88
110
|
*
|
|
89
111
|
* @cssproperty --list-first - The number of the first row, written where `start` or `reversed` moves it.
|
|
90
112
|
* @cssproperty --list-step - What each row adds to the number before it: `1`, or `-1` where `reversed`.
|
|
113
|
+
*
|
|
114
|
+
* @cssstate empty - While the list has no rows.
|
|
115
|
+
* @cssstate loading - While `loading` is set.
|
|
91
116
|
*/
|
|
92
117
|
export declare class HelixHeadlessList extends HelixElement {
|
|
93
118
|
/**
|
|
@@ -102,6 +127,11 @@ export declare class HelixHeadlessList extends HelixElement {
|
|
|
102
127
|
canceledLabel: string;
|
|
103
128
|
/** What screen readers hear when a row is dropped, in a `reorderable` list. */
|
|
104
129
|
droppedLabel: string;
|
|
130
|
+
/**
|
|
131
|
+
* The text of the row shown while the list has no rows, where the `empty`
|
|
132
|
+
* slot is empty. Empty, the default, shows no row.
|
|
133
|
+
*/
|
|
134
|
+
emptyLabel: string;
|
|
105
135
|
/**
|
|
106
136
|
* The kind of list, which follows from the content.
|
|
107
137
|
*
|
|
@@ -129,6 +159,18 @@ export declare class HelixHeadlessList extends HelixElement {
|
|
|
129
159
|
* The list's name, read by screen readers.
|
|
130
160
|
*/
|
|
131
161
|
label: string;
|
|
162
|
+
/**
|
|
163
|
+
* Marks the list as busy fetching more rows, and draws a row after the last
|
|
164
|
+
* that says so. The list asks for no more while it is set.
|
|
165
|
+
*/
|
|
166
|
+
loading: boolean;
|
|
167
|
+
/** The text of the row drawn while `loading` is set, where the `loading` slot is empty. */
|
|
168
|
+
loadingLabel: string;
|
|
169
|
+
/**
|
|
170
|
+
* Sends `helix-load-more` once the end of the list comes within a screen of
|
|
171
|
+
* view, so you can add the next rows as a reader nears them.
|
|
172
|
+
*/
|
|
173
|
+
loadMore: boolean;
|
|
132
174
|
/**
|
|
133
175
|
* What screen readers hear each time an arrow key moves a held row, in a
|
|
134
176
|
* `reorderable` list. `{name}` is the row's words, `{position}` the place it
|
|
@@ -185,7 +227,7 @@ export declare class HelixHeadlessList extends HelixElement {
|
|
|
185
227
|
*/
|
|
186
228
|
type?: '1' | 'a' | 'A' | 'i' | 'I';
|
|
187
229
|
/**
|
|
188
|
-
* The selected rows' values, separated by commas, in a list that selects.
|
|
230
|
+
* The selected rows' values, separated by commas with no spaces, such as `a,c`, in a list that selects.
|
|
189
231
|
*
|
|
190
232
|
* Set it to start with rows selected. A row's value is its `value`, or its
|
|
191
233
|
* words where it has none.
|
|
@@ -303,6 +345,37 @@ export declare class HelixHeadlessList extends HelixElement {
|
|
|
303
345
|
*/
|
|
304
346
|
private get numbers();
|
|
305
347
|
protected render(): TemplateResult;
|
|
348
|
+
/**
|
|
349
|
+
* The row shown while the list has no rows: a list item, or a grid row with
|
|
350
|
+
* one cell in a list that selects. Drawn only once the children are known,
|
|
351
|
+
* so a server and a hydrating render never claim an empty list, and hidden
|
|
352
|
+
* while it has neither `empty-label` nor content in the `empty` slot.
|
|
353
|
+
*/
|
|
354
|
+
private drawEmpty;
|
|
355
|
+
/**
|
|
356
|
+
* The row after the last while `loading` is set: a list item, or a grid row
|
|
357
|
+
* with one cell in a list that selects. It is not one of the rows, so it
|
|
358
|
+
* carries no value and a key never lands on it.
|
|
359
|
+
*/
|
|
360
|
+
private drawLoading;
|
|
361
|
+
/** Watches the end of a `load-more` list coming near the box that scrolls it. */
|
|
362
|
+
private endWatcher;
|
|
363
|
+
/** The end being watched, so a new one is watched again. */
|
|
364
|
+
private watchedEnd;
|
|
365
|
+
/** Whether the end of the list was in reach when last measured. */
|
|
366
|
+
private endInView;
|
|
367
|
+
/** The number of rows `helix-load-more` was last sent for. */
|
|
368
|
+
private askedAt;
|
|
369
|
+
/**
|
|
370
|
+
* Starts or stops watching the end of the list. The box it is measured
|
|
371
|
+
* against is the one that scrolls the list, or the page, and a screen of
|
|
372
|
+
* room is added below it, so the next rows are asked for before a reader
|
|
373
|
+
* reaches the last.
|
|
374
|
+
*/
|
|
375
|
+
private watchEnd;
|
|
376
|
+
/** Sends `helix-load-more`, once for each number of rows, and never while loading. */
|
|
377
|
+
private askForMore;
|
|
378
|
+
private readonly handleEmptyChange;
|
|
306
379
|
private readonly handleSlotChange;
|
|
307
380
|
}
|
|
308
381
|
declare global {
|
|
@@ -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 { commaList, customElement, deepActiveElement, drawStatus, focusItemOf, formatWords, HelixActionEvent, HelixElement, hostAria, inlineStep, KeyboardNavigationController, placeKey, pressedOwnControl, ReorderController, requestReorder, requestValue, rovingStop, StatusController, scrollingAncestor, scrollRoom, selectsAll, typeaheadByWords, whenHydrated, words, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { auditName, commaList, customElement, deepActiveElement, drawStatus, focusItemOf, formatWords, HelixActionEvent, HelixElement, HelixLoadMoreEvent, hostAria, inlineStep, KeyboardNavigationController, placeKey, pressedOwnControl, ReorderController, requestReorder, requestValue, rovingStop, StatusController, scrollingAncestor, scrollRoom, selectsAll, typeaheadByWords, whenHydrated, words, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html, nothing } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
import { HelixHeadlessListItem } from '../item/list-item.js';
|
|
@@ -15,7 +15,11 @@ let HelixHeadlessList = class HelixHeadlessList extends HelixElement {
|
|
|
15
15
|
super(...arguments);
|
|
16
16
|
this.canceledLabel = 'Canceled';
|
|
17
17
|
this.droppedLabel = 'Dropped';
|
|
18
|
+
this.emptyLabel = '';
|
|
18
19
|
this.label = '';
|
|
20
|
+
this.loading = false;
|
|
21
|
+
this.loadingLabel = 'Loading more';
|
|
22
|
+
this.loadMore = false;
|
|
19
23
|
this.movedLabel = '{name} moved to position {position} of {count}';
|
|
20
24
|
this.moveLabel = 'Move {name}';
|
|
21
25
|
this.pickedUpLabel = 'Picked up {name}';
|
|
@@ -81,7 +85,7 @@ let HelixHeadlessList = class HelixHeadlessList extends HelixElement {
|
|
|
81
85
|
}
|
|
82
86
|
if (selectsAll(event) && this.selectionMode === 'multiple') {
|
|
83
87
|
event.preventDefault();
|
|
84
|
-
const every = this.reachable.map((one) => this.valueFor(one)).join(',
|
|
88
|
+
const every = this.reachable.map((one) => this.valueFor(one)).join(',');
|
|
85
89
|
this.choose(every === this.value ? null : every);
|
|
86
90
|
}
|
|
87
91
|
else if (event.key === 'Escape' && this.value !== '' && this.selectionMode !== 'none') {
|
|
@@ -125,6 +129,10 @@ let HelixHeadlessList = class HelixHeadlessList extends HelixElement {
|
|
|
125
129
|
this.sync();
|
|
126
130
|
}
|
|
127
131
|
};
|
|
132
|
+
this.endInView = false;
|
|
133
|
+
this.handleEmptyChange = () => {
|
|
134
|
+
this.requestUpdate();
|
|
135
|
+
};
|
|
128
136
|
this.handleSlotChange = () => {
|
|
129
137
|
this.rowsChanged = true;
|
|
130
138
|
this.requestUpdate();
|
|
@@ -306,9 +314,15 @@ let HelixHeadlessList = class HelixHeadlessList extends HelixElement {
|
|
|
306
314
|
this.addEventListener('keydown', this.handleKeydown);
|
|
307
315
|
this.addEventListener('click', this.handleClick);
|
|
308
316
|
this.addEventListener('focusin', this.handleFocusin);
|
|
317
|
+
if (this.hasUpdated) {
|
|
318
|
+
this.watchEnd();
|
|
319
|
+
}
|
|
309
320
|
}
|
|
310
321
|
disconnectedCallback() {
|
|
311
322
|
super.disconnectedCallback();
|
|
323
|
+
this.endWatcher?.disconnect();
|
|
324
|
+
this.endWatcher = undefined;
|
|
325
|
+
this.watchedEnd = undefined;
|
|
312
326
|
this.removeEventListener('keydown', this.handleKeyCapture, true);
|
|
313
327
|
this.removeEventListener('keydown', this.handleKeydown);
|
|
314
328
|
this.removeEventListener('click', this.handleClick);
|
|
@@ -323,6 +337,17 @@ let HelixHeadlessList = class HelixHeadlessList extends HelixElement {
|
|
|
323
337
|
if (this.interactive || changed.get('selectionMode') !== undefined) {
|
|
324
338
|
this.sync();
|
|
325
339
|
}
|
|
340
|
+
if (this.interactive) {
|
|
341
|
+
auditName(this, () => null, 'Give it a `label`, or an `aria-label` that says what the rows are.', () => this.label !== '');
|
|
342
|
+
}
|
|
343
|
+
if (this.childrenKnown) {
|
|
344
|
+
this.toggleState('empty', this.rows.length === 0);
|
|
345
|
+
}
|
|
346
|
+
this.toggleState('loading', this.loading);
|
|
347
|
+
this.watchEnd();
|
|
348
|
+
if (this.endInView) {
|
|
349
|
+
this.askForMore();
|
|
350
|
+
}
|
|
326
351
|
if (this.reorderable || changed.get('reorderable') !== undefined) {
|
|
327
352
|
this.syncMoves();
|
|
328
353
|
this.refocus();
|
|
@@ -331,14 +356,64 @@ let HelixHeadlessList = class HelixHeadlessList extends HelixElement {
|
|
|
331
356
|
willUpdate(changed) {
|
|
332
357
|
super.willUpdate(changed);
|
|
333
358
|
hostAria(this.internals, { label: this.label, role: this.interactive ? 'grid' : 'list' });
|
|
359
|
+
this.internals.ariaBusy = this.loading ? 'true' : null;
|
|
334
360
|
this.internals.ariaMultiSelectable = this.selectionMode === 'multiple' ? 'true' : null;
|
|
335
361
|
}
|
|
336
362
|
get numbers() {
|
|
337
|
-
const counted = numbering(this.start, this.reversed, this.childrenKnown
|
|
363
|
+
const counted = numbering(this.start, this.reversed, this.childrenKnown
|
|
364
|
+
? this.knownChildren.filter((child) => !child.hasAttribute('slot')).length
|
|
365
|
+
: undefined);
|
|
338
366
|
return counted ? `--list-first: ${counted.first}; --list-step: ${counted.step}` : nothing;
|
|
339
367
|
}
|
|
340
368
|
render() {
|
|
341
|
-
return html `<slot style=${this.numbers} @slotchange=${this.handleSlotChange}></slot>${drawStatus(this.status)}${this.reorder.drawDragPreview()}`;
|
|
369
|
+
return html `<slot style=${this.numbers} @slotchange=${this.handleSlotChange}></slot>${this.drawLoading()}${this.drawEmpty()}${this.loadMore ? html `<span data-end aria-hidden="true"></span>` : nothing}${drawStatus(this.status)}${this.reorder.drawDragPreview()}`;
|
|
370
|
+
}
|
|
371
|
+
drawEmpty() {
|
|
372
|
+
if (!this.childrenKnown || this.rows.length > 0 || this.loading) {
|
|
373
|
+
return nothing;
|
|
374
|
+
}
|
|
375
|
+
const shown = this.emptyLabel !== '' || this.querySelector(':scope > [slot="empty"]') !== null;
|
|
376
|
+
const content = html `<slot name="empty" @slotchange=${this.handleEmptyChange}>${this.emptyLabel}</slot>`;
|
|
377
|
+
return this.interactive
|
|
378
|
+
? html `<div part="empty" role="row" ?hidden=${!shown}><div role="gridcell">${content}</div></div>`
|
|
379
|
+
: html `<div part="empty" role="listitem" ?hidden=${!shown}>${content}</div>`;
|
|
380
|
+
}
|
|
381
|
+
drawLoading() {
|
|
382
|
+
if (!this.loading) {
|
|
383
|
+
return nothing;
|
|
384
|
+
}
|
|
385
|
+
const content = html `<slot name="loading">${this.loadingLabel}</slot>`;
|
|
386
|
+
return this.interactive
|
|
387
|
+
? html `<div part="loading" role="row"><div role="gridcell">${content}</div></div>`
|
|
388
|
+
: html `<div part="loading" role="listitem">${content}</div>`;
|
|
389
|
+
}
|
|
390
|
+
watchEnd() {
|
|
391
|
+
const end = this.loadMore ? this.renderRoot.querySelector('[data-end]') : null;
|
|
392
|
+
if (end === this.watchedEnd) {
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
this.endWatcher?.disconnect();
|
|
396
|
+
this.endWatcher = undefined;
|
|
397
|
+
this.endInView = false;
|
|
398
|
+
this.watchedEnd = end ?? undefined;
|
|
399
|
+
if (!end || typeof IntersectionObserver === 'undefined') {
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
this.endWatcher = new IntersectionObserver((entries) => {
|
|
403
|
+
this.endInView = entries.some((entry) => entry.isIntersecting);
|
|
404
|
+
if (this.endInView) {
|
|
405
|
+
this.askForMore();
|
|
406
|
+
}
|
|
407
|
+
}, { root: scrollingAncestor(this, { axis: 'y' }), rootMargin: '0px 0px 100% 0px' });
|
|
408
|
+
this.endWatcher.observe(end);
|
|
409
|
+
}
|
|
410
|
+
askForMore() {
|
|
411
|
+
const count = this.rows.length;
|
|
412
|
+
if (!this.loadMore || this.loading || this.askedAt === count) {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
this.askedAt = count;
|
|
416
|
+
this.dispatchEvent(new HelixLoadMoreEvent());
|
|
342
417
|
}
|
|
343
418
|
};
|
|
344
419
|
__decorate([
|
|
@@ -349,12 +424,25 @@ __decorate([
|
|
|
349
424
|
words,
|
|
350
425
|
property({ attribute: 'dropped-label' })
|
|
351
426
|
], HelixHeadlessList.prototype, "droppedLabel", void 0);
|
|
427
|
+
__decorate([
|
|
428
|
+
property({ attribute: 'empty-label' })
|
|
429
|
+
], HelixHeadlessList.prototype, "emptyLabel", void 0);
|
|
352
430
|
__decorate([
|
|
353
431
|
property({ reflect: true })
|
|
354
432
|
], HelixHeadlessList.prototype, "kind", void 0);
|
|
355
433
|
__decorate([
|
|
356
434
|
property()
|
|
357
435
|
], HelixHeadlessList.prototype, "label", void 0);
|
|
436
|
+
__decorate([
|
|
437
|
+
property({ type: Boolean })
|
|
438
|
+
], HelixHeadlessList.prototype, "loading", void 0);
|
|
439
|
+
__decorate([
|
|
440
|
+
words,
|
|
441
|
+
property({ attribute: 'loading-label' })
|
|
442
|
+
], HelixHeadlessList.prototype, "loadingLabel", void 0);
|
|
443
|
+
__decorate([
|
|
444
|
+
property({ attribute: 'load-more', type: Boolean })
|
|
445
|
+
], HelixHeadlessList.prototype, "loadMore", void 0);
|
|
358
446
|
__decorate([
|
|
359
447
|
words,
|
|
360
448
|
property({ attribute: 'moved-label' })
|
package/dist/list/selection.js
CHANGED
|
@@ -7,7 +7,7 @@ export const choiceValue = ({ anchor, arrived = false, behavior, extend = false,
|
|
|
7
7
|
if (extend && mode === 'multiple') {
|
|
8
8
|
const from = anchor === undefined ? -1 : shown.indexOf(anchor);
|
|
9
9
|
const range = rangeAdd(now, shown, from, shown.indexOf(pressed));
|
|
10
|
-
return range ? range.join(',
|
|
10
|
+
return range ? range.join(',') : null;
|
|
11
11
|
}
|
|
12
12
|
if (behavior === 'replace' && !toggle) {
|
|
13
13
|
return now.length === 1 && now[0] === pressed ? null : pressed;
|
|
@@ -15,8 +15,8 @@ export const choiceValue = ({ anchor, arrived = false, behavior, extend = false,
|
|
|
15
15
|
if (arrived) {
|
|
16
16
|
return null;
|
|
17
17
|
}
|
|
18
|
-
const next = pressedValue(mode, value, pressed);
|
|
19
|
-
return next === value ? null : next;
|
|
18
|
+
const next = commaList(pressedValue(mode, value, pressed)).join(',');
|
|
19
|
+
return next === commaList(value).join(',') ? null : next;
|
|
20
20
|
};
|
|
21
21
|
export const rowStop = (count, at, step) => {
|
|
22
22
|
if (count <= 1) {
|
|
@@ -214,6 +214,12 @@ export declare class HelixHeadlessMap extends HelixHeadlessMap_base {
|
|
|
214
214
|
* above `0`.
|
|
215
215
|
*/
|
|
216
216
|
pitch: number;
|
|
217
|
+
/**
|
|
218
|
+
* What a screen reader calls the map in place of its role, after its
|
|
219
|
+
* name: "Edinburgh, map". Said only where the map has a name of its
|
|
220
|
+
* own, since the fallback name is the same word. Empty leaves the role.
|
|
221
|
+
*/
|
|
222
|
+
roleLabel: string;
|
|
217
223
|
/** The rotate left button's label. */
|
|
218
224
|
rotateLeftLabel: string;
|
|
219
225
|
/** The rotate right button's label. */
|
|
@@ -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, focusItemOf, formatWords, HelixActivateEvent, HelixBeforeChangeEvent, HelixBeforeToggleEvent, HelixElement, HelixMapClusterEvent, HelixMapPanEvent, HelixMapPartChangeEvent, HelixMapReadyEvent, HelixMapRotateEvent, HelixMapTiltEvent, HelixMapZoomEvent, HelixToggleEvent, hostAria, KeyboardNavigationController, MixinViewport, requestChange, words, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { auditName, customElement, focusItemOf, formatWords, HelixActivateEvent, HelixBeforeChangeEvent, HelixBeforeToggleEvent, HelixElement, HelixMapClusterEvent, HelixMapPanEvent, HelixMapPartChangeEvent, HelixMapReadyEvent, HelixMapRotateEvent, HelixMapTiltEvent, HelixMapZoomEvent, HelixToggleEvent, hostAria, KeyboardNavigationController, MixinViewport, requestChange, words, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { buildings, cloudRain, dotsNine, leaf, mountains, path, polygon, stack, target, } from '@fusion.dev/helix-icons';
|
|
9
9
|
import { html, nothing } from 'lit';
|
|
10
10
|
import { property, state } from 'lit/decorators.js';
|
|
@@ -60,6 +60,7 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
|
|
|
60
60
|
this.movedUpLabel = 'Moved up';
|
|
61
61
|
this.northLabel = 'Facing north';
|
|
62
62
|
this.pitch = 0;
|
|
63
|
+
this.roleLabel = 'map';
|
|
63
64
|
this.rotateLeftLabel = 'Rotate left';
|
|
64
65
|
this.rotateRightLabel = 'Rotate right';
|
|
65
66
|
this.satelliteLabel = 'Satellite';
|
|
@@ -340,6 +341,7 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
|
|
|
340
341
|
firstUpdated(changed) {
|
|
341
342
|
super.firstUpdated(changed);
|
|
342
343
|
this.finishHydration();
|
|
344
|
+
auditName(this, () => null, 'Give it a `label` that says what the map shows, such as "Stores in Edinburgh".', () => this.label !== '');
|
|
343
345
|
}
|
|
344
346
|
updated(changed) {
|
|
345
347
|
super.updated(changed);
|
|
@@ -609,6 +611,7 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
|
|
|
609
611
|
part="region"
|
|
610
612
|
role="group"
|
|
611
613
|
aria-label=${this.label || this.getAttribute('aria-label') || this.fallbackLabel}
|
|
614
|
+
aria-roledescription=${(this.label || this.getAttribute('aria-label')) && this.roleLabel ? this.roleLabel : nothing}
|
|
612
615
|
aria-keyshortcuts=${this.keyShortcuts}
|
|
613
616
|
tabindex="0"
|
|
614
617
|
@focusin=${this.idle.stir}
|
|
@@ -705,6 +708,10 @@ __decorate([
|
|
|
705
708
|
__decorate([
|
|
706
709
|
property({ type: Number })
|
|
707
710
|
], HelixHeadlessMap.prototype, "pitch", void 0);
|
|
711
|
+
__decorate([
|
|
712
|
+
words,
|
|
713
|
+
property({ attribute: 'role-label' })
|
|
714
|
+
], HelixHeadlessMap.prototype, "roleLabel", void 0);
|
|
708
715
|
__decorate([
|
|
709
716
|
words,
|
|
710
717
|
property({ attribute: 'rotate-left-label' })
|
|
@@ -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);
|