@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
@@ -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}
@@ -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, extent, HelixElement, linear, linePath, MixinChartSource, seriesIndex, ticks, } from '@fusion.dev/helix-core';
7
+ import { CHART_PLOT, customElement, drawTooltip, extent, 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
  let HelixHeadlessLineChart = class HelixHeadlessLineChart extends MixinChartSource(HelixElement) {
14
12
  constructor() {
15
13
  super(...arguments);
@@ -36,8 +34,8 @@ let HelixHeadlessLineChart = class HelixHeadlessLineChart extends MixinChartSour
36
34
  return [
37
35
  {
38
36
  index: seriesIndex(depth),
39
- x: (x(one.points.length - 1) / WIDTH) * 100,
40
- y: (y(last.value) / HEIGHT) * 100,
37
+ x: (x(one.points.length - 1) / PLOT_WIDTH) * 100,
38
+ y: (y(last.value) / PLOT_HEIGHT) * 100,
41
39
  },
42
40
  ];
43
41
  });
@@ -49,9 +47,9 @@ let HelixHeadlessLineChart = class HelixHeadlessLineChart extends MixinChartSour
49
47
  return html `<div id=${CHART_PLOT} part="plot"></div>`;
50
48
  }
51
49
  const domain = this.range(series);
52
- const y = linear(domain, [HEIGHT, 0]);
53
- const slot = WIDTH / readings.length;
54
- const x = linear([0, Math.max(readings.length - 1, 1)], [slot / 2, WIDTH - slot / 2]);
50
+ const y = linear(domain, [PLOT_HEIGHT, 0]);
51
+ const slot = PLOT_WIDTH / readings.length;
52
+ const x = linear([0, Math.max(readings.length - 1, 1)], [slot / 2, PLOT_WIDTH - slot / 2]);
55
53
  return html `${this.renderPlot(series, x, y, domain)} ${this.renderLabels(readings)}`;
56
54
  }
57
55
  renderPlot(series, x, y, domain) {
@@ -71,13 +69,13 @@ let HelixHeadlessLineChart = class HelixHeadlessLineChart extends MixinChartSour
71
69
  <svg part="svg"
72
70
  data-mirror
73
71
  preserveAspectRatio="none"
74
- viewBox="0 0 ${WIDTH} ${HEIGHT}"
72
+ viewBox="0 0 ${PLOT_WIDTH} ${PLOT_HEIGHT}"
75
73
  xmlns="http://www.w3.org/2000/svg"
76
74
  >
77
75
  ${this.renderGridlines(x, y, domain, series[0]?.points.length ?? 0)}
78
76
  ${floor === undefined
79
77
  ? nothing
80
- : svg `<line part="baseline" x1="0" x2=${WIDTH} y1=${floor} y2=${floor} />`}
78
+ : svg `<line part="baseline" x1="0" x2=${PLOT_WIDTH} y1=${floor} y2=${floor} />`}
81
79
  ${this.renderBand(series[0]?.points.length ?? 0)}
82
80
  ${this.renderGuide(x)}
83
81
  ${series.map((one, depth) => this.renderSeries(one, depth, x, y))}
@@ -91,10 +89,10 @@ let HelixHeadlessLineChart = class HelixHeadlessLineChart extends MixinChartSour
91
89
  return nothing;
92
90
  }
93
91
  const down = this.rulesCategory
94
- ? Array.from({ length: count }, (_, index) => svg `<line part="gridline" x1=${x(index)} x2=${x(index)} y1="0" y2=${HEIGHT} />`)
92
+ ? Array.from({ length: count }, (_, index) => svg `<line part="gridline" x1=${x(index)} x2=${x(index)} y1="0" y2=${PLOT_HEIGHT} />`)
95
93
  : [];
96
94
  const across = this.rulesValue
97
- ? ticks(domain).map((level) => svg `<line part="gridline" x1="0" x2=${WIDTH} y1=${y(level)} y2=${y(level)} />`)
95
+ ? ticks(domain).map((level) => svg `<line part="gridline" x1="0" x2=${PLOT_WIDTH} y1=${y(level)} y2=${y(level)} />`)
98
96
  : [];
99
97
  return svg `${down}${across}`;
100
98
  }
