@fusion.dev/helix-headless 0.153.0 → 0.155.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 +1885 -140
- package/dist/accordion/item/accordion-item.d.ts +15 -0
- package/dist/accordion/item/accordion-item.js +12 -1
- package/dist/accordion/root/accordion-root.d.ts +2 -1
- package/dist/accordion/root/accordion-root.js +1 -0
- package/dist/badge/root/badge-root.d.ts +15 -2
- package/dist/badge/root/badge-root.js +20 -3
- package/dist/breadcrumbs/breadcrumb/breadcrumb.d.ts +36 -3
- package/dist/breadcrumbs/breadcrumb/breadcrumb.js +42 -2
- package/dist/breadcrumbs/root/breadcrumbs-root.d.ts +15 -5
- package/dist/breadcrumbs/root/breadcrumbs-root.js +29 -4
- package/dist/button-group/root/button-group-root.d.ts +2 -0
- package/dist/button-group/root/button-group-root.js +1 -0
- package/dist/card/root/card-root.d.ts +31 -11
- package/dist/card/root/card-root.js +25 -11
- package/dist/code/root/code-root.d.ts +7 -0
- package/dist/code/root/code-root.js +1 -1
- package/dist/code-block/root/code-block-root.d.ts +37 -1
- package/dist/code-block/root/code-block-root.js +38 -5
- package/dist/color-input/root/color-input-root.d.ts +9 -3
- package/dist/color-input/root/color-input-root.js +3 -1
- package/dist/color-picker/channels.d.ts +5 -3
- package/dist/color-picker/channels.js +79 -21
- package/dist/color-picker/root/color-picker-root.d.ts +58 -5
- package/dist/color-picker/root/color-picker-root.js +75 -13
- package/dist/date-input/root/date-input-root.d.ts +1 -0
- package/dist/date-input/root/date-input-root.js +2 -1
- package/dist/date-picker/root/date-picker-root.d.ts +43 -13
- package/dist/date-picker/root/date-picker-root.js +54 -31
- package/dist/element-events.generated.js +2 -1
- package/dist/empty/root/empty-root.d.ts +19 -1
- package/dist/empty/root/empty-root.js +13 -4
- package/dist/icon/fetch.d.ts +20 -0
- package/dist/icon/fetch.js +70 -0
- package/dist/icon/root/icon-root.d.ts +27 -5
- package/dist/icon/root/icon-root.js +41 -17
- package/dist/navigation/root/navigation-root.d.ts +26 -0
- package/dist/navigation/root/navigation-root.js +58 -1
- package/dist/quote/root/quote-root.d.ts +17 -1
- package/dist/quote/root/quote-root.js +15 -2
- package/dist/select/root/select-root.d.ts +29 -3
- package/dist/select/root/select-root.js +34 -9
- package/dist/skeleton/root/skeleton-root.d.ts +7 -0
- package/dist/skeleton/root/skeleton-root.js +4 -1
- package/dist/slider/root/slider-root.d.ts +71 -9
- package/dist/slider/root/slider-root.js +129 -42
- package/dist/speed-dial/item/speed-dial-item.d.ts +29 -1
- package/dist/speed-dial/item/speed-dial-item.js +48 -7
- package/dist/speed-dial/root/speed-dial-root.d.ts +32 -6
- package/dist/speed-dial/root/speed-dial-root.js +50 -5
- package/dist/splitter/root/splitter-root.d.ts +22 -0
- package/dist/splitter/root/splitter-root.js +36 -7
- package/dist/stat/group/stat-group.d.ts +23 -4
- package/dist/stat/group/stat-group.js +13 -1
- package/dist/stat/root/stat-root.d.ts +46 -1
- package/dist/stat/root/stat-root.js +29 -5
- package/dist/steps/reach.d.ts +5 -1
- package/dist/steps/reach.js +2 -2
- package/dist/steps/root/steps-root.d.ts +37 -2
- package/dist/steps/root/steps-root.js +49 -7
- package/dist/steps/step/step.d.ts +6 -0
- package/dist/steps/step/step.js +4 -0
- package/dist/tabs/root/tabs-root.d.ts +37 -0
- package/dist/tabs/root/tabs-root.js +92 -2
- package/dist/tabs/tab/tab.d.ts +38 -2
- package/dist/tabs/tab/tab.js +45 -4
- package/dist/tag/group/tag-group.d.ts +29 -3
- package/dist/tag/group/tag-group.js +42 -11
- package/dist/toggle/root/toggle-root.d.ts +2 -0
- package/dist/toggle/root/toggle-root.js +1 -1
- package/dist/tree/item/tree-item.d.ts +10 -0
- package/dist/tree/item/tree-item.js +12 -0
- package/dist/tree/root/tree-root.d.ts +40 -1
- package/dist/tree/root/tree-root.js +120 -4
- package/dist/tree/selection.d.ts +6 -0
- package/dist/tree/selection.js +11 -0
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +1412 -207
- package/types/vue.d.ts +294 -65
- package/types/words.d.ts +49 -1
|
@@ -35,6 +35,13 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
35
35
|
* takes all of the change, as Web Awesome's split panel does. Without it,
|
|
36
36
|
* both panes keep their share.
|
|
37
37
|
*
|
|
38
|
+
* **Three or more panes along one axis are splitters nested on that axis**, a
|
|
39
|
+
* splitter in the `end` pane of another for three columns. The nested one
|
|
40
|
+
* keeps the pane on its far side the size it was while the outer divider
|
|
41
|
+
* moves, so a divider resizes only the two panes beside it, as Zag's splitter
|
|
42
|
+
* does with any number of panels. Where the whole row is resized, every pane
|
|
43
|
+
* keeps its share, unless `primary` says otherwise.
|
|
44
|
+
*
|
|
38
45
|
* **Keyboard.** The divider is one tab stop.
|
|
39
46
|
*
|
|
40
47
|
* | Key | What it does |
|
|
@@ -213,6 +220,21 @@ export declare class HelixHeadlessSplitter extends HelixElement {
|
|
|
213
220
|
primary?: 'end' | 'start';
|
|
214
221
|
/** Watches this element's length along its axis while `primary` is set. */
|
|
215
222
|
private lengths;
|
|
223
|
+
/**
|
|
224
|
+
* The splitter this one is nested in on the same axis, and the pane of it
|
|
225
|
+
* this one is in, for a row of three or more panes.
|
|
226
|
+
*/
|
|
227
|
+
private chain;
|
|
228
|
+
/** The length of the splitter this one is chained to, last seen. */
|
|
229
|
+
private chainLength;
|
|
230
|
+
/**
|
|
231
|
+
* The pane that keeps its pixels as this splitter's length changes:
|
|
232
|
+
* `primary`, or the far pane of one chained into another, whose divider
|
|
233
|
+
* would otherwise drag this one's along with it.
|
|
234
|
+
*/
|
|
235
|
+
private get held();
|
|
236
|
+
/** The chained splitter's length along the shared axis, in pixels. */
|
|
237
|
+
private measureChain;
|
|
216
238
|
/** The length along the axis last seen, in pixels. */
|
|
217
239
|
private length;
|
|
218
240
|
/**
|
|
@@ -70,8 +70,15 @@ let HelixHeadlessSplitter = class HelixHeadlessSplitter extends HelixElement {
|
|
|
70
70
|
this.reached = undefined;
|
|
71
71
|
this.requestUpdate('position', previous);
|
|
72
72
|
}
|
|
73
|
+
get held() {
|
|
74
|
+
return this.primary ?? this.chain?.side;
|
|
75
|
+
}
|
|
76
|
+
measureChain() {
|
|
77
|
+
const box = this.chain?.by.getBoundingClientRect();
|
|
78
|
+
return box ? (this.vertical ? box.height : box.width) : 0;
|
|
79
|
+
}
|
|
73
80
|
watchLength() {
|
|
74
|
-
if (!this.
|
|
81
|
+
if (!this.held || typeof ResizeObserver === 'undefined') {
|
|
75
82
|
this.lengths?.disconnect();
|
|
76
83
|
this.lengths = undefined;
|
|
77
84
|
this.length = 0;
|
|
@@ -92,17 +99,26 @@ let HelixHeadlessSplitter = class HelixHeadlessSplitter extends HelixElement {
|
|
|
92
99
|
holdPrimary(length) {
|
|
93
100
|
const before = this.length;
|
|
94
101
|
this.length = length;
|
|
95
|
-
|
|
102
|
+
const held = this.held;
|
|
103
|
+
if (!this.primary && this.chain) {
|
|
104
|
+
const outer = this.measureChain();
|
|
105
|
+
const outerBefore = this.chainLength;
|
|
106
|
+
this.chainLength = outer;
|
|
107
|
+
if (outer !== outerBefore) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (!held || before <= 0 || length <= 0 || length === before) {
|
|
96
112
|
return;
|
|
97
113
|
}
|
|
98
114
|
if (this.collapsed || this.dragging) {
|
|
99
115
|
return;
|
|
100
116
|
}
|
|
101
117
|
const position = this.position;
|
|
102
|
-
const kept = ((
|
|
118
|
+
const kept = ((held === 'start' ? position : 100 - position) / 100) * before;
|
|
103
119
|
const share = (kept / length) * 100;
|
|
104
120
|
const previous = position;
|
|
105
|
-
this.positionState = this.clamp(
|
|
121
|
+
this.positionState = this.clamp(held === 'start' ? share : 100 - share);
|
|
106
122
|
this.requestUpdate('position', previous);
|
|
107
123
|
}
|
|
108
124
|
get showing() {
|
|
@@ -261,6 +277,8 @@ let HelixHeadlessSplitter = class HelixHeadlessSplitter extends HelixElement {
|
|
|
261
277
|
});
|
|
262
278
|
this.max = 100;
|
|
263
279
|
this.min = 0;
|
|
280
|
+
this.chain = null;
|
|
281
|
+
this.chainLength = 0;
|
|
264
282
|
this.length = 0;
|
|
265
283
|
this.handlePaneKeys = (event) => {
|
|
266
284
|
if (event.key !== 'F6' ||
|
|
@@ -484,7 +502,17 @@ let HelixHeadlessSplitter = class HelixHeadlessSplitter extends HelixElement {
|
|
|
484
502
|
}
|
|
485
503
|
findOuter() {
|
|
486
504
|
const outer = closestInstance(this.parentElement, HelixHeadlessSplitter_1);
|
|
487
|
-
|
|
505
|
+
this.chain = null;
|
|
506
|
+
if (outer && outer !== this && outer.vertical === this.vertical) {
|
|
507
|
+
let node = this;
|
|
508
|
+
while (node.parentElement && node.parentElement !== outer) {
|
|
509
|
+
node = node.parentElement;
|
|
510
|
+
}
|
|
511
|
+
this.chain = { by: outer, side: node.slot === 'start' ? 'start' : 'end' };
|
|
512
|
+
this.chainLength = this.measureChain();
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
if (!outer || outer === this) {
|
|
488
516
|
return;
|
|
489
517
|
}
|
|
490
518
|
this.outer = outer;
|
|
@@ -499,8 +527,8 @@ let HelixHeadlessSplitter = class HelixHeadlessSplitter extends HelixElement {
|
|
|
499
527
|
super.connectedCallback();
|
|
500
528
|
if (this.hasUpdated) {
|
|
501
529
|
this.watchWidth();
|
|
502
|
-
this.watchLength();
|
|
503
530
|
this.findOuter();
|
|
531
|
+
this.watchLength();
|
|
504
532
|
}
|
|
505
533
|
this.addEventListener('keydown', this.handlePaneKeys);
|
|
506
534
|
}
|
|
@@ -512,6 +540,7 @@ let HelixHeadlessSplitter = class HelixHeadlessSplitter extends HelixElement {
|
|
|
512
540
|
this.lengths?.disconnect();
|
|
513
541
|
this.lengths = undefined;
|
|
514
542
|
this.length = 0;
|
|
543
|
+
this.chain = null;
|
|
515
544
|
if (this.outer) {
|
|
516
545
|
channel.get(this.outer)?.uncross(this);
|
|
517
546
|
}
|
|
@@ -521,8 +550,8 @@ let HelixHeadlessSplitter = class HelixHeadlessSplitter extends HelixElement {
|
|
|
521
550
|
firstUpdated(changed) {
|
|
522
551
|
super.firstUpdated(changed);
|
|
523
552
|
this.watchWidth();
|
|
524
|
-
this.watchLength();
|
|
525
553
|
this.findOuter();
|
|
554
|
+
this.watchLength();
|
|
526
555
|
this.finishHydration();
|
|
527
556
|
}
|
|
528
557
|
updated(changed) {
|
|
@@ -4,9 +4,12 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
4
4
|
* @summary A row of related measurements.
|
|
5
5
|
*
|
|
6
6
|
* @description
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* **A named group is a group**, so a screen reader announces the stats
|
|
8
|
+
* together under its name, such as "Usage, group". Name it with `label`,
|
|
9
|
+
* `aria-label` or `aria-labelledby`. An unnamed one has no role, since a group
|
|
10
|
+
* with no name only adds "group" to what a reader says, as an unnamed Card
|
|
11
|
+
* has none. The figures are read together and their order has no meaning, so
|
|
12
|
+
* the group does not announce positions such as "1 of 4".
|
|
10
13
|
*
|
|
11
14
|
* @since 0.1.0
|
|
12
15
|
* @status stable
|
|
@@ -17,9 +20,25 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
17
20
|
*/
|
|
18
21
|
export declare class HelixHeadlessStatGroup extends HelixElement {
|
|
19
22
|
/**
|
|
20
|
-
* Accessible name for the group.
|
|
23
|
+
* Accessible name for the group, which makes it a group.
|
|
24
|
+
*
|
|
25
|
+
* Absent leaves the stats with no group around them, unless `aria-label` or
|
|
26
|
+
* `aria-labelledby` names it.
|
|
21
27
|
*/
|
|
22
28
|
label: string;
|
|
29
|
+
/**
|
|
30
|
+
* Hears a name written on the element, which no property mirrors.
|
|
31
|
+
*
|
|
32
|
+
* Lit observes the attributes of its own properties only, and `ariaLabel`
|
|
33
|
+
* and `ariaLabelledByElements` belong to the platform.
|
|
34
|
+
*/
|
|
35
|
+
static get observedAttributes(): string[];
|
|
36
|
+
/**
|
|
37
|
+
* Called by the browser when an observed attribute changes. A name written
|
|
38
|
+
* or taken off asks for an update, because it decides whether the stats are
|
|
39
|
+
* a group.
|
|
40
|
+
*/
|
|
41
|
+
attributeChangedCallback(name: string, old: string | null, value: string | null): void;
|
|
23
42
|
protected willUpdate(changed: PropertyValues): void;
|
|
24
43
|
protected render(): TemplateResult;
|
|
25
44
|
}
|
|
@@ -12,9 +12,21 @@ let HelixHeadlessStatGroup = class HelixHeadlessStatGroup extends HelixElement {
|
|
|
12
12
|
super(...arguments);
|
|
13
13
|
this.label = '';
|
|
14
14
|
}
|
|
15
|
+
static get observedAttributes() {
|
|
16
|
+
return [...super.observedAttributes, 'aria-label', 'aria-labelledby'];
|
|
17
|
+
}
|
|
18
|
+
attributeChangedCallback(name, old, value) {
|
|
19
|
+
super.attributeChangedCallback(name, old, value);
|
|
20
|
+
if (name === 'aria-label' || name === 'aria-labelledby') {
|
|
21
|
+
this.requestUpdate();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
15
24
|
willUpdate(changed) {
|
|
16
25
|
super.willUpdate(changed);
|
|
17
|
-
|
|
26
|
+
const named = this.label.trim() !== '' ||
|
|
27
|
+
Boolean(this.getAttribute?.('aria-label')?.trim()) ||
|
|
28
|
+
Boolean(this.getAttribute?.('aria-labelledby')?.trim());
|
|
29
|
+
hostAria(this.internals, { label: this.label, role: named ? 'group' : '' });
|
|
18
30
|
}
|
|
19
31
|
render() {
|
|
20
32
|
return html `<slot></slot>`;
|
|
@@ -19,6 +19,19 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
19
19
|
* attribute; a stat with no label has no role. Put a `Trend` in the `trend`
|
|
20
20
|
* slot to have it read inside the group, after the figure it qualifies.
|
|
21
21
|
*
|
|
22
|
+
* **Or give the figure as a number.** `value` is written with
|
|
23
|
+
* `Intl.NumberFormat`, in `locale` and with `format-options`, so
|
|
24
|
+
* `value="1234567" format-options='{"notation":"compact"}'` shows `1.2M`, and
|
|
25
|
+
* a server writes it as the browser does. A number in `value` takes the place
|
|
26
|
+
* of a slotted figure.
|
|
27
|
+
*
|
|
28
|
+
* **Each slot is a part of the same name**, and `value` for the figure, so a
|
|
29
|
+
* page reaches the label, the unit and the trend from outside. A slot draws no
|
|
30
|
+
* box, so a part passes inherited properties to what is slotted. **The stat
|
|
31
|
+
* says which pieces it holds**, as `:state(has-label)`, `:state(has-unit)` and
|
|
32
|
+
* `:state(has-trend)`, read in the browser, so a served stat carries none
|
|
33
|
+
* until the script runs.
|
|
34
|
+
*
|
|
22
35
|
* @since 0.1.0
|
|
23
36
|
* @status stable
|
|
24
37
|
* @category data
|
|
@@ -30,7 +43,14 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
30
43
|
* @slot trend - How the value moved, usually a `Trend`, read after the value inside the stat's group.
|
|
31
44
|
* @slot unit - The unit, shown beside the value.
|
|
32
45
|
*
|
|
33
|
-
* @csspart
|
|
46
|
+
* @csspart label - The slot the label arrives through, drawing no box, as `value` does.
|
|
47
|
+
* @csspart trend - The slot the trend arrives through, drawing no box, as `value` does.
|
|
48
|
+
* @csspart unit - The slot the unit arrives through, drawing no box, as `value` does.
|
|
49
|
+
* @csspart value - The slot the value is written in, or the figure `value` writes. A slot draws no box, so it takes the inherited properties, such as color and font, and passes them to the value.
|
|
50
|
+
*
|
|
51
|
+
* @cssstate has-label - Something is slotted into `label`.
|
|
52
|
+
* @cssstate has-trend - Something is slotted into `trend`.
|
|
53
|
+
* @cssstate has-unit - Something is slotted into `unit`.
|
|
34
54
|
*/
|
|
35
55
|
export declare class HelixHeadlessStat extends HelixElement {
|
|
36
56
|
/**
|
|
@@ -61,9 +81,34 @@ export declare class HelixHeadlessStat extends HelixElement {
|
|
|
61
81
|
* carries nothing nobody wrote.
|
|
62
82
|
*/
|
|
63
83
|
direction?: 'column-reverse' | 'row' | 'row-reverse';
|
|
84
|
+
/**
|
|
85
|
+
* `Intl.NumberFormat` options `value` is written with, such as
|
|
86
|
+
* `{"notation":"compact"}` for `1.2M` or `{"style":"percent"}` for `98%`,
|
|
87
|
+
* written as JSON in the attribute.
|
|
88
|
+
*
|
|
89
|
+
* Absent writes the number as the locale writes any number.
|
|
90
|
+
*/
|
|
91
|
+
formatOptions?: Intl.NumberFormatOptions;
|
|
92
|
+
/**
|
|
93
|
+
* The locale `value` is written in, such as `de` for `1.234.567`.
|
|
94
|
+
*
|
|
95
|
+
* Absent reads the nearest `lang`, then the runtime's own. Set it on a page
|
|
96
|
+
* rendered on a server, so the server and the browser write the figure
|
|
97
|
+
* alike.
|
|
98
|
+
*/
|
|
99
|
+
locale: string;
|
|
100
|
+
/**
|
|
101
|
+
* The figure as a number, written with `format-options` in `locale` in
|
|
102
|
+
* place of a slotted value.
|
|
103
|
+
*
|
|
104
|
+
* Absent leaves the figure to the slot.
|
|
105
|
+
*/
|
|
106
|
+
value?: number;
|
|
64
107
|
protected willUpdate(changed: PropertyValues): void;
|
|
65
108
|
protected firstUpdated(changed: PropertyValues): void;
|
|
66
109
|
protected updated(changed: PropertyValues): void;
|
|
110
|
+
/** Says which pieces the stat holds, one call apiece so each pairs with its `@cssstate`. */
|
|
111
|
+
private readonly readPieces;
|
|
67
112
|
protected render(): TemplateResult;
|
|
68
113
|
}
|
|
69
114
|
declare global {
|
|
@@ -4,10 +4,20 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { customElement, HelixElement, hostAria, reportRuntime, slottedWords, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { customElement, formatNumber, HelixElement, hostAria, reportRuntime, slotFilled, slottedWords, wordsLocale, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
let HelixHeadlessStat = class HelixHeadlessStat extends HelixElement {
|
|
11
|
+
constructor() {
|
|
12
|
+
super(...arguments);
|
|
13
|
+
this.locale = '';
|
|
14
|
+
this.readPieces = () => {
|
|
15
|
+
const filled = (name) => slotFilled(this.renderRoot?.querySelector(`slot[name="${name}"]`));
|
|
16
|
+
this.toggleState('has-label', filled('label'));
|
|
17
|
+
this.toggleState('has-unit', filled('unit'));
|
|
18
|
+
this.toggleState('has-trend', filled('trend'));
|
|
19
|
+
};
|
|
20
|
+
}
|
|
11
21
|
willUpdate(changed) {
|
|
12
22
|
super.willUpdate(changed);
|
|
13
23
|
const labels = this.knownChildren.filter((child) => child.slot === 'label');
|
|
@@ -25,6 +35,7 @@ let HelixHeadlessStat = class HelixHeadlessStat extends HelixElement {
|
|
|
25
35
|
}
|
|
26
36
|
updated(changed) {
|
|
27
37
|
super.updated(changed);
|
|
38
|
+
this.readPieces();
|
|
28
39
|
const given = this.direction;
|
|
29
40
|
if (changed.has('direction') && (given === 'up' || given === 'down' || given === 'neutral')) {
|
|
30
41
|
reportRuntime({
|
|
@@ -37,15 +48,28 @@ let HelixHeadlessStat = class HelixHeadlessStat extends HelixElement {
|
|
|
37
48
|
}
|
|
38
49
|
}
|
|
39
50
|
render() {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
51
|
+
const value = this.value;
|
|
52
|
+
const figure = value === undefined || !Number.isFinite(value)
|
|
53
|
+
? ''
|
|
54
|
+
: formatNumber(value, wordsLocale(this), this.formatOptions);
|
|
55
|
+
return html `<slot name="label" part="label" @slotchange=${() => this.requestUpdate()}></slot>
|
|
56
|
+
${figure === '' ? html `<slot part="value"></slot>` : html `<span part="value">${figure}</span>`}
|
|
57
|
+
<slot name="unit" part="unit" @slotchange=${this.readPieces}></slot>
|
|
58
|
+
<slot name="trend" part="trend" @slotchange=${this.readPieces}></slot>`;
|
|
44
59
|
}
|
|
45
60
|
};
|
|
46
61
|
__decorate([
|
|
47
62
|
property({ reflect: true })
|
|
48
63
|
], HelixHeadlessStat.prototype, "direction", void 0);
|
|
64
|
+
__decorate([
|
|
65
|
+
property({ attribute: 'format-options', type: Object })
|
|
66
|
+
], HelixHeadlessStat.prototype, "formatOptions", void 0);
|
|
67
|
+
__decorate([
|
|
68
|
+
property()
|
|
69
|
+
], HelixHeadlessStat.prototype, "locale", void 0);
|
|
70
|
+
__decorate([
|
|
71
|
+
property({ type: Number })
|
|
72
|
+
], HelixHeadlessStat.prototype, "value", void 0);
|
|
49
73
|
HelixHeadlessStat = __decorate([
|
|
50
74
|
customElement('helix-headless-stat')
|
|
51
75
|
], HelixHeadlessStat);
|
package/dist/steps/reach.d.ts
CHANGED
|
@@ -11,8 +11,12 @@ export interface ReachableStep {
|
|
|
11
11
|
* Back is always allowed, as Ark UI's linear steps allow it. Forward is allowed
|
|
12
12
|
* only where every stage from the one the reader is on up to the target is
|
|
13
13
|
* complete or optional, so no stage is passed unfinished.
|
|
14
|
+
*
|
|
15
|
+
* `passing` is a sequence whose stages are complete once a reader moves past
|
|
16
|
+
* them, so the stage the reader is on counts as finished and the next one is
|
|
17
|
+
* in reach.
|
|
14
18
|
*/
|
|
15
|
-
export declare const canReach: (steps: readonly ReachableStep[], from: number, to: number) => boolean;
|
|
19
|
+
export declare const canReach: (steps: readonly ReachableStep[], from: number, to: number, passing?: boolean) => boolean;
|
|
16
20
|
/**
|
|
17
21
|
* How far through a sequence is, from 0 to 100: the share of its stages that
|
|
18
22
|
* are complete, rounded to a whole number. An empty sequence is at 0.
|
package/dist/steps/reach.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export const canReach = (steps, from, to) => {
|
|
1
|
+
export const canReach = (steps, from, to, passing = false) => {
|
|
2
2
|
if (to <= from) {
|
|
3
3
|
return true;
|
|
4
4
|
}
|
|
5
|
-
for (let at = Math.max(from, 0); at < to; at += 1) {
|
|
5
|
+
for (let at = Math.max(passing ? from + 1 : from, 0); at < to; at += 1) {
|
|
6
6
|
const step = steps[at];
|
|
7
7
|
if (step && !step.complete && !step.optional) {
|
|
8
8
|
return false;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HelixElement } from '@fusion.dev/helix-core';
|
|
1
|
+
import { HelixElement, KeyboardNavigationController } from '@fusion.dev/helix-core';
|
|
2
2
|
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
3
|
import type { HelixHeadlessStep } from '../step/step.js';
|
|
4
4
|
declare const HelixHeadlessSteps_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerActiveInterface) & typeof HelixElement;
|
|
@@ -18,7 +18,13 @@ declare const HelixHeadlessSteps_base: (new (...args: any[]) => import("@fusion.
|
|
|
18
18
|
* UI's linear steps do: a stage ahead is out of reach, and its button disabled,
|
|
19
19
|
* until every stage before it is `complete` or `optional`. Going back is always
|
|
20
20
|
* allowed. `next()` and `previous()` move one stage either way, under the
|
|
21
|
-
* same rules, for your own buttons.
|
|
21
|
+
* same rules, for your own buttons. `reset()` goes back to the first stage and
|
|
22
|
+
* clears every stage's `complete`, as Ark UI's `resetStep` does.
|
|
23
|
+
*
|
|
24
|
+
* `completion="passed"` marks a stage `complete` once a reader moves past it,
|
|
25
|
+
* as Ark UI counts a stage before the current one as completed. Going back
|
|
26
|
+
* leaves it complete. In a `linear` sequence the next stage is then in reach
|
|
27
|
+
* from the current one, and `validate` decides whether the reader may go.
|
|
22
28
|
*
|
|
23
29
|
* To check a stage before a reader moves on from it, set `validate` to a
|
|
24
30
|
* function that answers whether the stage is valid, as Ark UI's `isStepValid`
|
|
@@ -39,6 +45,13 @@ declare const HelixHeadlessSteps_base: (new (...args: any[]) => import("@fusion.
|
|
|
39
45
|
* current stage's button says `aria-current="step"`, and a finished stage's
|
|
40
46
|
* button is described by its `complete-label`.
|
|
41
47
|
*
|
|
48
|
+
* **Keyboard.** Each stage's name is a button and its own tab stop, and Enter
|
|
49
|
+
* or Space goes to the stage. Set `arrows` and the arrow keys move focus
|
|
50
|
+
* between the names as well, as Ark UI's steps do: Left and Right along a row,
|
|
51
|
+
* Up and Down a column, Home and End to the first and the last. The arrows
|
|
52
|
+
* pass over a disabled stage and one a linear sequence holds out of reach,
|
|
53
|
+
* and they move focus without going to the stage.
|
|
54
|
+
*
|
|
42
55
|
* @since 0.1.0
|
|
43
56
|
* @status stable
|
|
44
57
|
* @category navigation
|
|
@@ -63,6 +76,20 @@ export declare class HelixHeadlessSteps extends HelixHeadlessSteps_base {
|
|
|
63
76
|
* `registerWords('de', { 'helix-steps': { progressLabel: '{share, number, percent} erledigt' } })`.
|
|
64
77
|
*/
|
|
65
78
|
static words: string;
|
|
79
|
+
/**
|
|
80
|
+
* Whether the arrow keys move focus between the stages' names, along the
|
|
81
|
+
* way the steps run. Off by default, so the arrows keep scrolling the page.
|
|
82
|
+
*/
|
|
83
|
+
arrows: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* What marks a stage `complete`.
|
|
86
|
+
*
|
|
87
|
+
* Absent, you do: each step's `complete` is yours, because a reader who
|
|
88
|
+
* steps back has finished more stages than the one they are on. `passed`
|
|
89
|
+
* marks every stage before the one a reader moves to, and leaves the marks
|
|
90
|
+
* when they go back.
|
|
91
|
+
*/
|
|
92
|
+
completion?: 'passed';
|
|
66
93
|
/**
|
|
67
94
|
* The sequence's name, read by screen readers.
|
|
68
95
|
*
|
|
@@ -120,6 +147,7 @@ export declare class HelixHeadlessSteps extends HelixHeadlessSteps_base {
|
|
|
120
147
|
* a reader is told to come back to by number.
|
|
121
148
|
*/
|
|
122
149
|
variant?: 'numeric';
|
|
150
|
+
protected readonly keys: KeyboardNavigationController;
|
|
123
151
|
/** The steps, in the order the consumer wrote them. */
|
|
124
152
|
private get steps();
|
|
125
153
|
/** The index of the stage the reader is on, or `-1`. */
|
|
@@ -132,6 +160,13 @@ export declare class HelixHeadlessSteps extends HelixHeadlessSteps_base {
|
|
|
132
160
|
next(): boolean;
|
|
133
161
|
/** Moves to the stage before, as Ark UI's `goToPrevStep` does. Returns whether it moved. */
|
|
134
162
|
previous(): boolean;
|
|
163
|
+
/**
|
|
164
|
+
* Goes back to the first stage that can be gone to, and clears `complete`
|
|
165
|
+
* and the invalid mark from every stage, as Ark UI's `resetStep` does.
|
|
166
|
+
* Returns whether it moved: a listener can refuse the move with
|
|
167
|
+
* `helix-before-activate`, and the stages then keep their marks.
|
|
168
|
+
*/
|
|
169
|
+
reset(): boolean;
|
|
135
170
|
/** Moves `delta` stages, skipping disabled ones. */
|
|
136
171
|
private step;
|
|
137
172
|
/**
|
|
@@ -4,16 +4,35 @@ 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, formatWords, HelixElement, hostAria, MixinControllerActive, whenHydrated, words, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { customElement, focusItemOf, formatWords, HelixElement, hostAria, KeyboardNavigationController, MixinControllerActive, whenHydrated, words, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
import { canReach, progressOf } from '../reach.js';
|
|
11
|
+
const navigable = (step) => {
|
|
12
|
+
const label = () => step.shadowRoot?.querySelector('[part="label"]');
|
|
13
|
+
return focusItemOf(step, {
|
|
14
|
+
disabled: step.disabled || step.locked,
|
|
15
|
+
focus: () => step.focus(),
|
|
16
|
+
owns: (node) => {
|
|
17
|
+
const control = label();
|
|
18
|
+
return Boolean(control && node && (node === control || control.contains(node)));
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
};
|
|
11
22
|
let HelixHeadlessSteps = class HelixHeadlessSteps extends MixinControllerActive(HelixElement) {
|
|
12
23
|
constructor() {
|
|
13
24
|
super(...arguments);
|
|
25
|
+
this.arrows = false;
|
|
14
26
|
this.label = 'Steps';
|
|
15
27
|
this.linear = false;
|
|
16
28
|
this.progressLabel = '{share, number, percent} complete';
|
|
29
|
+
this.keys = new KeyboardNavigationController(this, {
|
|
30
|
+
items: () => (this.arrows ? this.steps.map(navigable) : []),
|
|
31
|
+
origin: 'focus',
|
|
32
|
+
reserved: () => this.orientation === 'vertical' ? ['ArrowLeft', 'ArrowRight'] : ['ArrowDown', 'ArrowUp'],
|
|
33
|
+
selects: () => false,
|
|
34
|
+
wrap: false,
|
|
35
|
+
});
|
|
17
36
|
this.handleSelection = (event) => {
|
|
18
37
|
if (event.defaultPrevented || !this.steps.includes(event.target)) {
|
|
19
38
|
return;
|
|
@@ -45,6 +64,18 @@ let HelixHeadlessSteps = class HelixHeadlessSteps extends MixinControllerActive(
|
|
|
45
64
|
previous() {
|
|
46
65
|
return this.step(-1);
|
|
47
66
|
}
|
|
67
|
+
reset() {
|
|
68
|
+
const steps = this.steps;
|
|
69
|
+
const first = steps.find((one) => !one.disabled);
|
|
70
|
+
if (!first || !this.requestActive(first.value)) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
for (const one of steps) {
|
|
74
|
+
one.complete = false;
|
|
75
|
+
one.invalid = false;
|
|
76
|
+
}
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
48
79
|
step(delta) {
|
|
49
80
|
const steps = this.steps;
|
|
50
81
|
let index = this.at + delta;
|
|
@@ -67,7 +98,7 @@ let HelixHeadlessSteps = class HelixHeadlessSteps extends MixinControllerActive(
|
|
|
67
98
|
return valid;
|
|
68
99
|
}
|
|
69
100
|
reachable(index) {
|
|
70
|
-
return !this.linear || canReach(this.steps, this.at, index);
|
|
101
|
+
return !this.linear || canReach(this.steps, this.at, index, this.completion === 'passed');
|
|
71
102
|
}
|
|
72
103
|
lock() {
|
|
73
104
|
const steps = this.steps;
|
|
@@ -84,12 +115,17 @@ let HelixHeadlessSteps = class HelixHeadlessSteps extends MixinControllerActive(
|
|
|
84
115
|
}
|
|
85
116
|
const steps = this.steps;
|
|
86
117
|
const value = this.resolveActive(steps)?.value ?? '';
|
|
87
|
-
|
|
88
|
-
|
|
118
|
+
const reached = steps.findIndex((step) => step.value === value);
|
|
119
|
+
steps.forEach((step, index) => {
|
|
120
|
+
const current = index === reached;
|
|
121
|
+
const passed = this.completion === 'passed' && index < reached;
|
|
89
122
|
whenHydrated(step, () => {
|
|
90
123
|
step.current = current;
|
|
124
|
+
if (passed) {
|
|
125
|
+
step.complete = true;
|
|
126
|
+
}
|
|
91
127
|
});
|
|
92
|
-
}
|
|
128
|
+
});
|
|
93
129
|
}
|
|
94
130
|
connectedCallback() {
|
|
95
131
|
super.connectedCallback();
|
|
@@ -125,10 +161,10 @@ let HelixHeadlessSteps = class HelixHeadlessSteps extends MixinControllerActive(
|
|
|
125
161
|
}
|
|
126
162
|
updated(changed) {
|
|
127
163
|
super.updated(changed);
|
|
128
|
-
if (changed.has('active')) {
|
|
164
|
+
if (changed.has('active') || changed.has('completion')) {
|
|
129
165
|
this.sync();
|
|
130
166
|
}
|
|
131
|
-
if (changed.has('linear') || changed.has('active')) {
|
|
167
|
+
if (changed.has('linear') || changed.has('active') || changed.has('completion')) {
|
|
132
168
|
this.lock();
|
|
133
169
|
}
|
|
134
170
|
}
|
|
@@ -148,6 +184,12 @@ let HelixHeadlessSteps = class HelixHeadlessSteps extends MixinControllerActive(
|
|
|
148
184
|
></div>`;
|
|
149
185
|
}
|
|
150
186
|
};
|
|
187
|
+
__decorate([
|
|
188
|
+
property({ type: Boolean })
|
|
189
|
+
], HelixHeadlessSteps.prototype, "arrows", void 0);
|
|
190
|
+
__decorate([
|
|
191
|
+
property({ reflect: true })
|
|
192
|
+
], HelixHeadlessSteps.prototype, "completion", void 0);
|
|
151
193
|
__decorate([
|
|
152
194
|
words,
|
|
153
195
|
property()
|
|
@@ -53,6 +53,7 @@ import '../../button/index.js';
|
|
|
53
53
|
* @csspart state - The words that describe a finished stage's button. Hidden.
|
|
54
54
|
*
|
|
55
55
|
* @cssstate invalid - The sequence's `validate` last answered that this stage is not valid.
|
|
56
|
+
* @cssstate locked - A `linear` sequence holds this stage out of reach until the stages before it are finished.
|
|
56
57
|
*/
|
|
57
58
|
export declare class HelixHeadlessStep extends HelixElement {
|
|
58
59
|
/**
|
|
@@ -106,6 +107,11 @@ export declare class HelixHeadlessStep extends HelixElement {
|
|
|
106
107
|
value: string;
|
|
107
108
|
/** Says a stage was finished, from the first time one is. */
|
|
108
109
|
private readonly status;
|
|
110
|
+
/**
|
|
111
|
+
* Puts focus on the step's name, the control a reader presses, as a
|
|
112
|
+
* `focus()` on a native control would.
|
|
113
|
+
*/
|
|
114
|
+
focus(options?: FocusOptions): void;
|
|
109
115
|
/**
|
|
110
116
|
* Fires `helix-select` for the step, unless the step is `disabled` or a
|
|
111
117
|
* linear sequence has it out of reach.
|
package/dist/steps/step/step.js
CHANGED
|
@@ -26,6 +26,9 @@ let HelixHeadlessStep = class HelixHeadlessStep extends HelixElement {
|
|
|
26
26
|
this.status = new StatusController(this);
|
|
27
27
|
}
|
|
28
28
|
static { this.words = 'helix-step'; }
|
|
29
|
+
focus(options) {
|
|
30
|
+
this.partNamed('label')?.focus(options);
|
|
31
|
+
}
|
|
29
32
|
select() {
|
|
30
33
|
if (this.disabled || this.locked) {
|
|
31
34
|
return;
|
|
@@ -44,6 +47,7 @@ let HelixHeadlessStep = class HelixHeadlessStep extends HelixElement {
|
|
|
44
47
|
super.willUpdate(changed);
|
|
45
48
|
hostAria(this.internals, { role: 'listitem' });
|
|
46
49
|
this.toggleState('invalid', this.invalid);
|
|
50
|
+
this.toggleState('locked', this.locked);
|
|
47
51
|
}
|
|
48
52
|
updated(changed) {
|
|
49
53
|
super.updated(changed);
|