@fusion.dev/helix-headless 0.142.1 → 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 (285) hide show
  1. package/custom-elements.json +25381 -1202
  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.d.ts +12 -4
  11. package/dist/area-chart/root/area-chart-root.js +39 -11
  12. package/dist/avatar/group/avatar-group.d.ts +24 -3
  13. package/dist/avatar/group/avatar-group.js +34 -2
  14. package/dist/avatar/root/avatar-root.d.ts +17 -1
  15. package/dist/avatar/root/avatar-root.js +31 -10
  16. package/dist/badge/root/badge-root.d.ts +12 -0
  17. package/dist/badge/root/badge-root.js +1 -1
  18. package/dist/bar-chart/root/bar-chart-root.d.ts +30 -5
  19. package/dist/bar-chart/root/bar-chart-root.js +58 -21
  20. package/dist/blockquote/root/blockquote-root.d.ts +12 -7
  21. package/dist/blockquote/root/blockquote-root.js +6 -4
  22. package/dist/breadcrumbs/root/breadcrumbs-root.d.ts +9 -3
  23. package/dist/breadcrumbs/root/breadcrumbs-root.js +9 -1
  24. package/dist/button/root/button-root.d.ts +84 -1
  25. package/dist/button/root/button-root.js +81 -3
  26. package/dist/callout/root/callout-root.d.ts +32 -6
  27. package/dist/callout/root/callout-root.js +26 -7
  28. package/dist/canvas/root/canvas-root.d.ts +1 -0
  29. package/dist/canvas/root/canvas-root.js +10 -1
  30. package/dist/card/root/card-root.d.ts +30 -1
  31. package/dist/card/root/card-root.js +38 -5
  32. package/dist/carousel/root/carousel-root.d.ts +47 -5
  33. package/dist/carousel/root/carousel-root.js +53 -21
  34. package/dist/chat/composer/chat-composer.d.ts +13 -11
  35. package/dist/chat/composer/chat-composer.js +6 -12
  36. package/dist/chat/message/chat-message.js +8 -1
  37. package/dist/chat/root/chat-root.d.ts +1 -0
  38. package/dist/chat/root/chat-root.js +2 -1
  39. package/dist/checkbox/root/checkbox-root.d.ts +7 -0
  40. package/dist/checkbox/root/checkbox-root.js +9 -3
  41. package/dist/checkbox-group/root/checkbox-group-root.d.ts +116 -33
  42. package/dist/checkbox-group/root/checkbox-group-root.js +141 -44
  43. package/dist/code/root/code-root.d.ts +1 -1
  44. package/dist/code-block/lines.d.ts +13 -0
  45. package/dist/code-block/lines.js +2 -0
  46. package/dist/code-block/root/code-block-root.d.ts +49 -0
  47. package/dist/code-block/root/code-block-root.js +44 -3
  48. package/dist/color-input/root/color-input-root.d.ts +60 -2
  49. package/dist/color-input/root/color-input-root.js +55 -7
  50. package/dist/color-picker/channels.d.ts +26 -0
  51. package/dist/color-picker/channels.js +84 -0
  52. package/dist/color-picker/root/color-picker-root.d.ts +118 -12
  53. package/dist/color-picker/root/color-picker-root.js +183 -13
  54. package/dist/combobox/root/combobox-root.d.ts +126 -3
  55. package/dist/combobox/root/combobox-root.js +224 -21
  56. package/dist/command/root/command-root.d.ts +47 -10
  57. package/dist/command/root/command-root.js +45 -4
  58. package/dist/copy-button/from.d.ts +22 -0
  59. package/dist/copy-button/from.js +28 -0
  60. package/dist/copy-button/root/copy-button-root.d.ts +48 -7
  61. package/dist/copy-button/root/copy-button-root.js +54 -8
  62. package/dist/data-table/root/data-table-root.d.ts +164 -10
  63. package/dist/data-table/root/data-table-root.js +488 -57
  64. package/dist/date-input/root/date-input-root.d.ts +123 -9
  65. package/dist/date-input/root/date-input-root.js +147 -45
  66. package/dist/date-picker/root/date-picker-root.d.ts +229 -18
  67. package/dist/date-picker/root/date-picker-root.js +342 -46
  68. package/dist/dialog/description.d.ts +9 -0
  69. package/dist/dialog/description.js +14 -0
  70. package/dist/dialog/root/dialog-root.d.ts +101 -10
  71. package/dist/dialog/root/dialog-root.js +83 -4
  72. package/dist/donut-chart/root/donut-chart-root.d.ts +61 -16
  73. package/dist/donut-chart/root/donut-chart-root.js +48 -13
  74. package/dist/drawer/root/drawer-root.d.ts +93 -7
  75. package/dist/drawer/root/drawer-root.js +126 -5
  76. package/dist/drawer/swipe-start.d.ts +9 -0
  77. package/dist/drawer/swipe-start.js +12 -0
  78. package/dist/drilldown/item/drilldown-item.d.ts +16 -13
  79. package/dist/drilldown/item/drilldown-item.js +6 -2
  80. package/dist/drilldown/root/drilldown-root.d.ts +21 -5
  81. package/dist/drilldown/root/drilldown-root.js +26 -10
  82. package/dist/element-events.generated.js +42 -13
  83. package/dist/empty/root/empty-root.d.ts +1 -1
  84. package/dist/file-input/root/file-input-root.d.ts +65 -1
  85. package/dist/file-input/root/file-input-root.js +76 -12
  86. package/dist/file-input/screen.d.ts +35 -0
  87. package/dist/file-input/screen.js +49 -0
  88. package/dist/filter-input/menu/filter-menu.d.ts +9 -0
  89. package/dist/filter-input/menu/filter-menu.js +8 -0
  90. package/dist/filter-input/root/filter-input-root.d.ts +43 -6
  91. package/dist/filter-input/root/filter-input-root.js +89 -10
  92. package/dist/filter-input/suggest.d.ts +17 -0
  93. package/dist/filter-input/suggest.js +23 -0
  94. package/dist/format-date/options.d.ts +33 -0
  95. package/dist/format-date/options.js +42 -0
  96. package/dist/format-date/root/format-date-root.d.ts +62 -3
  97. package/dist/format-date/root/format-date-root.js +78 -37
  98. package/dist/graph/root/graph-root.d.ts +24 -3
  99. package/dist/graph/root/graph-root.js +66 -12
  100. package/dist/heatmap/root/buckets.d.ts +2 -1
  101. package/dist/heatmap/root/buckets.js +5 -1
  102. package/dist/heatmap/root/heatmap-root.d.ts +47 -9
  103. package/dist/heatmap/root/heatmap-root.js +46 -18
  104. package/dist/icon/index.d.ts +2 -0
  105. package/dist/icon/index.js +2 -0
  106. package/dist/icon/registry.d.ts +37 -0
  107. package/dist/icon/registry.js +83 -0
  108. package/dist/icon/root/icon-root.d.ts +53 -6
  109. package/dist/icon/root/icon-root.js +72 -4
  110. package/dist/icon/root/index.d.ts +2 -0
  111. package/dist/icon/root/index.js +2 -0
  112. package/dist/icon/set.d.ts +1 -0
  113. package/dist/icon/set.js +3 -0
  114. package/dist/icon-button/root/icon-button-root.d.ts +1 -1
  115. package/dist/image/root/image-root.d.ts +23 -2
  116. package/dist/image/root/image-root.js +23 -4
  117. package/dist/input/root/input-root.d.ts +62 -9
  118. package/dist/input/root/input-root.js +61 -5
  119. package/dist/kbd/root/kbd-root.d.ts +26 -11
  120. package/dist/kbd/root/kbd-root.js +35 -38
  121. package/dist/kbd/shortcut.d.ts +18 -0
  122. package/dist/kbd/shortcut.js +35 -0
  123. package/dist/legend/hidden-series.d.ts +8 -0
  124. package/dist/legend/hidden-series.js +13 -0
  125. package/dist/legend/root/legend-root.d.ts +63 -0
  126. package/dist/legend/root/legend-root.js +61 -7
  127. package/dist/lightbox/item/lightbox-item.d.ts +17 -1
  128. package/dist/lightbox/item/lightbox-item.js +10 -1
  129. package/dist/lightbox/root/lightbox-root.d.ts +77 -6
  130. package/dist/lightbox/root/lightbox-root.js +116 -5
  131. package/dist/line-chart/root/line-chart-root.d.ts +29 -4
  132. package/dist/line-chart/root/line-chart-root.js +58 -17
  133. package/dist/list/item/list-item.d.ts +11 -5
  134. package/dist/list/item/list-item.js +5 -6
  135. package/dist/listbox/range.d.ts +17 -0
  136. package/dist/listbox/range.js +5 -0
  137. package/dist/listbox/root/listbox-root.d.ts +184 -4
  138. package/dist/listbox/root/listbox-root.js +229 -23
  139. package/dist/loader/busy.d.ts +17 -0
  140. package/dist/loader/busy.js +24 -0
  141. package/dist/loader/root/loader-root.d.ts +37 -0
  142. package/dist/loader/root/loader-root.js +52 -3
  143. package/dist/map/root/map-root.d.ts +19 -4
  144. package/dist/map/root/map-root.js +36 -7
  145. package/dist/megamenu/root/megamenu-root.d.ts +10 -1
  146. package/dist/megamenu/root/megamenu-root.js +10 -1
  147. package/dist/menu/item/menu-item.d.ts +41 -12
  148. package/dist/menu/item/menu-item.js +46 -3
  149. package/dist/menu/root/menu-root.d.ts +32 -2
  150. package/dist/menu/root/menu-root.js +48 -8
  151. package/dist/meter/root/meter-root.d.ts +13 -0
  152. package/dist/meter/root/meter-root.js +24 -5
  153. package/dist/navigation/root/navigation-root.d.ts +10 -2
  154. package/dist/navigation/root/navigation-root.js +13 -2
  155. package/dist/notifications/feed-keys.d.ts +10 -0
  156. package/dist/notifications/feed-keys.js +14 -0
  157. package/dist/notifications/group/notification-group.d.ts +1 -0
  158. package/dist/notifications/notification/notification.d.ts +29 -0
  159. package/dist/notifications/notification/notification.js +51 -3
  160. package/dist/notifications/root/notifications-root.d.ts +49 -0
  161. package/dist/notifications/root/notifications-root.js +76 -2
  162. package/dist/number-input/root/number-input-root.d.ts +109 -7
  163. package/dist/number-input/root/number-input-root.js +109 -21
  164. package/dist/object-inspector/root/object-inspector-root.d.ts +49 -5
  165. package/dist/object-inspector/root/object-inspector-root.js +92 -12
  166. package/dist/option/root/option-root.d.ts +58 -6
  167. package/dist/option/root/option-root.js +35 -5
  168. package/dist/outline/root/outline-root.d.ts +13 -2
  169. package/dist/outline/root/outline-root.js +4 -2
  170. package/dist/pagination/root/pagination-root.d.ts +19 -1
  171. package/dist/pagination/root/pagination-root.js +49 -9
  172. package/dist/pagination/size/pagination-size.d.ts +13 -3
  173. package/dist/pagination/size/pagination-size.js +7 -0
  174. package/dist/pin-input/format.d.ts +7 -0
  175. package/dist/pin-input/format.js +16 -0
  176. package/dist/pin-input/root/pin-input-root.d.ts +37 -0
  177. package/dist/pin-input/root/pin-input-root.js +48 -7
  178. package/dist/popover/root/popover-root.d.ts +78 -3
  179. package/dist/popover/root/popover-root.js +74 -5
  180. package/dist/progress/root/progress-root.d.ts +23 -1
  181. package/dist/progress/root/progress-root.js +40 -10
  182. package/dist/qr-code/export.d.ts +31 -0
  183. package/dist/qr-code/export.js +10 -0
  184. package/dist/qr-code/root/qr-code-root.d.ts +45 -0
  185. package/dist/qr-code/root/qr-code-root.js +78 -3
  186. package/dist/radio/group/radio-group.d.ts +64 -0
  187. package/dist/radio/group/radio-group.js +66 -12
  188. package/dist/radio/root/radio-root.d.ts +8 -5
  189. package/dist/radio/root/radio-root.js +1 -2
  190. package/dist/rating/root/rating-root.d.ts +49 -1
  191. package/dist/rating/root/rating-root.js +69 -10
  192. package/dist/scatter-chart/root/scatter-chart-root.d.ts +17 -3
  193. package/dist/scatter-chart/root/scatter-chart-root.js +40 -14
  194. package/dist/select/root/select-root.d.ts +93 -3
  195. package/dist/select/root/select-root.js +111 -13
  196. package/dist/skeleton/root/skeleton-root.d.ts +39 -3
  197. package/dist/skeleton/root/skeleton-root.js +33 -4
  198. package/dist/skip-link/root/skip-link-root.d.ts +10 -0
  199. package/dist/skip-link/root/skip-link-root.js +15 -1
  200. package/dist/slider/root/slider-root.d.ts +59 -2
  201. package/dist/slider/root/slider-root.js +82 -8
  202. package/dist/sparkline/root/sparkline-root.d.ts +9 -10
  203. package/dist/speed-dial/item/speed-dial-item.d.ts +11 -5
  204. package/dist/speed-dial/item/speed-dial-item.js +7 -2
  205. package/dist/speed-dial/root/speed-dial-root.d.ts +15 -4
  206. package/dist/speed-dial/root/speed-dial-root.js +19 -5
  207. package/dist/splitter/root/splitter-root.d.ts +28 -6
  208. package/dist/splitter/root/splitter-root.js +35 -1
  209. package/dist/stat/root/stat-root.d.ts +9 -1
  210. package/dist/stat/root/stat-root.js +15 -2
  211. package/dist/steps/root/steps-root.js +1 -1
  212. package/dist/steps/step/step.d.ts +1 -0
  213. package/dist/steps/step/step.js +1 -1
  214. package/dist/switch/root/switch-root.d.ts +14 -2
  215. package/dist/switch/root/switch-root.js +7 -1
  216. package/dist/table/cell/table-cell.d.ts +24 -4
  217. package/dist/table/cell/table-cell.js +21 -1
  218. package/dist/table/column/table-column.d.ts +32 -6
  219. package/dist/table/column/table-column.js +36 -6
  220. package/dist/table/root/table-root.d.ts +94 -3
  221. package/dist/table/root/table-root.js +128 -5
  222. package/dist/table/row/table-row.d.ts +7 -0
  223. package/dist/table/row/table-row.js +9 -1
  224. package/dist/tabs/root/tabs-root.d.ts +6 -1
  225. package/dist/tabs/root/tabs-root.js +6 -2
  226. package/dist/tag/group/tag-group.d.ts +107 -0
  227. package/dist/tag/group/tag-group.js +157 -0
  228. package/dist/tag/index.d.ts +1 -0
  229. package/dist/tag/index.js +1 -0
  230. package/dist/tag/root/tag-root.d.ts +83 -1
  231. package/dist/tag/root/tag-root.js +87 -17
  232. package/dist/tag/selection.d.ts +10 -0
  233. package/dist/tag/selection.js +12 -0
  234. package/dist/textarea/grow.d.ts +22 -0
  235. package/dist/textarea/grow.js +20 -0
  236. package/dist/textarea/root/textarea-root.d.ts +13 -0
  237. package/dist/textarea/root/textarea-root.js +10 -2
  238. package/dist/time-input/root/time-input-root.d.ts +71 -6
  239. package/dist/time-input/root/time-input-root.js +110 -44
  240. package/dist/time-picker/root/time-picker-root.d.ts +155 -26
  241. package/dist/time-picker/root/time-picker-root.js +192 -63
  242. package/dist/toast/hotkey.d.ts +22 -0
  243. package/dist/toast/hotkey.js +23 -0
  244. package/dist/toast/item/toast-item.d.ts +16 -1
  245. package/dist/toast/item/toast-item.js +41 -13
  246. package/dist/toast/root/toast-root.d.ts +105 -8
  247. package/dist/toast/root/toast-root.js +209 -4
  248. package/dist/toggle/group/toggle-group.d.ts +95 -16
  249. package/dist/toggle/group/toggle-group.js +99 -13
  250. package/dist/toggle/root/toggle-root.d.ts +26 -11
  251. package/dist/toggle/root/toggle-root.js +14 -6
  252. package/dist/toolbar/root/toolbar-root.d.ts +55 -15
  253. package/dist/toolbar/root/toolbar-root.js +99 -16
  254. package/dist/tooltip/focus-target.d.ts +6 -0
  255. package/dist/tooltip/focus-target.js +9 -0
  256. package/dist/tooltip/root/tooltip-root.d.ts +73 -15
  257. package/dist/tooltip/root/tooltip-root.js +96 -7
  258. package/dist/tour/naming.d.ts +30 -0
  259. package/dist/tour/naming.js +10 -0
  260. package/dist/tour/root/tour-root.d.ts +67 -3
  261. package/dist/tour/root/tour-root.js +105 -17
  262. package/dist/tour/stop/tour-stop.d.ts +65 -3
  263. package/dist/tour/stop/tour-stop.js +61 -11
  264. package/dist/tour/wait.d.ts +11 -0
  265. package/dist/tour/wait.js +19 -0
  266. package/dist/tree/item/tree-item.d.ts +21 -5
  267. package/dist/tree/item/tree-item.js +14 -4
  268. package/dist/tree/root/tree-root.d.ts +29 -5
  269. package/dist/tree/root/tree-root.js +64 -8
  270. package/dist/trend/root/trend-root.d.ts +36 -1
  271. package/dist/trend/root/trend-root.js +41 -15
  272. package/dist/video/root/keys.d.ts +5 -0
  273. package/dist/video/root/keys.js +13 -1
  274. package/dist/video/root/video-root.d.ts +19 -0
  275. package/dist/video/root/video-root.js +48 -4
  276. package/dist/video-playlist/menu/video-playlist-menu.d.ts +20 -2
  277. package/dist/video-playlist/menu/video-playlist-menu.js +27 -5
  278. package/dist/video-playlist/root/video-playlist-root.js +6 -3
  279. package/dist/virtual-list/root/virtual-list-root.d.ts +52 -4
  280. package/dist/virtual-list/root/virtual-list-root.js +103 -21
  281. package/lint.json +1 -1
  282. package/package.json +5 -5
  283. package/types/jsx.d.ts +9140 -1149
  284. package/types/svelte.d.ts +1 -0
  285. package/types/vue.d.ts +2089 -267
