@fusion.dev/helix-headless 0.155.0 → 0.156.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 +1787 -92
- package/dist/canvas/node/canvas-node.js +1 -1
- package/dist/canvas/root/canvas-root.d.ts +17 -2
- package/dist/canvas/root/canvas-root.js +23 -6
- package/dist/chat/composer/chat-composer.js +1 -1
- package/dist/chat/root/chat-root.d.ts +14 -1
- package/dist/chat/root/chat-root.js +14 -4
- package/dist/combobox/root/combobox-root.d.ts +10 -0
- package/dist/combobox/root/combobox-root.js +18 -3
- package/dist/command/root/command-root.d.ts +116 -1
- package/dist/command/root/command-root.js +124 -15
- package/dist/data-table/root/data-table-root.d.ts +25 -0
- package/dist/data-table/root/data-table-root.js +13 -3
- package/dist/date-input/root/date-input-root.d.ts +32 -1
- package/dist/date-input/root/date-input-root.js +7 -1
- package/dist/element-events.generated.js +1 -0
- package/dist/graph/root/graph-root.d.ts +13 -0
- package/dist/graph/root/graph-root.js +16 -0
- package/dist/list/root/list-root.d.ts +74 -1
- package/dist/list/root/list-root.js +92 -4
- package/dist/list/selection.js +3 -3
- package/dist/map/root/map-root.d.ts +6 -0
- package/dist/map/root/map-root.js +8 -1
- package/dist/number-input/root/number-input-root.d.ts +63 -6
- package/dist/number-input/root/number-input-root.js +96 -11
- package/dist/object-inspector/root/object-inspector-root.d.ts +36 -1
- package/dist/object-inspector/root/object-inspector-root.js +26 -2
- package/dist/option/root/option-root.d.ts +32 -0
- package/dist/option/root/option-root.js +44 -5
- package/dist/outline/root/outline-root.d.ts +6 -2
- package/dist/outline/root/outline-root.js +7 -3
- package/dist/table/root/table-root.d.ts +20 -0
- package/dist/table/root/table-root.js +13 -3
- package/dist/time-input/root/time-input-root.d.ts +32 -1
- package/dist/time-input/root/time-input-root.js +9 -2
- package/dist/time-picker/root/time-picker-root.d.ts +63 -1
- package/dist/time-picker/root/time-picker-root.js +58 -7
- package/dist/timeline/item/timeline-item.d.ts +25 -0
- package/dist/timeline/item/timeline-item.js +27 -2
- package/dist/video/root/keys.d.ts +6 -2
- package/dist/video/root/keys.js +18 -1
- package/dist/video/root/video-root.d.ts +60 -1
- package/dist/video/root/video-root.js +70 -2
- package/dist/video-playlist/item/video-playlist-item.d.ts +15 -1
- package/dist/video-playlist/item/video-playlist-item.js +7 -0
- package/dist/video-playlist/root/video-playlist-root.d.ts +43 -4
- package/dist/video-playlist/root/video-playlist-root.js +69 -2
- package/dist/virtual-list/root/virtual-list-root.d.ts +8 -0
- package/dist/virtual-list/root/virtual-list-root.js +3 -0
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +2901 -232
- package/types/vue.d.ts +824 -58
- package/types/words.d.ts +110 -0
|
@@ -8,18 +8,31 @@ import { customElement, HelixElement, HelixSelectEvent, MixinControllerOpen, mat
|
|
|
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/root/option-root.js';
|
|
11
12
|
import '../../dialog/index.js';
|
|
12
13
|
import '../../listbox/index.js';
|
|
14
|
+
const says = (element, state) => {
|
|
15
|
+
try {
|
|
16
|
+
return element?.matches(`:state(${state})`) ?? false;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
13
22
|
let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpen(HelixElement) {
|
|
14
23
|
constructor() {
|
|
15
24
|
super(...arguments);
|
|
16
25
|
this.emptyText = 'No commands';
|
|
17
26
|
this.filterDelay = 0;
|
|
18
27
|
this.hotkey = 'mod+k';
|
|
28
|
+
this.inline = false;
|
|
29
|
+
this.items = undefined;
|
|
19
30
|
this.label = 'Commands';
|
|
20
31
|
this.loadError = '';
|
|
21
32
|
this.loading = false;
|
|
22
33
|
this.loadingText = 'Loading';
|
|
34
|
+
this.page = '';
|
|
35
|
+
this.trail = [];
|
|
23
36
|
this.placeholder = 'Search commands';
|
|
24
37
|
this.resultsLabel = '{count, plural, one {# command} other {# commands}}';
|
|
25
38
|
this.footed = false;
|
|
@@ -28,6 +41,10 @@ let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpe
|
|
|
28
41
|
return;
|
|
29
42
|
}
|
|
30
43
|
event.preventDefault();
|
|
44
|
+
if (this.inline) {
|
|
45
|
+
this.searchField?.focus();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
31
48
|
this.requestOpen(!this.open);
|
|
32
49
|
};
|
|
33
50
|
this.handleChange = (event) => {
|
|
@@ -37,10 +54,26 @@ let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpe
|
|
|
37
54
|
if (!this.dispatchEvent(new HelixSelectEvent({ value }))) {
|
|
38
55
|
return;
|
|
39
56
|
}
|
|
57
|
+
const opens = (this.listbox?.options ?? []).find((option) => option.value === value)?.opens;
|
|
58
|
+
if (opens) {
|
|
59
|
+
this.goTo([...this.trail, this.page], opens);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
40
62
|
this.requestOpen(false);
|
|
41
63
|
};
|
|
64
|
+
this.handleBack = (event) => {
|
|
65
|
+
if (event.key !== 'Backspace' ||
|
|
66
|
+
this.page === '' ||
|
|
67
|
+
event.composedPath()[0] !== this.searchField ||
|
|
68
|
+
this.searchField?.value !== '') {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
event.preventDefault();
|
|
72
|
+
const trail = [...this.trail];
|
|
73
|
+
this.goTo(trail, trail.pop() ?? '');
|
|
74
|
+
};
|
|
42
75
|
this.handleAlternate = (event) => {
|
|
43
|
-
if (!(event.metaKey || event.ctrlKey) || !this.open) {
|
|
76
|
+
if (!(event.metaKey || event.ctrlKey) || !(this.open || this.inline)) {
|
|
44
77
|
return;
|
|
45
78
|
}
|
|
46
79
|
if (event instanceof KeyboardEvent && event.key !== 'Enter') {
|
|
@@ -74,11 +107,32 @@ let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpe
|
|
|
74
107
|
}
|
|
75
108
|
this.requestOpen(false);
|
|
76
109
|
};
|
|
110
|
+
this.handleFilter = () => {
|
|
111
|
+
void Promise.resolve()
|
|
112
|
+
.then(() => this.listbox?.updateComplete)
|
|
113
|
+
.then(() => this.markStatus());
|
|
114
|
+
};
|
|
77
115
|
}
|
|
78
116
|
static { this.words = 'helix-command'; }
|
|
79
117
|
get listbox() {
|
|
80
118
|
return this.partNamed('listbox');
|
|
81
119
|
}
|
|
120
|
+
get searchField() {
|
|
121
|
+
return (this.listbox?.renderRoot?.querySelector('[part="search-field"]') ?? null);
|
|
122
|
+
}
|
|
123
|
+
goTo(trail, page) {
|
|
124
|
+
this.trail = trail;
|
|
125
|
+
this.page = page;
|
|
126
|
+
this.listbox?.requestFilter('');
|
|
127
|
+
void this.updateComplete.then(() => this.searchField?.focus());
|
|
128
|
+
}
|
|
129
|
+
get listName() {
|
|
130
|
+
if (this.page === '') {
|
|
131
|
+
return this.label;
|
|
132
|
+
}
|
|
133
|
+
const opener = optionsAmong(this.knownChildren).find((option) => option.opens === this.page);
|
|
134
|
+
return opener?.text || this.label;
|
|
135
|
+
}
|
|
82
136
|
get activeOption() {
|
|
83
137
|
return (this.listbox?.options.find((option) => option.active && !option.hidden) ??
|
|
84
138
|
this.querySelector('[data-active]'));
|
|
@@ -87,6 +141,7 @@ let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpe
|
|
|
87
141
|
super.connectedCallback();
|
|
88
142
|
this.addEventListener('helix-select', this.swallowOptionSelect);
|
|
89
143
|
this.addEventListener('keydown', this.handleAlternate, { capture: true });
|
|
144
|
+
this.addEventListener('keydown', this.handleBack);
|
|
90
145
|
this.addEventListener('click', this.handleAlternate, { capture: true });
|
|
91
146
|
document.addEventListener('keydown', this.handleHotkey);
|
|
92
147
|
}
|
|
@@ -94,11 +149,16 @@ let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpe
|
|
|
94
149
|
super.disconnectedCallback();
|
|
95
150
|
this.removeEventListener('helix-select', this.swallowOptionSelect);
|
|
96
151
|
this.removeEventListener('keydown', this.handleAlternate, { capture: true });
|
|
152
|
+
this.removeEventListener('keydown', this.handleBack);
|
|
97
153
|
this.removeEventListener('click', this.handleAlternate, { capture: true });
|
|
98
154
|
document.removeEventListener('keydown', this.handleHotkey);
|
|
99
155
|
}
|
|
100
156
|
willUpdate(changed) {
|
|
101
157
|
super.willUpdate(changed);
|
|
158
|
+
if (changed.get('open') === true && !this.open) {
|
|
159
|
+
this.trail = [];
|
|
160
|
+
this.page = '';
|
|
161
|
+
}
|
|
102
162
|
if (this.childrenKnown) {
|
|
103
163
|
this.footed = this.knownChildren.some((child) => child.slot === 'footer');
|
|
104
164
|
}
|
|
@@ -107,15 +167,32 @@ let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpe
|
|
|
107
167
|
super.firstUpdated(changed);
|
|
108
168
|
this.finishHydration();
|
|
109
169
|
}
|
|
170
|
+
markStatus() {
|
|
171
|
+
const list = this.listbox;
|
|
172
|
+
const options = list?.options ?? [];
|
|
173
|
+
const shown = options.some((option) => !option.hidden);
|
|
174
|
+
const settled = list !== null && !this.loading;
|
|
175
|
+
this.toggleState('loading', this.loading);
|
|
176
|
+
this.toggleState('error', Boolean(this.loadError));
|
|
177
|
+
if (this.items !== undefined) {
|
|
178
|
+
this.toggleState('empty', settled && says(list, 'empty'));
|
|
179
|
+
this.toggleState('no-results', settled && says(list, 'no-results'));
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
this.toggleState('empty', settled && !shown);
|
|
183
|
+
this.toggleState('no-results', settled && list.filter !== '' && options.length > 0 && !shown);
|
|
184
|
+
}
|
|
185
|
+
fills(name) {
|
|
186
|
+
return this.querySelector?.(`:scope > [slot="${name}"]`) != null;
|
|
187
|
+
}
|
|
110
188
|
updated(changed) {
|
|
111
189
|
super.updated?.(changed);
|
|
190
|
+
void (this.listbox?.updateComplete ?? Promise.resolve()).then(() => this.markStatus());
|
|
112
191
|
if (!changed.has('open')) {
|
|
113
192
|
return;
|
|
114
193
|
}
|
|
115
194
|
if (this.open) {
|
|
116
|
-
this.
|
|
117
|
-
?.renderRoot?.querySelector('[part="search-field"]')
|
|
118
|
-
?.focus();
|
|
195
|
+
this.searchField?.focus();
|
|
119
196
|
}
|
|
120
197
|
else {
|
|
121
198
|
this.listbox?.requestFilter('');
|
|
@@ -133,13 +210,25 @@ let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpe
|
|
|
133
210
|
}
|
|
134
211
|
}}
|
|
135
212
|
>
|
|
136
|
-
|
|
213
|
+
${this.renderListbox()}
|
|
214
|
+
<!-- Handed on only while something fills it, as the list's slots are,
|
|
215
|
+
so an empty header draws nothing in the dialog. -->
|
|
216
|
+
<slot
|
|
217
|
+
name="header"
|
|
218
|
+
slot=${this.fills('header') ? 'header' : nothing}
|
|
219
|
+
@slotchange=${() => this.requestUpdate()}
|
|
220
|
+
></slot>
|
|
221
|
+
${this.renderFooter('footer')}
|
|
222
|
+
</${this.tag('dialog')}>`;
|
|
223
|
+
}
|
|
224
|
+
renderListbox() {
|
|
225
|
+
return html `<${this.tag('listbox')}
|
|
137
226
|
part="listbox"
|
|
138
227
|
exportparts="empty: listbox-empty, error: listbox-error, feedback: listbox-feedback,
|
|
139
228
|
filter-icon: listbox-filter-icon, filter-mark: listbox-filter-mark, hint: listbox-hint,
|
|
140
229
|
items: listbox-items, label: listbox-label, live: listbox-live, loading: listbox-loading,
|
|
141
230
|
search: listbox-search, search-field: listbox-search-field"
|
|
142
|
-
aria-label=${this.
|
|
231
|
+
aria-label=${this.listName}
|
|
143
232
|
empty-text=${this.emptyText}
|
|
144
233
|
filter-delay=${this.filterDelay}
|
|
145
234
|
filter-label=${this.placeholder}
|
|
@@ -149,12 +238,14 @@ let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpe
|
|
|
149
238
|
result-label=${this.resultLabel ?? ''}
|
|
150
239
|
results-label=${this.resultsLabel}
|
|
151
240
|
?loading=${this.loading}
|
|
241
|
+
.items=${this.items}
|
|
152
242
|
filterable
|
|
153
243
|
@helix-before-change=${this.handleChange}
|
|
244
|
+
@helix-filter=${this.handleFilter}
|
|
154
245
|
@change=${this.stopInner}
|
|
155
246
|
@input=${this.stopInner}
|
|
156
247
|
>
|
|
157
|
-
|
|
248
|
+
${this.page === '' ? html `<slot></slot>` : html `<slot name=${`page:${this.page}`}></slot>`}
|
|
158
249
|
<!-- Handed on with the text as the fallback, for the reason Combobox
|
|
159
250
|
gives: the list's own fallback never shows behind a passed slot. -->
|
|
160
251
|
<slot name="empty" slot="empty">${this.emptyText}</slot>
|
|
@@ -164,21 +255,30 @@ let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpe
|
|
|
164
255
|
list's own fallback, which is no-results-text or empty-text. -->
|
|
165
256
|
<slot
|
|
166
257
|
name="no-results"
|
|
167
|
-
slot=${this.
|
|
258
|
+
slot=${this.fills('no-results') ? 'no-results' : nothing}
|
|
168
259
|
@slotchange=${() => this.requestUpdate()}
|
|
169
260
|
></slot>
|
|
170
|
-
</${this.tag('listbox')}>
|
|
171
|
-
<div ?hidden=${!this.footed} part="footer" slot="footer">
|
|
172
261
|
<slot
|
|
173
|
-
name="
|
|
174
|
-
|
|
262
|
+
name="filter-icon"
|
|
263
|
+
slot=${this.fills('filter-icon') ? 'filter-icon' : nothing}
|
|
264
|
+
@slotchange=${() => this.requestUpdate()}
|
|
265
|
+
></slot>
|
|
266
|
+
</${this.tag('listbox')}>`;
|
|
267
|
+
}
|
|
268
|
+
renderFooter(slot) {
|
|
269
|
+
return html `<div ?hidden=${!this.footed} part="footer" slot=${slot}>
|
|
270
|
+
<slot
|
|
271
|
+
name="footer"
|
|
272
|
+
@slotchange=${(event) => {
|
|
175
273
|
this.footed = event.target.assignedNodes().length > 0;
|
|
176
274
|
}}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
</${this.tag('dialog')}>`;
|
|
275
|
+
></slot>
|
|
276
|
+
</div>`;
|
|
180
277
|
}
|
|
181
278
|
render() {
|
|
279
|
+
if (this.inline) {
|
|
280
|
+
return html `<slot name="header"></slot>${this.renderListbox()}${this.renderFooter(nothing)}`;
|
|
281
|
+
}
|
|
182
282
|
return html `${this.renderDialog()}`;
|
|
183
283
|
}
|
|
184
284
|
};
|
|
@@ -192,6 +292,12 @@ __decorate([
|
|
|
192
292
|
__decorate([
|
|
193
293
|
property()
|
|
194
294
|
], HelixHeadlessCommand.prototype, "hotkey", void 0);
|
|
295
|
+
__decorate([
|
|
296
|
+
property({ reflect: true, type: Boolean })
|
|
297
|
+
], HelixHeadlessCommand.prototype, "inline", void 0);
|
|
298
|
+
__decorate([
|
|
299
|
+
property({ attribute: false })
|
|
300
|
+
], HelixHeadlessCommand.prototype, "items", void 0);
|
|
195
301
|
__decorate([
|
|
196
302
|
words,
|
|
197
303
|
property()
|
|
@@ -209,6 +315,9 @@ __decorate([
|
|
|
209
315
|
__decorate([
|
|
210
316
|
property({ attribute: 'no-results-text' })
|
|
211
317
|
], HelixHeadlessCommand.prototype, "noResultsText", void 0);
|
|
318
|
+
__decorate([
|
|
319
|
+
property()
|
|
320
|
+
], HelixHeadlessCommand.prototype, "page", void 0);
|
|
212
321
|
__decorate([
|
|
213
322
|
words,
|
|
214
323
|
property()
|
|
@@ -89,6 +89,11 @@ declare const HelixHeadlessDataTable_base: (new (...args: any[]) => import("@fus
|
|
|
89
89
|
* scrolls within one screen of the end, once for each number of rows. Add the
|
|
90
90
|
* next rows to `rows`, and set `loading` while they are on their way.
|
|
91
91
|
*
|
|
92
|
+
* When a fetch fails, set `error-text`, such as "The files did not load", and
|
|
93
|
+
* put a button that tries again in the `error` slot. The line shows after the
|
|
94
|
+
* rows in place of the empty and loading lines, the live region says it, and
|
|
95
|
+
* the table takes the `error` state. Clear it once you try again.
|
|
96
|
+
*
|
|
92
97
|
* The controls inside the cells leave the tab order, so Tab moves past the
|
|
93
98
|
* whole grid. A windowed table keeps the row holding focus drawn while the
|
|
94
99
|
* rest scroll away.
|
|
@@ -129,6 +134,7 @@ declare const HelixHeadlessDataTable_base: (new (...args: any[]) => import("@fus
|
|
|
129
134
|
*
|
|
130
135
|
* @slot - The columns, as `TableColumn` elements.
|
|
131
136
|
* @slot empty - Content shown when there are no rows to show and none are loading, such as a picture and a link. Falls back to `empty-text`, which the live region still says.
|
|
137
|
+
* @slot error - Content shown while `error-text` is set, such as a button that tries again. Falls back to `error-text`, which the live region still says.
|
|
132
138
|
* @slot footer - Content below the grid. It replaces the built-in pagination.
|
|
133
139
|
* @slot loading - Content shown while the rows load, such as a spinner. Falls back to `loading-text`, which the live region still says.
|
|
134
140
|
* @slot toolbar - Content above the grid, such as a search field or actions.
|
|
@@ -165,6 +171,7 @@ declare const HelixHeadlessDataTable_base: (new (...args: any[]) => import("@fus
|
|
|
165
171
|
* @csspart checkbox-mark-indeterminate - Added to `checkbox-mark` while only some rows are selected.
|
|
166
172
|
* @csspart drag-preview - A copy of the row a pointer drags by its grip, which follows the pointer in the top layer until the row is let go, with `reorderable-rows`. A column's grip moves the column itself. Hidden from assistive technology.
|
|
167
173
|
* @csspart empty - Shown when there are no rows, because none arrived or the filter matches none. While rows are loading, the `loading` part is shown in its place.
|
|
174
|
+
* @csspart error - Shown after the rows while `error-text` is set, in place of the empty and loading lines.
|
|
168
175
|
* @csspart footer - The region below the grid.
|
|
169
176
|
* @csspart grip-head - The heading over the row grips, with `reorderable-rows`.
|
|
170
177
|
* @csspart head - The row that holds the column headings. It is `display: contents`, so it takes no space.
|
|
@@ -205,6 +212,9 @@ declare const HelixHeadlessDataTable_base: (new (...args: any[]) => import("@fus
|
|
|
205
212
|
* @cssproperty --data-table-cell-align - The alignment of the cell's column: `start`, `center`, or `end`. Unset when the column sets none. The component writes it on each cell it draws, and the stylesheet reads it. `Table` sets `--table-cell-align` for the same purpose.
|
|
206
213
|
* @cssproperty --data-table-level - How deep a row is in a tree grid, from 1. The component writes it on each row header cell, and the stylesheet indents by it.
|
|
207
214
|
* @cssproperty --data-table-template - The column track list, worked out from the columns. The component writes it inline on the element that reads it, so setting it has no effect. Set a column's `width` to change it.
|
|
215
|
+
*
|
|
216
|
+
* @cssstate empty - While there are no rows to show and none are loading.
|
|
217
|
+
* @cssstate error - While `error-text` is set.
|
|
208
218
|
*/
|
|
209
219
|
export declare class HelixHeadlessDataTable extends HelixHeadlessDataTable_base {
|
|
210
220
|
/**
|
|
@@ -286,6 +296,16 @@ export declare class HelixHeadlessDataTable extends HelixHeadlessDataTable_base
|
|
|
286
296
|
descendingLabel: string;
|
|
287
297
|
/** What screen readers hear when a row is dropped, with `reorderable-rows`. */
|
|
288
298
|
droppedLabel: string;
|
|
299
|
+
/**
|
|
300
|
+
* The text shown and announced when the rows could not load, such as "The
|
|
301
|
+
* files did not load". Set it when a fetch fails, and clear it once you try
|
|
302
|
+
* again. Empty, the default, shows nothing.
|
|
303
|
+
*
|
|
304
|
+
* While it is set, a line after the rows shows it, or your own content in the
|
|
305
|
+
* `error` slot, in place of the empty and loading lines, and the live region
|
|
306
|
+
* says it.
|
|
307
|
+
*/
|
|
308
|
+
errorText: string;
|
|
289
309
|
/**
|
|
290
310
|
* The estimated height of a row before it is drawn, in pixels.
|
|
291
311
|
*
|
|
@@ -1119,6 +1139,11 @@ export declare class HelixHeadlessDataTable extends HelixHeadlessDataTable_base
|
|
|
1119
1139
|
* after the last row and Enter or Space on it asks.
|
|
1120
1140
|
*/
|
|
1121
1141
|
private renderMore;
|
|
1142
|
+
/**
|
|
1143
|
+
* The line shown while `error-text` is set: a row holding one cell, as the
|
|
1144
|
+
* empty and loading lines are, since a grid holds rows and nothing else.
|
|
1145
|
+
*/
|
|
1146
|
+
private renderError;
|
|
1122
1147
|
protected render(): TemplateResult;
|
|
1123
1148
|
/**
|
|
1124
1149
|
* Listens on the host, not on the slot the columns are assigned to.
|
|
@@ -42,6 +42,7 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
|
|
|
42
42
|
this.defaultSort = '';
|
|
43
43
|
this.descendingLabel = 'descending';
|
|
44
44
|
this.droppedLabel = 'Dropped';
|
|
45
|
+
this.errorText = '';
|
|
45
46
|
this.estimate = 32;
|
|
46
47
|
this.gapLabel = 'Skip to page {page}';
|
|
47
48
|
this.label = '';
|
|
@@ -913,6 +914,7 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
|
|
|
913
914
|
['toolbar', true],
|
|
914
915
|
['footer', true],
|
|
915
916
|
['empty', true],
|
|
917
|
+
['error', true],
|
|
916
918
|
['loading', true],
|
|
917
919
|
]) {
|
|
918
920
|
const slot = root.querySelector(`slot[name="${name}"]`);
|
|
@@ -980,6 +982,8 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
|
|
|
980
982
|
this.internals.ariaColCount =
|
|
981
983
|
this.counts.columns === undefined ? null : String(this.counts.columns);
|
|
982
984
|
this.markEmpty();
|
|
985
|
+
this.toggleState('error', this.errorText !== '');
|
|
986
|
+
this.toggleState('empty', !this.loading && !this.hasRows);
|
|
983
987
|
this.assign();
|
|
984
988
|
this.announceSort();
|
|
985
989
|
this.quietControls();
|
|
@@ -1173,10 +1177,13 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
|
|
|
1173
1177
|
>
|
|
1174
1178
|
</div>`;
|
|
1175
1179
|
}
|
|
1180
|
+
renderError() {
|
|
1181
|
+
return html `<p part="error" role="row"><span role="gridcell"><slot name="error">${this.errorText}</slot></span></p>`;
|
|
1182
|
+
}
|
|
1176
1183
|
render() {
|
|
1177
1184
|
const { available, matched, page } = this.view;
|
|
1178
1185
|
this.drawnPage = page;
|
|
1179
|
-
this.moreDrawn = this.loadMore && !this.loading && page.length > 0;
|
|
1186
|
+
this.moreDrawn = this.loadMore && !this.loading && this.errorText === '' && page.length > 0;
|
|
1180
1187
|
const rows = this.windowOf(page);
|
|
1181
1188
|
const kept = this.keptRow(page);
|
|
1182
1189
|
const headStop = this.currentRow(page) === -1;
|
|
@@ -1206,9 +1213,9 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
|
|
|
1206
1213
|
: html `<slot name="columns" @slotchange=${this.handleSlotChange}></slot>`}
|
|
1207
1214
|
</div>
|
|
1208
1215
|
${this.renderSpacer('before', kept)}${this.renderRows(rows, page)}${this.renderSpacer('after', kept)}
|
|
1209
|
-
${this.renderMore(page)}${this.renderStatus(filled, 'gridcell')}
|
|
1216
|
+
${this.renderMore(page)}${this.errorText === '' ? this.renderStatus(filled, 'gridcell') : this.renderError()}
|
|
1210
1217
|
</div>
|
|
1211
|
-
${this.renderLive(filled)}
|
|
1218
|
+
${this.errorText === '' ? this.renderLive(filled) : html `<span part="live" role="status">${this.errorText}</span>`}
|
|
1212
1219
|
<div part="footer">
|
|
1213
1220
|
<slot name="footer"></slot>
|
|
1214
1221
|
${this.paginated
|
|
@@ -1547,6 +1554,9 @@ __decorate([
|
|
|
1547
1554
|
words,
|
|
1548
1555
|
property({ attribute: 'dropped-label' })
|
|
1549
1556
|
], HelixHeadlessDataTable.prototype, "droppedLabel", void 0);
|
|
1557
|
+
__decorate([
|
|
1558
|
+
property({ attribute: 'error-text' })
|
|
1559
|
+
], HelixHeadlessDataTable.prototype, "errorText", void 0);
|
|
1550
1560
|
__decorate([
|
|
1551
1561
|
property({ type: Number })
|
|
1552
1562
|
], HelixHeadlessDataTable.prototype, "estimate", void 0);
|
|
@@ -27,12 +27,37 @@ declare const HelixHeadlessDateInput_base: (new (...args: any[]) => import("@fus
|
|
|
27
27
|
* month, a day and a year in the order the locale writes them, each set with
|
|
28
28
|
* digits or the arrow keys. Alt+Down opens the calendar from a segment.
|
|
29
29
|
*
|
|
30
|
+
* Text that is not a date gives the form an empty value, as a native date
|
|
31
|
+
* field does, and `value` keeps the text so it can be corrected.
|
|
32
|
+
*
|
|
33
|
+
* **Keyboard.** The field is one tab stop, or each segment is with
|
|
34
|
+
* `segmented`, and the trigger is one more.
|
|
35
|
+
*
|
|
36
|
+
* | Key | What it does |
|
|
37
|
+
* |---|---|
|
|
38
|
+
* | Down | In the text field, opens the calendar with focus on the chosen day. |
|
|
39
|
+
* | Alt with Down | Opens the calendar, from the text field or a segment. |
|
|
40
|
+
* | Enter | In the text field, takes the typed date and submits the form, as a native field does. |
|
|
41
|
+
* | Escape | Closes the calendar and puts focus back in the field. |
|
|
42
|
+
* | Tab | In the open calendar, moves between its controls. Out of the field, closes it. |
|
|
43
|
+
* | Up, Down on a segment | Steps the segment by one, round from its last value to its first. |
|
|
44
|
+
* | Page Up, Page Down on a segment | Steps by a page: a week of days, two months, or five years. |
|
|
45
|
+
* | Home, End on a segment | Goes to the segment's first or last value. |
|
|
46
|
+
* | Digits on a segment | Fill it, and move to the next once it can take no more. |
|
|
47
|
+
* | Backspace, Delete on a segment | Empty the segment. |
|
|
48
|
+
* | Left, Right on a segment | Move to the previous or the next segment, along the line of text. |
|
|
49
|
+
*
|
|
50
|
+
* In the calendar, the keys are `DatePicker`'s: the arrows move by a day or a
|
|
51
|
+
* week, Page Up and Page Down by a month, with Shift by a year, and Home and
|
|
52
|
+
* End to the start and the end of the week.
|
|
53
|
+
*
|
|
30
54
|
* @since 0.1.0
|
|
31
55
|
* @status stable
|
|
32
56
|
* @category forms
|
|
33
57
|
* @aliases DatePicker, DateField, InputDate
|
|
34
58
|
* @documentation https://fusion.dev/helix/components/date-input
|
|
35
59
|
*
|
|
60
|
+
* @slot end - Content after the date, before the trigger, such as a clear button.
|
|
36
61
|
* @slot feedback - The error message under the field.
|
|
37
62
|
* @slot hint - The help text under the field.
|
|
38
63
|
* @slot label - The label, for content richer than the `label` text.
|
|
@@ -40,6 +65,7 @@ declare const HelixHeadlessDateInput_base: (new (...args: any[]) => import("@fus
|
|
|
40
65
|
* @slot panel-footer - Content in the panel under the calendar, such as a note or an action.
|
|
41
66
|
* @slot panel-header - Content in the panel above the calendar, such as a heading or a shortcut.
|
|
42
67
|
* @slot previous-icon - The icon on the calendar's previous month control.
|
|
68
|
+
* @slot start - Content before the date, such as an icon.
|
|
43
69
|
* @slot trigger - The icon on the button that opens the calendar.
|
|
44
70
|
*
|
|
45
71
|
* @event {Event} change - Fired after the value changes.
|
|
@@ -384,7 +410,12 @@ export declare class HelixHeadlessDateInput extends HelixHeadlessDateInput_base
|
|
|
384
410
|
/** Takes one date back out of the set. */
|
|
385
411
|
private readonly removeDate;
|
|
386
412
|
private readonly handleCalendarChange;
|
|
387
|
-
/**
|
|
413
|
+
/**
|
|
414
|
+
* Each date, where `mode` holds several, sent one entry each.
|
|
415
|
+
*
|
|
416
|
+
* One date that is text and no date, such as `August 5`, is sent as an empty
|
|
417
|
+
* entry, as a native date field sends a date it cannot read.
|
|
418
|
+
*/
|
|
388
419
|
protected get answers(): readonly string[] | undefined;
|
|
389
420
|
/** What is wrong with the dates, which the text input cannot say. */
|
|
390
421
|
protected validate(): string | readonly [keyof ValidityStateFlags, string];
|
|
@@ -175,7 +175,11 @@ let HelixHeadlessDateInput = class HelixHeadlessDateInput extends MixinSegmented
|
|
|
175
175
|
return Math.max(0, dates(this.value).length - this.shownDates.length);
|
|
176
176
|
}
|
|
177
177
|
get answers() {
|
|
178
|
-
|
|
178
|
+
if (this.mode !== undefined) {
|
|
179
|
+
return dates(this.value);
|
|
180
|
+
}
|
|
181
|
+
const text = this.value.trim();
|
|
182
|
+
return text === '' || isDateValue(text) ? undefined : [''];
|
|
179
183
|
}
|
|
180
184
|
validate() {
|
|
181
185
|
const held = dates(this.value);
|
|
@@ -314,10 +318,12 @@ let HelixHeadlessDateInput = class HelixHeadlessDateInput extends MixinSegmented
|
|
|
314
318
|
render() {
|
|
315
319
|
return html `${this.renderLabel()}
|
|
316
320
|
<div part="field" @keydown=${this.handleKeydown}>
|
|
321
|
+
<slot name="start"></slot>
|
|
317
322
|
${this.mode === 'multiple'
|
|
318
323
|
? this.renderTokenRow(this.shownDates.map((date) => this.renderTag(date)), this.restCount)
|
|
319
324
|
: nothing}
|
|
320
325
|
${this.segmentsShown ? this.renderSegments() : this.renderControl()}
|
|
326
|
+
<slot name="end"></slot>
|
|
321
327
|
<button
|
|
322
328
|
part="trigger"
|
|
323
329
|
type="button"
|
|
@@ -39,6 +39,7 @@ declare const HelixHeadlessGraph_base: (new (...args: any[]) => import("@fusion.
|
|
|
39
39
|
* @documentation https://fusion.dev/helix/components/graph
|
|
40
40
|
*
|
|
41
41
|
* @slot - The renderer's box, which the drawing is made in.
|
|
42
|
+
* @slot empty - What the panel shows when the list holds no notes. Defaults to `empty-label`.
|
|
42
43
|
* @slot filter-icon - The icon in the search field. Defaults to a magnifying glass.
|
|
43
44
|
* @slot footer - Content below the graph.
|
|
44
45
|
* @slot fullscreen-icon - Replaces the glyph inside the fullscreen button.
|
|
@@ -65,6 +66,7 @@ declare const HelixHeadlessGraph_base: (new (...args: any[]) => import("@fusion.
|
|
|
65
66
|
* @csspart body - The drawing beside the list, or above it where the frame is narrow.
|
|
66
67
|
* @csspart controls - The group the pan and zoom buttons sit in.
|
|
67
68
|
* @csspart count - How many notes the list holds, which a search narrows, at the end of the search bar.
|
|
69
|
+
* @csspart empty - The box in place of the list when it holds no notes, holding the `empty` slot.
|
|
68
70
|
* @csspart filter-icon - The icon beside the search field.
|
|
69
71
|
* @csspart filter-mark - The default icon beside the search field, shown when none is slotted.
|
|
70
72
|
* @csspart footer - The strip below the graph.
|
|
@@ -135,6 +137,11 @@ export declare class HelixHeadlessGraph extends HelixHeadlessGraph_base {
|
|
|
135
137
|
* shows every note. The list holds what the drawing shows.
|
|
136
138
|
*/
|
|
137
139
|
depth?: number;
|
|
140
|
+
/**
|
|
141
|
+
* What the panel shows when the list holds no notes: the graph has none, or
|
|
142
|
+
* a search or the legend hid them all. A slotted `empty` replaces it.
|
|
143
|
+
*/
|
|
144
|
+
emptyLabel: string;
|
|
138
145
|
/**
|
|
139
146
|
* The graph's name where `label` is empty and the host has no
|
|
140
147
|
* `aria-label`.
|
|
@@ -180,6 +187,12 @@ export declare class HelixHeadlessGraph extends HelixHeadlessGraph_base {
|
|
|
180
187
|
noPanel: boolean;
|
|
181
188
|
/** The list's name, read by screen readers, and the name of the button that opens it with `no-panel`. */
|
|
182
189
|
notesLabel: string;
|
|
190
|
+
/**
|
|
191
|
+
* What a screen reader calls the graph in place of its role, after its
|
|
192
|
+
* name: "Research notes, graph". Said only where the graph has a name of its
|
|
193
|
+
* own, since the fallback name is the same word. Empty leaves the role.
|
|
194
|
+
*/
|
|
195
|
+
roleLabel: string;
|
|
183
196
|
/** The search field's name. */
|
|
184
197
|
searchLabel: string;
|
|
185
198
|
/** How the selected note is described in the list. */
|
|
@@ -17,6 +17,7 @@ let HelixHeadlessGraph = class HelixHeadlessGraph extends MixinViewport(MixinCon
|
|
|
17
17
|
this.activateHintLabel = 'Press the graph to use it';
|
|
18
18
|
this.controlsLabel = 'Graph controls';
|
|
19
19
|
this.countLabel = '{count, plural, one {# note} other {# notes}}';
|
|
20
|
+
this.emptyLabel = 'No notes';
|
|
20
21
|
this.fallbackLabel = 'Graph';
|
|
21
22
|
this.hiddenGroups = [];
|
|
22
23
|
this.label = '';
|
|
@@ -33,6 +34,7 @@ let HelixHeadlessGraph = class HelixHeadlessGraph extends MixinViewport(MixinCon
|
|
|
33
34
|
this.nodes = [];
|
|
34
35
|
this.noPanel = false;
|
|
35
36
|
this.notesLabel = 'Notes';
|
|
37
|
+
this.roleLabel = 'graph';
|
|
36
38
|
this.searchLabel = 'Find a note';
|
|
37
39
|
this.selectedLabel = 'Selected';
|
|
38
40
|
this.twoFingerHintLabel = 'Use two fingers to move the graph';
|
|
@@ -409,6 +411,8 @@ let HelixHeadlessGraph = class HelixHeadlessGraph extends MixinViewport(MixinCon
|
|
|
409
411
|
part="region"
|
|
410
412
|
role="group"
|
|
411
413
|
aria-label=${this.label || this.getAttribute?.('aria-label') || this.fallbackLabel}
|
|
414
|
+
aria-roledescription=${(this.label || this.getAttribute?.('aria-label')) && this.roleLabel ? this.roleLabel : nothing}
|
|
415
|
+
aria-keyshortcuts="ArrowUp ArrowDown ArrowLeft ArrowRight Plus Minus"
|
|
412
416
|
tabindex="0"
|
|
413
417
|
@focusin=${this.idle.stir}
|
|
414
418
|
@keydown=${this.handleKeydown}
|
|
@@ -454,6 +458,7 @@ let HelixHeadlessGraph = class HelixHeadlessGraph extends MixinViewport(MixinCon
|
|
|
454
458
|
data-scroller
|
|
455
459
|
aria-label=${this.notesLabel}
|
|
456
460
|
aria-activedescendant=${cursor}
|
|
461
|
+
aria-keyshortcuts="ArrowUp ArrowDown Home End PageUp PageDown Enter Space"
|
|
457
462
|
@keydown=${this.handleListKeydown}
|
|
458
463
|
@scroll=${() => this.virtualize.measure()}
|
|
459
464
|
>
|
|
@@ -461,6 +466,9 @@ let HelixHeadlessGraph = class HelixHeadlessGraph extends MixinViewport(MixinCon
|
|
|
461
466
|
${this.renderRows()}
|
|
462
467
|
<div part="after" role="presentation" style="block-size: ${after}px"></div>
|
|
463
468
|
</div>
|
|
469
|
+
${this.entries.length === 0
|
|
470
|
+
? html `<div part="empty"><slot name="empty">${this.emptyLabel}</slot></div>`
|
|
471
|
+
: nothing}
|
|
464
472
|
</div>
|
|
465
473
|
</div>
|
|
466
474
|
<div part="footer">
|
|
@@ -485,6 +493,10 @@ __decorate([
|
|
|
485
493
|
__decorate([
|
|
486
494
|
property({ type: Number })
|
|
487
495
|
], HelixHeadlessGraph.prototype, "depth", void 0);
|
|
496
|
+
__decorate([
|
|
497
|
+
words,
|
|
498
|
+
property({ attribute: 'empty-label' })
|
|
499
|
+
], HelixHeadlessGraph.prototype, "emptyLabel", void 0);
|
|
488
500
|
__decorate([
|
|
489
501
|
words,
|
|
490
502
|
property({ attribute: 'fallback-label' })
|
|
@@ -543,6 +555,10 @@ __decorate([
|
|
|
543
555
|
words,
|
|
544
556
|
property({ attribute: 'notes-label' })
|
|
545
557
|
], HelixHeadlessGraph.prototype, "notesLabel", void 0);
|
|
558
|
+
__decorate([
|
|
559
|
+
words,
|
|
560
|
+
property({ attribute: 'role-label' })
|
|
561
|
+
], HelixHeadlessGraph.prototype, "roleLabel", void 0);
|
|
546
562
|
__decorate([
|
|
547
563
|
words,
|
|
548
564
|
property({ attribute: 'search-label' })
|