@fusion.dev/helix-headless 0.1.0 → 0.3.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 +234 -234
- package/dist/accordion/item/accordion-item.js +0 -108
- package/dist/accordion/root/accordion-root.js +0 -33
- package/dist/announcement/root/announcement-root.js +1 -76
- package/dist/app-header/root/app-header-root.js +0 -84
- package/dist/avatar/group/avatar-group.js +0 -77
- package/dist/avatar/root/avatar-root.js +0 -72
- package/dist/badge/root/badge-root.js +0 -67
- package/dist/bar-chart/root/bar-chart-root.js +3 -263
- package/dist/blockquote/root/blockquote-root.js +0 -46
- package/dist/breadcrumbs/breadcrumb/breadcrumb.js +0 -72
- package/dist/breadcrumbs/root/breadcrumbs-root.js +0 -52
- package/dist/button/root/button-root.js +0 -215
- package/dist/button-group/root/button-group-root.js +0 -30
- package/dist/callout/root/callout-root.js +0 -52
- package/dist/card/root/card-root.js +0 -41
- package/dist/carousel/root/carousel-root.d.ts +2 -2
- package/dist/carousel/root/carousel-root.js +2 -362
- package/dist/chat/composer/chat-composer.js +0 -131
- package/dist/chat/message/chat-message.js +0 -137
- package/dist/chat/root/chat-root.js +0 -193
- package/dist/checkbox/root/checkbox-root.js +0 -113
- package/dist/checkbox-group/root/checkbox-group-root.js +1 -157
- package/dist/code/root/code-root.js +0 -26
- package/dist/code-block/root/code-block-root.js +0 -100
- package/dist/color-input/root/color-input-root.js +0 -217
- package/dist/color-picker/color.js +0 -69
- package/dist/color-picker/root/color-picker-root.js +0 -272
- package/dist/combobox/root/combobox-root.js +0 -428
- package/dist/command/root/command-root.js +0 -177
- package/dist/copy-button/root/copy-button-root.js +0 -134
- package/dist/data-table/root/data-table-root.js +0 -336
- package/dist/data-table/root/order.js +0 -100
- package/dist/date-input/root/date-input-root.js +1 -398
- package/dist/date-picker/root/date-picker-root.js +0 -358
- package/dist/dialog/root/dialog-root.js +0 -189
- package/dist/divider/root/divider-root.js +0 -71
- package/dist/donut-chart/root/donut-chart-root.js +0 -214
- package/dist/drawer/root/drawer-root.js +0 -116
- package/dist/drilldown/item/drilldown-item.js +0 -82
- package/dist/drilldown/root/drilldown-root.js +0 -91
- package/dist/element-events.generated.js +0 -5
- package/dist/empty/root/empty-root.js +0 -35
- package/dist/file-input/root/file-input-root.js +0 -232
- package/dist/format-date/root/format-date-root.js +0 -95
- package/dist/heatmap/root/buckets.js +0 -57
- package/dist/heatmap/root/heatmap-root.js +0 -313
- package/dist/icon/root/icon-root.js +0 -48
- package/dist/icon-button/root/icon-button-root.js +0 -30
- package/dist/image/root/image-root.js +0 -174
- package/dist/index.js +0 -9
- package/dist/input/root/input-root.js +0 -228
- package/dist/kbd/root/kbd-root.js +0 -117
- package/dist/legend/root/legend-root.js +0 -73
- package/dist/line-chart/root/line-chart-root.js +0 -172
- package/dist/list/item/list-item.js +0 -92
- package/dist/list/root/list-root.js +0 -26
- package/dist/listbox/root/listbox-root.js +2 -429
- package/dist/loader/root/loader-root.js +0 -40
- package/dist/megamenu/root/megamenu-root.js +0 -284
- package/dist/menu/item/menu-item.js +1 -221
- package/dist/menu/root/menu-root.js +1 -171
- package/dist/meter/root/meter-root.js +1 -152
- package/dist/navigation/root/navigation-root.js +0 -56
- package/dist/notifications/notification/notification.js +0 -159
- package/dist/notifications/root/notifications-root.js +0 -148
- package/dist/number-input/root/number-input-root.js +1 -263
- package/dist/option/group/option-group.js +0 -26
- package/dist/option/root/option-root.js +0 -157
- package/dist/outline/root/outline-root.js +0 -292
- package/dist/pagination/root/pagination-root.js +0 -361
- package/dist/pagination/size/pagination-size.js +0 -104
- package/dist/pagination/summary/pagination-summary.js +0 -90
- package/dist/pin-input/root/pin-input-root.js +0 -276
- package/dist/popover/root/popover-root.js +0 -103
- package/dist/progress/root/progress-root.js +1 -133
- package/dist/qr-code/root/qr-code-root.js +0 -96
- package/dist/quote/root/quote-root.js +0 -25
- package/dist/radio/group/radio-group.js +0 -81
- package/dist/radio/root/radio-root.js +0 -85
- package/dist/rating/root/rating-root.js +0 -220
- package/dist/scroll-area/root/scroll-area-root.js +0 -100
- package/dist/select/root/select-root.js +2 -218
- package/dist/skeleton/root/skeleton-root.js +0 -20
- package/dist/slider/root/slider-root.js +1 -267
- package/dist/sparkline/root/sparkline-root.js +3 -164
- package/dist/speed-dial/item/speed-dial-item.js +0 -52
- package/dist/speed-dial/root/speed-dial-root.js +1 -116
- package/dist/splitter/root/splitter-root.js +0 -489
- package/dist/stat/group/stat-group.js +0 -18
- package/dist/stat/root/stat-root.js +0 -23
- package/dist/steps/root/steps-root.js +0 -62
- package/dist/steps/step/step.js +0 -131
- package/dist/switch/root/switch-root.js +0 -80
- package/dist/table/cell/table-cell.js +0 -23
- package/dist/table/column/table-column.js +0 -158
- package/dist/table/root/table-root.js +0 -82
- package/dist/table/row/table-row.js +0 -21
- package/dist/tabs/panel/tab-panel.js +0 -49
- package/dist/tabs/root/tabs-root.js +0 -98
- package/dist/tabs/tab/tab.js +0 -59
- package/dist/tag/root/tag-root.js +0 -31
- package/dist/textarea/root/textarea-root.js +0 -162
- package/dist/time-input/root/time-input-root.js +0 -283
- package/dist/time-picker/root/time-picker-root.js +0 -326
- package/dist/timeline/item/timeline-item.js +0 -66
- package/dist/timeline/root/timeline-root.js +0 -41
- package/dist/toast/item/toast-item.js +0 -165
- package/dist/toast/root/toast-root.js +0 -51
- package/dist/toggle/group/toggle-group.js +0 -150
- package/dist/toggle/root/toggle-root.js +0 -78
- package/dist/toolbar/root/toolbar-root.js +0 -116
- package/dist/tooltip/root/tooltip-root.js +0 -427
- package/dist/tour/root/tour-root.js +0 -130
- package/dist/tour/stop/tour-stop.js +1 -127
- package/dist/tree/item/tree-item.js +0 -198
- package/dist/tree/root/tree-root.js +0 -97
- package/dist/trend/root/trend-root.js +0 -53
- package/dist/video/root/video-root.d.ts +8 -12
- package/dist/video/root/video-root.js +5 -756
- package/dist/video-playlist/item/video-playlist-item.js +1 -90
- package/dist/video-playlist/menu/video-playlist-menu.js +1 -132
- package/dist/video-playlist/root/video-playlist-root.js +0 -113
- package/dist/virtual-list/root/virtual-list-root.js +0 -105
- package/jsx.d.ts +1408 -2576
- package/package.json +6 -6
- package/vue.d.ts +1 -1
|
@@ -8,15 +8,9 @@ import { valueAsTime as asTime, customElement, timeAsValue as fromTime, HelixFor
|
|
|
8
8
|
import { nothing } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
import { html } from 'lit/static-html.js';
|
|
11
|
-
/* Side-effect imports, because this renders these in its own shadow root and a
|
|
12
|
-
custom element registers on import. The paths are relative, so this registers
|
|
13
|
-
the headless elements and the styled file's identical imports register the styled
|
|
14
|
-
ones; `tier` picks which names the template asks for. */
|
|
15
11
|
import '../../listbox/index.js';
|
|
16
12
|
import '../../option/index.js';
|
|
17
|
-
/** Whether a string is a time the rest of this component can reason about. */
|
|
18
13
|
export const isTime = (value) => /^([01]\d|2[0-3]):[0-5]\d$/.test(value);
|
|
19
|
-
/** Minutes since midnight, or `null` where it is not a time. */
|
|
20
14
|
export const toMinutes = (value) => {
|
|
21
15
|
if (!isTime(value)) {
|
|
22
16
|
return null;
|
|
@@ -24,195 +18,40 @@ export const toMinutes = (value) => {
|
|
|
24
18
|
const [hours = '0', minutes = '0'] = value.split(':');
|
|
25
19
|
return Number(hours) * 60 + Number(minutes);
|
|
26
20
|
};
|
|
27
|
-
/** Minutes since midnight back to `HH:mm`, which is the value shape throughout. */
|
|
28
21
|
export const fromMinutes = (minutes) => {
|
|
29
22
|
const whole = ((Math.round(minutes) % 1440) + 1440) % 1440;
|
|
30
23
|
return `${String(Math.floor(whole / 60)).padStart(2, '0')}:${String(whole % 60).padStart(2, '0')}`;
|
|
31
24
|
};
|
|
32
|
-
/**
|
|
33
|
-
* A time written the way the reader's locale writes one.
|
|
34
|
-
*
|
|
35
|
-
* `Intl` decides between a twelve and a twenty-four hour clock, because that is
|
|
36
|
-
* a property of the locale rather than of this component: `en-US` gets
|
|
37
|
-
* `2:30 PM` and `en-GB` gets `14:30` without either being configured. There is
|
|
38
|
-
* no flag to force one, deliberately. A product that needs to override its
|
|
39
|
-
* reader's own convention has a locale problem rather than a component one, and
|
|
40
|
-
* the value is 24 hour regardless.
|
|
41
|
-
*/
|
|
42
25
|
export const writeTime = (value, locale) => {
|
|
43
26
|
const minutes = toMinutes(value);
|
|
44
27
|
if (minutes === null) {
|
|
45
28
|
return value;
|
|
46
29
|
}
|
|
47
30
|
const at = new Date(Date.UTC(2000, 0, 1, Math.floor(minutes / 60), minutes % 60));
|
|
48
|
-
/* `timeStyle` rather than `hour` and `minute` assembled by hand. Measured in
|
|
49
|
-
Chrome: `hour: 'numeric'` gives `9:00` for `en-GB`, unpadded, which is not
|
|
50
|
-
how a twenty-four hour clock is written, while `hour: '2-digit'` gives
|
|
51
|
-
`09:00 AM` for `en-US`, which is not how a twelve hour one is. `short` is
|
|
52
|
-
each locale's own answer: `09:00` and `9:00 AM`. */
|
|
53
31
|
return new Intl.DateTimeFormat(locale || undefined, {
|
|
54
32
|
timeStyle: 'short',
|
|
55
33
|
timeZone: 'UTC',
|
|
56
34
|
}).format(at);
|
|
57
35
|
};
|
|
58
|
-
/**
|
|
59
|
-
* Whether the reader's locale writes times on a twelve hour clock.
|
|
60
|
-
*
|
|
61
|
-
* Asked of `Intl` rather than derived from a list of locales, because it is
|
|
62
|
-
* exactly the question `resolvedOptions` answers and the list is long and
|
|
63
|
-
* changes. `en-US` is true, `en-GB`, `fr-FR` and `ja-JP` are false, measured.
|
|
64
|
-
*/
|
|
65
36
|
const isTwelveHour = (locale) => new Intl.DateTimeFormat(locale || undefined, { timeStyle: 'short' }).resolvedOptions().hour12 ===
|
|
66
37
|
true;
|
|
67
|
-
/**
|
|
68
|
-
* @summary Picks a time, from a list of them.
|
|
69
|
-
*
|
|
70
|
-
* @description
|
|
71
|
-
* **A list rather than three wheels**, and the reason is what a list already
|
|
72
|
-
* is. `helix-listbox` brings the keyboard, the typeahead, the scroll into view
|
|
73
|
-
* and the announcement of a set of options, and a time at a step is a set of
|
|
74
|
-
* options. Three little wheels for hours, minutes and a meridiem are three
|
|
75
|
-
* controls to build, three to test, and a tab stop each for something a reader
|
|
76
|
-
* answers once.
|
|
77
|
-
*
|
|
78
|
-
* **A step is what makes it a list at all.** Fifteen minutes is ninety-six rows,
|
|
79
|
-
* which a reader scrolls or types into; one minute is one thousand four hundred
|
|
80
|
-
* and forty, which is a listbox in name only. So the list is for the times a
|
|
81
|
-
* product actually offers, and arbitrary times are the field's job in Input
|
|
82
|
-
* Time, the same division Date Input draws between typing a date and using the
|
|
83
|
-
* calendar.
|
|
84
|
-
*
|
|
85
|
-
* **The value is `HH:mm` on a twenty-four hour clock, always.** That is what a
|
|
86
|
-
* form submits and what `min` and `max` compare against, and it sorts as a
|
|
87
|
-
* string, which is the whole reason the shape is that one. What each row *says*
|
|
88
|
-
* is the reader's own convention, from `Intl`.
|
|
89
|
-
*
|
|
90
|
-
* @since 0.1.0
|
|
91
|
-
* @status experimental
|
|
92
|
-
* @category forms
|
|
93
|
-
* @aliases Time, TimeList
|
|
94
|
-
* @documentation https://helix.fusion.dev/components/time-picker
|
|
95
|
-
*
|
|
96
|
-
* @event {Event} change - Announced once the value has moved.
|
|
97
|
-
* @event {HelixBeforeChangeEvent} helix-before-change - Fired before the time changes. Cancelable: refusing it takes the decision and nothing moves.
|
|
98
|
-
* @event {Event} input - Announced once the value has moved.
|
|
99
|
-
*
|
|
100
|
-
* @csspart column - One of them.
|
|
101
|
-
* @csspart column-empty - Shown when a filter has hidden everything.
|
|
102
|
-
* @csspart column-feedback - What is wrong with it.
|
|
103
|
-
* @csspart column-filter-icon - The mark beside that field.
|
|
104
|
-
* @csspart column-filter-mark - The glyph beside the filter field, where none is slotted.
|
|
105
|
-
* @csspart column-hint - Help under the list.
|
|
106
|
-
* @csspart column-items - The list itself.
|
|
107
|
-
* @csspart column-label - The question, over the list.
|
|
108
|
-
* @csspart column-loading - Shown while the options are still on their way.
|
|
109
|
-
* @csspart column-search - The region holding the field that narrows the list.
|
|
110
|
-
* @csspart column-search-field - The field itself.
|
|
111
|
-
* @csspart columns - The row of columns, in `wheels`.
|
|
112
|
-
* @csspart list - The listbox holding the times, in `list`.
|
|
113
|
-
* @csspart list-empty - Shown when a filter has hidden everything.
|
|
114
|
-
* @csspart list-feedback - What is wrong with it.
|
|
115
|
-
* @csspart list-filter-icon - The mark beside that field.
|
|
116
|
-
* @csspart list-filter-mark - The glyph beside the filter field, where none is slotted.
|
|
117
|
-
* @csspart list-hint - Help under the list.
|
|
118
|
-
* @csspart list-items - The list itself.
|
|
119
|
-
* @csspart list-label - The question, over the list.
|
|
120
|
-
* @csspart list-loading - Shown while the options are still on their way.
|
|
121
|
-
* @csspart list-search - The region holding the field that narrows the list.
|
|
122
|
-
* @csspart list-search-field - The field itself.
|
|
123
|
-
* @csspart option - One time in it. Carries `data-between` inside a range.
|
|
124
|
-
* @csspart option-description - The caption under the words, when there is one.
|
|
125
|
-
* @csspart option-item - The row.
|
|
126
|
-
* @csspart option-label - The words.
|
|
127
|
-
* @csspart option-text - The words and the caption, as one column.
|
|
128
|
-
*/
|
|
129
38
|
let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControllerValue(HelixFormElement) {
|
|
130
39
|
constructor() {
|
|
131
40
|
super(...arguments);
|
|
132
|
-
/**
|
|
133
|
-
* Times that cannot be chosen, comma separated, as `HH:mm`.
|
|
134
|
-
*
|
|
135
|
-
* The counterpart to Date Picker's `disable-dates`, and it exists for the same
|
|
136
|
-
* shape of problem: `min` and `max` take away a span, and what a booking
|
|
137
|
-
* usually has to take away is a scatter. A morning with 09:30 and 11:00
|
|
138
|
-
* already gone is not two bounds.
|
|
139
|
-
*
|
|
140
|
-
* A time not on the step is simply never offered, so it is not an error to
|
|
141
|
-
* name one, and naming every time in the list is how the empty state is
|
|
142
|
-
* reached honestly rather than by handing over an empty range.
|
|
143
|
-
*/
|
|
144
41
|
this.disableTimes = '';
|
|
145
|
-
/**
|
|
146
|
-
* Whether the current selection fails validation.
|
|
147
|
-
*
|
|
148
|
-
* A flag rather than a message, the same split Date Picker draws. A flat
|
|
149
|
-
* picker has no room of its own for a sentence: the field that wraps one owns
|
|
150
|
-
* the label, the hint and the feedback, and a second message inside the panel
|
|
151
|
-
* would be the same error said twice. What is left for the picker is the one
|
|
152
|
-
* thing the field cannot draw from outside, the mark on the answer itself.
|
|
153
|
-
*/
|
|
154
42
|
this.invalid = false;
|
|
155
|
-
/**
|
|
156
|
-
* Accessible name for the list, which has no visible one of its own.
|
|
157
|
-
*/
|
|
158
43
|
this.label = 'Time';
|
|
159
|
-
/**
|
|
160
|
-
* Language tag the times are written in.
|
|
161
|
-
*
|
|
162
|
-
* Absent uses the runtime's own.
|
|
163
|
-
*/
|
|
164
44
|
this.locale = '';
|
|
165
|
-
/**
|
|
166
|
-
* Latest time offered, as `HH:mm`.
|
|
167
|
-
*/
|
|
168
45
|
this.max = '23:59';
|
|
169
|
-
/**
|
|
170
|
-
* Earliest time offered, as `HH:mm`.
|
|
171
|
-
*/
|
|
172
46
|
this.min = '00:00';
|
|
173
|
-
/**
|
|
174
|
-
* Whether the control refuses changes while still submitting its value.
|
|
175
|
-
*
|
|
176
|
-
* **Enforced by refusing every request in one place**, which is the seam this
|
|
177
|
-
* component already has: `requestValue` is what every path here comes through,
|
|
178
|
-
* and gating it once is what stops the next call site from being written
|
|
179
|
-
* without the check.
|
|
180
|
-
*
|
|
181
|
-
* Not `disabled`, which is the confusion worth naming: a disabled control
|
|
182
|
-
* submits nothing and leaves the tab order, and a read-only one submits its
|
|
183
|
-
* value and stays reachable, which is what a form showing a value somebody
|
|
184
|
-
* else set needs.
|
|
185
|
-
*/
|
|
186
47
|
this.readOnly = false;
|
|
187
|
-
/**
|
|
188
|
-
* Whether the form refuses to submit while nothing is chosen.
|
|
189
|
-
*/
|
|
190
48
|
this.required = false;
|
|
191
|
-
/**
|
|
192
|
-
* Takes what the list announced and constrains it to what the mode allows.
|
|
193
|
-
*
|
|
194
|
-
* The listbox is put in `multiple` for both modes, because that is what marks
|
|
195
|
-
* more than one row and toggles on press. A range then differs only in what is
|
|
196
|
-
* kept: two, and a third pick starts again from that third one. Working out
|
|
197
|
-
* which is newest by difference rather than by position, since a toggle can
|
|
198
|
-
* remove as easily as add and the announced list is a set rather than a
|
|
199
|
-
* sequence.
|
|
200
|
-
*/
|
|
201
49
|
this.handleChange = (event) => {
|
|
202
50
|
event.stopPropagation();
|
|
203
|
-
/* The listbox is told as well, which is what grays it and takes it out of
|
|
204
|
-
the tab order, and this refuses at the source so every path in is covered
|
|
205
|
-
by one check.
|
|
206
|
-
`isDisabled` rather than `disabled`, which is only the half a consumer
|
|
207
|
-
wrote: an ancestor `<fieldset disabled>` is the other half. */
|
|
208
51
|
if (this.isDisabled) {
|
|
209
52
|
return;
|
|
210
53
|
}
|
|
211
54
|
const announced = event.detail.value;
|
|
212
|
-
/* The listbox already skips a disabled option, so this is the second lock
|
|
213
|
-
rather than the first: it covers a value arriving from a consumer's own
|
|
214
|
-
dispatch, and it is what makes the refusal a fact about the component
|
|
215
|
-
rather than about the listbox it happens to render. */
|
|
216
55
|
if (announced.split(',').some((one) => this.barred(one.trim()))) {
|
|
217
56
|
return;
|
|
218
57
|
}
|
|
@@ -232,7 +71,6 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
|
|
|
232
71
|
const added = next.find((one) => !before.has(one));
|
|
233
72
|
this.requestValue(added ?? '');
|
|
234
73
|
};
|
|
235
|
-
/** Puts a changed column back together into a whole time. */
|
|
236
74
|
this.moveColumn = (part, to) => {
|
|
237
75
|
if (this.isDisabled) {
|
|
238
76
|
return;
|
|
@@ -243,10 +81,6 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
|
|
|
243
81
|
next = hour * 60 + Number(to);
|
|
244
82
|
}
|
|
245
83
|
else if (part === 'hour') {
|
|
246
|
-
/* On a twelve hour clock the column says 1 to 12 and the meridiem says
|
|
247
|
-
which half, so the two have to be recombined rather than read straight:
|
|
248
|
-
12 AM is midnight and 12 PM is noon, which is the one pair that does not
|
|
249
|
-
follow the pattern. */
|
|
250
84
|
const shown = Number(to);
|
|
251
85
|
const whole = isTwelveHour(this.locale) ? (shown % 12) + (meridiem === 'PM' ? 12 : 0) : shown;
|
|
252
86
|
next = whole * 60 + minute;
|
|
@@ -255,35 +89,14 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
|
|
|
255
89
|
const half = hour % 12;
|
|
256
90
|
next = (half + (to === 'PM' ? 12 : 0)) * 60 + minute;
|
|
257
91
|
}
|
|
258
|
-
/* The wheels build a value from three columns and never reach
|
|
259
|
-
`handleChange`, so the refusal has to be repeated here. Disabling the
|
|
260
|
-
options is not enough on its own: a reader on 09:00 who moves the hour to
|
|
261
|
-
10 lands on 10:00, and 10:00 can be barred while nothing in the hour
|
|
262
|
-
column was. */
|
|
263
92
|
const whole = fromMinutes(next);
|
|
264
93
|
if (this.barred(whole)) {
|
|
265
94
|
return;
|
|
266
95
|
}
|
|
267
96
|
this.requestValue(whole);
|
|
268
97
|
};
|
|
269
|
-
/**
|
|
270
|
-
* How far apart the offered times are, in minutes.
|
|
271
|
-
*
|
|
272
|
-
* Fifteen, which is four rows an hour and ninety-six in a day: enough that a
|
|
273
|
-
* reader scrolls rather than hunts, and few enough that the list is a list.
|
|
274
|
-
* Anything under five is a thousand rows and wants a field instead.
|
|
275
|
-
*/
|
|
276
98
|
this.step = 15;
|
|
277
99
|
}
|
|
278
|
-
/**
|
|
279
|
-
* Every time on offer, as minutes since midnight.
|
|
280
|
-
*
|
|
281
|
-
* Built from `min` upward rather than from midnight, so a list that starts at
|
|
282
|
-
* `09:00` steps from nine rather than offering `09:15` because that is where
|
|
283
|
-
* the quarter hours from midnight happen to land. A step that does not divide
|
|
284
|
-
* the hour is a real thing to want, twenty minutes being the obvious one, and
|
|
285
|
-
* this is the reading that makes it behave.
|
|
286
|
-
*/
|
|
287
100
|
get times() {
|
|
288
101
|
const from = toMinutes(this.min) ?? 0;
|
|
289
102
|
const to = toMinutes(this.max) ?? 1439;
|
|
@@ -294,17 +107,6 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
|
|
|
294
107
|
}
|
|
295
108
|
return found;
|
|
296
109
|
}
|
|
297
|
-
/**
|
|
298
|
-
* Whether a time is barred by name.
|
|
299
|
-
*
|
|
300
|
-
* `barred`, not `disabled`, for the reason Date Picker records: this is a form
|
|
301
|
-
* control and `HelixFormElement.disabled` is a boolean about the whole of it,
|
|
302
|
-
* so a method and a flag cannot share the name.
|
|
303
|
-
*
|
|
304
|
-
* Only the named list. `min` and `max` are not consulted here because they
|
|
305
|
-
* never produce a row to bar: `times` builds the list between them, so a time
|
|
306
|
-
* outside the bounds is absent rather than present and refused.
|
|
307
|
-
*/
|
|
308
110
|
barred(at) {
|
|
309
111
|
return this.disableTimes
|
|
310
112
|
.split(',')
|
|
@@ -312,30 +114,21 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
|
|
|
312
114
|
.filter(Boolean)
|
|
313
115
|
.includes(at);
|
|
314
116
|
}
|
|
315
|
-
/**
|
|
316
|
-
* Whether every offered time in an hour is barred.
|
|
317
|
-
*
|
|
318
|
-
* `shown` is the meridiem the wheel is on, so `1` means 01:00 under AM and
|
|
319
|
-
* 13:00 under PM. Absent is a 24 hour clock, where the number is the hour.
|
|
320
|
-
*/
|
|
321
117
|
wholeHourBarred(shown, half) {
|
|
322
118
|
const whole = half === undefined ? shown : (shown % 12) + (half === 'PM' ? 12 : 0);
|
|
323
119
|
const within = this.times.filter((at) => Math.floor(at / 60) === whole);
|
|
324
120
|
return within.length > 0 && within.every((at) => this.barred(fromMinutes(at)));
|
|
325
121
|
}
|
|
326
|
-
/** Whether every offered time in a half of the clock is barred. */
|
|
327
122
|
halfBarred(half) {
|
|
328
123
|
const within = this.times.filter((at) => (half === 'PM') === at >= 720);
|
|
329
124
|
return within.length > 0 && within.every((at) => this.barred(fromMinutes(at)));
|
|
330
125
|
}
|
|
331
|
-
/** The times held, which is a list whether or not a mode is set. */
|
|
332
126
|
get held() {
|
|
333
127
|
return this.value
|
|
334
128
|
.split(',')
|
|
335
129
|
.map((one) => one.trim())
|
|
336
130
|
.filter(Boolean);
|
|
337
131
|
}
|
|
338
|
-
/** Whether a time falls inside a range, which is what gets banded. */
|
|
339
132
|
between(at) {
|
|
340
133
|
if (this.mode !== 'range') {
|
|
341
134
|
return false;
|
|
@@ -343,41 +136,19 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
|
|
|
343
136
|
const [from, to] = this.held;
|
|
344
137
|
return from !== undefined && to !== undefined && at > from && at < to;
|
|
345
138
|
}
|
|
346
|
-
/** The three readings a wheel shows, from the value or from `min`. */
|
|
347
139
|
get parts() {
|
|
348
140
|
const at = toMinutes(this.value) ?? toMinutes(this.min) ?? 0;
|
|
349
141
|
const hour = Math.floor(at / 60);
|
|
350
142
|
return { hour, meridiem: hour < 12 ? 'AM' : 'PM', minute: at % 60 };
|
|
351
143
|
}
|
|
352
|
-
/** Every whole hour that has at least one offered time in it. */
|
|
353
144
|
get hours() {
|
|
354
145
|
const found = new Set(this.times.map((at) => Math.floor(at / 60)));
|
|
355
146
|
return [...found];
|
|
356
147
|
}
|
|
357
|
-
/** Every minute offered within the hour currently shown. */
|
|
358
148
|
get minutes() {
|
|
359
149
|
const hour = this.parts.hour;
|
|
360
150
|
return this.times.filter((at) => Math.floor(at / 60) === hour).map((at) => at % 60);
|
|
361
151
|
}
|
|
362
|
-
/**
|
|
363
|
-
* Each wheel comes to rest on its own answer.
|
|
364
|
-
*
|
|
365
|
-
* A dial that opens at midnight when it holds ten in the evening is a dial
|
|
366
|
-
* showing the wrong thing, and CSS cannot fix it: `scroll-snap-type` chooses a
|
|
367
|
-
* snap point when something scrolls, and nothing has. Measured before this,
|
|
368
|
-
* with the value at 22:00, the hour wheel sat 288 pixels above its answer.
|
|
369
|
-
*
|
|
370
|
-
* On every update rather than on the first, because the columns re-render as
|
|
371
|
-
* the value moves and the minute column's contents change with the hour.
|
|
372
|
-
*
|
|
373
|
-
* **And it is public, because an update is not the only moment that matters.**
|
|
374
|
-
* Inside a panel this element renders while the panel is still
|
|
375
|
-
* `display: none`, so every box measures zero and there is nothing to center
|
|
376
|
-
* against: measured, the hour wheel stayed 288 pixels off its answer because
|
|
377
|
-
* the only call happened before anything had a size. Whatever shows the panel
|
|
378
|
-
* calls this again afterwards. The guard below makes the early call harmless
|
|
379
|
-
* rather than wrong, which is why it can be left in.
|
|
380
|
-
*/
|
|
381
152
|
reveal() {
|
|
382
153
|
if (this.variant !== 'wheels' || this.mode !== undefined) {
|
|
383
154
|
return;
|
|
@@ -386,34 +157,18 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
|
|
|
386
157
|
column.reveal?.();
|
|
387
158
|
}
|
|
388
159
|
}
|
|
389
|
-
/** Refuses every change while read-only. See `readOnly`. */
|
|
390
160
|
requestValue(next) {
|
|
391
161
|
if (this.readOnly) {
|
|
392
162
|
return false;
|
|
393
163
|
}
|
|
394
164
|
return super.requestValue(next);
|
|
395
165
|
}
|
|
396
|
-
/**
|
|
397
|
-
* Reports "required and empty", or clears it.
|
|
398
|
-
*
|
|
399
|
-
* Written out rather than taken from `MixinField`, which is where the seven
|
|
400
|
-
* form controls get this. That mixin brings a label, a hint and a feedback
|
|
401
|
-
* region with it, and this is a grid rather than a field: it is composed
|
|
402
|
-
* *into* the field that has those, so taking the mixin would give one control
|
|
403
|
-
* two of each. What is shared is five lines, and the anchor is what the
|
|
404
|
-
* browser points its bubble at.
|
|
405
|
-
*/
|
|
406
166
|
reportRequired() {
|
|
407
167
|
const anchor = this.renderRoot?.querySelector('[part="list"]') ?? undefined;
|
|
408
168
|
if (this.required && this.value === '') {
|
|
409
169
|
this.internals.setValidity({ valueMissing: true }, 'Choose a time.', anchor);
|
|
410
170
|
return;
|
|
411
171
|
}
|
|
412
|
-
/* A value the wheels cannot read is bad input, the same as on the calendar:
|
|
413
|
-
`value="not-a-time"` marks no row, and would otherwise report `:valid` and
|
|
414
|
-
be handed to the form. `asTime` rejects the hours and minutes that are
|
|
415
|
-
shaped right and out of range as well as the ones that are not shaped
|
|
416
|
-
right at all. */
|
|
417
172
|
const held = this.held;
|
|
418
173
|
if (held.length > 0 && held.some((one) => asTime(one) === null)) {
|
|
419
174
|
this.internals.setValidity({ badInput: true }, this.mode === undefined
|
|
@@ -423,21 +178,6 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
|
|
|
423
178
|
}
|
|
424
179
|
this.internals.setValidity({});
|
|
425
180
|
}
|
|
426
|
-
/**
|
|
427
|
-
* Reports the times to the form, one entry each.
|
|
428
|
-
*
|
|
429
|
-
* `FormData` and one entry per time, which is `DatePicker.report` exactly.
|
|
430
|
-
* `mode="multiple"` is genuinely several answers, and a server reading
|
|
431
|
-
* `times=09:00,17:00` would have to split it on a convention this library
|
|
432
|
-
* invented; `range` sends its two in order, so start and end arrive as the
|
|
433
|
-
* pair they already are.
|
|
434
|
-
*
|
|
435
|
-
* **Nothing at all without a `name`.** A `FormData` carries its own names and
|
|
436
|
-
* its entries are appended whatever the element is called, so an unnamed
|
|
437
|
-
* picker would submit empty-named times rather than being skipped the way a
|
|
438
|
-
* control reporting a plain string is. That is the same guard, for the same
|
|
439
|
-
* reason, that the calendar's own note spells out.
|
|
440
|
-
*/
|
|
441
181
|
report() {
|
|
442
182
|
const times = this.held;
|
|
443
183
|
if (this.name === '' || this.disabled || times.length === 0) {
|
|
@@ -450,25 +190,6 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
|
|
|
450
190
|
}
|
|
451
191
|
this.internals.setFormValue(data);
|
|
452
192
|
}
|
|
453
|
-
/**
|
|
454
|
-
* Reveals once the wheels have rendered themselves.
|
|
455
|
-
*
|
|
456
|
-
* `updated` is this element's own moment, and each wheel is a `helix-listbox`
|
|
457
|
-
* that was created during this very render: its first update has been
|
|
458
|
-
* scheduled and has not run, so it has no rows and no height and `reveal`
|
|
459
|
-
* measures zero and gives up. Measured on a standalone picker, every column
|
|
460
|
-
* sat at `scrollTop: 0` with the answer nine rows down, which reads as a wheel
|
|
461
|
-
* showing the wrong time rather than as one that failed to scroll.
|
|
462
|
-
*
|
|
463
|
-
* Whatever opens a panel calls `reveal` again afterwards, for the separate
|
|
464
|
-
* reason that method documents. Flat on a page there is no panel and no
|
|
465
|
-
* second call.
|
|
466
|
-
*
|
|
467
|
-
* Safe to run on every update: the listbox stops as soon as the chosen row is
|
|
468
|
-
* within a pixel of center, so a call with nothing to do costs two rect reads
|
|
469
|
-
* and changes nothing. It cannot fight a reader mid-scroll either, since
|
|
470
|
-
* scrolling a wheel changes no property and so schedules no update.
|
|
471
|
-
*/
|
|
472
193
|
async revealWhenSettled() {
|
|
473
194
|
const columns = [
|
|
474
195
|
...(this.renderRoot?.querySelectorAll('[part="column"]') ?? []),
|
|
@@ -476,16 +197,9 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
|
|
|
476
197
|
await Promise.all(columns.map((column) => column.updateComplete));
|
|
477
198
|
this.reveal();
|
|
478
199
|
}
|
|
479
|
-
/* `ariaInvalid` on the host through `ElementInternals`, the same route
|
|
480
|
-
Date Picker takes and for the reason the whole library records: a `role` or a
|
|
481
|
-
state written onto the host as an attribute is one a React consumer's
|
|
482
|
-
server markup never had. */
|
|
483
200
|
willUpdate(changed) {
|
|
484
201
|
super.willUpdate(changed);
|
|
485
202
|
this.internals.ariaInvalid = this.invalid ? 'true' : null;
|
|
486
|
-
/* Here rather than in `updated`, which is where `DatePicker` reports from
|
|
487
|
-
too: the value is known before the render and a form read between the two
|
|
488
|
-
should not see the previous one. */
|
|
489
203
|
this.report();
|
|
490
204
|
}
|
|
491
205
|
updated(changed) {
|
|
@@ -494,23 +208,9 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
|
|
|
494
208
|
this.reveal();
|
|
495
209
|
void this.revealWhenSettled();
|
|
496
210
|
}
|
|
497
|
-
/** Puts the keyboard on the list, which is the only control here. */
|
|
498
211
|
focus(options) {
|
|
499
212
|
this.renderRoot?.querySelector('[part="list"]')?.focus(options);
|
|
500
213
|
}
|
|
501
|
-
/**
|
|
502
|
-
* One wheel, which is a listbox like the whole list is.
|
|
503
|
-
*
|
|
504
|
-
* `protected` rather than `private`, which is the whole of what a seam is
|
|
505
|
-
* here: a tier above has to be able to reach it.
|
|
506
|
-
*
|
|
507
|
-
* `barred` is passed per column rather than read from `disableTimes` here,
|
|
508
|
-
* because a column holds a third of a time and only the caller knows the
|
|
509
|
-
* other two thirds. What that means differs by column and the difference is
|
|
510
|
-
* not a detail: a minute is barred when *this* hour plus it is, and an hour is
|
|
511
|
-
* barred only when every minute in it is, since one free minute is a reason to
|
|
512
|
-
* open the wheel rather than to close it.
|
|
513
|
-
*/
|
|
514
214
|
renderColumn(part, label, values, written, value, barred = () => false) {
|
|
515
215
|
return html `<${this.tag('listbox')}
|
|
516
216
|
part="column"
|
|
@@ -536,9 +236,6 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
|
|
|
536
236
|
const twelve = isTwelveHour(this.locale);
|
|
537
237
|
const { hour, meridiem, minute } = this.parts;
|
|
538
238
|
const shownHour = twelve ? String(hour % 12 === 0 ? 12 : hour % 12) : String(hour);
|
|
539
|
-
/* The hour column reads 1 to 12 on a twelve hour clock, so the whole hours
|
|
540
|
-
on offer are mapped into that half and de-duplicated: 13:00 and 01:00 are
|
|
541
|
-
both "1", and offering it twice is offering a choice that is not one. */
|
|
542
239
|
const hours = twelve
|
|
543
240
|
? [...new Set(this.hours.map((one) => (one % 12 === 0 ? 12 : one % 12)))].sort((a, b) => a - b)
|
|
544
241
|
: this.hours;
|
|
@@ -550,16 +247,6 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
|
|
|
550
247
|
: nothing}
|
|
551
248
|
</div>`;
|
|
552
249
|
}
|
|
553
|
-
/**
|
|
554
|
-
* The flat list of times, for the variant that is not wheels, behind a seam the styled tier can move.
|
|
555
|
-
*
|
|
556
|
-
* A method rather than a fragment inline, so a tier above can render its own,
|
|
557
|
-
* wrap this one, pass it a property the base does not know about, or leave it
|
|
558
|
-
* out, without reimplementing the whole template to do any of them. The tag is
|
|
559
|
-
* interpolated rather than written, so `tier` resolves it: this renders the
|
|
560
|
-
* headless element, and a styled subclass that sets `tier` renders the styled one
|
|
561
|
-
* from the same template.
|
|
562
|
-
*/
|
|
563
250
|
renderList() {
|
|
564
251
|
return html `<${this.tag('listbox')}
|
|
565
252
|
part="list"
|
|
@@ -587,30 +274,17 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
|
|
|
587
274
|
</${this.tag('listbox')}>`;
|
|
588
275
|
}
|
|
589
276
|
render() {
|
|
590
|
-
/* A mode falls back to the list, for the reason the property records: a
|
|
591
|
-
range on wheels is two sets of wheels and a multiple is a set, which a
|
|
592
|
-
wheel cannot hold. */
|
|
593
277
|
if (this.variant === 'wheels' && this.mode === undefined) {
|
|
594
278
|
return this.renderSpinner();
|
|
595
279
|
}
|
|
596
280
|
return html `${this.renderList()}`;
|
|
597
281
|
}
|
|
598
|
-
/**
|
|
599
|
-
* The value as a `Date` on 1 January 1970 UTC, or `null` while there is no time.
|
|
600
|
-
*
|
|
601
|
-
* The platform's own pair on `<input type="time">`, epoch day included: a time
|
|
602
|
-
* is an offset into a day rather than an instant, and putting it on the epoch
|
|
603
|
-
* is what makes `valueAsNumber` come out as milliseconds since midnight rather
|
|
604
|
-
* than as a moment nobody meant. Assigning any date reads the clock off it, so
|
|
605
|
-
* a consumer handing over `new Date()` gets the time of day they expected.
|
|
606
|
-
*/
|
|
607
282
|
get valueAsDate() {
|
|
608
283
|
return asTime(this.value);
|
|
609
284
|
}
|
|
610
285
|
set valueAsDate(date) {
|
|
611
286
|
this.value = fromTime(date);
|
|
612
287
|
}
|
|
613
|
-
/** The value as milliseconds since midnight, or `NaN` while there is no time. */
|
|
614
288
|
get valueAsNumber() {
|
|
615
289
|
return asTime(this.value)?.getTime() ?? Number.NaN;
|
|
616
290
|
}
|
|
@@ -9,76 +9,15 @@ import { circle, record } from '@fusion.dev/helix-icons';
|
|
|
9
9
|
import { html } from 'lit';
|
|
10
10
|
import { property } from 'lit/decorators.js';
|
|
11
11
|
import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
|
|
12
|
-
/**
|
|
13
|
-
* @summary One event on a timeline: when it happened, and what it was.
|
|
14
|
-
*
|
|
15
|
-
* @description
|
|
16
|
-
* **The time is a `<time>`, and only when there is a time to put in it.** Given
|
|
17
|
-
* `datetime`, the label a consumer wrote is wrapped in the element the platform
|
|
18
|
-
* has for exactly this, so the machine-readable instant travels with the human
|
|
19
|
-
* one: a crawler, a reader's own tooling and a copy-paste all get "2026-08-19",
|
|
20
|
-
* where "yesterday" on its own is a word. Given no `datetime`, it stays a span,
|
|
21
|
-
* because a `<time>` with nothing to be precise about is markup pretending.
|
|
22
|
-
*
|
|
23
|
-
* **The connector is this element's, not the list's.** Each entry draws the line
|
|
24
|
-
* that arrives at it and hides it when it is first, which `:host(:first-child)`
|
|
25
|
-
* settles with no index, no counting parent and no script. `Steps` and
|
|
26
|
-
* `Breadcrumbs` both do it this way and for the same reason.
|
|
27
|
-
*
|
|
28
|
-
* `tone` says what kind of event it was, and it is the only thing that colors
|
|
29
|
-
* the mark. It takes `Announcement`'s vocabulary rather than a second one.
|
|
30
|
-
*
|
|
31
|
-
* @since 0.1.0
|
|
32
|
-
* @status stable
|
|
33
|
-
* @category data
|
|
34
|
-
* @aliases Activity item, History entry, Event
|
|
35
|
-
* @documentation https://helix.fusion.dev/components/timeline
|
|
36
|
-
*
|
|
37
|
-
* @slot - What happened.
|
|
38
|
-
* @slot marker - The mark on the line. Falls back to a dot, which is the slot's
|
|
39
|
-
* own fallback content: whether a consumer slotted a mark is a question only the
|
|
40
|
-
* platform can answer, and it answers it at assignment time.
|
|
41
|
-
* @slot time - When it happened, as a reader should see it written.
|
|
42
|
-
*
|
|
43
|
-
* @csspart connector - The line arriving at this entry.
|
|
44
|
-
* @csspart content - What happened, and the time above it.
|
|
45
|
-
* @csspart dot - The mark drawn where nothing was slotted for one.
|
|
46
|
-
* @csspart marker - The mark on the line.
|
|
47
|
-
* @csspart rail - The column holding the mark and the line.
|
|
48
|
-
* @csspart time - When it happened.
|
|
49
|
-
*/
|
|
50
12
|
let HelixHeadlessTimelineItem = class HelixHeadlessTimelineItem extends HelixElement {
|
|
51
13
|
constructor() {
|
|
52
14
|
super(...arguments);
|
|
53
|
-
/**
|
|
54
|
-
* When it happened, machine-readable.
|
|
55
|
-
*
|
|
56
|
-
* Whatever `<time>` accepts, which is a date, a time, both, a duration or a
|
|
57
|
-
* week. Unset, the label is written as it was given and nothing claims to
|
|
58
|
-
* know what instant it names.
|
|
59
|
-
*/
|
|
60
15
|
this.datetime = '';
|
|
61
16
|
}
|
|
62
17
|
willUpdate(changed) {
|
|
63
18
|
super.willUpdate(changed);
|
|
64
19
|
hostAria(this.internals, { role: 'listitem' });
|
|
65
20
|
}
|
|
66
|
-
/**
|
|
67
|
-
* The mark for an entry that slotted none, which the tone decides.
|
|
68
|
-
*
|
|
69
|
-
* **A seam is a `protected render…()` and never a getter.** A slot's fallback
|
|
70
|
-
* answers a consumer and a seam answers the tier above, and a getter answers
|
|
71
|
-
* neither. The check tests for parentheses, `${this.x}` being permitted
|
|
72
|
-
* because a property is not markup a tier would redraw, so a getter returning
|
|
73
|
-
* a template reads as a bare reference and is let through.
|
|
74
|
-
*
|
|
75
|
-
* Toned entries are reporting something, so they take the circle with a
|
|
76
|
-
* center, which is `Steps` drawing the stage a reader is standing on. Untoned
|
|
77
|
-
* ones are just things that happened and take the open circle, which is
|
|
78
|
-
* `Steps` drawing a stage nobody has reached. The same two glyphs in both
|
|
79
|
-
* components rather than two that resemble each other: a shape drawn twice
|
|
80
|
-
* drifts, and these are already the marks a reader has learned one page over.
|
|
81
|
-
*/
|
|
82
21
|
renderMarker() {
|
|
83
22
|
const icon = this.tone === undefined ? circle : record;
|
|
84
23
|
return html `<span part="marker"
|
|
@@ -91,11 +30,6 @@ let HelixHeadlessTimelineItem = class HelixHeadlessTimelineItem extends HelixEle
|
|
|
91
30
|
}
|
|
92
31
|
render() {
|
|
93
32
|
const when = html `<slot name="time"></slot>`;
|
|
94
|
-
/* The time is a sibling of the content rather than the first thing inside
|
|
95
|
-
it, so the two are separate rows and the rail can align its mark to the
|
|
96
|
-
second of them. Nested, the mark could only ever be a fixed distance from
|
|
97
|
-
the top of the entry, which is the time's height on a good day and wrong
|
|
98
|
-
the moment a consumer writes two lines of it. */
|
|
99
33
|
return html `<div part="rail">
|
|
100
34
|
<div
|
|
101
35
|
part="connector"
|
|
@@ -7,47 +7,9 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
import { customElement, HelixElement, hostAria } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
|
-
/**
|
|
11
|
-
* @summary A record of what happened, in the order it happened.
|
|
12
|
-
*
|
|
13
|
-
* @description
|
|
14
|
-
* **Not `Steps`, and the difference is what the thing is for.** A stepper is a
|
|
15
|
-
* process a reader is moving through: it has a stage they are on, stages they
|
|
16
|
-
* have finished, and stages ahead, so it carries `active` and `complete` and
|
|
17
|
-
* numbers itself. A timeline is a record of events that already happened, or are
|
|
18
|
-
* scheduled to. Nothing is current, nothing is complete, and what each entry
|
|
19
|
-
* carries instead is a time. Building this as an appearance of `Steps` would
|
|
20
|
-
* have meant a stepper with no step you are on.
|
|
21
|
-
*
|
|
22
|
-
* What it does take from `Steps` is the connector. Each entry draws the line
|
|
23
|
-
* that arrives at it and hides it when it is first, which needs neither an index
|
|
24
|
-
* nor a parent that counts: `:host(:first-child)` is the whole of it, resolved
|
|
25
|
-
* at layout with no script, so a server's markup is right before anything runs.
|
|
26
|
-
*
|
|
27
|
-
* `role="list"` on the host rather than an `<ol>` in the shadow root, for the
|
|
28
|
-
* reason `Steps` gives: the entries are the consumer's elements in the light
|
|
29
|
-
* DOM, so a list element here would be a list whose items are somewhere else.
|
|
30
|
-
*
|
|
31
|
-
* **It runs down the page and has no horizontal form.** Laid along a line there
|
|
32
|
-
* is no room for the time or the sentence that are the whole reason to reach for
|
|
33
|
-
* a timeline, so what survives is a mark and a short label, and that is a
|
|
34
|
-
* stepper. `Steps` is already horizontal by default and already draws it. A run
|
|
35
|
-
* of stages along a page is `Steps`.
|
|
36
|
-
*
|
|
37
|
-
* @since 0.1.0
|
|
38
|
-
* @status stable
|
|
39
|
-
* @category data
|
|
40
|
-
* @aliases Activity feed, History, Changelog, Audit trail
|
|
41
|
-
* @documentation https://helix.fusion.dev/components/timeline
|
|
42
|
-
*
|
|
43
|
-
* @slot - The entries.
|
|
44
|
-
*/
|
|
45
10
|
let HelixHeadlessTimeline = class HelixHeadlessTimeline extends HelixElement {
|
|
46
11
|
constructor() {
|
|
47
12
|
super(...arguments);
|
|
48
|
-
/**
|
|
49
|
-
* Names the record, for a reader arriving at it out of context.
|
|
50
|
-
*/
|
|
51
13
|
this.label = '';
|
|
52
14
|
}
|
|
53
15
|
render() {
|
|
@@ -55,9 +17,6 @@ let HelixHeadlessTimeline = class HelixHeadlessTimeline extends HelixElement {
|
|
|
55
17
|
}
|
|
56
18
|
willUpdate(changed) {
|
|
57
19
|
super.willUpdate(changed);
|
|
58
|
-
/* On the host through internals rather than as an attribute, so the role is
|
|
59
|
-
there for a screen reader without being markup the server never wrote.
|
|
60
|
-
`Steps` says the same about its own list. */
|
|
61
20
|
hostAria(this.internals, { label: this.label, role: 'list' });
|
|
62
21
|
}
|
|
63
22
|
};
|