@fusion.dev/helix-headless 0.52.1 → 0.53.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 (58) hide show
  1. package/custom-elements.json +691 -273
  2. package/dist/announcement/root/announcement-root.js +2 -5
  3. package/dist/area-chart/root/area-chart-root.js +10 -12
  4. package/dist/bar-chart/root/bar-chart-root.js +4 -5
  5. package/dist/blockquote/root/blockquote-root.js +2 -5
  6. package/dist/chat/composer/chat-composer.js +2 -5
  7. package/dist/chat/message/chat-message.js +4 -11
  8. package/dist/chat/root/chat-root.js +1 -1
  9. package/dist/checkbox/root/checkbox-root.js +1 -1
  10. package/dist/checkbox-group/root/checkbox-group-root.js +1 -1
  11. package/dist/color-input/root/color-input-root.js +1 -1
  12. package/dist/combobox/root/combobox-root.d.ts +16 -0
  13. package/dist/combobox/root/combobox-root.js +5 -2
  14. package/dist/data-table/root/data-table-root.d.ts +23 -10
  15. package/dist/data-table/root/data-table-root.js +12 -13
  16. package/dist/date-input/root/date-input-root.js +1 -1
  17. package/dist/divider/root/divider-root.js +2 -5
  18. package/dist/donut-chart/root/donut-chart-root.js +5 -6
  19. package/dist/drilldown/item/drilldown-item.d.ts +18 -0
  20. package/dist/drilldown/item/drilldown-item.js +3 -0
  21. package/dist/drilldown/root/drilldown-root.d.ts +20 -1
  22. package/dist/drilldown/root/drilldown-root.js +6 -13
  23. package/dist/file-input/root/file-input-root.js +2 -2
  24. package/dist/filter-input/root/filter-input-root.js +1 -1
  25. package/dist/heatmap/root/buckets.d.ts +2 -2
  26. package/dist/heatmap/root/buckets.js +3 -4
  27. package/dist/heatmap/root/heatmap-root.d.ts +0 -10
  28. package/dist/heatmap/root/heatmap-root.js +2 -8
  29. package/dist/input/root/input-root.js +1 -1
  30. package/dist/line-chart/root/line-chart-root.js +15 -17
  31. package/dist/listbox/root/listbox-root.js +1 -1
  32. package/dist/menu/item/menu-item.js +2 -2
  33. package/dist/notifications/notification/notification.d.ts +3 -9
  34. package/dist/notifications/notification/notification.js +3 -11
  35. package/dist/notifications/root/notifications-root.d.ts +0 -10
  36. package/dist/notifications/root/notifications-root.js +3 -9
  37. package/dist/number-input/root/number-input-root.js +2 -2
  38. package/dist/radio/root/radio-root.d.ts +10 -32
  39. package/dist/radio/root/radio-root.js +6 -27
  40. package/dist/scatter-chart/root/scatter-chart-root.js +10 -11
  41. package/dist/select/root/select-root.d.ts +24 -0
  42. package/dist/select/root/select-root.js +5 -2
  43. package/dist/slider/root/slider-root.js +1 -1
  44. package/dist/sparkline/root/sparkline-root.js +10 -12
  45. package/dist/switch/root/switch-root.js +3 -6
  46. package/dist/tabs/root/tabs-root.js +11 -4
  47. package/dist/tabs/tab/tab.d.ts +11 -30
  48. package/dist/tabs/tab/tab.js +7 -28
  49. package/dist/textarea/root/textarea-root.js +1 -1
  50. package/dist/time-input/root/time-input-root.js +1 -1
  51. package/dist/toggle/root/toggle-root.d.ts +10 -36
  52. package/dist/toggle/root/toggle-root.js +6 -27
  53. package/dist/tree/item/tree-item.js +6 -5
  54. package/dist/tree/root/tree-root.js +8 -4
  55. package/dist/video/root/video-root.js +7 -14
  56. package/package.json +5 -5
  57. package/types/jsx.d.ts +192 -74
  58. package/types/vue.d.ts +36 -16
@@ -4,13 +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, extent, HelixElement, linear, linePath, SourceController, } from '@fusion.dev/helix-core';
7
+ import { customElement, extent, HelixElement, linear, linePath, PLOT_WIDTH, SourceController, writtenList, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing, svg } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
- const BOX = { height: 32, width: 100 };
11
- const readValues = (input) => input
12
- .split(/[\s,]+/)
13
- .filter((one) => one !== '')
10
+ const STRIP_HEIGHT = 32;
11
+ const readValues = (input) => writtenList(input)
14
12
  .map(Number)
