@fusion.dev/helix-headless 0.178.0 → 0.180.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 (54) hide show
  1. package/custom-elements.json +543 -145
  2. package/dist/button/root/button-root.d.ts +12 -0
  3. package/dist/button/root/button-root.js +9 -0
  4. package/dist/chat/composer/chat-composer.d.ts +2 -1
  5. package/dist/chat/composer/chat-composer.js +1 -1
  6. package/dist/combobox/root/combobox-root.d.ts +12 -0
  7. package/dist/combobox/root/combobox-root.js +3 -2
  8. package/dist/command/root/command-root.d.ts +12 -0
  9. package/dist/command/root/command-root.js +3 -2
  10. package/dist/data-table/root/data-table-root.d.ts +13 -1
  11. package/dist/data-table/root/data-table-root.js +18 -15
  12. package/dist/date-input/root/date-input-root.d.ts +7 -0
  13. package/dist/date-input/root/date-input-root.js +12 -0
  14. package/dist/date-picker/root/date-picker-root.d.ts +7 -1
  15. package/dist/date-picker/root/date-picker-root.js +12 -0
  16. package/dist/drilldown/item/drilldown-item.d.ts +6 -0
  17. package/dist/drilldown/item/drilldown-item.js +9 -0
  18. package/dist/drilldown/root/drilldown-root.js +9 -0
  19. package/dist/element-events.generated.js +7 -1
  20. package/dist/filter-input/root/filter-input-root.d.ts +13 -0
  21. package/dist/filter-input/root/filter-input-root.js +2 -1
  22. package/dist/heatmap/root/heatmap-root.d.ts +54 -42
  23. package/dist/heatmap/root/heatmap-root.js +72 -54
  24. package/dist/listbox/root/listbox-root.d.ts +16 -2
  25. package/dist/listbox/root/listbox-root.js +8 -4
  26. package/dist/menu/item/menu-item.d.ts +6 -0
  27. package/dist/menu/item/menu-item.js +9 -0
  28. package/dist/meter/root/meter-root.js +1 -2
  29. package/dist/notifications/notification/notification.d.ts +37 -9
  30. package/dist/notifications/notification/notification.js +35 -5
  31. package/dist/option/among.d.ts +23 -0
  32. package/dist/option/among.js +2 -0
  33. package/dist/option/root/option-root.d.ts +1 -17
  34. package/dist/option/root/option-root.js +1 -4
  35. package/dist/scatter-chart/root/scatter-chart-root.d.ts +5 -4
  36. package/dist/scatter-chart/root/scatter-chart-root.js +1 -0
  37. package/dist/select/root/select-root.d.ts +13 -2
  38. package/dist/select/root/select-root.js +3 -3
  39. package/dist/speed-dial/item/speed-dial-item.d.ts +6 -0
  40. package/dist/speed-dial/item/speed-dial-item.js +9 -0
  41. package/dist/table/column/table-column.d.ts +1 -10
  42. package/dist/table/column/table-column.js +1 -2
  43. package/dist/table/edge.d.ts +10 -0
  44. package/dist/table/edge.js +2 -0
  45. package/dist/tabs/root/tabs-root.d.ts +1 -0
  46. package/dist/tabs/root/tabs-root.js +13 -0
  47. package/dist/tabs/tab/tab.d.ts +6 -0
  48. package/dist/tabs/tab/tab.js +9 -0
  49. package/dist/tooltip/root/tooltip-root.js +1 -0
  50. package/lint.json +1 -1
  51. package/package.json +5 -5
  52. package/types/jsx.d.ts +472 -130
  53. package/types/vue.d.ts +71 -50
  54. package/types/words.d.ts +27 -6
