@fusion.dev/helix-headless 0.159.0 → 0.160.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.
@@ -1,4 +1,4 @@
1
- import { type DateCalendar, HelixElement } from '@fusion.dev/helix-core';
1
+ import { type DateCalendar, HelixElement, type MonthCalendar } from '@fusion.dev/helix-core';
2
2
  import { nothing, type PropertyValues, type TemplateResult } from 'lit';
3
3
  /**
4
4
  * @summary A timestamp, formatted for the user's locale.
@@ -45,8 +45,8 @@ import { nothing, type PropertyValues, type TemplateResult } from 'lit';
45
45
  *
46
46
  * **A date is written in a calendar Date Picker draws**: the locale's own
47
47
  * where its months are the Gregorian months, as `th-TH` writes 2569 for 2026,
48
- * and `gregory` where they are not, as for `fa-IR`. `calendar` picks one of
49
- * the five.
48
+ * and `gregory` where they are not, as for `fa-IR`. `calendar` picks any of
49
+ * them, `persian` and `hebrew` included.
50
50
  *
51
51
  * @since 0.1.0
52
52
  * @status stable
@@ -57,6 +57,7 @@ import { nothing, type PropertyValues, type TemplateResult } from 'lit';
57
57
  * @csspart day - The day of the month, with `fields`.
58
58
  * @csspart day-period - AM or PM, or the locale's word for the part of the day, with `fields`.
59
59
  * @csspart era - The era, such as AD, with `fields` and `era`.
60
+ * @csspart fractional-second - The fraction of a second, with `fields` and `fractional-second-digits`.
60
61
  * @csspart hour - The hour, with `fields`.
61
62
  * @csspart minute - The minute, with `fields`.
62
63
  * @csspart month - The month, with `fields`.
@@ -72,14 +73,16 @@ import { nothing, type PropertyValues, type TemplateResult } from 'lit';
72
73
  export declare class HelixHeadlessFormatDate extends HelixElement {
73
74
  /**
74
75
  * The calendar a date is written in: `gregory`, `buddhist`, `japanese`,
75
- * `roc`, or `iso8601`, the calendars Date Picker and Date Input draw.
76
+ * `roc`, or `iso8601`, or a calendar with months of its own, such as
77
+ * `persian`, `hebrew`, `islamic-umalqura`, `ethiopic`, or `chinese`: every
78
+ * calendar Date Picker and Date Input draw.
76
79
  *
77
- * Absent is the locale's own where it is one of the five, such as
78
- * `buddhist` for `th-TH`, and `gregory` for every other, such as `persian`
79
- * for `fa-IR`, so a date written here reads as the same date a Date Picker
80
- * beside it shows.
80
+ * Absent is the locale's own where its months are the Gregorian months,
81
+ * such as `buddhist` for `th-TH`, and `gregory` for every other, such as
82
+ * `persian` for `fa-IR`, so a date written here reads as the same date a
83
+ * Date Picker beside it shows. Write `calendar="persian"` for Mehr 14, 1405.
81
84
  */
82
- calendar?: DateCalendar;
85
+ calendar?: DateCalendar | MonthCalendar;
83
86
  /**
84
87
  * The timestamp, in any format `Date` can parse.
85
88
  *
@@ -107,6 +110,12 @@ export declare class HelixHeadlessFormatDate extends HelixElement {
107
110
  * How the day of the month is written, where a date is shown.
108
111
  */
109
112
  day?: 'numeric' | '2-digit';
113
+ /**
114
+ * Writes the part of the day in words, such as "in the morning" or "at
115
+ * night", in its `long`, `short`, or `narrow` form, where `hour` is on a
116
+ * twelve-hour clock.
117
+ */
118
+ dayPeriod?: 'long' | 'narrow' | 'short';
110
119
  /**
111
120
  * Shows how long ago the timestamp was, in a compact form such as "3h", in
112
121
  * place of the date.
@@ -130,6 +139,11 @@ export declare class HelixHeadlessFormatDate extends HelixElement {
130
139
  * way.
131
140
  */
