@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
|
@@ -34,17 +34,41 @@ declare const HelixHeadlessTimeInput_base: (new (...args: any[]) => import("@fus
|
|
|
34
34
|
* hour, a minute, and AM or PM where the clock has them, each set with digits
|
|
35
35
|
* or the arrow keys. Alt+Down opens the list from a segment.
|
|
36
36
|
*
|
|
37
|
+
* Text that is not a time gives the form an empty value, as a native time
|
|
38
|
+
* field does, and `value` keeps the text so it can be corrected.
|
|
39
|
+
*
|
|
40
|
+
* **Keyboard.** The field is one tab stop, or each segment is with
|
|
41
|
+
* `segmented`, and the trigger is one more.
|
|
42
|
+
*
|
|
43
|
+
* | Key | What it does |
|
|
44
|
+
* |---|---|
|
|
45
|
+
* | Down | In the text field, opens the list on the chosen time. |
|
|
46
|
+
* | Alt with Down | Opens the list, from the text field or a segment. |
|
|
47
|
+
* | Enter | In the text field, takes the typed time and submits the form, as a native field does. In the list, chooses the highlighted time. |
|
|
48
|
+
* | Escape | Closes the list and puts focus back in the field. |
|
|
49
|
+
* | Up, Down on a segment | Steps the segment by one, round from its last value to its first. |
|
|
50
|
+
* | Page Up, Page Down on a segment | Steps by a page: two hours, or fifteen minutes or seconds. |
|
|
51
|
+
* | Home, End on a segment | Goes to the segment's first or last value. |
|
|
52
|
+
* | Digits on a segment | Fill it, and move to the next once it can take no more. |
|
|
53
|
+
* | A letter on the half of the day | Picks the half whose name starts with it, such as A for AM. |
|
|
54
|
+
* | Backspace, Delete on a segment | Empty the segment. |
|
|
55
|
+
* | Left, Right on a segment | Move to the previous or the next segment, along the line of text. |
|
|
56
|
+
*
|
|
57
|
+
* In the list, the keys are `TimePicker`'s.
|
|
58
|
+
*
|
|
37
59
|
* @since 0.1.0
|
|
38
60
|
* @status stable
|
|
39
61
|
* @category forms
|
|
40
62
|
* @aliases TimeField, InputTime
|
|
41
63
|
* @documentation https://fusion.dev/helix/components/time-input
|
|
42
64
|
*
|
|
65
|
+
* @slot end - Content after the time, before the trigger, such as a clear button.
|
|
43
66
|
* @slot feedback - The message that says what is wrong with the value.
|
|
44
67
|
* @slot hint - Help under the field.
|
|
45
68
|
* @slot label - The label, for content richer than the `label` text.
|
|
46
69
|
* @slot panel-footer - Content in the panel under the times, such as a note or an action.
|
|
47
70
|
* @slot panel-header - Content in the panel above the times, such as a heading or a shortcut.
|
|
71
|
+
* @slot start - Content before the time, such as an icon.
|
|
48
72
|
* @slot trigger - The icon on the field that shows a list opens.
|
|
49
73
|
*
|
|
50
74
|
* @event {Event} change - Fired after the value changes.
|
|
@@ -76,6 +100,8 @@ declare const HelixHeadlessTimeInput_base: (new (...args: any[]) => import("@fus
|
|
|
76
100
|
* @csspart picker-column-search-field - The filter field.
|
|
77
101
|
* @csspart picker-columns - The row of columns, in `wheels`.
|
|
78
102
|
* @csspart picker-feedback - The picker's own message, under the times, while the picker's `feedback` is set.
|
|
103
|
+
* @csspart picker-hint - The picker's hint, which Time Input never sets: help under the field is the field's own `hint`.
|
|
104
|
+
* @csspart picker-label - The picker's visible label, which Time Input never draws: the field's `label` names it.
|
|
79
105
|
* @csspart picker-list - The listbox holding the times, in `list`.
|
|
80
106
|
* @csspart picker-list-empty - The text shown when the filter hides every option.
|
|
81
107
|
* @csspart picker-list-error - The list's `error` part, which the list never sets: its times do not load.
|
|
@@ -283,7 +309,12 @@ export declare class HelixHeadlessTimeInput extends HelixHeadlessTimeInput_base
|
|
|
283
309
|
* what says the reader is done.
|
|
284
310
|
*/
|
|
285
311
|
private readonly handleWheelEnter;
|
|
286
|
-
/**
|
|
312
|
+
/**
|
|
313
|
+
* Each time, where `mode` holds several, sent one entry each.
|
|
314
|
+
*
|
|
315
|
+
* One time that is text and no time, such as `2:45 P`, is sent as an empty
|
|
316
|
+
* entry, as a native time field sends a time it cannot read.
|
|
317
|
+
*/
|
|
287
318
|
protected get answers(): readonly string[] | undefined;
|
|
288
319
|
/** What is wrong with the times, which the text input cannot say. */
|
|
289
320
|
protected validate(): string | readonly [keyof ValidityStateFlags, string];
|
|
@@ -146,7 +146,11 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinSegmented
|
|
|
146
146
|
return held.length > 0 && held.every((one) => this.isTime(one)) ? held.join(',') : '';
|
|
147
147
|
}
|
|
148
148
|
get answers() {
|
|
149
|
-
|
|
149
|
+
if (this.mode !== undefined) {
|
|
150
|
+
return this.held;
|
|
151
|
+
}
|
|
152
|
+
const text = this.value.trim();
|
|
153
|
+
return text === '' || this.isTime(text) ? undefined : [''];
|
|
150
154
|
}
|
|
151
155
|
validate() {
|
|
152
156
|
const held = this.held;
|
|
@@ -197,7 +201,8 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinSegmented
|
|
|
197
201
|
column-filter-mark: picker-column-filter-mark, column-hint: picker-column-hint,
|
|
198
202
|
column-items: picker-column-items, column-label: picker-column-label,
|
|
199
203
|
column-live: picker-column-live, column-loading: picker-column-loading, column-search: picker-column-search,
|
|
200
|
-
column-search-field: picker-column-search-field, columns: picker-columns, feedback: picker-feedback,
|
|
204
|
+
column-search-field: picker-column-search-field, columns: picker-columns, feedback: picker-feedback, hint: picker-hint,
|
|
205
|
+
label: picker-label, list: picker-list,
|
|
201
206
|
list-empty: picker-list-empty, list-error: picker-list-error, list-feedback: picker-list-feedback,
|
|
202
207
|
list-filter-icon: picker-list-filter-icon, list-filter-mark: picker-list-filter-mark,
|
|
203
208
|
list-hint: picker-list-hint, list-items: picker-list-items, list-label: picker-list-label,
|
|
@@ -269,10 +274,12 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinSegmented
|
|
|
269
274
|
render() {
|
|
270
275
|
return html `${this.renderLabel()}
|
|
271
276
|
<div part="field" @keydown=${this.handleKeydown}>
|
|
277
|
+
<slot name="start"></slot>
|
|
272
278
|
${this.mode === 'multiple'
|
|
273
279
|
? this.renderTokenRow(this.shownTimes.map((at) => this.renderTag(at)), this.restCount)
|
|
274
280
|
: nothing}
|
|
275
281
|
${this.segmentsShown ? this.renderSegments() : this.renderControl()}
|
|
282
|
+
<slot name="end"></slot>
|
|
276
283
|
<button
|
|
277
284
|
part="trigger"
|
|
278
285
|
type="button"
|
|
@@ -35,12 +35,37 @@ declare const HelixHeadlessTimePicker_base: (new (...args: any[]) => import("@fu
|
|
|
35
35
|
* its choice in markup for the same reason. The list and the wheels both do
|
|
36
36
|
* this, because each wheel is also a listbox.
|
|
37
37
|
*
|
|
38
|
+
* **Keyboard.** The list is one tab stop, and each wheel is one.
|
|
39
|
+
*
|
|
40
|
+
* | Key | What it does |
|
|
41
|
+
* |---|---|
|
|
42
|
+
* | Down, Up | Moves to the next or the previous time, or in a wheel to the next or the previous hour, minute, second, or half of the day. |
|
|
43
|
+
* | Home, End | Moves to the first or the last time, or the first or the last in a wheel. |
|
|
44
|
+
* | Page Down, Page Up | Moves by as many times as the list shows at once. |
|
|
45
|
+
* | Digits | Moves to the next time that starts with what you typed, such as `9` for 9:00. |
|
|
46
|
+
* | Enter, Space | Chooses the highlighted time. Moving chooses too, except under `manual` or with a `mode`. With a `mode`, Space adds or removes the time. |
|
|
47
|
+
* | Right, Left | In the wheels, moves to the next or the previous wheel, mirrored right to left. |
|
|
48
|
+
* | Tab | Moves to the next wheel, then out of the picker. |
|
|
49
|
+
*
|
|
50
|
+
* **ARIA.** The list is a `listbox` named by `label`, a slotted label, the
|
|
51
|
+
* host's `aria-label` or its `aria-labelledby`. A development build warns
|
|
52
|
+
* where all four are empty. The list is described by the hint, or by the
|
|
53
|
+
* message in `feedback` in its place, and carries `aria-multiselectable` with
|
|
54
|
+
* a `mode` and `aria-invalid` while `feedback` says what is wrong. The wheels
|
|
55
|
+
* are a `group` with that name, holding one `listbox` per wheel, named "Hour",
|
|
56
|
+
* "Minute", "Second" and "AM/PM" in the page's language, and each described
|
|
57
|
+
* the same way.
|
|
58
|
+
*
|
|
38
59
|
* @since 0.1.0
|
|
39
60
|
* @status stable
|
|
40
61
|
* @category forms
|
|
41
62
|
* @aliases Time, TimeList
|
|
42
63
|
* @documentation https://fusion.dev/helix/components/time-picker
|
|
43
64
|
*
|
|
65
|
+
* @slot empty - What the list shows when no time falls between `min` and `max`, for content richer than `empty-text`.
|
|
66
|
+
* @slot hint - Help under the times, for content richer than the `hint` text.
|
|
67
|
+
* @slot label - A visible label above the times, which names the list, for content richer than the `label` text.
|
|
68
|
+
*
|
|
44
69
|
* @event {Event} change - Fired after the value changes.
|
|
45
70
|
* @event {HelixBeforeChangeEvent} helix-before-change - Fired before the time changes, and cancelable. Canceling it keeps the current time.
|
|
46
71
|
* @event {Event} input - Fired after the value changes.
|
|
@@ -60,6 +85,8 @@ declare const HelixHeadlessTimePicker_base: (new (...args: any[]) => import("@fu
|
|
|
60
85
|
* @csspart column-search-field - The filter field.
|
|
61
86
|
* @csspart columns - The row of columns, in `wheels`.
|
|
62
87
|
* @csspart feedback - The message under the times, while `feedback` is set.
|
|
88
|
+
* @csspart hint - Help under the times, shown while there is no message in `feedback`.
|
|
89
|
+
* @csspart label - The visible label above the times, drawn while a label is slotted.
|
|
63
90
|
* @csspart list - The listbox holding the times, in `list`.
|
|
64
91
|
* @csspart list-empty - The text shown when the filter hides every option.
|
|
65
92
|
* @csspart list-error - The list's `error` part, which Time Picker never sets: its times do not load.
|
|
@@ -115,6 +142,19 @@ export declare class HelixHeadlessTimePicker extends HelixHeadlessTimePicker_bas
|
|
|
115
142
|
* reached honestly rather than by handing over an empty range.
|
|
116
143
|
*/
|
|
117
144
|
disableTimes: string;
|
|
145
|
+
/**
|
|
146
|
+
* What the list says when no time falls between `min` and `max`.
|
|
147
|
+
*
|
|
148
|
+
* The fallback of the `empty` slot.
|
|
149
|
+
*/
|
|
150
|
+
emptyText: string;
|
|
151
|
+
/**
|
|
152
|
+
* Help under the times, such as the hours a shop is open, read by screen
|
|
153
|
+
* readers as the list's description. A message in `feedback` replaces it.
|
|
154
|
+
*
|
|
155
|
+
* The fallback of the `hint` slot.
|
|
156
|
+
*/
|
|
157
|
+
hint: string;
|
|
118
158
|
/**
|
|
119
159
|
* The clock the times are written on: `h12` for twelve hours with a
|
|
120
160
|
* meridiem, `h23` for twenty-four.
|
|
@@ -134,7 +174,8 @@ export declare class HelixHeadlessTimePicker extends HelixHeadlessTimePicker_bas
|
|
|
134
174
|
/**
|
|
135
175
|
* Accessible name for the list, which has no visible one of its own.
|
|
136
176
|
*
|
|
137
|
-
* An `aria-label` or `aria-labelledby` on the host names the list instead
|
|
177
|
+
* An `aria-label` or `aria-labelledby` on the host names the list instead,
|
|
178
|
+
* and so does a label slotted into `label`, which is also drawn above it.
|
|
138
179
|
*/
|
|
139
180
|
label: string;
|
|
140
181
|
/**
|
|
@@ -473,6 +514,26 @@ export declare class HelixHeadlessTimePicker extends HelixHeadlessTimePicker_bas
|
|
|
473
514
|
* aria-label="Slot">` was a listbox named "Time".
|
|
474
515
|
*/
|
|
475
516
|
private get listName();
|
|
517
|
+
/** Whether a hint is slotted, which draws the `hint` part around it. */
|
|
518
|
+
private hintSlotted;
|
|
519
|
+
/** Whether the hint shows: there is one, and no message stands in its place. */
|
|
520
|
+
private get hinted();
|
|
521
|
+
/** The ids that describe each list: the hint, or the message in its place. */
|
|
522
|
+
private get describedBy();
|
|
523
|
+
/**
|
|
524
|
+
* The hint under the times, or a bare slot while there is none to show,
|
|
525
|
+
* hidden while a message stands in its place, so a slotted hint goes too.
|
|
526
|
+
*/
|
|
527
|
+
private renderHint;
|
|
528
|
+
/** Whether a label is slotted, which draws the `label` part around it. */
|
|
529
|
+
private labelSlotted;
|
|
530
|
+
/** Reads the `label` slot, and the list's name again with it. */
|
|
531
|
+
private readonly handleLabelSlot;
|
|
532
|
+
/**
|
|
533
|
+
* The slotted label, in the `label` part, above the times. A bare slot while
|
|
534
|
+
* nothing is slotted, which draws no box.
|
|
535
|
+
*/
|
|
536
|
+
private renderSlottedLabel;
|
|
476
537
|
/**
|
|
477
538
|
* Hears the host's `aria-label` and `aria-labelledby` change, which no
|
|
478
539
|
* property reports. Made on connection, there being no `MutationObserver` on
|
|
@@ -482,6 +543,7 @@ export declare class HelixHeadlessTimePicker extends HelixHeadlessTimePicker_bas
|
|
|
482
543
|
connectedCallback(): void;
|
|
483
544
|
disconnectedCallback(): void;
|
|
484
545
|
protected willUpdate(changed: PropertyValues): void;
|
|
546
|
+
protected firstUpdated(changed: PropertyValues): void;
|
|
485
547
|
protected updated(changed: PropertyValues): void;
|
|
486
548
|
/**
|
|
487
549
|
* Moves focus to the list, or to the first wheel.
|
|
@@ -4,9 +4,9 @@ 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 { clockLocale, commaList, customElement, dayPeriod, formatWords, HelixFormElement, hostWords, inlineStep, isTwelveHour, MixinControllerValue, MixinPickerFeedback, nearestTime, offeredTimes, reportValues, secondsAsValue, timeBarred, timeFault, valueAsSeconds, wheelParts, wholeHour, words, wordsLocale, writeClock, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { auditName, clockLocale, commaList, customElement, dayPeriod, formatWords, HelixFormElement, hostWords, inlineStep, isTwelveHour, MixinControllerValue, MixinPickerFeedback, nearestTime, offeredTimes, reportValues, secondsAsValue, slotFilled, slottedWords, timeBarred, timeFault, valueAsSeconds, wheelParts, wholeHour, words, wordsLocale, writeClock, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { nothing } from 'lit';
|
|
9
|
-
import { property } from 'lit/decorators.js';
|
|
9
|
+
import { property, state } from 'lit/decorators.js';
|
|
10
10
|
import { html } from 'lit/static-html.js';
|
|
11
11
|
import { wheelName } from '../wheel-name.js';
|
|
12
12
|
import '../../listbox/index.js';
|
|
@@ -16,6 +16,8 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinPickerF
|
|
|
16
16
|
super(...arguments);
|
|
17
17
|
this.badInputMessage = '{mode, select, single {Enter a time as {shape}.} other {Enter times as {shape}, separated by commas.}}';
|
|
18
18
|
this.disableTimes = '';
|
|
19
|
+
this.emptyText = 'No times to choose from';
|
|
20
|
+
this.hint = '';
|
|
19
21
|
this.label = 'Time';
|
|
20
22
|
this.locale = '';
|
|
21
23
|
this.manual = false;
|
|
@@ -83,6 +85,12 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinPickerF
|
|
|
83
85
|
this.unavailableMessage = 'Choose a time that is available.';
|
|
84
86
|
this.valueMissingMessage = 'Choose a time.';
|
|
85
87
|
this.listRevealed = false;
|
|
88
|
+
this.hintSlotted = false;
|
|
89
|
+
this.labelSlotted = false;
|
|
90
|
+
this.handleLabelSlot = (event) => {
|
|
91
|
+
this.labelSlotted = slotFilled(event.target);
|
|
92
|
+
this.requestUpdate();
|
|
93
|
+
};
|
|
86
94
|
this.naming = undefined;
|
|
87
95
|
this.handleColumnsKeydown = (event) => {
|
|
88
96
|
const step = inlineStep(this, event.key);
|
|
@@ -247,7 +255,27 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinPickerF
|
|
|
247
255
|
this.reveal();
|
|
248
256
|
}
|
|
249
257
|
get listName() {
|
|
250
|
-
|
|
258
|
+
const slotted = this.labelSlotted ? slottedWords(this, ['label']) : '';
|
|
259
|
+
return hostWords(this, slotted || this.label);
|
|
260
|
+
}
|
|
261
|
+
get hinted() {
|
|
262
|
+
return (this.hint !== '' || this.hintSlotted) && this.feedback === '';
|
|
263
|
+
}
|
|
264
|
+
get describedBy() {
|
|
265
|
+
return this.feedback !== '' ? 'feedback' : this.hinted ? 'hint' : nothing;
|
|
266
|
+
}
|
|
267
|
+
renderHint() {
|
|
268
|
+
const read = (event) => {
|
|
269
|
+
this.hintSlotted = slotFilled(event.target);
|
|
270
|
+
};
|
|
271
|
+
return this.hinted
|
|
272
|
+
? html `<p part="hint" id="hint"><slot name="hint" @slotchange=${read}>${this.hint}</slot></p>`
|
|
273
|
+
: html `<slot name="hint" ?hidden=${this.feedback !== ''} @slotchange=${read}></slot>`;
|
|
274
|
+
}
|
|
275
|
+
renderSlottedLabel() {
|
|
276
|
+
return this.labelSlotted
|
|
277
|
+
? html `<div part="label"><slot name="label" @slotchange=${this.handleLabelSlot}></slot></div>`
|
|
278
|
+
: html `<slot name="label" @slotchange=${this.handleLabelSlot}></slot>`;
|
|
251
279
|
}
|
|
252
280
|
connectedCallback() {
|
|
253
281
|
super.connectedCallback();
|
|
@@ -264,6 +292,14 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinPickerF
|
|
|
264
292
|
this.report();
|
|
265
293
|
this.toggleState('blank', this.value === '');
|
|
266
294
|
}
|
|
295
|
+
firstUpdated(changed) {
|
|
296
|
+
super.firstUpdated(changed);
|
|
297
|
+
auditName(this, () => null, 'Give it a `label`, a label in the `label` slot, or an `aria-label` that says what the time is for.', () => this.listName !== '');
|
|
298
|
+
const list = this.partNamed('list');
|
|
299
|
+
if (list) {
|
|
300
|
+
auditName(list, () => null, '', () => true);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
267
303
|
updated(changed) {
|
|
268
304
|
super.updated(changed);
|
|
269
305
|
this.reportRequired();
|
|
@@ -284,7 +320,7 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinPickerF
|
|
|
284
320
|
return html `<${this.tag('listbox')}
|
|
285
321
|
part="column"
|
|
286
322
|
data-column=${part}
|
|
287
|
-
aria-describedby=${this.
|
|
323
|
+
aria-describedby=${this.describedBy}
|
|
288
324
|
aria-invalid=${this.marked ? 'true' : nothing}
|
|
289
325
|
exportparts="empty: column-empty, error: column-error, feedback: column-feedback, filter-icon: column-filter-icon,
|
|
290
326
|
filter-mark: column-filter-mark, hint: column-hint, items: column-items, label: column-label,
|
|
@@ -336,7 +372,7 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinPickerF
|
|
|
336
372
|
renderList() {
|
|
337
373
|
return html `<${this.tag('listbox')}
|
|
338
374
|
part="list"
|
|
339
|
-
aria-describedby=${this.
|
|
375
|
+
aria-describedby=${this.describedBy}
|
|
340
376
|
aria-invalid=${this.marked ? 'true' : nothing}
|
|
341
377
|
exportparts="empty: list-empty, error: list-error, feedback: list-feedback, filter-icon: list-filter-icon,
|
|
342
378
|
filter-mark: list-filter-mark, hint: list-hint, items: list-items, label: list-label,
|
|
@@ -344,6 +380,7 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinPickerF
|
|
|
344
380
|
aria-label=${this.listName}
|
|
345
381
|
data-unset=${this.value === '' ? '' : nothing}
|
|
346
382
|
?disabled=${this.isDisabled}
|
|
383
|
+
empty-text=${this.emptyText}
|
|
347
384
|
?manual=${this.manual}
|
|
348
385
|
?multiple=${this.mode !== undefined}
|
|
349
386
|
.value=${this.value}
|
|
@@ -365,13 +402,14 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinPickerF
|
|
|
365
402
|
>${writeClock(at, this.clock, this.seconds)}</${this.tag('option')}
|
|
366
403
|
>`;
|
|
367
404
|
})}
|
|
405
|
+
<slot name="empty" slot="empty">${this.emptyText}</slot>
|
|
368
406
|
</${this.tag('listbox')}>`;
|
|
369
407
|
}
|
|
370
408
|
render() {
|
|
371
409
|
if (this.variant === 'wheels' && this.mode === undefined) {
|
|
372
|
-
return html `${this.renderSpinner()}${this.renderPickerFeedback()}`;
|
|
410
|
+
return html `${this.renderSlottedLabel()}${this.renderSpinner()}${this.renderHint()}${this.renderPickerFeedback()}`;
|
|
373
411
|
}
|
|
374
|
-
return html `${this.renderList()}${this.renderPickerFeedback()}`;
|
|
412
|
+
return html `${this.renderSlottedLabel()}${this.renderList()}${this.renderHint()}${this.renderPickerFeedback()}`;
|
|
375
413
|
}
|
|
376
414
|
get valueAsDate() {
|
|
377
415
|
const at = valueAsSeconds(this.value);
|
|
@@ -397,6 +435,13 @@ __decorate([
|
|
|
397
435
|
__decorate([
|
|
398
436
|
property({ attribute: 'disable-times' })
|
|
399
437
|
], HelixHeadlessTimePicker.prototype, "disableTimes", void 0);
|
|
438
|
+
__decorate([
|
|
439
|
+
words,
|
|
440
|
+
property({ attribute: 'empty-text' })
|
|
441
|
+
], HelixHeadlessTimePicker.prototype, "emptyText", void 0);
|
|
442
|
+
__decorate([
|
|
443
|
+
property()
|
|
444
|
+
], HelixHeadlessTimePicker.prototype, "hint", void 0);
|
|
400
445
|
__decorate([
|
|
401
446
|
property({ attribute: 'hour-cycle' })
|
|
402
447
|
], HelixHeadlessTimePicker.prototype, "hourCycle", void 0);
|
|
@@ -466,6 +511,12 @@ __decorate([
|
|
|
466
511
|
__decorate([
|
|
467
512
|
property({ reflect: true })
|
|
468
513
|
], HelixHeadlessTimePicker.prototype, "variant", void 0);
|
|
514
|
+
__decorate([
|
|
515
|
+
state()
|
|
516
|
+
], HelixHeadlessTimePicker.prototype, "hintSlotted", void 0);
|
|
517
|
+
__decorate([
|
|
518
|
+
state()
|
|
519
|
+
], HelixHeadlessTimePicker.prototype, "labelSlotted", void 0);
|
|
469
520
|
HelixHeadlessTimePicker = __decorate([
|
|
470
521
|
customElement('helix-headless-time-picker')
|
|
471
522
|
], HelixHeadlessTimePicker);
|
|
@@ -8,6 +8,12 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
8
8
|
* machine-readable value such as "2026-08-19" travels with a label such as
|
|
9
9
|
* "yesterday". Without `datetime`, the label is a `<span>`.
|
|
10
10
|
*
|
|
11
|
+
* **`relative` writes the time for you** as how long ago it was, such as "3
|
|
12
|
+
* hours ago", from `datetime`, where the `time` slot is empty. The words move
|
|
13
|
+
* on as they go stale, "3 hours ago" becoming "4 hours ago", with one wakeup
|
|
14
|
+
* shared by every reading on the page, as `FormatDate` keeps its own. They are
|
|
15
|
+
* in the language of the page, or of the nearest `lang`.
|
|
16
|
+
*
|
|
11
17
|
* Each entry draws the connector line that arrives at it and hides it when it
|
|
12
18
|
* is the first entry, using `:host(:first-child)`. This needs no index, no
|
|
13
19
|
* counting parent, and no script. `Steps` and `Breadcrumbs` work the same way.
|
|
@@ -44,6 +50,25 @@ export declare class HelixHeadlessTimelineItem extends HelixElement {
|
|
|
44
50
|
* know what instant it names.
|
|
45
51
|
*/
|
|
46
52
|
datetime: string;
|
|
53
|
+
/**
|
|
54
|
+
* Writes the time as how long ago it was, or how long until it, from
|
|
55
|
+
* `datetime`, where the `time` slot is empty: `long` ("3 hours ago"),
|
|
56
|
+
* `short` ("3 hr. ago") or `narrow` ("3h ago"). Set with no value, it is
|
|
57
|
+
* `long`. The words move on as they go stale.
|
|
58
|
+
*/
|
|
59
|
+
relative?: 'long' | 'narrow' | 'short';
|
|
60
|
+
/** The time the reading is drawn against, moved on when the words go stale. */
|
|
61
|
+
private readonly clock;
|
|
62
|
+
/**
|
|
63
|
+
* What the `time` slot shows where nothing is slotted: the reading under
|
|
64
|
+
* `relative`, read against the clock before each render, and nothing
|
|
65
|
+
* otherwise.
|
|
66
|
+
*/
|
|
67
|
+
private reading;
|
|
68
|
+
/** The instant `datetime` names, where `relative` asks for a reading of it. */
|
|
69
|
+
private get moment();
|
|
70
|
+
/** What the reading says at `now`, and when it stops being true. */
|
|
71
|
+
private readingAt;
|
|
47
72
|
/**
|
|
48
73
|
* The kind of event. It sets the color of the mark and nothing else.
|
|
49
74
|
*/
|
|
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { customElement, HelixElement, hostAria } from '@fusion.dev/helix-core';
|
|
7
|
+
import { customElement, HelixElement, hostAria, RelativeClockController, relativeTime, wordsLocale, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { circle, record } from '@fusion.dev/helix-icons';
|
|
9
9
|
import { html } from 'lit';
|
|
10
10
|
import { property } from 'lit/decorators.js';
|
|
@@ -13,9 +13,31 @@ let HelixHeadlessTimelineItem = class HelixHeadlessTimelineItem extends HelixEle
|
|
|
13
13
|
constructor() {
|
|
14
14
|
super(...arguments);
|
|
15
15
|
this.datetime = '';
|
|
16
|
+
this.clock = new RelativeClockController(this, {
|
|
17
|
+
expires: (now) => {
|
|
18
|
+
const moment = this.moment;
|
|
19
|
+
return moment ? this.readingAt(moment, now).next : undefined;
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
this.reading = '';
|
|
23
|
+
}
|
|
24
|
+
get moment() {
|
|
25
|
+
if (this.relative === undefined || this.datetime === '') {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
const parsed = new Date(this.datetime);
|
|
29
|
+
return Number.isNaN(parsed.getTime()) ? undefined : parsed;
|
|
30
|
+
}
|
|
31
|
+
readingAt(moment, now) {
|
|
32
|
+
return relativeTime(wordsLocale(this), { style: this.relative || 'long' })(moment, now);
|
|
16
33
|
}
|
|
17
34
|
willUpdate(changed) {
|
|
18
35
|
super.willUpdate(changed);
|
|
36
|
+
if (['datetime', 'relative'].some((key) => changed.has(key))) {
|
|
37
|
+
this.clock.tick();
|
|
38
|
+
}
|
|
39
|
+
const moment = this.moment;
|
|
40
|
+
this.reading = moment ? this.readingAt(moment, new Date(this.clock.now)).text : '';
|
|
19
41
|
hostAria(this.internals, { role: 'listitem' });
|
|
20
42
|
}
|
|
21
43
|
renderMarker() {
|
|
@@ -29,7 +51,7 @@ let HelixHeadlessTimelineItem = class HelixHeadlessTimelineItem extends HelixEle
|
|
|
29
51
|
>`;
|
|
30
52
|
}
|
|
31
53
|
render() {
|
|
32
|
-
const when = html `<slot name="time"
|
|
54
|
+
const when = html `<slot name="time">${this.reading}</slot>`;
|
|
33
55
|
return html `<div part="rail">
|
|
34
56
|
${this.renderMarker()}
|
|
35
57
|
</div>
|
|
@@ -46,6 +68,9 @@ let HelixHeadlessTimelineItem = class HelixHeadlessTimelineItem extends HelixEle
|
|
|
46
68
|
__decorate([
|
|
47
69
|
property()
|
|
48
70
|
], HelixHeadlessTimelineItem.prototype, "datetime", void 0);
|
|
71
|
+
__decorate([
|
|
72
|
+
property()
|
|
73
|
+
], HelixHeadlessTimelineItem.prototype, "relative", void 0);
|
|
49
74
|
__decorate([
|
|
50
75
|
property({ reflect: true })
|
|
51
76
|
], HelixHeadlessTimelineItem.prototype, "tone", void 0);
|
|
@@ -3,18 +3,22 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Separable because the table is a convention rather than a fact about this
|
|
5
5
|
* element: Space and `k` play, the arrows scrub and change the volume, `m`
|
|
6
|
-
* mutes, `f` fills the screen,
|
|
7
|
-
*
|
|
6
|
+
* mutes, `f` fills the screen, `c` turns captions on and off, Home, End and
|
|
7
|
+
* the digits go to the start, the end and each tenth, and `j` and `l` jump the
|
|
8
|
+
* distance the bar's own buttons jump. What it needs is the media element and four actions, which is
|
|
8
9
|
* the same handful the media session hands to a headset. Whether the keys are
|
|
9
10
|
* bound at all, and what a taken press does to the event, stay with the element.
|
|
10
11
|
*/
|
|
11
12
|
/** What a press asks the player for, which is all a key can do. */
|
|
12
13
|
export interface KeyActions {
|
|
14
|
+
captions: () => void;
|
|
13
15
|
fullscreen: () => void;
|
|
14
16
|
seek: (seconds: number) => void;
|
|
15
17
|
skipBy: (seconds: number) => void;
|
|
16
18
|
toggle: () => void;
|
|
17
19
|
}
|
|
20
|
+
/** The keys `takeKey` answers, as `aria-keyshortcuts` names them. */
|
|
21
|
+
export declare const PLAYER_KEYS = "Space K ArrowLeft ArrowRight Shift+ArrowLeft Shift+ArrowRight J L ArrowUp ArrowDown M F C Home End 0 1 2 3 4 5 6 7 8 9";
|
|
18
22
|
/**
|
|
19
23
|
* Answers one press, and says whether it was one of these.
|
|
20
24
|
*
|
package/dist/video/root/keys.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { SKIP_SECONDS } from '@fusion.dev/helix-core';
|
|
2
2
|
const PRESSED_BY_SPACE = new Set(['a', 'button', 'select', 'summary', 'textarea']);
|
|
3
3
|
const PRESSED_ROLES = /^(button|checkbox|link|menuitem\w*|option|radio|switch|tab)$/;
|
|
4
|
+
export const PLAYER_KEYS = 'Space K ArrowLeft ArrowRight Shift+ArrowLeft Shift+ArrowRight J L ArrowUp ArrowDown M F C Home End 0 1 2 3 4 5 6 7 8 9';
|
|
4
5
|
export const takeKey = (event, video, actions) => {
|
|
5
|
-
if (event.defaultPrevented) {
|
|
6
|
+
if (event.defaultPrevented || event.ctrlKey || event.metaKey || event.altKey) {
|
|
6
7
|
return false;
|
|
7
8
|
}
|
|
8
9
|
const origin = (event.composedPath?.()[0] ?? event.target);
|
|
@@ -54,7 +55,23 @@ export const takeKey = (event, video, actions) => {
|
|
|
54
55
|
actions.fullscreen();
|
|
55
56
|
break;
|
|
56
57
|
}
|
|
58
|
+
case 'c': {
|
|
59
|
+
actions.captions();
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
case 'Home': {
|
|
63
|
+
actions.seek(0);
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
case 'End': {
|
|
67
|
+
actions.seek(video.duration);
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
57
70
|
default: {
|
|
71
|
+
if (/^[0-9]$/.test(event.key) && Number.isFinite(video.duration)) {
|
|
72
|
+
actions.seek((video.duration * Number(event.key)) / 10);
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
58
75
|
return false;
|
|
59
76
|
}
|
|
60
77
|
}
|
|
@@ -29,8 +29,16 @@ import '../../menu/index.js';
|
|
|
29
29
|
* | Up, Down | Raises or lowers the volume by a tenth. |
|
|
30
30
|
* | `m` | Mutes or unmutes. |
|
|
31
31
|
* | `f` | Enters or leaves fullscreen. |
|
|
32
|
+
* | `c` | Turns captions on or off. |
|
|
33
|
+
* | Home, End | Goes to the start or the end. |
|
|
34
|
+
* | `0` to `9` | Goes to that tenth of the video: `5` is the middle. |
|
|
32
35
|
* | Tab | Moves through the controls on the bar. |
|
|
33
36
|
*
|
|
37
|
+
* A key with Control, Command, or Alt held is left to the browser. The
|
|
38
|
+
* player lists its keys in `aria-keyshortcuts`, and a live region says what a
|
|
39
|
+
* key changed that is not on the focused control: playing or paused, muted,
|
|
40
|
+
* the volume, captions on or off, and where the video is after a seek.
|
|
41
|
+
*
|
|
34
42
|
* On the scrubber and the volume slider, the arrow keys, Page Up, Page Down,
|
|
35
43
|
* Home, and End move the slider, as on any range input.
|
|
36
44
|
*
|
|
@@ -125,7 +133,7 @@ import '../../menu/index.js';
|
|
|
125
133
|
* @csspart forward - The button that skips forward ten seconds.
|
|
126
134
|
* @csspart fullscreen - The button that enters and leaves fullscreen.
|
|
127
135
|
* @csspart header - The region at the top left that holds the `header` slot.
|
|
128
|
-
* @csspart live - A live region that
|
|
136
|
+
* @csspart live - A live region that says the loading text, and what a key changed, such as the volume. Visually hidden in the styled tier.
|
|
129
137
|
* @csspart loading - The text shown while the video waits for more data. Draw the spinner
|
|
130
138
|
* with `::part(loading)::before` in your stylesheet. This tier ships no CSS and renders
|
|
131
139
|
* no extra box for it.
|
|
@@ -187,6 +195,10 @@ export declare class HelixHeadlessVideo extends HelixElement {
|
|
|
187
195
|
* looking at.
|
|
188
196
|
*/
|
|
189
197
|
autoplay: boolean;
|
|
198
|
+
/**
|
|
199
|
+
* What the live region says when `c` turns captions off.
|
|
200
|
+
*/
|
|
201
|
+
captionsHiddenLabel: string;
|
|
190
202
|
/**
|
|
191
203
|
* The name of the captions control when the video has one track. The control
|
|
192
204
|
* turns that track on and off.
|
|
@@ -201,6 +213,10 @@ export declare class HelixHeadlessVideo extends HelixElement {
|
|
|
201
213
|
* tracks.
|
|
202
214
|
*/
|
|
203
215
|
captionsOffLabel: string;
|
|
216
|
+
/**
|
|
217
|
+
* What the live region says when `c` turns captions on.
|
|
218
|
+
*/
|
|
219
|
+
captionsShownLabel: string;
|
|
204
220
|
/**
|
|
205
221
|
* Whether the browser draws its native controls. When set, this element draws
|
|
206
222
|
* no controls of its own.
|
|
@@ -326,6 +342,10 @@ export declare class HelixHeadlessVideo extends HelixElement {
|
|
|
326
342
|
* What the button that opens `overflow-controls` is called.
|
|
327
343
|
*/
|
|
328
344
|
moreLabel: string;
|
|
345
|
+
/**
|
|
346
|
+
* What the live region says when a key mutes the video.
|
|
347
|
+
*/
|
|
348
|
+
mutedLabel: string;
|
|
329
349
|
/**
|
|
330
350
|
* The name of the mute control. The control shows as pressed while the video
|
|
331
351
|
* is muted.
|
|
@@ -353,6 +373,10 @@ export declare class HelixHeadlessVideo extends HelixElement {
|
|
|
353
373
|
* bar: two of the same control is worse than either placement.
|
|
354
374
|
*/
|
|
355
375
|
overflowControls: string;
|
|
376
|
+
/**
|
|
377
|
+
* What the live region says when a key pauses the video.
|
|
378
|
+
*/
|
|
379
|
+
pausedLabel: string;
|
|
356
380
|
/**
|
|
357
381
|
* The name of the play control while pressing it would pause the video.
|
|
358
382
|
*/
|
|
@@ -372,6 +396,10 @@ export declare class HelixHeadlessVideo extends HelixElement {
|
|
|
372
396
|
* this is what a screen reader calls it on arriving.
|
|
373
397
|
*/
|
|
374
398
|
playerLabel: string;
|
|
399
|
+
/**
|
|
400
|
+
* What the live region says when a key plays the video.
|
|
401
|
+
*/
|
|
402
|
+
playingLabel: string;
|
|
375
403
|
/**
|
|
376
404
|
* The name of the play control while pressing it would start the video.
|
|
377
405
|
*/
|
|
@@ -453,6 +481,15 @@ export declare class HelixHeadlessVideo extends HelixElement {
|
|
|
453
481
|
* `{duration}` filled in.
|
|
454
482
|
*/
|
|
455
483
|
timeLabel: string;
|
|
484
|
+
/**
|
|
485
|
+
* What the live region says when a key unmutes the video.
|
|
486
|
+
*/
|
|
487
|
+
unmutedLabel: string;
|
|
488
|
+
/**
|
|
489
|
+
* What the live region says when a key changes the volume. `{percent}` is the
|
|
490
|
+
* volume from 0 to 100, written in the locale.
|
|
491
|
+
*/
|
|
492
|
+
volumeChangedLabel: string;
|
|
456
493
|
/**
|
|
457
494
|
* What the volume slider is called.
|
|
458
495
|
*/
|
|
@@ -773,6 +810,28 @@ export declare class HelixHeadlessVideo extends HelixElement {
|
|
|
773
810
|
* key means and this says that nothing else gets it.
|
|
774
811
|
*/
|
|
775
812
|
private readonly handleKeydown;
|
|
813
|
+
/**
|
|
814
|
+
* What a reader hears after a key, which changed something they may not be
|
|
815
|
+
* looking at: the focus is on the player or a control, and the bar may be
|
|
816
|
+
* hidden.
|
|
817
|
+
*
|
|
818
|
+
* **Said from the video's state after the press**, except for playing,
|
|
819
|
+
* which starts on a promise: whether the video was paused before the press
|
|
820
|
+
* says which way it went. Nothing for fullscreen, which a reader sees and a
|
|
821
|
+
* screen reader reports as the window changing, nor for `c` where there are
|
|
822
|
+
* no captions to turn on.
|
|
823
|
+
*/
|
|
824
|
+
private sayKey;
|
|
825
|
+
/**
|
|
826
|
+
* What the live region says after a key, until the video waits for data,
|
|
827
|
+
* which it says over this.
|
|
828
|
+
*/
|
|
829
|
+
private said;
|
|
830
|
+
/**
|
|
831
|
+
* Puts words in the live region. The same words twice differ by a space no
|
|
832
|
+
* reader hears, since a region whose text does not change is not read again.
|
|
833
|
+
*/
|
|
834
|
+
private say;
|
|
776
835
|
/** The caption tracks among the copies, named as the menu names them. */
|
|
777
836
|
private captions;
|
|
778
837
|
/**
|