@fusion.dev/helix-headless 0.2.0 → 0.4.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.
Files changed (127) hide show
  1. package/custom-elements.json +234 -234
  2. package/dist/accordion/item/accordion-item.js +0 -108
  3. package/dist/accordion/root/accordion-root.js +0 -33
  4. package/dist/announcement/root/announcement-root.js +1 -76
  5. package/dist/app-header/root/app-header-root.js +0 -84
  6. package/dist/avatar/group/avatar-group.js +0 -77
  7. package/dist/avatar/root/avatar-root.js +0 -72
  8. package/dist/badge/root/badge-root.js +0 -67
  9. package/dist/bar-chart/root/bar-chart-root.js +3 -263
  10. package/dist/blockquote/root/blockquote-root.js +0 -46
  11. package/dist/breadcrumbs/breadcrumb/breadcrumb.js +0 -72
  12. package/dist/breadcrumbs/root/breadcrumbs-root.js +0 -52
  13. package/dist/button/root/button-root.js +0 -215
  14. package/dist/button-group/root/button-group-root.js +0 -30
  15. package/dist/callout/root/callout-root.js +0 -52
  16. package/dist/card/root/card-root.js +0 -41
  17. package/dist/carousel/root/carousel-root.d.ts +2 -2
  18. package/dist/carousel/root/carousel-root.js +2 -362
  19. package/dist/chat/composer/chat-composer.js +0 -131
  20. package/dist/chat/message/chat-message.js +0 -137
  21. package/dist/chat/root/chat-root.js +0 -193
  22. package/dist/checkbox/root/checkbox-root.js +0 -113
  23. package/dist/checkbox-group/root/checkbox-group-root.js +1 -157
  24. package/dist/code/root/code-root.js +0 -26
  25. package/dist/code-block/root/code-block-root.js +0 -100
  26. package/dist/color-input/root/color-input-root.js +0 -217
  27. package/dist/color-picker/color.js +0 -69
  28. package/dist/color-picker/root/color-picker-root.js +0 -272
  29. package/dist/combobox/root/combobox-root.js +0 -428
  30. package/dist/command/root/command-root.js +0 -177
  31. package/dist/copy-button/root/copy-button-root.js +0 -134
  32. package/dist/data-table/root/data-table-root.js +0 -336
  33. package/dist/data-table/root/order.js +0 -100
  34. package/dist/date-input/root/date-input-root.js +1 -398
  35. package/dist/date-picker/root/date-picker-root.js +0 -358
  36. package/dist/dialog/root/dialog-root.js +0 -189
  37. package/dist/divider/root/divider-root.js +0 -71
  38. package/dist/donut-chart/root/donut-chart-root.js +0 -214
  39. package/dist/drawer/root/drawer-root.js +0 -116
  40. package/dist/drilldown/item/drilldown-item.js +0 -82
  41. package/dist/drilldown/root/drilldown-root.js +0 -91
  42. package/dist/element-events.generated.js +0 -5
  43. package/dist/empty/root/empty-root.js +0 -35
  44. package/dist/file-input/root/file-input-root.js +0 -232
  45. package/dist/format-date/root/format-date-root.js +0 -95
  46. package/dist/heatmap/root/buckets.js +0 -57
  47. package/dist/heatmap/root/heatmap-root.js +0 -313
  48. package/dist/icon/root/icon-root.js +0 -48
  49. package/dist/icon-button/root/icon-button-root.js +0 -30
  50. package/dist/image/root/image-root.js +0 -174
  51. package/dist/index.js +0 -9
  52. package/dist/input/root/input-root.js +0 -228
  53. package/dist/kbd/root/kbd-root.js +0 -117
  54. package/dist/legend/root/legend-root.js +0 -73
  55. package/dist/line-chart/root/line-chart-root.js +0 -172
  56. package/dist/list/item/list-item.js +0 -92
  57. package/dist/list/root/list-root.js +0 -26
  58. package/dist/listbox/root/listbox-root.js +2 -429
  59. package/dist/loader/root/loader-root.js +0 -40
  60. package/dist/megamenu/root/megamenu-root.js +0 -284
  61. package/dist/menu/item/menu-item.js +1 -221
  62. package/dist/menu/root/menu-root.js +1 -171
  63. package/dist/meter/root/meter-root.js +1 -152
  64. package/dist/navigation/root/navigation-root.js +0 -56
  65. package/dist/notifications/notification/notification.js +0 -159
  66. package/dist/notifications/root/notifications-root.js +0 -148
  67. package/dist/number-input/root/number-input-root.js +1 -263
  68. package/dist/option/group/option-group.js +0 -26
  69. package/dist/option/root/option-root.js +0 -157
  70. package/dist/outline/root/outline-root.js +0 -292
  71. package/dist/pagination/root/pagination-root.js +0 -361
  72. package/dist/pagination/size/pagination-size.js +0 -104
  73. package/dist/pagination/summary/pagination-summary.js +0 -90
  74. package/dist/pin-input/root/pin-input-root.js +0 -276
  75. package/dist/popover/root/popover-root.js +0 -103
  76. package/dist/progress/root/progress-root.js +1 -133
  77. package/dist/qr-code/root/qr-code-root.js +0 -96
  78. package/dist/quote/root/quote-root.js +0 -25
  79. package/dist/radio/group/radio-group.js +0 -81
  80. package/dist/radio/root/radio-root.js +0 -85
  81. package/dist/rating/root/rating-root.js +0 -220
  82. package/dist/scroll-area/root/scroll-area-root.js +0 -100
  83. package/dist/select/root/select-root.js +2 -218
  84. package/dist/skeleton/root/skeleton-root.js +0 -20
  85. package/dist/slider/root/slider-root.js +1 -267
  86. package/dist/sparkline/root/sparkline-root.js +3 -164
  87. package/dist/speed-dial/item/speed-dial-item.js +0 -52
  88. package/dist/speed-dial/root/speed-dial-root.js +1 -116
  89. package/dist/splitter/root/splitter-root.js +0 -489
  90. package/dist/stat/group/stat-group.js +0 -18
  91. package/dist/stat/root/stat-root.js +0 -23
  92. package/dist/steps/root/steps-root.js +0 -62
  93. package/dist/steps/step/step.js +0 -131
  94. package/dist/switch/root/switch-root.js +0 -80
  95. package/dist/table/cell/table-cell.js +0 -23
  96. package/dist/table/column/table-column.js +0 -158
  97. package/dist/table/root/table-root.js +0 -82
  98. package/dist/table/row/table-row.js +0 -21
  99. package/dist/tabs/panel/tab-panel.js +0 -49
  100. package/dist/tabs/root/tabs-root.js +0 -98
  101. package/dist/tabs/tab/tab.js +0 -59
  102. package/dist/tag/root/tag-root.js +0 -31
  103. package/dist/textarea/root/textarea-root.js +0 -162
  104. package/dist/time-input/root/time-input-root.js +0 -283
  105. package/dist/time-picker/root/time-picker-root.js +0 -326
  106. package/dist/timeline/item/timeline-item.js +0 -66
  107. package/dist/timeline/root/timeline-root.js +0 -41
  108. package/dist/toast/item/toast-item.js +0 -165
  109. package/dist/toast/root/toast-root.js +0 -51
  110. package/dist/toggle/group/toggle-group.js +0 -150
  111. package/dist/toggle/root/toggle-root.js +0 -78
  112. package/dist/toolbar/root/toolbar-root.js +0 -116
  113. package/dist/tooltip/root/tooltip-root.js +0 -427
  114. package/dist/tour/root/tour-root.js +0 -130
  115. package/dist/tour/stop/tour-stop.js +1 -127
  116. package/dist/tree/item/tree-item.js +0 -198
  117. package/dist/tree/root/tree-root.js +0 -97
  118. package/dist/trend/root/trend-root.js +0 -53
  119. package/dist/video/root/video-root.d.ts +8 -12
  120. package/dist/video/root/video-root.js +5 -756
  121. package/dist/video-playlist/item/video-playlist-item.js +1 -90
  122. package/dist/video-playlist/menu/video-playlist-menu.js +1 -132
  123. package/dist/video-playlist/root/video-playlist-root.js +0 -113
  124. package/dist/virtual-list/root/virtual-list-root.js +0 -105
  125. package/jsx.d.ts +1408 -2576
  126. package/package.json +6 -6
  127. 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
  }