@fusion.dev/helix-headless 0.142.1 → 0.143.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 (217) hide show
  1. package/custom-elements.json +16710 -707
  2. package/dist/accordion/item/accordion-item.d.ts +38 -2
  3. package/dist/accordion/item/accordion-item.js +38 -6
  4. package/dist/accordion/root/accordion-root.d.ts +23 -1
  5. package/dist/accordion/root/accordion-root.js +39 -3
  6. package/dist/announcement/root/announcement-root.d.ts +11 -7
  7. package/dist/announcement/root/announcement-root.js +1 -1
  8. package/dist/app-header/root/app-header-root.d.ts +21 -0
  9. package/dist/app-header/root/app-header-root.js +31 -5
  10. package/dist/area-chart/root/area-chart-root.js +16 -5
  11. package/dist/avatar/group/avatar-group.d.ts +24 -3
  12. package/dist/avatar/group/avatar-group.js +34 -2
  13. package/dist/avatar/root/avatar-root.d.ts +3 -1
  14. package/dist/avatar/root/avatar-root.js +8 -1
  15. package/dist/badge/root/badge-root.d.ts +8 -0
  16. package/dist/bar-chart/root/bar-chart-root.d.ts +19 -3
  17. package/dist/bar-chart/root/bar-chart-root.js +43 -13
  18. package/dist/blockquote/root/blockquote-root.d.ts +12 -7
  19. package/dist/blockquote/root/blockquote-root.js +6 -4
  20. package/dist/breadcrumbs/root/breadcrumbs-root.d.ts +9 -3
  21. package/dist/breadcrumbs/root/breadcrumbs-root.js +9 -1
  22. package/dist/button/root/button-root.d.ts +37 -0
  23. package/dist/button/root/button-root.js +44 -0
  24. package/dist/callout/root/callout-root.d.ts +14 -5
  25. package/dist/callout/root/callout-root.js +11 -3
  26. package/dist/canvas/root/canvas-root.d.ts +1 -0
  27. package/dist/canvas/root/canvas-root.js +10 -1
  28. package/dist/carousel/root/carousel-root.d.ts +47 -5
  29. package/dist/carousel/root/carousel-root.js +53 -21
  30. package/dist/chat/composer/chat-composer.d.ts +13 -11
  31. package/dist/chat/composer/chat-composer.js +6 -12
  32. package/dist/chat/message/chat-message.js +8 -1
  33. package/dist/chat/root/chat-root.d.ts +1 -0
  34. package/dist/chat/root/chat-root.js +2 -1
  35. package/dist/checkbox/root/checkbox-root.d.ts +7 -0
  36. package/dist/checkbox/root/checkbox-root.js +7 -3
  37. package/dist/checkbox-group/root/checkbox-group-root.d.ts +65 -32
  38. package/dist/checkbox-group/root/checkbox-group-root.js +85 -37
  39. package/dist/code/root/code-root.d.ts +1 -1
  40. package/dist/code-block/root/code-block-root.d.ts +19 -0
  41. package/dist/code-block/root/code-block-root.js +21 -1
  42. package/dist/color-input/root/color-input-root.d.ts +26 -1
  43. package/dist/color-input/root/color-input-root.js +32 -3
  44. package/dist/color-picker/root/color-picker-root.d.ts +37 -7
  45. package/dist/color-picker/root/color-picker-root.js +83 -6
  46. package/dist/combobox/root/combobox-root.d.ts +90 -2
  47. package/dist/combobox/root/combobox-root.js +160 -19
  48. package/dist/command/root/command-root.d.ts +17 -6
  49. package/dist/command/root/command-root.js +12 -1
  50. package/dist/copy-button/root/copy-button-root.d.ts +21 -6
  51. package/dist/copy-button/root/copy-button-root.js +11 -1
  52. package/dist/data-table/root/data-table-root.d.ts +164 -10
  53. package/dist/data-table/root/data-table-root.js +488 -57
  54. package/dist/date-input/root/date-input-root.d.ts +53 -8
  55. package/dist/date-input/root/date-input-root.js +70 -18
  56. package/dist/date-picker/root/date-picker-root.d.ts +84 -17
  57. package/dist/date-picker/root/date-picker-root.js +125 -20
  58. package/dist/dialog/root/dialog-root.d.ts +38 -10
  59. package/dist/dialog/root/dialog-root.js +55 -2
  60. package/dist/donut-chart/root/donut-chart-root.d.ts +39 -15
  61. package/dist/donut-chart/root/donut-chart-root.js +30 -10
  62. package/dist/drawer/root/drawer-root.d.ts +32 -7
  63. package/dist/drawer/root/drawer-root.js +50 -5
  64. package/dist/drilldown/item/drilldown-item.d.ts +16 -13
  65. package/dist/drilldown/item/drilldown-item.js +6 -2
  66. package/dist/drilldown/root/drilldown-root.d.ts +21 -5
  67. package/dist/drilldown/root/drilldown-root.js +26 -10
  68. package/dist/element-events.generated.js +35 -9
  69. package/dist/file-input/root/file-input-root.d.ts +18 -1
  70. package/dist/file-input/root/file-input-root.js +34 -8
  71. package/dist/filter-input/menu/filter-menu.d.ts +9 -0
  72. package/dist/filter-input/menu/filter-menu.js +8 -0
  73. package/dist/filter-input/root/filter-input-root.d.ts +5 -2
  74. package/dist/filter-input/root/filter-input-root.js +22 -7
  75. package/dist/format-date/root/format-date-root.d.ts +11 -3
  76. package/dist/format-date/root/format-date-root.js +26 -5
  77. package/dist/graph/root/graph-root.d.ts +24 -3
  78. package/dist/graph/root/graph-root.js +66 -12
  79. package/dist/heatmap/root/buckets.d.ts +2 -1
  80. package/dist/heatmap/root/buckets.js +5 -1
  81. package/dist/heatmap/root/heatmap-root.d.ts +27 -6
  82. package/dist/heatmap/root/heatmap-root.js +23 -6
  83. package/dist/icon/root/icon-root.d.ts +24 -3
  84. package/dist/icon/root/icon-root.js +34 -1
  85. package/dist/icon-button/root/icon-button-root.d.ts +1 -1
  86. package/dist/image/root/image-root.d.ts +9 -2
  87. package/dist/image/root/image-root.js +8 -2
  88. package/dist/input/root/input-root.d.ts +51 -9
  89. package/dist/input/root/input-root.js +46 -5
  90. package/dist/kbd/root/kbd-root.d.ts +12 -4
  91. package/dist/kbd/root/kbd-root.js +10 -11
  92. package/dist/legend/root/legend-root.d.ts +7 -0
  93. package/dist/legend/root/legend-root.js +7 -2
  94. package/dist/lightbox/item/lightbox-item.d.ts +2 -1
  95. package/dist/lightbox/root/lightbox-root.d.ts +16 -5
  96. package/dist/lightbox/root/lightbox-root.js +24 -3
  97. package/dist/line-chart/root/line-chart-root.d.ts +19 -2
  98. package/dist/line-chart/root/line-chart-root.js +40 -13
  99. package/dist/list/item/list-item.d.ts +11 -5
  100. package/dist/list/item/list-item.js +5 -6
  101. package/dist/listbox/root/listbox-root.d.ts +111 -2
  102. package/dist/listbox/root/listbox-root.js +144 -16
  103. package/dist/map/root/map-root.d.ts +19 -4
  104. package/dist/map/root/map-root.js +36 -7
  105. package/dist/megamenu/root/megamenu-root.d.ts +10 -1
  106. package/dist/megamenu/root/megamenu-root.js +10 -1
  107. package/dist/menu/item/menu-item.d.ts +41 -12
  108. package/dist/menu/item/menu-item.js +46 -3
  109. package/dist/menu/root/menu-root.d.ts +32 -2
  110. package/dist/menu/root/menu-root.js +48 -8
  111. package/dist/meter/root/meter-root.d.ts +6 -0
  112. package/dist/meter/root/meter-root.js +18 -2
  113. package/dist/navigation/root/navigation-root.d.ts +10 -2
  114. package/dist/navigation/root/navigation-root.js +13 -2
  115. package/dist/notifications/group/notification-group.d.ts +1 -0
  116. package/dist/notifications/notification/notification.d.ts +12 -0
  117. package/dist/notifications/notification/notification.js +31 -2
  118. package/dist/notifications/root/notifications-root.d.ts +7 -0
  119. package/dist/notifications/root/notifications-root.js +17 -2
  120. package/dist/number-input/root/number-input-root.d.ts +62 -7
  121. package/dist/number-input/root/number-input-root.js +68 -16
  122. package/dist/object-inspector/root/object-inspector-root.d.ts +48 -5
  123. package/dist/object-inspector/root/object-inspector-root.js +91 -11
  124. package/dist/option/root/option-root.d.ts +4 -1
  125. package/dist/option/root/option-root.js +1 -0
  126. package/dist/outline/root/outline-root.d.ts +13 -2
  127. package/dist/outline/root/outline-root.js +4 -2
  128. package/dist/pagination/root/pagination-root.d.ts +19 -1
  129. package/dist/pagination/root/pagination-root.js +49 -9
  130. package/dist/pagination/size/pagination-size.d.ts +13 -3
  131. package/dist/pagination/size/pagination-size.js +7 -0
  132. package/dist/pin-input/root/pin-input-root.d.ts +16 -0
  133. package/dist/pin-input/root/pin-input-root.js +19 -5
  134. package/dist/popover/root/popover-root.d.ts +19 -2
  135. package/dist/popover/root/popover-root.js +22 -1
  136. package/dist/progress/root/progress-root.d.ts +16 -1
  137. package/dist/progress/root/progress-root.js +31 -7
  138. package/dist/qr-code/root/qr-code-root.d.ts +19 -0
  139. package/dist/qr-code/root/qr-code-root.js +29 -1
  140. package/dist/radio/group/radio-group.d.ts +41 -0
  141. package/dist/radio/group/radio-group.js +50 -14
  142. package/dist/radio/root/radio-root.d.ts +8 -5
  143. package/dist/radio/root/radio-root.js +1 -2
  144. package/dist/rating/root/rating-root.d.ts +24 -1
  145. package/dist/rating/root/rating-root.js +24 -7
  146. package/dist/scatter-chart/root/scatter-chart-root.js +15 -7
  147. package/dist/select/root/select-root.d.ts +69 -3
  148. package/dist/select/root/select-root.js +92 -8
  149. package/dist/skeleton/root/skeleton-root.d.ts +14 -3
  150. package/dist/skeleton/root/skeleton-root.js +17 -3
  151. package/dist/skip-link/root/skip-link-root.d.ts +10 -0
  152. package/dist/skip-link/root/skip-link-root.js +15 -1
  153. package/dist/slider/root/slider-root.d.ts +20 -2
  154. package/dist/slider/root/slider-root.js +41 -3
  155. package/dist/sparkline/root/sparkline-root.d.ts +9 -10
  156. package/dist/speed-dial/item/speed-dial-item.d.ts +11 -5
  157. package/dist/speed-dial/item/speed-dial-item.js +7 -2
  158. package/dist/speed-dial/root/speed-dial-root.d.ts +15 -4
  159. package/dist/speed-dial/root/speed-dial-root.js +19 -5
  160. package/dist/splitter/root/splitter-root.d.ts +28 -6
  161. package/dist/splitter/root/splitter-root.js +35 -1
  162. package/dist/stat/root/stat-root.d.ts +7 -1
  163. package/dist/stat/root/stat-root.js +14 -1
  164. package/dist/steps/root/steps-root.js +1 -1
  165. package/dist/switch/root/switch-root.d.ts +2 -0
  166. package/dist/switch/root/switch-root.js +1 -0
  167. package/dist/table/cell/table-cell.d.ts +24 -4
  168. package/dist/table/cell/table-cell.js +21 -1
  169. package/dist/table/column/table-column.d.ts +32 -6
  170. package/dist/table/column/table-column.js +36 -6
  171. package/dist/table/root/table-root.d.ts +94 -3
  172. package/dist/table/root/table-root.js +128 -5
  173. package/dist/table/row/table-row.d.ts +7 -0
  174. package/dist/table/row/table-row.js +9 -1
  175. package/dist/tabs/root/tabs-root.d.ts +6 -1
  176. package/dist/tabs/root/tabs-root.js +6 -2
  177. package/dist/tag/root/tag-root.d.ts +23 -1
  178. package/dist/tag/root/tag-root.js +16 -2
  179. package/dist/textarea/root/textarea-root.d.ts +4 -0
  180. package/dist/textarea/root/textarea-root.js +1 -1
  181. package/dist/time-input/root/time-input-root.d.ts +28 -2
  182. package/dist/time-input/root/time-input-root.js +46 -10
  183. package/dist/time-picker/root/time-picker-root.d.ts +101 -18
  184. package/dist/time-picker/root/time-picker-root.js +115 -16
  185. package/dist/toast/root/toast-root.d.ts +64 -1
  186. package/dist/toast/root/toast-root.js +147 -2
  187. package/dist/toggle/group/toggle-group.d.ts +38 -1
  188. package/dist/toggle/group/toggle-group.js +34 -8
  189. package/dist/toggle/root/toggle-root.d.ts +14 -10
  190. package/dist/toggle/root/toggle-root.js +3 -3
  191. package/dist/toolbar/root/toolbar-root.d.ts +55 -15
  192. package/dist/toolbar/root/toolbar-root.js +99 -16
  193. package/dist/tooltip/root/tooltip-root.d.ts +37 -4
  194. package/dist/tooltip/root/tooltip-root.js +48 -0
  195. package/dist/tour/root/tour-root.d.ts +34 -5
  196. package/dist/tour/root/tour-root.js +51 -14
  197. package/dist/tour/stop/tour-stop.d.ts +18 -1
  198. package/dist/tour/stop/tour-stop.js +25 -5
  199. package/dist/tree/item/tree-item.d.ts +21 -5
  200. package/dist/tree/item/tree-item.js +14 -4
  201. package/dist/tree/root/tree-root.d.ts +29 -5
  202. package/dist/tree/root/tree-root.js +64 -8
  203. package/dist/trend/root/trend-root.d.ts +23 -1
  204. package/dist/trend/root/trend-root.js +28 -7
  205. package/dist/video/root/keys.d.ts +5 -0
  206. package/dist/video/root/keys.js +13 -1
  207. package/dist/video/root/video-root.d.ts +19 -0
  208. package/dist/video/root/video-root.js +48 -4
  209. package/dist/video-playlist/menu/video-playlist-menu.d.ts +20 -2
  210. package/dist/video-playlist/menu/video-playlist-menu.js +27 -5
  211. package/dist/video-playlist/root/video-playlist-root.js +6 -3
  212. package/dist/virtual-list/root/virtual-list-root.d.ts +52 -4
  213. package/dist/virtual-list/root/virtual-list-root.js +103 -21
  214. package/lint.json +1 -1
  215. package/package.json +5 -5
  216. package/types/jsx.d.ts +4348 -965
  217. package/types/vue.d.ts +972 -232