15
13
  .filter((one) => Number.isFinite(one));
16
14
  let HelixHeadlessSparkline = class HelixHeadlessSparkline extends HelixElement {
@@ -34,17 +32,17 @@ let HelixHeadlessSparkline = class HelixHeadlessSparkline extends HelixElement {
34
32
  }
35
33
  render() {
36
34
  const points = this.points();
37
- const x = linear([0, Math.max(points.length - 1, 1)], [0, BOX.width]);
35
+ const x = linear([0, Math.max(points.length - 1, 1)], [0, PLOT_WIDTH]);
38
36
  const [low, high] = extent(points.length > 0 ? points : [0]);
39
37
  const y = this.plot === 'bar'
40
- ? linear([Math.min(0, low), Math.max(0, high)], [BOX.height, 0])
41
- : linear([low, high], [BOX.height, 0]);
38
+ ? linear([Math.min(0, low), Math.max(0, high)], [STRIP_HEIGHT, 0])
39
+ : linear([low, high], [STRIP_HEIGHT, 0]);
42
40
  const coordinates = points.map((value, index) => [x(index), y(value)]);
43
41
  const path = this.plot === 'bar' ? '' : linePath(coordinates, this.curve ?? 'smooth');
44
42
  const area = this.plot !== 'bar' && this.plot !== 'line' && path !== ''
45
- ? `${path}L${BOX.width},${BOX.height}L0,${BOX.height}Z`
43
+ ? `${path}L${PLOT_WIDTH},${STRIP_HEIGHT}L0,${STRIP_HEIGHT}Z`
46
44
  : '';
47
- const slot = BOX.width / Math.max(points.length, 1);
45
+ const slot = PLOT_WIDTH / Math.max(points.length, 1);
48
46
  const floor = y(0);
49
47
  const bars = this.plot === 'bar'
50
48
  ? points.map((value, index) => ({
@@ -56,7 +54,7 @@ let HelixHeadlessSparkline = class HelixHeadlessSparkline extends HelixElement {
56
54
  : [];
57
55
  const end = points.length < 2
58
56
  ? undefined
59
- : `--chart-terminus-x: 100%; --chart-terminus-y: ${(y(points[points.length - 1]) / BOX.height) * 100}%`;
57
+ : `--chart-terminus-x: 100%; --chart-terminus-y: ${(y(points[points.length - 1]) / STRIP_HEIGHT) * 100}%`;
60
58
  return html `<svg
61
59
  aria-hidden=${this.label === '' ? 'true' : nothing}
62
60
  aria-label=${this.label === '' ? nothing : this.label}
@@ -65,7 +63,7 @@ let HelixHeadlessSparkline = class HelixHeadlessSparkline extends HelixElement {
65
63
  preserveAspectRatio="none"
66
64
  role=${this.label === '' ? nothing : 'img'}
67
65
  style=${end ?? nothing}
68
- viewBox="0 0 ${BOX.width} ${BOX.height}"
66
+ viewBox="0 0 ${PLOT_WIDTH} ${STRIP_HEIGHT}"
69
67
  >
70
68
  ${svg `<defs>
71
69
  <linearGradient id="sparkline-wash" x1="0" x2="0" y1="0" y2="1">
@@ -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, customElement, HelixFormElement, MixinControllerChecked, MixinField, slotFilled, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, customElement, HelixFormElement, MixinControllerChecked, MixinField, markRegion, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  let HelixHeadlessSwitch = class HelixHeadlessSwitch extends MixinControllerChecked(MixinField(HelixFormElement)) {
@@ -38,10 +38,7 @@ let HelixHeadlessSwitch = class HelixHeadlessSwitch extends MixinControllerCheck
38
38
  this.sync();
39
39
  }
40
40
  markEmpty() {
41
- const region = this.partNamed('label');
42
- const empty = !slotFilled(this.renderRoot?.querySelector('[part="label"] slot'));
43
- region?.toggleAttribute('data-empty', empty);
44
- region?.toggleAttribute('hidden', empty);
41
+ markRegion(this.renderRoot, 'label', ['']);
45
42
  }
46
43
  render() {
47
44
  return html `<label part="field">
@@ -50,7 +47,7 @@ let HelixHeadlessSwitch = class HelixHeadlessSwitch extends MixinControllerCheck
50
47
  part="control"
51
48
  type="checkbox"
52
49
  role="switch"
53
- .checked=${this.checked}
50
+ .checked=${this.checked || nothing}
54
51
  ?disabled=${this.isDisabled}
55
52
  ?required=${this.required}
56
53
  aria-describedby=${this.describedBy ?? nothing}
@@ -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, IndicatorController, KeyboardNavigationController, MixinControllerActive, } from '@fusion.dev/helix-core';
7
+ import { customElement, HelixElement, IndicatorController, KeyboardNavigationController, MixinControllerActive, whenHydrated, } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  import { HelixHeadlessTabPanel } from '../panel/tab-panel.js';
@@ -47,12 +47,19 @@ let HelixHeadlessTabs = class HelixHeadlessTabs extends MixinControllerActive(He
47
47
  const declared = this.active === '' ? tabs.find((tab) => tab.selected) : undefined;
48
48
  const value = (declared ?? this.resolveActive(tabs))?.value ?? '';
49
49
  for (const tab of tabs) {
50
- tab.selected = tab.value === value;
50
+ const selected = tab.value === value;
51
+ whenHydrated(tab, () => {
52
+ tab.selected = selected;
53
+ });
51
54
  }
52
55
  const names = new Map(tabs.map((tab) => [tab.value, (tab.textContent ?? '').trim()]));
53
56
  for (const panel of this.panels) {
54
- panel.selected = panel.value === value;
55
- panel.label = names.get(panel.value) ?? '';
57
+ const selected = panel.value === value;
58
+ const label = names.get(panel.value) ?? '';
59
+ whenHydrated(panel, () => {
60
+ panel.selected = selected;
61
+ panel.label = label;
62
+ });
56
63
  }
57
64
  }
58
65
  connectedCallback() {
@@ -1,5 +1,6 @@
1
1
  import { HelixElement } from '@fusion.dev/helix-core';
2
2
  import { type PropertyValues, type TemplateResult } from 'lit';
3
+ declare const HelixHeadlessTab_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinSelectableItemInterface) & typeof HelixElement;
3
4
  /**
4
5
  * @summary One tab in a tab list.
5
6
  *
@@ -24,39 +25,18 @@ import { type PropertyValues, type TemplateResult } from 'lit';
24
25
  *
25
26
  * @cssstate selected - While it is the tab selected.
26
27
  */
27
- export declare class HelixHeadlessTab extends HelixElement {
28
+ export declare class HelixHeadlessTab extends HelixHeadlessTab_base {
29
+ protected itemPart: string;
28
30
  /**
29
- * Whether the tab can be chosen.
30
- */
31
- disabled: boolean;
32
- /**
33
- * Whether this tab is the current one.
34
- */
35
- selected: boolean;
36
- /**
37
- * Identifies the tab, and pairs it with the panel of the same value.
38
- */
39
- value: string;
40
- /** Moves focus to this tab, for the list's arrow keys. */
41
- focusItem(): void;
42
- /** Selects this tab and announces it. */
43
- select(): void;
44
- /**
45
- * Mirrors the chosen state where a stylesheet can select on it.
46
- *
47
- * **A custom state, not a reflected `selected` attribute.** The parent writes
48
- * this: a listbox choosing a row, a tab strip moving the tab, a radio group
49
- * answering for its set. Reflected, it puts the element's own bookkeeping onto
50
- * markup a consumer wrote and a consumer's server never sent, which React
51
- * reconciles and reports as a mismatch, and which a serialized element pasted
52
- * back declares as a choice nobody made. `ElementInternals` holds it instead,
53
- * where it is not in the DOM at all.
31
+ * Asks for the render that follows a hydration, so the stop stops being every
32
+ * tab's.
54
33
  *
55
- * Reading `selected` is unaffected and so is writing it: the attribute still
56
- * seeds the property, which is what `<helix-tab selected>` in a consumer's
57
- * markup means. What stops is the write back.
34
+ * `tabindex` reads `childrenKnown` and a hydrating render is one where that is
35
+ * false, which is what keeps the hydrating render the server's. Nothing else
36
+ * would ask: the root writes an already-false `selected` onto an unchosen tab,
37
+ * which changes nothing and so re-renders nothing.
58
38
  */
59
- protected willUpdate(changed: PropertyValues): void;
39
+ protected firstUpdated(changed: PropertyValues): void;
60
40
  protected render(): TemplateResult;
61
41
  }
62
42
  declare global {
@@ -64,3 +44,4 @@ declare global {
64
44
  'helix-headless-tab': HelixHeadlessTab;
65
45
  }
66
46
  }
47
+ export {};
@@ -4,28 +4,16 @@ 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, HelixSelectEvent } from '@fusion.dev/helix-core';
7
+ import { customElement, HelixElement, MixinSelectableItem } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
- import { property } from 'lit/decorators.js';
10
- let HelixHeadlessTab = class HelixHeadlessTab extends HelixElement {
9
+ let HelixHeadlessTab = class HelixHeadlessTab extends MixinSelectableItem(HelixElement) {
11
10
  constructor() {
12
11
  super(...arguments);
13
- this.disabled = false;
14
- this.selected = false;
15
- this.value = '';
12
+ this.itemPart = 'tab';
16
13
  }
17
- focusItem() {
18
- this.partNamed('tab')?.focus();
19
- }
20
- select() {
21
- if (this.disabled) {
22
- return;
23
- }
24
- this.dispatchEvent(new HelixSelectEvent({ value: this.value }));
25
- }
26
- willUpdate(changed) {
27
- super.willUpdate(changed);
28
- this.toggleState('selected', this.selected);
14
+ firstUpdated(changed) {
15
+ super.firstUpdated(changed);
16
+ this.finishHydration();
29
17
  }
30
18
  render() {
31
19
  return html `<button
@@ -34,22 +22,13 @@ let HelixHeadlessTab = class HelixHeadlessTab extends HelixElement {
34
22
  type="button"
35
23
  aria-selected=${this.selected ? 'true' : 'false'}
36
24
  ?disabled=${this.disabled}
37
- tabindex=${this.selected ? '0' : '-1'}
25
+ tabindex=${this.selected || !this.childrenKnown ? '0' : '-1'}
38
26
  @click=${() => this.select()}
39
27
  >
40
28
  <slot></slot>
41
29
  </button>`;
42
30
  }
43
31
  };
44
- __decorate([
45
- property({ reflect: true, type: Boolean })
46
- ], HelixHeadlessTab.prototype, "disabled", void 0);
47
- __decorate([
48
- property({ type: Boolean })
49
- ], HelixHeadlessTab.prototype, "selected", void 0);
50
- __decorate([
51
- property()
52
- ], HelixHeadlessTab.prototype, "value", void 0);
53
32
  HelixHeadlessTab = __decorate([
54
33
  customElement('helix-headless-tab')
55
34
  ], HelixHeadlessTab);
@@ -157,7 +157,7 @@ let HelixHeadlessTextarea = class HelixHeadlessTextarea extends MixinControllerV
157
157
  <div part="field">
158
158
  <textarea
159
159
  part="control"
160
- id="control"
160
+ id=${this.controlId}
161
161
  .value=${this.value}
162
162
  rows=${this.rows}
163
163
  cols=${this.cols ?? nothing}
@@ -192,7 +192,7 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinPickerEdi
192
192
  : nothing}
193
193
  <input
194
194
  part="control"
195
- id="control"
195
+ id=${this.controlId}
196
196
  type="text"
197
197
  .value=${this.display}
198
198
  name=${this.name || nothing}
@@ -1,5 +1,6 @@
1
1
  import { HelixElement } from '@fusion.dev/helix-core';
2
2
  import { type PropertyValues, type TemplateResult } from 'lit';
3
+ declare const HelixHeadlessToggle_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinSelectableItemInterface) & typeof HelixElement;
3
4
  /**
4
5
  * @summary One option in a toggle group.
5
6
  *
@@ -24,11 +25,7 @@ import { type PropertyValues, type TemplateResult } from 'lit';
24
25
  *
25
26
  * @cssstate selected - While it is on.
26
27
  */
27
- export declare class HelixHeadlessToggle extends HelixElement {
28
- /**
29
- * Whether the item can be chosen.
30
- */
31
- disabled: boolean;
28
+ export declare class HelixHeadlessToggle extends HelixHeadlessToggle_base {
32
29
  /**
33
30
  * Whether the markup declared this one chosen, which is what a reset returns to.
34
31
  *
@@ -69,39 +66,15 @@ export declare class HelixHeadlessToggle extends HelixElement {
69
66
  */
70
67
  focusable: boolean;
71
68
  /**
72
- * Whether this item is the current one.
73
- */
74
- selected: boolean;
75
- /**
76
- * Moves focus to this item's own control.
77
- *
78
- * The group drives arrow-key navigation and cannot reach the control itself,
79
- * which is inside this shadow root. Exposing the intent as a method keeps the
80
- * group out of the child's internals.
81
- */
82
- focusItem(): void;
83
- /** Chooses this item and announces it. */
84
- select(): void;
85
- /**
86
- * Identifies the item to the group.
87
- */
88
- value: string;
89
- /**
90
- * Mirrors the chosen state where a stylesheet can select on it.
91
- *
92
- * **A custom state rather than the `selected` attribute.** The parent writes
93
- * this: a listbox choosing a row, a tab strip moving the tab, a radio group
94
- * answering for its set. Reflected, that puts the element's own bookkeeping
95
- * onto markup a consumer wrote and a consumer's server never sent, which
96
- * React reconciles and reports as a mismatch, and which a serialized element
97
- * pasted back declares as a choice nobody made. `ElementInternals` holds it
98
- * instead, where it is not in the DOM at all.
69
+ * Asks for the render that follows a hydration, so the stop stops being every
70
+ * choice's.
99
71
  *
100
- * Reading `selected` is unaffected and so is writing it: the attribute still
101
- * seeds the property, which is what `<helix-toggle selected>` in a consumer's
102
- * markup means. There is no write back.
72
+ * `tabindex` reads `childrenKnown` and a hydrating render is one where that is
73
+ * false, which is what keeps the hydrating render the server's. Nothing else
74
+ * would ask: the group writes an already-false `focusable` onto every choice
75
+ * but one, which changes nothing and so re-renders nothing.
103
76
  */
104
- protected willUpdate(changed: PropertyValues): void;
77
+ protected firstUpdated(changed: PropertyValues): void;
105
78
  protected render(): TemplateResult;
106
79
  }
107
80
  declare global {
@@ -109,3 +82,4 @@ declare global {
109
82
  'helix-headless-toggle': HelixHeadlessToggle;
110
83
  }
111
84
  }
85
+ export {};
@@ -4,16 +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 { customElement, HelixElement, HelixSelectEvent } from '@fusion.dev/helix-core';
7
+ import { customElement, HelixElement, MixinSelectableItem } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
- let HelixHeadlessToggle = class HelixHeadlessToggle extends HelixElement {
10
+ let HelixHeadlessToggle = class HelixHeadlessToggle extends MixinSelectableItem(HelixElement) {
11
11
  constructor() {
12
12
  super(...arguments);
13
- this.disabled = false;
14
13
  this.focusable = false;
15
- this.selected = false;
16
- this.value = '';
17
14
  }
18
15
  get defaultSelected() {
19
16
  return this.hasAttribute('selected');
@@ -21,18 +18,9 @@ let HelixHeadlessToggle = class HelixHeadlessToggle extends HelixElement {
21
18
  set defaultSelected(next) {
22
19
  this.toggleAttribute('selected', next);
23
20
  }
24
- focusItem() {
25
- this.partNamed('item')?.focus();
26
- }
27
- select() {
28
- if (this.disabled) {
29
- return;
30
- }
31
- this.dispatchEvent(new HelixSelectEvent({ value: this.value }));
32
- }
33
- willUpdate(changed) {
34
- super.willUpdate(changed);
35
- this.toggleState('selected', this.selected);
21
+ firstUpdated(changed) {
22
+ super.firstUpdated(changed);
23
+ this.finishHydration();
36
24
  }
37
25
  render() {
38
26
  return html `<button
@@ -41,25 +29,16 @@ let HelixHeadlessToggle = class HelixHeadlessToggle extends HelixElement {
41
29
  role="radio"
42
30
  aria-checked=${this.selected ? 'true' : 'false'}
43
31
  ?disabled=${this.disabled}
44
- tabindex=${this.selected || this.focusable ? '0' : '-1'}
32
+ tabindex=${this.selected || this.focusable || !this.childrenKnown ? '0' : '-1'}
45
33
  @click=${() => this.select()}
46
34
  >
47
35
  <slot></slot>
48
36
  </button>`;
49
37
  }
50
38
  };
51
- __decorate([
52
- property({ reflect: true, type: Boolean })
53
- ], HelixHeadlessToggle.prototype, "disabled", void 0);
54
39
  __decorate([
55
40
  property({ attribute: false })
56
41
  ], HelixHeadlessToggle.prototype, "focusable", void 0);
57
- __decorate([
58
- property({ type: Boolean })
59
- ], HelixHeadlessToggle.prototype, "selected", void 0);
60
- __decorate([
61
- property()
62
- ], HelixHeadlessToggle.prototype, "value", void 0);
63
42
  HelixHeadlessToggle = __decorate([
64
43
  customElement('helix-headless-toggle')
65
44
  ], HelixHeadlessToggle);
@@ -97,21 +97,22 @@ let HelixHeadlessTreeItem = class HelixHeadlessTreeItem extends MixinControllerO
97
97
  willUpdate(changed) {
98
98
  super.willUpdate(changed);
99
99
  this.toggleState('selected', this.selected);
100
- if (this.children !== undefined && !this.hydrating) {
101
- this.branch = [...this.children].some((child) => child.slot === 'children');
100
+ if (this.childrenKnown) {
101
+ this.branch = this.knownChildren.some((child) => child.slot === 'children');
102
102
  }
103
103
  }
104
104
  render() {
105
105
  const linked = this.href !== '';
106
106
  const level = this.level ?? 1;
107
+ const depth = this.level ?? nothing;
107
108
  const expanded = this.branch ? (this.open ? 'true' : 'false') : nothing;
108
- const tabIndex = this.focusable ? '0' : '-1';
109
+ const tabIndex = this.focusable || !this.childrenKnown ? '0' : '-1';
109
110
  return html `<div
110
111
  part="item"
111
112
  role=${linked ? 'none' : 'treeitem'}
112
113
  aria-expanded=${linked ? nothing : expanded}
113
114
  aria-selected=${linked ? nothing : this.selected ? 'true' : 'false'}
114
- aria-level=${linked ? nothing : level}
115
+ aria-level=${linked ? nothing : depth}
115
116
  tabindex=${linked ? nothing : tabIndex}
116
117
  >
117
118
  ${linked
@@ -123,7 +124,7 @@ let HelixHeadlessTreeItem = class HelixHeadlessTreeItem extends MixinControllerO
123
124
  rel=${this.rel || nothing}
124
125
  aria-expanded=${expanded}
125
126
  aria-selected=${this.selected ? 'true' : 'false'}
126
- aria-level=${level}
127
+ aria-level=${depth}
127
128
  tabindex=${tabIndex}
128
129
  style="--tree-level: ${level}"
129
130
  @click=${this.handleRowClick}
@@ -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 { closestInstance, customElement, HelixElement, inlineStep, MixinCollection, MixinControllerActive, scrollItemIntoView, } from '@fusion.dev/helix-core';
7
+ import { closestInstance, customElement, HelixElement, inlineStep, MixinCollection, MixinControllerActive, scrollItemIntoView, whenHydrated, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  import { HelixHeadlessTreeItem } from '../item/tree-item.js';
@@ -101,8 +101,8 @@ let HelixHeadlessTree = class HelixHeadlessTree extends MixinCollection(MixinCon
101
101
  const held = visible.find((item) => item.focusable);
102
102
  const stop = held ?? chosen;
103
103
  for (const item of this.items) {
104
- item.selected = item.value !== '' && item.value === this.active;
105
- item.focusable = item === stop;
104
+ const selected = item.value !== '' && item.value === this.active;
105
+ const focusable = item === stop;
106
106
  let level = 1;
107
107
  let parent = item.parentElement;
108
108
  while (parent && parent !== this) {
@@ -111,7 +111,11 @@ let HelixHeadlessTree = class HelixHeadlessTree extends MixinCollection(MixinCon
111
111
  }
112
112
  parent = parent.parentElement;
113
113
  }
114
- item.level = level;
114
+ whenHydrated(item, () => {
115
+ item.selected = selected;
116
+ item.focusable = focusable;
117
+ item.level = level;
118
+ });
115
119
  }
116
120
  }
117
121
  move(from, delta) {
@@ -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, drawIcon, HelixElement, HelixNextEvent, HelixPreviousEvent, onUnlessFalse, } from '@fusion.dev/helix-core';
7
+ import { customElement, drawIcon, HelixElement, HelixNextEvent, HelixPreviousEvent, onUnlessFalse, writtenList, } from '@fusion.dev/helix-core';
8
8
  import { airplay, arrowClockwise, arrowCounterClockwise, closedCaptioning, cornersIn, cornersOut, dotsThreeVertical, download, gauge, pause, pictureInPicture, play, skipBack, skipForward, speakerHigh, speakerX, } from '@fusion.dev/helix-icons';
9
9
  import { nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
@@ -250,17 +250,11 @@ let HelixHeadlessVideo = class HelixHeadlessVideo extends HelixElement {
250
250
  return one === 'speed';
251
251
  }
252
252
  get extras() {
253
- return this.extraControls
254
- .split(/\s+/)
255
- .filter(Boolean)
256
- .filter((one) => this.offers(one));
253
+ return writtenList(this.extraControls).filter((one) => this.offers(one));
257
254
  }
258
255
  get overflow() {
259
256
  const onBar = new Set(this.extras);
260
- return this.overflowControls
261
- .split(/\s+/)
262
- .filter(Boolean)
263
- .filter((one) => !onBar.has(one) && this.offers(one));
257
+ return writtenList(this.overflowControls).filter((one) => !onBar.has(one) && this.offers(one));
264
258
  }
265
259
  get currentSrc() {
266
260
  return this.video?.currentSrc ?? '';
@@ -398,7 +392,7 @@ let HelixHeadlessVideo = class HelixHeadlessVideo extends HelixElement {
398
392
  });
399
393
  }
400
394
  claimSession() {
401
- const skips = `${this.extraControls} ${this.overflowControls}`.split(/\s+/);
395
+ const skips = writtenList(`${this.extraControls} ${this.overflowControls}`);
402
396
  this.session.claim({ artwork: this.poster, title: this.label }, {
403
397
  next: skips.includes('next') ? () => this.skip(false) : null,
404
398
  previous: skips.includes('previous') ? () => this.skip(true) : null,
@@ -430,8 +424,7 @@ let HelixHeadlessVideo = class HelixHeadlessVideo extends HelixElement {
430
424
  this.dispatchEvent(back ? new HelixPreviousEvent() : new HelixNextEvent());
431
425
  }
432
426
  cycleSpeed() {
433
- const speeds = this.speeds
434
- .split(/\s+/)
427
+ const speeds = writtenList(this.speeds)
435
428
  .map(Number)
436
429
  .filter((one) => one > 0);
437
430
  const video = this.video;
@@ -456,9 +449,9 @@ let HelixHeadlessVideo = class HelixHeadlessVideo extends HelixElement {
456
449
  }
457
450
  willUpdate(changed) {
458
451
  super.willUpdate(changed);
459
- if (!this.mediaRead && this.children !== undefined && !this.hydrating) {
452
+ if (!this.mediaRead && this.childrenKnown) {
460
453
  this.mediaRead = true;
461
- this.media = this.copiesOf([...this.children].filter((child) => child.slot === ''));
454
+ this.media = this.copiesOf(this.knownChildren.filter((child) => child.slot === ''));
462
455
  }
463
456
  if (changed.has('media')) {
464
457
  this.captions = captionsAmong(this.media);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fusion.dev/helix-headless",
3
3
  "description": "Headless component tier. Structure, accessibility, state, and form participation. No styling.",
4
- "version": "0.52.1",
4
+ "version": "0.53.1",
5
5
  "license": "MIT",
6
6
  "author": "Ross Sheppard <ross.sheppard@fusion.dev>",
7
7
  "repository": {
@@ -46,14 +46,14 @@
46
46
  },
47
47
  "bugs": "https://github.com/project-fusion/fusion/issues",
48
48
  "dependencies": {
49
- "@fusion.dev/helix-core": "0.52.1",
50
- "@fusion.dev/helix-icons": "0.52.1",
49
+ "@fusion.dev/helix-core": "0.53.1",
50
+ "@fusion.dev/helix-icons": "0.53.1",
51
51
  "uqr": "^0.1.3"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@custom-elements-manifest/analyzer": "^0.11.0",
55
- "@fusion.dev/helix-cem": "0.52.1",
56
- "@fusion.dev/helix-testing": "0.52.1",
55
+ "@fusion.dev/helix-cem": "0.53.1",
56
+ "@fusion.dev/helix-testing": "0.53.1",
57
57
  "@fusion.dev/scripts": "0.0.0-dev",
58
58
  "@happy-dom/global-registrator": "^20.0.0",
59
59
  "@lit-labs/ssr": "^4.1.0",