@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,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, MixinDismissible, slotFilled, } from '@fusion.dev/helix-core';
7
+ import { customElement, HelixElement, hostAria, MixinDismissible, markRegion, } 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 } from 'lit/decorators.js';
@@ -20,10 +20,7 @@ let HelixHeadlessAnnouncement = class HelixHeadlessAnnouncement extends MixinDis
20
20
  this.markEmpty();
21
21
  }
22
22
  markEmpty() {
23
- const region = this.partNamed('actions');
24
- const empty = !slotFilled(this.renderRoot?.querySelector('slot[name="actions"]'));
25
- region?.toggleAttribute('data-empty', empty);
26
- region?.toggleAttribute('hidden', empty);
23
+ markRegion(this.renderRoot, 'actions', ['actions']);
27
24
  }
28
25
  renderDismissIcon() {
29
26
  return html `<slot name="icon">
@@ -4,12 +4,10 @@ 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, customElement, drawTooltip, formatNumbers, HelixElement, linear, linePath, MixinChartSource, seriesIndex, ticks, } from '@fusion.dev/helix-core';
7
+ import { CHART_PLOT, customElement, drawTooltip, formatNumbers, HelixElement, linear, linePath, MixinChartSource, PLOT_HEIGHT, PLOT_WIDTH, seriesIndex, ticks, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing, svg } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  import '../../tooltip/index.js';
11
- const WIDTH = 100;
12
- const HEIGHT = 100;
13
11
  const percent = new Intl.NumberFormat(undefined, { maximumFractionDigits: 0, style: 'percent' });
14
12
  let HelixHeadlessAreaChart = class HelixHeadlessAreaChart extends MixinChartSource(HelixElement) {
15
13
  constructor() {
@@ -73,9 +71,9 @@ let HelixHeadlessAreaChart = class HelixHeadlessAreaChart extends MixinChartSour
73
71
  }
74
72
  const top = this.share ? 1 : Math.max(0, ...stack.totals);
75
73
  const domain = [0, top > 0 ? top : 1];
76
- const y = linear(domain, [HEIGHT, 0]);
77
- const slot = WIDTH / rows.length;
78
- const x = linear([0, Math.max(rows.length - 1, 1)], [slot / 2, WIDTH - slot / 2]);
74
+ const y = linear(domain, [PLOT_HEIGHT, 0]);
75
+ const slot = PLOT_WIDTH / rows.length;
76
+ const x = linear([0, Math.max(rows.length - 1, 1)], [slot / 2, PLOT_WIDTH - slot / 2]);
79
77
  return html `${this.renderPlot(stack, rows.length, x, y, domain)}
80
78
  ${this.renderLabels(rows.map((row) => row.label))} ${this.renderSummary(stack, rows)}`;
81
79
  }
@@ -85,12 +83,12 @@ let HelixHeadlessAreaChart = class HelixHeadlessAreaChart extends MixinChartSour
85
83
  <svg part="svg"
86
84
  data-mirror
87
85
  preserveAspectRatio="none"
88
- viewBox="0 0 ${WIDTH} ${HEIGHT}"
86
+ viewBox="0 0 ${PLOT_WIDTH} ${PLOT_HEIGHT}"
89
87
  xmlns="http://www.w3.org/2000/svg"
90
88
  >
91
89
  ${order.map((depth) => this.renderLayer(stack.layers[depth] ?? [], depth, x, y))}
92
90
  ${this.renderGridlines(x, y, domain, count)}
93
- <line part="baseline" x1="0" x2=${WIDTH} y1=${HEIGHT} y2=${HEIGHT} />
91
+ <line part="baseline" x1="0" x2=${PLOT_WIDTH} y1=${PLOT_HEIGHT} y2=${PLOT_HEIGHT} />
94
92
  ${this.renderGuide(x)}
95
93
  </svg>
96
94
  ${this.renderTooltip(stack, y)}
@@ -105,7 +103,7 @@ let HelixHeadlessAreaChart = class HelixHeadlessAreaChart extends MixinChartSour
105
103
  const index = seriesIndex(depth);
106
104
  const first = points[0];
107
105
  const last = points.at(-1);
108
- const wash = `${path}L${last[0]},${HEIGHT}L${first[0]},${HEIGHT}Z`;
106
+ const wash = `${path}L${last[0]},${PLOT_HEIGHT}L${first[0]},${PLOT_HEIGHT}Z`;
109
107
  return svg `<path
110
108
  d=${wash}
111
109
  data-series=${index}
@@ -124,12 +122,12 @@ let HelixHeadlessAreaChart = class HelixHeadlessAreaChart extends MixinChartSour
124
122
  return nothing;
125
123
  }
