@fusion.dev/helix-headless 0.171.0 → 0.173.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 +1350 -124
- package/dist/button-group/root/button-group-root.d.ts +30 -0
- package/dist/button-group/root/button-group-root.js +50 -4
- package/dist/canvas/root/canvas-root.d.ts +7 -0
- package/dist/canvas/root/canvas-root.js +7 -0
- package/dist/chat/composer/chat-composer.d.ts +28 -1
- package/dist/chat/composer/chat-composer.js +22 -3
- package/dist/chat/root/chat-root.d.ts +38 -2
- package/dist/chat/root/chat-root.js +38 -1
- package/dist/color-input/root/color-input-root.d.ts +15 -1
- package/dist/color-input/root/color-input-root.js +19 -1
- package/dist/color-picker/root/color-picker-root.d.ts +5 -1
- package/dist/color-picker/root/color-picker-root.js +1 -1
- package/dist/copy-button/root/copy-button-root.d.ts +43 -3
- package/dist/copy-button/root/copy-button-root.js +49 -3
- package/dist/data-table/root/data-table-root.d.ts +5 -1
- package/dist/data-table/root/data-table-root.js +1 -0
- package/dist/dialog/root/dialog-root.js +1 -0
- package/dist/drawer/root/drawer-root.d.ts +4 -0
- package/dist/drawer/root/drawer-root.js +1 -0
- package/dist/element-events.generated.js +3 -0
- package/dist/graph/root/graph-root.d.ts +22 -0
- package/dist/graph/root/graph-root.js +21 -5
- package/dist/lightbox/root/lightbox-root.js +1 -0
- package/dist/list/root/list-root.d.ts +6 -1
- package/dist/list/root/list-root.js +3 -2
- package/dist/listbox/root/listbox-root.d.ts +2 -1
- package/dist/listbox/root/listbox-root.js +3 -2
- package/dist/menu/item/menu-item.d.ts +2 -0
- package/dist/menu/item/menu-item.js +2 -0
- package/dist/menu/root/menu-root.d.ts +73 -12
- package/dist/menu/root/menu-root.js +103 -7
- package/dist/outline/root/outline-root.d.ts +57 -0
- package/dist/outline/root/outline-root.js +98 -29
- package/dist/popover/root/popover-root.d.ts +59 -6
- package/dist/popover/root/popover-root.js +51 -3
- package/dist/select/root/select-root.d.ts +2 -2
- package/dist/select/root/select-root.js +2 -2
- package/dist/speed-dial/root/speed-dial-root.d.ts +14 -0
- package/dist/speed-dial/root/speed-dial-root.js +34 -2
- package/dist/splitter/root/splitter-root.d.ts +25 -2
- package/dist/splitter/root/splitter-root.js +19 -4
- package/dist/tag/group/tag-group.d.ts +8 -1
- package/dist/tag/group/tag-group.js +8 -0
- package/dist/tag/root/tag-root.d.ts +12 -0
- package/dist/tag/root/tag-root.js +12 -0
- package/dist/textarea/root/textarea-root.d.ts +8 -0
- package/dist/textarea/root/textarea-root.js +16 -0
- package/dist/time-picker/root/time-picker-root.d.ts +14 -0
- package/dist/time-picker/root/time-picker-root.js +8 -1
- package/dist/timeline/item/timeline-item.d.ts +5 -1
- package/dist/timeline/item/timeline-item.js +9 -0
- package/dist/timeline/root/timeline-root.d.ts +23 -0
- package/dist/timeline/root/timeline-root.js +17 -3
- package/dist/tooltip/root/tooltip-root.d.ts +10 -0
- package/dist/tooltip/root/tooltip-root.js +14 -2
- package/dist/virtual-list/reorder/index.d.ts +1 -0
- package/dist/virtual-list/reorder/index.js +4 -0
- package/dist/virtual-list/reorder/virtual-list-reorder.d.ts +86 -0
- package/dist/virtual-list/reorder/virtual-list-reorder.js +193 -0
- package/dist/virtual-list/root/virtual-list-root.d.ts +49 -11
- package/dist/virtual-list/root/virtual-list-root.js +10 -1
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +880 -48
- package/types/vue.d.ts +174 -19
- package/types/words.d.ts +22 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AriaForwardController, HelixElement, KeyboardNavigationController, type Placement } from '@fusion.dev/helix-core';
|
|
1
|
+
import { AriaForwardController, HelixElement, KeyboardNavigationController, OpenPhaseController, type Placement } from '@fusion.dev/helix-core';
|
|
2
2
|
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
3
|
declare const HelixHeadlessMenu_base: (new (...args: any[]) => import("@fusion.dev/helix-core").CollectionElement) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerOpenInterface) & typeof HelixElement;
|
|
4
4
|
/**
|
|
@@ -18,7 +18,9 @@ declare const HelixHeadlessMenu_base: (new (...args: any[]) => import("@fusion.d
|
|
|
18
18
|
*
|
|
19
19
|
* Submenus drill down by default. A submenu replaces the panel's content, and a
|
|
20
20
|
* back row returns to the previous page. One panel stays inside a narrow
|
|
21
|
-
* window, and works on touch screens, which have no hover.
|
|
21
|
+
* window, and works on touch screens, which have no hover. Focus follows a
|
|
22
|
+
* mouse resting on a row in either mode, and a drilled page is a `menu` named
|
|
23
|
+
* by the row that opened it.
|
|
22
24
|
*
|
|
23
25
|
* **`submenus="flyout"` opens each submenu in a panel of its own beside its
|
|
24
26
|
* row**, as React Aria's `SubmenuTrigger` and Radix's `Sub` do. Resting the
|
|
@@ -52,6 +54,11 @@ declare const HelixHeadlessMenu_base: (new (...args: any[]) => import("@fusion.d
|
|
|
52
54
|
* trigger's width, for a menu as wide as its trigger:
|
|
53
55
|
* `helix-menu::part(surface) { min-inline-size: var(--floating-anchor-inline-size) }`.
|
|
54
56
|
*
|
|
57
|
+
* **The choices in one place.** `checkedValues` holds the values of the rows
|
|
58
|
+
* that are checked, anywhere in the menu, and setting it checks exactly
|
|
59
|
+
* those rows, as React Aria's `selectedKeys` does. `change` is sent on the
|
|
60
|
+
* menu when a reader's choice changes it.
|
|
61
|
+
*
|
|
55
62
|
* With `context`, the trigger is an area and the menu is its context menu: a
|
|
56
63
|
* right-click, a long press, Shift+F10 or the menu key over the area opens it
|
|
57
64
|
* at the pointer, or at the area's corner from the keyboard, as Radix's context
|
|
@@ -74,7 +81,8 @@ declare const HelixHeadlessMenu_base: (new (...args: any[]) => import("@fusion.d
|
|
|
74
81
|
* field: Up and Down Arrow move the highlight through the rows that are left,
|
|
75
82
|
* Enter chooses the highlighted row, Right Arrow opens its submenu, Left Arrow
|
|
76
83
|
* on a submenu goes back, and Escape clears the filter and then closes the
|
|
77
|
-
* menu.
|
|
84
|
+
* menu. A screen reader hears how many rows the filter keeps
|
|
85
|
+
* (`results-label`). Home, End and the other keys move the caret and type. The field names
|
|
78
86
|
* the highlighted row with `aria-activedescendant`, and the rows are a `menu`
|
|
79
87
|
* after the field. Cancel `helix-filter` to keep every row
|
|
80
88
|
* showing and narrow the rows yourself, such as by replacing them with rows
|
|
@@ -87,9 +95,12 @@ declare const HelixHeadlessMenu_base: (new (...args: any[]) => import("@fusion.d
|
|
|
87
95
|
* @documentation https://fusion.dev/helix/components/menu
|
|
88
96
|
*
|
|
89
97
|
* @slot - The items.
|
|
98
|
+
* @slot empty - What the menu shows when it has no items, for content richer than `empty-text`.
|
|
99
|
+
* @slot loading - What the menu shows while its items load, for content richer than `loading-text`.
|
|
90
100
|
* @slot no-results - What the menu shows when the filter keeps no rows, for content richer than `no-results-text`.
|
|
91
101
|
* @slot trigger - The control that opens the menu, or with `context` the area a context menu opens over.
|
|
92
102
|
*
|
|
103
|
+
* @event {Event} change - A reader's choice checked or unchecked rows. Read `checkedValues` for the rows that are checked now.
|
|
93
104
|
* @event {HelixBeforeCloseEvent} helix-before-close - The menu is about to close. Cancelable: cancel it to keep it open.
|
|
94
105
|
* @event {HelixBeforeOpenEvent} helix-before-open - The menu is about to open. Cancelable: cancel it to keep it closed.
|
|
95
106
|
* @event {HelixFilterEvent} helix-filter - The text in the filter field changed, with the new text in `detail.value`. Cancelable: cancel it to keep every row showing and narrow the rows yourself.
|
|
@@ -110,6 +121,8 @@ declare const HelixHeadlessMenu_base: (new (...args: any[]) => import("@fusion.d
|
|
|
110
121
|
* @cssproperty --floating-available-block-size - The room between the panel and the edge of the screen it opens toward, written on the panel while it is open. Read it; a value set here is written over.
|
|
111
122
|
*
|
|
112
123
|
* @cssstate drilled - A submenu's page is showing, so content that is not a row can hide with the top page.
|
|
124
|
+
* @cssstate entering - From the moment the panel shows until its entrance has finished.
|
|
125
|
+
* @cssstate exiting - From the moment the panel hides until its exit has finished.
|
|
113
126
|
* @cssstate mark - A row on the current page has a checkbox or a link, so every row keeps the trailing column.
|
|
114
127
|
* @cssstate open - While the menu is open.
|
|
115
128
|
* @cssstate placed-bottom - While the open panel sits below its trigger.
|
|
@@ -124,6 +137,7 @@ export declare class HelixHeadlessMenu extends HelixHeadlessMenu_base {
|
|
|
124
137
|
* `registerWords('de', { 'helix-menu': { backLabel: 'Zurück' } })`.
|
|
125
138
|
*/
|
|
126
139
|
static words: string;
|
|
140
|
+
protected statusSlots: boolean;
|
|
127
141
|
/**
|
|
128
142
|
* The text of the row that returns from a submenu.
|
|
129
143
|
*/
|
|
@@ -204,6 +218,12 @@ export declare class HelixHeadlessMenu extends HelixHeadlessMenu_base {
|
|
|
204
218
|
* Space between the trigger and the surface, in pixels.
|
|
205
219
|
*/
|
|
206
220
|
offset: number;
|
|
221
|
+
/**
|
|
222
|
+
* What opens the menu from its trigger. `long-press` leaves a press to the
|
|
223
|
+
* trigger's own action and opens the menu on a press held for half a second,
|
|
224
|
+
* or on Alt+Down or Alt+Up.
|
|
225
|
+
*/
|
|
226
|
+
openOn: 'click' | 'long-press';
|
|
207
227
|
/** Whether the trigger opens the menu only on a held press. */
|
|
208
228
|
private get pressHeld();
|
|
209
229
|
/**
|
|
@@ -213,18 +233,18 @@ export declare class HelixHeadlessMenu extends HelixHeadlessMenu_base {
|
|
|
213
233
|
private readonly longPress;
|
|
214
234
|
/** Where a held press on a context menu's area is, to open the menu there. */
|
|
215
235
|
private heldAt;
|
|
216
|
-
/**
|
|
217
|
-
* What opens the menu from its trigger. `long-press` leaves a press to the
|
|
218
|
-
* trigger's own action and opens the menu on a press held for half a second,
|
|
219
|
-
* or on Alt+Down or Alt+Up.
|
|
220
|
-
*/
|
|
221
|
-
openOn: 'click' | 'long-press';
|
|
222
236
|
/**
|
|
223
237
|
* The side the menu opens on, when there is room.
|
|
224
238
|
*
|
|
225
239
|
* It moves if that side does not fit.
|
|
226
240
|
*/
|
|
227
241
|
placement: Placement;
|
|
242
|
+
/**
|
|
243
|
+
* What a screen reader hears when the filter narrows the rows. `{count}` is
|
|
244
|
+
* how many rows are left, written in the locale, and each language writes
|
|
245
|
+
* the forms it has, as Listbox's `results-label` does.
|
|
246
|
+
*/
|
|
247
|
+
resultsLabel: string;
|
|
228
248
|
/**
|
|
229
249
|
* How a submenu opens. `drill` shows it in place of the page showing, with
|
|
230
250
|
* a way back. `flyout` opens it in a panel of its own beside its row.
|
|
@@ -257,6 +277,7 @@ export declare class HelixHeadlessMenu extends HelixHeadlessMenu_base {
|
|
|
257
277
|
* menu that drills, so those keys are spoken for here rather than unhandled.
|
|
258
278
|
*/
|
|
259
279
|
protected readonly keys: KeyboardNavigationController;
|
|
280
|
+
protected readonly phases: OpenPhaseController;
|
|
260
281
|
/** Carries a host `aria-label` and `aria-labelledby` to the surface. */
|
|
261
282
|
protected readonly aria: AriaForwardController;
|
|
262
283
|
/** Where a context menu was asked for, in the viewport, while it is open. */
|
|
@@ -310,6 +331,21 @@ export declare class HelixHeadlessMenu extends HelixHeadlessMenu_base {
|
|
|
310
331
|
* it, the one direction a reference crosses.
|
|
311
332
|
*/
|
|
312
333
|
private reach;
|
|
334
|
+
/** Watches for rows taken away while the menu is open. */
|
|
335
|
+
private rowWatch;
|
|
336
|
+
/**
|
|
337
|
+
* Moves the reader to a neighbor when the page takes away the row they were
|
|
338
|
+
* on: the row that took its place, or the one before it where it was the
|
|
339
|
+
* last. A menu with no rows left closes and gives focus to its trigger.
|
|
340
|
+
*
|
|
341
|
+
* **Focus went to the body**, and the menu then ignored every key, its
|
|
342
|
+
* listener being on the host: measured in Chromium, removing the focused
|
|
343
|
+
* row and pressing ArrowUp did nothing. List and Virtual List move focus to
|
|
344
|
+
* a neighbor the same way, as React Aria's collections do. With `filterable`,
|
|
345
|
+
* focus stays in the field and the highlight moves. Focus a reader put
|
|
346
|
+
* anywhere but the body is left there.
|
|
347
|
+
*/
|
|
348
|
+
private readonly followRemoved;
|
|
313
349
|
/** Opens the menu. */
|
|
314
350
|
show(): void;
|
|
315
351
|
/** Opens the menu with focus on its first or its last row. */
|
|
@@ -327,6 +363,23 @@ export declare class HelixHeadlessMenu extends HelixHeadlessMenu_base {
|
|
|
327
363
|
private enter;
|
|
328
364
|
/** Returns to the page above, or closes when there is none. */
|
|
329
365
|
private leave;
|
|
366
|
+
/** The choice rows anywhere in the menu, in document order. */
|
|
367
|
+
private get choiceRows();
|
|
368
|
+
/**
|
|
369
|
+
* The values of the choice rows that are checked, anywhere in the menu, in
|
|
370
|
+
* document order.
|
|
371
|
+
*
|
|
372
|
+
* Set it to check exactly those rows and uncheck every other choice row, as
|
|
373
|
+
* React Aria's `selectedKeys` controls a menu's selection from one place.
|
|
374
|
+
* Each row keeps its own `checked` too, which this reads and writes. `change`
|
|
375
|
+
* is sent on the menu when a reader's choice changes it.
|
|
376
|
+
*/
|
|
377
|
+
get checkedValues(): string[];
|
|
378
|
+
set checkedValues(values: readonly string[]);
|
|
379
|
+
/** The checked values `change` last said, joined, so a choice that moved nothing says nothing. */
|
|
380
|
+
private toldChecked;
|
|
381
|
+
/** Sends `change` when a reader's choice has changed which rows are checked. */
|
|
382
|
+
private tellChecked;
|
|
330
383
|
private readonly handleSelect;
|
|
331
384
|
/**
|
|
332
385
|
* Keeps whatever has focus in sight, however it got there.
|
|
@@ -390,9 +443,14 @@ export declare class HelixHeadlessMenu extends HelixHeadlessMenu_base {
|
|
|
390
443
|
private grace;
|
|
391
444
|
private stopHover;
|
|
392
445
|
/**
|
|
393
|
-
* A mouse resting on a row
|
|
394
|
-
* moment the row's submenu opens, or the submenus below the row's own page
|
|
446
|
+
* A mouse resting on a row: focus follows it, and under `flyout`, after a
|
|
447
|
+
* moment, the row's submenu opens, or the submenus below the row's own page
|
|
395
448
|
* close. Not while the pointer is crossing toward an open submenu.
|
|
449
|
+
*
|
|
450
|
+
* **In every mode**, as the APG's menus, React Aria's and Radix's have it.
|
|
451
|
+
* Under `drill` the pointer did not move focus, so the row under the
|
|
452
|
+
* pointer and the row the keys moved from were two rows, measured in
|
|
453
|
+
* Chromium: hovering Delete left focus on Cut.
|
|
396
454
|
*/
|
|
397
455
|
private readonly handleHover;
|
|
398
456
|
/** The pointer leaving a row whose submenu is open, which starts the grace area. */
|
|
@@ -402,8 +460,11 @@ export declare class HelixHeadlessMenu extends HelixHeadlessMenu_base {
|
|
|
402
460
|
connectedCallback(): void;
|
|
403
461
|
disconnectedCallback(): void;
|
|
404
462
|
protected updated(changed: PropertyValues): void;
|
|
463
|
+
/** Whether the surface is named by a submenu's page, which `nameSurface` undoes at the top. */
|
|
464
|
+
private namedByPage;
|
|
405
465
|
/**
|
|
406
|
-
* Names the surface by its trigger, where nothing else names it
|
|
466
|
+
* Names the surface by its trigger, where nothing else names it, and a
|
|
467
|
+
* submenu's page by its title.
|
|
407
468
|
*
|
|
408
469
|
* By element reference, which is the one direction that crosses: the surface
|
|
409
470
|
* is in this shadow root and the trigger is in the tree around it. A host
|
|
@@ -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, anchorElement, customElement, DrilldownController, deepActiveElement, deepContains, filterKeeps, focusTarget, HelixElement, HelixFilterEvent, inlineStep, KeyboardNavigationController, linkDescription, MixinCollection, MixinControllerOpen, markMatches, onUnlessFalse, PopoverController, scrollItemIntoView, sideOf, slottedWords, tellDisclosure, typeaheadBy, unlinkDescription, words, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { AriaForwardController, anchorElement, customElement, DrilldownController, deepActiveElement, deepContains, filterKeeps, focusTarget, formatWords, HelixElement, HelixFilterEvent, inlineStep, KeyboardNavigationController, linkDescription, MixinCollection, MixinControllerOpen, markMatches, OpenPhaseController, onUnlessFalse, PopoverController, scrollItemIntoView, sideOf, slottedWords, tellDisclosure, typeaheadBy, unlinkDescription, 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 } from 'lit/decorators.js';
|
|
@@ -30,6 +30,7 @@ const isSection = (element) => 'isMenuSection' in element && element.isMenuSecti
|
|
|
30
30
|
let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinControllerOpen(HelixElement)) {
|
|
31
31
|
constructor() {
|
|
32
32
|
super(...arguments);
|
|
33
|
+
this.statusSlots = true;
|
|
33
34
|
this.backLabel = 'Back';
|
|
34
35
|
this.closeOnSelect = true;
|
|
35
36
|
this.context = false;
|
|
@@ -40,6 +41,7 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
40
41
|
this.longPressLabel = 'Long press or press Alt+Down to show the menu';
|
|
41
42
|
this.noResultsText = 'No matches';
|
|
42
43
|
this.offset = 4;
|
|
44
|
+
this.openOn = 'click';
|
|
43
45
|
this.longPress = new LongPress(this, {
|
|
44
46
|
onLongPress: () => {
|
|
45
47
|
if (this.context) {
|
|
@@ -54,8 +56,8 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
54
56
|
}
|
|
55
57
|
},
|
|
56
58
|
});
|
|
57
|
-
this.openOn = 'click';
|
|
58
59
|
this.placement = 'bottom-start';
|
|
60
|
+
this.resultsLabel = '{count, plural, one {# result} other {# results}}';
|
|
59
61
|
this.submenus = 'drill';
|
|
60
62
|
this.drilldown = new DrilldownController(this, {
|
|
61
63
|
isRow: (element) => 'isMenuItem' in element && element.isMenuItem === true,
|
|
@@ -70,6 +72,14 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
70
72
|
selects: () => false,
|
|
71
73
|
typeahead: typeaheadBy((item) => slottedWords(item, [''])),
|
|
72
74
|
});
|
|
75
|
+
this.phases = new OpenPhaseController(this, {
|
|
76
|
+
mark: {
|
|
77
|
+
entering: (on) => this.toggleState('entering', on),
|
|
78
|
+
exiting: (on) => this.toggleState('exiting', on),
|
|
79
|
+
},
|
|
80
|
+
open: () => this.open,
|
|
81
|
+
surface: () => this.partNamed('surface'),
|
|
82
|
+
});
|
|
73
83
|
this.aria = new AriaForwardController(this, {
|
|
74
84
|
label: true,
|
|
75
85
|
target: () => this.menuBox,
|
|
@@ -98,6 +108,37 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
98
108
|
event.stopPropagation();
|
|
99
109
|
this.filterBy(event.target.value);
|
|
100
110
|
};
|
|
111
|
+
this.followRemoved = (records) => {
|
|
112
|
+
if (!this.open) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const active = deepActiveElement();
|
|
116
|
+
const lost = active === null || active === document.body || !active.isConnected;
|
|
117
|
+
const record = records.find((one) => [...one.removedNodes].some((node) => isItem(node) && (this.filterable ? node === this.highlighted : lost)));
|
|
118
|
+
if (!record) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const items = this.items;
|
|
122
|
+
const after = (from, step) => {
|
|
123
|
+
for (let node = from; node; node = node[step]) {
|
|
124
|
+
if (items.includes(node)) {
|
|
125
|
+
return node;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return undefined;
|
|
129
|
+
};
|
|
130
|
+
const next = after(record.nextSibling, 'nextSibling') ??
|
|
131
|
+
after(record.previousSibling, 'previousSibling') ??
|
|
132
|
+
items.at(-1);
|
|
133
|
+
if (this.filterable || next) {
|
|
134
|
+
this.reach(next);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
this.hide();
|
|
138
|
+
if (!this.context) {
|
|
139
|
+
this.trigger.focus();
|
|
140
|
+
}
|
|
141
|
+
};
|
|
101
142
|
this.handleContextMenu = (event) => {
|
|
102
143
|
if (!this.context) {
|
|
103
144
|
return;
|
|
@@ -124,6 +165,7 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
124
165
|
return;
|
|
125
166
|
}
|
|
126
167
|
const chosen = item;
|
|
168
|
+
this.tellChecked();
|
|
127
169
|
if (chosen.hasSubmenu) {
|
|
128
170
|
event.stopPropagation();
|
|
129
171
|
this.enter(chosen);
|
|
@@ -141,6 +183,7 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
141
183
|
if (item) {
|
|
142
184
|
this.revealRow(item);
|
|
143
185
|
}
|
|
186
|
+
this.nameSurface();
|
|
144
187
|
};
|
|
145
188
|
this.handleKeydown = (event) => {
|
|
146
189
|
if (!this.open) {
|
|
@@ -235,7 +278,7 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
235
278
|
}
|
|
236
279
|
};
|
|
237
280
|
this.handleHover = (event) => {
|
|
238
|
-
if (!
|
|
281
|
+
if (!this.open || event.pointerType !== 'mouse') {
|
|
239
282
|
return;
|
|
240
283
|
}
|
|
241
284
|
const row = event
|
|
@@ -293,6 +336,7 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
293
336
|
until: Date.now() + GRACE,
|
|
294
337
|
};
|
|
295
338
|
};
|
|
339
|
+
this.namedByPage = false;
|
|
296
340
|
}
|
|
297
341
|
static { this.words = 'helix-menu'; }
|
|
298
342
|
get flies() {
|
|
@@ -487,6 +531,27 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
487
531
|
this.drilldown.back();
|
|
488
532
|
this.updateComplete.then(() => this.reach(leaving ?? undefined));
|
|
489
533
|
}
|
|
534
|
+
get choiceRows() {
|
|
535
|
+
return [...this.querySelectorAll('*')].filter((node) => isItem(node) && node.choice);
|
|
536
|
+
}
|
|
537
|
+
get checkedValues() {
|
|
538
|
+
return this.choiceRows.filter((row) => row.checked).map((row) => row.value);
|
|
539
|
+
}
|
|
540
|
+
set checkedValues(values) {
|
|
541
|
+
const wanted = new Set(values);
|
|
542
|
+
for (const row of this.choiceRows) {
|
|
543
|
+
row.checked = wanted.has(row.value);
|
|
544
|
+
}
|
|
545
|
+
this.toldChecked = this.checkedValues.join('\u0000');
|
|
546
|
+
}
|
|
547
|
+
tellChecked() {
|
|
548
|
+
const now = this.checkedValues.join('\u0000');
|
|
549
|
+
const before = this.toldChecked;
|
|
550
|
+
this.toldChecked = now;
|
|
551
|
+
if (before !== undefined && before !== now) {
|
|
552
|
+
this.dispatchEvent(new Event('change', { bubbles: true }));
|
|
553
|
+
}
|
|
554
|
+
}
|
|
490
555
|
revealRow(item) {
|
|
491
556
|
const holder = this.flies ? this.holderOf(item) : null;
|
|
492
557
|
const surface = holder
|
|
@@ -555,6 +620,8 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
555
620
|
this.addEventListener('pointercancel', this.handlePointerCancel);
|
|
556
621
|
this.addEventListener('click', this.handleHeldClick, true);
|
|
557
622
|
this.addEventListener('focusin', this.handleTriggerFocusIn);
|
|
623
|
+
this.rowWatch = new MutationObserver(this.followRemoved);
|
|
624
|
+
this.rowWatch.observe(this, { childList: true, subtree: true });
|
|
558
625
|
void this.whenSettled.then(() => {
|
|
559
626
|
this.settledOnce = true;
|
|
560
627
|
if (!this.context) {
|
|
@@ -581,6 +648,8 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
581
648
|
this.removeEventListener('pointercancel', this.handlePointerCancel);
|
|
582
649
|
this.removeEventListener('click', this.handleHeldClick, true);
|
|
583
650
|
this.removeEventListener('focusin', this.handleTriggerFocusIn);
|
|
651
|
+
this.rowWatch?.disconnect();
|
|
652
|
+
this.rowWatch = undefined;
|
|
584
653
|
if (this.described) {
|
|
585
654
|
unlinkDescription(this.described, this);
|
|
586
655
|
this.described = undefined;
|
|
@@ -588,6 +657,9 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
588
657
|
}
|
|
589
658
|
updated(changed) {
|
|
590
659
|
super.updated(changed);
|
|
660
|
+
if (changed.has('open') && this.open) {
|
|
661
|
+
this.toldChecked = this.checkedValues.join('\u0000');
|
|
662
|
+
}
|
|
591
663
|
if (!this.context) {
|
|
592
664
|
this.tellTrigger();
|
|
593
665
|
}
|
|
@@ -628,7 +700,20 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
628
700
|
}
|
|
629
701
|
nameSurface() {
|
|
630
702
|
const surface = this.menuBox;
|
|
631
|
-
if (!surface
|
|
703
|
+
if (!surface) {
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
if (!this.flies && !this.drilldown.atRoot && this.partNamed('title')) {
|
|
707
|
+
surface.setAttribute('aria-labelledby', 'title');
|
|
708
|
+
this.namedByPage = true;
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
if (this.namedByPage) {
|
|
712
|
+
this.namedByPage = false;
|
|
713
|
+
surface.removeAttribute('aria-labelledby');
|
|
714
|
+
this.aria.forward();
|
|
715
|
+
}
|
|
716
|
+
if (!('ariaLabelledByElements' in surface)) {
|
|
632
717
|
return;
|
|
633
718
|
}
|
|
634
719
|
if (this.hasAttribute('aria-labelledby')) {
|
|
@@ -673,8 +758,15 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
673
758
|
render() {
|
|
674
759
|
const point = this.context && this.point;
|
|
675
760
|
const filtering = this.filtering;
|
|
676
|
-
const
|
|
677
|
-
|
|
761
|
+
const count = filtering
|
|
762
|
+
? this.rowsOf(this.filterPage).filter((row) => this.kept(row)).length
|
|
763
|
+
: 0;
|
|
764
|
+
const none = filtering && count === 0;
|
|
765
|
+
this.announced = none
|
|
766
|
+
? this.noResultsText
|
|
767
|
+
: filtering
|
|
768
|
+
? formatWords(this, 'resultsLabel', { count })
|
|
769
|
+
: '';
|
|
678
770
|
const rows = html `${this.flies || this.drilldown.atRoot
|
|
679
771
|
? nothing
|
|
680
772
|
: html `<div part="header">
|
|
@@ -690,7 +782,7 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
690
782
|
${unsafeSVG(caretLeft.content)}
|
|
691
783
|
</svg>
|
|
692
784
|
</button>
|
|
693
|
-
<span part="title">${this.drilldown.label}</span>
|
|
785
|
+
<span part="title" id="title">${this.drilldown.label}</span>
|
|
694
786
|
</div>`}
|
|
695
787
|
<slot @slotchange=${(event) => this.countRows(event)}></slot>
|
|
696
788
|
${none
|
|
@@ -775,6 +867,10 @@ __decorate([
|
|
|
775
867
|
__decorate([
|
|
776
868
|
property()
|
|
777
869
|
], HelixHeadlessMenu.prototype, "placement", void 0);
|
|
870
|
+
__decorate([
|
|
871
|
+
words,
|
|
872
|
+
property({ attribute: 'results-label' })
|
|
873
|
+
], HelixHeadlessMenu.prototype, "resultsLabel", void 0);
|
|
778
874
|
__decorate([
|
|
779
875
|
property()
|
|
780
876
|
], HelixHeadlessMenu.prototype, "submenus", void 0);
|
|
@@ -40,6 +40,22 @@ import { type Branch } from '../levels.js';
|
|
|
40
40
|
* headings are far apart, none is in the band and no section is marked, and
|
|
41
41
|
* when they are close together, several are in the band at once.
|
|
42
42
|
*
|
|
43
|
+
* **Several sections at once.** `marks="visible"` marks the link of every
|
|
44
|
+
* section with part of it on screen, from the reading line to the bottom of
|
|
45
|
+
* what the sections scroll in, as Zag's table of contents marks its active
|
|
46
|
+
* ids. The mark then runs from the first of those links to the last, and
|
|
47
|
+
* `marked` lists their ids in page order.
|
|
48
|
+
*
|
|
49
|
+
* **Going to a section from code.** `scrollToSection(id)` scrolls to the
|
|
50
|
+
* section as a press on its link does, with `behavior`, and marks its link at
|
|
51
|
+
* once.
|
|
52
|
+
*
|
|
53
|
+
* **Styling a link by its state.** Each link the outline finds carries
|
|
54
|
+
* `data-depth`, its depth in the outline from `1`, nested lists and
|
|
55
|
+
* `data-level` alike, and `aria-current="location"` while its section is
|
|
56
|
+
* marked, so `a[aria-current]` and `a[data-depth="2"]` are the hooks for a
|
|
57
|
+
* rule of yours.
|
|
58
|
+
*
|
|
43
59
|
* @since 0.1.0
|
|
44
60
|
* @status stable
|
|
45
61
|
* @category navigation
|
|
@@ -109,6 +125,12 @@ export declare class HelixHeadlessOutline extends HelixElement {
|
|
|
109
125
|
* slot names it in place of this.
|
|
110
126
|
*/
|
|
111
127
|
label: string;
|
|
128
|
+
/**
|
|
129
|
+
* Which links are marked. `reading` marks the one section being read, the
|
|
130
|
+
* last whose heading has passed the reading line. `visible` marks every
|
|
131
|
+
* section with part of it on screen, and the mark runs across their links.
|
|
132
|
+
*/
|
|
133
|
+
marks: 'reading' | 'visible';
|
|
112
134
|
/**
|
|
113
135
|
* How far below the top of the scroll container a heading counts as reached,
|
|
114
136
|
* in pixels. The scroll container is the window or the panel the sections
|
|
@@ -130,6 +152,14 @@ export declare class HelixHeadlessOutline extends HelixElement {
|
|
|
130
152
|
* than saying it in a sentence nobody reads.
|
|
131
153
|
*/
|
|
132
154
|
get current(): string;
|
|
155
|
+
/**
|
|
156
|
+
* The ids of the sections whose links are marked, in page order: the one
|
|
157
|
+
* `current` names, or under `marks="visible"` every section on screen.
|
|
158
|
+
* Read-only, for the reason `current` gives.
|
|
159
|
+
*/
|
|
160
|
+
get marked(): string[];
|
|
161
|
+
/** What `marked` reports, which only `markTo` writes. */
|
|
162
|
+
private markedIds;
|
|
133
163
|
/**
|
|
134
164
|
* What `current` reports, which only `mark` writes.
|
|
135
165
|
*
|
|
@@ -183,6 +213,12 @@ export declare class HelixHeadlessOutline extends HelixElement {
|
|
|
183
213
|
* wherever the page says the top of the readable area is.
|
|
184
214
|
*/
|
|
185
215
|
private mark;
|
|
216
|
+
/**
|
|
217
|
+
* The sections with part of them on screen, under `marks="visible"`: each
|
|
218
|
+
* runs from its own target to the next one's, and the last to the end of the
|
|
219
|
+
* page.
|
|
220
|
+
*/
|
|
221
|
+
private onScreen;
|
|
186
222
|
/** Where the destination is set, whether measured or asked for. */
|
|
187
223
|
private markTo;
|
|
188
224
|
/**
|
|
@@ -293,6 +329,18 @@ export declare class HelixHeadlessOutline extends HelixElement {
|
|
|
293
329
|
* handled is not ours, and `auto` is the whole point of `auto`.
|
|
294
330
|
*/
|
|
295
331
|
private readonly handleClick;
|
|
332
|
+
/**
|
|
333
|
+
* Scrolls to the section with this id, as a press on its link does, with
|
|
334
|
+
* `behavior`, and marks its link at once. The address and focus stay where
|
|
335
|
+
* they are. Returns whether the outline has a link to that section.
|
|
336
|
+
*/
|
|
337
|
+
scrollToSection(id: string): boolean;
|
|
338
|
+
/**
|
|
339
|
+
* Marks the section a reader asked for, and holds the mark there until the
|
|
340
|
+
* page stops moving. Scrolls to it where `scroll` says this element does
|
|
341
|
+
* the scrolling, and returns whether it did.
|
|
342
|
+
*/
|
|
343
|
+
private travel;
|
|
296
344
|
/**
|
|
297
345
|
* Watches every section, and re-marks whenever any of them crosses anything.
|
|
298
346
|
*
|
|
@@ -312,6 +360,15 @@ export declare class HelixHeadlessOutline extends HelixElement {
|
|
|
312
360
|
* life of the page.
|
|
313
361
|
*/
|
|
314
362
|
private readonly handleSlotChange;
|
|
363
|
+
/**
|
|
364
|
+
* Writes each link's depth in the outline, from 1, as `data-depth`: the
|
|
365
|
+
* list items around it inside the outline, or for a flat run, its place in
|
|
366
|
+
* the lists `data-level` makes of it.
|
|
367
|
+
*
|
|
368
|
+
* On the consumer's links, as `aria-current` is, and only after the first
|
|
369
|
+
* render, where a framework has hydrated them.
|
|
370
|
+
*/
|
|
371
|
+
private writeDepths;
|
|
315
372
|
disconnectedCallback(): void;
|
|
316
373
|
/**
|
|
317
374
|
* Stops waiting for a scroll to land, both halves of the wait.
|