@fusion.dev/helix-headless 0.52.1 → 0.53.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 (53) hide show
  1. package/custom-elements.json +668 -270
  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-group/root/checkbox-group-root.js +1 -1
  10. package/dist/color-input/root/color-input-root.js +1 -1
  11. package/dist/combobox/root/combobox-root.js +1 -1
  12. package/dist/data-table/root/data-table-root.d.ts +0 -10
  13. package/dist/data-table/root/data-table-root.js +4 -10
  14. package/dist/date-input/root/date-input-root.js +1 -1
  15. package/dist/divider/root/divider-root.js +2 -5
  16. package/dist/donut-chart/root/donut-chart-root.js +5 -6
  17. package/dist/drilldown/item/drilldown-item.d.ts +18 -0
  18. package/dist/drilldown/item/drilldown-item.js +3 -0
  19. package/dist/drilldown/root/drilldown-root.d.ts +20 -1
  20. package/dist/drilldown/root/drilldown-root.js +6 -13
  21. package/dist/file-input/root/file-input-root.js +2 -2
  22. package/dist/filter-input/root/filter-input-root.js +1 -1
  23. package/dist/heatmap/root/buckets.d.ts +2 -2
  24. package/dist/heatmap/root/buckets.js +3 -4
  25. package/dist/heatmap/root/heatmap-root.d.ts +0 -10
  26. package/dist/heatmap/root/heatmap-root.js +2 -8
  27. package/dist/input/root/input-root.js +1 -1
  28. package/dist/line-chart/root/line-chart-root.js +15 -17
  29. package/dist/listbox/root/listbox-root.js +1 -1
  30. package/dist/menu/item/menu-item.js +2 -2
  31. package/dist/notifications/notification/notification.d.ts +3 -9
  32. package/dist/notifications/notification/notification.js +3 -11
  33. package/dist/notifications/root/notifications-root.d.ts +0 -10
  34. package/dist/notifications/root/notifications-root.js +3 -9
  35. package/dist/number-input/root/number-input-root.js +2 -2
  36. package/dist/radio/root/radio-root.d.ts +4 -36
  37. package/dist/radio/root/radio-root.js +2 -27
  38. package/dist/scatter-chart/root/scatter-chart-root.js +10 -11
  39. package/dist/select/root/select-root.js +1 -1
  40. package/dist/slider/root/slider-root.js +1 -1
  41. package/dist/sparkline/root/sparkline-root.js +10 -12
  42. package/dist/switch/root/switch-root.js +2 -5
  43. package/dist/tabs/tab/tab.d.ts +5 -34
  44. package/dist/tabs/tab/tab.js +3 -28
  45. package/dist/textarea/root/textarea-root.js +1 -1
  46. package/dist/time-input/root/time-input-root.js +1 -1
  47. package/dist/toggle/root/toggle-root.d.ts +4 -40
  48. package/dist/toggle/root/toggle-root.js +2 -27
  49. package/dist/tree/item/tree-item.js +2 -2
  50. package/dist/video/root/video-root.js +7 -14
  51. package/package.json +5 -5
  52. package/types/jsx.d.ts +192 -74
  53. 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));
@@ -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}
@@ -295,7 +295,7 @@ let HelixHeadlessCombobox = class HelixHeadlessCombobox extends MixinControllerO
295
295
  : nothing}
296
296
  <input
297
297
  part="control"
298
- id="control"
298
+ id=${this.controlId}
299
299
  type="text"
300
300
  role="combobox"
301
301
  autocomplete=${this.autocomplete || nothing}
@@ -569,16 +569,6 @@ export declare class HelixHeadlessDataTable extends HelixHeadlessDataTable_base
569
569
  * assigned nodes are not a slot's DOM children. Both verified in a browser.
570
570
  */
571
571
  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
572
  protected updated(changed: PropertyValues): void;
583
573
  /**
584
574
  * 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
  }
@@ -516,14 +516,8 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
516
516
  }
517
517
  }
518
518
  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);
519
+ markRegion(this.renderRoot, 'toolbar', ['toolbar']);
520
+ markRegion(this.renderRoot, 'footer', ['footer'], { occupied: '[part="pagination"]' });
527
521
  }
528
522
  updated(changed) {
529
523
  super.updated(changed);
@@ -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}
@@ -29,9 +29,9 @@ export interface Reading {
29
29
  * painting it as the lowest bucket claims a reading that does not exist.
30
30
  */