126
124
  const down = this.rulesCategory
127
- ? Array.from({ length: count }, (_, index) => svg `<line part="gridline" x1=${x(index)} x2=${x(index)} y1="0" y2=${HEIGHT} />`)
125
+ ? Array.from({ length: count }, (_, index) => svg `<line part="gridline" x1=${x(index)} x2=${x(index)} y1="0" y2=${PLOT_HEIGHT} />`)
128
126
  : [];
129
127
  const across = this.rulesValue
130
128
  ? ticks(domain)
131
129
  .filter((level) => level > domain[0])
132
- .map((level) => svg `<line part="gridline" x1="0" x2=${WIDTH} y1=${y(level)} y2=${y(level)} />`)
130
+ .map((level) => svg `<line part="gridline" x1="0" x2=${PLOT_WIDTH} y1=${y(level)} y2=${y(level)} />`)
133
131
  : [];
134
132
  return svg `${down}${across}`;
135
133
  }
@@ -138,7 +136,7 @@ let HelixHeadlessAreaChart = class HelixHeadlessAreaChart extends MixinChartSour
138
136
  if (at < 0) {
139
137
  return nothing;
140
138
  }
141
- return svg `<line part="guide" x1=${x(at)} x2=${x(at)} y1="0" y2=${HEIGHT} />`;
139
+ return svg `<line part="guide" x1=${x(at)} x2=${x(at)} y1="0" y2=${PLOT_HEIGHT} />`;
142
140
  }
143
141
  renderLabels(readings) {
144
142
  return html `<div aria-hidden="true" part="labels">
@@ -4,11 +4,10 @@ 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, customElement, drawTooltip, extent, HelixElement, linear, linePath, MixinChartSource, seriesIndex, seriesNames, ticks, } from '@fusion.dev/helix-core';
7
+ import { CHART_PLOT, customElement, drawTooltip, extent, HelixElement, linear, linePath, MixinChartSource, PLOT_HEIGHT, PLOT_WIDTH, seriesIndex, seriesNames, ticks, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing, svg } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  import '../../tooltip/index.js';
11
- const HEIGHT = 100;
12
11
  let HelixHeadlessBarChart = class HelixHeadlessBarChart extends MixinChartSource(HelixElement) {
13
12
  get scaled() {
14
13
  return new Set(seriesNames(this.secondary));
@@ -41,7 +40,7 @@ let HelixHeadlessBarChart = class HelixHeadlessBarChart extends MixinChartSource
41
40
  };
42
41
  const [low, high] = extent(scaled.size === 0 ? on(() => true) : on((one) => !own(one)));
43
42
  const domain = [Math.min(0, low), Math.max(0, high)];
44
- const range = this.direction === 'horizontal' ? [0, HEIGHT] : [HEIGHT, 0];
43
+ const range = this.direction === 'horizontal' ? [0, PLOT_HEIGHT] : [PLOT_HEIGHT, 0];
45
44
  const y = linear(domain, range);
46
45
  const floor = y(0);
47
46
  const sideways = this.direction === 'horizontal';
@@ -103,7 +102,7 @@ let HelixHeadlessBarChart = class HelixHeadlessBarChart extends MixinChartSource
103
102
  <svg part="svg"
104
103
  data-mirror
105
104
  preserveAspectRatio="none"
106
- viewBox="0 0 100 ${HEIGHT}"
105
+ viewBox="0 0 ${PLOT_WIDTH} ${PLOT_HEIGHT}"
107
106
  xmlns="http://www.w3.org/2000/svg"
108
107
  >
109
108
  ${svg `<defs>
@@ -208,7 +207,7 @@ let HelixHeadlessBarChart = class HelixHeadlessBarChart extends MixinChartSource
208
207
  const [low, high] = extent(all.length > 0 ? all : [0]);
209
208
  const domain = [Math.min(0, low), Math.max(0, high)];
210
209
  const sideways = this.direction === 'horizontal';
211
- const scale = sideways ? linear(domain, [0, HEIGHT]) : linear(domain, [HEIGHT, 0]);
210
+ const scale = sideways ? linear(domain, [0, PLOT_HEIGHT]) : linear(domain, [PLOT_HEIGHT, 0]);
212
211
  const ends = values.map((one) => scale(one));
213
212
  const far = sideways ? Math.max(...ends) : Math.min(...ends);
214
213
  const slot = 100 / this.readings;
@@ -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, slotFilled } from '@fusion.dev/helix-core';
7
+ import { customElement, HelixElement, markRegion } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  let HelixHeadlessBlockquote = class HelixHeadlessBlockquote extends HelixElement {
@@ -13,10 +13,7 @@ let HelixHeadlessBlockquote = class HelixHeadlessBlockquote extends HelixElement
13
13
  this.cite = '';
14
14
  }