@@ -256,6 +256,18 @@ export declare class HelixHeadlessButton extends HelixFormElement {
256
256
  */
257
257
  protected announcePress(click: Event): void;
258
258
  private readonly handleClick;
259
+ /**
260
+ * Presses the button, as `click()` presses a native one: the command is sent,
261
+ * the form submits or resets, and `helix-press` says `virtual`.
262
+ *
263
+ * **Passed to the inner control, because a click on the host reaches nothing
264
+ * that acts.** The control in the shadow root is what answers a click, so
265
+ * `click()` from a script, an automation or a test, dispatched on the host,
266
+ * submitted nothing and sent no `helix-press`. The control's own click reaches
267
+ * the host's listeners once, retargeted, as one from a native button does. A
268
+ * disabled button does nothing, and a pending one refuses the press.
269
+ */
270
+ click(): void;
259
271
  /** What a press does: sends the command, and submits or resets the form. */
260
272
  private act;
261
273
  /**
@@ -89,6 +89,15 @@ let HelixHeadlessButton = class HelixHeadlessButton extends HelixFormElement {
89
89
  this.recent = {};
90
90
  this.dispatchEvent(new HelixPressEvent({ pointerType }));
91
91
  }
92
+ click() {
93
+ const control = this.partNamed('button');
94
+ if (control) {
95
+ control.click();
96
+ }
97
+ else {
98
+ super.click();
99
+ }
100
+ }
92
101
  act() {
93
102
  this.linkCommand();
94
103
  this.sendCommand();
@@ -15,7 +15,8 @@ import { type PropertyValues, type TemplateResult } from 'lit';
15
15
  * The composer clears only when no listener cancels the send, so the text is
16
16
  * kept when you cancel a send that failed.
17
17
  *
18
- * **In a form, the draft is the composer's value**, submitted under `name`. A
18
+ * **In a form, the draft is the composer's value**, submitted under `name`,
19
+ * and an empty draft submits an empty entry, as a native textarea does. A
19
20
  * form reset puts back the `value` the markup declared, and a `<fieldset
20
21
  * disabled>` turns the composer off. Sending is still `helix-send`, and it
21
22
  * does not submit the form.
@@ -124,7 +124,7 @@ let HelixHeadlessChatComposer = class HelixHeadlessChatComposer extends HelixFor
124
124
  super.updated(changed);
125
125
  this.markEmpty();
126
126
  if (changed.has('value')) {
127
- this.internals.setFormValue(this.value === '' ? null : this.value, this.value);
127
+ this.internals.setFormValue(this.value, this.value);
128
128
  }
129
129
  const field = this.partNamed('field');
130
130
  if (field?.validity) {
@@ -59,6 +59,18 @@ declare const HelixHeadlessCombobox_base: (new (...args: any[]) => import("@fusi
59
59
  * framework would re-render over an element the combobox inserted, so add the
60
60
  * option in your event handler.
61
61
  *
62
+ * **The options are an import of their own.** An option is the Option family's,
63
+ * which the combobox does not register, so a page that writes one imports it
64
+ * beside the combobox:
65
+ *
66
+ * ```js
67
+ * import '@fusion.dev/helix/combobox'
68
+ * import '@fusion.dev/helix/option'
69
+ * ```
70
+ *
71
+ * In development, a combobox holding an option nobody registered says so once
72
+ * the page has loaded, with the line to add.
73
+ *
62
74
  * @since 0.1.0
63
75
  * @status stable
64
76
  * @category forms
@@ -4,13 +4,13 @@ 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, afterLinkFollowed, chosenValues, countLabel, customElement, DelayController, drawAutofill, drawStatus, formatWords, HelixCreateEvent, HelixFilterEvent, HelixFormElement, handedLocale, MixinControllerOpen, MixinControllerValue, MixinField, MixinTokens, onApple, PopoverController, reportValues, StatusController, submitImplicitly, UpgradeController, words, wordsLocale, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, afterLinkFollowed, auditChildren, chosenValues, countLabel, customElement, DelayController, drawAutofill, drawStatus, formatWords, HelixCreateEvent, HelixFilterEvent, HelixFormElement, handedLocale, MixinControllerOpen, MixinControllerValue, MixinField, MixinTokens, onApple, PopoverController, reportValues, StatusController, submitImplicitly, UpgradeController, words, wordsLocale, } from '@fusion.dev/helix-core';
8
8
  import { caretDown } from '@fusion.dev/helix-icons';
9
9
  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
- import { optionsAmong } from '../../option/root/option-root.js';
13
+ import { optionsAmong } from '../../option/among.js';
14
14
  import { HelixHeadlessTag } from '../../tag/root/tag-root.js';
15
15
  import '../../listbox/index.js';
16
16
  import '../../tag/root/index.js';
@@ -507,6 +507,7 @@ let HelixHeadlessCombobox = class HelixHeadlessCombobox extends MixinControllerO
507
507
  }
508
508
  connectedCallback() {
509
509
  super.connectedCallback();
510
+ auditChildren(this, 'option', 'option', 'it suggests nothing');
510
511
  this.addEventListener('helix-select', this.handleLinkFollowed);
511
512
  this.watcher = new MutationObserver(() => this.requestUpdate());
512
513
  this.watcher.observe(this, {
@@ -72,6 +72,18 @@ declare const HelixHeadlessCommand_base: (new (...args: any[]) => import("@fusio
72
72
  * Aria's `Autocomplete` is. The commands are a `listbox` of `option`s. How many commands typing left, the empty text and
73
73
  * the loading text are said in a polite live region.
74
74
  *
75
+ * **The commands are an import of their own.** A command is an option, the
76
+ * Option family's, which the palette does not register, so a page that writes
77
+ * one imports it beside the palette:
78
+ *
79
+ * ```js
80
+ * import '@fusion.dev/helix/command'
81
+ * import '@fusion.dev/helix/option'
82
+ * ```
83
+ *
84
+ * In development, a palette holding an option nobody registered says so once
85
+ * the page has loaded, with the line to add.
86
+ *
75
87
  * @since 0.1.0
76
88
  * @status stable
77
89
  * @category navigation
@@ -4,11 +4,11 @@ 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, drawStatus, HelixElement, HelixSelectEvent, MixinControllerOpen, matchesChord, StatusController, words, } from '@fusion.dev/helix-core';
7
+ import { auditChildren, customElement, drawStatus, HelixElement, HelixSelectEvent, MixinControllerOpen, matchesChord, StatusController, words, } from '@fusion.dev/helix-core';
8
8
  import { nothing } from 'lit';
9
9
  import { property, state } from 'lit/decorators.js';
10
10
  import { html } from 'lit/static-html.js';
11
- import { optionsAmong } from '../../option/root/option-root.js';
11
+ import { optionsAmong } from '../../option/among.js';
12
12
  import '../../dialog/index.js';
13
13
  import '../../listbox/index.js';
14
14
  const says = (element, state) => {
@@ -145,6 +145,7 @@ let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpe
145
145
  }
146
146
  connectedCallback() {
147
147
  super.connectedCallback();
148
+ auditChildren(this, 'option', 'option', 'the palette lists no commands');
148
149
  this.addEventListener('helix-select', this.swallowOptionSelect);
149
150
  this.addEventListener('keydown', this.handleAlternate, { capture: true });
150
151
  this.addEventListener('keydown', this.handleBack);
@@ -1,6 +1,6 @@
1
1
  import { type DataRow, HelixElement } from '@fusion.dev/helix-core';
2
2
  import { type PropertyValues, type TemplateResult } from 'lit';
3
- import { type HelixHeadlessTableColumn } from '../../table/column/table-column.js';
3
+ import type { HelixHeadlessTableColumn } from '../../table/column/table-column.js';
4
4
  import '../../checkbox/index.js';
5
5
  import '../../pagination/index.js';
6
6
  declare const HelixHeadlessDataTable_base: (new (...args: any[]) => import("@fusion.dev/helix-core").CollectionElement) & typeof HelixElement;
@@ -131,6 +131,18 @@ declare const HelixHeadlessDataTable_base: (new (...args: any[]) => import("@fus
131
131
  * Written on `DataTable`, `columns` has no effect, so watch for it when you
132
132
  * move a table from `Table` to `DataTable`.
133
133
  *
134
+ * **The columns are an import of their own.** A column is the Table family's
135
+ * `TableColumn`, which the data table does not register, so a page that writes
136
+ * one imports the Table entry beside the data table:
137
+ *
138
+ * ```js
139
+ * import '@fusion.dev/helix/data-table'
140
+ * import '@fusion.dev/helix/table'
141
+ * ```
142
+ *
143
+ * In development, a data table holding a column nobody registered says so once
144
+ * the page has loaded, with the line to add.
145
+ *
134
146
  * @since 0.1.0
135
147
  * @status stable
136
148
  * @category data
@@ -5,14 +5,14 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
7
  var HelixHeadlessDataTable_1;
8
- import { anchorAt, arranged, auditServed, commaList, countLabel, customElement, cycle, deepActiveElement, deepContains, everyState, format, formatWords, gridMove, HelixActivateEvent, HelixElement, HelixExpandEvent, HelixFilterEvent, HelixFilteredEvent, HelixLoadMoreEvent, HelixNavigateEvent, HelixReorderEvent, HelixReorderedEvent, HelixSelectEvent, HelixSelectedEvent, HelixSortEvent, HelixSortedEvent, handedLocale, hostAria, inlineStep, inRowOrder, MixinCollection, markRegion, onAndroid, ordered, pageRows, parse, placeKey, ReorderController, rangeAdd, reportRuntime, requestChange, requestReorder, scrollingAncestor, scrollRoom, selectsAll, Typeahead, toggleEvery, treeArrow, typesCharacter, VirtualizeController, valueAt, window_, words, wordsLocale, writtenList, } from '@fusion.dev/helix-core';
8
+ import { anchorAt, arranged, auditChildren, auditServed, commaList, countLabel, customElement, cycle, deepActiveElement, deepContains, everyState, format, formatWords, gridMove, HelixActivateEvent, HelixElement, HelixExpandEvent, HelixFilterEvent, HelixFilteredEvent, HelixLoadMoreEvent, HelixNavigateEvent, HelixReorderEvent, HelixReorderedEvent, HelixSelectEvent, HelixSelectedEvent, HelixSortEvent, HelixSortedEvent, handedLocale, hostAria, inlineStep, inRowOrder, MixinCollection, markRegion, onAndroid, ordered, pageRows, parse, placeKey, ReorderController, rangeAdd, reportRuntime, requestChange, requestReorder, scrollingAncestor, scrollRoom, selectsAll, Typeahead, toggleEvery, treeArrow, typesCharacter, VirtualizeController, valueAt, whenHydrated, window_, words, wordsLocale, writtenList, } from '@fusion.dev/helix-core';
9
9
  import { caretDown, caretRight, dotsSixVertical } from '@fusion.dev/helix-icons';
10
10
  import { nothing } from 'lit';
11
11
  import { property, state } from 'lit/decorators.js';
12
12
  import { repeat } from 'lit/directives/repeat.js';
13
13
  import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
14
14
  import { html } from 'lit/static-html.js';
15
- import { edgeOf } from '../../table/column/table-column.js';
15
+ import { edgeOf } from '../../table/edge.js';
16
16
  import '../../checkbox/index.js';
17
17
  import '../../pagination/index.js';
18
18
  import { toggledOpen } from '../keys.js';
@@ -1021,19 +1021,21 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
1021
1021
  const shown = this.shownColumns;
1022
1022
  const android = onAndroid();
1023
1023
  for (const column of this.tableColumns) {
1024
- column.movable = this.reorderable && column.field !== '';
1025
- column.gridCell = true;
1026
- column.tabStop = shown.indexOf(column) + offset === stop;
1027
- column.sorted =
1028
- current && column.canSort && column.field === current.field
1029
- ? current.direction === 'asc'
1030
- ? 'ascending'
1031
- : 'descending'
1032
- : undefined;
1033
- column.sortWords =
1034
- android && column.sorted
1035
- ? this.sortSaid(column.textContent?.trim() || column.field, column.sorted)
1036
- : '';
1024
+ whenHydrated(column, () => {
1025
+ column.movable = this.reorderable && column.field !== '';
1026
+ column.gridCell = true;
1027
+ column.tabStop = shown.indexOf(column) + offset === stop;
1028
+ column.sorted =
1029
+ current && column.canSort && column.field === current.field
1030
+ ? current.direction === 'asc'
1031
+ ? 'ascending'
1032
+ : 'descending'
1033
+ : undefined;
1034
+ column.sortWords =
1035
+ android && column.sorted
1036
+ ? this.sortSaid(column.textContent?.trim() || column.field, column.sorted)
1037
+ : '';
1038
+ });
1037
1039
  }
1038
1040
  }
1039
1041
  renderCell(column, row, picked, place, stop, header, id) {
@@ -1276,6 +1278,7 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
1276
1278
  }
1277
1279
  connectedCallback() {
1278
1280
  super.connectedCallback();
1281
+ auditChildren(this, 'table-column', 'table', 'the table draws no columns');
1279
1282
  this.addEventListener('click', this.handleHeadClick);
1280
1283
  this.addEventListener('click', this.handleRowClick);
1281
1284
  this.addEventListener('pointerdown', this.notePress, true);
@@ -33,6 +33,8 @@ declare const HelixHeadlessDateInput_base: (new (...args: any[]) => import("@fus
33
33
  * digits or the arrow keys. Alt+Down opens the calendar from a segment. A
34
34
  * `range` is two rows with a dash between them, the start and the end, as
35
35
  * React Aria's DateRangePicker draws them, and `multiple` keeps the text.
36
+ * A range whose end is before its start reports `rangeUnderflow`, as Date
37
+ * Picker does, with `range-order-message`.
36
38
  *
37
39
  * Text that is not a date gives the form an empty value, as a native date
38
40
  * field does, and `value` keeps the text so it can be corrected.
@@ -359,6 +361,11 @@ export declare class HelixHeadlessDateInput extends HelixHeadlessDateInput_base
359
361
  * for the page's language, then the English.
360
362
  */
361
363
  rangeLengthMessage: string;
364
+ /**
365
+ * The message shown when a range ends before it starts. `{start}` is
366
+ * replaced with the start, written the way the field writes dates.
367
+ */
368
+ rangeOrderMessage: string;
362
369
  /**
363
370
  * The message shown when a date is after `max`. `{max}` is replaced with
364
371
  * `max`, written the way the field writes dates.
@@ -47,6 +47,7 @@ let HelixHeadlessDateInput = class HelixHeadlessDateInput extends MixinSegmented
47
47
  this.previousYearLabel = 'Previous year';
48
48
  this.previousYearsLabel = 'Earlier years';
49
49
  this.rangeLengthMessage = 'A range is two dates.';
50
+ this.rangeOrderMessage = 'Choose an end on or after {start}.';
50
51
  this.rangeOverflowMessage = 'Choose a date on or before {max}.';
51
52
  this.rangeUnderflowMessage = 'Choose a date on or after {min}.';
52
53
  this.removeLabel = 'Remove {name}';
@@ -263,6 +264,13 @@ let HelixHeadlessDateInput = class HelixHeadlessDateInput extends MixinSegmented
263
264
  if (this.mode === 'range' && held.length > 2) {
264
265
  return this.rangeLengthMessage;
265
266
  }
267
+ const [start, end] = held;
268
+ if (this.mode === 'range' && start !== undefined && end !== undefined && end < start) {
269
+ return [
270
+ 'rangeUnderflow',
271
+ formatWords(this, 'rangeOrderMessage', { start: this.written(start) }),
272
+ ];
273
+ }
266
274
  return '';
267
275
  }
268
276
  renderPicker() {
@@ -547,6 +555,10 @@ __decorate([
547
555
  words,
548
556
  property({ attribute: 'range-length-message' })
549
557
  ], HelixHeadlessDateInput.prototype, "rangeLengthMessage", void 0);
558
+ __decorate([
559
+ words,
560
+ property({ attribute: 'range-order-message' })
561
+ ], HelixHeadlessDateInput.prototype, "rangeOrderMessage", void 0);
550
562
  __decorate([
551
563
  words,
552
564
  property({ attribute: 'range-overflow-message' })
@@ -48,7 +48,8 @@ declare const HelixHeadlessDatePicker_base: (new (...args: any[]) => import("@fu
48
48
  * when Previous or Next is pressed. Neither moves past `min` or `max`, and the
49
49
  * keys stop at them too. A value outside the bounds, on a barred day, or off
50
50
  * `step` reports `rangeUnderflow`, `rangeOverflow`, `badInput` or
51
- * `stepMismatch`, so a form does not submit it.
51
+ * `stepMismatch`, so a form does not submit it. A range set from a script
52
+ * whose end is before its start reports `rangeUnderflow` as well.
52
53
  *
53
54
  * @since 0.1.0
54
55
  * @status stable
@@ -278,6 +279,11 @@ export declare class HelixHeadlessDatePicker extends HelixHeadlessDatePicker_bas
278
279
  * Accessible name for the control that moves back a page, in the years.
279
280
  */
280
281
  previousYearsLabel: string;
282
+ /**
283
+ * The validation message for a range that ends before it starts. `{start}`
284
+ * is replaced with the start, written as a date in `locale`.
285
+ */
286
+ rangeOrderMessage: string;
281
287
  /**
282
288
  * The validation message for a date after `max`. `{max}` is replaced with
283
289
  * `max`, written as a date in `locale`.
@@ -44,6 +44,7 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinPickerF
44
44
  this.previousLabel = 'Previous month';
45
45
  this.previousYearLabel = 'Previous year';
46
46
  this.previousYearsLabel = 'Earlier years';
47
+ this.rangeOrderMessage = 'Choose an end on or after {start}.';
47
48
  this.rangeOverflowMessage = 'Choose a date on or before {max}.';
48
49
  this.focused = '';
49
50
  this.choosing = false;
@@ -504,6 +505,13 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinPickerF
504
505
  return [{ [fault[0]]: true }, fault[1]];
505
506
  }
506
507
  }
508
+ const [start, end] = held;
509
+ if (this.mode === 'range' && start !== undefined && end !== undefined && end < start) {
510
+ return [
511
+ { rangeUnderflow: true },
512
+ formatWords(this, 'rangeOrderMessage', { start: this.spoken(start) }),
513
+ ];
514
+ }
507
515
  return undefined;
508
516
  }
509
517
  get failure() {
@@ -855,6 +863,10 @@ __decorate([
855
863
  words,
856
864
  property({ attribute: 'previous-years-label' })
857
865
  ], HelixHeadlessDatePicker.prototype, "previousYearsLabel", void 0);
866
+ __decorate([
867
+ words,
868
+ property({ attribute: 'range-order-message' })
869
+ ], HelixHeadlessDatePicker.prototype, "rangeOrderMessage", void 0);
858
870
  __decorate([
859
871
  words,
860
872
  property({ attribute: 'range-overflow-message' })
@@ -207,6 +207,12 @@ export declare class HelixHeadlessDrilldownItem extends HelixElement {
207
207
  * an icon of your own replaces it by being slotted.
208
208
  */
209
209
  protected renderMark(): TemplateResult;
210
+ /**
211
+ * Presses the row, as `click()` presses a native button: passed to the
212
+ * control inside, which is what answers a click, so a script's `click()`
213
+ * chooses the row, or follows its link, once.
214
+ */
215
+ click(): void;
210
216
  protected render(): TemplateResult | typeof nothing;
211
217
  }
