@fusion.dev/helix-headless 0.157.0 → 0.159.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 +851 -56
- package/dist/app-header/root/app-header-root.d.ts +59 -12
- package/dist/app-header/root/app-header-root.js +68 -29
- package/dist/button/root/button-root.d.ts +8 -0
- package/dist/button/root/button-root.js +4 -0
- package/dist/button-group/root/button-group-root.d.ts +20 -7
- package/dist/button-group/root/button-group-root.js +30 -5
- 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/element-events.generated.js +7 -1
- package/dist/lightbox/item/lightbox-item.d.ts +7 -1
- package/dist/lightbox/item/lightbox-item.js +15 -2
- package/dist/lightbox/root/lightbox-root.d.ts +8 -4
- package/dist/lightbox/root/lightbox-root.js +9 -1
- package/dist/lightbox/zoom-controller.d.ts +13 -4
- package/dist/lightbox/zoom-controller.js +17 -0
- 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/menu/item/menu-item.js +1 -1
- package/dist/menu/root/menu-root.d.ts +6 -2
- package/dist/menu/root/menu-root.js +9 -2
- 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/steps/root/steps-root.js +8 -6
- package/dist/steps/step/step.d.ts +9 -0
- package/dist/steps/step/step.js +19 -2
- package/dist/tabs/panel/tab-panel.d.ts +22 -1
- package/dist/tabs/panel/tab-panel.js +22 -1
- package/dist/tabs/root/tabs-root.d.ts +21 -5
- package/dist/tabs/root/tabs-root.js +24 -13
- package/dist/tabs/tab/tab.d.ts +4 -2
- package/dist/tabs/tab/tab.js +3 -1
- package/dist/toast/item/toast-item.d.ts +27 -8
- package/dist/toast/item/toast-item.js +47 -11
- package/dist/toast/root/toast-root.d.ts +24 -1
- package/dist/toast/root/toast-root.js +54 -0
- package/dist/tour/root/tour-root.d.ts +34 -0
- package/dist/tour/root/tour-root.js +92 -3
- package/dist/tour/stop/tour-stop.d.ts +33 -1
- package/dist/tour/stop/tour-stop.js +12 -1
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +667 -102
- package/types/vue.d.ts +148 -23
|
@@ -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
|
};
|
|
@@ -104,7 +104,7 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
|
|
|
104
104
|
updated(changed) {
|
|
105
105
|
super.updated(changed);
|
|
106
106
|
if (this.partNamed('item')) {
|
|
107
|
-
auditName(this, () => this.partNamed('item'), 'Give the row words in its default slot, or a label on the icon it holds.');
|
|
107
|
+
auditName(this, () => this.partNamed('item') ?? undefined, 'Give the row words in its default slot, or a label on the icon it holds.');
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
renderDescription() {
|
|
@@ -42,8 +42,8 @@ declare const HelixHeadlessMenu_base: (new (...args: any[]) => import("@fusion.d
|
|
|
42
42
|
* rows, Home and End go to the first and the last, a typed letter moves to
|
|
43
43
|
* the next row whose name starts with it, Enter and Space choose, Right Arrow
|
|
44
44
|
* opens a row's submenu and Left Arrow goes back, the two swapping on a
|
|
45
|
-
* right-to-left page
|
|
46
|
-
* trigger. Tab closes it and moves on.
|
|
45
|
+
* right-to-left page. Left Arrow on the top page does nothing. Escape closes
|
|
46
|
+
* the menu and returns focus to the trigger. Tab closes it and moves on.
|
|
47
47
|
*
|
|
48
48
|
* While it is open, the surface carries the trigger's width as
|
|
49
49
|
* `--floating-anchor-inline-size` and the room left below it as
|
|
@@ -93,6 +93,10 @@ declare const HelixHeadlessMenu_base: (new (...args: any[]) => import("@fusion.d
|
|
|
93
93
|
* @cssstate drilled - A submenu's page is showing, so content that is not a row can hide with the top page.
|
|
94
94
|
* @cssstate mark - A row on the current page has a checkbox or a link, so every row keeps the trailing column.
|
|
95
95
|
* @cssstate open - While the menu is open.
|
|
96
|
+
* @cssstate placed-bottom - While the open panel sits below its trigger.
|
|
97
|
+
* @cssstate placed-left - While the open panel sits to the left of its trigger.
|
|
98
|
+
* @cssstate placed-right - While the open panel sits to the right of its trigger.
|
|
99
|
+
* @cssstate placed-top - While the open panel sits above its trigger.
|
|
96
100
|
* @cssstate start - A row on the current page has content in its leading column, so every row keeps that column.
|
|
97
101
|
*/
|
|
98
102
|
export declare class HelixHeadlessMenu extends HelixHeadlessMenu_base {
|
|
@@ -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, focusTarget, HelixElement, inlineStep, KeyboardNavigationController, linkDescription, MixinCollection, MixinControllerOpen, PopoverController, scrollItemIntoView, slottedWords, tellDisclosure, typeaheadBy, unlinkDescription, words, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { AriaForwardController, anchorElement, customElement, DrilldownController, deepActiveElement, deepContains, focusTarget, HelixElement, inlineStep, KeyboardNavigationController, linkDescription, MixinCollection, MixinControllerOpen, 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';
|
|
@@ -76,6 +76,13 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
76
76
|
boundary: () => this,
|
|
77
77
|
offset: () => this.offset,
|
|
78
78
|
onDismiss: () => this.hide(),
|
|
79
|
+
onPlace: (placement) => {
|
|
80
|
+
const side = sideOf(placement);
|
|
81
|
+
this.toggleState('placed-bottom', side === 'bottom');
|
|
82
|
+
this.toggleState('placed-left', side === 'left');
|
|
83
|
+
this.toggleState('placed-right', side === 'right');
|
|
84
|
+
this.toggleState('placed-top', side === 'top');
|
|
85
|
+
},
|
|
79
86
|
open: () => this.open,
|
|
80
87
|
placement: () => this.placement,
|
|
81
88
|
surface: () => this.partNamed('surface'),
|
|
@@ -145,7 +152,7 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
145
152
|
this.enter(items[at]);
|
|
146
153
|
}
|
|
147
154
|
else if (along === -1) {
|
|
148
|
-
if (this.flies
|
|
155
|
+
if (this.flies ? this.keyPage === null : this.drilldown.atRoot) {
|
|
149
156
|
return;
|
|
150
157
|
}
|
|
151
158
|
this.leave();
|
|
@@ -25,9 +25,9 @@ declare const HelixHeadlessNumberInput_base: (new (...args: any[]) => import("@f
|
|
|
25
25
|
* The locale's group separators are read in every field, so `1,234` is a
|
|
26
26
|
* number in `en-US`.
|
|
27
27
|
*
|
|
28
|
-
* The field and its two buttons sit in a `role="group"
|
|
29
|
-
* `role-label`, "number field" in English, as its
|
|
30
|
-
* React Aria's NumberField does. On iOS and iPadOS the field is a plain text
|
|
28
|
+
* The field and its two buttons sit in a `role="group"` named by the label,
|
|
29
|
+
* and the field carries `role-label`, "number field" in English, as its
|
|
30
|
+
* `aria-roledescription`, as React Aria's NumberField does. On iOS and iPadOS the field is a plain text
|
|
31
31
|
* field, with no `role="spinbutton"`, which VoiceOver reads as a slider you
|
|
32
32
|
* cannot type in, and no role description, so VoiceOver says the field is
|
|
33
33
|
* required. React Aria's NumberField does the same.
|
|
@@ -492,8 +492,11 @@ export declare class HelixHeadlessNumberInput extends HelixHeadlessNumberInput_b
|
|
|
492
492
|
protected renderIncrementIcon(): TemplateResult;
|
|
493
493
|
protected render(): TemplateResult;
|
|
494
494
|
/**
|
|
495
|
-
* The value as a number, or `NaN` while the control is empty
|
|
495
|
+
* The value as a number, or `NaN` while the control is empty or does not
|
|
496
|
+
* hold a number.
|
|
496
497
|
*
|
|
498
|
+
* Read in the locale and the format, as the form reads it, so `1,5` typed
|
|
499
|
+
* in a locale with a decimal comma is 1.5 here while the form holds 1.5.
|
|
497
500
|
* `NaN` and not zero, which is the trap the platform already avoids: `Number('')`
|
|
498
501
|
* is zero, so an empty control would report a number it does not hold and a
|
|
499
502
|
* form would submit 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, appleTouch,
|
|
7
|
+
import { AriaForwardController, appleTouch, customElement, formatNumber, formatWords, numberAsValue as fromNumber, HelixFormElement, holdCaret, MixinControllerValue, MixinField, parseNumber, restoreValue, submitImplicitly, words, wordsLocale, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { minus, plus } from '@fusion.dev/helix-icons';
|
|
9
9
|
import { html, nothing } from 'lit';
|
|
10
10
|
import { property, state } from 'lit/decorators.js';
|
|
@@ -410,7 +410,7 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
|
|
|
410
410
|
const atMin = parsed !== null && this.min !== undefined && parsed <= this.min;
|
|
411
411
|
const atMax = parsed !== null && this.max !== undefined && parsed >= this.max;
|
|
412
412
|
return html `${this.renderLabel()}
|
|
413
|
-
<div part="field" role="group">
|
|
413
|
+
<div part="field" role="group" aria-labelledby=${this.labeled ? this.labelId : nothing}>
|
|
414
414
|
<button
|
|
415
415
|
part="decrement"
|
|
416
416
|
type="button"
|
|
@@ -484,7 +484,7 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
|
|
|
484
484
|
${this.renderHint()}${this.renderFeedback()}`;
|
|
485
485
|
}
|
|
486
486
|
get valueAsNumber() {
|
|
487
|
-
return
|
|
487
|
+
return this.numeric ?? Number.NaN;
|
|
488
488
|
}
|
|
489
489
|
set valueAsNumber(count) {
|
|
490
490
|
this.value = fromNumber(count);
|
|
@@ -51,7 +51,7 @@ let HelixHeadlessProgress = class HelixHeadlessProgress extends HelixElement {
|
|
|
51
51
|
this.internals.ariaValueMax = String(max);
|
|
52
52
|
this.internals.ariaValueNow = this.indeterminate
|
|
53
53
|
? null
|
|
54
|
-
: String(Math.min(Math.max(this.value
|
|
54
|
+
: String(Math.min(Math.max(Number.isNaN(this.value) ? min : this.value, min), max));
|
|
55
55
|
const shown = this.valueText || slottedWords(this, ['']);
|
|
56
56
|
this.internals.ariaValueText = this.indeterminate ? shown || null : shown || this.text;
|
|
57
57
|
this.toggleState('complete', this.complete);
|
|
@@ -19,7 +19,9 @@ declare const HelixHeadlessSelect_base: (new (...args: any[]) => import("@fusion
|
|
|
19
19
|
* whole set.
|
|
20
20
|
*
|
|
21
21
|
* `Select` owns the value, passes it down to the listbox, and takes changes
|
|
22
|
-
* back, so the value lives in one place.
|
|
22
|
+
* back, so the value lives in one place. With `multiple`, the value is the
|
|
23
|
+
* chosen values joined with commas, and the form receives one entry for each.
|
|
24
|
+
* An option's value may hold a comma, and it is read as one value.
|
|
23
25
|
*
|
|
24
26
|
* Set `valueContent` to draw the trigger's content from the chosen options,
|
|
25
27
|
* and several chosen options are written as a list in the reader's language.
|
|
@@ -291,6 +293,11 @@ export declare class HelixHeadlessSelect extends HelixHeadlessSelect_base {
|
|
|
291
293
|
private get display();
|
|
292
294
|
/** The chosen options, in the order they appear. None on a server or while hydrating, and none for `items`. */
|
|
293
295
|
private get chosenOptions();
|
|
296
|
+
/**
|
|
297
|
+
* The chosen values, read against the options' values, so an option whose
|
|
298
|
+
* value holds a comma is one value. See `chosenValues`.
|
|
299
|
+
*/
|
|
300
|
+
private get chosen();
|
|
294
301
|
/** `items` as the list reads them, built once per array. */
|
|
295
302
|
private data;
|
|
296
303
|
/**
|
|
@@ -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, afterLinkFollowed, customElement, dataOptions, drawAutofill, drawStatus, HelixFormElement, hostAria, hostWords, isRtl, MixinControllerOpen, MixinControllerValue, MixinField, onApple, PopoverController, reportValues, StatusController, sideOf, typesCharacter, words, wordsLocale, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { AriaForwardController, afterLinkFollowed, chosenValues, customElement, dataOptions, drawAutofill, drawStatus, HelixFormElement, hostAria, hostWords, isRtl, MixinControllerOpen, MixinControllerValue, MixinField, onApple, PopoverController, reportValues, StatusController, sideOf, typesCharacter, words, wordsLocale, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { caretDown } from '@fusion.dev/helix-icons';
|
|
9
9
|
import { nothing } from 'lit';
|
|
10
10
|
import { property } from 'lit/decorators.js';
|
|
@@ -173,7 +173,7 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
|
|
|
173
173
|
return this.partNamed('listbox');
|
|
174
174
|
}
|
|
175
175
|
get display() {
|
|
176
|
-
const chosen = new Set(this.
|
|
176
|
+
const chosen = new Set(this.chosen);
|
|
177
177
|
const words = this.entries.filter((option) => chosen.has(option.value)).map((one) => one.text);
|
|
178
178
|
if (words.length > 0) {
|
|
179
179
|
return new Intl.ListFormat(wordsLocale(this), { style: 'short', type: 'unit' }).format(words);
|
|
@@ -184,9 +184,12 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
|
|
|
184
184
|
if (this.items) {
|
|
185
185
|
return [];
|
|
186
186
|
}
|
|
187
|
-
const chosen = new Set(this.
|
|
187
|
+
const chosen = new Set(this.chosen);
|
|
188
188
|
return optionsAmong(this.knownChildren).filter((option) => chosen.has(option.value));
|
|
189
189
|
}
|
|
190
|
+
get chosen() {
|
|
191
|
+
return chosenValues(this.value, new Set(this.entries.map((option) => option.value)), this.multiple);
|
|
192
|
+
}
|
|
190
193
|
get entries() {
|
|
191
194
|
if (!this.items) {
|
|
192
195
|
return optionsAmong(this.knownChildren);
|
|
@@ -257,7 +260,7 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
|
|
|
257
260
|
}
|
|
258
261
|
}
|
|
259
262
|
const present = new Set(options.map((option) => option.value));
|
|
260
|
-
const held = this.value
|
|
263
|
+
const held = chosenValues(this.value, new Set([...present, ...this.seen]), this.multiple);
|
|
261
264
|
const gone = held.filter((value) => this.seen.has(value) && !present.has(value));
|
|
262
265
|
this.seen = present;
|
|
263
266
|
if (gone.length > 0) {
|
|
@@ -266,7 +269,7 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
|
|
|
266
269
|
}
|
|
267
270
|
sync() {
|
|
268
271
|
if (this.multiple) {
|
|
269
|
-
reportValues(this.internals, this.name, this.
|
|
272
|
+
reportValues(this.internals, this.name, this.chosen);
|
|
270
273
|
}
|
|
271
274
|
else {
|
|
272
275
|
this.internals.setFormValue(this.value === '' ? null : this.value);
|
|
@@ -41,7 +41,8 @@ export declare class HelixHeadlessSkeleton extends HelixElement {
|
|
|
41
41
|
* What is loading, read out as the placeholder's name, such as "Loading
|
|
42
42
|
* profile".
|
|
43
43
|
*
|
|
44
|
-
* Absent hides the placeholder from screen readers.
|
|
44
|
+
* Absent hides the placeholder from screen readers. Around content, the name
|
|
45
|
+
* is read while `loading` is set, and the loaded content is read as it is.
|
|
45
46
|
*/
|
|
46
47
|
label: string;
|
|
47
48
|
/**
|
|
@@ -22,7 +22,7 @@ let HelixHeadlessSkeleton = class HelixHeadlessSkeleton extends HelixElement {
|
|
|
22
22
|
const wraps = this.wraps;
|
|
23
23
|
this.toggleState('wrapping', wraps);
|
|
24
24
|
this.internals.ariaBusy = this.loading ? 'true' : null;
|
|
25
|
-
if (this.label) {
|
|
25
|
+
if (this.label && (this.loading || !wraps)) {
|
|
26
26
|
hostAria(this.internals, { label: this.label, role: 'progressbar' });
|
|
27
27
|
this.internals.ariaHidden = null;
|
|
28
28
|
}
|
|
@@ -34,14 +34,16 @@ let HelixHeadlessSteps = class HelixHeadlessSteps extends MixinControllerActive(
|
|
|
34
34
|
wrap: false,
|
|
35
35
|
});
|
|
36
36
|
this.handleSelection = (event) => {
|
|
37
|
-
if (
|
|
37
|
+
if (!this.steps.includes(event.target)) {
|
|
38
38
|
return;
|
|
39
39
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
queueMicrotask(() => {
|
|
41
|
+
const index = this.steps.indexOf(event.target);
|
|
42
|
+
if (event.defaultPrevented || !this.reachable(index) || !this.valid(index)) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
this.requestActive(event.detail.value);
|
|
46
|
+
});
|
|
45
47
|
};
|
|
46
48
|
this.handleSlotChange = () => {
|
|
47
49
|
this.sync();
|
|
@@ -27,6 +27,10 @@ import '../../button/index.js';
|
|
|
27
27
|
* region. The current stage's button carries `aria-current="step"`, and only
|
|
28
28
|
* the button, so the state is said once, on the control.
|
|
29
29
|
*
|
|
30
|
+
* A stage's `content`, while it holds anything, is a group named by the
|
|
31
|
+
* stage's name, so a screen reader moving into it hears which stage it
|
|
32
|
+
* belongs to.
|
|
33
|
+
*
|
|
30
34
|
* @since 0.1.0
|
|
31
35
|
* @status stable
|
|
32
36
|
* @aliases Stage
|
|
@@ -105,6 +109,11 @@ export declare class HelixHeadlessStep extends HelixElement {
|
|
|
105
109
|
* The value the step sends in `helix-select`.
|
|
106
110
|
*/
|
|
107
111
|
value: string;
|
|
112
|
+
/** Whether anything is slotted into `content`, which makes it a named group. */
|
|
113
|
+
private holding;
|
|
114
|
+
/** Reads whether the `content` slot holds anything. */
|
|
115
|
+
private readonly readContent;
|
|
116
|
+
protected firstUpdated(changed: PropertyValues): void;
|
|
108
117
|
/** Says a stage was finished, from the first time one is. */
|
|
109
118
|
private readonly status;
|
|
110
119
|
/**
|
package/dist/steps/step/step.js
CHANGED
|
@@ -7,7 +7,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
import { customElement, drawStatus, formatWords, HelixElement, HelixSelectEvent, hostAria, StatusController, slottedWords, words, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { check, circle, record } from '@fusion.dev/helix-icons';
|
|
9
9
|
import { nothing } from 'lit';
|
|
10
|
-
import { property } from 'lit/decorators.js';
|
|
10
|
+
import { property, state } from 'lit/decorators.js';
|
|
11
11
|
import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
|
|
12
12
|
import { html } from 'lit/static-html.js';
|
|
13
13
|
import '../../button/index.js';
|
|
@@ -23,9 +23,18 @@ let HelixHeadlessStep = class HelixHeadlessStep extends HelixElement {
|
|
|
23
23
|
this.locked = false;
|
|
24
24
|
this.optional = false;
|
|
25
25
|
this.value = '';
|
|
26
|
+
this.holding = false;
|
|
27
|
+
this.readContent = () => {
|
|
28
|
+
const slot = this.renderRoot.querySelector('slot[name="content"]');
|
|
29
|
+
this.holding = (slot?.assignedNodes() ?? []).some((node) => node.nodeType === Node.ELEMENT_NODE || (node.textContent ?? '').trim() !== '');
|
|
30
|
+
};
|
|
26
31
|
this.status = new StatusController(this);
|
|
27
32
|
}
|
|
28
33
|
static { this.words = 'helix-step'; }
|
|
34
|
+
firstUpdated(changed) {
|
|
35
|
+
super.firstUpdated(changed);
|
|
36
|
+
this.readContent();
|
|
37
|
+
}
|
|
29
38
|
focus(options) {
|
|
30
39
|
this.partNamed('label')?.focus(options);
|
|
31
40
|
}
|
|
@@ -60,6 +69,7 @@ let HelixHeadlessStep = class HelixHeadlessStep extends HelixElement {
|
|
|
60
69
|
}
|
|
61
70
|
renderLabel() {
|
|
62
71
|
return html `<${this.tag('button')}
|
|
72
|
+
id="label"
|
|
63
73
|
part="label"
|
|
64
74
|
exportparts="button: label-button, live: label-live, spinner: label-spinner, text: label-text"
|
|
65
75
|
emphasis="ghost"
|
|
@@ -79,7 +89,11 @@ let HelixHeadlessStep = class HelixHeadlessStep extends HelixElement {
|
|
|
79
89
|
${this.renderLabel()}
|
|
80
90
|
<span part="description"><slot name="description"></slot></span>
|
|
81
91
|
</span>
|
|
82
|
-
<div
|
|
92
|
+
<div
|
|
93
|
+
part="content"
|
|
94
|
+
role=${this.holding ? 'group' : nothing}
|
|
95
|
+
aria-labelledby=${this.holding ? 'label' : nothing}
|
|
96
|
+
><slot name="content" @slotchange=${this.readContent}></slot></div>${drawStatus(this.status)}`;
|
|
83
97
|
}
|
|
84
98
|
};
|
|
85
99
|
__decorate([
|
|
@@ -111,6 +125,9 @@ __decorate([
|
|
|
111
125
|
__decorate([
|
|
112
126
|
property()
|
|
113
127
|
], HelixHeadlessStep.prototype, "value", void 0);
|
|
128
|
+
__decorate([
|
|
129
|
+
state()
|
|
130
|
+
], HelixHeadlessStep.prototype, "holding", void 0);
|
|
114
131
|
HelixHeadlessStep = __decorate([
|
|
115
132
|
customElement('helix-headless-step')
|
|
116
133
|
], HelixHeadlessStep);
|
|
@@ -15,15 +15,24 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
15
15
|
* the tab list lands on the panel's first link or control where there is one,
|
|
16
16
|
* and on the panel itself where there is none.
|
|
17
17
|
*
|
|
18
|
+
* **Set `lazy` for a panel that is expensive to draw.** Its content waits in a
|
|
19
|
+
* `<template>` inside it, and goes into the panel the first time the panel is
|
|
20
|
+
* shown, so it costs nothing until a reader opens that tab. Once in, it stays.
|
|
21
|
+
* The panel sends `helix-load` at the same moment, for content your code
|
|
22
|
+
* renders itself.
|
|
23
|
+
*
|
|
18
24
|
* @since 0.1.0
|
|
19
25
|
* @status stable
|
|
20
26
|
* @aliases TabPanel
|
|
21
27
|
* @documentation https://fusion.dev/helix/components/tabs
|
|
22
28
|
*
|
|
23
|
-
* @slot - The panel's content.
|
|
29
|
+
* @slot - The panel's content. Under `lazy`, a `<template>` holding it.
|
|
30
|
+
*
|
|
31
|
+
* @event {HelixLoadEvent} helix-load - Fired the first time a `lazy` panel is shown, once its template's content is in.
|
|
24
32
|
*
|
|
25
33
|
* @csspart panel - The panel container, which takes the focus while it holds nothing focusable.
|
|
26
34
|
*
|
|
35
|
+
* @cssstate loaded - Once a `lazy` panel has been shown and its content is in.
|
|
27
36
|
* @cssstate selected - While its tab is the one selected.
|
|
28
37
|
*/
|
|
29
38
|
export declare class HelixHeadlessTabPanel extends HelixElement {
|
|
@@ -40,6 +49,18 @@ export declare class HelixHeadlessTabPanel extends HelixElement {
|
|
|
40
49
|
* the pattern names one anyway.
|
|
41
50
|
*/
|
|
42
51
|
label: string;
|
|
52
|
+
/**
|
|
53
|
+
* Whether the panel's content waits in a `<template>` until the panel is
|
|
54
|
+
* first shown.
|
|
55
|
+
*/
|
|
56
|
+
lazy: boolean;
|
|
57
|
+
/** Whether a `lazy` panel has put its content in. */
|
|
58
|
+
private loaded;
|
|
59
|
+
/**
|
|
60
|
+
* Puts a `lazy` panel's content in the first time the panel is shown: the
|
|
61
|
+
* content of each `<template>` it holds, in place of the template.
|
|
62
|
+
*/
|
|
63
|
+
private load;
|
|
43
64
|
/**
|
|
44
65
|
* Whether this panel is the visible one.
|
|
45
66
|
*/
|
|
@@ -4,13 +4,15 @@ 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, focusableElements, HelixElement, hostAria } from '@fusion.dev/helix-core';
|
|
7
|
+
import { customElement, focusableElements, HelixElement, HelixLoadEvent, hostAria, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html, nothing } from 'lit';
|
|
9
9
|
import { property, state } from 'lit/decorators.js';
|
|
10
10
|
let HelixHeadlessTabPanel = class HelixHeadlessTabPanel extends HelixElement {
|
|
11
11
|
constructor() {
|
|
12
12
|
super(...arguments);
|
|
13
13
|
this.label = '';
|
|
14
|
+
this.lazy = false;
|
|
15
|
+
this.loaded = false;
|
|
14
16
|
this.selected = false;
|
|
15
17
|
this.value = '';
|
|
16
18
|
this.holdsStop = false;
|
|
@@ -21,6 +23,19 @@ let HelixHeadlessTabPanel = class HelixHeadlessTabPanel extends HelixElement {
|
|
|
21
23
|
this.holdsStop = this.selected && focusableElements(this).length > 0;
|
|
22
24
|
};
|
|
23
25
|
}
|
|
26
|
+
load() {
|
|
27
|
+
if (!this.lazy || !this.selected || this.loaded) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
this.loaded = true;
|
|
31
|
+
for (const template of [...this.children]) {
|
|
32
|
+
if (template instanceof HTMLTemplateElement) {
|
|
33
|
+
template.replaceWith(document.importNode(template.content, true));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
this.toggleState('loaded', true);
|
|
37
|
+
this.dispatchEvent(new HelixLoadEvent());
|
|
38
|
+
}
|
|
24
39
|
connectedCallback() {
|
|
25
40
|
super.connectedCallback();
|
|
26
41
|
this.watcher?.observe(this, {
|
|
@@ -36,6 +51,9 @@ let HelixHeadlessTabPanel = class HelixHeadlessTabPanel extends HelixElement {
|
|
|
36
51
|
}
|
|
37
52
|
updated(changed) {
|
|
38
53
|
super.updated(changed);
|
|
54
|
+
if (changed.has('selected') || changed.has('lazy')) {
|
|
55
|
+
this.load();
|
|
56
|
+
}
|
|
39
57
|
if (changed.has('selected')) {
|
|
40
58
|
this.readStops();
|
|
41
59
|
}
|
|
@@ -58,6 +76,9 @@ let HelixHeadlessTabPanel = class HelixHeadlessTabPanel extends HelixElement {
|
|
|
58
76
|
__decorate([
|
|
59
77
|
property({ attribute: false })
|
|
60
78
|
], HelixHeadlessTabPanel.prototype, "label", void 0);
|
|
79
|
+
__decorate([
|
|
80
|
+
property({ type: Boolean })
|
|
81
|
+
], HelixHeadlessTabPanel.prototype, "lazy", void 0);
|
|
61
82
|
__decorate([
|
|
62
83
|
property({ type: Boolean })
|
|
63
84
|
], HelixHeadlessTabPanel.prototype, "selected", void 0);
|
|
@@ -46,9 +46,9 @@ declare const HelixHeadlessTabs_base: (new (...args: any[]) => import("@fusion.d
|
|
|
46
46
|
* | Enter, Space | Selects the tab with focus, or follows its link. |
|
|
47
47
|
* | Tab | Leaves the list for the selected panel. |
|
|
48
48
|
*
|
|
49
|
-
* Removing the selected tab selects the
|
|
50
|
-
* `active` says so, with `helix-activate`. Where
|
|
51
|
-
* focus moves to the tab now selected.
|
|
49
|
+
* Removing the selected tab selects the enabled tab beside it, the next one
|
|
50
|
+
* or else the one before, and `active` says so, with `helix-activate`. Where
|
|
51
|
+
* the removed tab had focus, focus moves to the tab now selected.
|
|
52
52
|
*
|
|
53
53
|
* **More tabs than fit.** With `scroll-controls`, a strip that scrolls shows a
|
|
54
54
|
* button at each end it can scroll toward, as Web Awesome's tab group does,
|
|
@@ -74,6 +74,8 @@ declare const HelixHeadlessTabs_base: (new (...args: any[]) => import("@fusion.d
|
|
|
74
74
|
* @slot - The tabs, as direct children of this element.
|
|
75
75
|
* @slot indicator - A mark of your own, drawn inside the `indicator` part, where `--indicator-x`, `--indicator-y`, `--indicator-inline-size` and `--indicator-block-size` reach it.
|
|
76
76
|
* @slot panels - The panels, as direct children of this element. Panels inside a wrapper are not paired with tabs, because pairing reads only the direct children.
|
|
77
|
+
* @slot scroll-end-icon - Replaces the glyph on the button that scrolls the strip toward its end.
|
|
78
|
+
* @slot scroll-start-icon - Replaces the glyph on the button that scrolls the strip toward its start.
|
|
77
79
|
*
|
|
78
80
|
* @event {HelixActivateEvent} helix-activate - Fired after the active tab changes, so the element already holds the new value.
|
|
79
81
|
* @event {HelixBeforeActivateEvent} helix-before-activate - Fired before the active tab changes, and cancelable. Canceling it is the only way to block a change.
|
|
@@ -85,7 +87,9 @@ declare const HelixHeadlessTabs_base: (new (...args: any[]) => import("@fusion.d
|
|
|
85
87
|
* and animate yourself.
|
|
86
88
|
* @csspart panels - The panel container.
|
|
87
89
|
* @csspart scroll-end - The button that scrolls the strip toward its end, shown under `scroll-controls` while there is more that way.
|
|
90
|
+
* @csspart scroll-end-mark - The glyph on that button, where none is slotted.
|
|
88
91
|
* @csspart scroll-start - The button that scrolls the strip toward its start, shown under `scroll-controls` while there is more that way.
|
|
92
|
+
* @csspart scroll-start-mark - The glyph on that button, where none is slotted.
|
|
89
93
|
* @csspart tabs - The tab list.
|
|
90
94
|
*/
|
|
91
95
|
export declare class HelixHeadlessTabs extends HelixHeadlessTabs_base {
|
|
@@ -169,6 +173,12 @@ export declare class HelixHeadlessTabs extends HelixHeadlessTabs_base {
|
|
|
169
173
|
private scrollStrip;
|
|
170
174
|
/** The values of the tabs the last pairing read, to tell a removed tab from one not yet here. */
|
|
171
175
|
private held;
|
|
176
|
+
/**
|
|
177
|
+
* The enabled tab nearest to where a removed one stood: the next ones
|
|
178
|
+
* nearest first, then the ones before it nearest first. The same order a
|
|
179
|
+
* dismissed tab hands focus in.
|
|
180
|
+
*/
|
|
181
|
+
private beside;
|
|
172
182
|
/** Pushes the current value down to whichever tab and panel own it. */
|
|
173
183
|
private sync;
|
|
174
184
|
/** The tab that last had focus, so its removal can hand focus on. */
|
|
@@ -205,10 +215,16 @@ export declare class HelixHeadlessTabs extends HelixHeadlessTabs_base {
|
|
|
205
215
|
private readonly handleSlotChange;
|
|
206
216
|
protected updated(changed: PropertyValues): void;
|
|
207
217
|
/**
|
|
208
|
-
* One of the two scroll buttons, pointing the way it scrolls.
|
|
209
|
-
* the line is mirrored on a right-to-left page, and one down a column is not.
|
|
218
|
+
* One of the two scroll buttons, with the glyph pointing the way it scrolls.
|
|
210
219
|
*/
|
|
211
220
|
private renderScroll;
|
|
221
|
+
/**
|
|
222
|
+
* The glyph on a scroll button, behind a seam: slot fallback, so a consumer
|
|
223
|
+
* replaces it by slotting, and a method, so a tier above replaces it by
|
|
224
|
+
* overriding. A caret along the line is mirrored on a right-to-left page, and
|
|
225
|
+
* one down a column is not.
|
|
226
|
+
*/
|
|
227
|
+
protected renderScrollIcon(toward: 1 | -1): TemplateResult;
|
|
212
228
|
protected render(): TemplateResult;
|
|
213
229
|
}
|
|
214
230
|
declare global {
|