@fusion.dev/helix-headless 0.155.0 → 0.157.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 (74) hide show
  1. package/custom-elements.json +2695 -137
  2. package/dist/canvas/node/canvas-node.js +1 -1
  3. package/dist/canvas/root/canvas-root.d.ts +17 -2
  4. package/dist/canvas/root/canvas-root.js +23 -6
  5. package/dist/chat/composer/chat-composer.js +1 -1
  6. package/dist/chat/root/chat-root.d.ts +14 -1
  7. package/dist/chat/root/chat-root.js +14 -4
  8. package/dist/combobox/root/combobox-root.d.ts +10 -0
  9. package/dist/combobox/root/combobox-root.js +18 -3
  10. package/dist/command/root/command-root.d.ts +116 -1
  11. package/dist/command/root/command-root.js +124 -15
  12. package/dist/data-table/root/data-table-root.d.ts +25 -0
  13. package/dist/data-table/root/data-table-root.js +13 -3
  14. package/dist/date-input/root/date-input-root.d.ts +32 -1
  15. package/dist/date-input/root/date-input-root.js +7 -1
  16. package/dist/drilldown/item/drilldown-item.d.ts +49 -1
  17. package/dist/drilldown/item/drilldown-item.js +77 -17
  18. package/dist/drilldown/root/drilldown-root.d.ts +45 -5
  19. package/dist/drilldown/root/drilldown-root.js +51 -5
  20. package/dist/element-events.generated.js +2 -1
  21. package/dist/filter-input/menu/filter-menu.d.ts +1 -0
  22. package/dist/filter-input/menu/filter-menu.js +4 -4
  23. package/dist/graph/root/graph-root.d.ts +13 -0
  24. package/dist/graph/root/graph-root.js +16 -0
  25. package/dist/list/root/list-root.d.ts +74 -1
  26. package/dist/list/root/list-root.js +92 -4
  27. package/dist/list/selection.js +3 -3
  28. package/dist/map/root/map-root.d.ts +6 -0
  29. package/dist/map/root/map-root.js +8 -1
  30. package/dist/megamenu/root/megamenu-root.d.ts +50 -13
  31. package/dist/megamenu/root/megamenu-root.js +44 -6
  32. package/dist/menu/item/menu-item.d.ts +24 -1
  33. package/dist/menu/item/menu-item.js +42 -2
  34. package/dist/menu/root/menu-root.d.ts +120 -9
  35. package/dist/menu/root/menu-root.js +329 -26
  36. package/dist/notifications/root/notifications-root.d.ts +20 -0
  37. package/dist/notifications/root/notifications-root.js +32 -1
  38. package/dist/number-input/root/number-input-root.d.ts +63 -6
  39. package/dist/number-input/root/number-input-root.js +96 -11
  40. package/dist/object-inspector/root/object-inspector-root.d.ts +36 -1
  41. package/dist/object-inspector/root/object-inspector-root.js +26 -2
  42. package/dist/option/root/option-root.d.ts +32 -0
  43. package/dist/option/root/option-root.js +44 -5
  44. package/dist/outline/root/outline-root.d.ts +6 -2
  45. package/dist/outline/root/outline-root.js +7 -3
  46. package/dist/table/root/table-root.d.ts +20 -0
  47. package/dist/table/root/table-root.js +13 -3
  48. package/dist/time-input/root/time-input-root.d.ts +32 -1
  49. package/dist/time-input/root/time-input-root.js +9 -2
  50. package/dist/time-picker/root/time-picker-root.d.ts +63 -1
  51. package/dist/time-picker/root/time-picker-root.js +58 -7
  52. package/dist/timeline/item/timeline-item.d.ts +25 -0
  53. package/dist/timeline/item/timeline-item.js +27 -2
  54. package/dist/toast/item/toast-item.d.ts +26 -0
  55. package/dist/toast/item/toast-item.js +55 -3
  56. package/dist/toast/root/toast-root.d.ts +32 -0
  57. package/dist/toast/root/toast-root.js +47 -2
  58. package/dist/video/root/keys.d.ts +6 -2
  59. package/dist/video/root/keys.js +18 -1
  60. package/dist/video/root/video-root.d.ts +61 -1
  61. package/dist/video/root/video-root.js +79 -11
  62. package/dist/video-playlist/item/video-playlist-item.d.ts +15 -1
  63. package/dist/video-playlist/item/video-playlist-item.js +7 -0
  64. package/dist/video-playlist/menu/video-playlist-menu.d.ts +1 -0
  65. package/dist/video-playlist/menu/video-playlist-menu.js +1 -1
  66. package/dist/video-playlist/root/video-playlist-root.d.ts +43 -4
  67. package/dist/video-playlist/root/video-playlist-root.js +69 -2
  68. package/dist/virtual-list/root/virtual-list-root.d.ts +8 -0
  69. package/dist/virtual-list/root/virtual-list-root.js +3 -0
  70. package/lint.json +1 -1
  71. package/package.json +5 -5
  72. package/types/jsx.d.ts +3346 -266
  73. package/types/vue.d.ts +910 -66
  74. package/types/words.d.ts +124 -0