@@ -103,15 +101,15 @@ let HelixHeadlessLineChart = class HelixHeadlessLineChart extends MixinChartSour
103
101
  if (at < 0 || count === 0) {
104
102
  return nothing;
105
103
  }
106
- const slot = WIDTH / count;
107
- return svg `<rect height=${HEIGHT} part="band" width=${slot} x=${at * slot} y="0" />`;
104
+ const slot = PLOT_WIDTH / count;
105
+ return svg `<rect height=${PLOT_HEIGHT} part="band" width=${slot} x=${at * slot} y="0" />`;
108
106
  }
109
107
  renderGuide(x) {
110
108
  const at = this.pointer.at;
111
109
  if (at < 0) {
112
110
  return nothing;
113
111
  }
114
- return svg `<line part="guide" x1=${x(at)} x2=${x(at)} y1="0" y2=${HEIGHT} />`;
112
+ return svg `<line part="guide" x1=${x(at)} x2=${x(at)} y1="0" y2=${PLOT_HEIGHT} />`;
115
113
  }
116
114
  renderTermini(ends) {
117
115
  if (ends.length === 0) {
@@ -132,7 +130,7 @@ let HelixHeadlessLineChart = class HelixHeadlessLineChart extends MixinChartSour
132
130
  const index = seriesIndex(depth);
133
131
  const first = points[0];
134
132
  const last = points.at(-1);
135
- const wash = `${path}L${last[0]},${HEIGHT}L${first[0]},${HEIGHT}Z`;
133
+ const wash = `${path}L${last[0]},${PLOT_HEIGHT}L${first[0]},${PLOT_HEIGHT}Z`;
136
134
  return svg `${this.area
137
135
  ? svg `<path
138
136
  d=${wash}
@@ -160,7 +158,7 @@ let HelixHeadlessLineChart = class HelixHeadlessLineChart extends MixinChartSour
160
158
  return drawTooltip(this.sourceRows, at, this.tag('tooltip'));
161
159
  }
162
160
  const domain = this.range(series);
163
- const scale = linear(domain, [HEIGHT, 0]);
161
+ const scale = linear(domain, [PLOT_HEIGHT, 0]);
164
162
  const tops = series
165
163
  .map((one) => one.points[at]?.value)
166
164
  .filter((one) => one !== undefined)
@@ -305,7 +305,7 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
305
305
  super.willUpdate(changed);
306
306
  hostAria(this.internals, { label: this.label, role: 'listbox' });
307
307
  this.internals.ariaMultiSelectable = this.multiple ? 'true' : null;
308
- if (this.children !== undefined && !this.hydrating) {
308
+ if (this.childrenKnown) {
309
309
  this.place();
310
310
  }
311
311
  }
@@ -58,10 +58,10 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
58
58
  }
