@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
|
@@ -12,6 +12,9 @@ import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
|
|
|
12
12
|
import { decimals, formatGrain, strideOf } from '../grain.js';
|
|
13
13
|
const SCRUB_PITCH = 4;
|
|
14
14
|
const SCRUB_FINE = 16;
|
|
15
|
+
const PLAIN = /^-?(\d+(\.\d*)?|\.\d+)$/;
|
|
16
|
+
const REPEAT_DELAY = 400;
|
|
17
|
+
const REPEAT_EVERY = 60;
|
|
15
18
|
let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinControllerValue(MixinField(HelixFormElement)) {
|
|
16
19
|
constructor() {
|
|
17
20
|
super(...arguments);
|
|
@@ -28,6 +31,7 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
|
|
|
28
31
|
this.rangeOverflowMessage = 'Value must be {max} or less.';
|
|
29
32
|
this.rangeUnderflowMessage = 'Value must be {min} or more.';
|
|
30
33
|
this.readOnly = false;
|
|
34
|
+
this.roleLabel = 'number field';
|
|
31
35
|
this.scrub = false;
|
|
32
36
|
this.stepMismatchMessage = 'Please enter a valid value. The two nearest valid values are {below} and {above}.';
|
|
33
37
|
this.valueText = '';
|
|
@@ -49,6 +53,53 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
|
|
|
49
53
|
this.editing = true;
|
|
50
54
|
};
|
|
51
55
|
this.touchApple = appleTouch(typeof navigator === 'undefined' ? undefined : navigator);
|
|
56
|
+
this.pressStepped = false;
|
|
57
|
+
this.handleStepperDown = (event) => {
|
|
58
|
+
if (event.button !== 0) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const button = event.currentTarget;
|
|
62
|
+
const up = button.getAttribute('part') === 'increment';
|
|
63
|
+
clearTimeout(this.repeat);
|
|
64
|
+
this.pressStepped = true;
|
|
65
|
+
button.setPointerCapture?.(event.pointerId);
|
|
66
|
+
const step = () => {
|
|
67
|
+
const before = this.value;
|
|
68
|
+
if (up) {
|
|
69
|
+
this.stepUp();
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
this.stepDown();
|
|
73
|
+
}
|
|
74
|
+
this.repeat = this.value === before ? undefined : setTimeout(step, REPEAT_EVERY);
|
|
75
|
+
};
|
|
76
|
+
step();
|
|
77
|
+
if (this.repeat !== undefined) {
|
|
78
|
+
clearTimeout(this.repeat);
|
|
79
|
+
this.repeat = setTimeout(step, REPEAT_DELAY);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
this.stopRepeat = () => {
|
|
83
|
+
clearTimeout(this.repeat);
|
|
84
|
+
this.repeat = undefined;
|
|
85
|
+
if (this.pressStepped) {
|
|
86
|
+
setTimeout(() => {
|
|
87
|
+
this.pressStepped = false;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
this.handleStepperClick = (event) => {
|
|
92
|
+
if (this.pressStepped) {
|
|
93
|
+
this.pressStepped = false;
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (event.currentTarget.getAttribute('part') === 'increment') {
|
|
97
|
+
this.stepUp();
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
this.stepDown();
|
|
101
|
+
}
|
|
102
|
+
};
|
|
52
103
|
this.scrubFrom = 0;
|
|
53
104
|
this.scrubValue = 0;
|
|
54
105
|
this.scrubStart = '';
|
|
@@ -240,6 +291,10 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
|
|
|
240
291
|
stepDown(large = false) {
|
|
241
292
|
this.nudge(large ? -(this.largeStep ?? 10) : -1);
|
|
242
293
|
}
|
|
294
|
+
disconnectedCallback() {
|
|
295
|
+
super.disconnectedCallback();
|
|
296
|
+
this.stopRepeat();
|
|
297
|
+
}
|
|
243
298
|
renderLabel() {
|
|
244
299
|
if (!this.scrub || !this.labeled) {
|
|
245
300
|
return super.renderLabel();
|
|
@@ -267,13 +322,20 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
|
|
|
267
322
|
restoreValue(control, this.shown);
|
|
268
323
|
return this.value !== before;
|
|
269
324
|
}
|
|
325
|
+
submitted(parsed) {
|
|
326
|
+
const text = this.value.trim();
|
|
327
|
+
if (parsed === null) {
|
|
328
|
+
return '';
|
|
329
|
+
}
|
|
330
|
+
return PLAIN.test(text) ? text : String(parsed);
|
|
331
|
+
}
|
|
270
332
|
sync() {
|
|
271
333
|
const control = this.control;
|
|
272
|
-
this.
|
|
334
|
+
const parsed = this.numeric;
|
|
335
|
+
this.internals.setFormValue(this.submitted(parsed), this.value);
|
|
273
336
|
if (!control) {
|
|
274
337
|
return;
|
|
275
338
|
}
|
|
276
|
-
const parsed = this.numeric;
|
|
277
339
|
if (this.readOnly) {
|
|
278
340
|
this.internals.setValidity({});
|
|
279
341
|
}
|
|
@@ -329,23 +391,39 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
|
|
|
329
391
|
get blocksImplicitSubmission() {
|
|
330
392
|
return true;
|
|
331
393
|
}
|
|
394
|
+
renderDecrementIcon() {
|
|
395
|
+
return html `<slot name="decrement-icon" aria-hidden="true"
|
|
396
|
+
><svg part="decrement-mark" fill="currentColor" viewBox=${minus.viewBox} aria-hidden="true">
|
|
397
|
+
${unsafeSVG(minus.content)}
|
|
398
|
+
</svg></slot
|
|
399
|
+
>`;
|
|
400
|
+
}
|
|
401
|
+
renderIncrementIcon() {
|
|
402
|
+
return html `<slot name="increment-icon" aria-hidden="true"
|
|
403
|
+
><svg part="increment-mark" fill="currentColor" viewBox=${plus.viewBox} aria-hidden="true">
|
|
404
|
+
${unsafeSVG(plus.content)}
|
|
405
|
+
</svg></slot
|
|
406
|
+
>`;
|
|
407
|
+
}
|
|
332
408
|
render() {
|
|
333
409
|
const parsed = this.numeric;
|
|
334
410
|
const atMin = parsed !== null && this.min !== undefined && parsed <= this.min;
|
|
335
411
|
const atMax = parsed !== null && this.max !== undefined && parsed >= this.max;
|
|
336
412
|
return html `${this.renderLabel()}
|
|
337
|
-
<div part="field">
|
|
413
|
+
<div part="field" role="group">
|
|
338
414
|
<button
|
|
339
415
|
part="decrement"
|
|
340
416
|
type="button"
|
|
341
417
|
aria-label=${this.decrementLabel}
|
|
342
418
|
tabindex="-1"
|
|
343
419
|
?disabled=${this.isDisabled || this.readOnly || atMin}
|
|
344
|
-
@click=${
|
|
420
|
+
@click=${this.handleStepperClick}
|
|
421
|
+
@lostpointercapture=${this.stopRepeat}
|
|
422
|
+
@pointercancel=${this.stopRepeat}
|
|
423
|
+
@pointerdown=${this.handleStepperDown}
|
|
424
|
+
@pointerup=${this.stopRepeat}
|
|
345
425
|
>
|
|
346
|
-
|
|
347
|
-
${unsafeSVG(minus.content)}
|
|
348
|
-
</svg>
|
|
426
|
+
${this.renderDecrementIcon()}
|
|
349
427
|
</button>
|
|
350
428
|
<slot name="start"></slot>
|
|
351
429
|
${''}
|
|
@@ -355,6 +433,7 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
|
|
|
355
433
|
.value=${this.shown}
|
|
356
434
|
type="text"
|
|
357
435
|
role=${this.touchApple ? nothing : 'spinbutton'}
|
|
436
|
+
aria-roledescription=${this.touchApple || this.roleLabel === '' ? nothing : this.roleLabel}
|
|
358
437
|
inputmode="decimal"
|
|
359
438
|
autocomplete=${this.autocomplete || nothing}
|
|
360
439
|
name=${this.name || nothing}
|
|
@@ -393,11 +472,13 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
|
|
|
393
472
|
aria-label=${this.incrementLabel}
|
|
394
473
|
tabindex="-1"
|
|
395
474
|
?disabled=${this.isDisabled || this.readOnly || atMax}
|
|
396
|
-
@click=${
|
|
475
|
+
@click=${this.handleStepperClick}
|
|
476
|
+
@lostpointercapture=${this.stopRepeat}
|
|
477
|
+
@pointercancel=${this.stopRepeat}
|
|
478
|
+
@pointerdown=${this.handleStepperDown}
|
|
479
|
+
@pointerup=${this.stopRepeat}
|
|
397
480
|
>
|
|
398
|
-
|
|
399
|
-
${unsafeSVG(plus.content)}
|
|
400
|
-
</svg>
|
|
481
|
+
${this.renderIncrementIcon()}
|
|
401
482
|
</button>
|
|
402
483
|
</div>
|
|
403
484
|
${this.renderHint()}${this.renderFeedback()}`;
|
|
@@ -453,6 +534,10 @@ __decorate([
|
|
|
453
534
|
__decorate([
|
|
454
535
|
property({ attribute: 'readonly', reflect: true, type: Boolean })
|
|
455
536
|
], HelixHeadlessNumberInput.prototype, "readOnly", void 0);
|
|
537
|
+
__decorate([
|
|
538
|
+
words,
|
|
539
|
+
property({ attribute: 'role-label' })
|
|
540
|
+
], HelixHeadlessNumberInput.prototype, "roleLabel", void 0);
|
|
456
541
|
__decorate([
|
|
457
542
|
property({ reflect: true, type: Boolean })
|
|
458
543
|
], HelixHeadlessNumberInput.prototype, "scrub", void 0);
|
|
@@ -2,6 +2,24 @@ import { AriaForwardController, HelixElement } from '@fusion.dev/helix-core';
|
|
|
2
2
|
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
3
|
import '../../copy-button/index.js';
|
|
4
4
|
import { type Row } from '../rows.js';
|
|
5
|
+
/** What a `renderValue` function is told about the entry it draws. */
|
|
6
|
+
export interface ObjectInspectorEntry {
|
|
7
|
+
/** How deep the entry is, from 0 at the root. */
|
|
8
|
+
depth: number;
|
|
9
|
+
/** The property name or array index the value is under, absent for the root. */
|
|
10
|
+
key: string | undefined;
|
|
11
|
+
/** The entry's path, as `active` writes it: `/meta/tags/0`. */
|
|
12
|
+
path: string;
|
|
13
|
+
/** What kind of value it is, as JSON names it: `array`, `boolean`, `null`, `number`, `object` or `string`. Anything else is `other`. */
|
|
14
|
+
type: 'array' | 'boolean' | 'null' | 'number' | 'object' | 'other' | 'string';
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Draws one entry's value, for an inspector given `renderValue`.
|
|
18
|
+
*
|
|
19
|
+
* Returns anything Lit renders, such as a template from `html`, a string, or a
|
|
20
|
+
* DOM node, or `undefined` to draw the value as the inspector does.
|
|
21
|
+
*/
|
|
22
|
+
export type ObjectInspectorRenderer = (value: unknown, entry: ObjectInspectorEntry) => unknown;
|
|
5
23
|
/**
|
|
6
24
|
* @summary A structured value shown as a tree, where you can close branches,
|
|
7
25
|
* see how many entries each holds, and copy any part.
|
|
@@ -65,6 +83,12 @@ import { type Row } from '../rows.js';
|
|
|
65
83
|
* selected. The button sits beside the tree, because assistive technology
|
|
66
84
|
* cannot reliably reach a button inside a tree item.
|
|
67
85
|
*
|
|
86
|
+
* **Your own drawing of a value.** `renderValue` is a function from a value and
|
|
87
|
+
* its entry, its path, key, depth and type, to what Lit renders, such as a
|
|
88
|
+
* link for a URL or a swatch for a color. Return `undefined` to draw the value
|
|
89
|
+
* as the inspector does. What you draw sits in the `value` part, after the
|
|
90
|
+
* key. Filtering and copying still read the value itself.
|
|
91
|
+
*
|
|
68
92
|
* `windowed` draws only the rows in view, for values with thousands of entries.
|
|
69
93
|
* The box needs a height for windowing to work. The styled tier sets one, and
|
|
70
94
|
* in the headless tier you set it yourself.
|
|
@@ -119,6 +143,7 @@ import { type Row } from '../rows.js';
|
|
|
119
143
|
* @csspart summary - The brackets that stand for a branch's contents.
|
|
120
144
|
* @csspart toggle - The caret on a branch. It points down when the branch is open and sideways when it is closed.
|
|
121
145
|
* @csspart tree - The tree grid that holds the rows.
|
|
146
|
+
* @csspart value - What `renderValue` drew for an entry, in place of its own drawing.
|
|
122
147
|
*
|
|
123
148
|
* @cssproperty --object-inspector-level - How deep a row is, from 0 at the root, for indenting it.
|
|
124
149
|
*/
|
|
@@ -229,6 +254,16 @@ export declare class HelixHeadlessObjectInspector extends HelixElement {
|
|
|
229
254
|
* reach a value.
|
|
230
255
|
*/
|
|
231
256
|
noCopy: boolean;
|
|
257
|
+
/**
|
|
258
|
+
* Draws an entry's value your way, given the value and its entry. Set it as
|
|
259
|
+
* a property.
|
|
260
|
+
*
|
|
261
|
+
* It returns what Lit renders: a template from `html`, a string, or a DOM
|
|
262
|
+
* node. `undefined` draws the value as the inspector does, so a function can
|
|
263
|
+
* draw some entries and leave the rest, such as only the strings under
|
|
264
|
+
* `/links`.
|
|
265
|
+
*/
|
|
266
|
+
renderValue: ObjectInspectorRenderer | undefined;
|
|
232
267
|
/**
|
|
233
268
|
* What a screen reader hears when a filter matches one entry, where
|
|
234
269
|
* `results-label` should not say it. `{count}` is the count.
|
|
@@ -397,7 +432,7 @@ export declare class HelixHeadlessObjectInspector extends HelixElement {
|
|
|
397
432
|
private readonly handleFilterInput;
|
|
398
433
|
/** Down from the filter field goes to the active row, where the matches are. */
|
|
399
434
|
private readonly handleFilterKeydown;
|
|
400
|
-
private
|
|
435
|
+
private drawValue;
|
|
401
436
|
/**
|
|
402
437
|
* One entry, named for its state where it holds one.
|
|
403
438
|
*
|
|
@@ -14,6 +14,18 @@ import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
|
|
|
14
14
|
import { html as staticHtml } from 'lit/static-html.js';
|
|
15
15
|
import '../../copy-button/index.js';
|
|
16
16
|
import { cursorFor, entries, flatten, search, seed, siblingBranches, withAncestors, } from '../rows.js';
|
|
17
|
+
const kindOf = (value) => {
|
|
18
|
+
if (value === null) {
|
|
19
|
+
return 'null';
|
|
20
|
+
}
|
|
21
|
+
if (Array.isArray(value)) {
|
|
22
|
+
return 'array';
|
|
23
|
+
}
|
|
24
|
+
const type = typeof value;
|
|
25
|
+
return type === 'boolean' || type === 'number' || type === 'object' || type === 'string'
|
|
26
|
+
? type
|
|
27
|
+
: 'other';
|
|
28
|
+
};
|
|
17
29
|
let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends HelixElement {
|
|
18
30
|
constructor() {
|
|
19
31
|
super(...arguments);
|
|
@@ -393,8 +405,17 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
|
|
|
393
405
|
this.toggle(row);
|
|
394
406
|
}
|
|
395
407
|
}
|
|
396
|
-
|
|
408
|
+
drawValue(row) {
|
|
397
409
|
const { value } = row;
|
|
410
|
+
const drawn = this.renderValue?.(value, {
|
|
411
|
+
depth: row.depth,
|
|
412
|
+
key: row.key,
|
|
413
|
+
path: row.path,
|
|
414
|
+
type: kindOf(value),
|
|
415
|
+
});
|
|
416
|
+
if (drawn !== undefined) {
|
|
417
|
+
return html `<span part="value">${drawn}</span>`;
|
|
418
|
+
}
|
|
398
419
|
if (row.branch) {
|
|
399
420
|
const count = entries(value).length;
|
|
400
421
|
const array = Array.isArray(value);
|
|
@@ -439,7 +460,7 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
|
|
|
439
460
|
>
|
|
440
461
|
${unsafeSVG((row.open ? caretDown : caretRight).content)}
|
|
441
462
|
</svg>`
|
|
442
|
-
: nothing}${row.key === undefined ? nothing : html `<span part="key">${row.key}</span>: `}${this.
|
|
463
|
+
: nothing}${row.key === undefined ? nothing : html `<span part="key">${row.key}</span>: `}${this.drawValue(row)}</div>
|
|
443
464
|
</div>`;
|
|
444
465
|
}
|
|
445
466
|
hasFocusedRow() {
|
|
@@ -563,6 +584,9 @@ __decorate([
|
|
|
563
584
|
__decorate([
|
|
564
585
|
property({ attribute: 'no-copy', type: Boolean })
|
|
565
586
|
], HelixHeadlessObjectInspector.prototype, "noCopy", void 0);
|
|
587
|
+
__decorate([
|
|
588
|
+
property({ attribute: false })
|
|
589
|
+
], HelixHeadlessObjectInspector.prototype, "renderValue", void 0);
|
|
566
590
|
__decorate([
|
|
567
591
|
property({ attribute: 'result-label' })
|
|
568
592
|
], HelixHeadlessObjectInspector.prototype, "resultLabel", void 0);
|
|
@@ -37,6 +37,12 @@ export declare const optionsAmong: (elements: readonly Element[]) => HelixHeadle
|
|
|
37
37
|
* An option with `href` is a link: choosing it follows the link and selects
|
|
38
38
|
* nothing, as an entry in a command palette that goes to a page does.
|
|
39
39
|
*
|
|
40
|
+
* The option's name is its label alone, as React Aria's `slot="label"` makes
|
|
41
|
+
* it: what is in `start` and `end`, an avatar's initials or a shortcut, is
|
|
42
|
+
* drawn and not read as part of the name. The description is read as the
|
|
43
|
+
* option's description. A name you give with `aria-label` or
|
|
44
|
+
* `aria-labelledby` wins over both.
|
|
45
|
+
*
|
|
40
46
|
* @since 0.1.0
|
|
41
47
|
* @status stable
|
|
42
48
|
* @category forms
|
|
@@ -61,6 +67,8 @@ export declare const optionsAmong: (elements: readonly Element[]) => HelixHeadle
|
|
|
61
67
|
* @cssstate active - While the keyboard is on the option, which is the option `aria-activedescendant` names.
|
|
62
68
|
* @cssstate disabled - While the option cannot be chosen.
|
|
63
69
|
* @cssstate has-indicator - An icon is slotted into `indicator`.
|
|
70
|
+
* @cssstate hovered - While a mouse or a pen is over the option. A touch sets no hover, as React Aria's `data-hovered` does not.
|
|
71
|
+
* @cssstate pressed - While a pointer presses the option, from the press to the release.
|
|
64
72
|
* @cssstate selected - While the option is chosen.
|
|
65
73
|
*/
|
|
66
74
|
export declare class HelixHeadlessOption extends HelixElement {
|
|
@@ -188,6 +196,17 @@ export declare class HelixHeadlessOption extends HelixElement {
|
|
|
188
196
|
* nothing for every choice.
|
|
189
197
|
*/
|
|
190
198
|
label: string;
|
|
199
|
+
/**
|
|
200
|
+
* The page of commands this option opens, by name, where choosing it would
|
|
201
|
+
* otherwise run it.
|
|
202
|
+
*
|
|
203
|
+
* Declared here and acted on by whatever owns the list, as `alternate` is.
|
|
204
|
+
* `Command` is the only owner that reads it: choosing the option shows the
|
|
205
|
+
* commands slotted as `page:` and the name, such as `slot="page:theme"`,
|
|
206
|
+
* and Backspace in an empty filter goes back. Empty is an option that opens
|
|
207
|
+
* nothing, which is nearly every option.
|
|
208
|
+
*/
|
|
209
|
+
opens: string;
|
|
191
210
|
/**
|
|
192
211
|
* The link's `rel`. Set it whenever you set `target="_blank"`.
|
|
193
212
|
*/
|
|
@@ -291,6 +310,19 @@ export declare class HelixHeadlessOption extends HelixElement {
|
|
|
291
310
|
* consumer's markup means. What stops is the write back.
|
|
292
311
|
*/
|
|
293
312
|
protected willUpdate(changed: PropertyValues): void;
|
|
313
|
+
/**
|
|
314
|
+
* The name is the label part and the description the description part, by
|
|
315
|
+
* reference, because the host's own content runs `start` and `end` into its
|
|
316
|
+
* name: an option with initials and a shortcut was called `P Priya Shah ⌘P`.
|
|
317
|
+
* The alternate destination is part of the description where there is a
|
|
318
|
+
* caption to read with it. A name the consumer wrote on the host wins.
|
|
319
|
+
*/
|
|
320
|
+
protected updated(changed: PropertyValues): void;
|
|
321
|
+
/** A hover from a mouse or a pen, which a touch does not make. */
|
|
322
|
+
private readonly handlePointerEnter;
|
|
323
|
+
private readonly handlePointerLeave;
|
|
324
|
+
private readonly handlePointerDown;
|
|
325
|
+
private readonly handlePointerUp;
|
|
294
326
|
protected render(): TemplateResult;
|
|
295
327
|
}
|
|
296
328
|
declare global {
|
|
@@ -4,8 +4,8 @@ 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, followLink, HelixElement, HelixSelectEvent, slotFilled, } from '@fusion.dev/helix-core';
|
|
8
|
-
import { html } from 'lit';
|
|
7
|
+
import { customElement, followLink, HelixElement, HelixSelectEvent, hostAria, slotFilled, } from '@fusion.dev/helix-core';
|
|
8
|
+
import { html, nothing } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
export const optionsAmong = (elements) => {
|
|
11
11
|
const isOption = (element) => 'text' in element && 'value' in element && 'selected' in element;
|
|
@@ -21,12 +21,26 @@ let HelixHeadlessOption = class HelixHeadlessOption extends HelixElement {
|
|
|
21
21
|
this.download = false;
|
|
22
22
|
this.href = '';
|
|
23
23
|
this.label = '';
|
|
24
|
+
this.opens = '';
|
|
24
25
|
this.rel = '';
|
|
25
26
|
this.selected = false;
|
|
26
27
|
this.selectionFollowsActive = false;
|
|
27
28
|
this.target = '';
|
|
28
29
|
this.textValue = '';
|
|
29
30
|
this.value = '';
|
|
31
|
+
this.handlePointerEnter = (event) => {
|
|
32
|
+
this.toggleState('hovered', event.pointerType !== 'touch');
|
|
33
|
+
};
|
|
34
|
+
this.handlePointerLeave = () => {
|
|
35
|
+
this.toggleState('hovered', false);
|
|
36
|
+
this.toggleState('pressed', false);
|
|
37
|
+
};
|
|
38
|
+
this.handlePointerDown = (event) => {
|
|
39
|
+
this.toggleState('pressed', event.button === 0 && !this.disabled);
|
|
40
|
+
};
|
|
41
|
+
this.handlePointerUp = () => {
|
|
42
|
+
this.toggleState('pressed', false);
|
|
43
|
+
};
|
|
30
44
|
}
|
|
31
45
|
get defaultSelected() {
|
|
32
46
|
return this.hasAttribute('selected');
|
|
@@ -62,6 +76,21 @@ let HelixHeadlessOption = class HelixHeadlessOption extends HelixElement {
|
|
|
62
76
|
this.toggleState('active', this.active);
|
|
63
77
|
this.toggleState('disabled', this.disabled);
|
|
64
78
|
}
|
|
79
|
+
updated(changed) {
|
|
80
|
+
super.updated(changed);
|
|
81
|
+
const named = this.hasAttribute('aria-label') || this.hasAttribute('aria-labelledby');
|
|
82
|
+
const label = this.partNamed('label');
|
|
83
|
+
const caption = this.partNamed('description');
|
|
84
|
+
const alternate = this.renderRoot.querySelector('[data-alternate]');
|
|
85
|
+
const described = [
|
|
86
|
+
...(caption ? [caption] : this.knownChildren.filter((child) => child.slot === 'description')),
|
|
87
|
+
...(alternate ? [alternate] : []),
|
|
88
|
+
];
|
|
89
|
+
hostAria(this.internals, {
|
|
90
|
+
describedBy: described,
|
|
91
|
+
labelledBy: named || !label ? [] : [label],
|
|
92
|
+
});
|
|
93
|
+
}
|
|
65
94
|
render() {
|
|
66
95
|
this.internals.role = 'option';
|
|
67
96
|
this.internals.ariaSelected =
|
|
@@ -69,19 +98,26 @@ let HelixHeadlessOption = class HelixHeadlessOption extends HelixElement {
|
|
|
69
98
|
? String(this.selected || (this.selectionFollowsActive && this.active))
|
|
70
99
|
: null;
|
|
71
100
|
this.internals.ariaDisabled = this.disabled ? 'true' : null;
|
|
72
|
-
this.
|
|
73
|
-
|
|
101
|
+
const alternate = this.alternate !== '' && this.alternateLabel !== '';
|
|
102
|
+
const captioned = Boolean(this.description) || this.knownChildren.some((child) => child.slot === 'description');
|
|
103
|
+
this.internals.ariaDescription = alternate && !captioned ? this.alternateLabel : null;
|
|
74
104
|
return html `<div
|
|
75
105
|
part="item${this.selected ? ' item-selected' : ''}${this.active ? ' item-active' : ''}"
|
|
76
106
|
@click=${(event) => this.select(event)}
|
|
107
|
+
@pointercancel=${this.handlePointerUp}
|
|
108
|
+
@pointerdown=${this.handlePointerDown}
|
|
109
|
+
@pointerenter=${this.handlePointerEnter}
|
|
110
|
+
@pointerleave=${this.handlePointerLeave}
|
|
111
|
+
@pointerup=${this.handlePointerUp}
|
|
77
112
|
>
|
|
78
113
|
<slot name="start"></slot>
|
|
79
114
|
<span part="text">
|
|
80
115
|
<span part="label"><slot>${this.label}</slot></span>
|
|
81
116
|
${this.description
|
|
82
117
|
? html `<span part="description"><slot name="description">${this.description}</slot></span>`
|
|
83
|
-
: html `<slot name="description"></slot>`}
|
|
118
|
+
: html `<slot name="description" @slotchange=${() => this.requestUpdate()}></slot>`}
|
|
84
119
|
</span>
|
|
120
|
+
${alternate && captioned ? html `<span data-alternate hidden>${this.alternateLabel}</span>` : nothing}
|
|
85
121
|
<slot name="end"></slot>
|
|
86
122
|
<slot
|
|
87
123
|
name="indicator"
|
|
@@ -114,6 +150,9 @@ __decorate([
|
|
|
114
150
|
__decorate([
|
|
115
151
|
property()
|
|
116
152
|
], HelixHeadlessOption.prototype, "label", void 0);
|
|
153
|
+
__decorate([
|
|
154
|
+
property()
|
|
155
|
+
], HelixHeadlessOption.prototype, "opens", void 0);
|
|
117
156
|
__decorate([
|
|
118
157
|
property()
|
|
119
158
|
], HelixHeadlessOption.prototype, "rel", void 0);
|
|
@@ -48,11 +48,15 @@ import { type Branch } from '../levels.js';
|
|
|
48
48
|
*
|
|
49
49
|
* @slot - The links, as anchors with fragment hrefs, or lists holding them.
|
|
50
50
|
* @slot heading - A heading above the links, such as "On this page". It names the navigation.
|
|
51
|
+
* @slot indicator - A mark of your own, drawn inside the `indicator` part, where `--indicator-x`, `--indicator-y`, `--indicator-inline-size` and `--indicator-block-size` reach it.
|
|
51
52
|
*
|
|
52
53
|
* @event {HelixActivateEvent} helix-activate - Fired when the section in view changes, with its id as the value. It reports the scroll position and cannot be prevented.
|
|
53
54
|
*
|
|
54
55
|
* @csspart heading - The box holding the `heading` slot, above the navigation.
|
|
55
|
-
* @csspart indicator - The mark that moves to the section in view.
|
|
56
|
+
* @csspart indicator - The mark that moves to the link of the section in view. It carries that link's
|
|
57
|
+
* place and size as `--indicator-x`, `--indicator-y`, `--indicator-inline-size` and
|
|
58
|
+
* `--indicator-block-size`, and `data-placed` once it has been placed, for a mark you draw
|
|
59
|
+
* and animate yourself.
|
|
56
60
|
* @csspart nav - The navigation landmark.
|
|
57
61
|
*/
|
|
58
62
|
export declare class HelixHeadlessOutline extends HelixElement {
|
|
@@ -150,7 +154,7 @@ export declare class HelixHeadlessOutline extends HelixElement {
|
|
|
150
154
|
* nothing was announced, and nothing said why.
|
|
151
155
|
*/
|
|
152
156
|
private get links();
|
|
153
|
-
/** The children that are the content, which is all of them but the heading. */
|
|
157
|
+
/** The children that are the content, which is all of them but the heading and the mark. */
|
|
154
158
|
private get content();
|
|
155
159
|
/**
|
|
156
160
|
* The links of a flat run, which the render puts into lists, or `undefined`
|
|
@@ -8,6 +8,7 @@ import { customElement, elementById, HelixActivateEvent, HelixElement, Indicator
|
|
|
8
8
|
import { html, nothing } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
import { levelOf, nest } from '../levels.js';
|
|
11
|
+
const OWN_SLOTS = new Set(['heading', 'indicator']);
|
|
11
12
|
const isFragment = (element) => element instanceof HTMLAnchorElement && element.getAttribute('href')?.startsWith('#') === true;
|
|
12
13
|
let HelixHeadlessOutline = class HelixHeadlessOutline extends HelixElement {
|
|
13
14
|
constructor() {
|
|
@@ -97,7 +98,7 @@ let HelixHeadlessOutline = class HelixHeadlessOutline extends HelixElement {
|
|
|
97
98
|
: [...element.querySelectorAll('a[href^="#"]')].filter(isFragment));
|
|
98
99
|
}
|
|
99
100
|
get content() {
|
|
100
|
-
return this.knownChildren.filter((child) => child.slot
|
|
101
|
+
return this.knownChildren.filter((child) => !OWN_SLOTS.has(child.slot));
|
|
101
102
|
}
|
|
102
103
|
get run() {
|
|
103
104
|
const content = this.content;
|
|
@@ -238,6 +239,9 @@ let HelixHeadlessOutline = class HelixHeadlessOutline extends HelixElement {
|
|
|
238
239
|
const root = this.renderRoot;
|
|
239
240
|
const heading = root?.querySelector?.('slot[name="heading"]');
|
|
240
241
|
heading?.assign?.(...this.knownChildren.filter((child) => child.slot === 'heading'));
|
|
242
|
+
root
|
|
243
|
+
?.querySelector?.('slot[name="indicator"]')
|
|
244
|
+
?.assign?.(...this.knownChildren.filter((child) => child.slot === 'indicator'));
|
|
241
245
|
const run = this.run;
|
|
242
246
|
if (run) {
|
|
243
247
|
for (const slot of root?.querySelectorAll?.('slot[data-link]') ?? []) {
|
|
@@ -249,7 +253,7 @@ let HelixHeadlessOutline = class HelixHeadlessOutline extends HelixElement {
|
|
|
249
253
|
return;
|
|
250
254
|
}
|
|
251
255
|
const plain = root?.querySelector?.('nav > slot:not([role])');
|
|
252
|
-
plain?.assign?.(...[...this.childNodes].filter((node) => node instanceof Element ? node.slot
|
|
256
|
+
plain?.assign?.(...[...this.childNodes].filter((node) => node instanceof Element ? !OWN_SLOTS.has(node.slot) : node instanceof Text));
|
|
253
257
|
}
|
|
254
258
|
updated(changed) {
|
|
255
259
|
super.updated(changed);
|
|
@@ -269,7 +273,7 @@ let HelixHeadlessOutline = class HelixHeadlessOutline extends HelixElement {
|
|
|
269
273
|
return html `<div part="heading"><slot name="heading" @slotchange=${this.nameByHeading}></slot></div>
|
|
270
274
|
<nav part="nav" aria-label=${this.label} @slotchange=${this.handleSlotChange}>
|
|
271
275
|
${run ? this.renderList(nest(run.map((link) => levelOf(link.getAttribute('data-level'))))) : html `<slot></slot>`}
|
|
272
|
-
<span part="indicator" aria-hidden="true"></span>
|
|
276
|
+
<span part="indicator" aria-hidden="true"><slot name="indicator"></slot></span>
|
|
273
277
|
</nav>`;
|
|
274
278
|
}
|
|
275
279
|
renderList(branches) {
|
|
@@ -96,6 +96,11 @@ declare const HelixHeadlessTable_base: (new (...args: any[]) => import("@fusion.
|
|
|
96
96
|
* | A letter or a word | Moves to the next row whose first cell starts with what you typed. |
|
|
97
97
|
* | Tab | Leaves the table. |
|
|
98
98
|
*
|
|
99
|
+
* When the rows did not load, set `error-text`, such as "The orders did not
|
|
100
|
+
* load", and put a button that tries again in the `error` slot. The line shows
|
|
101
|
+
* after the rows in place of the empty and loading lines, the live region says
|
|
102
|
+
* it, and the table takes the `error` state. Clear it once you try again.
|
|
103
|
+
*
|
|
99
104
|
* The CSS custom properties listed for this component are written by it and
|
|
100
105
|
* read by the stylesheet that draws it. Do not set them. Each holds a number
|
|
101
106
|
* the component computes and CSS cannot, so the stylesheet can draw the table
|
|
@@ -109,6 +114,7 @@ declare const HelixHeadlessTable_base: (new (...args: any[]) => import("@fusion.
|
|
|
109
114
|
*
|
|
110
115
|
* @slot - The table's rows.
|
|
111
116
|
* @slot empty - Content shown when there are no rows and none are loading, such as a picture and a link. Falls back to `empty-text`, which the live region still says.
|
|
117
|
+
* @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.
|
|
112
118
|
* @slot heading - Words that name the table, shown above it, such as a heading.
|
|
113
119
|
* @slot loading - Content shown while the rows load, such as a spinner. Falls back to `loading-text`, which the live region still says.
|
|
114
120
|
*
|
|
@@ -132,6 +138,7 @@ declare const HelixHeadlessTable_base: (new (...args: any[]) => import("@fusion.
|
|
|
132
138
|
* @csspart checkbox-mark-indeterminate - Added to `checkbox-mark` while only some rows are selected.
|
|
133
139
|
* @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, in a `reorderable` table. Hidden from assistive technology.
|
|
134
140
|
* @csspart empty - The text shown when there are no rows.
|
|
141
|
+
* @csspart error - The line shown after the rows while `error-text` is set, in place of the empty and loading text.
|
|
135
142
|
* @csspart grid-end - An empty box after the rows, in a `grid` table. Tab focuses it on its way out of the table, so the key leaves from the end.
|
|
136
143
|
* @csspart grid-start - An empty box before the rows, in a `grid` table. Shift with Tab focuses it on its way out of the table.
|
|
137
144
|
* @csspart grip-head - The heading over the row grips, in a `reorderable` table.
|
|
@@ -142,6 +149,9 @@ declare const HelixHeadlessTable_base: (new (...args: any[]) => import("@fusion.
|
|
|
142
149
|
* @csspart table - The grid.
|
|
143
150
|
*
|
|
144
151
|
* @cssproperty --table-template - The column track list the table works out from its columns. The component writes it inline on the element that reads it, so setting it has no effect. Set `columns` to change the tracks.
|
|
152
|
+
*
|
|
153
|
+
* @cssstate empty - While there are no rows and none are loading.
|
|
154
|
+
* @cssstate error - While `error-text` is set.
|
|
145
155
|
*/
|
|
146
156
|
export declare class HelixHeadlessTable extends HelixHeadlessTable_base {
|
|
147
157
|
/**
|
|
@@ -214,6 +224,16 @@ export declare class HelixHeadlessTable extends HelixHeadlessTable_base {
|
|
|
214
224
|
descendingLabel: string;
|
|
215
225
|
/** What screen readers hear when a held row is dropped, in a `reorderable` table. */
|
|
216
226
|
droppedLabel: string;
|
|
227
|
+
/**
|
|
228
|
+
* The text shown and announced when the rows could not load. Set it when a
|
|
229
|
+
* fetch fails, and clear it once you try again. Empty, the default, shows
|
|
230
|
+
* nothing.
|
|
231
|
+
*
|
|
232
|
+
* While it is set, a line after the rows shows it, or your own content in the
|
|
233
|
+
* `error` slot, in place of the empty and loading lines, and the live region
|
|
234
|
+
* says it.
|
|
235
|
+
*/
|
|
236
|
+
errorText: string;
|
|
217
237
|
/**
|
|
218
238
|
* The table's name where neither `label` nor an `aria-label` or
|
|
219
239
|
* `aria-labelledby` on the host gives it one.
|
|
@@ -44,7 +44,7 @@ const SAMPLE = 200;
|
|
|
44
44
|
const textOf = (element) => (element?.textContent ?? '').replace(/\s+/g, ' ').trim();
|
|
45
45
|
const lengthOf = (element) => textOf(element).length;
|
|
46
46
|
const numberOf = (value) => Number(value.replaceAll(',', ''));
|
|
47
|
-
const STATUS = new Set(['empty', 'loading']);
|
|
47
|
+
const STATUS = new Set(['empty', 'error', 'loading']);
|
|
48
48
|
const NAMED = new Set([...STATUS, 'heading']);
|
|
49
49
|
let HelixHeadlessTable = class HelixHeadlessTable extends MixinCollection(HelixElement) {
|
|
50
50
|
constructor() {
|
|
@@ -57,6 +57,7 @@ let HelixHeadlessTable = class HelixHeadlessTable extends MixinCollection(HelixE
|
|
|
57
57
|
this.defaultSort = '';
|
|
58
58
|
this.descendingLabel = 'descending';
|
|
59
59
|
this.droppedLabel = 'Dropped';
|
|
60
|
+
this.errorText = '';
|
|
60
61
|
this.fallbackLabel = 'Table';
|
|
61
62
|
this.grid = false;
|
|
62
63
|
this.label = '';
|
|
@@ -496,6 +497,8 @@ let HelixHeadlessTable = class HelixHeadlessTable extends MixinCollection(HelixE
|
|
|
496
497
|
}
|
|
497
498
|
updated(changed) {
|
|
498
499
|
super.updated(changed);
|
|
500
|
+
this.toggleState('error', this.errorText !== '');
|
|
501
|
+
this.toggleState('empty', !this.loading && !this.hasRows);
|
|
499
502
|
this.assign();
|
|
500
503
|
this.alignCells();
|
|
501
504
|
this.markSorted();
|
|
@@ -561,8 +564,12 @@ let HelixHeadlessTable = class HelixHeadlessTable extends MixinCollection(HelixE
|
|
|
561
564
|
this.handleSlotChange();
|
|
562
565
|
}}
|
|
563
566
|
></slot>
|
|
564
|
-
${this.
|
|
565
|
-
|
|
567
|
+
${this.errorText === ''
|
|
568
|
+
? this.renderStatus(this.hasRows, grid ? 'gridcell' : 'cell')
|
|
569
|
+
: html `<p part="error" role="row"><span role=${grid ? 'gridcell' : 'cell'}><slot name="error">${this.errorText}</slot></span></p>`}
|
|
570
|
+
</div>${end}${this.errorText === ''
|
|
571
|
+
? this.renderLive(this.hasRows)
|
|
572
|
+
: html `<span part="live" role="status">${this.errorText}</span>`}${this.reorder.drawDragPreview()}`;
|
|
566
573
|
}
|
|
567
574
|
};
|
|
568
575
|
__decorate([
|
|
@@ -590,6 +597,9 @@ __decorate([
|
|
|
590
597
|
words,
|
|
591
598
|
property({ attribute: 'dropped-label' })
|
|
592
599
|
], HelixHeadlessTable.prototype, "droppedLabel", void 0);
|
|
600
|
+
__decorate([
|
|
601
|
+
property({ attribute: 'error-text' })
|
|
602
|
+
], HelixHeadlessTable.prototype, "errorText", void 0);
|
|
593
603
|
__decorate([
|
|
594
604
|
words,
|
|
595
605
|
property({ attribute: 'fallback-label' })
|