31
31
  export declare const toneOf: (reading: Reading, thresholds: readonly number[], tones: readonly string[]) => string | undefined;
32
- /** A comma or space separated list of numbers, in the order written. */
32
+ /** A consumer's list of numbers, in the order written, with the rest dropped. */
33
33
  export declare const numbers: (source: string) => number[];
34
- /** A comma or space separated list of names, in the order written. */
34
+ /** A consumer's list of names, in the order written. */
35
35
  export declare const words: (source: string) => string[];
36
36
  /**
37
37
  * How many readings fit across a given width, and which ones they are.
@@ -1,3 +1,4 @@
1
+ import { writtenList } from '@fusion.dev/helix-core';
1
2
  export const toneOf = (reading, thresholds, tones) => {
2
3
  if (reading.tone) {
3
4
  return reading.tone;
@@ -13,12 +14,10 @@ export const toneOf = (reading, thresholds, tones) => {
13
14
  }
14
15
  return chosen;
15
16
  };
16
- export const numbers = (source) => source
17
- .split(/[\s,]+/)
18
- .filter((one) => one !== '')
17
+ export const numbers = (source) => writtenList(source)
19
18
  .map(Number)
20
19
  .filter((one) => Number.isFinite(one));
21
- export const words = (source) => source.split(/[\s,]+/).filter((one) => one !== '');
20
+ export const words = (source) => writtenList(source);
22
21
  export const fits = (total, width, minimum, gap, rows = 1) => {
23
22
  if (!Number.isFinite(width) || width <= 0 || minimum <= 0) {
24
23
  return total;
@@ -277,16 +277,6 @@ export declare class HelixHeadlessHeatmap extends HelixHeadlessHeatmap_base {
277
277
  * than the fill, and is false either way. Both verified in a browser.
278
278
  */
279
279
  private markEmpty;
280
- /**
281
- * One region, hidden when its slot holds nothing and nothing of ours fills it.
282
- *
283
- * `hidden` beside the `data-empty` mirror: `data-empty` is the hook the styled
284
- * sheet reads, and this tier ships no sheet, so an empty region left in the box
285
- * tree would be a gap a headless consumer cannot close without knowing to look
286
- * for it. The styled rule stays too, because that sheet sets `display` on these
287
- * parts and therefore outranks the UA's `[hidden]` one.
288
- */
289
- private markRegion;
290
280
  protected updated(changed: PropertyValues): void;
291
281
  protected render(): TemplateResult;
292
282
  /**
@@ -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, customElement, drawTooltip, HelixElement, HelixResizeEvent, hostAria, MixinChartSource, slotFilled, } from '@fusion.dev/helix-core';
7
+ import { CHART_PLOT, customElement, drawTooltip, HelixElement, HelixResizeEvent, hostAria, MixinChartSource, markRegion, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property, state } from 'lit/decorators.js';
10
10
  import '../../tooltip/index.js';
@@ -102,13 +102,7 @@ let HelixHeadlessHeatmap = class HelixHeadlessHeatmap extends MixinChartSource(H
102
102
  hostAria(this.internals, { label: this.label || 'Heatmap', role: 'img' });
103
103
  }
104
104
  markEmpty() {
105
- this.markRegion('footer', ['start', 'summary', 'end']);
106
- }
107
- markRegion(part, slots, occupied) {
108
- const region = this.renderRoot?.querySelector(`[part="${part}"]`);
109
- const empty = !slots.some((name) => slotFilled(this.renderRoot?.querySelector(`slot[name="${name}"]`))) && !(occupied && this.renderRoot?.querySelector(occupied));
110
- region?.toggleAttribute('data-empty', empty);
111
- region?.toggleAttribute('hidden', empty);
105
+ markRegion(this.renderRoot, 'footer', ['start', 'summary', 'end']);
112
106
  }
113
107
  updated(changed) {
114
108
  super.updated(changed);
@@ -125,7 +125,7 @@ let HelixHeadlessInput = class HelixHeadlessInput extends MixinControllerValue(M
125
125
  <slot name="start"></slot>
126
126
  <input
127
127
  part="control"
128
- id="control"
128
+ id=${this.controlId}
129
129
  .value=${this.value}
130
130
  type=${this.effectiveType}
131
131
  name=${this.name || nothing}