@@ -9,11 +9,19 @@ import { html, nothing, svg } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  import '../../tooltip/index.js';
11
11
  const REACH = 24;
12
- const coefficient = new Intl.NumberFormat(undefined, {
13
- maximumFractionDigits: 2,
14
- minimumFractionDigits: 2,
15
- signDisplay: 'exceptZero',
16
- });
12
+ const COEFFICIENTS = new Map();
13
+ const coefficientIn = (locale) => {
14
+ let found = COEFFICIENTS.get(locale);
15
+ if (!found) {
16
+ found = new Intl.NumberFormat(locale || undefined, {
17
+ maximumFractionDigits: 2,
18
+ minimumFractionDigits: 2,
19
+ signDisplay: 'exceptZero',
20
+ });
21
+ COEFFICIENTS.set(locale, found);
22
+ }
23
+ return found;
24
+ };
17
25
  const nice = ([low, high]) => {
18
26
  if (low === high) {
19
27
  return [low - 1, high + 1];
@@ -108,14 +116,17 @@ let HelixHeadlessScatterChart = class HelixHeadlessScatterChart extends MixinCha
108
116
  const y = row.cells[column]?.value;
109
117
  return x === undefined || y === undefined ? [] : [[x, y, index]];
110
118
  }));
111
- const all = pairs.flat();
119
+ const shown = ups.map((column) => this.showsSeries(name(column)));
120
+ const all = pairs.filter((_, depth) => shown[depth]).flat();
112
121
  const x = nice(extent(all.length > 0 ? all.map(([one]) => one) : [0]));