@@ -38,7 +38,7 @@ let HelixHeadlessAvatar = class HelixHeadlessAvatar extends HelixElement {
38
38
  super.willUpdate(changed);
39
39
  if (changed.has('src')) {
40
40
  this.broken = false;
41
- this.status = 'loading';
41
+ this.status = this.src === '' && this.hasUpdated ? 'error' : 'loading';
42
42
  }
43
43
  hostAria(this.internals, {
44
44
  label: this.name,
@@ -47,6 +47,13 @@ let HelixHeadlessAvatar = class HelixHeadlessAvatar extends HelixElement {
47
47
  }
48
48
  firstUpdated(changed) {
49
49
  super.firstUpdated(changed);
50
+ if (this.src === '') {
51
+ setTimeout(() => {
52
+ if (this.src === '' && this.status === 'loading') {
53
+ this.status = 'error';
54
+ }
55
+ });
56
+ }
50
57
  const image = this.serverRendered ? this.renderRoot.querySelector('img') : null;
51
58
  if (image?.complete) {
52
59
  const broken = image.naturalWidth === 0;
@@ -18,6 +18,14 @@ 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.
28
+ *
21
29
  * @since 0.1.0
22
30
  * @status stable
23
31
  * @category markers
@@ -16,7 +16,9 @@ declare const HelixHeadlessBarChart_base: typeof HelixElement & (new (...args: a
16
16
  * the way `vector-effect` does for strokes.
17
17
  *
18
18
  * The table is the accessible representation, and the drawing is `aria-hidden`,
19
- * so screen reader users get the numbers themselves.
19
+ * so screen reader users get the numbers themselves. The `summary` part names
20
+ * the chart, from `label` or the table's caption, and gives each series' highest
21
+ * reading.
20
22
  *
21
23
  * The component writes the custom properties below with numbers it computes,
22
24
  * and the stylesheet that draws the chart reads them. Do not set them yourself.
@@ -41,6 +43,7 @@ declare const HelixHeadlessBarChart_base: typeof HelixElement & (new (...args: a
41
43
  * @csspart line - A series drawn as a line, named in `lines`.
42
44
  * @csspart mark - The invisible point the tooltip is anchored to.
43
45
  * @csspart plot - The drawing.
46
+ * @csspart summary - The chart's name and each series' highest reading, written as a sentence for screen readers. Hide it visually.
44
47
  * @csspart svg - The drawing surface the bars and lines sit on.
45
48
  * @csspart tick - One number on an axis.
46
49
  * @csspart tooltip - The tooltip bubble, forwarded from `Tooltip`.
@@ -87,6 +90,10 @@ export declare class HelixHeadlessBarChart extends HelixHeadlessBarChart_base {
87
90
  * and reach no reader through the table.
88
91
  */
89
92
  gridlines?: 'both' | 'category' | 'value';
93
+ /**
94
+ * The word the summary puts before each series' highest reading.
95
+ */
96
+ highestLabel: string;
90
97
  /**
91
98
  * The names of the series to draw as lines. The other series are drawn as
92
99
  * columns.
@@ -121,8 +128,8 @@ export declare class HelixHeadlessBarChart extends HelixHeadlessBarChart_base {
121
128
  * turns on its side, and neither is as readable as putting them down the left
122
129
  * where they read normally.
123
130
  *
124
- * `orientation` and not `orientation`, which `Trend` uses in the same group for
125
- * which way a number moved.
131
+ * Named `orientation` because `direction` is taken: `Trend` uses it in the
132
+ * same group for which way a number moved.
126
133
  */
127
134
  orientation?: 'horizontal';
128
135
  /**
@@ -182,6 +189,15 @@ export declare class HelixHeadlessBarChart extends HelixHeadlessBarChart_base {
182
189
  */
183
190
  protected willUpdate(changed: PropertyValues<this>): void;
184
191
  protected render(): TemplateResult;
192
+ /**
193
+ * What a reader who cannot see the columns is told: the chart's name and
194
+ * each series' highest reading.
195
+ *
196
+ * The highest, because which category leads is the question a bar chart is
197
+ * read for, and the table beside it has every other number. The reading is
198
+ * written the way the table writes it, such as `$128k` for `128000`.
199
+ */
200
+ protected renderSummary(series: Series[]): TemplateResult;
185
201
  /**
186
202
  * The columns themselves, behind a seam the styled tier can move.
187
203
  *
@@ -4,11 +4,15 @@ 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, PLOT_HEIGHT, PLOT_WIDTH, seriesIndex, seriesNames, ticks, } from '@fusion.dev/helix-core';
7
+ import { byRow, CHART_PLOT, customElement, drawTooltip, extent, formatNumbers, HelixElement, linear, linePath, MixinChartSource, PLOT_HEIGHT, PLOT_WIDTH, seriesIndex, seriesNames, ticks, unbroken, } 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
11
  let HelixHeadlessBarChart = class HelixHeadlessBarChart extends MixinChartSource(HelixElement) {
12
+ constructor() {
13
+ super(...arguments);
14
+ this.highestLabel = 'Highest';
15
+ }
12
16
  get scaled() {
13
17
  return new Set(seriesNames(this.secondary));
14
18
  }
@@ -27,7 +31,7 @@ let HelixHeadlessBarChart = class HelixHeadlessBarChart extends MixinChartSource
27
31
  }
28
32
  render() {
29
33
  const series = this.series;
30
- const categories = series[0]?.points.map((point) => point.label) ?? [];
34
+ const categories = series.length === 0 ? [] : this.sourceRows.map((row) => row.label);
31
35
  if (categories.length === 0) {
32
36
  return html `<div id=${CHART_PLOT} part="plot"></div>`;
33
37
  }
@@ -49,12 +53,26 @@ let HelixHeadlessBarChart = class HelixHeadlessBarChart extends MixinChartSource
49
53
  const z = scaled.size > 0 ? linear(other, range) : undefined;
50
54
  const widest = z === undefined
51
55
  ? 0
52
- : Math.max(...[...ticks(domain), ...ticks(other)].map((level) => level.toLocaleString().length));
56
+ : Math.max(...[...ticks(domain), ...ticks(other)].map((level) => formatNumbers(this.locale)(level).length));
53
57
  const lone = series.filter(own);
54
58
  const tint = lone.length === 1 && lone[0] !== undefined
55
59
  ? `--bar-chart-secondary: var(--chart-series-${seriesIndex(series.indexOf(lone[0]))})`
56
60
  : undefined;
57
- return html `${z === undefined ? nothing : this.renderAxis(y, domain, 'primary', sideways, widest)}${this.renderPlot(series, categories, y, domain, floor, z)}${z === undefined ? nothing : this.renderAxis(z, other, 'secondary', sideways, widest, tint)}${this.renderLabels(categories)}`;
61
+ return html `${z === undefined ? nothing : this.renderAxis(y, domain, 'primary', sideways, widest)}${this.renderPlot(series, categories, y, domain, floor, z)}${z === undefined ? nothing : this.renderAxis(z, other, 'secondary', sideways, widest, tint)}${this.renderLabels(categories)}${this.renderSummary(series)}`;
62
+ }
63
+ renderSummary(series) {
64
+ const rows = this.sourceRows;
65
+ const columns = this.plottedColumns;
66
+ const highest = series.flatMap((one, depth) => {
67
+ const top = one.points.reduce((best, point) => (best === undefined || point.value > best.value ? point : best), undefined);
68
+ if (top === undefined) {
69
+ return [];
70
+ }
71
+ const text = rows[top.at]?.cells[columns[depth] ?? -1]?.text ?? String(top.value);
72
+ return [`${one.name} ${top.label} ${text}`];
73
+ });
74
+ const named = this.named ? `${this.named}. ` : '';
75
+ return html `<p part="summary">${named}${this.highestLabel}: ${highest.join('; ')}.</p>`;
58
76
  }
59
77
  renderPlot(series, categories, y, domain, floor, z) {
60
78
  const sideways = this.orientation === 'horizontal';
@@ -64,7 +82,7 @@ let HelixHeadlessBarChart = class HelixHeadlessBarChart extends MixinChartSource
64
82
  const own = (one) => scaled.has(one.name.trim().toLowerCase());
65
83
  const against = (one) => z !== undefined && own(one) ? z : y;
66
84
  const columns = series
67
- .map((one, index) => ({ one, tone: index }))
85
+ .map((one, index) => ({ one, slots: byRow(one, categories.length), tone: index }))
68
86
  .filter(({ one }) => !lined.has(one.name.trim().toLowerCase()));
69
87
  const stride = (slot * 0.72) / Math.max(columns.length, 1);
70
88
  const gap = columns.length > 1 ? stride * 0.08 : 0;
@@ -127,7 +145,12 @@ let HelixHeadlessBarChart = class HelixHeadlessBarChart extends MixinChartSource
127
145
  data-active=${this.pointer.at === index ? '' : nothing}
128
146
  part="group${this.pointer.at === index ? ' group-active' : ''}"
129
147
  >
130
- ${columns.map(({ one, tone }, depth) => bar(one.points[index]?.value ?? 0, index, depth, tone, against(one)))}
148
+ ${columns.map(({ slots, one, tone }, depth) => {
149
+ const point = slots[index];
150
+ return point === undefined
151
+ ? nothing
152
+ : bar(point.value, index, depth, tone, against(one));
153
+ })}
131
154
  </g>`)}
132
155
  ${series.map((one) => lined.has(one.name.trim().toLowerCase())
133
156
  ? this.renderLine(one, series.indexOf(one), slot, against(one), sideways)
@@ -147,7 +170,7 @@ let HelixHeadlessBarChart = class HelixHeadlessBarChart extends MixinChartSource
147
170
  ${ticks(domain).map((level) => html `<span
148
171
  part="tick"
149
172
  style=${sideways ? `inset-inline-start: ${scale(level)}%` : `inset-block-start: ${scale(level)}%`}
150
- >${level.toLocaleString()}</span
173
+ >${formatNumbers(this.locale)(level)}</span
151
174
  >`)}
152
175
  </div>`;
153
176
  }
@@ -184,13 +207,15 @@ let HelixHeadlessBarChart = class HelixHeadlessBarChart extends MixinChartSource
184
207
  </div>`;
185
208
  }
186
209
  renderLine(one, depth, slot, y, sideways) {
187
- const points = one.points.map((point, index) => {
188
- const across = index * slot + slot / 2;
210
+ const path = unbroken(one)
211
+ .map((run) => linePath(run.map((point) => {
212
+ const across = point.at * slot + slot / 2;
189
213
  const along = y(point.value);
190
214
  return sideways ? [along, across] : [across, along];
191
- });
215
+ }), 'straight'))
216
+ .join('');
192
217
  return svg `<path
193
- d=${linePath(points, 'straight')}
218
+ d=${path}
194
219
  data-series=${seriesIndex(depth)}
195
220
  part="line"
196
221
  style="--chart-series: var(--chart-series-${seriesIndex(depth)})"
@@ -202,13 +227,15 @@ let HelixHeadlessBarChart = class HelixHeadlessBarChart extends MixinChartSource
202
227
  if (at < 0 || series.length === 0) {
203
228
  return drawTooltip(this.sourceRows, at, this.tag('tooltip'));
204
229
  }
205
- const values = series.map((one) => one.points[at]?.value ?? 0);
230
+ const values = series
231
+ .map((one) => byRow(one, this.readings)[at]?.value)
232
+ .filter((one) => one !== undefined);
206
233
  const all = series.flatMap((one) => one.points.map((point) => point.value));
207
234
  const [low, high] = extent(all.length > 0 ? all : [0]);
208
235
  const domain = [Math.min(0, low), Math.max(0, high)];
209
236
  const sideways = this.orientation === 'horizontal';
210
237
  const scale = sideways ? linear(domain, [0, PLOT_HEIGHT]) : linear(domain, [PLOT_HEIGHT, 0]);
211
- const ends = values.map((one) => scale(one));
238
+ const ends = (values.length > 0 ? values : [0]).map((one) => scale(one));
212
239
  const far = sideways ? Math.max(...ends) : Math.min(...ends);
213
240
  const slot = 100 / this.readings;
214
241
  const across = at * slot + slot / 2;
@@ -222,6 +249,9 @@ let HelixHeadlessBarChart = class HelixHeadlessBarChart extends MixinChartSource
222
249
  __decorate([
223
250
  property()
224
251
  ], HelixHeadlessBarChart.prototype, "gridlines", void 0);
252
+ __decorate([
253
+ property({ attribute: 'highest-label' })
254
+ ], HelixHeadlessBarChart.prototype, "highestLabel", void 0);
225
255
  __decorate([
226
256
  property()
227
257
  ], HelixHeadlessBarChart.prototype, "lines", void 0);
@@ -4,9 +4,13 @@ import { type PropertyValues, type TemplateResult } from 'lit';
4
4
  * @summary A passage set off from the prose around it.
5
5
  *
6
6
  * @description
7
- * Renders a `<blockquote>` with a `<footer>` for the attribution, the structure
8
- * the HTML spec describes. The attribution sits outside the quoted text because
9
- * it is about the quotation.
7
+ * Renders a `<figure>` holding the `<blockquote>` and a `<figcaption>` for the
8
+ * attribution, the structure the HTML spec describes. The attribution sits
9
+ * outside the quotation because it is about the quotation, and it names the
10
+ * figure, so a screen reader reads the source as the figure's caption.
11
+ *
12
+ * The figure keeps the browser's default margins on this tier. Set
13
+ * `::part(figure) { margin: 0 }` where they are unwanted.
10
14
  *
11
15
  * For a quotation inside a sentence, use `Quote`.
12
16
  *
@@ -19,8 +23,9 @@ import { type PropertyValues, type TemplateResult } from 'lit';
19
23
  * @slot - The quoted passage.
20
24
  * @slot attribution - Who said it, and where.
21
25
  *
22
- * @csspart attribution - The footer carrying the source.
26
+ * @csspart attribution - The `<figcaption>` carrying the source.
23
27
  * @csspart blockquote - The quotation.
28
+ * @csspart figure - The `<figure>` holding the quotation and its attribution.
24
29
  */
25
30
  export declare class HelixHeadlessBlockquote extends HelixElement {
26
31
  /**
@@ -28,13 +33,13 @@ export declare class HelixHeadlessBlockquote extends HelixElement {
28
33
  */
29
34
  cite: string;
30
35
  /**
31
- * Says on the footer whether there is an attribution in it.
36
+ * Says on the caption whether there is an attribution in it.
32
37
  *
33
- * A `<footer>` with nothing in it is still a block in the quote's flow and
38
+ * A `<figcaption>` with nothing in it is still a block in the quote's flow and
34
39
  * still takes the container's gap, so every blockquote written without an
35
40
  * attribution reserved a full space below itself. There is no selector that
36
41
  * can ask, `slot:empty` being about a slot's fallback rather than its
37
- * assigned nodes, so the component asks and says so on the footer inside this
42
+ * assigned nodes, so the component asks and says so on the caption inside this
38
43
  * shadow root: an attribute on the host is a change to markup the consumer
39
44
  * rendered, which React reports on hydration.
40
45
  */
@@ -20,10 +20,12 @@ let HelixHeadlessBlockquote = class HelixHeadlessBlockquote extends HelixElement
20
20
  this.markEmpty();
21
21
  }
22
22
  render() {
23
- return html `<blockquote part="blockquote" cite=${this.cite || nothing}>
24
- <slot></slot>
25
- <footer part="attribution"><slot name="attribution"></slot></footer>
26
- </blockquote>`;
23
+ return html `<figure aria-labelledby="attribution" part="figure">
24
+ <blockquote part="blockquote" cite=${this.cite || nothing}><slot></slot></blockquote>
25
+ <figcaption id="attribution" part="attribution"
26
+ ><slot name="attribution" @slotchange=${() => this.markEmpty()}></slot
27
+ ></figcaption>
28
+ </figure>`;
27
29
  }
28
30
  };
29
31
  __decorate([
@@ -4,9 +4,10 @@ import { type PropertyValues, type TemplateResult } from 'lit';
4
4
  * @summary Shows the path to the current page.
5
5
  *
6
6
  * @description
7
- * The host is the navigation landmark, so an `aria-label` on it names the
8
- * trail. When a page has more than one navigation landmark, screen reader users
9
- * choose between them by name.
7
+ * The host is the navigation landmark, named "Breadcrumb" by default. An
8
+ * `aria-label` on the host names the trail instead, and so does `label`. When a
9
+ * page has more than one navigation landmark, screen reader users choose
10
+ * between them by name.
10
11
  *
11
12
  * The `<ol>` has an explicit `role="list"`, because Safari removes list
12
13
  * semantics from a list with `list-style: none`, and the trail always hides its
@@ -30,6 +31,11 @@ import { type PropertyValues, type TemplateResult } from 'lit';
30
31
  * @csspart list - The ordered list.
31
32
  */
32
33
  export declare class HelixHeadlessBreadcrumbs extends HelixElement {
34
+ /**
35
+ * The landmark's name, read by screen readers. An `aria-label` on the host
36
+ * wins over it.
37
+ */
38
+ label: string;
33
39
  protected willUpdate(changed: PropertyValues): void;
34
40
  protected render(): TemplateResult;
35
41
  }
@@ -6,10 +6,15 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
6
6
  };
7
7
  import { customElement, HelixElement, hostAria } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
+ import { property } from 'lit/decorators.js';
9
10
  let HelixHeadlessBreadcrumbs = class HelixHeadlessBreadcrumbs extends HelixElement {
11
+ constructor() {
12
+ super(...arguments);
13
+ this.label = 'Breadcrumb';
14
+ }
10
15
  willUpdate(changed) {
11
16
  super.willUpdate(changed);
12
- hostAria(this.internals, { role: 'navigation' });
17
+ hostAria(this.internals, { label: this.label, role: 'navigation' });
13
18
  }
14
19
  render() {
15
20
  return html `<ol part="list" role="list">
@@ -17,6 +22,9 @@ let HelixHeadlessBreadcrumbs = class HelixHeadlessBreadcrumbs extends HelixEleme
17
22
  </ol>`;
18
23
  }
19
24
  };
25
+ __decorate([
26
+ property()
27
+ ], HelixHeadlessBreadcrumbs.prototype, "label", void 0);
20
28
  HelixHeadlessBreadcrumbs = __decorate([
21
29
  customElement('helix-headless-breadcrumbs')
22
30
  ], HelixHeadlessBreadcrumbs);
@@ -21,6 +21,14 @@ import { type PropertyValues, type TemplateResult } from 'lit';
21
21
  * it. To submit, the button calls `requestSubmit()` on its form, which runs
22
22
  * validation, unlike `submit()`.
23
23
  *
24
+ * A `submit` listener reads `event.submitter` as `null`, because the platform
25
+ * accepts only a native submit button there. The button's `name` and `value`
26
+ * are in the form data, so read them from `new FormData(form)` to tell two
27
+ * submit buttons apart.
28
+ *
29
+ * As the first submit button in its form, it is the form's default button:
30
+ * Enter in a text field of the form presses it, as it presses a native one.
31
+ *
24
32
  * @since 0.1.0
25
33
  * @status stable
26
34
  * @category actions
@@ -35,6 +43,12 @@ import { type PropertyValues, type TemplateResult } from 'lit';
35
43
  */
36
44
  export declare class HelixHeadlessButton extends HelixFormElement {
37
45
  protected readonly aria: AriaForwardController;
46
+ /**
47
+ * @internal Whether a host `aria-label` names the inner button. A subclass
48
+ * that writes the name itself, as `CopyButton` does while it confirms a
49
+ * copy, says no.
50
+ */
51
+ protected get forwardsLabel(): boolean;
38
52
  /**
39
53
  * What to send when the button is activated, and what to send it to.
40
54
  *
@@ -236,6 +250,29 @@ export declare class HelixHeadlessButton extends HelixFormElement {
236
250
  */
237
251
  private sendCommand;
238
252
  protected updated(changed: PropertyValues): void;
253
+ /** @internal A submit button is what its form's implicit submission presses. */
254
+ protected get submitsForm(): boolean;
255
+ /** @internal Pressed as the form's default button, through the inner control. */
256
+ protected activate(): void;
257
+ /** The form whose Enter key this button listens to, as its default button. */
258
+ private watched;
259
+ /**
260
+ * Presses this button for Enter in a native text field of its form, when it
261
+ * is the form's default button.
262
+ *
263
+ * The platform cannot find a default button in a shadow root, so a form whose
264
+ * only submit button is this one found none: with two fields Enter did
265
+ * nothing, and with one it submitted without this button's name, value or
266
+ * overrides. The event's default is prevented so a later native submit button
267
+ * is not pressed instead.
268
+ */
269
+ private readonly pressOnEnter;
270
+ /** Listens to the form this button now belongs to, and to no other. */
271
+ private watchForm;
272
+ connectedCallback(): void;
273
+ disconnectedCallback(): void;
274
+ /** Called by the browser when the button's form changes. */
275
+ formAssociatedCallback(form: HTMLFormElement | null): void;
239
276
  protected render(): TemplateResult;
240
277
  }
241
278
  declare global {
@@ -11,6 +11,7 @@ let HelixHeadlessButton = class HelixHeadlessButton extends HelixFormElement {
11
11
  constructor() {
12
12
  super(...arguments);
13
13
  this.aria = new AriaForwardController(this, {
14
+ label: () => this.forwardsLabel,
14
15
  target: () => this.partNamed('button'),
15
16
  });
16
17
  this.command = '';
@@ -41,6 +42,24 @@ let HelixHeadlessButton = class HelixHeadlessButton extends HelixFormElement {
41
42
  };
42
43
  this.type = 'button';
43
44
  this.value = '';
45
+ this.watched = null;
46
+ this.pressOnEnter = (event) => {
47
+ const form = this.watched;
48
+ if (event.key !== 'Enter' ||
49
+ event.defaultPrevented ||
50
+ !form ||
51
+ !HelixFormElement.submitsOnEnter(event.composedPath()[0], form) ||
52
+ HelixFormElement.defaultButton(form) !== this) {
53
+ return;
54
+ }
55
+ event.preventDefault();
56
+ if (!this.isDisabled) {
57
+ this.activate();
58
+ }
59
+ };
60
+ }
61
+ get forwardsLabel() {
62
+ return true;
44
63
  }
45
64
  static { this.shadowRootOptions = {
46
65
  ...HelixFormElement.shadowRootOptions,
@@ -116,6 +135,31 @@ let HelixHeadlessButton = class HelixHeadlessButton extends HelixFormElement {
116
135
  super.updated(changed);
117
136
  this.linkCommand();
118
137
  }
138
+ get submitsForm() {
139
+ return this.type === 'submit';
140
+ }
141
+ activate() {
142
+ this.partNamed('button')?.click();
143
+ }
144
+ watchForm(form) {
145
+ if (form === this.watched) {
146
+ return;
147
+ }
148
+ this.watched?.removeEventListener('keypress', this.pressOnEnter);
149
+ form?.addEventListener('keypress', this.pressOnEnter);
150
+ this.watched = form;
151
+ }
152
+ connectedCallback() {
153
+ super.connectedCallback();
154
+ this.watchForm(this.form);
155
+ }
156
+ disconnectedCallback() {
157
+ super.disconnectedCallback();
158
+ this.watchForm(null);
159
+ }
160
+ formAssociatedCallback(form) {
161
+ this.watchForm(form);
162
+ }
119
163
  render() {
120
164
  const label = html `<slot name="start"></slot><slot part="text"></slot><slot name="end"></slot>`;
121
165
  const stop = this.focusable ? nothing : '-1';
@@ -5,11 +5,13 @@ import { type PropertyValues, type TemplateResult } from 'lit';
5
5
  *
6
6
  * @description
7
7
  * `tone` is on the headless tier because it carries meaning. It picks the icon
8
- * and the icon's accessible name, so a screen reader announces "Error" for an
9
- * error callout whether or not the color is seen.
8
+ * and the icon region's accessible name, so a screen reader announces "Error"
9
+ * for an error callout whether or not the color is seen. Set `tone-label` to
10
+ * say the name in another language.
10
11
  *
11
12
  * The icon is the fallback content of a slot. Slot your own icon to replace it,
12
- * and the default disappears with no extra check.
13
+ * and the default disappears with no extra check. The tone's name stays on the
14
+ * region around it.
13
15
  *
14
16
  * A callout sets a passage apart inside a document and is read where it sits.
15
17
  * For page-level chrome above the masthead that is a live region and can be
@@ -26,7 +28,7 @@ import { type PropertyValues, type TemplateResult } from 'lit';
26
28
  * @slot icon - Replaces the icon chosen by `tone`.
27
29
  *
28
30
  * @csspart content - The content region.
29
- * @csspart icon - The icon region.
31
+ * @csspart icon - The icon region, an image named for the tone.
30
32
  * @csspart icon-mark - The icon for the tone, shown when no icon is slotted.
31
33
  */
32
34
  export declare class HelixHeadlessCallout extends HelixElement {
@@ -40,6 +42,13 @@ export declare class HelixHeadlessCallout extends HelixElement {
40
42
  * is the whole of the registry, which is why there is no `severity` beside it.
41
43
  */
42
44
  tone?: 'danger' | 'info' | 'neutral' | 'success' | 'warning';
45
+ /**
46
+ * What a screen reader is told the tone is, such as "Fehler".
47
+ *
48
+ * Absent is the English for the tone: "Error", "Information", "Note",
49
+ * "Success" or "Warning".
50
+ */
51
+ toneLabel: string;
43
52
  /**
44
53
  * The mark for the callout's tone, behind a seam.
45
54
  *
@@ -56,7 +65,7 @@ export declare class HelixHeadlessCallout extends HelixElement {
56
65
  protected renderIcon(icon: {
57
66
  content: string;
58
67
  viewBox: string;
59
- }, label: string): TemplateResult;
68
+ }, _label: string): TemplateResult;
60
69
  protected render(): TemplateResult;
61
70
  }
62
71
  declare global {
@@ -10,20 +10,25 @@ import { property } from 'lit/decorators.js';
10
10
  import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
11
11
  const NOTE = { icon: TONE_MARKS.info.icon, label: 'Note' };
12
12
  let HelixHeadlessCallout = class HelixHeadlessCallout extends HelixElement {
13
+ constructor() {
14
+ super(...arguments);
15
+ this.toneLabel = '';
16
+ }
13
17
  willUpdate(changed) {
14
18
  super.willUpdate(changed);
15
19
  hostAria(this.internals, { role: 'note' });
16
20
  }
17
- renderIcon(icon, label) {
21
+ renderIcon(icon, _label) {
18
22
  return html `<slot name="icon">
19
- <svg part="icon-mark" fill="currentColor" viewBox=${icon.viewBox} role="img" aria-label=${label}>
23
+ <svg part="icon-mark" fill="currentColor" viewBox=${icon.viewBox} aria-hidden="true">
20
24
  ${unsafeSVG(icon.content)}
21
25
  </svg>
22
26
  </slot>`;
23
27
  }
24
28
  render() {
25
29
  const { icon, label } = this.tone === 'neutral' ? NOTE : (toneMark(this.tone) ?? TONE_MARKS.info);
26
- return html `<span part="icon">${this.renderIcon(icon, label)}</span>
30
+ const named = this.toneLabel || label;
31
+ return html `<span part="icon" role="img" aria-label=${named}>${this.renderIcon(icon, named)}</span>
27
32
  <div part="content">
28
33
  <slot name="heading"></slot>
29
34
  <slot></slot>
@@ -33,6 +38,9 @@ let HelixHeadlessCallout = class HelixHeadlessCallout extends HelixElement {
33
38
  __decorate([
34
39
  property({ reflect: true })
35
40
  ], HelixHeadlessCallout.prototype, "tone", void 0);
41
+ __decorate([
42
+ property({ attribute: 'tone-label' })
43
+ ], HelixHeadlessCallout.prototype, "toneLabel", void 0);
36
44
  HelixHeadlessCallout = __decorate([
37
45
  customElement('helix-headless-callout')
38
46
  ], HelixHeadlessCallout);
@@ -72,6 +72,7 @@ declare const HelixHeadlessCanvas_base: (new (...args: any[]) => import("@fusion
72
72
  * @aliases Board, JSON Canvas, Whiteboard
73
73
  * @documentation https://fusion.dev/helix/components/canvas
74
74
  *
75
+ * @slot - The board's `helix-canvas-node` and `helix-canvas-edge` elements, drawn on the plane.
75
76
  * @slot empty - Content shown when the document has no nodes.
76
77
  * @slot footer - Content below the board.
77
78
  * @slot header - Content above the board.
@@ -1738,7 +1738,6 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
1738
1738
  return html `<svg
1739
1739
  part="edges"
1740
1740
  role="none"
1741
- @focusin=${this.handleFocusIn}
1742
1741
  viewBox=${`0 0 ${box.width} ${box.height}`}
1743
1742
  width=${box.width}
1744
1743
  height=${box.height}
@@ -1889,9 +1888,19 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
1889
1888
  ruling's own offset is where the document's zero lands, so a line
1890
1889
  falls on every multiple of its spacing in the document's units and
1891
1890
  a card placed on one sits on a line. -->
1891
+ <!-- **Focus is heard here as well as on the host.** Moving from one
1892
+ connection to the next moves focus inside this shadow root, and a
1893
+ browser does not tell the host about a move that starts and ends
1894
+ in its own tree. So the stop stayed on the first of two
1895
+ connections in a row. Heard on the board and never on the edges'
1896
+ SVG element: Chromium makes an SVG element with a focus listener
1897
+ a Tab stop of its own, an unnamed one between the board and the
1898
+ first card. Heard twice from a card, which the handler answers
1899
+ the same way both times. -->
1892
1900
  <div
1893
1901
  part="board"
1894
1902
  style=${`--_canvas-plane-inline-size: ${box.width}px; --_canvas-plane-block-size: ${box.height}px`}
1903
+ @focusin=${this.handleFocusIn}
1895
1904
  >
1896
1905
  ${this.renderEdges(origin, box)} ${this.renderLabels(origin)}
1897
1906
  <slot @slotchange=${this.handleSlotChange}></slot>