@fusion.dev/helix-headless 0.176.1 → 0.177.1

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 (51) hide show
  1. package/custom-elements.json +1383 -766
  2. package/dist/area-chart/root/area-chart-root.d.ts +18 -1
  3. package/dist/area-chart/root/area-chart-root.js +2 -2
  4. package/dist/avatar/root/avatar-root.d.ts +1 -1
  5. package/dist/avatar/root/avatar-root.js +1 -1
  6. package/dist/bar-chart/root/bar-chart-root.d.ts +19 -1
  7. package/dist/bar-chart/root/bar-chart-root.js +2 -2
  8. package/dist/button/root/button-root.js +1 -0
  9. package/dist/canvas/node/canvas-node.js +1 -1
  10. package/dist/canvas/root/canvas-root.d.ts +5 -1
  11. package/dist/canvas/root/canvas-root.js +9 -1
  12. package/dist/chat/composer/chat-composer.d.ts +1 -1
  13. package/dist/chat/composer/chat-composer.js +1 -1
  14. package/dist/color-picker/root/color-picker-root.d.ts +10 -0
  15. package/dist/color-picker/root/color-picker-root.js +20 -13
  16. package/dist/combobox/root/combobox-root.d.ts +13 -0
  17. package/dist/combobox/root/combobox-root.js +16 -4
  18. package/dist/donut-chart/root/donut-chart-root.d.ts +37 -2
  19. package/dist/donut-chart/root/donut-chart-root.js +22 -8
  20. package/dist/element-events.generated.js +2 -0
  21. package/dist/file-input/root/file-input-root.d.ts +3 -2
  22. package/dist/file-input/root/file-input-root.js +5 -2
  23. package/dist/graph/root/graph-root.d.ts +28 -3
  24. package/dist/graph/root/graph-root.js +38 -3
  25. package/dist/heatmap/root/heatmap-root.d.ts +28 -5
  26. package/dist/heatmap/root/heatmap-root.js +7 -2
  27. package/dist/line-chart/root/line-chart-root.d.ts +18 -1
  28. package/dist/line-chart/root/line-chart-root.js +2 -2
  29. package/dist/loader/root/loader-root.d.ts +15 -0
  30. package/dist/loader/root/loader-root.js +28 -6
  31. package/dist/pin-input/root/pin-input-root.js +1 -0
  32. package/dist/rating/root/rating-root.d.ts +2 -1
  33. package/dist/rating/root/rating-root.js +8 -2
  34. package/dist/scatter-chart/root/scatter-chart-root.d.ts +29 -6
  35. package/dist/scatter-chart/root/scatter-chart-root.js +18 -3
  36. package/dist/select/root/select-root.d.ts +4 -1
  37. package/dist/select/root/select-root.js +1 -0
  38. package/dist/sparkline/root/sparkline-root.d.ts +29 -4
  39. package/dist/sparkline/root/sparkline-root.js +8 -1
  40. package/dist/tag/group/tag-group.d.ts +4 -3
  41. package/dist/tag/group/tag-group.js +6 -5
  42. package/dist/tag/root/tag-root.js +2 -1
  43. package/dist/time-input/root/time-input-root.d.ts +7 -1
  44. package/dist/time-input/root/time-input-root.js +6 -3
  45. package/dist/tree/item/tree-item.d.ts +4 -4
  46. package/dist/tree/item/tree-item.js +5 -1
  47. package/lint.json +1 -1
  48. package/package.json +5 -5
  49. package/types/jsx.d.ts +688 -382
  50. package/types/vue.d.ts +101 -22
  51. package/types/words.d.ts +26 -2
@@ -2,6 +2,7 @@ import { AriaForwardController, type DataOptionItem, HelixFormElement, PopoverCo
2
2
  import { type PropertyValues, type TemplateResult } from 'lit';
3
3
  import { type HelixHeadlessOption } from '../../option/root/option-root.js';
4
4
  import '../../listbox/index.js';
5
+ import '../../option/index.js';
5
6
  declare const HelixHeadlessSelect_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerOpenInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerValueInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinFieldInterface) & typeof HelixFormElement;