212
218
  declare global {
@@ -104,6 +104,15 @@ let HelixHeadlessDrilldownItem = class HelixHeadlessDrilldownItem extends HelixE
104
104
  </svg></slot
105
105
  >`;
106
106
  }
107
+ click() {
108
+ const control = this.partNamed('item');
109
+ if (control) {
110
+ control.click();
111
+ }
112
+ else {
113
+ super.click();
114
+ }
115
+ }
107
116
  render() {
108
117
  if (this.showingPage || this.onPath) {
109
118
  return html `<slot name="page" @slotchange=${this.readPage}></slot>`;
@@ -158,8 +158,17 @@ let HelixHeadlessDrilldown = class HelixHeadlessDrilldown extends MixinCollectio
158
158
  willUpdate(changed) {
159
159
  super.willUpdate(changed);
160
160
  if (changed.has('page') && (this.drilldown.page?.value ?? '') !== this.page) {
161
+ const leaving = this.drilldown.page;
162
+ const focused = this.contains(document.activeElement);
161
163
  this.showPage(this.page);
162
164
  this.page = this.drilldown.page?.value ?? '';
165
+ if (focused) {
166
+ this.updateComplete.then(() => {
167
+ const items = this.items;
168
+ const to = leaving && items.includes(leaving) ? leaving : items[0];
169
+ to?.focusItem();
170
+ });
171
+ }
163
172
  }
164
173
  hostAria(this.internals, {
165
174
  label: this.navigation ? this.label : '',
@@ -137,7 +137,13 @@ registerElementEvents({
137
137
  'helix-graph-signal',
138
138
  'helix-graph-zoom',
139
139
  ],
140
- 'helix-headless-heatmap': ['change', 'helix-before-change', 'helix-resize'],
140
+ 'helix-headless-heatmap': [
141
+ 'change',
142
+ 'helix-before-change',
143
+ 'helix-reading-change',
144
+ 'helix-reading-press',
145
+ 'helix-resize',
146
+ ],
141
147
  'helix-headless-icon': ['error', 'load'],
142
148
  'helix-headless-icon-button': ['helix-press'],
143
149
  'helix-headless-image': ['error', 'load'],
@@ -128,6 +128,19 @@ declare const HelixHeadlessFilterInput_base: (new (...args: any[]) => import("@f
128
128
  * filter goes. Set `chips` and the field adds, updates, and removes those
129
129
  * chips itself, from every `helix-facet` you do not cancel.
130
130
  *
131
+ * **The suggestions you write are an import of their own.** A suggestion is an
132
+ * option, the Option family's, which the field does not register, so a page
133
+ * that slots one imports it beside the field:
134
+ *
135
+ * ```js
136
+ * import '@fusion.dev/helix/filter-input'
137
+ * import '@fusion.dev/helix/option'
138
+ * ```
139
+ *
140
+ * In development, a field holding an option nobody registered says so once the
141
+ * page has loaded, with the line to add. The options `source` gives are drawn
142
+ * as this Option too, and need the same import.
143
+ *
131
144
  * @since 0.1.0
132
145
  * @status stable
133
146
  * @category forms
@@ -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 { AriaForwardController, countLabel, customElement, DelayController, drawIcon, drawStatus, formatWords, HelixFacetEvent, HelixFormElement, holdCaret, MixinControllerOpen, MixinControllerValue, MixinField, MixinTokens, PopoverController, reportRuntime, restoreValue, StatusController, scrollItemIntoView, words, wordsLocale, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, auditChildren, countLabel, customElement, DelayController, drawIcon, drawStatus, formatWords, HelixFacetEvent, HelixFormElement, holdCaret, MixinControllerOpen, MixinControllerValue, MixinField, MixinTokens, PopoverController, reportRuntime, restoreValue, StatusController, scrollItemIntoView, words, wordsLocale, } from '@fusion.dev/helix-core';
8
8
  import { x } from '@fusion.dev/helix-icons';
9
9
  import { html, nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
@@ -552,6 +552,7 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
552
552
  }
553
553
  connectedCallback() {
554
554
  super.connectedCallback();
555
+ auditChildren(this, 'option', 'option', 'the field suggests nothing');
555
556
  this.addEventListener('helix-dismiss', this.handleDismiss, { capture: true });
556
557
  this.addEventListener(HelixFacetEvent.eventName, this.handleFacet);
557
558
  this.addEventListener('helix-select', this.handleSourcedSelect);
@@ -2,7 +2,7 @@ import { AriaForwardController, HelixElement } from '@fusion.dev/helix-core';
2
2
  import { nothing, type PropertyValues, type TemplateResult } from 'lit';
3
3
  import '../../tooltip/index.js';
4
4
  import { type Reading } from './buckets.js';
5
- declare const HelixHeadlessHeatmap_base: typeof HelixElement & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinChartSourceInterface);
5
+ declare const HelixHeadlessHeatmap_base: typeof HelixElement & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinChartSourceInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinChartStatusInterface);
6
6
  /**
7
7
  * @summary A strip of cells, each colored by its reading, for uptime, contributions, or activity.
8
8
  *
@@ -50,6 +50,25 @@ declare const HelixHeadlessHeatmap_base: typeof HelixElement & (new (...args: an
50
50
  * `helix-before-change` and tells with `change`, and the `selection` part says
51
51
  * how many are selected, in `selection-label`.
52
52
  *
53
+ * **Under `selection-mode` the strip is a grid**, so the selection is state a
54
+ * reader is told rather than words alone: each column of the strip is a `row`,
55
+ * a week with `rows="7"`, and each cell a `gridcell` named by its row in the
56
+ * table and carrying `aria-selected`. The strip says
57
+ * `aria-multiselectable` under `multiple` and points at the cell the keys rest
58
+ * on with `aria-activedescendant`, so a screen reader reads that cell, and the
59
+ * `live` part stays quiet. Without `selection-mode` nothing is selectable, and
60
+ * the strip is an application with the readout, as every chart's plot is.
61
+ *
62
+ * **Readings are sent as every chart sends them.** `helix-reading-change`
63
+ * says each move of the reading, by pointer, tap or key, and
64
+ * `helix-reading-press` a reading clicked, tapped, or pressed with Enter or
65
+ * Space, each with the row of the table in `index`.
66
+ *
67
+ * **Nothing to draw.** A table with no readings shows `empty-text`, "No
68
+ * readings", under the strip, and `loading` shows `loading-text` there until
69
+ * readings arrive. The `empty` and `loading` slots take markup in place of
70
+ * either, as on every chart.
71
+ *
53
72
  * **Keyboard.** The strip is one tab stop once it has cells.
54
73
  *
55
74
  * | Key | What it does |
@@ -59,7 +78,7 @@ declare const HelixHeadlessHeatmap_base: typeof HelixElement & (new (...args: an
59
78
  * | Right, Left | Moves a column on or back, a week with `rows="7"`. On a right-to-left page, Left moves on. |
60
79
  * | Home, End | Moves to the first or the last cell. |
61
80
  * | Page Down, Page Up | Moves a month on or back where the rows carry dates, and four columns otherwise. |
62
- * | Enter, Space | Selects the cell, or lets it go, under `selection-mode`. |
81
+ * | Enter, Space | Presses the cell, which sends `helix-reading-press`, and selects it or lets it go under `selection-mode`. |
63
82
  * | Shift with an arrow, Home, End, Page Up or Page Down | Selects every cell from the last one pressed to where the key went, under `selection-mode="multiple"`. |
64
83
  * | Escape | Puts the cell's tooltip away. |
65
84
  *
@@ -69,23 +88,30 @@ declare const HelixHeadlessHeatmap_base: typeof HelixElement & (new (...args: an
69
88
  * @aliases Contribution graph, Uptime strip, Calendar heatmap, Activity graph
70
89
  * @documentation https://fusion.dev/helix/components/heatmap
71
90
  *
91
+ * @slot empty - Content shown under the strip when the table has no readings, such as a link to add the first. Falls back to `empty-text`.
72
92
  * @slot end - Below the strip, at the trailing edge: usually the newest date.
93
+ * @slot loading - Content shown under the strip while `loading` is set and there are no readings yet. Falls back to `loading-text`.
73
94
  * @slot start - Below the strip, at the leading edge: usually the oldest date.
74
95
  * @slot summary - Below the strip, in the middle: a summary of the range shown.
75
96
  *
76
97
  * @event {Event} change - Fired once a press or a key has changed which cells are selected.
77
98
  * @event {HelixBeforeChangeEvent} helix-before-change - Fired before a press or a key changes which cells are selected, with the `value` it would leave. Cancelable.
99
+ * @event {HelixReadingChangeEvent} helix-reading-change - The reading shown moved, or was put away: `index` is the row, or `-1` for none, with its `label`, the `series` the keys rest on, and the `pointerType` that moved it.
100
+ * @event {HelixReadingPressEvent} helix-reading-press - A reading was clicked, tapped, or pressed with Enter or Space, with the same detail as `helix-reading-change`.
78
101
  * @event {HelixResizeEvent} helix-resize - Fired when the number of cells drawn changes, with the new number.
79
102
  *
80
103
  * @csspart cell - One reading.
81
104
  * @csspart cell-active - The cell the pointer or the keys rest on. Set alongside `cell`.
82
105
  * @csspart cell-selected - A cell that is selected, under `selection-mode`. Set alongside `cell`.
83
- * @csspart empty - Shown when the table has no readings. It stays hidden until the table can be read: on the server and during hydration, there is no document to resolve `for` in.
106
+ * @csspart empty - The words under the strip when the table has no readings: the `empty` slot, falling back to `empty-text`. Not drawn while the table cannot be read yet, on the server and during hydration, there being no document to resolve `for` in.
84
107
  * @csspart end - The trailing slot's box.
85
108
  * @csspart footer - The three-slot row under the strip.
86
109
  * @csspart live - The readout: a polite live region that says the cell the keys moved to. Hide it visually.
110
+ * @csspart loading - The words under the strip while `loading` is set and there are no readings: the `loading` slot, falling back to `loading-text`.
87
111
  * @csspart mark - The invisible point the tooltip is anchored to.
88
112
  * @csspart overview - The busiest reading and the total of the readings drawn, written as a sentence for screen readers, which describes the strip. Rendered once there are cells. Hide it visually.
113
+ * @csspart plot - The same element as `strip`, under the name every chart gives the drawing that takes focus.
114
+ * @csspart row - A column of cells, which a grid reads as a row, under `selection-mode`. It draws no box.
89
115
  * @csspart selection - A polite region that says how many cells are selected after a change, rendered from the first change. Hide it visually.
90
116
  * @csspart start - The leading slot's box.
91
117
  * @csspart strip - The grid of cells, which takes focus to be read by key. It is named by the host's `aria-labelledby` or `aria-label`, then by `label`, then by the caption of the table.
@@ -125,6 +151,7 @@ declare const HelixHeadlessHeatmap_base: typeof HelixElement & (new (...args: an
125
151
  *
126
152
  * @cssstate active - While a cell is shown, under the pointer or the keys, with its tooltip open.
127
153
  * @cssstate empty - While the `empty` part stands in place of the cells.
154
+ * @cssstate loading - While the `loading` part stands in place of the cells.
128
155
  */
129
156
  export declare class HelixHeadlessHeatmap extends HelixHeadlessHeatmap_base {
130
157
  /**
@@ -137,13 +164,6 @@ export declare class HelixHeadlessHeatmap extends HelixHeadlessHeatmap_base {
137
164
  * `overview-label`.
138
165
  */
139
166
  busiestLabel: string;
140
- /**
141
- * Shown when the table has no readings in it.
142
- *
143
- * Never shown while the table cannot be read at all, which is a server render
144
- * and the hydrating one after it: see `sourceKnown`.
145
- */
146
- emptyText: string;
147
167
  /**
148
168
  * Space between cells, in CSS pixels. It is a number for the same reason
149
169
  * `min-cell` is.
@@ -187,9 +207,11 @@ export declare class HelixHeadlessHeatmap extends HelixHeadlessHeatmap_base {
187
207
  */
188
208
  rows: number;
189
209
  /**
190
- * What the readout adds for a selected cell, under `selection-mode`, after
191
- * where the cell stands: "Day 1: 0 (1 of 14, selected)". Empty adds
192
- * nothing.
210
+ * What the readout added for a selected cell, under `selection-mode`.
211
+ *
212
+ * @deprecated The strip is a grid under `selection-mode`, and each cell says
213
+ * whether it is selected with `aria-selected`, in the reader's own words, so
214
+ * the readout says nothing there and this is not read.
193
215
  */
194
216
  selectedLabel: string;
195
217
  /**
@@ -334,28 +356,6 @@ export declare class HelixHeadlessHeatmap extends HelixHeadlessHeatmap_base {
334
356
  * the one under the pointer.
335
357
  */
336
358
  private get points();
337
- /**
338
- * Whether this render can tell a table with no readings from one it has not
339
- * resolved yet.
340
- *
341
- * **Unknown is not empty, which is what an empty state must never say.**
342
- * `for` names an id and resolving an id wants a document, so a server has
343
- * none and a hydrating render must not look either. Without this the server
344
- * drew "No readings" over a table holding ninety days of them, a crawler read
345
- * that sentence, and the strip flipped to a full one on hydration: the fault
346
- * `MixinCollection` names for a slotted collection, arriving through the one
347
- * channel that mixin cannot see.
348
- *
349
- * The same shape as `childrenKnown`, asked of a tree that can answer about
350
- * ids rather than of one that holds children. An id that resolves to nothing
351
- * in a browser is still known, and still empty: a `for` naming no table
352
- * should say so rather than sit blank, which is why this asks
353
- * `searchableById` and not `elementById`.
354
- *
355
- * `readingsKnown` asks it for every chart, and for `data` too, which a
356
- * server does not draw.
357
- */
358
- private get sourceKnown();
359
359
  /**
360
360
  * `rows` as a usable count, which is not always what the attribute says.
361
361
  *
@@ -432,6 +432,23 @@ export declare class HelixHeadlessHeatmap extends HelixHeadlessHeatmap_base {
432
432
  private markEmpty;
433
433
  protected updated(changed: PropertyValues): void;
434
434
  protected render(): TemplateResult;
435
+ /**
436
+ * The cells as a grid's rows, one for each column of the strip, so a week
437
+ * is a row with `rows="7"` and a status strip is one row.
438
+ *
439
+ * **A column, because that is the order the cells are in.** The strip fills
440
+ * down the column and then across, so a row wrapping a column keeps every
441
+ * cell where it was, and `display: contents` keeps the wrapper out of the
442
+ * strip's grid, drawing no box, which is a mechanism and not a look. A row
443
+ * across the strip would have taken cells out of order and moved them.
444
+ */
445
+ private renderRows;
446
+ /**
447
+ * One cell under `selection-mode`: a `gridcell` named by its row in the
448
+ * table, its reading and its place, and saying whether it is selected, which
449
+ * a reader hears on reaching it as the strip's active descendant.
450
+ */
451
+ private renderGridCell;
435
452
  /**
436
453
  * What the strip shows, in a sentence: the busiest reading drawn, as the
437
454
  * table writes it, and the total of them all, in `locale`. The strip is
@@ -461,13 +478,7 @@ export declare class HelixHeadlessHeatmap extends HelixHeadlessHeatmap_base {
461
478
  * that is usually wider, so a percentage of the box lands between cells. The
462
479
  * pitch is `cell + gap` and both are measured.
463
480
  */
464
- protected renderTooltip(): TemplateResult | typeof nothing;
465
- /**
466
- * The readout's cursor: the pointer, with the selected word after where a
467
- * selected cell stands, so a reader moving through the strip hears which
468
- * cells are in the selection and not only how many.
469
- */
470
- private reading;
481
+ protected renderTooltip(grid?: boolean): TemplateResult | typeof nothing;
471
482
  /**
472
483
  * Which drawn cell the pointer is over, or -1.
473
484
  *
@@ -476,6 +487,7 @@ export declare class HelixHeadlessHeatmap extends HelixHeadlessHeatmap_base {
476
487
  * `mouseover` it is delivered for a pen.
477
488
  */
478
489
  private readonly onPointerOver;
490
+ private readonly onPointerUp;
479
491
  private readonly onPointerLeave;
480
492
  }
481
493
  declare global {