15
15
  markEmpty() {
16
- const footer = this.partNamed('attribution');
17
- const empty = !slotFilled(this.renderRoot?.querySelector('slot[name="attribution"]'));
18
- footer?.toggleAttribute('data-empty', empty);
19
- footer?.toggleAttribute('hidden', empty);
16
+ markRegion(this.renderRoot, 'attribution', ['attribution']);
20
17
  }
21
18
  updated(changed) {
22
19
  super.updated(changed);
@@ -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, HelixSendEvent, hostAria, slotFilled, } from '@fusion.dev/helix-core';
7
+ import { customElement, HelixElement, HelixSendEvent, hostAria, markRegion, } from '@fusion.dev/helix-core';
8
8
  import { arrowElbowDownLeft } from '@fusion.dev/helix-icons';
9
9
  import { html } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
@@ -56,10 +56,7 @@ let HelixHeadlessChatComposer = class HelixHeadlessChatComposer extends HelixEle
56
56
  }
57
57
  markEmpty() {
58
58
  for (const name of ['start', 'end']) {
59
- const region = this.renderRoot?.querySelector(`[part="${name}"]`);
60
- const empty = !slotFilled(this.renderRoot?.querySelector(`slot[name="${name}"]`));
61
- region?.toggleAttribute('data-empty', empty);
62
- region?.toggleAttribute('hidden', empty);
59
+ markRegion(this.renderRoot, name, [name]);
63
60
  }
64
61
  }
65
62
  updated(changed) {
@@ -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, HelixSendEvent, hostAria, relativeTime, requestChange, slotFilled, TONE_MARKS, } from '@fusion.dev/helix-core';
7
+ import { customElement, HelixElement, HelixSendEvent, hostAria, markRegion, relativeTime, requestChange, TONE_MARKS, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property, state } from 'lit/decorators.js';
10
10
  import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
