@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
|
@@ -74,6 +74,7 @@ import '../../menu/index.js';
|
|
|
74
74
|
* @csspart entry-shortcut-key - One key of the shortcut's chord.
|
|
75
75
|
* @csspart entry-shortcut-separator - The `+` between two keys of the shortcut, where the platform writes one.
|
|
76
76
|
* @csspart entry-start - The leading column.
|
|
77
|
+
* @csspart entry-surface - The panel a row's submenu opens in beside it. The filter menu's rows have none.
|
|
77
78
|
* @csspart menu - The menu the fields are offered in.
|
|
78
79
|
* @csspart menu-back - The button that goes back from a field's page.
|
|
79
80
|
* @csspart menu-back-mark - The icon on the back button.
|
|
@@ -141,7 +141,7 @@ let HelixHeadlessFilterMenu = class HelixHeadlessFilterMenu extends HelixElement
|
|
|
141
141
|
exportparts="end: entry-end, item: entry-item, mark: entry-mark, shortcut: entry-shortcut,
|
|
142
142
|
shortcut-kbd: entry-shortcut-kbd,
|
|
143
143
|
shortcut-key: entry-shortcut-key, shortcut-separator: entry-shortcut-separator,
|
|
144
|
-
start: entry-start"
|
|
144
|
+
start: entry-start, surface: entry-surface"
|
|
145
145
|
data-facet="field"
|
|
146
146
|
data-field=${name}
|
|
147
147
|
>
|
|
@@ -152,7 +152,7 @@ let HelixHeadlessFilterMenu = class HelixHeadlessFilterMenu extends HelixElement
|
|
|
152
152
|
exportparts="end: entry-end, item: entry-item, mark: entry-mark, shortcut: entry-shortcut,
|
|
153
153
|
shortcut-kbd: entry-shortcut-kbd,
|
|
154
154
|
shortcut-key: entry-shortcut-key, shortcut-separator: entry-shortcut-separator,
|
|
155
|
-
start: entry-start"
|
|
155
|
+
start: entry-start, surface: entry-surface"
|
|
156
156
|
slot="submenu"
|
|
157
157
|
data-facet="comparison"
|
|
158
158
|
data-field=${name}
|
|
@@ -163,7 +163,7 @@ let HelixHeadlessFilterMenu = class HelixHeadlessFilterMenu extends HelixElement
|
|
|
163
163
|
exportparts="end: entry-end, item: entry-item, mark: entry-mark, shortcut: entry-shortcut,
|
|
164
164
|
shortcut-kbd: entry-shortcut-kbd,
|
|
165
165
|
shortcut-key: entry-shortcut-key, shortcut-separator: entry-shortcut-separator,
|
|
166
|
-
start: entry-start"
|
|
166
|
+
start: entry-start, surface: entry-surface"
|
|
167
167
|
slot="submenu"
|
|
168
168
|
checkable
|
|
169
169
|
exclusive
|
|
@@ -188,7 +188,7 @@ let HelixHeadlessFilterMenu = class HelixHeadlessFilterMenu extends HelixElement
|
|
|
188
188
|
exportparts="end: entry-end, item: entry-item, mark: entry-mark, shortcut: entry-shortcut,
|
|
189
189
|
shortcut-kbd: entry-shortcut-kbd,
|
|
190
190
|
shortcut-key: entry-shortcut-key, shortcut-separator: entry-shortcut-separator,
|
|
191
|
-
start: entry-start"
|
|
191
|
+
start: entry-start, surface: entry-surface"
|
|
192
192
|
slot="submenu"
|
|
193
193
|
checkable
|
|
194
194
|
?exclusive=${using?.arity === 'one'}
|
|
@@ -63,6 +63,7 @@ export type ListItemMove = 'dragging' | 'drop-after' | 'drop-before';
|
|
|
63
63
|
* @cssstate dragging - The row is being moved, in a `reorderable` list.
|
|
64
64
|
* @cssstate drop-after - The row being moved goes after this one when it is dropped.
|
|
65
65
|
* @cssstate drop-before - The row being moved goes before this one when it is dropped.
|
|
66
|
+
* @cssstate focus-visible - The row's cell has keyboard focus that the browser shows, in a list with `selection-mode`.
|
|
66
67
|
* @cssstate selected - The row is selected, in a list that selects.
|
|
67
68
|
*/
|
|
68
69
|
export declare class HelixHeadlessListItem extends HelixElement {
|
|
@@ -78,7 +79,9 @@ export declare class HelixHeadlessListItem extends HelixElement {
|
|
|
78
79
|
current?: ListItemCurrent;
|
|
79
80
|
/**
|
|
80
81
|
* Takes the row out of reach in a list with `selection-mode`: the arrow keys
|
|
81
|
-
* pass over it and it cannot be selected or acted on.
|
|
82
|
+
* pass over it and it cannot be selected or acted on. In a list with
|
|
83
|
+
* `disabled-behavior="selection"`, it cannot be selected and is reached and
|
|
84
|
+
* acted on as any row is.
|
|
82
85
|
*/
|
|
83
86
|
disabled: boolean;
|
|
84
87
|
/**
|
|
@@ -121,6 +124,12 @@ export declare class HelixHeadlessListItem extends HelixElement {
|
|
|
121
124
|
* selected, which keeps `aria-selected` off a list that does not select.
|
|
122
125
|
*/
|
|
123
126
|
selected?: boolean | undefined;
|
|
127
|
+
/**
|
|
128
|
+
* Whether `disabled` takes the row out of selection alone, written by a list
|
|
129
|
+
* with `disabled-behavior="selection"`. No attribute: it is coordination
|
|
130
|
+
* between elements, as `focusable` is.
|
|
131
|
+
*/
|
|
132
|
+
selectionOnly: boolean;
|
|
124
133
|
/**
|
|
125
134
|
* Where the link opens. Use it for a row that leaves the site.
|
|
126
135
|
*/
|
|
@@ -137,6 +146,11 @@ export declare class HelixHeadlessListItem extends HelixElement {
|
|
|
137
146
|
private get described();
|
|
138
147
|
/** Whether a list with `selection-mode` holds this row, which makes it a row of a grid. */
|
|
139
148
|
private get grouped();
|
|
149
|
+
/**
|
|
150
|
+
* Says whether the cell shows keyboard focus, as `:focus-visible` does, so a
|
|
151
|
+
* sheet outside can draw the ring on the row by the host.
|
|
152
|
+
*/
|
|
153
|
+
private readonly handleCellFocus;
|
|
140
154
|
/** @internal The cell a list with `selection-mode` moves focus to. */
|
|
141
155
|
get cell(): HTMLElement | null;
|
|
142
156
|
/**
|
|
@@ -16,8 +16,19 @@ let HelixHeadlessListItem = class HelixHeadlessListItem extends HelixElement {
|
|
|
16
16
|
this.disabled = false;
|
|
17
17
|
this.href = '';
|
|
18
18
|
this.rel = '';
|
|
19
|
+
this.selectionOnly = false;
|
|
19
20
|
this.target = '';
|
|
20
21
|
this.value = '';
|
|
22
|
+
this.handleCellFocus = (event) => {
|
|
23
|
+
const cell = event.currentTarget;
|
|
24
|
+
let shown = false;
|
|
25
|
+
try {
|
|
26
|
+
shown = event.type === 'focus' && cell.matches(':focus-visible');
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
}
|
|
30
|
+
this.toggleState('focus-visible', shown);
|
|
31
|
+
};
|
|
21
32
|
}
|
|
22
33
|
get words() {
|
|
23
34
|
return this.childrenKnown ? slottedWords(this, ['']) : '';
|
|
@@ -56,7 +67,8 @@ let HelixHeadlessListItem = class HelixHeadlessListItem extends HelixElement {
|
|
|
56
67
|
super.willUpdate(changed);
|
|
57
68
|
hostAria(this.internals, { role: this.grouped ? 'row' : 'listitem' });
|
|
58
69
|
this.internals.ariaSelected = this.selected === undefined ? null : String(this.selected);
|
|
59
|
-
this.internals.ariaDisabled =
|
|
70
|
+
this.internals.ariaDisabled =
|
|
71
|
+
this.grouped && this.disabled && !this.selectionOnly ? 'true' : null;
|
|
60
72
|
this.internals.ariaCurrent =
|
|
61
73
|
this.current !== undefined && this.href === '' ? this.current : null;
|
|
62
74
|
this.toggleState('selected', this.selected === true);
|
|
@@ -91,6 +103,8 @@ let HelixHeadlessListItem = class HelixHeadlessListItem extends HelixElement {
|
|
|
91
103
|
tabindex=${tabindex}
|
|
92
104
|
aria-labelledby=${labelledby}
|
|
93
105
|
aria-describedby=${describedby}
|
|
106
|
+
@focus=${this.handleCellFocus}
|
|
107
|
+
@blur=${this.handleCellFocus}
|
|
94
108
|
>${content}<slot name="end"></slot>${this.renderGrip()}</div>`
|
|
95
109
|
: html `<div
|
|
96
110
|
part="row${this.current === undefined ? '' : ' row-current'}"
|
|
@@ -98,6 +112,8 @@ let HelixHeadlessListItem = class HelixHeadlessListItem extends HelixElement {
|
|
|
98
112
|
tabindex=${tabindex}
|
|
99
113
|
aria-labelledby=${labelledby}
|
|
100
114
|
aria-describedby=${describedby}
|
|
115
|
+
@focus=${this.handleCellFocus}
|
|
116
|
+
@blur=${this.handleCellFocus}
|
|
101
117
|
><a
|
|
102
118
|
part="link"
|
|
103
119
|
href=${this.href}
|
|
@@ -137,6 +153,9 @@ __decorate([
|
|
|
137
153
|
__decorate([
|
|
138
154
|
property({ attribute: false })
|
|
139
155
|
], HelixHeadlessListItem.prototype, "selected", void 0);
|
|
156
|
+
__decorate([
|
|
157
|
+
property({ attribute: false })
|
|
158
|
+
], HelixHeadlessListItem.prototype, "selectionOnly", void 0);
|
|
140
159
|
__decorate([
|
|
141
160
|
property()
|
|
142
161
|
], HelixHeadlessListItem.prototype, "target", void 0);
|
|
@@ -14,7 +14,9 @@ import { type ListSelectionBehavior, type ListSelectionMode } from '../selection
|
|
|
14
14
|
* `multiple` any number, and `value` holds the selected rows' values,
|
|
15
15
|
* separated by commas. A change asks with `helix-before-change` and tells
|
|
16
16
|
* with `change`. `none` selects nothing, so a press acts on the row. A
|
|
17
|
-
* `disabled` row is passed over.
|
|
17
|
+
* `disabled` row is passed over. With `disabled-behavior="selection"`, a
|
|
18
|
+
* `disabled` row cannot be selected, and is reached and acted on as any row
|
|
19
|
+
* is, as React Aria's GridList has it. Each row is named by its label and described
|
|
18
20
|
* by its description, so the words of the controls in `end` are not read as
|
|
19
21
|
* part of its name.
|
|
20
22
|
*
|
|
@@ -125,6 +127,15 @@ export declare class HelixHeadlessList extends HelixElement {
|
|
|
125
127
|
* refused, in a `reorderable` list.
|
|
126
128
|
*/
|
|
127
129
|
canceledLabel: string;
|
|
130
|
+
/**
|
|
131
|
+
* What `disabled` takes from a row, in a list that selects: `all`, which
|
|
132
|
+
* passes over the row, or `selection`, which keeps the row out of the
|
|
133
|
+
* selection and lets the keys reach it and Enter or a press act on it, as
|
|
134
|
+
* React Aria's GridList takes `disabledBehavior`.
|
|
135
|
+
*
|
|
136
|
+
* Absent is `all`.
|
|
137
|
+
*/
|
|
138
|
+
disabledBehavior?: 'all' | 'selection' | undefined;
|
|
128
139
|
/** What screen readers hear when a row is dropped, in a `reorderable` list. */
|
|
129
140
|
droppedLabel: string;
|
|
130
141
|
/**
|
|
@@ -203,6 +214,10 @@ export declare class HelixHeadlessList extends HelixElement {
|
|
|
203
214
|
* lets the arrow keys carry the selection.
|
|
204
215
|
*/
|
|
205
216
|
selectionBehavior: ListSelectionBehavior;
|
|
217
|
+
/** Whether the row is out of reach of the keys and of actions, past selection. */
|
|
218
|
+
private blocked;
|
|
219
|
+
/** The rows a key can land on that can also be selected. */
|
|
220
|
+
private get selectable();
|
|
206
221
|
/**
|
|
207
222
|
* Makes the list interactive: `single` selects one row, `multiple` any
|
|
208
223
|
* number, and `none` moves and acts without selecting. Unset is a list a
|
|
@@ -235,6 +250,8 @@ export declare class HelixHeadlessList extends HelixElement {
|
|
|
235
250
|
value: string;
|
|
236
251
|
/** The row the reader was last on, which keeps the tab stop. */
|
|
237
252
|
private last;
|
|
253
|
+
/** The rows in order on the last `sync`, so a removed row's place is known. */
|
|
254
|
+
private placed;
|
|
238
255
|
/** The value of the row a Shift range runs from: the last row pressed. */
|
|
239
256
|
private anchor;
|
|
240
257
|
/**
|
|
@@ -295,6 +312,17 @@ export declare class HelixHeadlessList extends HelixElement {
|
|
|
295
312
|
* row a server rendered draws its served markup first.
|
|
296
313
|
*/
|
|
297
314
|
private sync;
|
|
315
|
+
/**
|
|
316
|
+
* Moves the tab stop off a row the page removed, to the next row a key can
|
|
317
|
+
* reach or, at the end, the one before it, and moves focus there where
|
|
318
|
+
* removing the row dropped it to the body.
|
|
319
|
+
*
|
|
320
|
+
* **Focus went to the body**, and the arrow keys did nothing until the
|
|
321
|
+
* reader tabbed back into the list. React Aria's GridList moves focus to a
|
|
322
|
+
* neighbor the same way. Focus anywhere but the body is where you put it,
|
|
323
|
+
* and is left there.
|
|
324
|
+
*/
|
|
325
|
+
private followRemoved;
|
|
298
326
|
/** The row a press or a key came from, read off the event's path. */
|
|
299
327
|
private rowOf;
|
|
300
328
|
/**
|
|
@@ -27,6 +27,7 @@ let HelixHeadlessList = class HelixHeadlessList extends HelixElement {
|
|
|
27
27
|
this.reversed = false;
|
|
28
28
|
this.selectionBehavior = 'toggle';
|
|
29
29
|
this.value = '';
|
|
30
|
+
this.placed = [];
|
|
30
31
|
this.keys = new KeyboardNavigationController(this, {
|
|
31
32
|
items: () => this.reachable.map((row) => this.navigable(row)),
|
|
32
33
|
origin: 'focus',
|
|
@@ -67,12 +68,12 @@ let HelixHeadlessList = class HelixHeadlessList extends HelixElement {
|
|
|
67
68
|
};
|
|
68
69
|
this.handleKeydown = (event) => {
|
|
69
70
|
const row = this.interactive ? this.rowOf(event) : undefined;
|
|
70
|
-
if (row && !row
|
|
71
|
+
if (row && !this.blocked(row) && !event.altKey && /^Page(Down|Up)$/.test(event.key)) {
|
|
71
72
|
event.preventDefault();
|
|
72
73
|
this.page(row, event.key === 'PageDown' ? 1 : -1);
|
|
73
74
|
}
|
|
74
75
|
this.moving = undefined;
|
|
75
|
-
if (!row || row
|
|
76
|
+
if (!row || this.blocked(row) || event.altKey) {
|
|
76
77
|
return;
|
|
77
78
|
}
|
|
78
79
|
const toggle = event.ctrlKey || event.metaKey;
|
|
@@ -85,7 +86,7 @@ let HelixHeadlessList = class HelixHeadlessList extends HelixElement {
|
|
|
85
86
|
}
|
|
86
87
|
if (selectsAll(event) && this.selectionMode === 'multiple') {
|
|
87
88
|
event.preventDefault();
|
|
88
|
-
const every = this.
|
|
89
|
+
const every = this.selectable.map((one) => this.valueFor(one)).join(',');
|
|
89
90
|
this.choose(every === this.value ? null : every);
|
|
90
91
|
}
|
|
91
92
|
else if (event.key === 'Escape' && this.value !== '' && this.selectionMode !== 'none') {
|
|
@@ -111,7 +112,7 @@ let HelixHeadlessList = class HelixHeadlessList extends HelixElement {
|
|
|
111
112
|
if (pressedOwnControl(event, cell && event.composedPath().includes(cell) ? cell : row)) {
|
|
112
113
|
return;
|
|
113
114
|
}
|
|
114
|
-
if (this.selectionMode === 'none') {
|
|
115
|
+
if (this.selectionMode === 'none' || row.disabled) {
|
|
115
116
|
this.act(row);
|
|
116
117
|
}
|
|
117
118
|
else {
|
|
@@ -139,6 +140,12 @@ let HelixHeadlessList = class HelixHeadlessList extends HelixElement {
|
|
|
139
140
|
};
|
|
140
141
|
}
|
|
141
142
|
static { this.words = 'helix-list'; }
|
|
143
|
+
blocked(row) {
|
|
144
|
+
return row.disabled && this.disabledBehavior !== 'selection';
|
|
145
|
+
}
|
|
146
|
+
get selectable() {
|
|
147
|
+
return this.reachable.filter((row) => !row.disabled);
|
|
148
|
+
}
|
|
142
149
|
get interactive() {
|
|
143
150
|
return this.selectionMode !== undefined;
|
|
144
151
|
}
|
|
@@ -146,11 +153,11 @@ let HelixHeadlessList = class HelixHeadlessList extends HelixElement {
|
|
|
146
153
|
return this.knownChildren.filter((child) => child instanceof HelixHeadlessListItem);
|
|
147
154
|
}
|
|
148
155
|
get reachable() {
|
|
149
|
-
return this.interactive ? this.rows.filter((row) => !row
|
|
156
|
+
return this.interactive ? this.rows.filter((row) => !this.blocked(row)) : [];
|
|
150
157
|
}
|
|
151
158
|
navigable(row) {
|
|
152
159
|
return focusItemOf(row, {
|
|
153
|
-
disabled: row
|
|
160
|
+
disabled: this.blocked(row),
|
|
154
161
|
focus: () => row.focusItem(),
|
|
155
162
|
words: row.words,
|
|
156
163
|
});
|
|
@@ -210,20 +217,41 @@ let HelixHeadlessList = class HelixHeadlessList extends HelixElement {
|
|
|
210
217
|
const mode = this.selectionMode;
|
|
211
218
|
const chosen = new Set(commaList(this.value));
|
|
212
219
|
const rows = this.rows;
|
|
220
|
+
if (mode) {
|
|
221
|
+
this.followRemoved(rows);
|
|
222
|
+
}
|
|
223
|
+
this.placed = rows;
|
|
213
224
|
const holder = mode
|
|
214
225
|
? rovingStop(rows, this.last, {
|
|
215
|
-
disabled: (row) => row
|
|
226
|
+
disabled: (row) => this.blocked(row),
|
|
216
227
|
selected: (row) => chosen.has(this.valueFor(row)),
|
|
217
228
|
})
|
|
218
229
|
: undefined;
|
|
219
230
|
for (const row of rows) {
|
|
220
231
|
whenHydrated(row, () => {
|
|
221
232
|
row.focusable = mode ? row === holder : undefined;
|
|
233
|
+
row.selectionOnly = this.disabledBehavior === 'selection';
|
|
222
234
|
row.selected =
|
|
223
235
|
mode === undefined || mode === 'none' ? undefined : chosen.has(this.valueFor(row));
|
|
224
236
|
});
|
|
225
237
|
}
|
|
226
238
|
}
|
|
239
|
+
followRemoved(rows) {
|
|
240
|
+
const last = this.last;
|
|
241
|
+
const at = last ? this.placed.indexOf(last) : -1;
|
|
242
|
+
if (!last || at < 0 || rows.includes(last)) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const present = new Set(rows);
|
|
246
|
+
const reachable = (row) => present.has(row) && !this.blocked(row);
|
|
247
|
+
const next = this.placed.slice(at + 1).find(reachable) ??
|
|
248
|
+
this.placed.slice(0, at).reverse().find(reachable);
|
|
249
|
+
this.last = next;
|
|
250
|
+
const active = deepActiveElement();
|
|
251
|
+
if (next && (active === null || active === document.body)) {
|
|
252
|
+
whenHydrated(next, () => next.focusItem());
|
|
253
|
+
}
|
|
254
|
+
}
|
|
227
255
|
rowOf(event) {
|
|
228
256
|
return event
|
|
229
257
|
.composedPath()
|
|
@@ -245,7 +273,7 @@ let HelixHeadlessList = class HelixHeadlessList extends HelixElement {
|
|
|
245
273
|
extend,
|
|
246
274
|
mode: this.selectionMode ?? 'none',
|
|
247
275
|
pressed: this.valueFor(row),
|
|
248
|
-
shown: this.
|
|
276
|
+
shown: this.selectable.map((one) => this.valueFor(one)),
|
|
249
277
|
toggle,
|
|
250
278
|
value: this.value,
|
|
251
279
|
};
|
|
@@ -263,7 +291,10 @@ let HelixHeadlessList = class HelixHeadlessList extends HelixElement {
|
|
|
263
291
|
}
|
|
264
292
|
arrive(row, from) {
|
|
265
293
|
const moving = this.moving;
|
|
266
|
-
if (!moving ||
|
|
294
|
+
if (!moving ||
|
|
295
|
+
this.selectionMode === undefined ||
|
|
296
|
+
this.selectionMode === 'none' ||
|
|
297
|
+
row.disabled) {
|
|
267
298
|
return;
|
|
268
299
|
}
|
|
269
300
|
if (moving.extend && this.anchor === undefined && from) {
|
|
@@ -287,7 +318,7 @@ let HelixHeadlessList = class HelixHeadlessList extends HelixElement {
|
|
|
287
318
|
}
|
|
288
319
|
}
|
|
289
320
|
act(row) {
|
|
290
|
-
if (row
|
|
321
|
+
if (this.blocked(row)) {
|
|
291
322
|
return;
|
|
292
323
|
}
|
|
293
324
|
if (row.href !== '') {
|
|
@@ -420,6 +451,9 @@ __decorate([
|
|
|
420
451
|
words,
|
|
421
452
|
property({ attribute: 'canceled-label' })
|
|
422
453
|
], HelixHeadlessList.prototype, "canceledLabel", void 0);
|
|
454
|
+
__decorate([
|
|
455
|
+
property({ attribute: 'disabled-behavior' })
|
|
456
|
+
], HelixHeadlessList.prototype, "disabledBehavior", void 0);
|
|
423
457
|
__decorate([
|
|
424
458
|
words,
|
|
425
459
|
property({ attribute: 'dropped-label' })
|
|
@@ -14,7 +14,9 @@ declare const HelixHeadlessListbox_base: (new (...args: any[]) => import("@fusio
|
|
|
14
14
|
* With `multiple` set, `value` is a comma-separated string, like
|
|
15
15
|
* `Checkbox.Group`'s, because `MixinControllerValue` holds a string. The form
|
|
16
16
|
* receives one entry per selected option, as a native `<select multiple>` sends
|
|
17
|
-
* it, so your server does not split on commas.
|
|
17
|
+
* it, so your server does not split on commas. An option's value may hold a
|
|
18
|
+
* comma: a run of the string that is an option's value is read as that one
|
|
19
|
+
* value, and the form sends it as one entry.
|
|
18
20
|
*
|
|
19
21
|
* Selected and active are separate. The selected option is the value. The
|
|
20
22
|
* active option is where the keyboard is: the arrow keys move it, and
|
|
@@ -185,6 +187,16 @@ export declare class HelixHeadlessListbox extends HelixHeadlessListbox_base {
|
|
|
185
187
|
* Placeholder text for the filter field.
|
|
186
188
|
*/
|
|
187
189
|
filterLabel: string;
|
|
190
|
+
/**
|
|
191
|
+
* Whether an option is kept by the filter: a function given the option's
|
|
192
|
+
* words and the filter, trimmed, that returns `true` to keep it showing, as
|
|
193
|
+
* React Aria's `Autocomplete` takes `filter`. For matching that ignores
|
|
194
|
+
* accents, matches the start of each word, or matches out of order.
|
|
195
|
+
*
|
|
196
|
+
* Absent, an option is kept where its words hold the filter, in any case. A
|
|
197
|
+
* property only, there being no way to write a function in markup.
|
|
198
|
+
*/
|
|
199
|
+
filterMatch?: ((text: string, filter: string) => boolean) | undefined;
|
|
188
200
|
/**
|
|
189
201
|
* The options as data, for a list too long to draw whole: each a value,
|
|
190
202
|
* which is also its words, or a record with a `value`, a `label` and
|
|
@@ -447,7 +459,12 @@ export declare class HelixHeadlessListbox extends HelixHeadlessListbox_base {
|
|
|
447
459
|
private get elements();
|
|
448
460
|
/** The options a filter has left showing. */
|
|
449
461
|
private get shown();
|
|
450
|
-
/**
|
|
462
|
+
/**
|
|
463
|
+
* The chosen values, as an array whether or not `multiple` is set.
|
|
464
|
+
*
|
|
465
|
+
* Read against the options' values, so an option whose value holds a comma
|
|
466
|
+
* is one value. See `chosenValues`.
|
|
467
|
+
*/
|
|
451
468
|
get values(): string[];
|
|
452
469
|
/**
|
|
453
470
|
* Applies a choice, announcing first so a listener can refuse it.
|
|
@@ -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, centerInView, columnStep, countLabel, customElement, DelayController, DisabledCascadeController, drawAutofill, endInSight, formatWords, HelixFilterEvent, HelixFormElement, HelixLoadMoreEvent, hostAria, inlineStep, MixinCollection, MixinControllerValue, MixinField, markMatches, OptionWindowController, onApple, pageRows, rangeAdd, reportValues, requestChange, scrollItemIntoView, selectsAll, Typeahead, toggleEvery, typesCharacter, words, wordsLocale, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { AriaForwardController, centerInView, chosenValues, columnStep, countLabel, customElement, DelayController, DisabledCascadeController, drawAutofill, endInSight, formatWords, HelixFilterEvent, HelixFormElement, HelixLoadMoreEvent, hostAria, inlineStep, MixinCollection, MixinControllerValue, MixinField, markMatches, OptionWindowController, onApple, pageRows, rangeAdd, reportValues, requestChange, scrollItemIntoView, selectsAll, Typeahead, toggleEvery, typesCharacter, words, wordsLocale, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { magnifyingGlass } from '@fusion.dev/helix-icons';
|
|
9
9
|
import { html, nothing } from 'lit';
|
|
10
10
|
import { property, state } from 'lit/decorators.js';
|
|
@@ -326,7 +326,7 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
|
|
|
326
326
|
return this.entries.filter((option) => !option.hidden);
|
|
327
327
|
}
|
|
328
328
|
get values() {
|
|
329
|
-
return this.value
|
|
329
|
+
return chosenValues(this.value, new Set(this.entries.map((option) => option.value)), this.multiple);
|
|
330
330
|
}
|
|
331
331
|
choose(value) {
|
|
332
332
|
const chosen = this.values;
|
|
@@ -392,10 +392,14 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
|
|
|
392
392
|
}
|
|
393
393
|
place() {
|
|
394
394
|
this.forgetRemoved();
|
|
395
|
-
const
|
|
395
|
+
const typed = this.filter.trim();
|
|
396
|
+
const needle = typed.toLowerCase();
|
|
397
|
+
const match = this.filterMatch;
|
|
396
398
|
const chosen = new Set(this.values);
|
|
397
399
|
for (const option of this.entries) {
|
|
398
|
-
option.hidden =
|
|
400
|
+
option.hidden =
|
|
401
|
+
needle !== '' &&
|
|
402
|
+
!(match ? match(option.text, typed) : option.text.toLowerCase().includes(needle));
|
|
399
403
|
option.selected = chosen.has(option.value);
|
|
400
404
|
}
|
|
401
405
|
for (const group of this.groups) {
|
|
@@ -425,10 +429,11 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
|
|
|
425
429
|
return;
|
|
426
430
|
}
|
|
427
431
|
const present = new Set(this.entries.map((option) => option.value));
|
|
428
|
-
const
|
|
432
|
+
const held = chosenValues(this.value, new Set([...present, ...this.seen]), this.multiple);
|
|
433
|
+
const gone = held.filter((value) => this.seen.has(value) && !present.has(value));
|
|
429
434
|
this.seen = present;
|
|
430
435
|
if (gone.length > 0) {
|
|
431
|
-
this.value =
|
|
436
|
+
this.value = held.filter((value) => !gone.includes(value)).join(',');
|
|
432
437
|
}
|
|
433
438
|
}
|
|
434
439
|
followActive(changed) {
|
|
@@ -444,7 +449,7 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
|
|
|
444
449
|
}
|
|
445
450
|
sync() {
|
|
446
451
|
if (this.multiple) {
|
|
447
|
-
reportValues(this.internals, this.name, this.
|
|
452
|
+
reportValues(this.internals, this.name, this.values);
|
|
448
453
|
}
|
|
449
454
|
else {
|
|
450
455
|
this.internals.setFormValue(this.value === '' ? null : this.value);
|
|
@@ -498,7 +503,7 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
|
|
|
498
503
|
}
|
|
499
504
|
}
|
|
500
505
|
get currentValue() {
|
|
501
|
-
return this.activeValue || (this.
|
|
506
|
+
return this.activeValue || (this.values[0] ?? '');
|
|
502
507
|
}
|
|
503
508
|
linkActive() {
|
|
504
509
|
const list = this.renderRoot.querySelector(this.filterable ? '[part="search-field"]' : '[part="items"]');
|
|
@@ -666,6 +671,9 @@ __decorate([
|
|
|
666
671
|
words,
|
|
667
672
|
property({ attribute: 'filter-label' })
|
|
668
673
|
], HelixHeadlessListbox.prototype, "filterLabel", void 0);
|
|
674
|
+
__decorate([
|
|
675
|
+
property({ attribute: false })
|
|
676
|
+
], HelixHeadlessListbox.prototype, "filterMatch", void 0);
|
|
669
677
|
__decorate([
|
|
670
678
|
property({ attribute: false })
|
|
671
679
|
], HelixHeadlessListbox.prototype, "items", void 0);
|
|
@@ -358,6 +358,10 @@ export declare class HelixHeadlessMap extends HelixHeadlessMap_base {
|
|
|
358
358
|
/**
|
|
359
359
|
* A route or area renamed, or its description changed, which the list says
|
|
360
360
|
* and a slot change does not report.
|
|
361
|
+
*
|
|
362
|
+
* Not the map's own `gestures` news, which it sends from `updated` and which
|
|
363
|
+
* changes nothing it draws: an update asked for there ran every map's
|
|
364
|
+
* update twice, and Lit's development build warned about it.
|
|
361
365
|
*/
|
|
362
366
|
private readonly handlePartChange;
|
|
363
367
|
/**
|
|
@@ -90,7 +90,7 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
|
|
|
90
90
|
}
|
|
91
91
|
};
|
|
92
92
|
this.handlePartChange = (event) => {
|
|
93
|
-
if (event.detail.kind !== 'place') {
|
|
93
|
+
if (event.detail.kind !== 'place' && event.detail.kind !== 'gestures') {
|
|
94
94
|
this.requestUpdate();
|
|
95
95
|
}
|
|
96
96
|
};
|
|
@@ -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;
|