6
7
  /**
7
8
  * @summary A control that opens a list of options to choose from.
@@ -13,7 +14,9 @@ declare const HelixHeadlessSelect_base: (new (...args: any[]) => import("@fusion
13
14
  * place for focus to return to when the list closes.
14
15
  *
15
16
  * Your options stay in your light DOM and are slotted through into the listbox,
16
- * so the listbox looks through slots to find them. For a list too long to draw
17
+ * so the listbox looks through slots to find them. Write them as `Option`,
18
+ * group them in `Option.Group`, and divide them with `Option.Separator`.
19
+ * Importing Select registers all three in the same tier. For a list too long to draw
17
20
  * whole, set `items` instead: the list draws only the options in sight, as
18
21
  * Listbox's `items` does, and the trigger, its keys and the form read the
19
22
  * whole set.
@@ -12,6 +12,7 @@ import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
12
12
  import { html } from 'lit/static-html.js';
13
13
  import { optionsAmong } from '../../option/root/option-root.js';
14
14
  import '../../listbox/index.js';
15
+ import '../../option/index.js';
15
16
  import { over } from '../align.js';
16
17
  let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(MixinControllerValue(MixinField(HelixFormElement))) {
17
18
  constructor() {
@@ -24,6 +24,10 @@ import { nothing, type PropertyValues, type TemplateResult } from 'lit';
24
24
  * the lowest, in `overview-label`: "From 12 to 20, highest 30, lowest 8". An
25
25
  * `aria-label` on the element names it as `label` does.
26
26
  *
27
+ * `helix-reading-change` says each move of the reading, by pointer, tap or
28
+ * key, and `helix-reading-press` says a reading was clicked, tapped, or pressed
29
+ * with Enter or Space, as on every chart.
30
+ *
27
31
  * A pointer resting on the drawing shows the reading under it: the `cursor`
28
32
  * part marks it and the `readout` part writes it. `marks` adds a mark on the
29
33
  * highest and the lowest reading, and `range` draws a band between two values,
@@ -38,12 +42,26 @@ import { nothing, type PropertyValues, type TemplateResult } from 'lit';
38
42
  * the component computes and CSS cannot, so a stylesheet can draw the sparkline
39
43
  * without measuring anything.
40
44
  *
45
+ * **Keyboard.** A sparkline with a `label` is one tab stop.
46
+ *
47
+ * | Key | What it does |
48
+ * |---|---|
49
+ * | Tab | Moves focus to the drawing and shows the first reading, where there is a `label`. |
50
+ * | Any arrow | Right and Down move to the next reading, Left and Up to the previous. On a right-to-left page, Left moves to the next. |
51
+ * | Home, End | Moves to the first or the last reading. |
52
+ * | Page Down, Page Up | Moves a tenth of the readings on or back. |
53
+ * | Enter, Space | Presses the reading shown, which sends `helix-reading-press`. |
54
+ * | Escape | Puts the reading and its tooltip away. |
55
+ *
41
56
  * @since 0.1.0
42
57
  * @status stable
43
58
  * @category charts
44
59
  * @aliases Micro Chart, Trend Line, Inline Chart
45
60
  * @documentation https://fusion.dev/helix/components/sparkline
46
61
  *
62
+ * @event {HelixReadingChangeEvent} helix-reading-change - The reading shown moved, or was put away: `index` is the reading, or `-1` for none, with its row's `label` from a table and the `pointerType` that moved it.
63
+ * @event {HelixReadingPressEvent} helix-reading-press - A reading was clicked, tapped, or pressed with Enter or Space, with the same detail as `helix-reading-change`.
64
+ *
47
65
  * @csspart area - The region between the line and the bottom of its range.
48
66
  * @csspart bar - One column, drawn when `plot="bar"`.
49
67
  * @csspart cursor - The mark on the reading a pointer or a key rests on, rendered only while there is one. Placed with `inset-inline-start` and `inset-block-start`.
@@ -201,17 +219,24 @@ export declare class HelixHeadlessSparkline extends HelixElement {
201
219
  values: string | readonly number[];
202
220
  protected readonly source: SourceController;
203
221
  /**
204
- * Finds the reading a pointer or a key rests on, as it does for every
205
- * chart, so a sparkline is read the way they are.
222
+ * @internal Finds the reading a pointer or a key rests on, as it does for
223
+ * every chart, so a sparkline is read the way they are.
206
224
  */