@@ -85,16 +85,9 @@ let HelixHeadlessChatMessage = class HelixHeadlessChatMessage extends HelixEleme
85
85
  </span>`;
86
86
  }
87
87
  markEmpty() {
88
- for (const name of ['content', 'sources', 'actions']) {
89
- const region = this.renderRoot?.querySelector(`[part="${name}"]`);
90
- const slot = name === 'content'
91
- ? this.renderRoot?.querySelector('slot:not([name])')
92
- : this.renderRoot?.querySelector(`slot[name="${name}"]`);
93
- const empty = !slotFilled(slot);
94
- region?.toggleAttribute('data-empty', empty);
95
- if (name !== 'content') {
96
- region?.toggleAttribute('hidden', empty);
97
- }
88
+ markRegion(this.renderRoot, 'content', [''], { hide: false });
89
+ for (const name of ['sources', 'actions']) {
90
+ markRegion(this.renderRoot, name, [name]);
98
91
  }
99
92
  }
100
93
  updated(changed) {
@@ -208,7 +208,7 @@ let HelixHeadlessChat = class HelixHeadlessChat extends MixinCollection(HelixEle
208
208
  return [...(this.children ?? [])].filter((child) => child.getAttribute('slot') === name);
209
209
  }
210
210
  countRegions() {
211
- if (this.children === undefined || this.hydrating) {
211
+ if (!this.childrenKnown) {
212
212
  return;
213
213
  }
214
214
  const filled = new Set(REGIONS.filter((name) => this.given(name).length > 0));
@@ -61,7 +61,7 @@ let HelixHeadlessCheckbox = class HelixHeadlessCheckbox extends MixinControllerC
61
61
  <input
62
62
  part="control"
63
63
  type="checkbox"
64
- .checked=${this.checked}
64
+ .checked=${this.checked || nothing}
65
65
  .indeterminate=${this.indeterminate}
66
66
  ?disabled=${this.isDisabled}
67
67
  ?required=${this.required}
@@ -81,7 +81,7 @@ let HelixHeadlessCheckboxGroup = class HelixHeadlessCheckboxGroup extends MixinF
81
81
  super.willUpdate(changed);
82
82
  hostAria(this.internals, { label: this.label, role: 'group' });
83
83
  this.internals.ariaRequired = this.required ? 'true' : null;
84
- if (!this.seeded && this.children !== undefined && !this.hydrating) {
84
+ if (!this.seeded && this.childrenKnown) {
85
85
  this.seeded = true;
86
86
  if (this.value === '') {
87
87
  this.seed();
@@ -118,7 +118,7 @@ let HelixHeadlessColorInput = class HelixHeadlessColorInput extends MixinPickerF
118
118
  ? nothing
119
119
  : html `<input
120
120
  part="control"
121
- id="control"
121
+ id=${this.controlId}
122
122
  type="text"
123
123
  .value=${this.value}
124
124
  name=${this.name || nothing}