113
122
  const y = nice(extent(all.length > 0 ? all.map(([, one]) => one) : [0]));
114
123
  const along = linear(x, [0, PLOT_WIDTH]);
115
124
  const up = linear(y, [PLOT_HEIGHT, 0]);
116
125
  return {
117
126
  pairs: pairs.map((series) => series.map(([one, two]) => [one, two])),
118
- points: pairs.flatMap((series, depth) => series.map(([one, two, row]) => ({ depth, row, x: along(one), y: up(two) }))),
127
+ points: pairs.flatMap((series, depth) => shown[depth]
128
+ ? series.map(([one, two, row]) => ({ depth, row, x: along(one), y: up(two) }))
129
+ : []),
119
130
  x,
120
131
  xName: name(across),
121
132
  y,
@@ -130,7 +141,7 @@ let HelixHeadlessScatterChart = class HelixHeadlessScatterChart extends MixinCha
130
141
  <div id=${CHART_PLOT} part="plot"></div>
131
142
  </div>`;
132
143
  }
133
- const format = formatNumbers('');
144
+ const format = formatNumbers(this.locale);
134
145
  const along = linear(field.x, [0, PLOT_WIDTH]);
135
146
  const up = linear(field.y, [PLOT_HEIGHT, 0]);
136
147
  return html `<div dir="ltr" part="plane">
@@ -148,8 +159,17 @@ let HelixHeadlessScatterChart = class HelixHeadlessScatterChart extends MixinCha
148
159
  }
