@fusion.dev/helix-headless 0.169.0 → 0.171.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 +663 -95
- package/dist/chat/composer/chat-composer.d.ts +2 -1
- package/dist/chat/composer/chat-composer.js +7 -1
- package/dist/chat/root/chat-root.d.ts +36 -6
- package/dist/chat/root/chat-root.js +53 -5
- package/dist/code-block/root/code-block-root.d.ts +20 -2
- package/dist/code-block/root/code-block-root.js +29 -8
- package/dist/color-picker/root/color-picker-root.d.ts +50 -7
- package/dist/color-picker/root/color-picker-root.js +62 -1
- package/dist/combobox/root/combobox-root.d.ts +16 -6
- package/dist/combobox/root/combobox-root.js +9 -2
- package/dist/command/root/command-root.d.ts +16 -1
- package/dist/command/root/command-root.js +8 -0
- package/dist/data-table/root/data-table-root.d.ts +40 -5
- package/dist/data-table/root/data-table-root.js +35 -7
- package/dist/filter-input/chip/filter-chip.d.ts +13 -3
- package/dist/filter-input/chip/filter-chip.js +5 -0
- package/dist/filter-input/menu/filter-menu.js +0 -1
- package/dist/filter-input/root/filter-input-root.d.ts +71 -4
- package/dist/filter-input/root/filter-input-root.js +87 -2
- package/dist/format-date/root/format-date-root.d.ts +3 -1
- package/dist/input/root/input-root.d.ts +6 -0
- package/dist/input/root/input-root.js +13 -0
- package/dist/listbox/root/listbox-root.d.ts +6 -0
- package/dist/listbox/root/listbox-root.js +6 -1
- package/dist/number-input/root/number-input-root.d.ts +16 -6
- package/dist/number-input/root/number-input-root.js +15 -4
- package/dist/select/root/select-root.js +2 -0
- package/dist/steps/root/steps-root.d.ts +32 -8
- package/dist/steps/root/steps-root.js +70 -9
- package/dist/steps/step/step.d.ts +9 -1
- package/dist/steps/step/step.js +5 -0
- package/dist/video-playlist/root/video-playlist-root.d.ts +19 -1
- package/dist/video-playlist/root/video-playlist-root.js +14 -3
- package/dist/virtual-list/root/virtual-list-root.d.ts +8 -0
- package/dist/virtual-list/root/virtual-list-root.js +7 -2
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +510 -126
- package/types/vue.d.ts +109 -26
- package/types/words.d.ts +12 -2
|
@@ -21,9 +21,13 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
|
|
|
21
21
|
target: () => this.partNamed('control'),
|
|
22
22
|
});
|
|
23
23
|
this.announcesInput = true;
|
|
24
|
+
this.chips = false;
|
|
24
25
|
this.clearable = false;
|
|
25
26
|
this.clearLabel = 'Clear';
|
|
26
27
|
this.filterSuggestions = false;
|
|
28
|
+
this.loading = false;
|
|
29
|
+
this.loadingText = 'Loading';
|
|
30
|
+
this.noResultsText = '';
|
|
27
31
|
this.placeholder = '';
|
|
28
32
|
this.suggestionsLabel = '{count, plural, one {# suggestion} other {# suggestions}}';
|
|
29
33
|
this.status = new StatusController(this);
|
|
@@ -59,6 +63,17 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
|
|
|
59
63
|
event.stopPropagation();
|
|
60
64
|
this.announceChange();
|
|
61
65
|
};
|
|
66
|
+
this.handleFacet = (event) => {
|
|
67
|
+
if (!this.chips) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const facet = event.detail;
|
|
71
|
+
queueMicrotask(() => {
|
|
72
|
+
if (!event.defaultPrevented) {
|
|
73
|
+
this.drawChip(facet);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
};
|
|
62
77
|
this.handleDismiss = (event) => {
|
|
63
78
|
const chip = event.composedPath()[0];
|
|
64
79
|
if (!(chip instanceof HTMLElement) || chip.slot !== 'tags') {
|
|
@@ -161,6 +176,20 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
|
|
|
161
176
|
};
|
|
162
177
|
this.handleTokens = () => {
|
|
163
178
|
this.over = this.hideOverflow(this.tokens);
|
|
179
|
+
for (const token of this.tokenElements()) {
|
|
180
|
+
const quiet = () => {
|
|
181
|
+
if ('dismissTabStop' in token) {
|
|
182
|
+
;
|
|
183
|
+
token.dismissTabStop = false;
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
if (token.localName.includes('-') && !customElements.get(token.localName)) {
|
|
187
|
+
void customElements.whenDefined(token.localName).then(quiet);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
quiet();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
164
193
|
};
|
|
165
194
|
}
|
|
166
195
|
static { this.words = 'helix-filter-input'; }
|
|
@@ -181,14 +210,22 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
|
|
|
181
210
|
}
|
|
182
211
|
}
|
|
183
212
|
sayCount() {
|
|
184
|
-
const count = this.open && this.suggesting ? this.suggestions.length : -1;
|
|
213
|
+
const count = this.open && this.suggesting ? (this.loading ? -2 : this.suggestions.length) : -1;
|
|
185
214
|
if (count === this.saidCount) {
|
|
186
215
|
return;
|
|
187
216
|
}
|
|
188
217
|
this.saidCount = count;
|
|
218
|
+
if (count === -2) {
|
|
219
|
+
this.status.sayNow(this.loadingText);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
189
222
|
if (count < 0) {
|
|
190
223
|
return;
|
|
191
224
|
}
|
|
225
|
+
if (count === 0 && this.noResultsText !== '') {
|
|
226
|
+
this.status.sayNow(this.noResultsText);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
192
229
|
this.status.sayNow(this.suggestionLabel
|
|
193
230
|
? countLabel(count, this.suggestionLabel, this.suggestionsLabel, wordsLocale(this))
|
|
194
231
|
: formatWords(this, 'suggestionsLabel', { count }));
|
|
@@ -259,6 +296,25 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
|
|
|
259
296
|
? menu.apply(facet.field, facet.values, facet.operator)
|
|
260
297
|
: this.dispatchEvent(new HelixFacetEvent(facet));
|
|
261
298
|
}
|
|
299
|
+
drawChip(facet) {
|
|
300
|
+
const tag = `${this.constructor.tier}filter-chip`;
|
|
301
|
+
const held = [...this.children].find((child) => child.localName === tag && child.getAttribute('name') === facet.field);
|
|
302
|
+
if (facet.values.length === 0 && facet.arity !== 'none') {
|
|
303
|
+
held?.remove();
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const chip = held ?? document.createElement(tag);
|
|
307
|
+
chip.slot = 'tags';
|
|
308
|
+
chip.dismissible = true;
|
|
309
|
+
chip.label = facet.label;
|
|
310
|
+
chip.setAttribute('name', facet.field);
|
|
311
|
+
chip.operator = facet.operator;
|
|
312
|
+
chip.operatorLabel = facet.operatorLabel;
|
|
313
|
+
chip.textContent = facet.values.join(', ');
|
|
314
|
+
if (!held) {
|
|
315
|
+
this.append(chip);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
262
318
|
readFilters() {
|
|
263
319
|
const fields = this.fields;
|
|
264
320
|
if (fields.length === 0) {
|
|
@@ -310,9 +366,13 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
|
|
|
310
366
|
refreshSuggesting() {
|
|
311
367
|
this.narrow();
|
|
312
368
|
const options = this.suggestions;
|
|
313
|
-
this.suggesting = options.length > 0;
|
|
369
|
+
this.suggesting = options.length > 0 || this.loading || this.unmatched;
|
|
314
370
|
this.activeAt = this.activeAt < options.length ? this.activeAt : -1;
|
|
315
371
|
}
|
|
372
|
+
get unmatched() {
|
|
373
|
+
const said = this.noResultsText !== '' || this.knownChildren.some((child) => child.slot === 'no-results');
|
|
374
|
+
return said && !this.loading && this.options.length > 0 && this.suggestions.length === 0;
|
|
375
|
+
}
|
|
316
376
|
tokenElements() {
|
|
317
377
|
return (this.tokens?.assignedElements({ flatten: true }) ?? []);
|
|
318
378
|
}
|
|
@@ -328,10 +388,12 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
|
|
|
328
388
|
connectedCallback() {
|
|
329
389
|
super.connectedCallback();
|
|
330
390
|
this.addEventListener('helix-dismiss', this.handleDismiss);
|
|
391
|
+
this.addEventListener(HelixFacetEvent.eventName, this.handleFacet);
|
|
331
392
|
}
|
|
332
393
|
disconnectedCallback() {
|
|
333
394
|
super.disconnectedCallback();
|
|
334
395
|
this.removeEventListener('helix-dismiss', this.handleDismiss);
|
|
396
|
+
this.removeEventListener(HelixFacetEvent.eventName, this.handleFacet);
|
|
335
397
|
}
|
|
336
398
|
willUpdate(changed) {
|
|
337
399
|
super.willUpdate(changed);
|
|
@@ -348,6 +410,8 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
|
|
|
348
410
|
}
|
|
349
411
|
this.handleTokens();
|
|
350
412
|
this.refreshSuggesting();
|
|
413
|
+
this.toggleState('loading', this.loading);
|
|
414
|
+
this.toggleState('no-results', this.unmatched);
|
|
351
415
|
}
|
|
352
416
|
firstUpdated(changed) {
|
|
353
417
|
super.firstUpdated(changed);
|
|
@@ -443,11 +507,22 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
|
|
|
443
507
|
?hidden=${!this.open}
|
|
444
508
|
>
|
|
445
509
|
<slot name="suggestions" @slotchange=${this.handleSuggestions}></slot>
|
|
510
|
+
<!-- Hidden from the tree, which a listbox keeps for its options: the
|
|
511
|
+
live region says these. -->
|
|
512
|
+
<div part="loading" aria-hidden="true" ?hidden=${!this.loading}>
|
|
513
|
+
<slot name="loading">${this.loadingText}</slot>
|
|
514
|
+
</div>
|
|
515
|
+
<div part="empty" aria-hidden="true" ?hidden=${!this.unmatched}>
|
|
516
|
+
<slot name="no-results">${this.noResultsText}</slot>
|
|
517
|
+
</div>
|
|
446
518
|
</div>
|
|
447
519
|
${drawStatus(this.status)}
|
|
448
520
|
${this.renderHint()}${this.renderFeedback()}`;
|
|
449
521
|
}
|
|
450
522
|
};
|
|
523
|
+
__decorate([
|
|
524
|
+
property({ type: Boolean })
|
|
525
|
+
], HelixHeadlessFilterInput.prototype, "chips", void 0);
|
|
451
526
|
__decorate([
|
|
452
527
|
property({ type: Boolean })
|
|
453
528
|
], HelixHeadlessFilterInput.prototype, "clearable", void 0);
|
|
@@ -458,6 +533,16 @@ __decorate([
|
|
|
458
533
|
__decorate([
|
|
459
534
|
property({ attribute: 'filter-suggestions', type: Boolean })
|
|
460
535
|
], HelixHeadlessFilterInput.prototype, "filterSuggestions", void 0);
|
|
536
|
+
__decorate([
|
|
537
|
+
property({ reflect: true, type: Boolean })
|
|
538
|
+
], HelixHeadlessFilterInput.prototype, "loading", void 0);
|
|
539
|
+
__decorate([
|
|
540
|
+
words,
|
|
541
|
+
property({ attribute: 'loading-text' })
|
|
542
|
+
], HelixHeadlessFilterInput.prototype, "loadingText", void 0);
|
|
543
|
+
__decorate([
|
|
544
|
+
property({ attribute: 'no-results-text' })
|
|
545
|
+
], HelixHeadlessFilterInput.prototype, "noResultsText", void 0);
|
|
461
546
|
__decorate([
|
|
462
547
|
property()
|
|
463
548
|
], HelixHeadlessFilterInput.prototype, "placeholder", void 0);
|
|
@@ -15,7 +15,9 @@ import { nothing, type PropertyValues, type TemplateResult } from 'lit';
|
|
|
15
15
|
* **`relative` shows the same reading in words**, such as "3 hours ago" at
|
|
16
16
|
* `long`, "3 hr. ago" at `short` and "3h ago" at `narrow`, the styles
|
|
17
17
|
* `sl-relative-time` offers. `numeric="always"` says "1 day ago" where the
|
|
18
|
-
* default says "yesterday".
|
|
18
|
+
* default says "yesterday". Both readings round the count, as
|
|
19
|
+
* `sl-relative-time` does: 100 seconds ago is "2 minutes ago", and 59.6
|
|
20
|
+
* minutes is "1 hour".
|
|
19
21
|
*
|
|
20
22
|
* **`date-style` and `time-style` take each locale's own pattern**, such as
|
|
21
23
|
* "Tuesday, August 4, 2026" at `full`, and stand in for the separate fields,
|
|
@@ -209,6 +209,12 @@ export declare class HelixHeadlessInput extends HelixHeadlessInput_base {
|
|
|
209
209
|
/** Where the selection starts, as an offset into the value. */
|
|
210
210
|
get selectionStart(): number;
|
|
211
211
|
set selectionStart(at: number);
|
|
212
|
+
/**
|
|
213
|
+
* Replaces a range of the text, as the native method does: the selection
|
|
214
|
+
* where `start` and `end` are left out, and `selectMode` says what is
|
|
215
|
+
* selected after. It sends no event, as the native method sends none.
|
|
216
|
+
*/
|
|
217
|
+
setRangeText(replacement: string, start?: number, end?: number, selectMode?: SelectionMode): void;
|
|
212
218
|
/**
|
|
213
219
|
* Selects the range from `start` to `end` in one call, which setting
|
|
214
220
|
* `selectionStart` and `selectionEnd` one at a time cannot always do.
|
|
@@ -97,6 +97,19 @@ let HelixHeadlessInput = class HelixHeadlessInput extends MixinControllerValue(M
|
|
|
97
97
|
control.selectionStart = at;
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
|
+
setRangeText(replacement, start, end, selectMode) {
|
|
101
|
+
const control = this.control;
|
|
102
|
+
if (!control) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (start === undefined || end === undefined) {
|
|
106
|
+
control.setRangeText(replacement);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
control.setRangeText(replacement, start, end, selectMode);
|
|
110
|
+
}
|
|
111
|
+
this.value = control.value;
|
|
112
|
+
}
|
|
100
113
|
setSelectionRange(start, end, direction) {
|
|
101
114
|
this.control?.setSelectionRange(start, end, direction);
|
|
102
115
|
}
|
|
@@ -154,6 +154,12 @@ export declare class HelixHeadlessListbox extends HelixHeadlessListbox_base {
|
|
|
154
154
|
emptyText: string;
|
|
155
155
|
/** What the list is narrowed to, whoever set it. */
|
|
156
156
|
private filterState;
|
|
157
|
+
/**
|
|
158
|
+
* @internal Set by `Command` under `home-end="list"`: whether Home and End
|
|
159
|
+
* in the filter field move the highlight, without Shift, rather than the
|
|
160
|
+
* caret. A property, as coordination between elements.
|
|
161
|
+
*/
|
|
162
|
+
fieldHomeEnd: boolean;
|
|
157
163
|
/**
|
|
158
164
|
* The text the list is filtered by, if any.
|
|
159
165
|
*
|
|
@@ -23,6 +23,7 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
|
|
|
23
23
|
this.defaultFilter = '';
|
|
24
24
|
this.emptyText = 'No matches';
|
|
25
25
|
this.filterState = undefined;
|
|
26
|
+
this.fieldHomeEnd = false;
|
|
26
27
|
this.filterable = false;
|
|
27
28
|
this.filterDelay = 0;
|
|
28
29
|
this.filterLabel = 'Filter';
|
|
@@ -127,7 +128,8 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
|
|
|
127
128
|
(event.key === 'Home' || event.key === 'End');
|
|
128
129
|
const grid = this.layout === 'grid' && !this.data.records;
|
|
129
130
|
const along = grid && !typing ? inlineStep(this, event.key) : 0;
|
|
130
|
-
|
|
131
|
+
const fieldMoves = this.fieldHomeEnd && !event.shiftKey ? MOVES : FIELD_MOVES;
|
|
132
|
+
if (!fieldRange && !along && !(typing ? fieldMoves : MOVES).has(event.key)) {
|
|
131
133
|
return;
|
|
132
134
|
}
|
|
133
135
|
event.preventDefault();
|
|
@@ -673,6 +675,9 @@ __decorate([
|
|
|
673
675
|
words,
|
|
674
676
|
property({ attribute: 'empty-text' })
|
|
675
677
|
], HelixHeadlessListbox.prototype, "emptyText", void 0);
|
|
678
|
+
__decorate([
|
|
679
|
+
property({ attribute: false })
|
|
680
|
+
], HelixHeadlessListbox.prototype, "fieldHomeEnd", void 0);
|
|
676
681
|
__decorate([
|
|
677
682
|
property()
|
|
678
683
|
], HelixHeadlessListbox.prototype, "filter", null);
|
|
@@ -124,7 +124,9 @@ export declare class HelixHeadlessNumberInput extends HelixHeadlessNumberInput_b
|
|
|
124
124
|
*/
|
|
125
125
|
badInputMessage: string;
|
|
126
126
|
/**
|
|
127
|
-
* The name of the step down button, read by screen readers.
|
|
127
|
+
* The name of the step down button, read by screen readers. The field's
|
|
128
|
+
* label follows it, so a reader hears "Decrease Quantity", as React Aria
|
|
129
|
+
* names its steppers.
|
|
128
130
|
*/
|
|
129
131
|
decrementLabel: string;
|
|
130
132
|
/**
|
|
@@ -138,7 +140,9 @@ export declare class HelixHeadlessNumberInput extends HelixHeadlessNumberInput_b
|
|
|
138
140
|
*/
|
|
139
141
|
formatOptions?: Intl.NumberFormatOptions;
|
|
140
142
|
/**
|
|
141
|
-
* The name of the step up button, read by screen readers.
|
|
143
|
+
* The name of the step up button, read by screen readers. The field's
|
|
144
|
+
* label follows it, so a reader hears "Increase Quantity", as React Aria
|
|
145
|
+
* names its steppers.
|
|
142
146
|
*/
|
|
143
147
|
incrementLabel: string;
|
|
144
148
|
/**
|
|
@@ -321,16 +325,22 @@ export declare class HelixHeadlessNumberInput extends HelixHeadlessNumberInput_b
|
|
|
321
325
|
* refused step, or one pinned at a bound, finished nothing.
|
|
322
326
|
*/
|
|
323
327
|
private land;
|
|
324
|
-
/** Steps the value up, by `largeStep` steps when `large` is set. */
|
|
325
|
-
stepUp(large?: boolean): void;
|
|
326
328
|
/**
|
|
327
|
-
* Steps the value
|
|
329
|
+
* Steps the value up by `step`, `count` times, as the native method and
|
|
330
|
+
* `Input.stepUp` do. `true` takes `largeStep` steps, as `PageUp` does.
|
|
331
|
+
*/
|
|
332
|
+
stepUp(count?: number | boolean): void;
|
|
333
|
+
/**
|
|
334
|
+
* Steps the value down by `step`, `count` times. `true` takes `largeStep`
|
|
335
|
+
* steps, as `PageDown` does.
|
|
328
336
|
*
|
|
329
337
|
* The same path a press of the down arrow takes, so a consumer driving this
|
|
330
338
|
* from their own control gets the clamping and the announcement rather than
|
|
331
339
|
* having to repeat both.
|
|
332
340
|
*/
|
|
333
|
-
stepDown(
|
|
341
|
+
stepDown(count?: number | boolean): void;
|
|
342
|
+
/** How many steps a `stepUp` argument asks for: a count, or `largeStep` for `true`. */
|
|
343
|
+
private stepsOf;
|
|
334
344
|
/** The timer of a held stepper button. */
|
|
335
345
|
private repeat;
|
|
336
346
|
/** Whether a pointer press stepped already, so the click that ends it does not step again. */
|
|
@@ -324,11 +324,17 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
|
|
|
324
324
|
this.announceChange();
|
|
325
325
|
}
|
|
326
326
|
}
|
|
327
|
-
stepUp(
|
|
328
|
-
this.nudge(
|
|
327
|
+
stepUp(count = 1) {
|
|
328
|
+
this.nudge(this.stepsOf(count));
|
|
329
329
|
}
|
|
330
|
-
stepDown(
|
|
331
|
-
this.nudge(
|
|
330
|
+
stepDown(count = 1) {
|
|
331
|
+
this.nudge(-this.stepsOf(count));
|
|
332
|
+
}
|
|
333
|
+
stepsOf(count) {
|
|
334
|
+
if (typeof count === 'boolean') {
|
|
335
|
+
return count ? (this.largeStep ?? 10) : 1;
|
|
336
|
+
}
|
|
337
|
+
return Number.isFinite(count) ? Math.trunc(count) : 1;
|
|
332
338
|
}
|
|
333
339
|
disconnectedCallback() {
|
|
334
340
|
super.disconnectedCallback();
|
|
@@ -342,6 +348,7 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
|
|
|
342
348
|
return html `<label
|
|
343
349
|
part="label"
|
|
344
350
|
for=${this.controlId}
|
|
351
|
+
id=${this.labelId}
|
|
345
352
|
?data-hidden=${hidden !== null}
|
|
346
353
|
@pointercancel=${this.handleScrubEnd}
|
|
347
354
|
@pointerdown=${this.handleScrubDown}
|
|
@@ -455,8 +462,10 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
|
|
|
455
462
|
<div part="field" role="group" aria-labelledby=${this.labeled ? this.labelId : nothing}>
|
|
456
463
|
<button
|
|
457
464
|
part="decrement"
|
|
465
|
+
id="decrement"
|
|
458
466
|
type="button"
|
|
459
467
|
aria-label=${this.decrementLabel}
|
|
468
|
+
aria-labelledby=${this.labeled ? `decrement ${this.labelId}` : nothing}
|
|
460
469
|
tabindex="-1"
|
|
461
470
|
?disabled=${this.isDisabled || this.readOnly || atMin}
|
|
462
471
|
@click=${this.handleStepperClick}
|
|
@@ -511,8 +520,10 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
|
|
|
511
520
|
<slot name="end"></slot>
|
|
512
521
|
<button
|
|
513
522
|
part="increment"
|
|
523
|
+
id="increment"
|
|
514
524
|
type="button"
|
|
515
525
|
aria-label=${this.incrementLabel}
|
|
526
|
+
aria-labelledby=${this.labeled ? `increment ${this.labelId}` : nothing}
|
|
516
527
|
tabindex="-1"
|
|
517
528
|
?disabled=${this.isDisabled || this.readOnly || atMax}
|
|
518
529
|
@click=${this.handleStepperClick}
|
|
@@ -310,6 +310,8 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
|
|
|
310
310
|
}
|
|
311
311
|
else {
|
|
312
312
|
this.stopAligning();
|
|
313
|
+
this.toggleState('placed-bottom', false);
|
|
314
|
+
this.toggleState('placed-top', false);
|
|
313
315
|
this.listbox?.requestFilter('');
|
|
314
316
|
}
|
|
315
317
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HelixElement, KeyboardNavigationController } from '@fusion.dev/helix-core';
|
|
1
|
+
import { CommandController, 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;
|
|
@@ -14,12 +14,21 @@ declare const HelixHeadlessSteps_base: (new (...args: any[]) => import("@fusion.
|
|
|
14
14
|
* sequence is correct in the server's markup, where the root cannot count its
|
|
15
15
|
* slotted children.
|
|
16
16
|
*
|
|
17
|
+
* `active` names a step by its `value`. A step with no `value` is named by its
|
|
18
|
+
* place, counted from 1, so a sequence written without values moves as one
|
|
19
|
+
* written with them, and `helix-activate` says `1`, `2` and so on. Give every
|
|
20
|
+
* step a value or none, because a value of `2` and the second place are one
|
|
21
|
+
* name.
|
|
22
|
+
*
|
|
17
23
|
* `linear` keeps a reader from passing a stage they have not finished, as Ark
|
|
18
24
|
* UI's linear steps do: a stage ahead is out of reach, and its button disabled,
|
|
19
25
|
* until every stage before it is `complete` or `optional`. Going back is always
|
|
20
26
|
* allowed. `next()` and `previous()` move one stage either way, under the
|
|
21
27
|
* 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.
|
|
28
|
+
* clears every stage's `complete`, as Ark UI's `resetStep` does. A button
|
|
29
|
+
* anywhere on the page calls them with no script, as Ark UI's previous and
|
|
30
|
+
* next triggers do: give it `commandfor` with the sequence's `id`, and
|
|
31
|
+
* `command="--previous"`, `"--next"` or `"--reset"`.
|
|
23
32
|
*
|
|
24
33
|
* `completion="passed"` marks a stage `complete` once a reader moves past it,
|
|
25
34
|
* as Ark UI counts a stage before the current one as completed. Going back
|
|
@@ -46,11 +55,13 @@ declare const HelixHeadlessSteps_base: (new (...args: any[]) => import("@fusion.
|
|
|
46
55
|
* button is described by its `complete-label`.
|
|
47
56
|
*
|
|
48
57
|
* **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
|
|
50
|
-
*
|
|
51
|
-
* Up and Down a column, Home and End to the first and the last.
|
|
52
|
-
*
|
|
53
|
-
*
|
|
58
|
+
* or Space goes to the stage. Set `arrows` and the names are one tab stop, the
|
|
59
|
+
* current stage's, and the arrow keys move focus between them: Left and Right
|
|
60
|
+
* along a row, Up and Down a column, Home and End to the first and the last.
|
|
61
|
+
* The stop follows focus, so Tab leaves the sequence and Shift+Tab comes back
|
|
62
|
+
* to the name the reader left. The arrows pass over a disabled stage and one a
|
|
63
|
+
* linear sequence holds out of reach, and they move focus without going to the
|
|
64
|
+
* stage.
|
|
54
65
|
*
|
|
55
66
|
* @since 0.1.0
|
|
56
67
|
* @status stable
|
|
@@ -78,7 +89,8 @@ export declare class HelixHeadlessSteps extends HelixHeadlessSteps_base {
|
|
|
78
89
|
static words: string;
|
|
79
90
|
/**
|
|
80
91
|
* Whether the arrow keys move focus between the stages' names, along the
|
|
81
|
-
* way the steps run. Off by default, so the
|
|
92
|
+
* way the steps run, and the names are one tab stop. Off by default, so the
|
|
93
|
+
* arrows keep scrolling the page.
|
|
82
94
|
*/
|
|
83
95
|
arrows: boolean;
|
|
84
96
|
/**
|
|
@@ -148,6 +160,7 @@ export declare class HelixHeadlessSteps extends HelixHeadlessSteps_base {
|
|
|
148
160
|
*/
|
|
149
161
|
variant?: 'numeric';
|
|
150
162
|
protected readonly keys: KeyboardNavigationController;
|
|
163
|
+
protected readonly commands: CommandController;
|
|
151
164
|
/** The steps, in the order the consumer wrote them. */
|
|
152
165
|
private get steps();
|
|
153
166
|
/**
|
|
@@ -158,6 +171,8 @@ export declare class HelixHeadlessSteps extends HelixHeadlessSteps_base {
|
|
|
158
171
|
* calls to `next()` in one task then move two stages.
|
|
159
172
|
*/
|
|
160
173
|
private get at();
|
|
174
|
+
/** The step `active` names, by `value` or by place, or the first enabled one. */
|
|
175
|
+
private named;
|
|
161
176
|
/**
|
|
162
177
|
* Requests a new current stage, and returns whether it moved.
|
|
163
178
|
*
|
|
@@ -207,6 +222,15 @@ export declare class HelixHeadlessSteps extends HelixHeadlessSteps_base {
|
|
|
207
222
|
*/
|
|
208
223
|
private sync;
|
|
209
224
|
private readonly handleSelection;
|
|
225
|
+
/** The step whose name holds the sequence's one tab stop under `arrows`, once focus has chosen it. */
|
|
226
|
+
private holder;
|
|
227
|
+
/**
|
|
228
|
+
* Gives the one tab stop to a name, under `arrows`: the one focus last left,
|
|
229
|
+
* else the current stage's, else the first a reader can go to. Without
|
|
230
|
+
* `arrows` every name is its own stop. Client only.
|
|
231
|
+
*/
|
|
232
|
+
private rove;
|
|
233
|
+
private readonly handleFocus;
|
|
210
234
|
private readonly handleSlotChange;
|
|
211
235
|
connectedCallback(): void;
|
|
212
236
|
disconnectedCallback(): void;
|
|
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { customElement, focusItemOf, formatWords, HelixElement, hostAria, KeyboardNavigationController, MixinControllerActive, whenHydrated, words, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { CommandController, 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';
|
|
@@ -19,6 +19,7 @@ const navigable = (step) => {
|
|
|
19
19
|
},
|
|
20
20
|
});
|
|
21
21
|
};
|
|
22
|
+
const keyOf = (step, index) => step.value || String(index + 1);
|
|
22
23
|
let HelixHeadlessSteps = class HelixHeadlessSteps extends MixinControllerActive(HelixElement) {
|
|
23
24
|
constructor() {
|
|
24
25
|
super(...arguments);
|
|
@@ -33,6 +34,13 @@ let HelixHeadlessSteps = class HelixHeadlessSteps extends MixinControllerActive(
|
|
|
33
34
|
selects: () => false,
|
|
34
35
|
wrap: false,
|
|
35
36
|
});
|
|
37
|
+
this.commands = new CommandController(this, {
|
|
38
|
+
commands: {
|
|
39
|
+
'--next': () => this.next(),
|
|
40
|
+
'--previous': () => this.previous(),
|
|
41
|
+
'--reset': () => this.reset(),
|
|
42
|
+
},
|
|
43
|
+
});
|
|
36
44
|
this.handleSelection = (event) => {
|
|
37
45
|
if (!this.steps.includes(event.target)) {
|
|
38
46
|
return;
|
|
@@ -42,12 +50,27 @@ let HelixHeadlessSteps = class HelixHeadlessSteps extends MixinControllerActive(
|
|
|
42
50
|
if (event.defaultPrevented || !this.reachable(index) || !this.valid(index)) {
|
|
43
51
|
return;
|
|
44
52
|
}
|
|
45
|
-
this.requestActive(
|
|
53
|
+
this.requestActive(keyOf(this.steps[index], index));
|
|
46
54
|
});
|
|
47
55
|
};
|
|
56
|
+
this.handleFocus = (event) => {
|
|
57
|
+
if (!this.arrows) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const path = event.composedPath();
|
|
61
|
+
const step = this.steps.find((one) => {
|
|
62
|
+
const name = one.shadowRoot?.querySelector('[part="label"]');
|
|
63
|
+
return name ? path.includes(name) : false;
|
|
64
|
+
});
|
|
65
|
+
if (step && step !== this.holder) {
|
|
66
|
+
this.holder = step;
|
|
67
|
+
this.rove();
|
|
68
|
+
}
|
|
69
|
+
};
|
|
48
70
|
this.handleSlotChange = () => {
|
|
49
71
|
this.sync();
|
|
50
72
|
this.lock();
|
|
73
|
+
this.rove();
|
|
51
74
|
};
|
|
52
75
|
}
|
|
53
76
|
static { this.words = 'helix-steps'; }
|
|
@@ -62,11 +85,20 @@ let HelixHeadlessSteps = class HelixHeadlessSteps extends MixinControllerActive(
|
|
|
62
85
|
if (this.active === '') {
|
|
63
86
|
return steps.findIndex((step) => step.current);
|
|
64
87
|
}
|
|
65
|
-
const item = this.
|
|
88
|
+
const item = this.named(steps);
|
|
66
89
|
return item ? steps.indexOf(item) : -1;
|
|
67
90
|
}
|
|
91
|
+
named(steps) {
|
|
92
|
+
const keyed = steps.map((step, index) => ({
|
|
93
|
+
disabled: step.disabled,
|
|
94
|
+
step,
|
|
95
|
+
value: keyOf(step, index),
|
|
96
|
+
}));
|
|
97
|
+
return this.resolveActive(keyed)?.step;
|
|
98
|
+
}
|
|
68
99
|
requestActive(next, cause) {
|
|
69
|
-
const
|
|
100
|
+
const at = this.active === '' ? this.at : -1;
|
|
101
|
+
const here = at === -1 ? undefined : keyOf(this.steps[at], at);
|
|
70
102
|
if (here) {
|
|
71
103
|
this.active = here;
|
|
72
104
|
}
|
|
@@ -80,8 +112,8 @@ let HelixHeadlessSteps = class HelixHeadlessSteps extends MixinControllerActive(
|
|
|
80
112
|
}
|
|
81
113
|
reset() {
|
|
82
114
|
const steps = this.steps;
|
|
83
|
-
const first = steps.
|
|
84
|
-
if (
|
|
115
|
+
const first = steps.findIndex((one) => !one.disabled);
|
|
116
|
+
if (first === -1 || !this.requestActive(keyOf(steps[first], first))) {
|
|
85
117
|
return false;
|
|
86
118
|
}
|
|
87
119
|
for (const one of steps) {
|
|
@@ -100,7 +132,7 @@ let HelixHeadlessSteps = class HelixHeadlessSteps extends MixinControllerActive(
|
|
|
100
132
|
if (!target || !this.reachable(index) || !this.valid(index)) {
|
|
101
133
|
return false;
|
|
102
134
|
}
|
|
103
|
-
return this.requestActive(target
|
|
135
|
+
return this.requestActive(keyOf(target, index));
|
|
104
136
|
}
|
|
105
137
|
valid(index) {
|
|
106
138
|
const from = this.steps[this.at];
|
|
@@ -128,8 +160,8 @@ let HelixHeadlessSteps = class HelixHeadlessSteps extends MixinControllerActive(
|
|
|
128
160
|
return;
|
|
129
161
|
}
|
|
130
162
|
const steps = this.steps;
|
|
131
|
-
const
|
|
132
|
-
const reached = steps.
|
|
163
|
+
const named = this.named(steps);
|
|
164
|
+
const reached = named ? steps.indexOf(named) : -1;
|
|
133
165
|
steps.forEach((step, index) => {
|
|
134
166
|
const current = index === reached;
|
|
135
167
|
const passed = this.completion === 'passed' && index < reached;
|
|
@@ -141,11 +173,30 @@ let HelixHeadlessSteps = class HelixHeadlessSteps extends MixinControllerActive(
|
|
|
141
173
|
});
|
|
142
174
|
});
|
|
143
175
|
}
|
|
176
|
+
rove() {
|
|
177
|
+
const steps = this.steps;
|
|
178
|
+
const open = steps.filter((step, index) => !step.disabled && this.reachable(index));
|
|
179
|
+
const current = steps[this.at];
|
|
180
|
+
const holder = !this.arrows
|
|
181
|
+
? undefined
|
|
182
|
+
: this.holder && open.includes(this.holder)
|
|
183
|
+
? this.holder
|
|
184
|
+
: current && open.includes(current)
|
|
185
|
+
? current
|
|
186
|
+
: open[0];
|
|
187
|
+
for (const step of steps) {
|
|
188
|
+
whenHydrated(step, () => {
|
|
189
|
+
step.stop = !this.arrows || step === holder;
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
144
193
|
connectedCallback() {
|
|
145
194
|
super.connectedCallback();
|
|
146
195
|
this.addEventListener('helix-select', this.handleSelection);
|
|
196
|
+
this.addEventListener('focusin', this.handleFocus);
|
|
147
197
|
this.watcher = new MutationObserver(() => {
|
|
148
198
|
this.lock();
|
|
199
|
+
this.rove();
|
|
149
200
|
this.requestUpdate();
|
|
150
201
|
});
|
|
151
202
|
this.watcher.observe(this, {
|
|
@@ -157,6 +208,7 @@ let HelixHeadlessSteps = class HelixHeadlessSteps extends MixinControllerActive(
|
|
|
157
208
|
disconnectedCallback() {
|
|
158
209
|
super.disconnectedCallback();
|
|
159
210
|
this.removeEventListener('helix-select', this.handleSelection);
|
|
211
|
+
this.removeEventListener('focusin', this.handleFocus);
|
|
160
212
|
this.watcher?.disconnect();
|
|
161
213
|
this.watcher = undefined;
|
|
162
214
|
}
|
|
@@ -181,6 +233,15 @@ let HelixHeadlessSteps = class HelixHeadlessSteps extends MixinControllerActive(
|
|
|
181
233
|
if (changed.has('linear') || changed.has('active') || changed.has('completion')) {
|
|
182
234
|
this.lock();
|
|
183
235
|
}
|
|
236
|
+
if (changed.has('active')) {
|
|
237
|
+
this.holder = undefined;
|
|
238
|
+
}
|
|
239
|
+
if (changed.has('arrows') ||
|
|
240
|
+
changed.has('linear') ||
|
|
241
|
+
changed.has('active') ||
|
|
242
|
+
changed.has('completion')) {
|
|
243
|
+
this.rove();
|
|
244
|
+
}
|
|
184
245
|
}
|
|
185
246
|
render() {
|
|
186
247
|
const progress = progressOf(this.known);
|
|
@@ -106,7 +106,15 @@ export declare class HelixHeadlessStep extends HelixElement {
|
|
|
106
106
|
*/
|
|
107
107
|
optional: boolean;
|
|
108
108
|
/**
|
|
109
|
-
*
|
|
109
|
+
* @internal Set by a sequence with `arrows`: whether the name holds the
|
|
110
|
+
* sequence's one tab stop. A property, because the sequence writes it after
|
|
111
|
+
* the step upgrades.
|
|
112
|
+
*/
|
|
113
|
+
stop: boolean;
|
|
114
|
+
/**
|
|
115
|
+
* The value the step sends in `helix-select`, and what names the step in its
|
|
116
|
+
* sequence's `active`. Without one, the sequence names the step by its place,
|
|
117
|
+
* counted from 1.
|
|
110
118
|
*/
|
|
111
119
|
value: string;
|
|
112
120
|
/** Whether anything is slotted into `content`, which makes it a named group. */
|