207
225
  readonly pointer: ChartPointerController;
208
226
  /**
209
- * Hears an `aria-label` written on the sparkline, which no property mirrors,
227
+ * @internal Hears an `aria-label` written on the sparkline, which no property mirrors,
210
228
  * because it names the drawing as `label` does, as on every chart.
211
229
  */
212
230
  static get observedAttributes(): string[];
213
- /** Called by the browser when an observed attribute changes; a new `aria-label` renames the drawing. */
231
+ /** @internal Called by the browser when an observed attribute changes; a new `aria-label` renames the drawing. */
214
232
  attributeChangedCallback(name: string, old: string | null, value: string | null): void;
233
+ /**
234
+ * Sends the reading's moves and presses as `helix-reading-change` and
235
+ * `helix-reading-press`, named by the table's row where there is one. Here
236
+ * and not in a constructor, where the manifest's analyzer reads the
237
+ * assignment as a member of this class.
238
+ */
239
+ connectedCallback(): void;
215
240
  /** The drawing's name: an `aria-label` written on the sparkline, then `label`. */
216
241
  get named(): string;
217
242
  /** The two values of `range`, lower first, or none. */
@@ -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 { CHART_PLOT, ChartPointerController, customElement, extent, extremesOf, formatNumbers, formatWords, HelixElement, linear, linePath, PLOT_WIDTH, SourceController, unbroken, words, wordsLocale, writtenList, writtenNumbers, } from '@fusion.dev/helix-core';
7
+ import { CHART_PLOT, ChartPointerController, customElement, extent, extremesOf, formatNumbers, formatWords, HelixElement, HelixReadingChangeEvent, HelixReadingPressEvent, linear, linePath, PLOT_WIDTH, SourceController, unbroken, words, wordsLocale, writtenList, writtenNumbers, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing, svg } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  import { areaPath, overviewWords, readingWords, slotsOf } from '../readings.js';
@@ -33,6 +33,13 @@ let HelixHeadlessSparkline = class HelixHeadlessSparkline extends HelixElement {
33
33
  this.requestUpdate();
34
34
  }
35
35
  }
36
+ connectedCallback() {
37
+ super.connectedCallback();
38
+ this.pointer.onReading = (heard, detail) => {
39
+ const told = { ...detail, label: this.source.rows[detail.index]?.label ?? '' };
40
+ this.dispatchEvent(heard === 'press' ? new HelixReadingPressEvent(told) : new HelixReadingChangeEvent(told));
41
+ };
42
+ }
36
43
  get named() {
37
44
  return this.getAttribute?.('aria-label') || this.label;
38
45
  }
