@fusion.dev/helix-headless 0.2.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
|
@@ -10,184 +10,29 @@ import { nothing } from 'lit';
|
|
|
10
10
|
import { property, state } from 'lit/decorators.js';
|
|
11
11
|
import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
|
|
12
12
|
import { html } from 'lit/static-html.js';
|
|
13
|
-
/* Side-effect imports, because this renders these in its own shadow root and a
|
|
14
|
-
custom element registers on import. Base elements here and styled ones in the
|
|
15
|
-
styled file, the arrangement Date Input has with its calendar. */
|
|
16
13
|
import '../../icon/index.js';
|
|
17
14
|
import '../../tag/index.js';
|
|
18
15
|
import '../../time-picker/index.js';
|
|
19
16
|
import { isTime, toMinutes, writeTime } from '../../time-picker/root/time-picker-root.js';
|
|
20
|
-
/**
|
|
21
|
-
* @summary A time, typed or picked from a list.
|
|
22
|
-
*
|
|
23
|
-
* @description
|
|
24
|
-
* **The list is `helix-time-picker`, not a second one written here.** It is worth
|
|
25
|
-
* having on its own, flat in a settings pane with nothing to open, and this
|
|
26
|
-
* element is the field, the overlay and the form. Exactly the arrangement Input
|
|
27
|
-
* Date has with `helix-date-picker` and Color Input with `helix-color-picker`.
|
|
28
|
-
*
|
|
29
|
-
* **Typed as well as picked, and that is the division the list depends on.** A
|
|
30
|
-
* list at a step offers the times a product actually has, and the field is how a
|
|
31
|
-
* reader says `09:07`. Neither is a worse version of the other: without the
|
|
32
|
-
* field a step is a cage, and without the list every common time is typed out.
|
|
33
|
-
*
|
|
34
|
-
* **The value is `HH:mm` on a twenty-four hour clock, always**, because that is
|
|
35
|
-
* what a form submits, what `min` and `max` compare against, and what sorts as a
|
|
36
|
-
* string. What the field *shows* is the reader's own convention while it is
|
|
37
|
-
* idle, and the value itself once the caret is in it: the same separation Input
|
|
38
|
-
* Date draws, and for the same reason, which is that `2:30 PM` cannot be typed
|
|
39
|
-
* back and `Intl` has no parser to make it so.
|
|
40
|
-
*
|
|
41
|
-
* @since 0.1.0
|
|
42
|
-
* @status experimental
|
|
43
|
-
* @category forms
|
|
44
|
-
* @aliases TimeField, InputTime
|
|
45
|
-
* @documentation https://helix.fusion.dev/components/time-input
|
|
46
|
-
*
|
|
47
|
-
* @slot feedback - What is wrong with it.
|
|
48
|
-
* @slot hint - Help under the field.
|
|
49
|
-
* @slot trigger - The mark on the field saying a list opens.
|
|
50
|
-
*
|
|
51
|
-
* @event {Event} change - Announced once the value has moved.
|
|
52
|
-
* @event {HelixBeforeChangeEvent} helix-before-change - Fired before the time changes. Cancelable: refusing it takes the decision and nothing moves.
|
|
53
|
-
* @event {HelixBeforeCloseEvent} helix-before-close - Fired before it closes. Cancelable: refusing it takes the decision and nothing moves.
|
|
54
|
-
* @event {HelixBeforeOpenEvent} helix-before-open - Fired before the list opens. Cancelable: refusing it takes the decision and nothing moves.
|
|
55
|
-
* @event {Event} input - Announced once the value has moved.
|
|
56
|
-
* @event {Event} toggle - Says the state moved, carrying `newState` and `oldState`.
|
|
57
|
-
*
|
|
58
|
-
* @csspart control - The text input.
|
|
59
|
-
* @csspart feedback - What is wrong with it.
|
|
60
|
-
* @csspart field - The box holding the text and the trigger.
|
|
61
|
-
* @csspart hint - Help under the field, while nothing is wrong.
|
|
62
|
-
* @csspart label - The question, over the field.
|
|
63
|
-
* @csspart picker - The list inside it.
|
|
64
|
-
* @csspart picker-column - One of them.
|
|
65
|
-
* @csspart picker-column-empty - Shown when a filter has hidden everything.
|
|
66
|
-
* @csspart picker-column-feedback - What is wrong with it.
|
|
67
|
-
* @csspart picker-column-filter-icon - The mark beside that field.
|
|
68
|
-
* @csspart picker-column-filter-mark - The glyph beside the filter field, where none is slotted.
|
|
69
|
-
* @csspart picker-column-hint - Help under the list.
|
|
70
|
-
* @csspart picker-column-items - The list itself.
|
|
71
|
-
* @csspart picker-column-label - The question, over the list.
|
|
72
|
-
* @csspart picker-column-loading - Shown while the options are still on their way.
|
|
73
|
-
* @csspart picker-column-search - The region holding the field that narrows the list.
|
|
74
|
-
* @csspart picker-column-search-field - The field itself.
|
|
75
|
-
* @csspart picker-columns - The row of columns, in `wheels`.
|
|
76
|
-
* @csspart picker-list - The listbox holding the times, in `list`.
|
|
77
|
-
* @csspart picker-list-empty - Shown when a filter has hidden everything.
|
|
78
|
-
* @csspart picker-list-feedback - What is wrong with it.
|
|
79
|
-
* @csspart picker-list-filter-icon - The mark beside that field.
|
|
80
|
-
* @csspart picker-list-filter-mark - The glyph beside the filter field, where none is slotted.
|
|
81
|
-
* @csspart picker-list-hint - Help under the list.
|
|
82
|
-
* @csspart picker-list-items - The list itself.
|
|
83
|
-
* @csspart picker-list-label - The question, over the list.
|
|
84
|
-
* @csspart picker-list-loading - Shown while the options are still on their way.
|
|
85
|
-
* @csspart picker-list-search - The region holding the field that narrows the list.
|
|
86
|
-
* @csspart picker-list-search-field - The field itself.
|
|
87
|
-
* @csspart picker-option - One time in it. Carries `data-between` inside a range.
|
|
88
|
-
* @csspart picker-option-description - The caption under the words, when there is one.
|
|
89
|
-
* @csspart picker-option-item - The row.
|
|
90
|
-
* @csspart picker-option-label - The words.
|
|
91
|
-
* @csspart picker-option-text - The words and the caption, as one column.
|
|
92
|
-
* @csspart rest - How many times are held beyond the ones shown.
|
|
93
|
-
* @csspart surface - The floating panel.
|
|
94
|
-
* @csspart time - One held time, as a tag, in `multiple`.
|
|
95
|
-
* @csspart time-dismiss - The remove button, when `dismissible`.
|
|
96
|
-
* @csspart time-dismiss-mark - The glyph on the dismiss control, where none is slotted.
|
|
97
|
-
* @csspart time-prefix - The category before the label.
|
|
98
|
-
* @csspart trigger - The control that opens the list.
|
|
99
|
-
* @csspart trigger-icon - The mark on the button that opens the clock.
|
|
100
|
-
*/
|
|
101
17
|
let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinControllerOpen(MixinControllerValue(MixinField(HelixFormElement))) {
|
|
102
18
|
constructor() {
|
|
103
19
|
super(...arguments);
|
|
104
|
-
/* A consumer's `aria-describedby` names ids in their own tree and the control
|
|
105
|
-
is in this shadow root, so the reference has to be carried down as an
|
|
106
|
-
element rather than copied as an id. See `AriaForwardController`: measured
|
|
107
|
-
in Chrome's own tree, the host held the description and the control the
|
|
108
|
-
reader is actually on was exposed without it. */
|
|
109
20
|
this.aria = new AriaForwardController(this, {
|
|
110
21
|
target: () => this.renderRoot?.querySelector('[part="control"]') ?? null,
|
|
111
22
|
});
|
|
112
|
-
/**
|
|
113
|
-
* What the browser may autofill here, as one of the platform's own tokens.
|
|
114
|
-
*
|
|
115
|
-
* Empty leaves the attribute off, which is the browser's default and is what
|
|
116
|
-
* lets autofill work at all.
|
|
117
|
-
*/
|
|
118
23
|
this.autocomplete = '';
|
|
119
|
-
/**
|
|
120
|
-
* Language tag the times are written in.
|
|
121
|
-
*
|
|
122
|
-
* Absent uses the runtime's own.
|
|
123
|
-
*/
|
|
124
24
|
this.locale = '';
|
|
125
|
-
/**
|
|
126
|
-
* Latest time that can be chosen, as `HH:mm`.
|
|
127
|
-
*/
|
|
128
25
|
this.max = '23:59';
|
|
129
|
-
/**
|
|
130
|
-
* How many times are shown as tags before the rest are counted, in `multiple`.
|
|
131
|
-
*
|
|
132
|
-
* `0` is all of them, and is the default, for the reason Date Input gives: how
|
|
133
|
-
* many fit depends on the width of the field and the length of what is in it,
|
|
134
|
-
* and this element knows neither.
|
|
135
|
-
*/
|
|
136
26
|
this.maxTags = 0;
|
|
137
|
-
/**
|
|
138
|
-
* Earliest time that can be chosen, as `HH:mm`.
|
|
139
|
-
*/
|
|
140
27
|
this.min = '00:00';
|
|
141
|
-
/**
|
|
142
|
-
* What the field says before a time is in it.
|
|
143
|
-
*/
|
|
144
28
|
this.placeholder = 'HH:mm';
|
|
145
|
-
/**
|
|
146
|
-
* Whether the control refuses changes while still submitting its value.
|
|
147
|
-
*
|
|
148
|
-
* **Enforced by refusing every request in one place**, which is the seam this
|
|
149
|
-
* component already has: `requestValue` is what every path here comes through,
|
|
150
|
-
* and gating it once is what stops the next call site from being written
|
|
151
|
-
* without the check.
|
|
152
|
-
*
|
|
153
|
-
* Not `disabled`, which is the confusion worth naming: a disabled control
|
|
154
|
-
* submits nothing and leaves the tab order, and a read-only one submits its
|
|
155
|
-
* value and stays reachable, which is what a form showing a value somebody
|
|
156
|
-
* else set needs.
|
|
157
|
-
*/
|
|
158
29
|
this.readOnly = false;
|
|
159
|
-
/**
|
|
160
|
-
* How far apart the offered times are, in minutes.
|
|
161
|
-
*/
|
|
162
30
|
this.step = 15;
|
|
163
|
-
/**
|
|
164
|
-
* Whether the field writes the time out, or holds the value as it is.
|
|
165
|
-
*
|
|
166
|
-
* `local` is the reader's own convention from `Intl`, which is what a time is
|
|
167
|
-
* for reading; `iso` is `HH:mm`, which is what it is for typing. The field
|
|
168
|
-
* swaps to the value while the caret is in it either way, so the rendering is
|
|
169
|
-
* never something this element would refuse to read back.
|
|
170
|
-
*/
|
|
171
31
|
this.timeStyle = 'local';
|
|
172
|
-
/**
|
|
173
|
-
* Accessible name for the control that opens the list.
|
|
174
|
-
*/
|
|
175
32
|
this.triggerLabel = 'Choose a time';
|
|
176
|
-
/**
|
|
177
|
-
* Whether the caret is in the field, which is when it holds the value.
|
|
178
|
-
*/
|
|
179
33
|
this.editing = false;
|
|
180
|
-
/** The overlay, placed against the field for the reason Date Input gives. */
|
|
181
34
|
this.panel = new PopoverController(this, {
|
|
182
35
|
anchor: () => this.renderRoot?.querySelector('[part="field"]'),
|
|
183
|
-
/* The surface *and* the trigger.
|
|
184
|
-
*
|
|
185
|
-
* A control is not always one element: the button that opens this panel is
|
|
186
|
-
* part of it, and a press on it is not a press outside. Scoped to the
|
|
187
|
-
* surface alone, `pointerdown` on the trigger closed the panel and the
|
|
188
|
-
* trigger's own `toggleOpen` reopened it on the `click` that followed, so
|
|
189
|
-
* the panel could never be closed by the button that opened it.
|
|
190
|
-
* `DismissController` documents this failure and takes a list for it. */
|
|
191
36
|
boundary: () => [
|
|
192
37
|
this.renderRoot?.querySelector('[part="surface"]'),
|
|
193
38
|
this.renderRoot?.querySelector('[part="trigger"]'),
|
|
@@ -197,15 +42,7 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinControlle
|
|
|
197
42
|
placement: 'bottom-start',
|
|
198
43
|
surface: () => this.renderRoot?.querySelector('[part="surface"]'),
|
|
199
44
|
});
|
|
200
|
-
/**
|
|
201
|
-
* What is being typed but not yet held, for `multiple` only.
|
|
202
|
-
*
|
|
203
|
-
* The other modes type into the value directly, because one time and a range
|
|
204
|
-
* are both a whole answer the moment they are complete. A multiple is a set
|
|
205
|
-
* being added to, so the field needs somewhere to hold a time before it joins.
|
|
206
|
-
*/
|
|
207
45
|
this.draft = '';
|
|
208
|
-
/** Adds what was typed to the set, or takes the last one back out. */
|
|
209
46
|
this.handleDraftKey = (event) => {
|
|
210
47
|
if (this.mode !== 'multiple') {
|
|
211
48
|
return;
|
|
@@ -222,24 +59,17 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinControlle
|
|
|
222
59
|
this.requestValue(held.slice(0, -1).join(','));
|
|
223
60
|
}
|
|
224
61
|
};
|
|
225
|
-
/** Takes one time back out of the set. */
|
|
226
62
|
this.removeTime = (at) => {
|
|
227
63
|
this.requestValue(this.held.filter((one) => one !== at).join(','));
|
|
228
64
|
};
|
|
229
65
|
this.handleInput = (event) => {
|
|
230
66
|
event.stopPropagation();
|
|
231
|
-
// Typing is editing, whatever the focus did. The field has to hold the value
|
|
232
|
-
// while it is being changed, or a keystroke lands in a rendering.
|
|
233
67
|
this.editing = true;
|
|
234
68
|
const typed = event.target.value;
|
|
235
69
|
if (this.mode === 'multiple') {
|
|
236
70
|
this.draft = typed;
|
|
237
71
|
return;
|
|
238
72
|
}
|
|
239
|
-
/* Stored as commas once every time in it is whole, and kept exactly as typed
|
|
240
|
-
until then, which is the same rule Date Input follows: a form should
|
|
241
|
-
submit `09:00,17:00` however the dash was written, and half-typed text
|
|
242
|
-
must survive so the caret does not move mid-entry. */
|
|
243
73
|
const held = typed
|
|
244
74
|
.replace(/\s+[–-]\s+/g, ',')
|
|
245
75
|
.split(',')
|
|
@@ -253,50 +83,16 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinControlle
|
|
|
253
83
|
event.stopPropagation();
|
|
254
84
|
this.requestValue(event.detail.value);
|
|
255
85
|
this.restore();
|
|
256
|
-
/* One time has nothing more to say, so the list gets out of the way. A range
|
|
257
|
-
or a multiple has: the reader is still choosing, and shutting the list
|
|
258
|
-
under them after the first pick is taking the control away mid-answer. */
|
|
259
86
|
if (this.mode === undefined) {
|
|
260
87
|
this.requestOpen(false);
|
|
261
88
|
this.updateComplete.then(() => this.returnFocus());
|
|
262
89
|
}
|
|
263
90
|
};
|
|
264
|
-
/**
|
|
265
|
-
* Puts the caret back in the field without the field reopening the list.
|
|
266
|
-
*
|
|
267
|
-
* Focus opens the list, so handing focus back after a pick would reopen the
|
|
268
|
-
* thing that was just answered, and a reader could not get out of it. Input
|
|
269
|
-
* Date guards the same case by asking where focus came from, which is enough
|
|
270
|
-
* there and is not enough here: the option that had focus is gone by the time
|
|
271
|
-
* this runs, so `relatedTarget` is whatever the document fell back to, which
|
|
272
|
-
* is outside this element and reads as a genuine arrival.
|
|
273
|
-
*
|
|
274
|
-
* A flag rather than a better guess, because this element is the one moving
|
|
275
|
-
* the focus and it is the only thing that can know so. Cleared synchronously,
|
|
276
|
-
* since `focus()` dispatches before it returns.
|
|
277
|
-
*
|
|
278
|
-
* The path cannot be exercised from a spec: `focus()` dispatches no event in
|
|
279
|
-
* an unfocused document, which is what both the test runtime and the browser
|
|
280
|
-
* pane here are.
|
|
281
|
-
*/
|
|
282
91
|
this.returning = false;
|
|
283
92
|
this.handleBlur = () => {
|
|
284
93
|
this.editing = false;
|
|
285
94
|
};
|
|
286
|
-
/**
|
|
287
|
-
* Focusing the field opens the list, and only from outside the element.
|
|
288
|
-
*
|
|
289
|
-
* The guard is the one Date Input needed: picking a time shuts the panel and
|
|
290
|
-
* puts the caret back in the field, and without it the list came straight
|
|
291
|
-
* back and a reader could not get out of it.
|
|
292
|
-
*/
|
|
293
95
|
this.handleFocus = (event) => {
|
|
294
|
-
/* Not while the caret is being handed back. Picking a time returns focus
|
|
295
|
-
to the field, and treating that as editing put the field into the shape it
|
|
296
|
-
takes for typing: a reader chose "10:00 PM" from a list and the field
|
|
297
|
-
answered "22:00". Focus is a good enough signal for editing everywhere
|
|
298
|
-
except the one moment this element moved the focus itself, and it already
|
|
299
|
-
knows when that is. */
|
|
300
96
|
this.editing = !this.returning;
|
|
301
97
|
const from = event.relatedTarget;
|
|
302
98
|
if (this.open || this.isDisabled || this.returning) {
|
|
@@ -307,7 +103,6 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinControlle
|
|
|
307
103
|
}
|
|
308
104
|
this.requestOpen(true);
|
|
309
105
|
};
|
|
310
|
-
/** Escape shuts the list without moving the time. */
|
|
311
106
|
this.handleKeydown = (event) => {
|
|
312
107
|
if (event.key === 'Escape' && this.open) {
|
|
313
108
|
event.stopPropagation();
|
|
@@ -322,23 +117,16 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinControlle
|
|
|
322
117
|
}
|
|
323
118
|
return this.renderRoot.querySelector('[part="control"]');
|
|
324
119
|
}
|
|
325
|
-
/** Every time in the value, which may hold more than one. */
|
|
326
120
|
get held() {
|
|
327
|
-
/* The en dash and the spaced hyphen both mean "to", because a range is shown
|
|
328
|
-
that way and what is shown is what gets typed back. A time carries a colon
|
|
329
|
-
rather than a hyphen, so unlike a date there is no ambiguity to guard
|
|
330
|
-
against, and the guard is kept anyway for the sake of one reading. */
|
|
331
121
|
return this.value
|
|
332
122
|
.replace(/\s+[–-]\s+/g, ',')
|
|
333
123
|
.split(',')
|
|
334
124
|
.map((one) => one.trim())
|
|
335
125
|
.filter(Boolean);
|
|
336
126
|
}
|
|
337
|
-
/** One time, written the way `time-style` asks for. */
|
|
338
127
|
written(at) {
|
|
339
128
|
return this.timeStyle === 'iso' || !isTime(at) ? at : writeTime(at, this.locale);
|
|
340
129
|
}
|
|
341
|
-
/** What the field shows: the value while it is being edited, a reading when not. */
|
|
342
130
|
get display() {
|
|
343
131
|
if (this.mode === 'multiple') {
|
|
344
132
|
return this.draft;
|
|
@@ -350,22 +138,13 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinControlle
|
|
|
350
138
|
const written = held.map((one) => this.written(one));
|
|
351
139
|
return this.mode === 'range' ? written.join(' – ') : written.join(', ');
|
|
352
140
|
}
|
|
353
|
-
/** The times that get a tag, which is all of them until `maxTags` says not. */
|
|
354
141
|
get shownTimes() {
|
|
355
142
|
const held = this.held;
|
|
356
143
|
return this.maxTags > 0 ? held.slice(0, this.maxTags) : held;
|
|
357
144
|
}
|
|
358
|
-
/** How many are held beyond the ones shown. */
|
|
359
145
|
get restCount() {
|
|
360
146
|
return Math.max(0, this.held.length - this.shownTimes.length);
|
|
361
147
|
}
|
|
362
|
-
/**
|
|
363
|
-
* What the list should show, which is only ever a whole time.
|
|
364
|
-
*
|
|
365
|
-
* Half-typed input reaches here on every keystroke and `09:` is not a time.
|
|
366
|
-
* Handing it on would clear the list's selection mid-word, which is the same
|
|
367
|
-
* reason Date Input withholds a half-typed date from its calendar.
|
|
368
|
-
*/
|
|
369
148
|
get picked() {
|
|
370
149
|
const held = this.held;
|
|
371
150
|
return held.length > 0 && held.every(isTime) ? held.join(',') : '';
|
|
@@ -375,7 +154,6 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinControlle
|
|
|
375
154
|
this.control?.focus();
|
|
376
155
|
this.returning = false;
|
|
377
156
|
}
|
|
378
|
-
/** Puts the field back where the answer differs from what was typed. */
|
|
379
157
|
restore() {
|
|
380
158
|
const control = this.control;
|
|
381
159
|
if (control && control.value !== this.display) {
|
|
@@ -383,25 +161,19 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinControlle
|
|
|
383
161
|
}
|
|
384
162
|
this.sync();
|
|
385
163
|
}
|
|
386
|
-
/** Reports the value and the validity the host stands behind. */
|
|
387
164
|
sync() {
|
|
388
165
|
this.internals.setFormValue(this.value);
|
|
389
166
|
const control = this.control;
|
|
390
167
|
if (!control) {
|
|
391
168
|
return;
|
|
392
169
|
}
|
|
393
|
-
/* Checked here rather than left to the input, which is `text` and has no
|
|
394
|
-
idea what a time is. String comparison is enough because a 24 hour `HH:mm`
|
|
395
|
-
sorts lexically, which is the whole reason the value is that shape. */
|
|
396
170
|
const value = this.value;
|
|
397
171
|
let message = '';
|
|
398
172
|
if (value !== '' && !isTime(value)) {
|
|
399
173
|
message = 'Enter a time as HH:mm.';
|
|
400
|
-
// biome-ignore lint/style/noNonNullAssertion: guarded by the `value !== ''` beside it, and an optional chain here would compare `undefined` and take the branch.
|
|
401
174
|
}
|
|
402
175
|
else if (value !== '' && toMinutes(value) < (toMinutes(this.min) ?? 0)) {
|
|
403
176
|
message = `Choose a time at or after ${writeTime(this.min, this.locale)}.`;
|
|
404
|
-
// biome-ignore lint/style/noNonNullAssertion: guarded by the `value !== ''` beside it, and an optional chain here would compare `undefined` and take the branch.
|
|
405
177
|
}
|
|
406
178
|
else if (value !== '' && toMinutes(value) > (toMinutes(this.max) ?? 1439)) {
|
|
407
179
|
message = `Choose a time at or before ${writeTime(this.max, this.locale)}.`;
|
|
@@ -414,7 +186,6 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinControlle
|
|
|
414
186
|
}
|
|
415
187
|
this.refreshFeedback();
|
|
416
188
|
}
|
|
417
|
-
/** Refuses every change while read-only. See `readOnly`. */
|
|
418
189
|
requestValue(next) {
|
|
419
190
|
if (this.readOnly) {
|
|
420
191
|
return false;
|
|
@@ -423,27 +194,13 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinControlle
|
|
|
423
194
|
}
|
|
424
195
|
firstUpdated(changed) {
|
|
425
196
|
super.firstUpdated(changed);
|
|
426
|
-
// Whatever the markup arrived with, so a form knows before anything is typed.
|
|
427
197
|
this.sync();
|
|
428
198
|
}
|
|
429
199
|
updated(changed) {
|
|
430
200
|
super.updated(changed);
|
|
431
|
-
// Every update, not only the ones that name `value`: see the note on
|
|
432
|
-
// `ColorInput.updated`.
|
|
433
201
|
this.sync();
|
|
434
|
-
/* Driven from here rather than from the toggle, because `open` is a property
|
|
435
|
-
a consumer may set directly and the panel has to follow either way. The
|
|
436
|
-
controller is idempotent, so arriving twice for one open costs nothing.
|
|
437
|
-
Without this the surface stays `display: none`, a `popover` element being
|
|
438
|
-
hidden until something shows it, and the list inside measures zero, which
|
|
439
|
-
is what a scroll into view cannot work without. */
|
|
440
202
|
if (this.open) {
|
|
441
203
|
this.panel.show();
|
|
442
|
-
/* After the panel is shown, not before. A set of wheels centers each on
|
|
443
|
-
its own answer and cannot do it against a box of zero height, which is
|
|
444
|
-
what everything in a `popover` measures until something shows it. The
|
|
445
|
-
picker calls this on its own updates too; that one lands too early and
|
|
446
|
-
is harmless, and this one is the call that works. */
|
|
447
204
|
this.renderRoot
|
|
448
205
|
?.querySelector('[part="picker"]')
|
|
449
206
|
?.reveal?.();
|
|
@@ -452,17 +209,9 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinControlle
|
|
|
452
209
|
this.panel.hide();
|
|
453
210
|
}
|
|
454
211
|
}
|
|
455
|
-
/** Puts the caret in the field, the way a label pointing at one would. */
|
|
456
212
|
focus(options) {
|
|
457
213
|
this.control?.focus(options);
|
|
458
214
|
}
|
|
459
|
-
/**
|
|
460
|
-
* The clock the field opens, behind a seam the styled tier can move.
|
|
461
|
-
*
|
|
462
|
-
* A method rather than an inline fragment, so a tier above can replace or
|
|
463
|
-
* wrap it without reimplementing the template. The tag is interpolated rather
|
|
464
|
-
* than written, so `tier` resolves it to this tier's own element.
|
|
465
|
-
*/
|
|
466
215
|
renderPicker() {
|
|
467
216
|
return html `<${this.tag('time-picker')}
|
|
468
217
|
part="picker"
|
|
@@ -493,13 +242,6 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinControlle
|
|
|
493
242
|
@input=${this.stopInner}
|
|
494
243
|
></${this.tag('time-picker')}>`;
|
|
495
244
|
}
|
|
496
|
-
/**
|
|
497
|
-
* One chosen time, as a tag, behind a seam the styled tier can move.
|
|
498
|
-
*
|
|
499
|
-
* A method rather than an inline fragment, so a tier above can replace or
|
|
500
|
-
* wrap it without reimplementing the template. The tag is interpolated rather
|
|
501
|
-
* than written, so `tier` resolves it to this tier's own element.
|
|
502
|
-
*/
|
|
503
245
|
renderTag(at) {
|
|
504
246
|
return html `<${this.tag('tag')}
|
|
505
247
|
part="time"
|
|
@@ -514,21 +256,6 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinControlle
|
|
|
514
256
|
>${this.written(at)}</${this.tag('tag')}
|
|
515
257
|
>`;
|
|
516
258
|
}
|
|
517
|
-
/**
|
|
518
|
-
* The mark on the button that opens the clock, behind a seam.
|
|
519
|
-
*
|
|
520
|
-
* Slot fallback, so a consumer replaces it by slotting; and a method, so the
|
|
521
|
-
* tier above replaces it by overriding.
|
|
522
|
-
*
|
|
523
|
-
* **A named import rather than `<helix-icon>`**, on three counts.
|
|
524
|
-
* `helix-icon` indexes the whole icon map at runtime, `icons[this.name]`, and
|
|
525
|
-
* no bundler can shake a runtime lookup, so every consumer of any component
|
|
526
|
-
* that renders one ships all 50kB of path data; a named import ships one
|
|
527
|
-
* path. The import is checked when the package compiles, where a `name`
|
|
528
|
-
* string is checked only by a later name lookup. And `helix-icon` is not
|
|
529
|
-
* registered on the headless tier at all, which registers `helix-headless-icon`,
|
|
530
|
-
* so a headless consumer gets an inert unknown element and no mark whatsoever.
|
|
531
|
-
*/
|
|
532
259
|
renderTrigger() {
|
|
533
260
|
return html `<slot name="trigger"><svg aria-hidden="true" fill="currentColor" part="trigger-icon" viewBox=${clock.viewBox}>
|
|
534
261
|
${unsafeSVG(clock.content)}
|
|
@@ -575,22 +302,12 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinControlle
|
|
|
575
302
|
</div>
|
|
576
303
|
${this.renderHint()}${this.renderFeedback()}`;
|
|
577
304
|
}
|
|
578
|
-
/**
|
|
579
|
-
* The value as a `Date` on 1 January 1970 UTC, or `null` while there is no time.
|
|
580
|
-
*
|
|
581
|
-
* The platform's own pair on `<input type="time">`, epoch day included: a time
|
|
582
|
-
* is an offset into a day rather than an instant, and putting it on the epoch
|
|
583
|
-
* is what makes `valueAsNumber` come out as milliseconds since midnight rather
|
|
584
|
-
* than as a moment nobody meant. Assigning any date reads the clock off it, so
|
|
585
|
-
* a consumer handing over `new Date()` gets the time of day they expected.
|
|
586
|
-
*/
|
|
587
305
|
get valueAsDate() {
|
|
588
306
|
return asTime(this.value);
|
|
589
307
|
}
|
|
590
308
|
set valueAsDate(date) {
|
|
591
309
|
this.value = fromTime(date);
|
|
592
310
|
}
|
|
593
|
-
/** The value as milliseconds since midnight, or `NaN` while there is no time. */
|
|
594
311
|
get valueAsNumber() {
|
|
595
312
|
return asTime(this.value)?.getTime() ?? Number.NaN;
|
|
596
313
|
}
|