@@ -96,6 +96,11 @@ declare const HelixHeadlessTable_base: (new (...args: any[]) => import("@fusion.
96
96
  * | A letter or a word | Moves to the next row whose first cell starts with what you typed. |
97
97
  * | Tab | Leaves the table. |
98
98
  *
99
+ * When the rows did not load, set `error-text`, such as "The orders did not
100
+ * load", and put a button that tries again in the `error` slot. The line shows
101
+ * after the rows in place of the empty and loading lines, the live region says
102
+ * it, and the table takes the `error` state. Clear it once you try again.
103
+ *
99
104
  * The CSS custom properties listed for this component are written by it and
100
105
  * read by the stylesheet that draws it. Do not set them. Each holds a number
101
106
  * the component computes and CSS cannot, so the stylesheet can draw the table
@@ -109,6 +114,7 @@ declare const HelixHeadlessTable_base: (new (...args: any[]) => import("@fusion.
109
114
  *
110
115
  * @slot - The table's rows.
111
116
  * @slot empty - Content shown when there are no rows and none are loading, such as a picture and a link. Falls back to `empty-text`, which the live region still says.
117
+ * @slot error - Content shown while `error-text` is set, such as a button that tries again. Falls back to `error-text`, which the live region still says.
112
118
  * @slot heading - Words that name the table, shown above it, such as a heading.
113
119
  * @slot loading - Content shown while the rows load, such as a spinner. Falls back to `loading-text`, which the live region still says.
114
120
  *
@@ -132,6 +138,7 @@ declare const HelixHeadlessTable_base: (new (...args: any[]) => import("@fusion.
132
138
  * @csspart checkbox-mark-indeterminate - Added to `checkbox-mark` while only some rows are selected.
133
139
  * @csspart drag-preview - A copy of the row a pointer drags by its grip, which follows the pointer in the top layer until the row is let go, in a `reorderable` table. Hidden from assistive technology.
134
140
  * @csspart empty - The text shown when there are no rows.
141
+ * @csspart error - The line shown after the rows while `error-text` is set, in place of the empty and loading text.
135
142
  * @csspart grid-end - An empty box after the rows, in a `grid` table. Tab focuses it on its way out of the table, so the key leaves from the end.
136
143
  * @csspart grid-start - An empty box before the rows, in a `grid` table. Shift with Tab focuses it on its way out of the table.
137
144
  * @csspart grip-head - The heading over the row grips, in a `reorderable` table.
@@ -142,6 +149,9 @@ declare const HelixHeadlessTable_base: (new (...args: any[]) => import("@fusion.
142
149
  * @csspart table - The grid.
143
150
  *
144
151
  * @cssproperty --table-template - The column track list the table works out from its columns. The component writes it inline on the element that reads it, so setting it has no effect. Set `columns` to change the tracks.
152
+ *
153
+ * @cssstate empty - While there are no rows and none are loading.
154
+ * @cssstate error - While `error-text` is set.
145
155
  */
146
156
  export declare class HelixHeadlessTable extends HelixHeadlessTable_base {
147
157
  /**
@@ -214,6 +224,16 @@ export declare class HelixHeadlessTable extends HelixHeadlessTable_base {
214
224
  descendingLabel: string;
215
225
  /** What screen readers hear when a held row is dropped, in a `reorderable` table. */
216
226
  droppedLabel: string;
227
+ /**
228
+ * The text shown and announced when the rows could not load. Set it when a
229
+ * fetch fails, and clear it once you try again. Empty, the default, shows
230
+ * nothing.
231
+ *
232
+ * While it is set, a line after the rows shows it, or your own content in the
233
+ * `error` slot, in place of the empty and loading lines, and the live region
234
+ * says it.
235
+ */
236
+ errorText: string;
217
237
  /**
218
238
  * The table's name where neither `label` nor an `aria-label` or
219
239
  * `aria-labelledby` on the host gives it one.
@@ -44,7 +44,7 @@ const SAMPLE = 200;
44
44
  const textOf = (element) => (element?.textContent ?? '').replace(/\s+/g, ' ').trim();
45
45
  const lengthOf = (element) => textOf(element).length;
46
46
  const numberOf = (value) => Number(value.replaceAll(',', ''));
47
- const STATUS = new Set(['empty', 'loading']);
47
+ const STATUS = new Set(['empty', 'error', 'loading']);
48
48
  const NAMED = new Set([...STATUS, 'heading']);
49
49
  let HelixHeadlessTable = class HelixHeadlessTable extends MixinCollection(HelixElement) {
50
50
  constructor() {
@@ -57,6 +57,7 @@ let HelixHeadlessTable = class HelixHeadlessTable extends MixinCollection(HelixE
57
57
  this.defaultSort = '';
58
58
  this.descendingLabel = 'descending';
59
59
  this.droppedLabel = 'Dropped';
60
+ this.errorText = '';
60
61
  this.fallbackLabel = 'Table';
61
62
  this.grid = false;
62
63
  this.label = '';
@@ -496,6 +497,8 @@ let HelixHeadlessTable = class HelixHeadlessTable extends MixinCollection(HelixE
496
497
  }
497
498
  updated(changed) {
498
499
  super.updated(changed);
500
+ this.toggleState('error', this.errorText !== '');
501
+ this.toggleState('empty', !this.loading && !this.hasRows);
499
502
  this.assign();
500
503
  this.alignCells();
501
504
  this.markSorted();
@@ -561,8 +564,12 @@ let HelixHeadlessTable = class HelixHeadlessTable extends MixinCollection(HelixE
561
564
  this.handleSlotChange();
562
565
  }}
563
566
  ></slot>
564
- ${this.renderStatus(this.hasRows, grid ? 'gridcell' : 'cell')}
565
- </div>${end}${this.renderLive(this.hasRows)}${this.reorder.drawDragPreview()}`;
567
+ ${this.errorText === ''
568
+ ? this.renderStatus(this.hasRows, grid ? 'gridcell' : 'cell')
569
+ : html `<p part="error" role="row"><span role=${grid ? 'gridcell' : 'cell'}><slot name="error">${this.errorText}</slot></span></p>`}
570
+ </div>${end}${this.errorText === ''
571
+ ? this.renderLive(this.hasRows)
572
+ : html `<span part="live" role="status">${this.errorText}</span>`}${this.reorder.drawDragPreview()}`;
566
573
  }
567
574
  };
568
575
  __decorate([
@@ -590,6 +597,9 @@ __decorate([
590
597
  words,
591
598
  property({ attribute: 'dropped-label' })
592
599
  ], HelixHeadlessTable.prototype, "droppedLabel", void 0);
600
+ __decorate([
601
+ property({ attribute: 'error-text' })
602
+ ], HelixHeadlessTable.prototype, "errorText", void 0);
593
603
  __decorate([
594
604
  words,
595
605
  property({ attribute: 'fallback-label' })
@@ -34,17 +34,41 @@ declare const HelixHeadlessTimeInput_base: (new (...args: any[]) => import("@fus
34
34
  * hour, a minute, and AM or PM where the clock has them, each set with digits
35
35
  * or the arrow keys. Alt+Down opens the list from a segment.
36
36
  *
37
+ * Text that is not a time gives the form an empty value, as a native time
38
+ * field does, and `value` keeps the text so it can be corrected.
39
+ *
40
+ * **Keyboard.** The field is one tab stop, or each segment is with
41
+ * `segmented`, and the trigger is one more.
42
+ *
43
+ * | Key | What it does |
44
+ * |---|---|
45
+ * | Down | In the text field, opens the list on the chosen time. |
46
+ * | Alt with Down | Opens the list, from the text field or a segment. |
47
+ * | Enter | In the text field, takes the typed time and submits the form, as a native field does. In the list, chooses the highlighted time. |
48
+ * | Escape | Closes the list and puts focus back in the field. |
49
+ * | Up, Down on a segment | Steps the segment by one, round from its last value to its first. |
50
+ * | Page Up, Page Down on a segment | Steps by a page: two hours, or fifteen minutes or seconds. |
51
+ * | Home, End on a segment | Goes to the segment's first or last value. |
52
+ * | Digits on a segment | Fill it, and move to the next once it can take no more. |
53
+ * | A letter on the half of the day | Picks the half whose name starts with it, such as A for AM. |
54
+ * | Backspace, Delete on a segment | Empty the segment. |
55
+ * | Left, Right on a segment | Move to the previous or the next segment, along the line of text. |
56
+ *
57
+ * In the list, the keys are `TimePicker`'s.
58
+ *
37
59
  * @since 0.1.0
38
60
  * @status stable
39
61
  * @category forms
40
62
  * @aliases TimeField, InputTime
41
63
  * @documentation https://fusion.dev/helix/components/time-input
42
64
  *
65
+ * @slot end - Content after the time, before the trigger, such as a clear button.
43
66
  * @slot feedback - The message that says what is wrong with the value.
44
67
  * @slot hint - Help under the field.
45
68
  * @slot label - The label, for content richer than the `label` text.
46
69
  * @slot panel-footer - Content in the panel under the times, such as a note or an action.
47
70
  * @slot panel-header - Content in the panel above the times, such as a heading or a shortcut.
71
+ * @slot start - Content before the time, such as an icon.
48
72
  * @slot trigger - The icon on the field that shows a list opens.
49
73
  *
50
74
  * @event {Event} change - Fired after the value changes.
@@ -76,6 +100,8 @@ declare const HelixHeadlessTimeInput_base: (new (...args: any[]) => import("@fus
76
100
  * @csspart picker-column-search-field - The filter field.
77
101
  * @csspart picker-columns - The row of columns, in `wheels`.
78
102
  * @csspart picker-feedback - The picker's own message, under the times, while the picker's `feedback` is set.
103
+ * @csspart picker-hint - The picker's hint, which Time Input never sets: help under the field is the field's own `hint`.
104
+ * @csspart picker-label - The picker's visible label, which Time Input never draws: the field's `label` names it.
79
105
  * @csspart picker-list - The listbox holding the times, in `list`.
80
106
  * @csspart picker-list-empty - The text shown when the filter hides every option.
81
107
  * @csspart picker-list-error - The list's `error` part, which the list never sets: its times do not load.
@@ -283,7 +309,12 @@ export declare class HelixHeadlessTimeInput extends HelixHeadlessTimeInput_base
283
309
  * what says the reader is done.
284
310
  */
285
311
  private readonly handleWheelEnter;
286
- /** Each time, where `mode` holds several, sent one entry each. */
312
+ /**
313
+ * Each time, where `mode` holds several, sent one entry each.
314
+ *
315
+ * One time that is text and no time, such as `2:45 P`, is sent as an empty
316
+ * entry, as a native time field sends a time it cannot read.
317
+ */
287
318
  protected get answers(): readonly string[] | undefined;
288
319
  /** What is wrong with the times, which the text input cannot say. */
289
320
  protected validate(): string | readonly [keyof ValidityStateFlags, string];
@@ -146,7 +146,11 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinSegmented
146
146
  return held.length > 0 && held.every((one) => this.isTime(one)) ? held.join(',') : '';
147
147
  }
148
148
  get answers() {
149
- return this.mode === undefined ? undefined : this.held;
149
+ if (this.mode !== undefined) {
150
+ return this.held;
151
+ }
152
+ const text = this.value.trim();
153
+ return text === '' || this.isTime(text) ? undefined : [''];
150
154
  }
151
155
  validate() {
152
156
  const held = this.held;
@@ -197,7 +201,8 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinSegmented
197
201
  column-filter-mark: picker-column-filter-mark, column-hint: picker-column-hint,
198
202
  column-items: picker-column-items, column-label: picker-column-label,
199
203
  column-live: picker-column-live, column-loading: picker-column-loading, column-search: picker-column-search,
200
- column-search-field: picker-column-search-field, columns: picker-columns, feedback: picker-feedback, list: picker-list,
204
+ column-search-field: picker-column-search-field, columns: picker-columns, feedback: picker-feedback, hint: picker-hint,
205
+ label: picker-label, list: picker-list,
201
206
  list-empty: picker-list-empty, list-error: picker-list-error, list-feedback: picker-list-feedback,
202
207
  list-filter-icon: picker-list-filter-icon, list-filter-mark: picker-list-filter-mark,
203
208
  list-hint: picker-list-hint, list-items: picker-list-items, list-label: picker-list-label,
@@ -269,10 +274,12 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinSegmented
269
274
  render() {
270
275
  return html `${this.renderLabel()}
271
276
  <div part="field" @keydown=${this.handleKeydown}>
277
+ <slot name="start"></slot>
272
278
  ${this.mode === 'multiple'
273
279
  ? this.renderTokenRow(this.shownTimes.map((at) => this.renderTag(at)), this.restCount)
274
280
  : nothing}
275
281
  ${this.segmentsShown ? this.renderSegments() : this.renderControl()}
282
+ <slot name="end"></slot>
276
283
  <button
277
284
  part="trigger"
278
285
  type="button"
@@ -35,12 +35,37 @@ declare const HelixHeadlessTimePicker_base: (new (...args: any[]) => import("@fu
35
35
  * its choice in markup for the same reason. The list and the wheels both do
36
36
  * this, because each wheel is also a listbox.
37
37
  *
38
+ * **Keyboard.** The list is one tab stop, and each wheel is one.
39
+ *
40
+ * | Key | What it does |
41
+ * |---|---|
42
+ * | Down, Up | Moves to the next or the previous time, or in a wheel to the next or the previous hour, minute, second, or half of the day. |
43
+ * | Home, End | Moves to the first or the last time, or the first or the last in a wheel. |
44
+ * | Page Down, Page Up | Moves by as many times as the list shows at once. |
45
+ * | Digits | Moves to the next time that starts with what you typed, such as `9` for 9:00. |
46
+ * | Enter, Space | Chooses the highlighted time. Moving chooses too, except under `manual` or with a `mode`. With a `mode`, Space adds or removes the time. |
47
+ * | Right, Left | In the wheels, moves to the next or the previous wheel, mirrored right to left. |
48
+ * | Tab | Moves to the next wheel, then out of the picker. |
49
+ *
50
+ * **ARIA.** The list is a `listbox` named by `label`, a slotted label, the
51
+ * host's `aria-label` or its `aria-labelledby`. A development build warns
52
+ * where all four are empty. The list is described by the hint, or by the
53
+ * message in `feedback` in its place, and carries `aria-multiselectable` with
54
+ * a `mode` and `aria-invalid` while `feedback` says what is wrong. The wheels
55
+ * are a `group` with that name, holding one `listbox` per wheel, named "Hour",
56
+ * "Minute", "Second" and "AM/PM" in the page's language, and each described
57
+ * the same way.
58
+ *
38
59
  * @since 0.1.0
39
60
  * @status stable
40
61
  * @category forms
41
62
  * @aliases Time, TimeList
42
63
  * @documentation https://fusion.dev/helix/components/time-picker
43
64
  *
65
+ * @slot empty - What the list shows when no time falls between `min` and `max`, for content richer than `empty-text`.
66
+ * @slot hint - Help under the times, for content richer than the `hint` text.
67
+ * @slot label - A visible label above the times, which names the list, for content richer than the `label` text.
68
+ *
44
69
  * @event {Event} change - Fired after the value changes.
45
70
  * @event {HelixBeforeChangeEvent} helix-before-change - Fired before the time changes, and cancelable. Canceling it keeps the current time.
46
71
  * @event {Event} input - Fired after the value changes.
@@ -60,6 +85,8 @@ declare const HelixHeadlessTimePicker_base: (new (...args: any[]) => import("@fu
60
85
  * @csspart column-search-field - The filter field.
61
86
  * @csspart columns - The row of columns, in `wheels`.
62
87
  * @csspart feedback - The message under the times, while `feedback` is set.
88
+ * @csspart hint - Help under the times, shown while there is no message in `feedback`.
89
+ * @csspart label - The visible label above the times, drawn while a label is slotted.
63
90
  * @csspart list - The listbox holding the times, in `list`.
64
91
  * @csspart list-empty - The text shown when the filter hides every option.
65
92
  * @csspart list-error - The list's `error` part, which Time Picker never sets: its times do not load.
@@ -115,6 +142,19 @@ export declare class HelixHeadlessTimePicker extends HelixHeadlessTimePicker_bas
115
142
  * reached honestly rather than by handing over an empty range.
116
143
  */
117
144
  disableTimes: string;
145
+ /**
146
+ * What the list says when no time falls between `min` and `max`.
147
+ *
148
+ * The fallback of the `empty` slot.
149
+ */
150
+ emptyText: string;
151
+ /**
152
+ * Help under the times, such as the hours a shop is open, read by screen
153
+ * readers as the list's description. A message in `feedback` replaces it.
154
+ *
155
+ * The fallback of the `hint` slot.
156
+ */
157
+ hint: string;
118
158
  /**
119
159
  * The clock the times are written on: `h12` for twelve hours with a
120
160
  * meridiem, `h23` for twenty-four.
@@ -134,7 +174,8 @@ export declare class HelixHeadlessTimePicker extends HelixHeadlessTimePicker_bas
134
174
  /**
135
175
  * Accessible name for the list, which has no visible one of its own.
136
176
  *
137
- * An `aria-label` or `aria-labelledby` on the host names the list instead.
177
+ * An `aria-label` or `aria-labelledby` on the host names the list instead,
178
+ * and so does a label slotted into `label`, which is also drawn above it.
138
179
  */
139
180
  label: string;
140
181
  /**
@@ -473,6 +514,26 @@ export declare class HelixHeadlessTimePicker extends HelixHeadlessTimePicker_bas
473
514
  * aria-label="Slot">` was a listbox named "Time".
474
515
  */
475
516
  private get listName();
517
+ /** Whether a hint is slotted, which draws the `hint` part around it. */
518
+ private hintSlotted;
519
+ /** Whether the hint shows: there is one, and no message stands in its place. */
520
+ private get hinted();
521
+ /** The ids that describe each list: the hint, or the message in its place. */
522
+ private get describedBy();
523
+ /**
524
+ * The hint under the times, or a bare slot while there is none to show,
525
+ * hidden while a message stands in its place, so a slotted hint goes too.
526
+ */
527
+ private renderHint;
528
+ /** Whether a label is slotted, which draws the `label` part around it. */
529
+ private labelSlotted;
530
+ /** Reads the `label` slot, and the list's name again with it. */
531
+ private readonly handleLabelSlot;
532
+ /**
533
+ * The slotted label, in the `label` part, above the times. A bare slot while
534
+ * nothing is slotted, which draws no box.
535
+ */
536
+ private renderSlottedLabel;
476
537
  /**
477
538
  * Hears the host's `aria-label` and `aria-labelledby` change, which no
478
539
  * property reports. Made on connection, there being no `MutationObserver` on
@@ -482,6 +543,7 @@ export declare class HelixHeadlessTimePicker extends HelixHeadlessTimePicker_bas
482
543
  connectedCallback(): void;
483
544
  disconnectedCallback(): void;
484
545
  protected willUpdate(changed: PropertyValues): void;
546
+ protected firstUpdated(changed: PropertyValues): void;
485
547
  protected updated(changed: PropertyValues): void;
486
548
  /**
487
549
  * Moves focus to the list, or to the first wheel.
@@ -4,9 +4,9 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { clockLocale, commaList, customElement, dayPeriod, formatWords, HelixFormElement, hostWords, inlineStep, isTwelveHour, MixinControllerValue, MixinPickerFeedback, nearestTime, offeredTimes, reportValues, secondsAsValue, timeBarred, timeFault, valueAsSeconds, wheelParts, wholeHour, words, wordsLocale, writeClock, } from '@fusion.dev/helix-core';
7
+ import { auditName, clockLocale, commaList, customElement, dayPeriod, formatWords, HelixFormElement, hostWords, inlineStep, isTwelveHour, MixinControllerValue, MixinPickerFeedback, nearestTime, offeredTimes, reportValues, secondsAsValue, slotFilled, slottedWords, timeBarred, timeFault, valueAsSeconds, wheelParts, wholeHour, words, wordsLocale, writeClock, } from '@fusion.dev/helix-core';
8
8
  import { nothing } from 'lit';
9
- import { property } from 'lit/decorators.js';
9
+ import { property, state } from 'lit/decorators.js';
10
10
  import { html } from 'lit/static-html.js';
11
11
  import { wheelName } from '../wheel-name.js';
12
12
  import '../../listbox/index.js';
@@ -16,6 +16,8 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinPickerF
16
16
  super(...arguments);
17
17
  this.badInputMessage = '{mode, select, single {Enter a time as {shape}.} other {Enter times as {shape}, separated by commas.}}';
18
18
  this.disableTimes = '';
19
+ this.emptyText = 'No times to choose from';
20
+ this.hint = '';
19
21
  this.label = 'Time';
20
22
  this.locale = '';
21
23
  this.manual = false;
@@ -83,6 +85,12 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinPickerF
83
85
  this.unavailableMessage = 'Choose a time that is available.';
84
86
  this.valueMissingMessage = 'Choose a time.';
85
87
  this.listRevealed = false;
88
+ this.hintSlotted = false;
89
+ this.labelSlotted = false;
90
+ this.handleLabelSlot = (event) => {
91
+ this.labelSlotted = slotFilled(event.target);
92
+ this.requestUpdate();
93
+ };
86
94
  this.naming = undefined;
87
95
  this.handleColumnsKeydown = (event) => {
88
96
  const step = inlineStep(this, event.key);
@@ -247,7 +255,27 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinPickerF
247
255
  this.reveal();
248
256
  }
249
257
  get listName() {
250
- return hostWords(this, this.label);
258
+ const slotted = this.labelSlotted ? slottedWords(this, ['label']) : '';
259
+ return hostWords(this, slotted || this.label);
260
+ }
261
+ get hinted() {
262
+ return (this.hint !== '' || this.hintSlotted) && this.feedback === '';
263
+ }
264
+ get describedBy() {
265
+ return this.feedback !== '' ? 'feedback' : this.hinted ? 'hint' : nothing;
266
+ }
267
+ renderHint() {
268
+ const read = (event) => {
269
+ this.hintSlotted = slotFilled(event.target);
270
+ };
271
+ return this.hinted
272
+ ? html `<p part="hint" id="hint"><slot name="hint" @slotchange=${read}>${this.hint}</slot></p>`
273
+ : html `<slot name="hint" ?hidden=${this.feedback !== ''} @slotchange=${read}></slot>`;
274
+ }
275
+ renderSlottedLabel() {
276
+ return this.labelSlotted
277
+ ? html `<div part="label"><slot name="label" @slotchange=${this.handleLabelSlot}></slot></div>`
278
+ : html `<slot name="label" @slotchange=${this.handleLabelSlot}></slot>`;
251
279
  }
252
280
  connectedCallback() {
253
281
  super.connectedCallback();
@@ -264,6 +292,14 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinPickerF
264
292
  this.report();
265
293
  this.toggleState('blank', this.value === '');
266
294
  }
295
+ firstUpdated(changed) {
296
+ super.firstUpdated(changed);
297
+ auditName(this, () => null, 'Give it a `label`, a label in the `label` slot, or an `aria-label` that says what the time is for.', () => this.listName !== '');
298
+ const list = this.partNamed('list');
299
+ if (list) {
300
+ auditName(list, () => null, '', () => true);
301
+ }
302
+ }
267
303
  updated(changed) {
268
304
  super.updated(changed);
269
305
  this.reportRequired();
@@ -284,7 +320,7 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinPickerF
284
320
  return html `<${this.tag('listbox')}
285
321
  part="column"
286
322
  data-column=${part}
287
- aria-describedby=${this.feedback === '' ? nothing : 'feedback'}
323
+ aria-describedby=${this.describedBy}
288
324
  aria-invalid=${this.marked ? 'true' : nothing}
289
325
  exportparts="empty: column-empty, error: column-error, feedback: column-feedback, filter-icon: column-filter-icon,
290
326
  filter-mark: column-filter-mark, hint: column-hint, items: column-items, label: column-label,
@@ -336,7 +372,7 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinPickerF
336
372
  renderList() {
337
373
  return html `<${this.tag('listbox')}
338
374
  part="list"
339
- aria-describedby=${this.feedback === '' ? nothing : 'feedback'}
375
+ aria-describedby=${this.describedBy}
340
376
  aria-invalid=${this.marked ? 'true' : nothing}
341
377
  exportparts="empty: list-empty, error: list-error, feedback: list-feedback, filter-icon: list-filter-icon,
342
378
  filter-mark: list-filter-mark, hint: list-hint, items: list-items, label: list-label,
@@ -344,6 +380,7 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinPickerF
344
380
  aria-label=${this.listName}
345
381
  data-unset=${this.value === '' ? '' : nothing}
346
382
  ?disabled=${this.isDisabled}
383
+ empty-text=${this.emptyText}
347
384
  ?manual=${this.manual}
348
385
  ?multiple=${this.mode !== undefined}
349
386
  .value=${this.value}
@@ -365,13 +402,14 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinPickerF
365
402
  >${writeClock(at, this.clock, this.seconds)}</${this.tag('option')}
366
403
  >`;
367
404
  })}
405
+ <slot name="empty" slot="empty">${this.emptyText}</slot>
368
406
  </${this.tag('listbox')}>`;
369
407
  }
370
408
  render() {
371
409
  if (this.variant === 'wheels' && this.mode === undefined) {
372
- return html `${this.renderSpinner()}${this.renderPickerFeedback()}`;
410
+ return html `${this.renderSlottedLabel()}${this.renderSpinner()}${this.renderHint()}${this.renderPickerFeedback()}`;
373
411
  }
374
- return html `${this.renderList()}${this.renderPickerFeedback()}`;
412
+ return html `${this.renderSlottedLabel()}${this.renderList()}${this.renderHint()}${this.renderPickerFeedback()}`;
375
413
  }
376
414
  get valueAsDate() {
377
415
  const at = valueAsSeconds(this.value);
@@ -397,6 +435,13 @@ __decorate([
397
435
  __decorate([
398
436
  property({ attribute: 'disable-times' })
399
437
  ], HelixHeadlessTimePicker.prototype, "disableTimes", void 0);
438
+ __decorate([
439
+ words,
440
+ property({ attribute: 'empty-text' })
441
+ ], HelixHeadlessTimePicker.prototype, "emptyText", void 0);
442
+ __decorate([
443
+ property()
444
+ ], HelixHeadlessTimePicker.prototype, "hint", void 0);
400
445
  __decorate([
401
446
  property({ attribute: 'hour-cycle' })
402
447
  ], HelixHeadlessTimePicker.prototype, "hourCycle", void 0);
@@ -466,6 +511,12 @@ __decorate([
466
511
  __decorate([
467
512
  property({ reflect: true })
468
513
  ], HelixHeadlessTimePicker.prototype, "variant", void 0);
514
+ __decorate([
515
+ state()
516
+ ], HelixHeadlessTimePicker.prototype, "hintSlotted", void 0);
517
+ __decorate([
518
+ state()
519
+ ], HelixHeadlessTimePicker.prototype, "labelSlotted", void 0);
469
520
  HelixHeadlessTimePicker = __decorate([
470
521
  customElement('helix-headless-time-picker')
471
522
  ], HelixHeadlessTimePicker);
@@ -8,6 +8,12 @@ import { type PropertyValues, type TemplateResult } from 'lit';
8
8
  * machine-readable value such as "2026-08-19" travels with a label such as
9
9
  * "yesterday". Without `datetime`, the label is a `<span>`.
10
10
  *
11
+ * **`relative` writes the time for you** as how long ago it was, such as "3
12
+ * hours ago", from `datetime`, where the `time` slot is empty. The words move
13
+ * on as they go stale, "3 hours ago" becoming "4 hours ago", with one wakeup
14
+ * shared by every reading on the page, as `FormatDate` keeps its own. They are
15
+ * in the language of the page, or of the nearest `lang`.
16
+ *
11
17
  * Each entry draws the connector line that arrives at it and hides it when it
12
18
  * is the first entry, using `:host(:first-child)`. This needs no index, no
13
19
  * counting parent, and no script. `Steps` and `Breadcrumbs` work the same way.
@@ -44,6 +50,25 @@ export declare class HelixHeadlessTimelineItem extends HelixElement {
44
50
  * know what instant it names.
45
51
  */
46
52
  datetime: string;
53
+ /**
54
+ * Writes the time as how long ago it was, or how long until it, from
55
+ * `datetime`, where the `time` slot is empty: `long` ("3 hours ago"),
56
+ * `short` ("3 hr. ago") or `narrow` ("3h ago"). Set with no value, it is
57
+ * `long`. The words move on as they go stale.
58
+ */
59
+ relative?: 'long' | 'narrow' | 'short';
60
+ /** The time the reading is drawn against, moved on when the words go stale. */
61
+ private readonly clock;
62
+ /**
63
+ * What the `time` slot shows where nothing is slotted: the reading under
64
+ * `relative`, read against the clock before each render, and nothing
65
+ * otherwise.
66
+ */
67
+ private reading;
68
+ /** The instant `datetime` names, where `relative` asks for a reading of it. */
69
+ private get moment();
70
+ /** What the reading says at `now`, and when it stops being true. */
71
+ private readingAt;
47
72
  /**
48
73
  * The kind of event. It sets the color of the mark and nothing else.
49
74
  */
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { customElement, HelixElement, hostAria } from '@fusion.dev/helix-core';
7
+ import { customElement, HelixElement, hostAria, RelativeClockController, relativeTime, wordsLocale, } from '@fusion.dev/helix-core';
8
8
  import { circle, record } from '@fusion.dev/helix-icons';
9
9
  import { html } from 'lit';
10
10
  import { property } from 'lit/decorators.js';
@@ -13,9 +13,31 @@ let HelixHeadlessTimelineItem = class HelixHeadlessTimelineItem extends HelixEle
13
13
  constructor() {
14
14
  super(...arguments);
15
15
  this.datetime = '';
16
+ this.clock = new RelativeClockController(this, {
17
+ expires: (now) => {
18
+ const moment = this.moment;
19
+ return moment ? this.readingAt(moment, now).next : undefined;
20
+ },
21
+ });
22
+ this.reading = '';
23
+ }
24
+ get moment() {
25
+ if (this.relative === undefined || this.datetime === '') {
26
+ return undefined;
27
+ }
28
+ const parsed = new Date(this.datetime);
29
+ return Number.isNaN(parsed.getTime()) ? undefined : parsed;
30
+ }
31
+ readingAt(moment, now) {
32
+ return relativeTime(wordsLocale(this), { style: this.relative || 'long' })(moment, now);
16
33
  }
17
34
  willUpdate(changed) {
18
35
  super.willUpdate(changed);
36
+ if (['datetime', 'relative'].some((key) => changed.has(key))) {
37
+ this.clock.tick();
38
+ }
39
+ const moment = this.moment;
40
+ this.reading = moment ? this.readingAt(moment, new Date(this.clock.now)).text : '';
19
41
  hostAria(this.internals, { role: 'listitem' });
20
42
  }
21
43
  renderMarker() {
@@ -29,7 +51,7 @@ let HelixHeadlessTimelineItem = class HelixHeadlessTimelineItem extends HelixEle
29
51
  >`;
30
52
  }
31
53
  render() {
32
- const when = html `<slot name="time"></slot>`;
54
+ const when = html `<slot name="time">${this.reading}</slot>`;
33
55
  return html `<div part="rail">
34
56
  ${this.renderMarker()}
35
57
  </div>
@@ -46,6 +68,9 @@ let HelixHeadlessTimelineItem = class HelixHeadlessTimelineItem extends HelixEle
46
68
  __decorate([
47
69
  property()
48
70
  ], HelixHeadlessTimelineItem.prototype, "datetime", void 0);
71
+ __decorate([
72
+ property()
73
+ ], HelixHeadlessTimelineItem.prototype, "relative", void 0);
49
74
  __decorate([
50
75
  property({ reflect: true })
51
76
  ], HelixHeadlessTimelineItem.prototype, "tone", void 0);
@@ -25,6 +25,9 @@ declare const HelixHeadlessToastItem_base: (new (...args: any[]) => import("@fus
25
25
  * control. While it moves, the distance is on the `item` part as
26
26
  * `--toast-swipe-x`.
27
27
  *
28
+ * **Escape dismisses the message holding focus**, through the same
29
+ * `helix-dismiss`, and focus moves to the next message.
30
+ *
28
31
  * **Messages are inverted by default.** The item flips `color-scheme`, so a new
29
32
  * message stands out from the page without using a color that carries meaning.
30
33
  *
@@ -57,6 +60,10 @@ declare const HelixHeadlessToastItem_base: (new (...args: any[]) => import("@fus
57
60
  *
58
61
  * @cssproperty --toast-duration - How long this message stays, in milliseconds, for a progress hint.
59
62
  * @cssproperty --toast-swipe-x - How far a swipe has moved the message across, in pixels, toward the right.
63
+ *
64
+ * @cssstate closed - The message has been dismissed and is hidden, or leaving.
65
+ * @cssstate paused - The dismiss timer is paused, while a pointer or focus is on the message, or while the page is hidden.
66
+ * @cssstate waiting - The region holds the message back, past its `max-visible`, until a message before it goes.
60
67
  */
61
68
  export declare class HelixHeadlessToastItem extends HelixHeadlessToastItem_base {
62
69
  /**
@@ -110,6 +117,18 @@ export declare class HelixHeadlessToastItem extends HelixHeadlessToastItem_base
110
117
  * A subset of the tone axis: a toast reports a status and never a brand role.
111
118
  */
112
119
  tone?: 'danger' | 'info' | 'success' | 'warning';
120
+ /**
121
+ * Whether the region holds the message back, because more messages are
122
+ * showing than its `max-visible`. The region sets it. A waiting message is
123
+ * hidden, its timer has not started, and it is said once it shows.
124
+ */
125
+ waiting: boolean;
126
+ /** Whether the dismiss timer has started, which it does once the message first shows. */
127
+ private started;
128
+ /** Whether the timer is held for `waiting`, so the hold is let go once. */
129
+ private holding;
130
+ /** Starts the timer when the message first shows, and holds it while it waits again. */
131
+ private wait;
113
132
  /** Whether it is showing, whoever said so, and `undefined` until somebody has. */
114
133
  private openState;
115
134
  /**
@@ -128,6 +147,13 @@ export declare class HelixHeadlessToastItem extends HelixHeadlessToastItem_base
128
147
  private readonly handlePause;
129
148
  private readonly handleResume;
130
149
  connectedCallback(): void;
150
+ disconnectedCallback(): void;
151
+ /**
152
+ * Escape dismisses the message holding focus, as Radix's toast does. Taken
153
+ * only when the dismissal goes ahead, so a refused one leaves the key to
154
+ * the page.
155
+ */
156
+ private readonly handleKeydown;
131
157
  protected willUpdate(changed: PropertyValues): void;
132
158
  protected updated(changed: PropertyValues): void;
133
159
  /** The elements assigned to one of the message's named slots. */