@@ -99,8 +99,9 @@ export declare class HelixHeadlessTagGroup extends HelixElement {
99
99
  */
100
100
  selectionMode?: TagSelectionMode;
101
101
  /**
102
- * The selected tags' values, separated by commas, since a value may hold a
103
- * space.
102
+ * The selected tags' values, separated by commas with no space, as Toggle
103
+ * Group and Table write theirs, since a value may hold a space. A value
104
+ * written with a space after each comma is read the same way.
104
105
  *
105
106
  * Set it to start with tags selected. A tag's value is its `value`, or its
106
107
  * words where it has none.
@@ -159,7 +160,7 @@ export declare class HelixHeadlessTagGroup extends HelixElement {
159
160
  /** Whether a press or a move selects the tag alone. */
160
161
  private get replaces();
161
162
  /**
162
- * Asks for `value` to become `next`, where it changes and where it leaves a
163
+ * Asks for `value` to become `asked`, where it changes and where it leaves a
163
164
  * tag selected under `disallow-empty-selection`.
164
165
  */
165
166
  private ask;
@@ -108,7 +108,7 @@ let HelixHeadlessTagGroup = class HelixHeadlessTagGroup extends HelixElement {
108
108
  const left = this.tags.map((one) => this.valueFor(one));
109
109
  const pruned = prunedValue(this.written, this.valueFor(tag), left);
110
110
  if (pruned !== this.written) {
111
- this.value = pruned;
111
+ this.value = commaList(pruned).join(',');
112
112
  this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
113
113
  }
114
114
  const active = document.activeElement;
@@ -150,7 +150,7 @@ let HelixHeadlessTagGroup = class HelixHeadlessTagGroup extends HelixElement {
150
150
  : this.value
151
151
  .map((one) => String(one).trim())
152
152
  .filter((one) => one !== '')
153
- .join(', ');
153
+ .join(',');
154
154
  }
155
155
  get tags() {
156
156
  return [...(this.children ?? [])].filter((child) => child instanceof HelixHeadlessTag);
@@ -212,8 +212,9 @@ let HelixHeadlessTagGroup = class HelixHeadlessTagGroup extends HelixElement {
212
212
  get replaces() {
213
213
  return this.selectionMode !== undefined && this.selectionBehavior === 'replace';
214
214
  }
215
- ask(next) {
216
- if (next === this.written) {
215
+ ask(asked) {
216
+ const next = commaList(asked).join(',');
217
+ if (next === commaList(this.written).join(',')) {
217
218
  return;
218
219
  }
219
220
  if (this.disallowEmptySelection && commaList(next).length === 0) {
@@ -239,7 +240,7 @@ let HelixHeadlessTagGroup = class HelixHeadlessTagGroup extends HelixElement {
239
240
  this.choose(this.tags.filter((tag) => chosen.includes(tag)));
240
241
  }
241
242
  choose(tags) {
242
- this.ask([...new Set(tags.map((tag) => this.valueFor(tag)))].join(', '));
243
+ this.ask([...new Set(tags.map((tag) => this.valueFor(tag)))].join(','));
243
244
  }
244
245
  connectedCallback() {
245
246
  super.connectedCallback();
@@ -70,12 +70,13 @@ let HelixHeadlessTag = class HelixHeadlessTag extends MixinDismissible(HelixElem
70
70
  }
71
71
  render() {
72
72
  const content = html `<slot name="start" part="start"></slot>${this.renderLabel()}${this.dismissible ? this.renderDismiss() : nothing}`;
73
+ const stop = this.focusable ? '0' : this.disabled ? nothing : '-1';
73
74
  return this.grouped
74
75
  ? html `<span
75
76
  aria-label=${this.words || nothing}
76
77
  part="cell"
77
78
  role="gridcell"
78
- tabindex=${this.focusable ? '0' : '-1'}
79
+ tabindex=${stop}
79
80
  @focus=${this.handleCellFocus}
80
81
  @blur=${this.handleCellFocus}
81
82
  >${content}</span
@@ -277,7 +277,13 @@ export declare class HelixHeadlessTimeInput extends HelixHeadlessTimeInput_base
277
277
  unavailableMessage: string;
278
278
  /**
279
279
  * The message for text that is no time, where `bad-input-message` is not
280
- * set. `{mode}` is `single`, `range`, or `multiple`, and `{shape}` is `HH:mm`, or `HH:mm:ss` with `seconds`.
280
+ * set. `{mode}` is `single`, `range`, or `multiple`, `{example}` is a time
281
+ * written as the field writes one, `2:30 PM` in `en-US` and `14:30` in
282
+ * `de-DE`, and `{shape}` is `HH:mm`, or `HH:mm:ss` with `seconds`.
283
+ *
284
+ * **The default names a time the field reads back.** It said "Enter a time
285
+ * as HH:mm." to a reader whose field showed `2:30 PM`, and a pattern token
286
+ * is words for a developer.
281
287
  */
282
288
  unreadableMessage: string;
283
289
  /** Every time in the value, which may hold more than one. */
@@ -36,7 +36,7 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinSegmented
36
36
  this.timeStyle = 'local';
37
37
  this.triggerLabel = 'Choose a time';
38
38
  this.unavailableMessage = 'Choose a time that is available.';
39
- this.unreadableMessage = '{mode, select, single {Enter a time as {shape}.} other {Enter times as {shape}, separated by commas.}}';
39
+ this.unreadableMessage = '{mode, select, single {Enter a time such as {example}.} other {Enter times such as {example}, separated by commas.}}';
40
40
  this.draft = '';
41
41
  this.handleTextCommit = (event) => {
42
42
  if (this.mode !== 'multiple') {
@@ -182,10 +182,13 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinSegmented
182
182
  }
183
183
  validate() {
184
184
  const held = this.held;
185
- const shape = this.seconds ? 'HH:mm:ss' : 'HH:mm';
186
185
  if (held.length > 0 && !held.every((one) => this.isTime(one))) {
187
186
  return (this.badInputMessage ||
188
- formatWords(this, 'unreadableMessage', { mode: this.mode ?? 'single', shape }));
187
+ formatWords(this, 'unreadableMessage', {
188
+ example: this.written(this.seconds ? '14:30:15' : '14:30'),
189
+ mode: this.mode ?? 'single',
190
+ shape: this.seconds ? 'HH:mm:ss' : 'HH:mm',
191
+ }));
189
192
  }
190
193
  const rules = { disableTimes: this.disableTimes, max: this.max, min: this.min };
191
194
  const faults = held.map((one) => timeFault(one, rules));
@@ -14,9 +14,9 @@ declare const HelixHeadlessTreeItem_base: (new (...args: any[]) => import("@fusi
14
14
  * Each item is an element because it holds state: whether it is open, selected,
15
15
  * and focused.
16
16
  *
17
- * An item is a branch when something is slotted into `children`. This is
18
- * detected on the client, so server-rendered markup shows a leaf and the twisty
19
- * appears on hydration.
17
+ * An item is a branch when an item is slotted into `children`, with
18
+ * `slot="children"` on each nested item. This is detected on the client, so
19
+ * server-rendered markup shows a leaf and the twisty appears on hydration.
20
20
  *
21
21
  * **`href` makes the whole row a link**, rendered as an `<a>`. Enter, middle
22
22
  * click, and the status bar then work anywhere on the row.
@@ -43,7 +43,7 @@ declare const HelixHeadlessTreeItem_base: (new (...args: any[]) => import("@fusi
43
43
  * @aliases TreeNode, Branch, Leaf
44
44
  * @documentation https://fusion.dev/helix/components/tree
45
45
  *
46
- * @slot - The item's label.
46
+ * @slot - The item's label. A nested item does not go here: give it `slot="children"`. In a development build, an item nested here is reported.
47
47
  * @slot chevron - Replaces the expand and collapse mark of a branch, such as with your own icon.
48
48
  * @slot children - Nested items, which make this a branch.
49
49
  * @slot end - Content after the label, at the far side of the row, such as a count or a state.
@@ -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, HelixLoadEvent, HelixSelectEvent, MixinControllerOpen, } from '@fusion.dev/helix-core';
7
+ import { auditNesting, customElement, HelixElement, HelixLoadEvent, HelixSelectEvent, MixinControllerOpen, } from '@fusion.dev/helix-core';
8
8
  import { caretDown, check, dotsSixVertical, minus } from '@fusion.dev/helix-icons';
9
9
  import { html, nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
@@ -68,6 +68,10 @@ let HelixHeadlessTreeItem = class HelixHeadlessTreeItem extends MixinControllerO
68
68
  slot?.addEventListener('slotchange', () => {
69
69
  this.branch = slot.assignedElements().length > 0;
70
70
  });
71
+ auditNesting(this, 'children');
72
+ this.renderRoot
73
+ .querySelector('[part="label"] > slot')
74
+ ?.addEventListener('slotchange', () => auditNesting(this, 'children'));
71
75
  }
72
76
  get choosable() {
73
77
  return this.value !== '' && !this.heads && !this.checkable && !this.disabled;