@fusion.dev/helix-headless 0.179.0 → 0.180.1
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 +212 -50
- package/dist/chat/composer/chat-composer.d.ts +2 -1
- package/dist/chat/composer/chat-composer.js +1 -1
- package/dist/checkbox/root/checkbox-root.d.ts +6 -0
- package/dist/checkbox/root/checkbox-root.js +5 -0
- package/dist/combobox/root/combobox-root.d.ts +12 -0
- package/dist/combobox/root/combobox-root.js +3 -2
- package/dist/command/root/command-root.d.ts +12 -0
- package/dist/command/root/command-root.js +3 -2
- package/dist/data-table/root/data-table-root.d.ts +13 -1
- package/dist/data-table/root/data-table-root.js +18 -15
- package/dist/date-input/root/date-input-root.d.ts +7 -0
- package/dist/date-input/root/date-input-root.js +12 -0
- package/dist/date-picker/root/date-picker-root.d.ts +7 -1
- package/dist/date-picker/root/date-picker-root.js +12 -0
- package/dist/drilldown/root/drilldown-root.js +9 -0
- package/dist/filter-input/root/filter-input-root.d.ts +13 -0
- package/dist/filter-input/root/filter-input-root.js +2 -1
- package/dist/listbox/root/listbox-root.d.ts +16 -2
- package/dist/listbox/root/listbox-root.js +8 -4
- package/dist/meter/root/meter-root.js +1 -2
- package/dist/notifications/notification/notification.js +14 -1
- package/dist/option/among.d.ts +23 -0
- package/dist/option/among.js +2 -0
- package/dist/option/root/option-root.d.ts +1 -17
- package/dist/option/root/option-root.js +1 -4
- package/dist/radio/root/radio-root.d.ts +6 -0
- package/dist/radio/root/radio-root.js +5 -0
- package/dist/scatter-chart/root/scatter-chart-root.d.ts +5 -4
- package/dist/scatter-chart/root/scatter-chart-root.js +1 -0
- package/dist/select/root/select-root.d.ts +13 -2
- package/dist/select/root/select-root.js +3 -3
- package/dist/switch/root/switch-root.d.ts +6 -0
- package/dist/switch/root/switch-root.js +5 -0
- package/dist/table/column/table-column.d.ts +1 -10
- package/dist/table/column/table-column.js +1 -2
- package/dist/table/edge.d.ts +10 -0
- package/dist/table/edge.js +2 -0
- package/dist/tabs/root/tabs-root.d.ts +1 -0
- package/dist/tabs/root/tabs-root.js +13 -0
- package/dist/toggle/root/toggle-root.d.ts +6 -0
- package/dist/toggle/root/toggle-root.js +5 -0
- package/dist/tooltip/root/tooltip-root.js +1 -0
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +234 -16
- package/types/vue.d.ts +25 -11
- package/types/words.d.ts +14 -0
|
@@ -15,7 +15,8 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
15
15
|
* The composer clears only when no listener cancels the send, so the text is
|
|
16
16
|
* kept when you cancel a send that failed.
|
|
17
17
|
*
|
|
18
|
-
* **In a form, the draft is the composer's value**, submitted under `name
|
|
18
|
+
* **In a form, the draft is the composer's value**, submitted under `name`,
|
|
19
|
+
* and an empty draft submits an empty entry, as a native textarea does. A
|
|
19
20
|
* form reset puts back the `value` the markup declared, and a `<fieldset
|
|
20
21
|
* disabled>` turns the composer off. Sending is still `helix-send`, and it
|
|
21
22
|
* does not submit the form.
|
|
@@ -124,7 +124,7 @@ let HelixHeadlessChatComposer = class HelixHeadlessChatComposer extends HelixFor
|
|
|
124
124
|
super.updated(changed);
|
|
125
125
|
this.markEmpty();
|
|
126
126
|
if (changed.has('value')) {
|
|
127
|
-
this.internals.setFormValue(this.value
|
|
127
|
+
this.internals.setFormValue(this.value, this.value);
|
|
128
128
|
}
|
|
129
129
|
const field = this.partNamed('field');
|
|
130
130
|
if (field?.validity) {
|
|
@@ -152,6 +152,12 @@ export declare class HelixHeadlessCheckbox extends HelixHeadlessCheckbox_base {
|
|
|
152
152
|
* any server sent. See `HelixElement.toggleState`.
|
|
153
153
|
*/
|
|
154
154
|
private syncLabelPresence;
|
|
155
|
+
/**
|
|
156
|
+
* Ticks or clears the box, as `click()` does a native checkbox: passed to the control
|
|
157
|
+
* inside, so the one click a listener hears is the control's, after it has
|
|
158
|
+
* moved. See `HostClickController`.
|
|
159
|
+
*/
|
|
160
|
+
click(): void;
|
|
155
161
|
protected render(): TemplateResult;
|
|
156
162
|
}
|
|
157
163
|
declare global {
|
|
@@ -83,6 +83,11 @@ let HelixHeadlessCheckbox = class HelixHeadlessCheckbox extends MixinControllerC
|
|
|
83
83
|
this.toggleState('has-label', filled && !hidden);
|
|
84
84
|
this.partNamed('label')?.toggleAttribute('hidden', !filled);
|
|
85
85
|
}
|
|
86
|
+
click() {
|
|
87
|
+
if (!this.clicks.click()) {
|
|
88
|
+
super.click();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
86
91
|
render() {
|
|
87
92
|
const named = this.label === '' ? this.getAttribute('aria-label') : null;
|
|
88
93
|
return html `<label part="field">
|
|
@@ -59,6 +59,18 @@ declare const HelixHeadlessCombobox_base: (new (...args: any[]) => import("@fusi
|
|
|
59
59
|
* framework would re-render over an element the combobox inserted, so add the
|
|
60
60
|
* option in your event handler.
|
|
61
61
|
*
|
|
62
|
+
* **The options are an import of their own.** An option is the Option family's,
|
|
63
|
+
* which the combobox does not register, so a page that writes one imports it
|
|
64
|
+
* beside the combobox:
|
|
65
|
+
*
|
|
66
|
+
* ```js
|
|
67
|
+
* import '@fusion.dev/helix/combobox'
|
|
68
|
+
* import '@fusion.dev/helix/option'
|
|
69
|
+
* ```
|
|
70
|
+
*
|
|
71
|
+
* In development, a combobox holding an option nobody registered says so once
|
|
72
|
+
* the page has loaded, with the line to add.
|
|
73
|
+
*
|
|
62
74
|
* @since 0.1.0
|
|
63
75
|
* @status stable
|
|
64
76
|
* @category forms
|
|
@@ -4,13 +4,13 @@ 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, chosenValues, countLabel, customElement, DelayController, drawAutofill, drawStatus, formatWords, HelixCreateEvent, HelixFilterEvent, HelixFormElement, handedLocale, MixinControllerOpen, MixinControllerValue, MixinField, MixinTokens, onApple, PopoverController, reportValues, StatusController, submitImplicitly, UpgradeController, words, wordsLocale, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { AriaForwardController, afterLinkFollowed, auditChildren, chosenValues, countLabel, customElement, DelayController, drawAutofill, drawStatus, formatWords, HelixCreateEvent, HelixFilterEvent, HelixFormElement, handedLocale, MixinControllerOpen, MixinControllerValue, MixinField, MixinTokens, onApple, PopoverController, reportValues, StatusController, submitImplicitly, UpgradeController, 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, 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
|
-
import { optionsAmong } from '../../option/
|
|
13
|
+
import { optionsAmong } from '../../option/among.js';
|
|
14
14
|
import { HelixHeadlessTag } from '../../tag/root/tag-root.js';
|
|
15
15
|
import '../../listbox/index.js';
|
|
16
16
|
import '../../tag/root/index.js';
|
|
@@ -507,6 +507,7 @@ let HelixHeadlessCombobox = class HelixHeadlessCombobox extends MixinControllerO
|
|
|
507
507
|
}
|
|
508
508
|
connectedCallback() {
|
|
509
509
|
super.connectedCallback();
|
|
510
|
+
auditChildren(this, 'option', 'option', 'it suggests nothing');
|
|
510
511
|
this.addEventListener('helix-select', this.handleLinkFollowed);
|
|
511
512
|
this.watcher = new MutationObserver(() => this.requestUpdate());
|
|
512
513
|
this.watcher.observe(this, {
|
|
@@ -72,6 +72,18 @@ declare const HelixHeadlessCommand_base: (new (...args: any[]) => import("@fusio
|
|
|
72
72
|
* Aria's `Autocomplete` is. The commands are a `listbox` of `option`s. How many commands typing left, the empty text and
|
|
73
73
|
* the loading text are said in a polite live region.
|
|
74
74
|
*
|
|
75
|
+
* **The commands are an import of their own.** A command is an option, the
|
|
76
|
+
* Option family's, which the palette does not register, so a page that writes
|
|
77
|
+
* one imports it beside the palette:
|
|
78
|
+
*
|
|
79
|
+
* ```js
|
|
80
|
+
* import '@fusion.dev/helix/command'
|
|
81
|
+
* import '@fusion.dev/helix/option'
|
|
82
|
+
* ```
|
|
83
|
+
*
|
|
84
|
+
* In development, a palette holding an option nobody registered says so once
|
|
85
|
+
* the page has loaded, with the line to add.
|
|
86
|
+
*
|
|
75
87
|
* @since 0.1.0
|
|
76
88
|
* @status stable
|
|
77
89
|
* @category navigation
|
|
@@ -4,11 +4,11 @@ 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, drawStatus, HelixElement, HelixSelectEvent, MixinControllerOpen, matchesChord, StatusController, words, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { auditChildren, customElement, drawStatus, HelixElement, HelixSelectEvent, MixinControllerOpen, matchesChord, StatusController, words, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { nothing } from 'lit';
|
|
9
9
|
import { property, state } from 'lit/decorators.js';
|
|
10
10
|
import { html } from 'lit/static-html.js';
|
|
11
|
-
import { optionsAmong } from '../../option/
|
|
11
|
+
import { optionsAmong } from '../../option/among.js';
|
|
12
12
|
import '../../dialog/index.js';
|
|
13
13
|
import '../../listbox/index.js';
|
|
14
14
|
const says = (element, state) => {
|
|
@@ -145,6 +145,7 @@ let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpe
|
|
|
145
145
|
}
|
|
146
146
|
connectedCallback() {
|
|
147
147
|
super.connectedCallback();
|
|
148
|
+
auditChildren(this, 'option', 'option', 'the palette lists no commands');
|
|
148
149
|
this.addEventListener('helix-select', this.swallowOptionSelect);
|
|
149
150
|
this.addEventListener('keydown', this.handleAlternate, { capture: true });
|
|
150
151
|
this.addEventListener('keydown', this.handleBack);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type DataRow, HelixElement } from '@fusion.dev/helix-core';
|
|
2
2
|
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
|
-
import {
|
|
3
|
+
import type { HelixHeadlessTableColumn } from '../../table/column/table-column.js';
|
|
4
4
|
import '../../checkbox/index.js';
|
|
5
5
|
import '../../pagination/index.js';
|
|
6
6
|
declare const HelixHeadlessDataTable_base: (new (...args: any[]) => import("@fusion.dev/helix-core").CollectionElement) & typeof HelixElement;
|
|
@@ -131,6 +131,18 @@ declare const HelixHeadlessDataTable_base: (new (...args: any[]) => import("@fus
|
|
|
131
131
|
* Written on `DataTable`, `columns` has no effect, so watch for it when you
|
|
132
132
|
* move a table from `Table` to `DataTable`.
|
|
133
133
|
*
|
|
134
|
+
* **The columns are an import of their own.** A column is the Table family's
|
|
135
|
+
* `TableColumn`, which the data table does not register, so a page that writes
|
|
136
|
+
* one imports the Table entry beside the data table:
|
|
137
|
+
*
|
|
138
|
+
* ```js
|
|
139
|
+
* import '@fusion.dev/helix/data-table'
|
|
140
|
+
* import '@fusion.dev/helix/table'
|
|
141
|
+
* ```
|
|
142
|
+
*
|
|
143
|
+
* In development, a data table holding a column nobody registered says so once
|
|
144
|
+
* the page has loaded, with the line to add.
|
|
145
|
+
*
|
|
134
146
|
* @since 0.1.0
|
|
135
147
|
* @status stable
|
|
136
148
|
* @category data
|
|
@@ -5,14 +5,14 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
7
|
var HelixHeadlessDataTable_1;
|
|
8
|
-
import { anchorAt, arranged, auditServed, commaList, countLabel, customElement, cycle, deepActiveElement, deepContains, everyState, format, formatWords, gridMove, HelixActivateEvent, HelixElement, HelixExpandEvent, HelixFilterEvent, HelixFilteredEvent, HelixLoadMoreEvent, HelixNavigateEvent, HelixReorderEvent, HelixReorderedEvent, HelixSelectEvent, HelixSelectedEvent, HelixSortEvent, HelixSortedEvent, handedLocale, hostAria, inlineStep, inRowOrder, MixinCollection, markRegion, onAndroid, ordered, pageRows, parse, placeKey, ReorderController, rangeAdd, reportRuntime, requestChange, requestReorder, scrollingAncestor, scrollRoom, selectsAll, Typeahead, toggleEvery, treeArrow, typesCharacter, VirtualizeController, valueAt, window_, words, wordsLocale, writtenList, } from '@fusion.dev/helix-core';
|
|
8
|
+
import { anchorAt, arranged, auditChildren, auditServed, commaList, countLabel, customElement, cycle, deepActiveElement, deepContains, everyState, format, formatWords, gridMove, HelixActivateEvent, HelixElement, HelixExpandEvent, HelixFilterEvent, HelixFilteredEvent, HelixLoadMoreEvent, HelixNavigateEvent, HelixReorderEvent, HelixReorderedEvent, HelixSelectEvent, HelixSelectedEvent, HelixSortEvent, HelixSortedEvent, handedLocale, hostAria, inlineStep, inRowOrder, MixinCollection, markRegion, onAndroid, ordered, pageRows, parse, placeKey, ReorderController, rangeAdd, reportRuntime, requestChange, requestReorder, scrollingAncestor, scrollRoom, selectsAll, Typeahead, toggleEvery, treeArrow, typesCharacter, VirtualizeController, valueAt, whenHydrated, window_, words, wordsLocale, writtenList, } from '@fusion.dev/helix-core';
|
|
9
9
|
import { caretDown, caretRight, dotsSixVertical } from '@fusion.dev/helix-icons';
|
|
10
10
|
import { nothing } from 'lit';
|
|
11
11
|
import { property, state } from 'lit/decorators.js';
|
|
12
12
|
import { repeat } from 'lit/directives/repeat.js';
|
|
13
13
|
import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
|
|
14
14
|
import { html } from 'lit/static-html.js';
|
|
15
|
-
import { edgeOf } from '../../table/
|
|
15
|
+
import { edgeOf } from '../../table/edge.js';
|
|
16
16
|
import '../../checkbox/index.js';
|
|
17
17
|
import '../../pagination/index.js';
|
|
18
18
|
import { toggledOpen } from '../keys.js';
|
|
@@ -1021,19 +1021,21 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
|
|
|
1021
1021
|
const shown = this.shownColumns;
|
|
1022
1022
|
const android = onAndroid();
|
|
1023
1023
|
for (const column of this.tableColumns) {
|
|
1024
|
-
column
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
? '
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1024
|
+
whenHydrated(column, () => {
|
|
1025
|
+
column.movable = this.reorderable && column.field !== '';
|
|
1026
|
+
column.gridCell = true;
|
|
1027
|
+
column.tabStop = shown.indexOf(column) + offset === stop;
|
|
1028
|
+
column.sorted =
|
|
1029
|
+
current && column.canSort && column.field === current.field
|
|
1030
|
+
? current.direction === 'asc'
|
|
1031
|
+
? 'ascending'
|
|
1032
|
+
: 'descending'
|
|
1033
|
+
: undefined;
|
|
1034
|
+
column.sortWords =
|
|
1035
|
+
android && column.sorted
|
|
1036
|
+
? this.sortSaid(column.textContent?.trim() || column.field, column.sorted)
|
|
1037
|
+
: '';
|
|
1038
|
+
});
|
|
1037
1039
|
}
|
|
1038
1040
|
}
|
|
1039
1041
|
renderCell(column, row, picked, place, stop, header, id) {
|
|
@@ -1276,6 +1278,7 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
|
|
|
1276
1278
|
}
|
|
1277
1279
|
connectedCallback() {
|
|
1278
1280
|
super.connectedCallback();
|
|
1281
|
+
auditChildren(this, 'table-column', 'table', 'the table draws no columns');
|
|
1279
1282
|
this.addEventListener('click', this.handleHeadClick);
|
|
1280
1283
|
this.addEventListener('click', this.handleRowClick);
|
|
1281
1284
|
this.addEventListener('pointerdown', this.notePress, true);
|
|
@@ -33,6 +33,8 @@ declare const HelixHeadlessDateInput_base: (new (...args: any[]) => import("@fus
|
|
|
33
33
|
* digits or the arrow keys. Alt+Down opens the calendar from a segment. A
|
|
34
34
|
* `range` is two rows with a dash between them, the start and the end, as
|
|
35
35
|
* React Aria's DateRangePicker draws them, and `multiple` keeps the text.
|
|
36
|
+
* A range whose end is before its start reports `rangeUnderflow`, as Date
|
|
37
|
+
* Picker does, with `range-order-message`.
|
|
36
38
|
*
|
|
37
39
|
* Text that is not a date gives the form an empty value, as a native date
|
|
38
40
|
* field does, and `value` keeps the text so it can be corrected.
|
|
@@ -359,6 +361,11 @@ export declare class HelixHeadlessDateInput extends HelixHeadlessDateInput_base
|
|
|
359
361
|
* for the page's language, then the English.
|
|
360
362
|
*/
|
|
361
363
|
rangeLengthMessage: string;
|
|
364
|
+
/**
|
|
365
|
+
* The message shown when a range ends before it starts. `{start}` is
|
|
366
|
+
* replaced with the start, written the way the field writes dates.
|
|
367
|
+
*/
|
|
368
|
+
rangeOrderMessage: string;
|
|
362
369
|
/**
|
|
363
370
|
* The message shown when a date is after `max`. `{max}` is replaced with
|
|
364
371
|
* `max`, written the way the field writes dates.
|
|
@@ -47,6 +47,7 @@ let HelixHeadlessDateInput = class HelixHeadlessDateInput extends MixinSegmented
|
|
|
47
47
|
this.previousYearLabel = 'Previous year';
|
|
48
48
|
this.previousYearsLabel = 'Earlier years';
|
|
49
49
|
this.rangeLengthMessage = 'A range is two dates.';
|
|
50
|
+
this.rangeOrderMessage = 'Choose an end on or after {start}.';
|
|
50
51
|
this.rangeOverflowMessage = 'Choose a date on or before {max}.';
|
|
51
52
|
this.rangeUnderflowMessage = 'Choose a date on or after {min}.';
|
|
52
53
|
this.removeLabel = 'Remove {name}';
|
|
@@ -263,6 +264,13 @@ let HelixHeadlessDateInput = class HelixHeadlessDateInput extends MixinSegmented
|
|
|
263
264
|
if (this.mode === 'range' && held.length > 2) {
|
|
264
265
|
return this.rangeLengthMessage;
|
|
265
266
|
}
|
|
267
|
+
const [start, end] = held;
|
|
268
|
+
if (this.mode === 'range' && start !== undefined && end !== undefined && end < start) {
|
|
269
|
+
return [
|
|
270
|
+
'rangeUnderflow',
|
|
271
|
+
formatWords(this, 'rangeOrderMessage', { start: this.written(start) }),
|
|
272
|
+
];
|
|
273
|
+
}
|
|
266
274
|
return '';
|
|
267
275
|
}
|
|
268
276
|
renderPicker() {
|
|
@@ -547,6 +555,10 @@ __decorate([
|
|
|
547
555
|
words,
|
|
548
556
|
property({ attribute: 'range-length-message' })
|
|
549
557
|
], HelixHeadlessDateInput.prototype, "rangeLengthMessage", void 0);
|
|
558
|
+
__decorate([
|
|
559
|
+
words,
|
|
560
|
+
property({ attribute: 'range-order-message' })
|
|
561
|
+
], HelixHeadlessDateInput.prototype, "rangeOrderMessage", void 0);
|
|
550
562
|
__decorate([
|
|
551
563
|
words,
|
|
552
564
|
property({ attribute: 'range-overflow-message' })
|
|
@@ -48,7 +48,8 @@ declare const HelixHeadlessDatePicker_base: (new (...args: any[]) => import("@fu
|
|
|
48
48
|
* when Previous or Next is pressed. Neither moves past `min` or `max`, and the
|
|
49
49
|
* keys stop at them too. A value outside the bounds, on a barred day, or off
|
|
50
50
|
* `step` reports `rangeUnderflow`, `rangeOverflow`, `badInput` or
|
|
51
|
-
* `stepMismatch`, so a form does not submit it.
|
|
51
|
+
* `stepMismatch`, so a form does not submit it. A range set from a script
|
|
52
|
+
* whose end is before its start reports `rangeUnderflow` as well.
|
|
52
53
|
*
|
|
53
54
|
* @since 0.1.0
|
|
54
55
|
* @status stable
|
|
@@ -278,6 +279,11 @@ export declare class HelixHeadlessDatePicker extends HelixHeadlessDatePicker_bas
|
|
|
278
279
|
* Accessible name for the control that moves back a page, in the years.
|
|
279
280
|
*/
|
|
280
281
|
previousYearsLabel: string;
|
|
282
|
+
/**
|
|
283
|
+
* The validation message for a range that ends before it starts. `{start}`
|
|
284
|
+
* is replaced with the start, written as a date in `locale`.
|
|
285
|
+
*/
|
|
286
|
+
rangeOrderMessage: string;
|
|
281
287
|
/**
|
|
282
288
|
* The validation message for a date after `max`. `{max}` is replaced with
|
|
283
289
|
* `max`, written as a date in `locale`.
|
|
@@ -44,6 +44,7 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinPickerF
|
|
|
44
44
|
this.previousLabel = 'Previous month';
|
|
45
45
|
this.previousYearLabel = 'Previous year';
|
|
46
46
|
this.previousYearsLabel = 'Earlier years';
|
|
47
|
+
this.rangeOrderMessage = 'Choose an end on or after {start}.';
|
|
47
48
|
this.rangeOverflowMessage = 'Choose a date on or before {max}.';
|
|
48
49
|
this.focused = '';
|
|
49
50
|
this.choosing = false;
|
|
@@ -504,6 +505,13 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinPickerF
|
|
|
504
505
|
return [{ [fault[0]]: true }, fault[1]];
|
|
505
506
|
}
|
|
506
507
|
}
|
|
508
|
+
const [start, end] = held;
|
|
509
|
+
if (this.mode === 'range' && start !== undefined && end !== undefined && end < start) {
|
|
510
|
+
return [
|
|
511
|
+
{ rangeUnderflow: true },
|
|
512
|
+
formatWords(this, 'rangeOrderMessage', { start: this.spoken(start) }),
|
|
513
|
+
];
|
|
514
|
+
}
|
|
507
515
|
return undefined;
|
|
508
516
|
}
|
|
509
517
|
get failure() {
|
|
@@ -855,6 +863,10 @@ __decorate([
|
|
|
855
863
|
words,
|
|
856
864
|
property({ attribute: 'previous-years-label' })
|
|
857
865
|
], HelixHeadlessDatePicker.prototype, "previousYearsLabel", void 0);
|
|
866
|
+
__decorate([
|
|
867
|
+
words,
|
|
868
|
+
property({ attribute: 'range-order-message' })
|
|
869
|
+
], HelixHeadlessDatePicker.prototype, "rangeOrderMessage", void 0);
|
|
858
870
|
__decorate([
|
|
859
871
|
words,
|
|
860
872
|
property({ attribute: 'range-overflow-message' })
|
|
@@ -158,8 +158,17 @@ let HelixHeadlessDrilldown = class HelixHeadlessDrilldown extends MixinCollectio
|
|
|
158
158
|
willUpdate(changed) {
|
|
159
159
|
super.willUpdate(changed);
|
|
160
160
|
if (changed.has('page') && (this.drilldown.page?.value ?? '') !== this.page) {
|
|
161
|
+
const leaving = this.drilldown.page;
|
|
162
|
+
const focused = this.contains(document.activeElement);
|
|
161
163
|
this.showPage(this.page);
|
|
162
164
|
this.page = this.drilldown.page?.value ?? '';
|
|
165
|
+
if (focused) {
|
|
166
|
+
this.updateComplete.then(() => {
|
|
167
|
+
const items = this.items;
|
|
168
|
+
const to = leaving && items.includes(leaving) ? leaving : items[0];
|
|
169
|
+
to?.focusItem();
|
|
170
|
+
});
|
|
171
|
+
}
|
|
163
172
|
}
|
|
164
173
|
hostAria(this.internals, {
|
|
165
174
|
label: this.navigation ? this.label : '',
|
|
@@ -128,6 +128,19 @@ declare const HelixHeadlessFilterInput_base: (new (...args: any[]) => import("@f
|
|
|
128
128
|
* filter goes. Set `chips` and the field adds, updates, and removes those
|
|
129
129
|
* chips itself, from every `helix-facet` you do not cancel.
|
|
130
130
|
*
|
|
131
|
+
* **The suggestions you write are an import of their own.** A suggestion is an
|
|
132
|
+
* option, the Option family's, which the field does not register, so a page
|
|
133
|
+
* that slots one imports it beside the field:
|
|
134
|
+
*
|
|
135
|
+
* ```js
|
|
136
|
+
* import '@fusion.dev/helix/filter-input'
|
|
137
|
+
* import '@fusion.dev/helix/option'
|
|
138
|
+
* ```
|
|
139
|
+
*
|
|
140
|
+
* In development, a field holding an option nobody registered says so once the
|
|
141
|
+
* page has loaded, with the line to add. The options `source` gives are drawn
|
|
142
|
+
* as this Option too, and need the same import.
|
|
143
|
+
*
|
|
131
144
|
* @since 0.1.0
|
|
132
145
|
* @status stable
|
|
133
146
|
* @category forms
|
|
@@ -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, countLabel, customElement, DelayController, drawIcon, drawStatus, formatWords, HelixFacetEvent, HelixFormElement, holdCaret, MixinControllerOpen, MixinControllerValue, MixinField, MixinTokens, PopoverController, reportRuntime, restoreValue, StatusController, scrollItemIntoView, words, wordsLocale, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { AriaForwardController, auditChildren, countLabel, customElement, DelayController, drawIcon, drawStatus, formatWords, HelixFacetEvent, HelixFormElement, holdCaret, MixinControllerOpen, MixinControllerValue, MixinField, MixinTokens, PopoverController, reportRuntime, restoreValue, StatusController, scrollItemIntoView, words, wordsLocale, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { x } from '@fusion.dev/helix-icons';
|
|
9
9
|
import { html, nothing } from 'lit';
|
|
10
10
|
import { property, state } from 'lit/decorators.js';
|
|
@@ -552,6 +552,7 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
|
|
|
552
552
|
}
|
|
553
553
|
connectedCallback() {
|
|
554
554
|
super.connectedCallback();
|
|
555
|
+
auditChildren(this, 'option', 'option', 'the field suggests nothing');
|
|
555
556
|
this.addEventListener('helix-dismiss', this.handleDismiss, { capture: true });
|
|
556
557
|
this.addEventListener(HelixFacetEvent.eventName, this.handleFacet);
|
|
557
558
|
this.addEventListener('helix-select', this.handleSourcedSelect);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AriaForwardController, type DataOptionItem, DisabledCascadeController, HelixFormElement, UpgradeController } from '@fusion.dev/helix-core';
|
|
2
2
|
import { nothing, type PropertyValues, type TemplateResult } from 'lit';
|
|
3
|
-
import { HelixHeadlessOption } from '../../option/root/option-root.js';
|
|
3
|
+
import type { HelixHeadlessOption } from '../../option/root/option-root.js';
|
|
4
4
|
declare const HelixHeadlessListbox_base: (new (...args: any[]) => import("@fusion.dev/helix-core").CollectionElement) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerValueInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinFieldInterface) & typeof HelixFormElement;
|
|
5
5
|
/**
|
|
6
6
|
* @summary A list of options, from which you choose one or several.
|
|
@@ -76,6 +76,19 @@ declare const HelixHeadlessListbox_base: (new (...args: any[]) => import("@fusio
|
|
|
76
76
|
* filter narrows the list, under `layout="grid"`, or while the list is
|
|
77
77
|
* disabled.
|
|
78
78
|
*
|
|
79
|
+
* **The options are an import of their own.** An option you write is the Option
|
|
80
|
+
* family's, which the list does not register, so a page that writes one imports
|
|
81
|
+
* it beside the list:
|
|
82
|
+
*
|
|
83
|
+
* ```js
|
|
84
|
+
* import '@fusion.dev/helix/listbox'
|
|
85
|
+
* import '@fusion.dev/helix/option'
|
|
86
|
+
* ```
|
|
87
|
+
*
|
|
88
|
+
* In development, a list holding an option nobody registered says so once the
|
|
89
|
+
* page has loaded, with the line to add. The options `items` draws are this
|
|
90
|
+
* Option too, and need the same import.
|
|
91
|
+
*
|
|
79
92
|
* @since 0.1.0
|
|
80
93
|
* @status stable
|
|
81
94
|
* @category forms
|
|
@@ -218,7 +231,8 @@ export declare class HelixHeadlessListbox extends HelixHeadlessListbox_base {
|
|
|
218
231
|
*
|
|
219
232
|
* **The list needs a height to scroll in**: the styled tier's
|
|
220
233
|
* `--listbox-max-block-size`, or a `max-block-size` on `::part(items)` in the
|
|
221
|
-
* headless tier.
|
|
234
|
+
* headless tier. The options drawn are this tier's Option, which the page
|
|
235
|
+
* imports, as it imports the options it writes.
|
|
222
236
|
* A server sends the list empty, and the options are drawn once it hydrates.
|
|
223
237
|
*/
|
|
224
238
|
items: readonly DataOptionItem[] | undefined;
|
|
@@ -4,12 +4,12 @@ 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, chosenValues, columnStep, countLabel, customElement, DelayController, DisabledCascadeController, drawAutofill, endInSight, filterKeeps, formatWords, HelixFilterEvent, HelixFormElement, HelixLoadMoreEvent, hostAria, inlineStep, MixinCollection, MixinControllerValue, MixinField, markMatches, OptionWindowController, onApple, pageRows, rangeAdd, reportValues, requestChange, scrollItemIntoView, selectsAll, Typeahead, toggleEvery, typesCharacter, UpgradeController, words, wordsLocale, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { AriaForwardController, auditChildren, centerInView, chosenValues, columnStep, countLabel, customElement, DelayController, DisabledCascadeController, drawAutofill, endInSight, filterKeeps, formatWords, HelixFilterEvent, HelixFormElement, HelixLoadMoreEvent, hostAria, inlineStep, MixinCollection, MixinControllerValue, MixinField, markMatches, OptionWindowController, onApple, pageRows, rangeAdd, reportValues, requestChange, scrollItemIntoView, selectsAll, Typeahead, toggleEvery, typesCharacter, UpgradeController, 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';
|
|
11
11
|
import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
|
|
12
|
-
import {
|
|
12
|
+
import { isOption } from '../../option/among.js';
|
|
13
13
|
const FIELD_MOVES = new Set(['ArrowDown', 'ArrowUp', 'PageDown', 'PageUp']);
|
|
14
14
|
const MOVES = new Set([...FIELD_MOVES, 'End', 'Home']);
|
|
15
15
|
const PAGE = 10;
|
|
@@ -306,7 +306,7 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
|
|
|
306
306
|
return this.multiple ? 'select-multiple' : 'select-one';
|
|
307
307
|
}
|
|
308
308
|
get options() {
|
|
309
|
-
return this.among(
|
|
309
|
+
return this.among(isOption, this.children);
|
|
310
310
|
}
|
|
311
311
|
among(is, nodes) {
|
|
312
312
|
const found = [];
|
|
@@ -421,7 +421,7 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
|
|
|
421
421
|
option.selected = chosen.has(option.value);
|
|
422
422
|
}
|
|
423
423
|
for (const group of this.groups) {
|
|
424
|
-
const inside = this.among(
|
|
424
|
+
const inside = this.among(isOption, group.children);
|
|
425
425
|
group.hidden = inside.length > 0 && inside.every((option) => option.hidden);
|
|
426
426
|
}
|
|
427
427
|
for (const rule of this.among((node) => node.localName.endsWith('-option-separator'), this.children)) {
|
|
@@ -479,6 +479,7 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
|
|
|
479
479
|
this.addEventListener('helix-select', this.handleSelection);
|
|
480
480
|
this.addEventListener('keydown', this.handleKeydown);
|
|
481
481
|
this.addEventListener('click', this.handleClick, true);
|
|
482
|
+
auditChildren(this, 'option', 'option', 'the list offers no choices');
|
|
482
483
|
}
|
|
483
484
|
disconnectedCallback() {
|
|
484
485
|
super.disconnectedCallback();
|
|
@@ -500,6 +501,9 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
|
|
|
500
501
|
if (this.childrenKnown) {
|
|
501
502
|
this.seedDeclared();
|
|
502
503
|
}
|
|
504
|
+
if (changed.has('items') && this.items) {
|
|
505
|
+
auditChildren(this, 'option', 'option', 'the items draw no choices', true);
|
|
506
|
+
}
|
|
503
507
|
if (this.childrenKnown) {
|
|
504
508
|
this.place();
|
|
505
509
|
this.tell(changed);
|
|
@@ -83,8 +83,7 @@ let HelixHeadlessMeter = class HelixHeadlessMeter extends HelixElement {
|
|
|
83
83
|
this.internals.ariaValueMin = String(min);
|
|
84
84
|
this.internals.ariaValueMax = String(max);
|
|
85
85
|
this.internals.ariaValueNow = String(this.reading);
|
|
86
|
-
this.internals.ariaValueText =
|
|
87
|
-
this.valueText || this.formatOptions !== undefined || this.beyond ? this.text : null;
|
|
86
|
+
this.internals.ariaValueText = this.text;
|
|
88
87
|
const band = this.computedBand;
|
|
89
88
|
const beyond = this.beyond;
|
|
90
89
|
this.toggleState('fair', band === 'fair');
|
|
@@ -31,8 +31,14 @@ let HelixHeadlessNotification = class HelixHeadlessNotification extends MixinDis
|
|
|
31
31
|
event.stopPropagation();
|
|
32
32
|
if (this.dismiss()) {
|
|
33
33
|
const next = deepContains(this, deepActiveElement()) ? this.neighbor() : null;
|
|
34
|
+
const holder = this.parentElement;
|
|
34
35
|
this.remove();
|
|
35
36
|
next?.focus();
|
|
37
|
+
if (next && next.getRootNode().activeElement !== next) {
|
|
38
|
+
void Promise.resolve()
|
|
39
|
+
.then(() => holder?.updateComplete)
|
|
40
|
+
.then(() => next.focus());
|
|
41
|
+
}
|
|
36
42
|
}
|
|
37
43
|
};
|
|
38
44
|
}
|
|
@@ -154,7 +160,14 @@ let HelixHeadlessNotification = class HelixHeadlessNotification extends MixinDis
|
|
|
154
160
|
? feed.items
|
|
155
161
|
: [...(this.parentElement?.children ?? [])].filter((one) => 'isNotification' in one);
|
|
156
162
|
const at = rows.indexOf(this);
|
|
157
|
-
const
|
|
163
|
+
const group = this.parentElement;
|
|
164
|
+
const promoted = group && 'isNotificationGroup' in group && this.assignedSlot !== null
|
|
165
|
+
? [...group.children].find((one) => one !== this && 'isNotification' in one)
|
|
166
|
+
: undefined;
|
|
167
|
+
const drawn = (row) => !('isNotificationGroup' in (row.parentElement ?? {})) ||
|
|
168
|
+
row.assignedSlot !== null ||
|
|
169
|
+
row === promoted;
|
|
170
|
+
const order = [...rows.slice(at + 1), ...rows.slice(0, Math.max(at, 0)).reverse()].filter(drawn);
|
|
158
171
|
for (const row of order) {
|
|
159
172
|
const target = row.shadowRoot?.querySelector('[part="item"]');
|
|
160
173
|
if (target) {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { HelixHeadlessOption } from './root/option-root.js';
|
|
2
|
+
/**
|
|
3
|
+
* Whether an element is an option, by its shape.
|
|
4
|
+
*
|
|
5
|
+
* **Not by `instanceof`.** A tag is defined once, by whichever copy of this
|
|
6
|
+
* class registered it first, and a second copy in the same document tests
|
|
7
|
+
* against a class no element on the page has: every option failed it, and a
|
|
8
|
+
* select said "Choose" over its value. Two copies are a consumer with a
|
|
9
|
+
* duplicated dependency, or the browser specs, where each family's bundle
|
|
10
|
+
* carries its own and they share one page. An option nobody registered has no
|
|
11
|
+
* such shape, and is no option until its entry is imported.
|
|
12
|
+
*/
|
|
13
|
+
export declare const isOption: (element: Element) => element is HelixHeadlessOption;
|
|
14
|
+
/**
|
|
15
|
+
* The options among some elements, reaching one level into a group.
|
|
16
|
+
*
|
|
17
|
+
* What a control holding options reads its words from, off its own children
|
|
18
|
+
* rather than off the listbox they are slotted into: the slot exists only once a
|
|
19
|
+
* render has made it, so the listbox has none on a first render, and on a page a
|
|
20
|
+
* server rendered the `slotchange` that would correct it fires before anything
|
|
21
|
+
* listens.
|
|
22
|
+
*/
|
|
23
|
+
export declare const optionsAmong: (elements: readonly Element[]) => HelixHeadlessOption[];
|
|
@@ -1,22 +1,6 @@
|
|
|
1
1
|
import { HelixElement, type LinkModifiers } from '@fusion.dev/helix-core';
|
|
2
2
|
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
|
-
|
|
4
|
-
* The options among some elements, reaching one level into a group.
|
|
5
|
-
*
|
|
6
|
-
* What a control holding options reads its words from, off its own children
|
|
7
|
-
* rather than off the listbox they are slotted into: the slot exists only once a
|
|
8
|
-
* render has made it, so the listbox has none on a first render, and on a page a
|
|
9
|
-
* server rendered the `slotchange` that would correct it fires before anything
|
|
10
|
-
* listens.
|
|
11
|
-
*
|
|
12
|
-
* **An option by its shape, not by `instanceof`.** A tag is defined once, by
|
|
13
|
-
* whichever copy of this class registered it first, and a second copy in the
|
|
14
|
-
* same document tests against a class no element on the page has: every option
|
|
15
|
-
* failed it, and a select said "Choose" over its value. Two copies are a
|
|
16
|
-
* consumer with a duplicated dependency, or the browser specs, where each
|
|
17
|
-
* family's bundle carries its own and they share one page.
|
|
18
|
-
*/
|
|
19
|
-
export declare const optionsAmong: (elements: readonly Element[]) => HelixHeadlessOption[];
|
|
3
|
+
export { isOption, optionsAmong } from '../among.js';
|
|
20
4
|
/**
|
|
21
5
|
* @summary One choice in a listbox.
|
|
22
6
|
*
|
|
@@ -7,10 +7,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
import { customElement, followLink, HelixElement, HelixSelectEvent, hostAria, slotFilled, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html, nothing } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
|
-
export
|
|
11
|
-
const isOption = (element) => 'text' in element && 'value' in element && 'selected' in element;
|
|
12
|
-
return elements.flatMap((element) => isOption(element) ? [element] : [...element.children].filter(isOption));
|
|
13
|
-
};
|
|
10
|
+
export { isOption, optionsAmong } from '../among.js';
|
|
14
11
|
let HelixHeadlessOption = class HelixHeadlessOption extends HelixElement {
|
|
15
12
|
constructor() {
|
|
16
13
|
super(...arguments);
|
|
@@ -106,6 +106,12 @@ export declare class HelixHeadlessRadio extends HelixHeadlessRadio_base {
|
|
|
106
106
|
* re-renders nothing.
|
|
107
107
|
*/
|
|
108
108
|
protected firstUpdated(changed: PropertyValues): void;
|
|
109
|
+
/**
|
|
110
|
+
* Chooses the radio, as `click()` does a native radio: passed to the control
|
|
111
|
+
* inside, so the one click a listener hears is the control's, after it has
|
|
112
|
+
* moved. See `HostClickController`.
|
|
113
|
+
*/
|
|
114
|
+
click(): void;
|
|
109
115
|
protected render(): TemplateResult;
|
|
110
116
|
}
|
|
111
117
|
declare global {
|
|
@@ -22,6 +22,11 @@ let HelixHeadlessRadio = class HelixHeadlessRadio extends MixinSelectableItem(He
|
|
|
22
22
|
super.firstUpdated(changed);
|
|
23
23
|
this.finishHydration();
|
|
24
24
|
}
|
|
25
|
+
click() {
|
|
26
|
+
if (!this.clicks.click()) {
|
|
27
|
+
super.click();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
25
30
|
render() {
|
|
26
31
|
return html `<button
|
|
27
32
|
part="item"
|