@fusion.dev/helix-headless 0.154.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 +3213 -179
- package/dist/accordion/item/accordion-item.d.ts +15 -0
- package/dist/accordion/item/accordion-item.js +12 -1
- package/dist/accordion/root/accordion-root.d.ts +2 -1
- package/dist/accordion/root/accordion-root.js +1 -0
- package/dist/breadcrumbs/breadcrumb/breadcrumb.d.ts +36 -3
- package/dist/breadcrumbs/breadcrumb/breadcrumb.js +42 -2
- package/dist/breadcrumbs/root/breadcrumbs-root.d.ts +15 -5
- package/dist/breadcrumbs/root/breadcrumbs-root.js +29 -4
- package/dist/button-group/root/button-group-root.d.ts +2 -0
- package/dist/button-group/root/button-group-root.js +1 -0
- package/dist/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/color-input/root/color-input-root.d.ts +9 -3
- package/dist/color-input/root/color-input-root.js +3 -1
- package/dist/color-picker/channels.d.ts +5 -3
- package/dist/color-picker/channels.js +79 -21
- package/dist/color-picker/root/color-picker-root.d.ts +58 -5
- package/dist/color-picker/root/color-picker-root.js +75 -13
- package/dist/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 +33 -1
- package/dist/date-input/root/date-input-root.js +9 -2
- package/dist/date-picker/root/date-picker-root.d.ts +43 -13
- package/dist/date-picker/root/date-picker-root.js +54 -31
- package/dist/element-events.generated.js +3 -1
- 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/navigation/root/navigation-root.d.ts +26 -0
- package/dist/navigation/root/navigation-root.js +58 -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/select/root/select-root.d.ts +29 -3
- package/dist/select/root/select-root.js +34 -9
- package/dist/slider/root/slider-root.d.ts +71 -9
- package/dist/slider/root/slider-root.js +129 -42
- package/dist/speed-dial/item/speed-dial-item.d.ts +29 -1
- package/dist/speed-dial/item/speed-dial-item.js +48 -7
- package/dist/speed-dial/root/speed-dial-root.d.ts +32 -6
- package/dist/speed-dial/root/speed-dial-root.js +50 -5
- package/dist/splitter/root/splitter-root.d.ts +22 -0
- package/dist/splitter/root/splitter-root.js +36 -7
- package/dist/steps/reach.d.ts +5 -1
- package/dist/steps/reach.js +2 -2
- package/dist/steps/root/steps-root.d.ts +37 -2
- package/dist/steps/root/steps-root.js +49 -7
- package/dist/steps/step/step.d.ts +6 -0
- package/dist/steps/step/step.js +4 -0
- package/dist/table/root/table-root.d.ts +20 -0
- package/dist/table/root/table-root.js +13 -3
- package/dist/tabs/root/tabs-root.d.ts +37 -0
- package/dist/tabs/root/tabs-root.js +92 -2
- package/dist/tabs/tab/tab.d.ts +38 -2
- package/dist/tabs/tab/tab.js +45 -4
- package/dist/tag/group/tag-group.d.ts +29 -3
- package/dist/tag/group/tag-group.js +42 -11
- package/dist/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/toggle/root/toggle-root.d.ts +2 -0
- package/dist/toggle/root/toggle-root.js +1 -1
- package/dist/tree/item/tree-item.d.ts +10 -0
- package/dist/tree/item/tree-item.js +12 -0
- package/dist/tree/root/tree-root.d.ts +40 -1
- package/dist/tree/root/tree-root.js +120 -4
- package/dist/tree/selection.d.ts +6 -0
- package/dist/tree/selection.js +11 -0
- package/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 +3885 -383
- package/types/vue.d.ts +1045 -107
- package/types/words.d.ts +146 -1
|
@@ -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) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AriaForwardController, HelixFormElement, PopoverController } from '@fusion.dev/helix-core';
|
|
1
|
+
import { AriaForwardController, type DataOptionItem, HelixFormElement, PopoverController } from '@fusion.dev/helix-core';
|
|
2
2
|
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
3
|
import { type HelixHeadlessOption } from '../../option/root/option-root.js';
|
|
4
4
|
import '../../listbox/index.js';
|
|
@@ -13,7 +13,10 @@ declare const HelixHeadlessSelect_base: (new (...args: any[]) => import("@fusion
|
|
|
13
13
|
* place for focus to return to when the list closes.
|
|
14
14
|
*
|
|
15
15
|
* Your options stay in your light DOM and are slotted through into the listbox,
|
|
16
|
-
* so the listbox looks through slots to find them.
|
|
16
|
+
* so the listbox looks through slots to find them. For a list too long to draw
|
|
17
|
+
* whole, set `items` instead: the list draws only the options in sight, as
|
|
18
|
+
* Listbox's `items` does, and the trigger, its keys and the form read the
|
|
19
|
+
* whole set.
|
|
17
20
|
*
|
|
18
21
|
* `Select` owns the value, passes it down to the listbox, and takes changes
|
|
19
22
|
* back, so the value lives in one place.
|
|
@@ -102,6 +105,8 @@ declare const HelixHeadlessSelect_base: (new (...args: any[]) => import("@fusion
|
|
|
102
105
|
*
|
|
103
106
|
* @cssstate invalid - The control has an error to show, from the browser's validation or from your `feedback`.
|
|
104
107
|
* @cssstate open - While the list is open.
|
|
108
|
+
* @cssstate placed-bottom - While the open list sits below the trigger.
|
|
109
|
+
* @cssstate placed-top - While the open list sits above the trigger, where there was no room below.
|
|
105
110
|
*/
|
|
106
111
|
export declare class HelixHeadlessSelect extends HelixHeadlessSelect_base {
|
|
107
112
|
/**
|
|
@@ -145,6 +150,20 @@ export declare class HelixHeadlessSelect extends HelixHeadlessSelect_base {
|
|
|
145
150
|
* anything a consumer sets.
|
|
146
151
|
*/
|
|
147
152
|
focusable: boolean;
|
|
153
|
+
/**
|
|
154
|
+
* The options as data, for a list too long to draw whole: each a value,
|
|
155
|
+
* which is also its words, or a record with a `value`, a `label` and
|
|
156
|
+
* `disabled`. Passed to the list, which draws only the options in sight,
|
|
157
|
+
* as Listbox's `items` does. Set, the options you write as children are
|
|
158
|
+
* not read.
|
|
159
|
+
*
|
|
160
|
+
* The trigger shows the chosen options' labels, the letters and the Left
|
|
161
|
+
* and Right keys on the closed trigger choose over the whole set, and a
|
|
162
|
+
* chosen value whose option leaves `items` drops out of `value`.
|
|
163
|
+
* `value-content` is not called for `items`. Import the Option family as
|
|
164
|
+
* well, whose elements are drawn.
|
|
165
|
+
*/
|
|
166
|
+
items: readonly DataOptionItem[] | undefined;
|
|
148
167
|
/**
|
|
149
168
|
* The open list's name where nothing names the Select, read by screen
|
|
150
169
|
* readers. The list has no visible name.
|
|
@@ -270,8 +289,15 @@ export declare class HelixHeadlessSelect extends HelixHeadlessSelect_base {
|
|
|
270
289
|
* the commit creates the text node it needs.
|
|
271
290
|
*/
|
|
272
291
|
private get display();
|
|
273
|
-
/** The chosen options, in the order they appear. None on a server or while hydrating
|
|
292
|
+
/** The chosen options, in the order they appear. None on a server or while hydrating, and none for `items`. */
|
|
274
293
|
private get chosenOptions();
|
|
294
|
+
/** `items` as the list reads them, built once per array. */
|
|
295
|
+
private data;
|
|
296
|
+
/**
|
|
297
|
+
* Every option the select reads, in order: the records of `items`, or the
|
|
298
|
+
* option elements among its children.
|
|
299
|
+
*/
|
|
300
|
+
private get entries();
|
|
275
301
|
/**
|
|
276
302
|
* What the trigger shows, behind a seam: what `value-content` draws for the
|
|
277
303
|
* chosen options, or their words, or the placeholder.
|