59
59
  willUpdate(changed) {
60
60
  super.willUpdate(changed);
61
- if (this.children === undefined || this.hydrating) {
61
+ if (!this.childrenKnown) {
62
62
  return;
63
63
  }
64
- const named = (name) => [...this.children].some((child) => child.slot === name);
64
+ const named = (name) => this.knownChildren.some((child) => child.slot === name);
65
65
  this.submenu = named('submenu');
66
66
  this.start = named('start');
67
67
  }
@@ -1,6 +1,7 @@
1
1
  import { HelixElement } from '@fusion.dev/helix-core';
2
2
  import { nothing, type PropertyValues, type TemplateResult } from 'lit';
3
3
  import '../../format-date/index.js';
4
+ declare const HelixHeadlessNotification_base: (new (...args: any[]) => import("@fusion.dev/helix-core").DismissibleElement) & typeof HelixElement;
4
5
  /**
5
6
  * @summary One thing that happened, which a reader may not have seen yet.
6
7
  *
@@ -66,7 +67,7 @@ import '../../format-date/index.js';
66
67
  * @cssstate controls - While it can be dismissed, so the time makes way for the control under a pointer.
67
68
  * @cssstate read - While it has been read.
68
69
  */
69
- export declare class HelixHeadlessNotification extends HelixElement {
70
+ export declare class HelixHeadlessNotification extends HelixHeadlessNotification_base {
70
71
  /**
71
72
  * When it happened, as anything `Date` parses.
72
73
  *
@@ -97,14 +98,6 @@ export declare class HelixHeadlessNotification extends HelixElement {
97
98
  * State to open with, where nothing declared one.
98
99
  */
99
100
  defaultRead: boolean;
100
- /**
101
- * Whether a reader may remove it.
102
- */
103
- dismissible: boolean;
104
- /**
105
- * Accessible name for the dismiss control.
106
- */
107
- dismissLabel: string;
108
101
  /**
109
102
  * Locale for the timestamp.
110
103
  *
@@ -212,3 +205,4 @@ declare global {
212
205
  'helix-headless-notification': HelixHeadlessNotification;
213
206
  }
214
207
  }
208
+ export {};
@@ -4,20 +4,18 @@ 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, HelixDismissEvent, HelixElement, HelixReadEvent, hostAria, requestChange, slotFilled, } from '@fusion.dev/helix-core';
7
+ import { customElement, HelixElement, HelixReadEvent, hostAria, MixinDismissible, requestChange, slotFilled, } from '@fusion.dev/helix-core';
8
8
  import { x } from '@fusion.dev/helix-icons';
9
9
  import { nothing } from 'lit';
10
10
  import { property } from 'lit/decorators.js';
11
11
  import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
12
12
  import { html } from 'lit/static-html.js';
13
13
  import '../../format-date/index.js';
14
- let HelixHeadlessNotification = class HelixHeadlessNotification extends HelixElement {
14
+ let HelixHeadlessNotification = class HelixHeadlessNotification extends MixinDismissible(HelixElement) {
15
15
  constructor() {
16
16
  super(...arguments);
17
17
  this.at = '';
18
18
  this.defaultRead = false;
19
- this.dismissible = false;
20
- this.dismissLabel = 'Dismiss';
21
19
  this.locale = '';
22
20
  this.isNotification = true;
23
21
  this.readState = undefined;
@@ -26,7 +24,7 @@ let HelixHeadlessNotification = class HelixHeadlessNotification extends HelixEle
26
24
  };
27
25
  this.handleDismiss = (event) => {
28
26
  event.stopPropagation();
29
- if (this.dispatchEvent(new HelixDismissEvent({ label: this.textContent?.trim() ?? '' }))) {
27
+ if (this.dismiss()) {
30
28
  this.remove();
31
29
  }
32
30
  };
@@ -128,12 +126,6 @@ __decorate([
128
126
  __decorate([
129
127
  property({ attribute: 'default-read', type: Boolean })
130
128
  ], HelixHeadlessNotification.prototype, "defaultRead", void 0);
131
- __decorate([
132
- property({ type: Boolean })
133
- ], HelixHeadlessNotification.prototype, "dismissible", void 0);
134
- __decorate([
135
- property({ attribute: 'dismiss-label' })
136
- ], HelixHeadlessNotification.prototype, "dismissLabel", void 0);
137
129
  __decorate([
138
130
  property()
139
131
  ], HelixHeadlessNotification.prototype, "locale", void 0);
@@ -201,16 +201,6 @@ export declare class HelixHeadlessNotifications extends HelixHeadlessNotificatio
201
201
  * which React reports on hydration and does not patch up.
202
202
  */
203
203
  private markEmpty;
204
- /**
205
- * One region, hidden when its slot holds nothing and nothing of ours fills it.
206
- *
207
- * `hidden` beside the `data-empty` mirror: `data-empty` is the hook the styled
208
- * sheet reads, and this tier ships no sheet, so an empty region left in the box
209
- * tree would be a gap a headless consumer cannot close without knowing to look
210
- * for it. The styled rule stays too, because that sheet sets `display` on these
211
- * parts and therefore outranks the UA's `[hidden]` one.
212
- */
213
- private markRegion;
214
204
  protected updated(changed: PropertyValues): void;
215
205
  protected render(): TemplateResult;
216
206
  private readonly handleMarkAll;
@@ -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, HelixReadEvent, hostAria, MixinCollection, slotFilled, } from '@fusion.dev/helix-core';
7
+ import { customElement, HelixElement, HelixReadEvent, hostAria, MixinCollection, markRegion, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  let HelixHeadlessNotifications = class HelixHeadlessNotifications extends MixinCollection(HelixElement) {
@@ -107,14 +107,8 @@ let HelixHeadlessNotifications = class HelixHeadlessNotifications extends MixinC
107
107
  this.toggleState('media', this.items.some((item) => item.querySelector(':scope > [slot="media"]') !== null));
108
108
  }
109
109
  markEmpty() {
110
- this.markRegion('header', ['header'], '[part="mark-all"]');
111
- this.markRegion('footer', ['footer']);
112
- }
113
- markRegion(part, slots, occupied) {
114
- const region = this.renderRoot?.querySelector(`[part="${part}"]`);
115
- const empty = !slots.some((name) => slotFilled(this.renderRoot?.querySelector(`slot[name="${name}"]`))) && !(occupied && this.renderRoot?.querySelector(occupied));
116
- region?.toggleAttribute('data-empty', empty);
117
- region?.toggleAttribute('hidden', empty);
110
+ markRegion(this.renderRoot, 'header', ['header'], { occupied: '[part="mark-all"]' });
111
+ markRegion(this.renderRoot, 'footer', ['footer']);
118
112
  }
119
113
  updated(changed) {
120
114
  super.updated(changed);
@@ -177,7 +177,7 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
177
177
  }
178
178
  return html `<label
179
179
  part="label"
180
- for="control"
180
+ for=${this.controlId}
181
181
  ?data-hidden=${hidden !== null}
182
182
  @pointercancel=${this.handleScrubEnd}
183
183
  @pointerdown=${this.handleScrubDown}
@@ -254,7 +254,7 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
254
254
  ${''}
255
255
  <input
256
256
  part="control"
257
- id="control"
257
+ id=${this.controlId}
258
258
  .value=${this.value}
259
259
  type="text"
260
260
  role="spinbutton"
@@ -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 HelixHeadlessRadio_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinSelectableItemInterface) & typeof HelixElement;
3
4
  /**
4
5
  * @summary One choice in a radio group.
5
6
  *
@@ -41,11 +42,7 @@ import { type PropertyValues, type TemplateResult } from 'lit';
41
42
  *
42
43
  * @cssstate selected - While it is the one chosen.
43
44
  */
44
- export declare class HelixHeadlessRadio extends HelixElement {
45
- /**
46
- * Whether this choice can be made.
47
- */
48
- disabled: boolean;
45
+ export declare class HelixHeadlessRadio extends HelixHeadlessRadio_base {
49
46
  /**
50
47
  * Whether the markup declared this one chosen, which is what a reset returns to.
51
48
  *
@@ -93,35 +90,15 @@ export declare class HelixHeadlessRadio extends HelixElement {
93
90
  */
94
91
  focusable: boolean;
95
92
  /**
96
- * Whether this is the current choice.
97
- *
98
- * Written by the group.
99
- */
100
- selected: boolean;
101
- /**
102
- * Identifies this choice to the group, and is what the form submits.
103
- */
104
- value: string;
105
- /** Moves focus to this choice, for the group's arrow keys. */
106
- focusItem(): void;
107
- /** Makes this choice and announces it. */
108
- select(): void;
109
- /**
110
- * Mirrors the chosen state where a stylesheet can select on it.
111
- *
112
- * **A custom state rather than the `selected` attribute.** The parent writes
113
- * this: a listbox choosing a row, a tab strip moving the tab, a radio group
114
- * answering for its set. Reflected, that puts the element's own bookkeeping
115
- * onto markup a consumer wrote and a consumer's server never sent, which
116
- * React reconciles and reports as a mismatch, and which a serialized element
117
- * pasted back declares as a choice nobody made. `ElementInternals` holds it
118
- * instead, where it is not in the DOM at all.
93
+ * Asks for the render that follows a hydration, so the stop stops being every
94
+ * choice's.
119
95
  *
120
- * Reading `selected` is unaffected and so is writing it: the attribute still
121
- * seeds the property, which is what `<helix-radio selected>` in a consumer's
122
- * markup means. There is no write back.
96
+ * `tabindex` reads `childrenKnown` and a hydrating render is one where that is
97
+ * false, which is what keeps the hydrating render the server's. Nothing else
98
+ * would ask: the group writes an already-false `focusable` onto every choice
99
+ * but one, which changes nothing and so re-renders nothing.
123
100
  */
124
- protected willUpdate(changed: PropertyValues): void;
101
+ protected firstUpdated(changed: PropertyValues): void;
125
102
  protected render(): TemplateResult;
126
103
  }
127
104
  declare global {
@@ -129,3 +106,4 @@ declare global {
129
106
  'helix-headless-radio': HelixHeadlessRadio;
130
107
  }
131
108
  }
109
+ 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 HelixHeadlessRadio = class HelixHeadlessRadio extends HelixElement {
10
+ let HelixHeadlessRadio = class HelixHeadlessRadio 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 HelixHeadlessRadio = class HelixHeadlessRadio 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,7 +29,7 @@ let HelixHeadlessRadio = class HelixHeadlessRadio 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
  <span part="box"><span part="mark" aria-hidden="true"></span></span>
@@ -50,18 +38,9 @@ let HelixHeadlessRadio = class HelixHeadlessRadio extends HelixElement {
50
38
  </button>`;
51
39
  }
52
40
  };
53
- __decorate([
54
- property({ reflect: true, type: Boolean })
55
- ], HelixHeadlessRadio.prototype, "disabled", void 0);
56
41
  __decorate([
57
42
  property({ attribute: false })
58
43
  ], HelixHeadlessRadio.prototype, "focusable", void 0);
59
- __decorate([
60
- property({ type: Boolean })
61
- ], HelixHeadlessRadio.prototype, "selected", void 0);
62
- __decorate([
63
- property()
64
- ], HelixHeadlessRadio.prototype, "value", void 0);
65
44
  HelixHeadlessRadio = __decorate([
66
45
  customElement('helix-headless-radio')
67
46
  ], HelixHeadlessRadio);
@@ -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, extent, formatNumbers, HelixElement, linear, MixinChartSource, seriesIndex, ticks, } from '@fusion.dev/helix-core';
7
+ import { CHART_PLOT, customElement, drawTooltip, extent, formatNumbers, HelixElement, linear, 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 REACH = 24;
14
12
  const coefficient = new Intl.NumberFormat(undefined, {
15
13
  maximumFractionDigits: 2,
@@ -62,7 +60,8 @@ let HelixHeadlessScatterChart = class HelixHeadlessScatterChart extends MixinCha
62
60
  let found;
63
61
  let best = REACH ** 2;
64
62
  for (const point of this.drawn) {
65
- const distance = ((point.x / WIDTH) * box.width - across) ** 2 + ((point.y / HEIGHT) * box.height - up) ** 2;
63
+ const distance = ((point.x / PLOT_WIDTH) * box.width - across) ** 2 +
64
+ ((point.y / PLOT_HEIGHT) * box.height - up) ** 2;
66
65
  if (distance <= best) {
67
66
  best = distance;
68
67
  found = point;
@@ -112,8 +111,8 @@ let HelixHeadlessScatterChart = class HelixHeadlessScatterChart extends MixinCha
112
111
  const all = pairs.flat();
113
112
  const x = nice(extent(all.length > 0 ? all.map(([one]) => one) : [0]));
114
113
  const y = nice(extent(all.length > 0 ? all.map(([, one]) => one) : [0]));
115
- const along = linear(x, [0, WIDTH]);
116
- const up = linear(y, [HEIGHT, 0]);
114
+ const along = linear(x, [0, PLOT_WIDTH]);
115
+ const up = linear(y, [PLOT_HEIGHT, 0]);
117
116
  return {
118
117
  pairs: pairs.map((series) => series.map(([one, two]) => [one, two])),
119
118
  points: pairs.flatMap((series, depth) => series.map(([one, two, row]) => ({ depth, row, x: along(one), y: up(two) }))),
@@ -132,8 +131,8 @@ let HelixHeadlessScatterChart = class HelixHeadlessScatterChart extends MixinCha
132
131
  </div>`;
133
132
  }
134
133
  const format = formatNumbers('');
135
- const along = linear(field.x, [0, WIDTH]);
136
- const up = linear(field.y, [HEIGHT, 0]);
134
+ const along = linear(field.x, [0, PLOT_WIDTH]);
135
+ const up = linear(field.y, [PLOT_HEIGHT, 0]);
137
136
  return html `<div dir="ltr" part="plane">
138
137
  ${this.renderPlot(field, along, up)}
139
138
  <div aria-hidden="true" part="x-axis">
@@ -152,14 +151,14 @@ let HelixHeadlessScatterChart = class HelixHeadlessScatterChart extends MixinCha
152
151
  return html `<div aria-hidden="true" id=${CHART_PLOT} part="plot">
153
152
  <svg part="svg"
154
153
  preserveAspectRatio="none"
155
- viewBox="0 0 ${WIDTH} ${HEIGHT}"
154
+ viewBox="0 0 ${PLOT_WIDTH} ${PLOT_HEIGHT}"
156
155
  xmlns="http://www.w3.org/2000/svg"
157
156
  >
158
157
  ${this.rulesX
159
- ? ticks(field.x).map((level) => svg `<line part="gridline" x1=${along(level)} x2=${along(level)} y1="0" y2=${HEIGHT} />`)
158
+ ? ticks(field.x).map((level) => svg `<line part="gridline" x1=${along(level)} x2=${along(level)} y1="0" y2=${PLOT_HEIGHT} />`)
160
159
  : nothing}
161
160
  ${this.rulesY
162
- ? ticks(field.y).map((level) => svg `<line part="gridline" x1="0" x2=${WIDTH} y1=${up(level)} y2=${up(level)} />`)
161
+ ? ticks(field.y).map((level) => svg `<line part="gridline" x1="0" x2=${PLOT_WIDTH} y1=${up(level)} y2=${up(level)} />`)
163
162
  : nothing}
164
163
  </svg>
165
164
  ${field.points.map((point) => {
@@ -134,6 +134,30 @@ export declare class HelixHeadlessSelect extends HelixHeadlessSelect_base {
134
134
  * `slotchange`, which a declarative shadow root fires before anything listens,
135
135
  * so a hydrated select kept "Choose" for good. Nothing on a server or while
136
136
  * hydrating what one sent, which drew the placeholder too.
137
+ *
138
+ * **And the placeholder is a statement, so a render that cannot read the
139
+ * words does not make it.** "Choose" means nothing is chosen. `value` is an
140
+ * attribute and a server has it, the words are the children's and a server
141
+ * has none, so a `<helix-select default-value="two">` served "Choose" over an
142
+ * answer the consumer had already given: false to a reader, and false to a
143
+ * crawler, on every select in the library's own examples. Where a value is
144
+ * declared and the word for it cannot be read, the trigger says nothing and
145
+ * keeps its box, which is the answer this library reaches for everywhere it
146
+ * cannot tell empty from unknown. `firstUpdated` asks for the render that
147
+ * fills it in.
148
+ *
149
+ * The placeholder still stands where there is genuinely no value, which is
150
+ * what it is for and the one case a server can be sure of.
151
+ *
152
+ * **`nothing` and never `''`, which is the difference between a word that
153
+ * arrives and one that never does.** Lit's hydration primes a child part with
154
+ * whatever the render computed, and `''` is a primitive, so `_commitText`
155
+ * takes the branch that assumes the server left a text node to overwrite.
156
+ * There is none, the write lands on the closing part marker's comment data,
157
+ * and the trigger stays empty for the life of the page: measured by
158
+ * `check:spec:dsd`, which caught all nine of these and showed a blank select
159
+ * beside a client's "Option Two". `nothing` primes the sentinel instead, so
160
+ * the commit creates the text node it needs.
137
161
  */
138
162
  private get display();
139
163
  /** Closes the list and puts focus back where it came from. */
@@ -74,7 +74,10 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
74
74
  const words = optionsAmong(this.knownChildren)
75
75
  .filter((option) => chosen.has(option.value))
76
76
  .map((option) => option.text);
77
- return words.length > 0 ? words.join(', ') : this.placeholder;
77
+ if (words.length > 0) {
78
+ return words.join(', ');
79
+ }
80
+ return chosen.size > 0 && !this.childrenKnown ? nothing : this.placeholder;
78
81
  }
79
82
  close() {
80
83
  if (!this.open) {
@@ -142,7 +145,7 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
142
145
  return html `${this.renderLabel()}
143
146
  <button
144
147
  part="trigger"
145
- id="control"
148
+ id=${this.controlId}
146
149
  type="button"
147
150
  aria-haspopup="listbox"
148
151
  aria-expanded=${this.open ? 'true' : 'false'}
@@ -146,7 +146,7 @@ let HelixHeadlessSlider = class HelixHeadlessSlider extends MixinControllerValue
146
146
  ? nothing
147
147
  : html `<input
148
148
  part="control"
149
- id="control"
149
+ id=${this.controlId}
150
150
  type="range"
151
151
  style="--slider-fraction: ${this.fraction}"
152
152
  min=${this.min}