@fusion.dev/helix-headless 0.143.0 → 0.144.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 (191) hide show
  1. package/custom-elements.json +8551 -375
  2. package/dist/area-chart/root/area-chart-root.d.ts +12 -4
  3. package/dist/area-chart/root/area-chart-root.js +24 -7
  4. package/dist/avatar/root/avatar-root.d.ts +14 -0
  5. package/dist/avatar/root/avatar-root.js +23 -9
  6. package/dist/badge/root/badge-root.d.ts +11 -7
  7. package/dist/badge/root/badge-root.js +1 -1
  8. package/dist/bar-chart/root/bar-chart-root.d.ts +12 -3
  9. package/dist/bar-chart/root/bar-chart-root.js +17 -10
  10. package/dist/button/root/button-root.d.ts +47 -1
  11. package/dist/button/root/button-root.js +38 -4
  12. package/dist/callout/root/callout-root.d.ts +18 -1
  13. package/dist/callout/root/callout-root.js +15 -4
  14. package/dist/card/root/card-root.d.ts +30 -1
  15. package/dist/card/root/card-root.js +38 -5
  16. package/dist/checkbox/root/checkbox-root.js +2 -0
  17. package/dist/checkbox-group/root/checkbox-group-root.d.ts +53 -3
  18. package/dist/checkbox-group/root/checkbox-group-root.js +71 -22
  19. package/dist/code-block/lines.d.ts +13 -0
  20. package/dist/code-block/lines.js +2 -0
  21. package/dist/code-block/root/code-block-root.d.ts +30 -0
  22. package/dist/code-block/root/code-block-root.js +23 -2
  23. package/dist/color-input/root/color-input-root.d.ts +34 -1
  24. package/dist/color-input/root/color-input-root.js +23 -4
  25. package/dist/color-picker/channels.d.ts +26 -0
  26. package/dist/color-picker/channels.js +84 -0
  27. package/dist/color-picker/root/color-picker-root.d.ts +81 -5
  28. package/dist/color-picker/root/color-picker-root.js +103 -10
  29. package/dist/combobox/root/combobox-root.d.ts +38 -3
  30. package/dist/combobox/root/combobox-root.js +64 -2
  31. package/dist/command/root/command-root.d.ts +30 -4
  32. package/dist/command/root/command-root.js +33 -3
  33. package/dist/copy-button/from.d.ts +22 -0
  34. package/dist/copy-button/from.js +28 -0
  35. package/dist/copy-button/root/copy-button-root.d.ts +27 -1
  36. package/dist/copy-button/root/copy-button-root.js +43 -7
  37. package/dist/date-input/root/date-input-root.d.ts +73 -4
  38. package/dist/date-input/root/date-input-root.js +93 -43
  39. package/dist/date-picker/root/date-picker-root.d.ts +145 -1
  40. package/dist/date-picker/root/date-picker-root.js +239 -48
  41. package/dist/dialog/description.d.ts +9 -0
  42. package/dist/dialog/description.js +14 -0
  43. package/dist/dialog/root/dialog-root.d.ts +64 -1
  44. package/dist/dialog/root/dialog-root.js +49 -23
  45. package/dist/donut-chart/root/donut-chart-root.d.ts +25 -4
  46. package/dist/donut-chart/root/donut-chart-root.js +21 -6
  47. package/dist/drawer/root/drawer-root.d.ts +63 -2
  48. package/dist/drawer/root/drawer-root.js +95 -19
  49. package/dist/drawer/swipe-start.d.ts +9 -0
  50. package/dist/drawer/swipe-start.js +12 -0
  51. package/dist/element-events.generated.js +8 -5
  52. package/dist/empty/root/empty-root.d.ts +1 -1
  53. package/dist/file-input/root/file-input-root.d.ts +47 -0
  54. package/dist/file-input/root/file-input-root.js +60 -22
  55. package/dist/file-input/screen.d.ts +35 -0
  56. package/dist/file-input/screen.js +49 -0
  57. package/dist/filter-input/root/filter-input-root.d.ts +38 -4
  58. package/dist/filter-input/root/filter-input-root.js +67 -3
  59. package/dist/filter-input/suggest.d.ts +17 -0
  60. package/dist/filter-input/suggest.js +23 -0
  61. package/dist/format-date/options.d.ts +33 -0
  62. package/dist/format-date/options.js +42 -0
  63. package/dist/format-date/root/format-date-root.d.ts +51 -0
  64. package/dist/format-date/root/format-date-root.js +58 -38
  65. package/dist/heatmap/root/heatmap-root.d.ts +23 -6
  66. package/dist/heatmap/root/heatmap-root.js +24 -13
  67. package/dist/icon/index.d.ts +2 -0
  68. package/dist/icon/index.js +2 -0
  69. package/dist/icon/registry.d.ts +37 -0
  70. package/dist/icon/registry.js +83 -0
  71. package/dist/icon/root/icon-root.d.ts +30 -4
  72. package/dist/icon/root/icon-root.js +41 -6
  73. package/dist/icon/root/index.d.ts +2 -0
  74. package/dist/icon/root/index.js +2 -0
  75. package/dist/icon/set.d.ts +1 -0
  76. package/dist/icon/set.js +3 -0
  77. package/dist/image/root/image-root.d.ts +14 -0
  78. package/dist/image/root/image-root.js +15 -2
  79. package/dist/input/root/input-root.d.ts +11 -0
  80. package/dist/input/root/input-root.js +15 -0
  81. package/dist/kbd/root/kbd-root.d.ts +20 -13
  82. package/dist/kbd/root/kbd-root.js +35 -37
  83. package/dist/kbd/shortcut.d.ts +18 -0
  84. package/dist/kbd/shortcut.js +35 -0
  85. package/dist/legend/hidden-series.d.ts +8 -0
  86. package/dist/legend/hidden-series.js +13 -0
  87. package/dist/legend/root/legend-root.d.ts +56 -0
  88. package/dist/legend/root/legend-root.js +54 -5
  89. package/dist/lightbox/item/lightbox-item.d.ts +15 -0
  90. package/dist/lightbox/item/lightbox-item.js +10 -1
  91. package/dist/lightbox/root/lightbox-root.d.ts +61 -1
  92. package/dist/lightbox/root/lightbox-root.js +94 -4
  93. package/dist/line-chart/root/line-chart-root.d.ts +11 -3
  94. package/dist/line-chart/root/line-chart-root.js +19 -5
  95. package/dist/listbox/range.d.ts +17 -0
  96. package/dist/listbox/range.js +5 -0
  97. package/dist/listbox/root/listbox-root.d.ts +74 -3
  98. package/dist/listbox/root/listbox-root.js +87 -9
  99. package/dist/loader/busy.d.ts +17 -0
  100. package/dist/loader/busy.js +24 -0
  101. package/dist/loader/root/loader-root.d.ts +37 -0
  102. package/dist/loader/root/loader-root.js +52 -3
  103. package/dist/meter/root/meter-root.d.ts +7 -0
  104. package/dist/meter/root/meter-root.js +6 -3
  105. package/dist/notifications/feed-keys.d.ts +10 -0
  106. package/dist/notifications/feed-keys.js +14 -0
  107. package/dist/notifications/notification/notification.d.ts +17 -0
  108. package/dist/notifications/notification/notification.js +21 -2
  109. package/dist/notifications/root/notifications-root.d.ts +42 -0
  110. package/dist/notifications/root/notifications-root.js +60 -1
  111. package/dist/number-input/root/number-input-root.d.ts +47 -0
  112. package/dist/number-input/root/number-input-root.js +58 -22
  113. package/dist/object-inspector/root/object-inspector-root.d.ts +1 -0
  114. package/dist/object-inspector/root/object-inspector-root.js +1 -1
  115. package/dist/option/root/option-root.d.ts +54 -5
  116. package/dist/option/root/option-root.js +34 -5
  117. package/dist/pin-input/format.d.ts +7 -0
  118. package/dist/pin-input/format.js +16 -0
  119. package/dist/pin-input/root/pin-input-root.d.ts +21 -0
  120. package/dist/pin-input/root/pin-input-root.js +29 -2
  121. package/dist/popover/root/popover-root.d.ts +62 -4
  122. package/dist/popover/root/popover-root.js +53 -5
  123. package/dist/progress/root/progress-root.d.ts +7 -0
  124. package/dist/progress/root/progress-root.js +9 -3
  125. package/dist/qr-code/export.d.ts +31 -0
  126. package/dist/qr-code/export.js +10 -0
  127. package/dist/qr-code/root/qr-code-root.d.ts +26 -0
  128. package/dist/qr-code/root/qr-code-root.js +49 -2
  129. package/dist/radio/group/radio-group.d.ts +23 -0
  130. package/dist/radio/group/radio-group.js +18 -0
  131. package/dist/rating/root/rating-root.d.ts +25 -0
  132. package/dist/rating/root/rating-root.js +45 -3
  133. package/dist/scatter-chart/root/scatter-chart-root.d.ts +17 -3
  134. package/dist/scatter-chart/root/scatter-chart-root.js +25 -7
  135. package/dist/select/root/select-root.d.ts +24 -0
  136. package/dist/select/root/select-root.js +20 -6
  137. package/dist/skeleton/root/skeleton-root.d.ts +25 -0
  138. package/dist/skeleton/root/skeleton-root.js +17 -2
  139. package/dist/slider/root/slider-root.d.ts +39 -0
  140. package/dist/slider/root/slider-root.js +45 -9
  141. package/dist/stat/root/stat-root.d.ts +2 -0
  142. package/dist/stat/root/stat-root.js +1 -1
  143. package/dist/steps/step/step.d.ts +1 -0
  144. package/dist/steps/step/step.js +1 -1
  145. package/dist/switch/root/switch-root.d.ts +12 -2
  146. package/dist/switch/root/switch-root.js +6 -1
  147. package/dist/tag/group/tag-group.d.ts +107 -0
  148. package/dist/tag/group/tag-group.js +157 -0
  149. package/dist/tag/index.d.ts +1 -0
  150. package/dist/tag/index.js +1 -0
  151. package/dist/tag/root/tag-root.d.ts +60 -0
  152. package/dist/tag/root/tag-root.js +74 -18
  153. package/dist/tag/selection.d.ts +10 -0
  154. package/dist/tag/selection.js +12 -0
  155. package/dist/textarea/grow.d.ts +22 -0
  156. package/dist/textarea/grow.js +20 -0
  157. package/dist/textarea/root/textarea-root.d.ts +9 -0
  158. package/dist/textarea/root/textarea-root.js +9 -1
  159. package/dist/time-input/root/time-input-root.d.ts +44 -5
  160. package/dist/time-input/root/time-input-root.js +69 -39
  161. package/dist/time-picker/root/time-picker-root.d.ts +54 -8
  162. package/dist/time-picker/root/time-picker-root.js +121 -91
  163. package/dist/toast/hotkey.d.ts +22 -0
  164. package/dist/toast/hotkey.js +23 -0
  165. package/dist/toast/item/toast-item.d.ts +16 -1
  166. package/dist/toast/item/toast-item.js +41 -13
  167. package/dist/toast/root/toast-root.d.ts +42 -8
  168. package/dist/toast/root/toast-root.js +64 -4
  169. package/dist/toggle/group/toggle-group.d.ts +57 -15
  170. package/dist/toggle/group/toggle-group.js +69 -9
  171. package/dist/toggle/root/toggle-root.d.ts +12 -1
  172. package/dist/toggle/root/toggle-root.js +11 -3
  173. package/dist/tooltip/focus-target.d.ts +6 -0
  174. package/dist/tooltip/focus-target.js +9 -0
  175. package/dist/tooltip/root/tooltip-root.d.ts +42 -17
  176. package/dist/tooltip/root/tooltip-root.js +50 -9
  177. package/dist/tour/naming.d.ts +30 -0
  178. package/dist/tour/naming.js +10 -0
  179. package/dist/tour/root/tour-root.d.ts +35 -0
  180. package/dist/tour/root/tour-root.js +56 -5
  181. package/dist/tour/stop/tour-stop.d.ts +53 -8
  182. package/dist/tour/stop/tour-stop.js +45 -15
  183. package/dist/tour/wait.d.ts +11 -0
  184. package/dist/tour/wait.js +19 -0
  185. package/dist/trend/root/trend-root.d.ts +13 -0
  186. package/dist/trend/root/trend-root.js +14 -9
  187. package/lint.json +1 -1
  188. package/package.json +5 -5
  189. package/types/jsx.d.ts +4862 -254
  190. package/types/svelte.d.ts +1 -0
  191. package/types/vue.d.ts +1149 -67