132
141
  fields?: boolean;
142
+ /**
143
+ * How many digits of a fraction of a second to write after `second`: 1, 2,
144
+ * or 3, as in 09:41:07.250.
145
+ */
146
+ fractionalSecondDigits?: 1 | 2 | 3;
133
147
  /**
134
148
  * Draws an `elapsed` or `relative` reading once and leaves it, where it
135
149
  * would otherwise move on as it goes stale.
@@ -206,9 +220,11 @@ export declare class HelixHeadlessFormatDate extends HelixElement {
206
220
  */
207
221
  timeZone?: string;
208
222
  /**
209
- * Shows the name of the time zone, in its `long` or `short` form.
223
+ * Shows the name of the time zone: `long` or `short`, as Pacific Daylight
224
+ * Time or PDT, `longOffset` or `shortOffset`, as GMT-07:00 or GMT-7, or
225
+ * `longGeneric` or `shortGeneric`, as Pacific Time or PT.
210
226
  */
211
- timeZoneName?: 'long' | 'short';
227
+ timeZoneName?: 'long' | 'longGeneric' | 'longOffset' | 'short' | 'shortGeneric' | 'shortOffset';
212
228
  /**
213
229
  * How the weekday is written, where one is shown.
214
230
  */
@@ -132,6 +132,9 @@ __decorate([
132
132
  __decorate([
133
133
  property()
134
134
  ], HelixHeadlessFormatDate.prototype, "day", void 0);
135
+ __decorate([
136
+ property({ attribute: 'day-period' })
137
+ ], HelixHeadlessFormatDate.prototype, "dayPeriod", void 0);
135
138
  __decorate([
136
139
  property({ reflect: true, type: Boolean })
137
140
  ], HelixHeadlessFormatDate.prototype, "elapsed", void 0);
@@ -141,6 +144,9 @@ __decorate([
141
144
  __decorate([
142
145
  property({ type: Boolean })
143
146
  ], HelixHeadlessFormatDate.prototype, "fields", void 0);
147
+ __decorate([
148
+ property({ attribute: 'fractional-second-digits', type: Number })
149
+ ], HelixHeadlessFormatDate.prototype, "fractionalSecondDigits", void 0);
144
150
  __decorate([
145
151
  property({ type: Boolean })
146
152
  ], HelixHeadlessFormatDate.prototype, "frozen", void 0);
@@ -98,6 +98,11 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
98
98
  }
99
99
  else if (event.key === ' ') {
100
100
  event.preventDefault();
101
+ const at = this.currentValue || shown.find((option) => !option.disabled)?.value;
102
+ if (at) {
103
+ this.activeValue = at;
104
+ this.choose(at);
105
+ }
101
106
  }
102
107
  return;
103
108
  }
@@ -56,6 +56,7 @@ declare const HelixHeadlessNotification_base: (new (...args: any[]) => import("@
56
56
  * @csspart time-day - Format Date's `day`, forwarded for a time drawn as fields. An elapsed time draws none.
57
57
  * @csspart time-day-period - Format Date's `day-period`, forwarded for a time drawn as fields. An elapsed time draws none.
58
58
  * @csspart time-era - Format Date's `era`, forwarded for a time drawn as fields. An elapsed time draws none.
59
+ * @csspart time-fractional-second - Format Date's `fractional-second`, forwarded for a time drawn as fields. An elapsed time draws none.
59
60
  * @csspart time-hour - Format Date's `hour`, forwarded for a time drawn as fields. An elapsed time draws none.
60
61
  * @csspart time-minute - Format Date's `minute`, forwarded for a time drawn as fields. An elapsed time draws none.
61
62
  * @csspart time-month - Format Date's `month`, forwarded for a time drawn as fields. An elapsed time draws none.
@@ -104,7 +104,8 @@ let HelixHeadlessNotification = class HelixHeadlessNotification extends MixinDis
104
104
  renderTime() {
105
105
  const stamp = this.tag('format-date');
106
106
  return html `<${stamp}
107
- exportparts="time, day: time-day, day-period: time-day-period, era: time-era, hour: time-hour,
107
+ exportparts="time, day: time-day, day-period: time-day-period, era: time-era,
108
+ fractional-second: time-fractional-second, hour: time-hour,
108
109
  minute: time-minute, month: time-month, second: time-second,
109
110
  time-zone-name: time-time-zone-name, weekday: time-weekday, year: time-year"
110
111
  elapsed
@@ -28,6 +28,10 @@ declare const HelixHeadlessSlider_base: (new (...args: any[]) => import("@fusion
28
28
  * `min-steps-between-thumbs` keeps them that many steps apart, and the readout
29
29
  * joins the two as `range-text` writes them.
30
30
  *
31
+ * A press on a range's track takes the nearest thumb to the point pressed,
32
+ * and moving the pointer drags it, as React Aria's and Web Awesome's sliders
33
+ * do. The thumb takes focus, and `change` is sent as the press ends.
34
+ *
31
35
  * `origin` sets where a single thumb's fill starts: at `min`, at the middle of
32
36
  * the track, or at `max`. A balance or an offset from zero fills from the
33
37
  * middle, as Zag's slider `origin` does. The fill's ends are published as
@@ -52,7 +56,7 @@ declare const HelixHeadlessSlider_base: (new (...args: any[]) => import("@fusion
52
56
  * |---|---|
53
57
  * | Right, Up | Moves the thumb up by `step`. |
54
58
  * | Left, Down | Moves the thumb down by `step`. |
55
- * | Page Up, Page Down | Moves the thumb by a larger step. |
59
+ * | Page Up, Page Down | Moves the thumb by `page-step`, or by the platform's larger step where it is not set. |
56
60
  * | Home, End | Moves the thumb to its lowest or highest value. |
57
61
  *
58
62
  * Under `readonly` none of these move the thumb, and Tab still reaches it.
@@ -97,6 +101,8 @@ declare const HelixHeadlessSlider_base: (new (...args: any[]) => import("@fusion
97
101
  * @cssproperty --slider-marker-at - Where one marker sits, as a percentage of the track.
98
102
  * @cssproperty --slider-to - Where the filled span ends, as a fraction of the track, 0 to 1: a range's upper thumb, or the farther of a single thumb and its `origin`.
99
103
  *
104
+ * @cssstate dragging - A thumb is being dragged, by its thumb or from a press on the track.
105
+ * @cssstate focus-visible - A thumb has focus that the keyboard brought, where the browser draws a focus ring.
100
106
  * @cssstate invalid - The slider has an error to show, from the browser's validation or from your `feedback`.
101
107
  */
102
108
  export declare class HelixHeadlessSlider extends HelixHeadlessSlider_base {
@@ -193,6 +199,12 @@ export declare class HelixHeadlessSlider extends HelixHeadlessSlider_base {
193
199
  * between its thumbs and ignores it.
194
200
  */
195
201
  origin?: 'center' | 'end' | 'start';
202
+ /**
203
+ * How far Page Up and Page Down move a thumb, in the slider's own units, as
204
+ * React Aria's `pageSize` and Zag's `largeStep` do. Absent leaves the keys
205
+ * to the platform, which moves a tenth of the track.
206
+ */
207
+ pageStep?: number;
196
208
  /**
197
209
  * Adds a second thumb, so the value is a pair.
198
210
  *
@@ -298,13 +310,52 @@ export declare class HelixHeadlessSlider extends HelixHeadlessSlider_base {
298
310
  /** Where one value sits along the track, as a fraction of it. */
299
311
  private along;
300
312
  private readonly handleInput;
313
+ /** Whether a pointer is dragging a thumb, which `:state(dragging)` says. */
314
+ private dragging;
315
+ /** Ends a drag when its pointer lifts, wherever it lifts. */
316
+ private readonly endDrag;
317
+ private startDrag;
318
+ /**
319
+ * Half a thumb, which is where the thumb's center stops short of each end
320
+ * of its input. Measured off the thumb's length as the stylesheet sets it,
321
+ * or a platform thumb's 16 pixels where none does.
322
+ */
323
+ private thumbInset;
324
+ /**
325
+ * The value under a pointer, along a thumb's input, less `inset` at each
326
+ * end. Upward on a vertical track, and from the right on a right-to-left
327
+ * page.
328
+ */
329
+ private valueAt;
330
+ /** Moves one of a range's thumbs, kept between its neighbors, and says whether it moved. */
331
+ private moveThumb;
332
+ /**
333
+ * A press on the field: on a thumb, the start of the platform's own drag,
334
+ * and on a range's track, the nearest thumb taken to the point and dragged
335
+ * from there.
336
+ *
337
+ * **A range's inputs take presses on their thumbs only**, being stacked on
338
+ * one track, so a press beside a thumb reached no input at all and did
339
+ * nothing. Measured before this: a press at 90 on a range holding 20 and 60
340
+ * left it at 20 and 60. React Aria's and Web Awesome's sliders move the
341
+ * nearest thumb there.
342
+ */
343
+ private readonly handlePress;
344
+ disconnectedCallback(): void;
301
345
  /** Relayed, because `change` does not cross a shadow boundary on its own. */
302
346
  private readonly handleChange;
303
347
  /**
304
- * Keeps a read-only thumb still under the keys that move it. Tab and every
305
- * other key pass, so focus moves on.
348
+ * Keeps a read-only thumb still under the keys that move it, and moves a
349
+ * thumb by `page-step` under Page Up and Page Down. Tab and every other key
350
+ * pass, so focus moves on.
306
351
  */
307
352
  private readonly handleKeydown;
353
+ /**
354
+ * Says whether the keyboard brought focus to a thumb, as
355
+ * `:state(focus-visible)` on the host, where a stylesheet outside can reach
356
+ * the whole control: a ring around the track, or the readout in bold.
357
+ */
358
+ private readonly handleFocusChange;
308
359
  /**
309
360
  * Reports the value: one entry, or for a named range one entry per thumb.
310
361
  * The joined pair is the state the browser restores.
@@ -4,9 +4,9 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { AriaForwardController, customElement, formatNumber, formatWords, HelixFormElement, MixinControllerValue, MixinField, reportValues, restoreValue, words, wordsLocale, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, customElement, formatNumber, formatWords, HelixFormElement, isRtl, MixinControllerValue, MixinField, reportValues, restoreValue, words, wordsLocale, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
- import { property } from 'lit/decorators.js';
9
+ import { property, state } from 'lit/decorators.js';
10
10
  const SLIDER_KEYS = new Set([
11
11
  'ArrowDown',
12
12
  'ArrowLeft',
@@ -63,6 +63,54 @@ let HelixHeadlessSlider = class HelixHeadlessSlider extends MixinControllerValue
63
63
  this.requestValue(next);
64
64
  restoreValue(control, this.value);
65
65
  };
66
+ this.dragging = false;
67
+ this.endDrag = () => {
68
+ this.dragging = false;
69
+ window.removeEventListener('pointerup', this.endDrag);
70
+ window.removeEventListener('pointercancel', this.endDrag);
71
+ };
72
+ this.handlePress = (event) => {
73
+ if (this.isDisabled || this.readOnly || event.button !== 0) {
74
+ return;
75
+ }
76
+ const inputs = [...this.renderRoot.querySelectorAll('input[part="control"]')];
77
+ if (inputs.includes(event.composedPath()[0])) {
78
+ this.startDrag();
79
+ return;
80
+ }
81
+ const first = inputs[0];
82
+ const field = event.currentTarget;
83
+ if (!this.range || !first) {
84
+ return;
85
+ }
86
+ const inset = this.thumbInset(field);
87
+ const wanted = this.valueAt(event, first, inset);
88
+ let at = 0;
89
+ this.points.forEach((one, index) => {
90
+ const distance = Math.abs(one - wanted);
91
+ const best = Math.abs((this.points[at] ?? 0) - wanted);
92
+ if (distance < best || (distance === best && wanted > one)) {
93
+ at = index;
94
+ }
95
+ });
96
+ event.preventDefault();
97
+ this.moveThumb(at, wanted);
98
+ inputs[at]?.focus({ focusVisible: false, preventScroll: true });
99
+ this.startDrag();
100
+ field.setPointerCapture?.(event.pointerId);
101
+ const move = (next) => {
102
+ this.moveThumb(at, this.valueAt(next, first, inset));
103
+ };
104
+ const end = () => {
105
+ field.removeEventListener('pointermove', move);
106
+ field.removeEventListener('pointerup', end);
107
+ field.removeEventListener('pointercancel', end);
108
+ this.announceChange();
109
+ };
110
+ field.addEventListener('pointermove', move);
111
+ field.addEventListener('pointerup', end);
112
+ field.addEventListener('pointercancel', end);
113
+ };
66
114
  this.handleChange = (event) => {
67
115
  event.stopPropagation();
68
116
  if (!this.readOnly) {
@@ -72,7 +120,31 @@ let HelixHeadlessSlider = class HelixHeadlessSlider extends MixinControllerValue
72
120
  this.handleKeydown = (event) => {
73
121
  if (this.readOnly && SLIDER_KEYS.has(event.key)) {
74
122
  event.preventDefault();
123
+ return;
124
+ }
125
+ const by = this.pageStep;
126
+ if ((event.key !== 'PageUp' && event.key !== 'PageDown') || !by || this.isDisabled) {
127
+ return;
75
128
  }
129
+ event.preventDefault();
130
+ const sign = event.key === 'PageUp' ? 1 : -1;
131
+ if (this.range) {
132
+ const at = Number(event.target.dataset.index ?? 0);
133
+ if (!this.moveThumb(at, this.settled((this.points[at] ?? this.min) + sign * by))) {
134
+ return;
135
+ }
136
+ }
137
+ else {
138
+ const next = String(this.settled(Number(this.value) + sign * by));
139
+ if (next === this.value || !this.requestValue(next)) {
140
+ return;
141
+ }
142
+ }
143
+ this.announceChange();
144
+ };
145
+ this.handleFocusChange = (event) => {
146
+ const input = event.target;
147
+ this.toggleState('focus-visible', event.type === 'focusin' && input.matches?.(':focus-visible') === true);
76
148
  };
77
149
  this.valueText = '';
78
150
  this.handleThumbSlot = () => {
@@ -132,6 +204,46 @@ let HelixHeadlessSlider = class HelixHeadlessSlider extends MixinControllerValue
132
204
  }
133
205
  return Math.min(1, Math.max(0, (at - this.min) / span));
134
206
  }
207
+ startDrag() {
208
+ this.dragging = true;
209
+ window.addEventListener('pointerup', this.endDrag);
210
+ window.addEventListener('pointercancel', this.endDrag);
211
+ }
212
+ thumbInset(field) {
213
+ const probe = document.createElement('span');
214
+ probe.style.cssText =
215
+ 'position:absolute;visibility:hidden;inline-size:var(--_slider-thumb-length,16px)';
216
+ field.append(probe);
217
+ const length = probe.getBoundingClientRect().width;
218
+ probe.remove();
219
+ return length / 2;
220
+ }
221
+ valueAt(event, input, inset) {
222
+ const box = input.getBoundingClientRect();
223
+ const vertical = this.orientation === 'vertical';
224
+ const length = vertical ? box.height : box.width;
225
+ const from = vertical
226
+ ? box.bottom - event.clientY
227
+ : isRtl(this)
228
+ ? box.right - event.clientX
229
+ : event.clientX - box.left;
230
+ const fraction = Math.min(1, Math.max(0, (from - inset) / Math.max(1, length - inset * 2)));
231
+ return this.settled(this.min + fraction * (this.max - this.min));
232
+ }
233
+ moveThumb(at, wanted) {
234
+ const points = this.points;
235
+ const gap = this.gap;
236
+ const low = at > 0 ? (points[at - 1] ?? this.min) + gap : Number.NEGATIVE_INFINITY;
237
+ const high = at < points.length - 1 ? (points[at + 1] ?? this.max) - gap : Number.POSITIVE_INFINITY;
238
+ const next = points
239
+ .map((one, index) => (index === at ? Math.min(high, Math.max(low, wanted)) : one))
240
+ .join(',');
241
+ return next !== points.join(',') && this.requestValue(next);
242
+ }
243
+ disconnectedCallback() {
244
+ super.disconnectedCallback();
245
+ this.endDrag();
246
+ }
135
247
  sync() {
136
248
  if (this.range && this.name) {
137
249
  reportValues(this.internals, this.name, this.points.map(String));
@@ -168,6 +280,7 @@ let HelixHeadlessSlider = class HelixHeadlessSlider extends MixinControllerValue
168
280
  }
169
281
  willUpdate(changed) {
170
282
  super.willUpdate(changed);
283
+ this.toggleState('dragging', this.dragging);
171
284
  if (!this.hasUpdated && this.value === '') {
172
285
  this.defaultValue = this.range
173
286
  ? `${this.min},${this.max}`
@@ -306,6 +419,9 @@ let HelixHeadlessSlider = class HelixHeadlessSlider extends MixinControllerValue
306
419
  : nothing}
307
420
  <div
308
421
  part="field"
422
+ @focusin=${this.handleFocusChange}
423
+ @focusout=${this.handleFocusChange}
424
+ @pointerdown=${this.handlePress}
309
425
  data-markers=${this.markerValues.length > 0 ? '' : nothing}
310
426
  data-range=${this.range ? '' : nothing}
311
427
  data-thumbs=${this.thumbContent ? '' : nothing}
@@ -405,6 +521,9 @@ __decorate([
405
521
  __decorate([
406
522
  property({ reflect: true })
407
523
  ], HelixHeadlessSlider.prototype, "origin", void 0);
524
+ __decorate([
525
+ property({ attribute: 'page-step', type: Number })
526
+ ], HelixHeadlessSlider.prototype, "pageStep", void 0);
408
527
  __decorate([
409
528
  property({ reflect: true, type: Boolean })
410
529
  ], HelixHeadlessSlider.prototype, "range", void 0);
@@ -428,6 +547,9 @@ __decorate([
428
547
  __decorate([
429
548
  property({ type: Boolean })
430
549
  ], HelixHeadlessSlider.prototype, "tooltip", void 0);
550
+ __decorate([
551
+ state()
552
+ ], HelixHeadlessSlider.prototype, "dragging", void 0);
431
553
  __decorate([
432
554
  property({ attribute: 'value-text' })
433
555
  ], HelixHeadlessSlider.prototype, "valueText", void 0);
@@ -19,20 +19,25 @@ declare const HelixHeadlessTimeInput_base: (new (...args: any[]) => import("@fus
19
19
  * `seconds`, because that is what the form
20
20
  * submits, what `min` and `max` compare against, and what sorts correctly as a
21
21
  * string. While idle, the field shows the time in the user's locale format.
22
- * Once the caret is in the field, it shows the value itself, because a
23
- * formatted time such as `2:30 PM` cannot be typed back and `Intl` has no
24
- * parser for it.
22
+ * Once the caret is in the field, it shows the value itself. You can type
23
+ * `14:30`, or a time as you write one, such as `2:30 PM`, `2.30pm`, or
24
+ * `14h30`, and the field reads it as you leave it, with AM and PM in the
25
+ * locale's words.
25
26
  *
26
27
  * Typing sends `input` on each keystroke and `change` when you leave the
27
28
  * field. The trigger and ArrowDown open the list, and so does a press into the
28
- * field; Tab into the field opens nothing. In the list, the arrow keys, typed digits and PageUp and PageDown
29
+ * field; Tab into the field opens nothing. The text field is a `combobox`
30
+ * with `aria-haspopup="dialog"`, `aria-expanded`, and `aria-controls`, since
31
+ * Down opens the list from it. In the list, the arrow keys, typed digits and PageUp and PageDown
29
32
  * move through the times, and Enter, Space or a click chooses one and closes
30
33
  * the panel. With `variant="wheels"` each wheel moves the time as you turn it,
31
34
  * the panel stays open, and Enter or Escape closes it.
32
35
  *
33
36
  * With `segmented`, the field is a row of segments in place of the text: an
34
37
  * hour, a minute, and AM or PM where the clock has them, each set with digits
35
- * or the arrow keys. Alt+Down opens the list from a segment.
38
+ * or the arrow keys. Alt+Down opens the list from a segment. A `range` is two
39
+ * rows with a dash between them, the start and the end, and `multiple` keeps
40
+ * the text.
36
41
  *
37
42
  * Text that is not a time gives the form an empty value, as a native time
38
43
  * field does, and `value` keeps the text so it can be corrected.
@@ -62,6 +67,7 @@ declare const HelixHeadlessTimeInput_base: (new (...args: any[]) => import("@fus
62
67
  * @aliases TimeField, InputTime
63
68
  * @documentation https://fusion.dev/helix/components/time-input
64
69
  *
70
+ * @slot clear-icon - Replaces the cross on the clear button, under `clearable`.
65
71
  * @slot end - Content after the time, before the trigger, such as a clear button.
66
72
  * @slot feedback - The message that says what is wrong with the value.
67
73
  * @slot hint - Help under the field.
@@ -75,9 +81,11 @@ declare const HelixHeadlessTimeInput_base: (new (...args: any[]) => import("@fus
75
81
  * @event {HelixBeforeChangeEvent} helix-before-change - Fired before the time changes. Cancelable: canceling it stops the change and leaves the decision to you.
76
82
  * @event {HelixBeforeCloseEvent} helix-before-close - Fired before the list closes. Cancelable: canceling it keeps the list open and leaves the decision to you.
77
83
  * @event {HelixBeforeOpenEvent} helix-before-open - Fired before the list opens. Cancelable: canceling it keeps the list closed and leaves the decision to you.
84
+ * @event {HelixClearEvent} helix-clear - Fired after the clear button has emptied the field, after `input` and `change`.
78
85
  * @event {Event} input - Fired after the value changes.
79
86
  * @event {ToggleEvent} toggle - Fired after the list opens or closes, with `newState` and `oldState`.
80
87
  *
88
+ * @csspart clear - The button that empties the field, under `clearable`, while it holds a time.
81
89
  * @csspart control - The text input.
82
90
  * @csspart feedback - The message that says what is wrong with the value.
83
91
  * @csspart field - The box that holds the text and the trigger.
@@ -123,6 +131,7 @@ declare const HelixHeadlessTimeInput_base: (new (...args: any[]) => import("@fus
123
131
  * @csspart picker-option-item-selected - The row of a chosen time: the one time, or either end of a range.
124
132
  * @csspart picker-option-label - The option's label.
125
133
  * @csspart picker-option-text - The label and the caption, as one column.
134
+ * @csspart range-separator - The dash between a range's two rows of segments, in `segmented`, beside `literal`.
126
135
  * @csspart rest - The count of times held beyond the ones shown. A `<button>` with `expandable`, and a `<span>` without it.
127
136
  * @csspart segment - One part of the time, an hour, a minute, a second or AM and PM, in `segmented`.
128
137
  * @csspart segment-placeholder - A segment with nothing in it yet. Applied with `segment`.
@@ -139,6 +148,7 @@ declare const HelixHeadlessTimeInput_base: (new (...args: any[]) => import("@fus
139
148
  * @csspart trigger - The control that opens the list.
140
149
  * @csspart trigger-icon - The icon on the button that opens the clock.
141
150
  *
151
+ * @cssstate blank - The field holds no time.
142
152
  * @cssstate invalid - The control has an error to show, from the browser's validation or from your `feedback`.
143
153
  * @cssstate open - While the clock is open.
144
154
  */
@@ -156,6 +166,14 @@ export declare class HelixHeadlessTimeInput extends HelixHeadlessTimeInput_base
156
166
  * lets autofill work at all.
157
167
  */
158
168
  autocomplete: string;
169
+ /**
170
+ * Whether the field shows a button that empties it, as Input's `clearable`
171
+ * does. Shown only while the field holds something to clear and is neither
172
+ * disabled nor read-only.
173
+ */
174
+ clearable: boolean;
175
+ /** The accessible name of the clear button, which shows only an icon. */
176
+ clearLabel: string;
159
177
  /** Typed, so a keystroke is announced as `input` alone. See `announcesInput`. */
160
178
  protected readonly announcesInput = true;
161
179
  /**
@@ -256,13 +274,20 @@ export declare class HelixHeadlessTimeInput extends HelixHeadlessTimeInput_base
256
274
  * The message shown when `disable-times` bars a time.
257
275
  */
258
276
  unavailableMessage: string;
277
+ /**
278
+ * The message for text that is no time, where `bad-input-message` is not
279
+ * set. `{mode}` is `single`, `range`, or `multiple`, and `{shape}` is `HH:mm`, or `HH:mm:ss` with `seconds`.
280
+ */
281
+ unreadableMessage: string;
259
282
  /** Every time in the value, which may hold more than one. */
260
283
  private get held();
261
284
  /**
262
- * Segments hold one time, so `range` and `multiple` keep the line of text,
263
- * whose separators and tags are how those modes write several.
285
+ * A row of segments holds one time, and `range` takes two rows with a dash
286
+ * between them, as React Aria's range fields do. `multiple` keeps the line
287
+ * of text, whose tags are how it writes several.
264
288
  */
265
289
  protected get segmentable(): boolean;
290
+ protected get segmentGroups(): number;
266
291
  protected get segmentedKind(): 'time';
267
292
  protected get segmentLocale(): string;
268
293
  protected get segmentOptions(): {
@@ -287,6 +312,18 @@ export declare class HelixHeadlessTimeInput extends HelixHeadlessTimeInput_base
287
312
  private get shownTimes();
288
313
  /** How many are held beyond the ones shown. */
289
314
  private get restCount();
315
+ /**
316
+ * A time as the reader wrote it, as the value writes one: `HH:mm` as it
317
+ * is, and `2:30 PM`, `2.30pm`, `14h30`, or `1430` read back, with the half
318
+ * of the day in the locale's words. Empty where it is no time.
319
+ */
320
+ private read;
321
+ /**
322
+ * The text the reader leaves, read: a time written as a reader writes one
323
+ * becomes the value as the field commits. Not on each key, where `2:3` is
324
+ * a time a moment before it is `2:30`.
325
+ */
326
+ private readonly handleTextCommit;
290
327
  /** Adds what was typed to the set, or takes the last one back out. */
291
328
  private readonly handleDraftKey;
292
329
  /** Takes one time back out of the set. */
@@ -368,8 +405,19 @@ export declare class HelixHeadlessTimeInput extends HelixHeadlessTimeInput_base
368
405
  * consumer gets an inert unknown element and no mark whatsoever.
369
406
  */
370
407
  protected renderTrigger(): TemplateResult;
408
+ /** Whether there is a clear button on screen right now. */
409
+ private get showsClear();
410
+ /**
411
+ * Empties the field, through `requestValue` so a consumer who owns the
412
+ * value decides, then says `change` and `helix-clear` and puts focus back
413
+ * in the field, the button having gone.
414
+ */
415
+ private readonly handleClear;
416
+ /** The cross on the clear button, behind a seam a tier can move. */
417
+ protected renderClearIcon(): TemplateResult;
371
418
  /** The line of text the times are typed into, where the field shows no segments. */
372
419
  private renderControl;
420
+ protected willUpdate(changed: PropertyValues): void;
373
421
  protected render(): TemplateResult;
374
422
  /**
375
423
  * The value as a `Date` on January 1, 1970 UTC, or `null` while there is no