@@ -208,6 +208,22 @@ export declare class HelixHeadlessCombobox extends HelixHeadlessCombobox_base {
208
208
  * so a tag does not move out from under the pointer about to remove it.
209
209
  */
210
210
  private get chosen();
211
+ /**
212
+ * Whether a value is declared and this render cannot say what it is.
213
+ *
214
+ * **A placeholder is a statement, so a render that cannot read the words does
215
+ * not make it.** `value` is an attribute and a server has it; the words are
216
+ * the options' and the options are light-DOM children a server has none of.
217
+ * The field then came out empty with "Search owners" over it, which says
218
+ * nothing is chosen where the consumer had already said what is. Blank, it
219
+ * says nothing, keeps its box, and is filled in by the render after
220
+ * hydration, which is the answer `Select.Root.display` reaches for in the same
221
+ * position and for the same reason.
222
+ *
223
+ * The placeholder still stands where there is genuinely no value, which is
224
+ * what it is for and the one case a server can be sure of.
225
+ */
226
+ private get unresolved();
211
227
  /**
212
228
  * Whether the filter has left anything to choose.
213
229
  *
@@ -147,6 +147,9 @@ let HelixHeadlessCombobox = class HelixHeadlessCombobox extends MixinControllerO
147
147
  .filter((value) => known || words.has(value))
148
148
  .map((value) => ({ text: words.get(value) ?? value, value }));
149
149
  }
150
+ get unresolved() {
151
+ return this.values.length > 0 && !this.childrenKnown;
152
+ }
150
153
  get showing() {
151
154
  return (this.listbox?.options ?? []).some((option) => !option.hidden);
152
155
  }
@@ -295,7 +298,7 @@ let HelixHeadlessCombobox = class HelixHeadlessCombobox extends MixinControllerO
295
298
  : nothing}
296
299
  <input
297
300
  part="control"
298
- id="control"
301
+ id=${this.controlId}
299
302
  type="text"
300
303
  role="combobox"
301
304
  autocomplete=${this.autocomplete || nothing}
@@ -304,7 +307,7 @@ let HelixHeadlessCombobox = class HelixHeadlessCombobox extends MixinControllerO
304
307
  aria-autocomplete=${this.suggest ? 'both' : 'list'}
305
308
  aria-expanded=${this.open ? 'true' : 'false'}
306
309
  aria-describedby=${this.describedBy ?? nothing}
307
- placeholder=${(this.chosen.length > 0 ? '' : this.placeholder) || nothing}
310
+ placeholder=${(this.chosen.length > 0 || this.unresolved ? '' : this.placeholder) || nothing}
308
311
  ?disabled=${this.isDisabled}
309
312
  .value=${this.multiple || this.open ? this.suggestion || this.typed : this.word}
310
313
  @input=${this.handleInput}
@@ -473,8 +473,31 @@ export declare class HelixHeadlessDataTable extends HelixHeadlessDataTable_base
473
473
  * The mixin's own answer counts children with no `slot`, which for this
474
474
  * element is the columns. Left in place, a table with columns and no data
475
475
  * would call itself full.
476
+ *
477
+ * **`sourceKnown` is the half the mixin was keeping**, and overriding without
478
+ * it is what put "Nothing to show" over eight rows of files on every server
479
+ * render of this family's own preview. `MixinCollection.hasRows` answers true
480
+ * until the rows have been counted for exactly that reason, and a count is
481
+ * what this replaced.
476
482
  */
477
483
  protected get hasRows(): boolean;
484
+ /**
485
+ * Whether this render can tell a result with no rows from one not yet handed
486
+ * over.
487
+ *
488
+ * **Unknown is not empty, which is what an empty state must never say.** The
489
+ * rows are a property, and a property is set by whoever holds the element: a
490
+ * consumer binding it in a template has it on the server, and a consumer
491
+ * assigning it from a script has it only once that script has run, which is
492
+ * after the parse a server's markup ends at. A render that cannot see a
493
+ * document cannot tell the two apart, and a hydrating render must not look,
494
+ * its output having to be the server's.
495
+ *
496
+ * The same shape as `Heatmap.sourceKnown`, asked of a property rather than of
497
+ * a document, and `childrenKnown` is already both halves of the question: a
498
+ * server has no tree and a hydrating render may not read one.
499
+ */
500
+ private get sourceKnown();
478
501
  /**
479
502
  * Asks for a different order, and reports whether the request stood.
480
503
  *
@@ -569,16 +592,6 @@ export declare class HelixHeadlessDataTable extends HelixHeadlessDataTable_base
569
592
  * assigned nodes are not a slot's DOM children. Both verified in a browser.
570
593
  */
571
594
  private markEmpty;
572
- /**
573
- * One region, hidden when its slot holds nothing and nothing of ours fills it.
574
- *
575
- * `hidden` beside the `data-empty` mirror: `data-empty` is the hook the styled
576
- * sheet reads, and this tier ships no sheet, so an empty region left in the box
577
- * tree would be a gap a headless consumer cannot close without knowing to look
578
- * for it. The styled rule stays too, because that sheet sets `display` on these
579
- * parts and therefore outranks the UA's `[hidden]` one.
580
- */
581
- private markRegion;
582
595
  protected updated(changed: PropertyValues): void;
583
596
  /**
584
597
  * Tells each column which way it is ordered, if it is the one.
@@ -5,7 +5,7 @@ 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 { customElement, HelixElement, HelixFilterEvent, HelixNavigateEvent, HelixReorderEvent, HelixSelectEvent, HelixSortEvent, hostAria, inlineStep, MixinCollection, requestChange, slotFilled, VirtualizeController, } from '@fusion.dev/helix-core';
8
+ import { customElement, HelixElement, HelixFilterEvent, HelixNavigateEvent, HelixReorderEvent, HelixSelectEvent, HelixSortEvent, hostAria, inlineStep, MixinCollection, markRegion, requestChange, VirtualizeController, writtenList, } from '@fusion.dev/helix-core';
9
9
  import { nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
11
11
  import { repeat } from 'lit/directives/repeat.js';
@@ -262,7 +262,7 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
262
262
  }
263
263
  get orderedColumns() {
264
264
  const columns = this.tableColumns;
265
- const wanted = this.dragOrder ?? this.order.split(/[\s,]+/).filter(Boolean);
265
+ const wanted = this.dragOrder ?? writtenList(this.order);
266
266
  if (wanted.length === 0) {
267
267
  return columns;
268
268
  }
@@ -353,7 +353,10 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
353
353
  })).length;
354
354
  }
355
355
  get hasRows() {
356
- return this.view.available > 0;
356
+ return this.view.available > 0 || !this.sourceKnown;
357
+ }
358
+ get sourceKnown() {
359
+ return this.childrenKnown;
357
360
  }
358
361
  requestSort(next) {
359
362
  return requestChange({
@@ -516,14 +519,8 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
516
519
  }
517
520
  }
518
521
  markEmpty() {
519
- this.markRegion('toolbar', ['toolbar']);
520
- this.markRegion('footer', ['footer'], '[part="pagination"]');
521
- }
522
- markRegion(part, slots, occupied) {
523
- const region = this.renderRoot?.querySelector(`[part="${part}"]`);
524
- const empty = !slots.some((name) => slotFilled(this.renderRoot?.querySelector(`slot[name="${name}"]`))) && !(occupied && this.renderRoot?.querySelector(occupied));
525
- region?.toggleAttribute('data-empty', empty);
526
- region?.toggleAttribute('hidden', empty);
522
+ markRegion(this.renderRoot, 'toolbar', ['toolbar']);
523
+ markRegion(this.renderRoot, 'footer', ['footer'], { occupied: '[part="pagination"]' });
527
524
  }
528
525
  updated(changed) {
529
526
  super.updated(changed);
@@ -624,12 +621,14 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
624
621
  const template = this.template;
625
622
  const columns = this.shownColumns;
626
623
  const pagination = this.tag('pagination');
624
+ const counted = this.mode === 'server' ? Math.max(this.total, available) : matched;
625
+ const countKnown = counted > 0 || this.sourceKnown;
627
626
  return html `<div part="toolbar"><slot name="toolbar"></slot></div>
628
627
  <div
629
628
  part="table"
630
629
  role="rowgroup"
631
630
  aria-busy=${this.loading ? 'true' : nothing}
632
- aria-rowcount=${this.mode === 'server' ? Math.max(this.total, available) + 1 : matched + 1}
631
+ aria-rowcount=${countKnown ? counted + 1 : nothing}
633
632
  aria-colcount=${columns.length ? columns.length + (this.selectable ? 1 : 0) : nothing}
634
633
  style=${template ? `--data-table-template: ${template}` : nothing}
635
634
  @scroll=${this.handleScroll}
@@ -654,7 +653,7 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
654
653
  status: pagination-status"
655
654
  page=${this.page}
656
655
  page-size=${this.pageSize || 1}
657
- total=${matched}
656
+ total=${countKnown ? matched : nothing}
658
657
  @helix-navigate=${this.handleNavigate}
659
658
  ></${pagination}>`
660
659
  : nothing}
@@ -217,7 +217,7 @@ let HelixHeadlessDateInput = class HelixHeadlessDateInput extends MixinPickerEdi
217
217
  : nothing}
218
218
  <input
219
219
  part="control"
220
- id="control"
220
+ id=${this.controlId}
221
221
  type="text"
222
222
  .value=${this.display}
223
223
  name=${this.name || 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, slotFilled } from '@fusion.dev/helix-core';
7
+ import { customElement, HelixElement, markRegion } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  let HelixHeadlessDivider = class HelixHeadlessDivider extends HelixElement {
@@ -13,10 +13,7 @@ let HelixHeadlessDivider = class HelixHeadlessDivider extends HelixElement {
13
13
  this.announced = false;
14
14
  }
15
15
  markEmpty() {
16
- const region = this.partNamed('content');
17
- const empty = !slotFilled(this.renderRoot?.querySelector('slot'));
18
- region?.toggleAttribute('data-empty', empty);
19
- region?.toggleAttribute('hidden', empty);
16
+ markRegion(this.renderRoot, 'content', ['']);
20
17
  }
21
18
  updated(changed) {
22
19
  super.updated(changed);
@@ -4,12 +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 { CHART_PLOT, customElement, drawTooltip, HelixElement, MixinChartSource, seriesIndex, } from '@fusion.dev/helix-core';
7
+ import { CHART_PLOT, customElement, drawTooltip, HelixElement, MixinChartSource, PLOT_HEIGHT, PLOT_WIDTH, seriesIndex, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing, svg } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  import '../../tooltip/index.js';
11
- const BOX = 100;
12
- const CENTER = BOX / 2;
11
+ const CENTER = PLOT_WIDTH / 2;
13
12
  const EDGE = 40;
14
13
  const GAP = 9999;
15
14
  const THICKNESS = {
@@ -86,9 +85,9 @@ let HelixHeadlessDonutChart = class HelixHeadlessDonutChart extends MixinChartSo
86
85
  }
87
86
  get frame() {
88
87
  if (this.sweep !== 'half') {
89
- return `0 0 ${BOX} ${BOX}`;
88
+ return `0 0 ${PLOT_WIDTH} ${PLOT_HEIGHT}`;
90
89
  }
91
- return `0 ${CENTER - EDGE} ${BOX} ${EDGE + this.thickness / 2}`;
90
+ return `0 ${CENTER - EDGE} ${PLOT_WIDTH} ${EDGE + this.thickness / 2}`;
92
91
  }
93
92
  renderSegment(share, index) {
94
93
  const circumference = 2 * Math.PI * this.radius;
@@ -150,7 +149,7 @@ let HelixHeadlessDonutChart = class HelixHeadlessDonutChart extends MixinChartSo
150
149
  const edge = EDGE;
151
150
  const x = CENTER + Math.cos(angle) * edge;
152
151
  const top = this.sweep === 'half' ? CENTER - EDGE : 0;
153
- const tall = this.sweep === 'half' ? EDGE + this.thickness / 2 : BOX;
152
+ const tall = this.sweep === 'half' ? EDGE + this.thickness / 2 : PLOT_HEIGHT;
154
153
  const y = ((CENTER + Math.sin(angle) * edge - top) / tall) * 100;
155
154
  const turned = this.sweep === 'half' && this.pointer.mirrored;
156
155
  const across = Math.cos(angle) > 0 !== turned;
@@ -111,6 +111,24 @@ export declare class HelixHeadlessDrilldownItem extends HelixElement {
111
111
  private readonly readPage;
112
112
  /** Moves focus onto the row itself. */
113
113
  focusItem(): void;
114
+ /**
115
+ * Whether this is the row the set is on, which in a drill-down is where focus
116
+ * is.
117
+ *
118
+ * A radio group's selection is an answer it holds; a page of rows has no
119
+ * answer, only the row a reader has walked to, so focus is the whole of what
120
+ * "the current one" can mean here. This is `Menu.Item`'s reading word for
121
+ * word, and the two are one pattern: a menu that drills and a drill-down are
122
+ * the same rows through the same controller.
123
+ *
124
+ * Asked of the document rather than of the shadow root. `focusItem` puts
125
+ * focus on the control inside this row and the platform retargets it, so from
126
+ * outside the shadow tree `activeElement` is this element already and nothing
127
+ * has to walk a tree. `ShadowRoot.activeElement` is also the reading that does
128
+ * not survive happy-dom, which climbs `getRootNode()` to the top and then
129
+ * dereferences what it ran out of.
130
+ */
131
+ get selected(): boolean;
114
132
  /** Announces that this row was chosen. */
115
133
  select(): void;
116
134
  protected render(): TemplateResult | typeof nothing;
@@ -37,6 +37,9 @@ let HelixHeadlessDrilldownItem = class HelixHeadlessDrilldownItem extends HelixE
37
37
  focusItem() {
38
38
  this.partNamed('item')?.focus();
39
39
  }
40
+ get selected() {
41
+ return this.ownerDocument.activeElement === this;
42
+ }
40
43
  select() {
41
44
  if (this.disabled) {
42
45
  return;
@@ -1,4 +1,4 @@
1
- import { HelixElement } from '@fusion.dev/helix-core';
1
+ import { HelixElement, KeyboardNavigationController } from '@fusion.dev/helix-core';
2
2
  import { type PropertyValues, type TemplateResult } from 'lit';
3
3
  declare const HelixHeadlessDrilldown_base: (new (...args: any[]) => import("@fusion.dev/helix-core").CollectionElement) & typeof HelixElement;
4
4
  /**
@@ -73,6 +73,25 @@ export declare class HelixHeadlessDrilldown extends HelixHeadlessDrilldown_base
73
73
  private readonly drilldown;
74
74
  /** The rows on the page showing, which is what the keys move over. */
75
75
  private get items();
76
+ /**
77
+ * Moving between rows, which is `Menu`'s arrangement and not a second one.
78
+ *
79
+ * The horizontal pair is reserved rather than released: in a drill-down they
80
+ * are the platform's own bindings for drilling, along the line to go in and
81
+ * back along it to come out, so they are spoken for here rather than
82
+ * unhandled. `handleKeydown` below owes them a binding, which is what
83
+ * reserving one means, and it has them.
84
+ *
85
+ * `selects` false, because arriving at a row must not follow it. Moving and
86
+ * choosing are one act in a radio group and two here, which is the same
87
+ * reading `Menu` takes over the same rows.
88
+ *
89
+ * The origin is the selection, which for these rows is where focus is. `focus`
90
+ * is the wrong reading however right it looks: it also carries a rule that a
91
+ * press which did not come from the set moves nothing, and Down on a page a
92
+ * reader has just tabbed to is how they get onto the first row at all.
93
+ */
94
+ protected readonly keys: KeyboardNavigationController;
76
95
  /**
77
96
  * Puts the region's one tab stop on the first row of the page showing.
78
97
  *
@@ -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, DrilldownController, HelixElement, hostAria, inlineStep, MixinCollection, } from '@fusion.dev/helix-core';
7
+ import { customElement, DrilldownController, HelixElement, hostAria, inlineStep, KeyboardNavigationController, MixinCollection, } from '@fusion.dev/helix-core';
8
8
  import { caretLeft } from '@fusion.dev/helix-icons';
9
9
  import { html, nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
@@ -20,6 +20,11 @@ let HelixHeadlessDrilldown = class HelixHeadlessDrilldown extends MixinCollectio
20
20
  root: () => [...this.children],
21
21
  slot: 'page',
22
22
  });
23
+ this.keys = new KeyboardNavigationController(this, {
24
+ items: () => this.items,
25
+ reserved: () => ['ArrowLeft', 'ArrowRight'],
26
+ selects: () => false,
27
+ });
23
28
  this.handleFocusIn = (event) => {
24
29
  const item = this.drilldown.rows.find((row) => row.contains(event.target));
25
30
  if (item && !item.disabled) {
@@ -45,18 +50,6 @@ let HelixHeadlessDrilldown = class HelixHeadlessDrilldown extends MixinCollectio
45
50
  this.handleKeydown = (event) => {
46
51
  const items = this.items;
47
52
  const current = items.findIndex((item) => item.matches(':focus-within'));
48
- if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
49
- event.preventDefault();
50
- const step = event.key === 'ArrowDown' ? 1 : -1;
51
- const next = (current + step + items.length) % Math.max(items.length, 1);
52
- items[current === -1 ? 0 : next]?.focusItem();
53
- return;
54
- }
55
- if (event.key === 'Home' || event.key === 'End') {
56
- event.preventDefault();
57
- (event.key === 'Home' ? items[0] : items[items.length - 1])?.focusItem();
58
- return;
59
- }
60
53
  const along = inlineStep(this, event.key);
61
54
  if (along === -1 || event.key === 'Escape') {
62
55
  if (this.back()) {
@@ -179,7 +179,7 @@ let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(Hel
179
179
  >
180
180
  <input
181
181
  part="control"
182
- id="control"
182
+ id=${this.controlId}
183
183
  type="file"
184
184
  accept=${this.accept || nothing}
185
185
  capture=${this.capture ?? nothing}
@@ -191,7 +191,7 @@ let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(Hel
191
191
  @change=${this.handleChange}
192
192
  @input=${this.stopInner}
193
193
  />
194
- <label part="trigger" for="control">
194
+ <label part="trigger" for=${this.controlId}>
195
195
  <slot name="trigger">${this.triggerLabel}</slot>
196
196
  </label>
197
197
  </div>
@@ -334,7 +334,7 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
334
334
  ${this.renderTokenRow(html `<slot name="tags" @slotchange=${this.handleTokens}></slot>`, this.over)}
335
335
  <input
336
336
  part="control"
337
- id="control"
337
+ id=${this.controlId}
338
338
  type="text"
339
339
  .value=${this.value}
340
340
  name=${this.name || nothing}