@@ -44,9 +44,15 @@ declare const HelixHeadlessAreaChart_base: typeof HelixElement & (new (...args:
44
44
  * bands show each part's share. Use it to see which part grew. The default
45
45
  * shows whether the total grew.
46
46
  *
47
- * The drawing is `aria-hidden`. The text summary gives the latest reading's
48
- * total and each part's share of it, and the table beside the chart has every
49
- * number.
47
+ * The drawing is hidden from screen readers. The text summary gives the latest
48
+ * reading's total and each part's share of it, and the table beside the chart
49
+ * has every number.
50
+ *
51
+ * **The plot is read by key.** It takes focus, named by `label` or the caption.
52
+ * The left and right arrows move from reading to reading, Home and End go to
53
+ * the first and the last, and the up and down arrows move among the series of
54
+ * a reading. The tooltip follows, the `live` part says the reading in words,
55
+ * and Escape puts the tooltip away.
50
56
  *
51
57
  * Every numeric column is a part, so leave a total column out of the table the
52
58
  * chart reads, or it is counted twice. A negative reading is drawn as zero.
@@ -64,8 +70,9 @@ declare const HelixHeadlessAreaChart_base: typeof HelixElement & (new (...args:
64
70
  * @csspart label - One reading's name.
65
71
  * @csspart labels - The row of reading names.
66
72
  * @csspart line - The top edge of one part's band.
73
+ * @csspart live - The readout: a polite live region that says the reading the keys moved to. Hide it visually.
67
74
  * @csspart mark - The invisible point the tooltip is anchored to.
68
- * @csspart plot - The drawing.
75
+ * @csspart plot - The drawing, which takes focus to be read by key.
69
76
  * @csspart summary - The total and each part's share at the latest reading, written as a sentence for screen readers.
70
77
  * @csspart svg - The drawing surface the bands sit on.
71
78
  * @csspart tooltip - The bubble, forwarded from `Tooltip`.
@@ -77,6 +84,7 @@ declare const HelixHeadlessAreaChart_base: typeof HelixElement & (new (...args:
77
84
  * @csspart tooltip-arrow-top - The point while the bubble is above the reading, so on its bottom edge, beside `tooltip-arrow`.
78
85
  * @csspart tooltip-bottom - The bubble while it is below the reading, beside `tooltip`.
79
86
  * @csspart tooltip-cell - One column's name and value inside the tooltip.
87
+ * @csspart tooltip-cell-current - The cell the up and down arrows rest on, while they rest there. Set alongside `tooltip-cell`.
80
88
  * @csspart tooltip-label - The reading's own name inside the tooltip.
81
89
  * @csspart tooltip-left - The bubble while it is left of the reading, beside `tooltip`.
82
90
  * @csspart tooltip-name - A column's name inside the tooltip.
@@ -36,7 +36,11 @@ let HelixHeadlessAreaChart = class HelixHeadlessAreaChart extends MixinChartSour
36
36
  get stack() {
37
37
  const rows = this.sourceRows;
38
38
  const columns = this.plottedColumns;
39
- const values = columns.map((column) => rows.map((row) => {
39
+ const names = columns.map((column) => rows.find((row) => row.cells[column])?.cells[column]?.name ?? '');
40
+ const values = columns.map((column, depth) => rows.map((row) => {
41
+ if (!this.showsSeries(names[depth] ?? '')) {
42
+ return 0;
43
+ }
40
44
  const value = row.cells[column]?.value ?? 0;
41
45
  if (value < 0) {
42
46
  this.warnNegative();
@@ -59,7 +63,7 @@ let HelixHeadlessAreaChart = class HelixHeadlessAreaChart extends MixinChartSour
59
63
  return {
60
64
  columns,
61
65
  layers,
62
- names: columns.map((column) => rows.find((row) => row.cells[column])?.cells[column]?.name ?? ''),
66
+ names,
63
67
  totals,
64
68
  values,
65
69
  };
@@ -94,8 +98,17 @@ let HelixHeadlessAreaChart = class HelixHeadlessAreaChart extends MixinChartSour
94
98
  }
95
99
  renderPlot(stack, count, x, y, domain) {
96
100
  const order = stack.layers.map((_, depth) => depth).reverse();
97
- return html `<div aria-hidden="true" id=${CHART_PLOT} part="plot">
101
+ return html `<div
102
+ aria-describedby=${this.named ? 'helix-chart-summary' : nothing}
103
+ aria-label=${this.named || nothing}
104
+ aria-labelledby=${this.named ? nothing : 'helix-chart-summary'}
105
+ id=${CHART_PLOT}
106
+ part="plot"
107
+ role="application"
108
+ tabindex="0"
109
+ >
98
110
  <svg part="svg"
111
+ aria-hidden="true"
99
112
  data-mirror
100
113
  preserveAspectRatio="none"
101
114
  viewBox="0 0 ${PLOT_WIDTH} ${PLOT_HEIGHT}"
@@ -162,9 +175,13 @@ let HelixHeadlessAreaChart = class HelixHeadlessAreaChart extends MixinChartSour
162
175
  const last = rows.length - 1;
163
176
  const total = stack.totals[last] ?? 0;
164
177
  const format = formatNumbers(this.locale);
165
- const shares = stack.names.map((name, depth) => `${name} ${percentIn(this.locale).format(total > 0 ? (stack.values[depth]?.[last] ?? 0) / total : 0)}`);
178
+ const shares = stack.names.flatMap((name, depth) => this.showsSeries(name)
179
+ ? [
180
+ `${name} ${percentIn(this.locale).format(total > 0 ? (stack.values[depth]?.[last] ?? 0) / total : 0)}`,
181
+ ]
182
+ : []);
166
183
  const named = this.named ? `${this.named}, ` : '';
167
- return html `<p part="summary">
184
+ return html `<p id="helix-chart-summary" part="summary">
168
185
  ${named}${rows[last]?.label ?? ''}: ${this.totalLabel} ${format(total)}; ${shares.join(', ')}.
169
186
  </p>`;
170
187
  }
@@ -190,14 +207,14 @@ let HelixHeadlessAreaChart = class HelixHeadlessAreaChart extends MixinChartSour
190
207
  const at = this.pointer.at;
191
208
  const rows = this.tallied(this.sourceRows, stack);
192
209
  if (at < 0) {
193
- return drawTooltip(rows, at, this.tag('tooltip'));
210
+ return drawTooltip(rows, at, this.tag('tooltip'), undefined, undefined, this.pointer);
194
211
  }
195
212
  const slot = 100 / rows.length;
196
213
  return drawTooltip(rows, at, this.tag('tooltip'), {
197
214
  placement: 'top',
198
215
  x: `${at * slot + slot / 2}%`,
199
216
  y: `${y(stack.layers.at(-1)?.[at] ?? 0)}%`,
200
- }, (cell) => this.seriesOf(cell));
217
+ }, (cell) => this.seriesOf(cell), this.pointer);
201
218
  }
202
219
  };
203
220
  __decorate([
@@ -32,6 +32,17 @@ export declare const initialsOf: (name: string) => string;
32
32
  * @csspart initials - The initials shown when there is no usable image.
33
33
  */
34
34
  export declare class HelixHeadlessAvatar extends HelixElement {
35
+ /**
36
+ * How long, in milliseconds, a picture may take before the initials stand
37
+ * in for it while it loads.
38
+ *
39
+ * Absent keeps the picture's place empty until it arrives or fails. Set,
40
+ * the initials show once the wait is over and the picture
41
+ * replaces them when it arrives, so a slow connection shows who it is
42
+ * without a cached picture flashing initials first. Radix's `delayMs` is the
43
+ * same idea.
44
+ */
45
+ fallbackDelay?: number;
35
46
  /**
36
47
  * Name of the person or entity.
37
48
  *
@@ -65,10 +76,13 @@ export declare class HelixHeadlessAvatar extends HelixElement {
65
76
  * report waits for the event it reports, below.
66
77
  */
67
78
  private broken;
79
+ /** The wait `fallback-delay` asks for, which has run out for the picture loading now or not. */
80
+ private readonly wait;
68
81
  private readonly handleError;
69
82
  private readonly handleLoad;
70
83
  protected willUpdate(changed: PropertyValues): void;
71
84
  protected firstUpdated(changed: PropertyValues): void;
85
+ private startWait;
72
86
  protected updated(changed: PropertyValues): void;
73
87
  protected render(): TemplateResult;
74
88
  /**
@@ -4,8 +4,8 @@ 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 } from '@fusion.dev/helix-core';
8
- import { html } from 'lit';
7
+ import { customElement, DelayController, HelixElement, hostAria } from '@fusion.dev/helix-core';
8
+ import { html, nothing } from 'lit';
9
9
  import { property, state } from 'lit/decorators.js';
10
10
  export const initialsOf = (name) => {
11
11
  const words = name.trim().split(/\s+/).filter(Boolean);
@@ -24,6 +24,7 @@ let HelixHeadlessAvatar = class HelixHeadlessAvatar extends HelixElement {
24
24
  this.src = '';
25
25
  this.status = 'loading';
26
26
  this.broken = false;
27
+ this.wait = new DelayController(this);
27
28
  this.handleError = (event) => {
28
29
  this.broken = true;
29
30
  this.status = 'error';
@@ -64,8 +65,16 @@ let HelixHeadlessAvatar = class HelixHeadlessAvatar extends HelixElement {
64
65
  });
65
66
  }
66
67
  }
68
+ startWait() {
69
+ this.wait.start(this.fallbackDelay !== undefined && this.src !== ''
70
+ ? Math.max(this.fallbackDelay, 0)
71
+ : undefined);
72
+ }
67
73
  updated(changed) {
68
74
  super.updated(changed);
75
+ if (changed.has('src') || changed.has('fallbackDelay')) {
76
+ this.startWait();
77
+ }
69
78
  if (changed.has('status')) {
70
79
  if (this.status === 'loading') {
71
80
  this.removeAttribute('data-status');
@@ -77,13 +86,15 @@ let HelixHeadlessAvatar = class HelixHeadlessAvatar extends HelixElement {
77
86
  }
78
87
  render() {
79
88
  if (this.src !== '' && !this.broken) {
80
- return html `<img
81
- part="image"
82
- src=${this.src}
83
- alt=""
84
- @error=${this.handleError}
85
- @load=${this.handleLoad}
86
- />`;
89
+ const standIn = this.wait.elapsed && this.status === 'loading';
90
+ return html `${standIn ? this.renderInitials() : nothing}<img
91
+ part="image"
92
+ src=${this.src}
93
+ alt=""
94
+ ?hidden=${standIn}
95
+ @error=${this.handleError}
96
+ @load=${this.handleLoad}
97
+ />`;
87
98
  }
88
99
  return this.renderInitials();
89
100
  }
@@ -93,6 +104,9 @@ let HelixHeadlessAvatar = class HelixHeadlessAvatar extends HelixElement {
93
104
  </slot>`;
94
105
  }
95
106
  };
107
+ __decorate([
108
+ property({ attribute: 'fallback-delay', type: Number })
109
+ ], HelixHeadlessAvatar.prototype, "fallbackDelay", void 0);
96
110
  __decorate([
97
111
  property()
98
112
  ], HelixHeadlessAvatar.prototype, "name", void 0);
@@ -18,13 +18,17 @@ import { type TemplateResult } from 'lit';
18
18
  * fuller name, such as `4 unread`. A dot needs a `label`. Without one, the dot
19
19
  * is a colored circle that screen readers do not announce.
20
20
  *
21
- * **The count is not part of the wrapped control's name.** It is a status of
22
- * its own, read when it changes and when a reader moves to it, so focusing a
23
- * button inside a badge announces the button's name alone. Where the count
24
- * belongs in that name, write it into the control's own label, such as
25
- * `aria-label="Inbox, 8 unread"` on a button that reads "Inbox". The badge
26
- * cannot add it: a reference from the control to the count would point into
27
- * the badge's shadow root, which the platform does not allow.
21
+ * **A badge wrapped around a control is not part of the control's name.** It
22
+ * is a status of its own, read when it changes and when a reader moves to it,
23
+ * so focusing a button inside a badge announces the button's name alone. A
24
+ * reference from the control to the count would point into the badge's shadow
25
+ * root, which the platform does not allow.
26
+ *
27
+ * **Put the badge inside the control to have its count read with the
28
+ * control's name**, as Web Awesome and Shoelace do: `<button>Inbox
29
+ * <helix-badge value="8" label="8 unread"></helix-badge></button>` is the
30
+ * button "Inbox 8 unread", and stays current as the count changes. The styled
31
+ * tier's `placement="inline"` draws the mark in the line for that case.
28
32
  *
29
33
  * @since 0.1.0
30
34
  * @status stable
@@ -36,7 +36,7 @@ let HelixHeadlessBadge = class HelixHeadlessBadge extends HelixElement {
36
36
  data-blank=${blank ? '' : nothing}
37
37
  data-single=${text.length <= 1 ? '' : nothing}
38
38
  role="status"
39
- aria-label=${this.label !== '' && announced ? this.label : nothing}
39
+ aria-label=${announced ? this.label || text : nothing}
40
40
  >${blank ? html `<span aria-hidden="true">${text}</span>` : text}</span
41
41
  >`;
42
42
  }
@@ -15,11 +15,18 @@ declare const HelixHeadlessBarChart_base: typeof HelixElement & (new (...args: a
15
15
  * and 43px in a wide one, and SVG has no setting that keeps text at one size
16
16
  * the way `vector-effect` does for strokes.
17
17
  *
18
- * The table is the accessible representation, and the drawing is `aria-hidden`,
19
- * so screen reader users get the numbers themselves. The `summary` part names
18
+ * The table is the accessible representation, and the drawing is hidden from
19
+ * screen readers, so they get the numbers themselves. The `summary` part names
20
20
  * the chart, from `label` or the table's caption, and gives each series' highest
21
21
  * reading.
22
22
  *
23
+ * **The plot is read by key.** It takes focus, named by `label` or the caption.
24
+ * The arrows along the categories move from one to the next, which is left and
25
+ * right for columns and up and down for `orientation="horizontal"`. Home and
26
+ * End go to the first and the last, and the other pair of arrows moves among
27
+ * the series of a category. The tooltip follows, the `live` part says the
28
+ * reading in words, and Escape puts the tooltip away.
29
+ *
23
30
  * The component writes the custom properties below with numbers it computes,
24
31
  * and the stylesheet that draws the chart reads them. Do not set them yourself.
25
32
  *
@@ -41,8 +48,9 @@ declare const HelixHeadlessBarChart_base: typeof HelixElement & (new (...args: a
41
48
  * @csspart label - One category's name.
42
49
  * @csspart labels - The row of category names.
43
50
  * @csspart line - A series drawn as a line, named in `lines`.
51
+ * @csspart live - The readout: a polite live region that says the reading the keys moved to. Hide it visually.
44
52
  * @csspart mark - The invisible point the tooltip is anchored to.
45
- * @csspart plot - The drawing.
53
+ * @csspart plot - The drawing, which takes focus to be read by key.
46
54
  * @csspart summary - The chart's name and each series' highest reading, written as a sentence for screen readers. Hide it visually.
47
55
  * @csspart svg - The drawing surface the bars and lines sit on.
48
56
  * @csspart tick - One number on an axis.
@@ -55,6 +63,7 @@ declare const HelixHeadlessBarChart_base: typeof HelixElement & (new (...args: a
55
63
  * @csspart tooltip-arrow-top - The arrow while the bubble is above the reading, on the bubble's bottom edge. Set together with `tooltip-arrow`.
56
64
  * @csspart tooltip-bottom - The bubble while it is below the reading. Set together with `tooltip`.
57
65
  * @csspart tooltip-cell - One column's name and value inside the tooltip.
66
+ * @csspart tooltip-cell-current - The cell the series keys rest on, while they rest there. Set alongside `tooltip-cell`.
58
67
  * @csspart tooltip-label - The reading's category name inside the tooltip.
59
68
  * @csspart tooltip-left - The bubble while it is left of the reading. Set together with `tooltip`.
60
69
  * @csspart tooltip-name - A column's name inside the tooltip.
@@ -37,9 +37,10 @@ let HelixHeadlessBarChart = class HelixHeadlessBarChart extends MixinChartSource
37
37
  }
38
38
  const scaled = this.scaled;
39
39
  const own = (one) => scaled.has(one.name.trim().toLowerCase());
40
- const values = series.flatMap((one) => one.points.map((point) => point.value));
40
+ const shown = series.filter((one) => this.showsSeries(one.name));
41
+ const values = shown.flatMap((one) => one.points.map((point) => point.value));
41
42
  const on = (which) => {
42
- const found = series.filter(which).flatMap((one) => one.points.map((point) => point.value));
43
+ const found = shown.filter(which).flatMap((one) => one.points.map((point) => point.value));
43
44
  return found.length > 0 ? found : values.length > 0 ? values : [0];
44
45
  };
45
46
  const [low, high] = extent(scaled.size === 0 ? on(() => true) : on((one) => !own(one)));
@@ -72,7 +73,7 @@ let HelixHeadlessBarChart = class HelixHeadlessBarChart extends MixinChartSource
72
73
  return [`${one.name} ${top.label} ${text}`];
73
74
  });
74
75
  const named = this.named ? `${this.named}. ` : '';
75
- return html `<p part="summary">${named}${this.highestLabel}: ${highest.join('; ')}.</p>`;
76
+ return html `<p id="helix-chart-summary" part="summary">${named}${this.highestLabel}: ${highest.join('; ')}.</p>`;
76
77
  }
77
78
  renderPlot(series, categories, y, domain, floor, z) {
78
79
  const sideways = this.orientation === 'horizontal';
@@ -83,7 +84,7 @@ let HelixHeadlessBarChart = class HelixHeadlessBarChart extends MixinChartSource
83
84
  const against = (one) => z !== undefined && own(one) ? z : y;
84
85
  const columns = series
85
86
  .map((one, index) => ({ one, slots: byRow(one, categories.length), tone: index }))
86
- .filter(({ one }) => !lined.has(one.name.trim().toLowerCase()));
87
+ .filter(({ one }) => !lined.has(one.name.trim().toLowerCase()) && this.showsSeries(one.name));
87
88
  const stride = (slot * 0.72) / Math.max(columns.length, 1);
88
89
  const gap = columns.length > 1 ? stride * 0.08 : 0;
89
90
  const thickness = stride - gap;
@@ -113,11 +114,16 @@ let HelixHeadlessBarChart = class HelixHeadlessBarChart extends MixinChartSource
113
114
  />`;
114
115
  };
115
116
  return html `<div
116
- aria-hidden="true"
117
+ aria-describedby=${this.named ? 'helix-chart-summary' : nothing}
118
+ aria-label=${this.named || nothing}
119
+ aria-labelledby=${this.named ? nothing : 'helix-chart-summary'}
117
120
  id=${CHART_PLOT}
118
121
  part="plot"
122
+ role="application"
123
+ tabindex="0"
119
124
  >
120
125
  <svg part="svg"
126
+ aria-hidden="true"
121
127
  data-mirror
122
128
  preserveAspectRatio="none"
123
129
  viewBox="0 0 ${PLOT_WIDTH} ${PLOT_HEIGHT}"
@@ -152,7 +158,7 @@ let HelixHeadlessBarChart = class HelixHeadlessBarChart extends MixinChartSource
152
158
  : bar(point.value, index, depth, tone, against(one));
153
159
  })}
154
160
  </g>`)}
155
- ${series.map((one) => lined.has(one.name.trim().toLowerCase())
161
+ ${series.map((one) => lined.has(one.name.trim().toLowerCase()) && this.showsSeries(one.name)
156
162
  ? this.renderLine(one, series.indexOf(one), slot, against(one), sideways)
157
163
  : nothing)}
158
164
  </svg>
@@ -225,12 +231,13 @@ let HelixHeadlessBarChart = class HelixHeadlessBarChart extends MixinChartSource
225
231
  const at = this.pointer.at;
226
232
  const series = this.series;
227
233
  if (at < 0 || series.length === 0) {
228
- return drawTooltip(this.sourceRows, at, this.tag('tooltip'));
234
+ return drawTooltip(this.sourceRows, at, this.tag('tooltip'), undefined, undefined, this.pointer);
229
235
  }
230
- const values = series
236
+ const shown = series.filter((one) => this.showsSeries(one.name));
237
+ const values = shown
231
238
  .map((one) => byRow(one, this.readings)[at]?.value)
232
239
  .filter((one) => one !== undefined);
233
- const all = series.flatMap((one) => one.points.map((point) => point.value));
240
+ const all = shown.flatMap((one) => one.points.map((point) => point.value));
234
241
  const [low, high] = extent(all.length > 0 ? all : [0]);
235
242
  const domain = [Math.min(0, low), Math.max(0, high)];
236
243
  const sideways = this.orientation === 'horizontal';
@@ -243,7 +250,7 @@ let HelixHeadlessBarChart = class HelixHeadlessBarChart extends MixinChartSource
243
250
  placement: sideways ? (this.pointer.mirrored ? 'left' : 'right') : 'top',
244
251
  x: sideways ? `${far}%` : `${across}%`,
245
252
  y: sideways ? `${across}%` : `${far}%`,
246
- }, (cell) => this.seriesOf(cell));
253
+ }, (cell) => this.seriesOf(cell), this.pointer);
247
254
  }
248
255
  };
249
256
  __decorate([
@@ -1,5 +1,5 @@
1
1
  import { AriaForwardController, HelixFormElement } from '@fusion.dev/helix-core';
2
- import { type PropertyValues, type TemplateResult } from 'lit';
2
+ import { nothing, type PropertyValues, type TemplateResult } from 'lit';
3
3
  /**
4
4
  * @summary Triggers an action, submits a form, or navigates.
5
5
  *
@@ -29,6 +29,9 @@ import { type PropertyValues, type TemplateResult } from 'lit';
29
29
  * As the first submit button in its form, it is the form's default button:
30
30
  * Enter in a text field of the form presses it, as it presses a native one.
31
31
  *
32
+ * Set `pending` while the action a press started is still running. The button
33
+ * keeps focus and refuses presses, and a reader on it hears `pending-label`.
34
+ *
32
35
  * @since 0.1.0
33
36
  * @status stable
34
37
  * @category actions
@@ -39,7 +42,10 @@ import { type PropertyValues, type TemplateResult } from 'lit';
39
42
  * @slot start - Content before the label, usually an icon.
40
43
  *
41
44
  * @csspart button - The inner control.
45
+ * @csspart live - The live region that says `pending-label` when the button becomes pending under focus. It is rendered from the first time the button is pending, and the styled button clips it out of view.
42
46
  * @csspart text - The button's words, which the styled button trims to their capitals and baseline so the letters sit at the middle in any font.
47
+ *
48
+ * @cssstate pending - The button is pending: `pending` is set.
43
49
  */
44
50
  export declare class HelixHeadlessButton extends HelixFormElement {
45
51
  protected readonly aria: AriaForwardController;
@@ -157,6 +163,24 @@ export declare class HelixHeadlessButton extends HelixFormElement {
157
163
  * "button" and nothing else.
158
164
  */
159
165
  label: string;
166
+ /**
167
+ * Whether the action the button started is still running.
168
+ *
169
+ * A pending button keeps focus and stays in the tab order, and refuses every
170
+ * press: a click, Enter and Space, and Enter in a field of its form. The
171
+ * click does not reach your listeners. The inner control carries
172
+ * `aria-busy` and `aria-disabled`, and when the button becomes pending while
173
+ * it has focus, a reader hears `pending-label`. The styled button draws a
174
+ * turning ring over its words.
175
+ *
176
+ * `disabled` takes the button out of reach. Use `pending` for the wait after
177
+ * a press, so focus stays where the reader left it.
178
+ */
179
+ pending: boolean;
180
+ /**
181
+ * What a reader hears when the button becomes pending while it has focus.
182
+ */
183
+ pendingLabel: string;
160
184
  /**
161
185
  * What kind of panel this button opens, where it opens one: `menu` for a
162
186
  * `Menu`.
@@ -167,6 +191,27 @@ export declare class HelixHeadlessButton extends HelixFormElement {
167
191
  * reported collapsed and expanded and no popup.
168
192
  */
169
193
  popup?: 'dialog' | 'grid' | 'listbox' | 'menu' | 'tree';
194
+ /**
195
+ * The status region, rendered from the first time the button is pending, so
196
+ * a button that never waits carries no live region.
197
+ */
198
+ private readonly status;
199
+ /**
200
+ * Says `pendingLabel` when the button becomes pending under focus, and
201
+ * clears it when the wait ends.
202
+ *
203
+ * **Only under focus**, as React Aria announces it: the reader who pressed
204
+ * the button is the one waiting, and a button elsewhere on the page that
205
+ * starts waiting is not news to them. The region arrives empty and takes its
206
+ * words a frame later, since a region that arrives with its words is read
207
+ * inconsistently.
208
+ */
209
+ private sayPending;
210
+ /**
211
+ * @internal The live region a pending button speaks in, for a subclass that
212
+ * replaces the template, as `CopyButton` does.
213
+ */
214
+ protected renderLive(): TemplateResult | typeof nothing;
170
215
  /** Moves focus to the inner control, so the host is never the focus target. */
171
216
  static shadowRootOptions: ShadowRootInit;
172
217
  private readonly handleClick;
@@ -249,6 +294,7 @@ export declare class HelixHeadlessButton extends HelixFormElement {
249
294
  * fixes and would only appear on the browsers that work.
250
295
  */
251
296
  private sendCommand;
297
+ protected willUpdate(changed: PropertyValues): void;
252
298
  protected updated(changed: PropertyValues): void;
253
299
  /** @internal A submit button is what its form's implicit submission presses. */
254
300
  protected get submitsForm(): boolean;
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { AriaForwardController, customElement, elementById, HelixFallbackCommandEvent, HelixFormElement, supportsInvokers, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, customElement, drawStatus, elementById, HelixFallbackCommandEvent, HelixFormElement, StatusController, supportsInvokers, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  let HelixHeadlessButton = class HelixHeadlessButton extends HelixFormElement {
@@ -13,6 +13,7 @@ let HelixHeadlessButton = class HelixHeadlessButton extends HelixFormElement {
13
13
  this.aria = new AriaForwardController(this, {
14
14
  label: () => this.forwardsLabel,
15
15
  target: () => this.partNamed('button'),
16
+ unnamed: 'Give it words, a `label`, or an `aria-label`.',
16
17
  });
17
18
  this.command = '';
18
19
  this.commandFor = '';
@@ -23,7 +24,15 @@ let HelixHeadlessButton = class HelixHeadlessButton extends HelixFormElement {
23
24
  this.formNoValidate = false;
24
25
  this.formTarget = '';
25
26
  this.label = '';
26
- this.handleClick = () => {
27
+ this.pending = false;
28
+ this.pendingLabel = 'Loading';
29
+ this.status = new StatusController(this);
30
+ this.handleClick = (event) => {
31
+ if (this.pending) {
32
+ event.preventDefault();
33
+ event.stopImmediatePropagation();
34
+ return;
35
+ }
27
36
  this.linkCommand();
28
37
  this.sendCommand();
29
38
  if (this.type === 'button') {
@@ -53,7 +62,7 @@ let HelixHeadlessButton = class HelixHeadlessButton extends HelixFormElement {
53
62
  return;
54
63
  }
55
64
  event.preventDefault();
56
- if (!this.isDisabled) {
65
+ if (!this.isDisabled && !this.pending) {
57
66
  this.activate();
58
67
  }
59
68
  };
@@ -61,6 +70,16 @@ let HelixHeadlessButton = class HelixHeadlessButton extends HelixFormElement {
61
70
  get forwardsLabel() {
62
71
  return true;
63
72
  }
73
+ sayPending() {
74
+ const focused = this.renderRoot?.activeElement != null;
75
+ if (!focused && this.status.said === '') {
76
+ return;
77
+ }
78
+ this.status.say(this.pending && focused ? this.pendingLabel : '', { clear: false });
79
+ }
80
+ renderLive() {
81
+ return drawStatus(this.status);
82
+ }
64
83
  static { this.shadowRootOptions = {
65
84
  ...HelixFormElement.shadowRootOptions,
66
85
  delegatesFocus: true,
@@ -131,9 +150,16 @@ let HelixHeadlessButton = class HelixHeadlessButton extends HelixFormElement {
131
150
  }
132
151
  this.commandTarget()?.dispatchEvent(new HelixFallbackCommandEvent(this.command, this));
133
152
  }
153
+ willUpdate(changed) {
154
+ super.willUpdate(changed);
155
+ this.toggleState('pending', this.pending);
156
+ }
134
157
  updated(changed) {
135
158
  super.updated(changed);
136
159
  this.linkCommand();
160
+ if (changed.has('pending') && changed.get('pending') !== undefined) {
161
+ this.sayPending();
162
+ }
137
163
  }
138
164
  get submitsForm() {
139
165
  return this.type === 'submit';
@@ -168,6 +194,8 @@ let HelixHeadlessButton = class HelixHeadlessButton extends HelixFormElement {
168
194
  type="button"
169
195
  aria-expanded=${this.expanded === undefined ? nothing : String(this.expanded)}
170
196
  aria-haspopup=${this.popup ?? nothing}
197
+ aria-busy=${this.pending ? 'true' : nothing}
198
+ aria-disabled=${this.pending ? 'true' : nothing}
171
199
  aria-label=${this.label || nothing}
172
200
  command=${this.command || nothing}
173
201
  tabindex=${stop}
@@ -175,7 +203,7 @@ let HelixHeadlessButton = class HelixHeadlessButton extends HelixFormElement {
175
203
  @click=${this.handleClick}
176
204
  >
177
205
  ${label}
178
- </button>`;
206
+ </button>${this.renderLive()}`;
179
207
  }
180
208
  };
181
209
  __decorate([
@@ -208,6 +236,12 @@ __decorate([
208
236
  __decorate([
209
237
  property()
210
238
  ], HelixHeadlessButton.prototype, "label", void 0);
239
+ __decorate([
240
+ property({ type: Boolean })
241
+ ], HelixHeadlessButton.prototype, "pending", void 0);
242
+ __decorate([
243
+ property({ attribute: 'pending-label' })
244
+ ], HelixHeadlessButton.prototype, "pendingLabel", void 0);
211
245
  __decorate([
212
246
  property({ attribute: false })
213
247
  ], HelixHeadlessButton.prototype, "popup", void 0);
@@ -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 HelixHeadlessCallout_base: (new (...args: any[]) => import("@fusion.dev/helix-core").DismissibleElement) & typeof HelixElement;
3
4
  /**
4
5
  * @summary A passage set apart as a note, information, a warning, or an error.
5
6
  *
@@ -13,6 +14,10 @@ import { type PropertyValues, type TemplateResult } from 'lit';
13
14
  * and the default disappears with no extra check. The tone's name stays on the
14
15
  * region around it.
15
16
  *
17
+ * Set `dismissible` for a note a reader may put away, as Shoelace's `closable`
18
+ * alert does. The button fires `helix-dismiss` and the callout stays until a
19
+ * listener removes it, so the page decides whether it comes back.
20
+ *
16
21
  * A callout sets a passage apart inside a document and is read where it sits.
17
22
  * For page-level chrome above the masthead that is a live region and can be
18
23
  * dismissed, use `Announcement`.
@@ -24,14 +29,19 @@ import { type PropertyValues, type TemplateResult } from 'lit';
24
29
  * @documentation https://fusion.dev/helix/components/callout
25
30
  *
26
31
  * @slot - The callout's content.
32
+ * @slot dismiss-icon - Replaces the cross inside the dismiss button.
27
33
  * @slot heading - A heading above the content.
28
34
  * @slot icon - Replaces the icon chosen by `tone`.
29
35
  *
36
+ * @event {HelixDismissEvent} helix-dismiss - The dismiss button was pressed. The callout does not remove itself, so remove it in response. Cancelable.
37
+ *
30
38
  * @csspart content - The content region.
39
+ * @csspart dismiss - The button that dismisses the callout, shown when `dismissible` is set.
40
+ * @csspart dismiss-mark - The cross on the dismiss button, shown when the `dismiss-icon` slot is empty.
31
41
  * @csspart icon - The icon region, an image named for the tone.
32
42
  * @csspart icon-mark - The icon for the tone, shown when no icon is slotted.
33
43
  */
34
- export declare class HelixHeadlessCallout extends HelixElement {
44
+ export declare class HelixHeadlessCallout extends HelixHeadlessCallout_base {
35
45
  protected willUpdate(changed: PropertyValues): void;
36
46
  /**
37
47
  * What kind of attention the passage needs.
@@ -66,6 +76,12 @@ export declare class HelixHeadlessCallout extends HelixElement {
66
76
  content: string;
67
77
  viewBox: string;
68
78
  }, _label: string): TemplateResult;
79
+ /**
80
+ * The cross inside the dismiss button, behind a seam, for the reason
81
+ * `renderIcon` gives: the slot answers the page and the method the tier
82
+ * above.
83
+ */
84
+ protected renderDismissIcon(): TemplateResult;
69
85
  protected render(): TemplateResult;
70
86
  }
71
87
  declare global {
@@ -73,3 +89,4 @@ declare global {
73
89
  'helix-headless-callout': HelixHeadlessCallout;
74
90
  }
75
91
  }
92
+ export {};