149
160
  renderPlot(field, along, up) {
150
161
  const at = this.pointer.at;
151
- return html `<div aria-hidden="true" id=${CHART_PLOT} part="plot">
162
+ return html `<div
163
+ aria-describedby=${this.named ? 'helix-chart-summary' : nothing}
164
+ aria-label=${this.named || nothing}
165
+ aria-labelledby=${this.named ? nothing : 'helix-chart-summary'}
166
+ id=${CHART_PLOT}
167
+ part="plot"
168
+ role="application"
169
+ tabindex="0"
170
+ >
152
171
  <svg part="svg"
172
+ aria-hidden="true"
153
173
  preserveAspectRatio="none"
154
174
  viewBox="0 0 ${PLOT_WIDTH} ${PLOT_HEIGHT}"
155
175
  xmlns="http://www.w3.org/2000/svg"
@@ -164,6 +184,7 @@ let HelixHeadlessScatterChart = class HelixHeadlessScatterChart extends MixinCha
164
184
  ${field.points.map((point) => {
165
185
  const index = seriesIndex(point.depth);
166
186
  return html `<span
187
+ aria-hidden="true"
167
188
  data-series=${index}
168
189
  ?data-active=${point.row === at}
169
190
  part="point${point.row === at ? ' point-active' : ''}"
@@ -177,17 +198,22 @@ let HelixHeadlessScatterChart = class HelixHeadlessScatterChart extends MixinCha
177
198
  const clauses = field.yNames.map((name, depth) => {
178
199
  const pairs = field.pairs[depth] ?? [];
179
200
  const r = correlation(pairs);
180
- const strength = r === undefined ? '' : `, ${this.correlationLabel} ${coefficient.format(r)}`;
201
+ const strength = r === undefined ? '' : `, ${this.correlationLabel} ${coefficientIn(this.locale).format(r)}`;
181
202
  return `${name} / ${field.xName}: ${pairs.length} ${this.pointsLabel}${strength}`;
182
203
  });
183
204
  const named = this.named ? `${this.named}. ` : '';
184
- return html `<p part="summary">${named}${clauses.join('; ')}.</p>`;
205
+ return html `<p id="helix-chart-summary" part="summary">${named}${clauses.join('; ')}.</p>`;
185
206
  }
186
207
  renderTooltip() {
187
208
  const at = this.pointer.at;
188
- const point = this.nearest;
209
+ const column = this.pointer.column;
210
+ const depth = column === undefined ? -1 : this.plottedColumns.indexOf(column) - 1;
211
+ const point = this.nearest?.row === at && !this.pointer.keyed
212
+ ? this.nearest
213
+ : (this.drawn.find((one) => one.row === at && one.depth === depth) ??
214
+ this.drawn.find((one) => one.row === at));
189
215
  if (at < 0 || !point) {
190
- return drawTooltip(this.sourceRows, -1, this.tag('tooltip'));
216
+ return drawTooltip(this.sourceRows, -1, this.tag('tooltip'), undefined, undefined, this.pointer);
191
217
  }
192
218
  return drawTooltip(this.sourceRows, at, this.tag('tooltip'), {
193
219
  dir: isRtl(this) ? 'rtl' : 'ltr',
@@ -195,7 +221,7 @@ let HelixHeadlessScatterChart = class HelixHeadlessScatterChart extends MixinCha
195
221
  placement: 'top',
196
222
  x: `${point.x}%`,
197
223
  y: `${point.y}%`,
198
- }, (cell) => this.seriesOf(cell));
224
+ }, (cell) => this.seriesOf(cell), this.pointer);
199
225
  }
200
226
  };
201
227
  __decorate([
@@ -1,5 +1,6 @@
1
1
  import { AriaForwardController, HelixFormElement, PopoverController } from '@fusion.dev/helix-core';
2
2
  import { type PropertyValues, type TemplateResult } from 'lit';
3
+ import { type HelixHeadlessOption } from '../../option/root/option-root.js';
3
4
  import '../../listbox/index.js';
4
5
  declare const HelixHeadlessSelect_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerOpenInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerValueInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinFieldInterface) & typeof HelixFormElement;
5
6
  /**
@@ -17,6 +18,17 @@ declare const HelixHeadlessSelect_base: (new (...args: any[]) => import("@fusion
17
18
  * `Select` owns the value, passes it down to the listbox, and takes changes
18
19
  * back, so the value lives in one place.
19
20
  *
21
+ * Set `valueContent` to draw the trigger's content from the chosen options,
22
+ * and several chosen options are written as a list in the reader's language.
23
+ *
24
+ * In the open list the arrow keys, Home, End, PageUp, PageDown and typed
25
+ * letters move the highlight, and Enter, Space or a click chooses it and
26
+ * closes the list. Tab closes the list and moves on. On the closed trigger,
27
+ * ArrowDown, Enter and Space open the list on the chosen option, ArrowUp opens
28
+ * it on the chosen or last option, Home and End open it on the first or last,
29
+ * and typed letters choose the option whose words start with them, as a native
30
+ * `<select>` does.
31
+ *
20
32
  * @since 0.1.0
21
33
  * @status stable
22
34
  * @category forms
@@ -25,6 +37,7 @@ declare const HelixHeadlessSelect_base: (new (...args: any[]) => import("@fusion
25
37
  *
26
38
  * @slot - The options, and any groups of them.
27
39
  * @slot end - Content after the chosen value, such as a count, a unit, or a clear button.
40
+ * @slot feedback - The message that says what is wrong, for content richer than a string. It shows while there is a message.
28
41
  * @slot hint - The hint, for content richer than plain text.
29
42
  * @slot indicator - The icon on the trigger that shows it opens, usually a caret.
30
43
  * @slot label - The label, for content richer than plain text.
@@ -34,8 +47,9 @@ declare const HelixHeadlessSelect_base: (new (...args: any[]) => import("@fusion
34
47
  * @event {HelixBeforeChangeEvent} helix-before-change - Relayed from the listbox when the value is about to change. Cancelable: canceling it stops the change and leaves the decision to you.
35
48
  * @event {HelixBeforeCloseEvent} helix-before-close - Fired before the list closes. Cancelable: canceling it keeps the list open and leaves the decision to you.
36
49
  * @event {HelixBeforeOpenEvent} helix-before-open - Fired before the list opens. Cancelable: canceling it keeps the list closed and leaves the decision to you.
50
+ * @event {HelixFilterEvent} helix-filter - Fired as the user types in the filter field of a `filterable` select, with the text the list is filtered by. Cancelable: cancel it to leave the list unfiltered and filter the options yourself.
37
51
  * @event {Event} input - Fired after the value changes.
38
- * @event {Event} toggle - Fired after the list opens or closes, with `newState` and `oldState`.
52
+ * @event {ToggleEvent} toggle - Fired after the list opens or closes, with `newState` and `oldState`.
39
53
  *
40
54
  * @csspart feedback - The message that says what is wrong with the value.
41
55
  * @csspart hint - Help under the trigger.
@@ -93,6 +107,17 @@ export declare class HelixHeadlessSelect extends HelixHeadlessSelect_base {
93
107
  * Shown on the trigger while nothing is chosen.
94
108
  */
95
109
  placeholder: string;
110
+ /**
111
+ * What the trigger shows for the chosen options: a function given them, in
112
+ * the order they appear, that returns text, an element or a Lit template.
113
+ *
114
+ * For a trigger that shows more than the words, an avatar beside a name or a
115
+ * count of a long set, as React Aria's `SelectValue` takes children. The
116
+ * trigger is named by what this draws, so keep the words in it. Nothing
117
+ * chosen still shows `placeholder`. A property only, there being no way to
118
+ * write a function in markup.
119
+ */
120
+ valueContent?: ((chosen: readonly HelixHeadlessOption[]) => unknown) | undefined;
96
121
  /**
97
122
  * Which option is chosen, as an index, or `-1` where none is.
98
123
  *
@@ -158,10 +183,73 @@ export declare class HelixHeadlessSelect extends HelixHeadlessSelect_base {
158
183
  * the commit creates the text node it needs.
159
184
  */
160
185
  private get display();
186
+ /** The chosen options, in the order they appear. None on a server or while hydrating. */
187
+ private get chosenOptions();
188
+ /**
189
+ * What the trigger shows, behind a seam: what `value-content` draws for the
190
+ * chosen options, or their words, or the placeholder.
191
+ */
192
+ protected renderValue(): unknown;
193
+ /** Where the highlight goes when the list opens, set by the key that opened it. */
194
+ private opening;
195
+ /**
196
+ * The values of the options seen on the last update, so an option that
197
+ * leaves can be told apart from one that has not arrived yet.
198
+ */
199
+ private seen;
200
+ /**
201
+ * Hears an option's words change, which no slot reports, and the host's
202
+ * `aria-labelledby` change, which `triggerNames` reads.
203
+ *
204
+ * The trigger shows the chosen option's words, read at render time. A slot
205
+ * reports an option arriving or leaving and says nothing when the text of
206
+ * one already there changes, so a relabeled option left the old words on the
207
+ * trigger. `label` and `value` are attributes that change the words too.
208
+ *
209
+ * Made on connection, there being no `MutationObserver` on a server.
210
+ */
211
+ private watchers;
212
+ /** A link option, once followed, closes the panel as a choice does. */
213
+ private readonly handleLinkFollowed;
214
+ connectedCallback(): void;
215
+ disconnectedCallback(): void;
216
+ /** Opens the list with the highlight where the opening key says. */
217
+ private openAt;
161
218
  /** Closes the list and puts focus back where it came from. */
162
219
  private close;
163
220
  private readonly handleChange;
221
+ /**
222
+ * The keys of the closed trigger.
223
+ *
224
+ * Letters choose without opening, the way a native `<select>` and React
225
+ * Aria's Select do, so a reader who knows the answer types it. A Space in the
226
+ * middle of those letters is one of them. With `multiple`, a letter opens the
227
+ * list on the match instead, because adding to a set is a choice the reader
228
+ * makes in the list.
229
+ */
164
230
  private readonly handleTriggerKeydown;
231
+ /**
232
+ * Tab closes the list and lets focus move on, as a native `<select>` does.
233
+ *
234
+ * Measured before this: Tab moved focus to the next control and left the
235
+ * panel open over the page, because the panel closes on a press outside or
236
+ * on Escape and nothing closed it when focus left.
237
+ */
238
+ private readonly handleListKeydown;
239
+ /**
240
+ * Takes `<helix-option selected>` as the default value, and an option that
241
+ * has left out of the value.
242
+ *
243
+ * The first is what a native `<select>` does with `<option selected>`, and
244
+ * what `Option.defaultSelected` says: a form reset returns to it. A
245
+ * `default-value` on the select wins.
246
+ *
247
+ * The second keeps the trigger and the form in agreement. Measured before
248
+ * this: removing the chosen option made the trigger say "Choose" while the
249
+ * form still submitted the removed value. Only an option seen before is
250
+ * dropped, so a value set ahead of options that load later is kept.
251
+ */
252
+ private reconcileOptions;
165
253
  /** Reports the answer and the verdict on it. */
166
254
  private sync;
167
255
  protected willUpdate(changed: PropertyValues): void;
@@ -198,8 +286,10 @@ export declare class HelixHeadlessSelect extends HelixHeadlessSelect_base {
198
286
  * native `<select>` says both, and this names the trigger by both, the label
199
287
  * and then the value, in that order.
200
288
  *
201
- * A consumer's own `aria-labelledby` on the host wins, carried to the trigger
202
- * by `AriaForwardController`, so nothing is written here to fight it.
289
+ * A consumer's own `aria-labelledby` on the host wins, and so does a
290
+ * `<label>` that names the host where there is no `label`. Both are carried
291
+ * to the trigger by `AriaForwardController`, which reads the value after
292
+ * them, so nothing is written here to fight it.
203
293
  */
204
294
  private get triggerNames();
205
295
  protected render(): TemplateResult;
@@ -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, HelixFormElement, hostAria, MixinControllerOpen, MixinControllerValue, MixinField, PopoverController, reportValues, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, afterLinkFollowed, customElement, HelixFormElement, hostAria, MixinControllerOpen, MixinControllerValue, MixinField, PopoverController, reportValues, typesCharacter, } from '@fusion.dev/helix-core';
8
8
  import { caretDown } from '@fusion.dev/helix-icons';
9
9
  import { nothing } from 'lit';
10
10
  import { property } from 'lit/decorators.js';
@@ -16,6 +16,10 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
16
16
  constructor() {
17
17
  super(...arguments);
18
18
  this.aria = new AriaForwardController(this, {
19
+ labelledByAfter: () => {
20
+ const value = this.partNamed('value');
21
+ return value ? [value] : [];
22
+ },
19
23
  target: () => this.partNamed('trigger'),
20
24
  });
21
25
  this.emptyText = 'No matches';
@@ -36,6 +40,12 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
36
40
  placement: 'bottom-start',
37
41
  surface: () => this.partNamed('surface'),
38
42
  });
43
+ this.opening = 'selected';
44
+ this.seen = new Set();
45
+ this.watchers = [];
46
+ this.handleLinkFollowed = (event) => {
47
+ afterLinkFollowed(event, () => this.requestOpen(false));
48
+ };
39
49
  this.handleChange = (event) => {
40
50
  const next = event.detail.value;
41
51
  if (!this.requestValue(next)) {
@@ -48,9 +58,39 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
48
58
  event.stopPropagation();
49
59
  };
50
60
  this.handleTriggerKeydown = (event) => {
51
- if (event.key === 'ArrowDown' || event.key === 'Enter' || event.key === ' ') {
61
+ const listbox = this.listbox;
62
+ if (typesCharacter(event) && (event.key !== ' ' || listbox?.typing)) {
63
+ event.preventDefault();
64
+ const match = listbox?.typeaheadTo(event.key);
65
+ if (match === undefined) {
66
+ return;
67
+ }
68
+ if (this.multiple) {
69
+ this.opening = 'selected';
70
+ this.requestOpen(true);
71
+ }
72
+ else {
73
+ this.requestValue(match);
74
+ }
75
+ return;
76
+ }
77
+ const opens = {
78
+ ' ': 'selected',
79
+ ArrowDown: 'selected',
80
+ ArrowUp: 'last',
81
+ End: 'last',
82
+ Enter: 'selected',
83
+ Home: 'first',
84
+ };
85
+ const where = opens[event.key];
86
+ if (where !== undefined) {
52
87
  event.preventDefault();
53
- this.requestOpen(true);
88
+ this.openAt(where === 'last' && event.key === 'ArrowUp' && this.value !== '' ? 'selected' : where);
89
+ }
90
+ };
91
+ this.handleListKeydown = (event) => {
92
+ if (event.key === 'Tab' && this.open) {
93
+ this.requestOpen(false);
54
94
  }
55
95
  };
56
96
  }
@@ -58,10 +98,7 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
58
98
  return this.listbox?.selectedIndex ?? -1;
59
99
  }
60
100
  set selectedIndex(at) {
61
- const listbox = this.listbox;
62
- if (listbox) {
63
- listbox.selectedIndex = at;
64
- }
101
+ this.value = this.listbox?.options[at]?.value ?? '';
65
102
  }
66
103
  get type() {
67
104
  return this.multiple ? 'select-multiple' : 'select-one';
@@ -71,14 +108,46 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
71
108
  }
72
109
  get display() {
73
110
  const chosen = new Set(this.value === '' ? [] : this.value.split(','));
74
- const words = optionsAmong(this.knownChildren)
75
- .filter((option) => chosen.has(option.value))
76
- .map((option) => option.text);
111
+ const words = this.chosenOptions.map((option) => option.text);
77
112
  if (words.length > 0) {
78
- return words.join(', ');
113
+ return new Intl.ListFormat(this.lang || undefined, { style: 'short', type: 'unit' }).format(words);
79
114
  }
80
115
  return chosen.size > 0 && !this.childrenKnown ? nothing : this.placeholder;
81
116
  }
117
+ get chosenOptions() {
118
+ const chosen = new Set(this.value === '' ? [] : this.value.split(','));
119
+ return optionsAmong(this.knownChildren).filter((option) => chosen.has(option.value));
120
+ }
121
+ renderValue() {
122
+ const chosen = this.chosenOptions;
123
+ return this.valueContent && chosen.length > 0 ? this.valueContent(chosen) : this.display;
124
+ }
125
+ connectedCallback() {
126
+ super.connectedCallback();
127
+ this.addEventListener('helix-select', this.handleLinkFollowed);
128
+ const words = new MutationObserver(() => this.requestUpdate());
129
+ words.observe(this, {
130
+ attributeFilter: ['label', 'value'],
131
+ characterData: true,
132
+ childList: true,
133
+ subtree: true,
134
+ });
135
+ const naming = new MutationObserver(() => this.requestUpdate());
136
+ naming.observe(this, { attributeFilter: ['aria-labelledby'] });
137
+ this.watchers = [words, naming];
138
+ }
139
+ disconnectedCallback() {
140
+ super.disconnectedCallback();
141
+ this.removeEventListener('helix-select', this.handleLinkFollowed);
142
+ for (const watcher of this.watchers) {
143
+ watcher.disconnect();
144
+ }
145
+ this.watchers = [];
146
+ }
147
+ openAt(where) {
148
+ this.opening = where;
149
+ this.requestOpen(true);
150
+ }
82
151
  close() {
83
152
  if (!this.open) {
84
153
  return;
@@ -86,6 +155,24 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
86
155
  this.requestOpen(false);
87
156
  this.partNamed('trigger')?.focus();
88
157
  }
158
+ reconcileOptions() {
159
+ const options = optionsAmong(this.knownChildren);
160
+ if (this.defaultValue === '' && !this.hasAttribute('default-value')) {
161
+ const declared = options
162
+ .filter((option) => option.defaultSelected)
163
+ .map((option) => option.value);
164
+ if (declared.length > 0) {
165
+ this.defaultValue = this.multiple ? declared.join(',') : (declared.at(-1) ?? '');
166
+ }
167
+ }
168
+ const present = new Set(options.map((option) => option.value));
169
+ const held = this.value === '' ? [] : this.value.split(',');
170
+ const gone = held.filter((value) => this.seen.has(value) && !present.has(value));
171
+ this.seen = present;
172
+ if (gone.length > 0) {
173
+ this.value = held.filter((value) => !gone.includes(value)).join(',');
174
+ }
175
+ }
89
176
  sync() {
90
177
  if (this.multiple) {
91
178
  reportValues(this.internals, this.name, this.value === '' ? [] : this.value.split(','));
@@ -98,6 +185,9 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
98
185
  willUpdate(changed) {
99
186
  super.willUpdate(changed);
100
187
  hostAria(this.internals, { label: this.label });
188
+ if (this.childrenKnown) {
189
+ this.reconcileOptions();
190
+ }
101
191
  }
102
192
  firstUpdated(changed) {
103
193
  super.firstUpdated(changed);
@@ -109,6 +199,8 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
109
199
  this.linkHost();
110
200
  if (this.open) {
111
201
  if (changed.has('open')) {
202
+ this.listbox?.highlight(this.opening);
203
+ this.opening = 'selected';
112
204
  this.listbox?.focus();
113
205
  }
114
206
  }
@@ -129,7 +221,9 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
129
221
  .value=${this.value}
130
222
  ?multiple=${this.multiple}
131
223
  ?filterable=${this.filterable}
224
+ manual
132
225
  aria-label=${this.label || nothing}
226
+ @keydown=${this.handleListKeydown}
133
227
  @helix-before-change=${this.handleChange}
134
228
  @change=${this.stopInner}
135
229
  @input=${this.stopInner}
@@ -147,7 +241,8 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
147
241
  </slot>`;
148
242
  }
149
243
  get triggerNames() {
150
- if (this.hasAttribute('aria-labelledby')) {
244
+ const outside = (this.internals.labels?.length ?? 0) > 0 && !this.labeled;
245
+ if (this.hasAttribute('aria-labelledby') || outside) {
151
246
  return nothing;
152
247
  }
153
248
  return this.labeled ? `${this.labelId} value` : 'value';
@@ -168,7 +263,7 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
168
263
  @keydown=${this.handleTriggerKeydown}
169
264
  >
170
265
  <slot name="start"></slot>
171
- <span part="value" id="value">${this.display}</span>
266
+ <span part="value" id="value">${this.renderValue()}</span>
172
267
  ${''}
173
268
  <slot name="end"></slot>
174
269
  ${''}
@@ -199,6 +294,9 @@ __decorate([
199
294
  __decorate([
200
295
  property()
201
296
  ], HelixHeadlessSelect.prototype, "placeholder", void 0);
297
+ __decorate([
298
+ property({ attribute: false })
299
+ ], HelixHeadlessSelect.prototype, "valueContent", void 0);
202
300
  HelixHeadlessSelect = __decorate([
203
301
  customElement('helix-headless-select')
204
302
  ], HelixHeadlessSelect);
@@ -4,23 +4,59 @@ import { type PropertyValues, type TemplateResult } from 'lit';
4
4
  * @summary A placeholder shown in place of content that is still loading.
5
5
  *
6
6
  * @description
7
- * Sets `aria-busy` and a polite live region, so a screen reader announces that
8
- * the region is loading. Both are set through `ElementInternals`, which writes
9
- * no attributes.
7
+ * A skeleton is hidden from screen readers unless it has a `label`. An empty
8
+ * placeholder has nothing to say, and a page of them would be a page of
9
+ * unnamed shapes. Tell a screen reader that a region is loading on the region
10
+ * itself: set `aria-busy="true"` on it while it loads, and put a `Loader` or a
11
+ * status message in it. Set `label` on one skeleton to have it read out by
12
+ * name, such as "Loading profile", when a reader reaches it. The role and the
13
+ * name are set through `ElementInternals`, which writes no attributes.
10
14
  *
11
15
  * There is no `shape` property. Whether a placeholder is a line, a block, or a
12
16
  * circle is presentation, which belongs to the styled tier.
13
17
  *
18
+ * **Put the real content inside and set `loading`, and the skeleton takes its
19
+ * size.** While `loading` is set the content is inert, the host is
20
+ * `aria-busy`, and the styled tier hides the content and paints the
21
+ * placeholder over its box, so nothing moves when the content arrives. Take
22
+ * `loading` off and the content is shown as it is. An empty skeleton is a
23
+ * placeholder of its own size, as before.
24
+ *
14
25
  * @since 0.1.0
15
26
  * @status stable
16
27
  * @category feedback
17
28
  * @aliases Placeholder, Shimmer, Ghost
18
29
  * @documentation https://fusion.dev/helix/components/skeleton
19
30
  *
31
+ * @slot - The content the skeleton stands in for, which sizes it. Optional.
32
+ *
33
+ * @csspart content - The box around the content, inert while `loading`.
20
34
  * @csspart skeleton - The placeholder shape.
21
35
  */
22
36
  export declare class HelixHeadlessSkeleton extends HelixElement {
37
+ /**
38
+ * What is loading, read out as the placeholder's name, such as "Loading
39
+ * profile".
40
+ *
41
+ * Absent hides the placeholder from screen readers.
42
+ */
43
+ label: string;
44
+ /**
45
+ * Whether the content inside is still loading, which keeps it inert and
46
+ * draws the placeholder over it.
47
+ *
48
+ * Absent shows the content. An empty skeleton is a placeholder whatever
49
+ * this says. Reflected, because the styled tier hides the content on it.
50
+ */
51
+ loading: boolean;
52
+ /**
53
+ * Whether the skeleton holds content, read off its children where they can
54
+ * be known. Not on a server, where an unlabeled skeleton is taken for an
55
+ * empty one until the page has its children.
56
+ */
57
+ private get wraps();
23
58
  protected willUpdate(changed: PropertyValues): void;
59
+ protected firstUpdated(changed: PropertyValues): void;
24
60
  protected render(): TemplateResult;
25
61
  }
26
62
  declare global {
@@ -4,18 +4,47 @@ 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 } from '@fusion.dev/helix-core';
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 HelixHeadlessSkeleton = class HelixHeadlessSkeleton extends HelixElement {
11
+ constructor() {
12
+ super(...arguments);
13
+ this.label = '';
14
+ this.loading = false;
15
+ }
16
+ get wraps() {
17
+ return (this.childrenKnown &&
18
+ [...this.childNodes].some((node) => node.nodeType === 1 || (node.textContent ?? '').trim() !== ''));
19
+ }
10
20
  willUpdate(changed) {
11
21
  super.willUpdate(changed);
12
- this.internals.ariaBusy = 'true';
13
- this.internals.ariaLive = 'polite';
22
+ this.internals.ariaBusy = this.loading ? 'true' : null;
23
+ if (this.label) {
24
+ hostAria(this.internals, { label: this.label, role: 'progressbar' });
25
+ this.internals.ariaHidden = null;
26
+ }
27
+ else {
28
+ hostAria(this.internals, { label: '', role: '' });
29
+ this.internals.ariaHidden = this.wraps ? null : 'true';
30
+ }
31
+ }
32
+ firstUpdated(changed) {
33
+ super.firstUpdated(changed);
34
+ this.finishHydration();
14
35
  }
15
36
  render() {
16
- return html `<span part="skeleton"></span>`;
37
+ return html `<span part="content" ?inert=${this.loading}
38
+ ><slot @slotchange=${() => this.requestUpdate()}></slot></span
39
+ ><span part="skeleton"></span>`;
17
40
  }
18
41
  };
42
+ __decorate([
43
+ property()
44
+ ], HelixHeadlessSkeleton.prototype, "label", void 0);
45
+ __decorate([
46
+ property({ reflect: true, type: Boolean })
47
+ ], HelixHeadlessSkeleton.prototype, "loading", void 0);
19
48
  HelixHeadlessSkeleton = __decorate([
20
49
  customElement('helix-headless-skeleton')
21
50
  ], HelixHeadlessSkeleton);
@@ -75,6 +75,16 @@ export declare class HelixHeadlessSkipLink extends HelixElement {
75
75
  * region.
76
76
  */
77
77
  private readonly skip;
78
+ /** The `href` the last warning was for, so a page hears once per target. */
79
+ private missed;
80
+ /**
81
+ * Says so when an in-page `href` names nothing.
82
+ *
83
+ * Asked on a press and not on connection: the link is first in the
84
+ * document, so its target is usually parsed or rendered after it, and an
85
+ * early check would warn about a page that is still arriving.
86
+ */
87
+ private reportMissing;
78
88
  protected render(): TemplateResult;
79
89
  }
80
90
  declare global {
@@ -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, elementById, HelixElement } from '@fusion.dev/helix-core';
7
+ import { customElement, elementById, HelixElement, reportRuntime } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  let HelixHeadlessSkipLink = class HelixHeadlessSkipLink extends HelixElement {
@@ -18,6 +18,7 @@ let HelixHeadlessSkipLink = class HelixHeadlessSkipLink extends HelixElement {
18
18
  }
19
19
  const target = this.target;
20
20
  if (!target) {
21
+ this.reportMissing();
21
22
  return;
22
23
  }
23
24
  event.preventDefault();
@@ -29,6 +30,7 @@ let HelixHeadlessSkipLink = class HelixHeadlessSkipLink extends HelixElement {
29
30
  target.addEventListener('blur', () => target.removeAttribute('tabindex'), { once: true });
30
31
  target.focus();
31
32
  };
33
+ this.missed = '';
32
34
  }
33
35
  static { this.shadowRootOptions = {
34
36
  ...HelixElement.shadowRootOptions,
@@ -40,6 +42,18 @@ let HelixHeadlessSkipLink = class HelixHeadlessSkipLink extends HelixElement {
40
42
  }
41
43
  return elementById(this, decodeURIComponent(this.href.slice(1)), { orDocument: true });
42
44
  }
45
+ reportMissing() {
46
+ if (!this.href.startsWith('#') || this.href.length < 2 || this.missed === this.href) {
47
+ return;
48
+ }
49
+ this.missed = this.href;
50
+ reportRuntime({
51
+ kind: 'missing-target',
52
+ message: `<${this.localName} href="${this.href}"> names no element in its tree, so the press ` +
53
+ 'moves focus nowhere. Give the main content that id, or point `href` at the id it has.',
54
+ tag: this.localName,
55
+ });
56
+ }
43
57
  render() {
44
58
  return html `<a part="link" href=${this.href} @click=${this.skip}><slot>${this.label}</slot></a>`;
45
59
  }