@fusion.dev/helix-headless 0.155.0 → 0.157.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 +2695 -137
- 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/drilldown/item/drilldown-item.d.ts +49 -1
- package/dist/drilldown/item/drilldown-item.js +77 -17
- package/dist/drilldown/root/drilldown-root.d.ts +45 -5
- package/dist/drilldown/root/drilldown-root.js +51 -5
- package/dist/element-events.generated.js +2 -1
- package/dist/filter-input/menu/filter-menu.d.ts +1 -0
- package/dist/filter-input/menu/filter-menu.js +4 -4
- 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/megamenu/root/megamenu-root.d.ts +50 -13
- package/dist/megamenu/root/megamenu-root.js +44 -6
- package/dist/menu/item/menu-item.d.ts +24 -1
- package/dist/menu/item/menu-item.js +42 -2
- package/dist/menu/root/menu-root.d.ts +120 -9
- package/dist/menu/root/menu-root.js +329 -26
- package/dist/notifications/root/notifications-root.d.ts +20 -0
- package/dist/notifications/root/notifications-root.js +32 -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/toast/item/toast-item.d.ts +26 -0
- package/dist/toast/item/toast-item.js +55 -3
- package/dist/toast/root/toast-root.d.ts +32 -0
- package/dist/toast/root/toast-root.js +47 -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 +61 -1
- package/dist/video/root/video-root.js +79 -11
- 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/menu/video-playlist-menu.d.ts +1 -0
- package/dist/video-playlist/menu/video-playlist-menu.js +1 -1
- 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 +3346 -266
- package/types/vue.d.ts +910 -66
- package/types/words.d.ts +124 -0
|
@@ -25,17 +25,24 @@ declare const HelixHeadlessNumberInput_base: (new (...args: any[]) => import("@f
|
|
|
25
25
|
* The locale's group separators are read in every field, so `1,234` is a
|
|
26
26
|
* number in `en-US`.
|
|
27
27
|
*
|
|
28
|
-
*
|
|
29
|
-
* `role
|
|
30
|
-
*
|
|
31
|
-
*
|
|
28
|
+
* The field and its two buttons sit in a `role="group"`, and the field carries
|
|
29
|
+
* `role-label`, "number field" in English, as its `aria-roledescription`, as
|
|
30
|
+
* React Aria's NumberField does. On iOS and iPadOS the field is a plain text
|
|
31
|
+
* field, with no `role="spinbutton"`, which VoiceOver reads as a slider you
|
|
32
|
+
* cannot type in, and no role description, so VoiceOver says the field is
|
|
33
|
+
* required. React Aria's NumberField does the same.
|
|
32
34
|
*
|
|
33
35
|
* Validity follows the platform's rules: `min`, `max`, and `step` are checked
|
|
34
36
|
* and reported through the host's own `setValidity`, under the platform's flag
|
|
35
37
|
* names.
|
|
36
38
|
*
|
|
37
39
|
* Stepping is separate from typing, so a number is not corrected while you type
|
|
38
|
-
* it.
|
|
40
|
+
* it. While you type, the form holds the number the text reads as, so a field
|
|
41
|
+
* showing `$2,500.50` gives the form 2500.5, and text that is not a number
|
|
42
|
+
* gives it nothing, as a native number field does.
|
|
43
|
+
*
|
|
44
|
+
* Holding a stepper button down steps once, then again after 400 milliseconds,
|
|
45
|
+
* then every 60 milliseconds until you let go, as React Aria's NumberField does.
|
|
39
46
|
*
|
|
40
47
|
* **Keyboard.** The field is one tab stop, and the stepper buttons are not in
|
|
41
48
|
* the tab order.
|
|
@@ -56,9 +63,11 @@ declare const HelixHeadlessNumberInput_base: (new (...args: any[]) => import("@f
|
|
|
56
63
|
* @aliases NumberField, Stepper, InputNumber
|
|
57
64
|
* @documentation https://fusion.dev/helix/components/number-input
|
|
58
65
|
*
|
|
66
|
+
* @slot decrement-icon - Replaces the icon on the step down button.
|
|
59
67
|
* @slot end - Content after the number, such as a unit.
|
|
60
68
|
* @slot feedback - The message that says what is wrong, for content richer than a string. It shows while there is a message.
|
|
61
69
|
* @slot hint - Help under the control, for content richer than the `hint` text.
|
|
70
|
+
* @slot increment-icon - Replaces the icon on the step up button.
|
|
62
71
|
* @slot label - The label, for content richer than the `label` text.
|
|
63
72
|
* @slot start - Content before the number, such as a unit or an icon.
|
|
64
73
|
*
|
|
@@ -70,7 +79,7 @@ declare const HelixHeadlessNumberInput_base: (new (...args: any[]) => import("@f
|
|
|
70
79
|
* @csspart decrement - The button that steps the value down.
|
|
71
80
|
* @csspart decrement-mark - The icon on the step down button.
|
|
72
81
|
* @csspart feedback - The message that says what is wrong.
|
|
73
|
-
* @csspart field - The box that holds the whole control.
|
|
82
|
+
* @csspart field - The box that holds the whole control, a `role="group"` around the field and its buttons.
|
|
74
83
|
* @csspart hint - Help text under the field, shown while there is no feedback.
|
|
75
84
|
* @csspart increment - The button that steps the value up.
|
|
76
85
|
* @csspart increment-mark - The icon on the step up button.
|
|
@@ -175,6 +184,15 @@ export declare class HelixHeadlessNumberInput extends HelixHeadlessNumberInput_b
|
|
|
175
184
|
* Prevents editing. The value is still submitted with the form.
|
|
176
185
|
*/
|
|
177
186
|
readOnly: boolean;
|
|
187
|
+
/**
|
|
188
|
+
* The words screen readers announce for the field's role, set as its
|
|
189
|
+
* `aria-roledescription`. Translate it with the rest of your text, or empty
|
|
190
|
+
* it to leave the role as `spinbutton`.
|
|
191
|
+
*
|
|
192
|
+
* Never written on iOS and iPadOS, where a role description stops VoiceOver
|
|
193
|
+
* saying that the field is required.
|
|
194
|
+
*/
|
|
195
|
+
roleLabel: string;
|
|
178
196
|
/**
|
|
179
197
|
* Makes the label a grip that changes the value when it is dragged.
|
|
180
198
|
*
|
|
@@ -292,6 +310,26 @@ export declare class HelixHeadlessNumberInput extends HelixHeadlessNumberInput_b
|
|
|
292
310
|
* having to repeat both.
|
|
293
311
|
*/
|
|
294
312
|
stepDown(large?: boolean): void;
|
|
313
|
+
/** The timer of a held stepper button. */
|
|
314
|
+
private repeat;
|
|
315
|
+
/** Whether a pointer press stepped already, so the click that ends it does not step again. */
|
|
316
|
+
private pressStepped;
|
|
317
|
+
/**
|
|
318
|
+
* A stepper button pressed with a pointer steps at once, and again while it
|
|
319
|
+
* is held.
|
|
320
|
+
*
|
|
321
|
+
* Captured, so letting go outside the button still ends the hold. A click
|
|
322
|
+
* with no press before it, from a screen reader or a script, steps once in
|
|
323
|
+
* `handleStepperClick`.
|
|
324
|
+
*/
|
|
325
|
+
private readonly handleStepperDown;
|
|
326
|
+
/**
|
|
327
|
+
* Ends a hold. The click that follows a release arrives in the same task,
|
|
328
|
+
* so the press is forgotten after it.
|
|
329
|
+
*/
|
|
330
|
+
private readonly stopRepeat;
|
|
331
|
+
private readonly handleStepperClick;
|
|
332
|
+
disconnectedCallback(): void;
|
|
295
333
|
/**
|
|
296
334
|
* The label is the surface, which is where the convention already is.
|
|
297
335
|
*
|
|
@@ -391,6 +429,17 @@ export declare class HelixHeadlessNumberInput extends HelixHeadlessNumberInput_b
|
|
|
391
429
|
* what a reader pressing them meant.
|
|
392
430
|
*/
|
|
393
431
|
private readonly handleKeydown;
|
|
432
|
+
/**
|
|
433
|
+
* What the form holds for the field: the number the text reads as.
|
|
434
|
+
*
|
|
435
|
+
* The text as written where it is already a plain number, so a value of
|
|
436
|
+
* `0.50` is sent as written. A number written in the locale or the format,
|
|
437
|
+
* `$2,500.50` or `1.234,5`, is sent as the number, 2500.5 or 1234.5, so a
|
|
438
|
+
* form read while the field still has focus gets what a submit would. Text
|
|
439
|
+
* that is not a number is sent as empty, as a native number field sends it.
|
|
440
|
+
* The text itself is the state the browser restores.
|
|
441
|
+
*/
|
|
442
|
+
private submitted;
|
|
394
443
|
/**
|
|
395
444
|
* Reports the value and a verdict on it.
|
|
396
445
|
*
|
|
@@ -433,6 +482,14 @@ export declare class HelixHeadlessNumberInput extends HelixHeadlessNumberInput_b
|
|
|
433
482
|
private readonly handleKeypress;
|
|
434
483
|
/** @internal A single line of text counts against implicit submission. */
|
|
435
484
|
protected get blocksImplicitSubmission(): boolean;
|
|
485
|
+
/**
|
|
486
|
+
* The step down button's icon, as the `decrement-icon` slot's fallback, so an
|
|
487
|
+
* icon of your own replaces it by being slotted, and a tier above by
|
|
488
|
+
* overriding this.
|
|
489
|
+
*/
|
|
490
|
+
protected renderDecrementIcon(): TemplateResult;
|
|
491
|
+
/** The step up button's icon, behind the same seam as `renderDecrementIcon`. */
|
|
492
|
+
protected renderIncrementIcon(): TemplateResult;
|
|
436
493
|
protected render(): TemplateResult;
|
|
437
494
|
/**
|
|
438
495
|
* The value as a number, or `NaN` while the control is empty.
|
|
@@ -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) {
|