@fusion.dev/helix-headless 0.146.0 → 0.147.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 (239) hide show
  1. package/custom-elements.json +5266 -785
  2. package/dist/accordion/item/accordion-item.d.ts +38 -19
  3. package/dist/accordion/item/accordion-item.js +31 -9
  4. package/dist/accordion/root/accordion-root.d.ts +8 -2
  5. package/dist/accordion/root/accordion-root.js +3 -3
  6. package/dist/announcement/root/announcement-root.d.ts +13 -4
  7. package/dist/announcement/root/announcement-root.js +34 -6
  8. package/dist/area-chart/root/area-chart-root.d.ts +18 -1
  9. package/dist/area-chart/root/area-chart-root.js +11 -3
  10. package/dist/avatar/root/avatar-root.d.ts +31 -1
  11. package/dist/avatar/root/avatar-root.js +11 -1
  12. package/dist/badge/root/badge-root.d.ts +57 -6
  13. package/dist/badge/root/badge-root.js +88 -5
  14. package/dist/bar-chart/root/bar-chart-root.js +11 -20
  15. package/dist/bar-chart/stack.d.ts +10 -0
  16. package/dist/bar-chart/stack.js +8 -0
  17. package/dist/breadcrumbs/breadcrumb/breadcrumb.d.ts +14 -0
  18. package/dist/breadcrumbs/breadcrumb/breadcrumb.js +12 -2
  19. package/dist/breadcrumbs/root/breadcrumbs-root.js +2 -2
  20. package/dist/button/root/button-root.d.ts +24 -38
  21. package/dist/button/root/button-root.js +6 -37
  22. package/dist/button/submitter.d.ts +27 -0
  23. package/dist/button/submitter.js +25 -0
  24. package/dist/card/root/card-root.d.ts +12 -1
  25. package/dist/card/root/card-root.js +11 -4
  26. package/dist/chat/root/chat-root.d.ts +15 -1
  27. package/dist/chat/root/chat-root.js +11 -2
  28. package/dist/checkbox/root/checkbox-root.js +5 -7
  29. package/dist/checkbox-group/root/checkbox-group-root.js +4 -3
  30. package/dist/checkbox-group/select-all.d.ts +0 -6
  31. package/dist/checkbox-group/select-all.js +0 -5
  32. package/dist/code-block/root/code-block-root.d.ts +35 -14
  33. package/dist/code-block/root/code-block-root.js +17 -3
  34. package/dist/color-input/root/color-input-root.d.ts +11 -0
  35. package/dist/color-input/root/color-input-root.js +12 -5
  36. package/dist/color-picker/channels.js +1 -2
  37. package/dist/color-picker/root/color-picker-root.d.ts +32 -24
  38. package/dist/color-picker/root/color-picker-root.js +101 -51
  39. package/dist/color-picker/wheel.d.ts +17 -0
  40. package/dist/color-picker/wheel.js +6 -0
  41. package/dist/combobox/root/combobox-root.d.ts +13 -0
  42. package/dist/combobox/root/combobox-root.js +14 -1
  43. package/dist/command/root/command-root.d.ts +3 -1
  44. package/dist/command/root/command-root.js +2 -19
  45. package/dist/copy-button/root/copy-button-root.d.ts +16 -1
  46. package/dist/copy-button/root/copy-button-root.js +7 -2
  47. package/dist/data-table/keys.d.ts +0 -18
  48. package/dist/data-table/keys.js +1 -11
  49. package/dist/data-table/root/data-table-root.js +13 -7
  50. package/dist/date-input/root/date-input-root.d.ts +8 -1
  51. package/dist/date-input/root/date-input-root.js +8 -5
  52. package/dist/date-picker/root/date-picker-root.d.ts +14 -9
  53. package/dist/date-picker/root/date-picker-root.js +21 -16
  54. package/dist/description-list/item/description-list-item.d.ts +18 -4
  55. package/dist/description-list/item/description-list-item.js +8 -4
  56. package/dist/donut-chart/root/donut-chart-root.js +13 -6
  57. package/dist/donut-chart/slice-label.d.ts +35 -0
  58. package/dist/donut-chart/slice-label.js +11 -0
  59. package/dist/drawer/root/drawer-root.d.ts +4 -2
  60. package/dist/drawer/root/drawer-root.js +15 -3
  61. package/dist/drilldown/item/drilldown-item.d.ts +21 -1
  62. package/dist/drilldown/item/drilldown-item.js +39 -4
  63. package/dist/drilldown/root/drilldown-root.js +3 -2
  64. package/dist/element-events.generated.js +4 -1
  65. package/dist/empty/root/empty-root.d.ts +31 -1
  66. package/dist/empty/root/empty-root.js +26 -5
  67. package/dist/file-input/root/file-input-root.d.ts +34 -6
  68. package/dist/file-input/root/file-input-root.js +54 -19
  69. package/dist/file-input/status.d.ts +24 -0
  70. package/dist/file-input/status.js +10 -0
  71. package/dist/filter-input/chip/filter-chip.d.ts +23 -0
  72. package/dist/filter-input/chip/filter-chip.js +11 -2
  73. package/dist/filter-input/root/filter-input-root.d.ts +8 -0
  74. package/dist/filter-input/root/filter-input-root.js +17 -27
  75. package/dist/format-date/options.d.ts +3 -4
  76. package/dist/format-date/options.js +2 -2
  77. package/dist/format-date/root/format-date-root.d.ts +18 -1
  78. package/dist/format-date/root/format-date-root.js +19 -23
  79. package/dist/heatmap/root/buckets.d.ts +21 -0
  80. package/dist/heatmap/root/buckets.js +34 -0
  81. package/dist/heatmap/root/heatmap-root.d.ts +64 -3
  82. package/dist/heatmap/root/heatmap-root.js +89 -19
  83. package/dist/icon/index.d.ts +1 -1
  84. package/dist/icon/index.js +1 -1
  85. package/dist/icon/registry.d.ts +26 -1
  86. package/dist/icon/registry.js +12 -1
  87. package/dist/icon/root/icon-root.d.ts +13 -0
  88. package/dist/icon/root/icon-root.js +23 -2
  89. package/dist/icon/root/index.d.ts +1 -1
  90. package/dist/icon/root/index.js +1 -1
  91. package/dist/image/root/image-root.d.ts +9 -0
  92. package/dist/image/root/image-root.js +6 -1
  93. package/dist/input/root/input-root.d.ts +23 -2
  94. package/dist/input/root/input-root.js +12 -8
  95. package/dist/kbd/keys.d.ts +29 -0
  96. package/dist/kbd/keys.js +52 -0
  97. package/dist/kbd/root/kbd-root.d.ts +20 -3
  98. package/dist/kbd/root/kbd-root.js +24 -50
  99. package/dist/legend/hidden-series.d.ts +10 -2
  100. package/dist/legend/hidden-series.js +5 -2
  101. package/dist/legend/root/legend-root.d.ts +25 -7
  102. package/dist/legend/root/legend-root.js +22 -10
  103. package/dist/lightbox/root/lightbox-root.d.ts +50 -5
  104. package/dist/lightbox/root/lightbox-root.js +76 -8
  105. package/dist/lightbox/zoom.d.ts +2 -0
  106. package/dist/lightbox/zoom.js +5 -3
  107. package/dist/line-chart/root/line-chart-root.d.ts +16 -0
  108. package/dist/line-chart/root/line-chart-root.js +10 -2
  109. package/dist/list/root/list-root.d.ts +10 -2
  110. package/dist/list/root/list-root.js +26 -5
  111. package/dist/list/selection.d.ts +17 -0
  112. package/dist/list/selection.js +16 -0
  113. package/dist/listbox/root/listbox-root.d.ts +9 -5
  114. package/dist/listbox/root/listbox-root.js +29 -37
  115. package/dist/megamenu/hand-over.d.ts +75 -0
  116. package/dist/megamenu/hand-over.js +67 -0
  117. package/dist/megamenu/root/megamenu-root.d.ts +38 -14
  118. package/dist/megamenu/root/megamenu-root.js +36 -50
  119. package/dist/menu/item/menu-item.js +4 -4
  120. package/dist/menu/root/menu-root.d.ts +3 -0
  121. package/dist/meter/root/meter-root.d.ts +42 -0
  122. package/dist/meter/root/meter-root.js +33 -7
  123. package/dist/notifications/root/notifications-root.d.ts +10 -1
  124. package/dist/notifications/root/notifications-root.js +11 -1
  125. package/dist/number-input/grain.d.ts +28 -0
  126. package/dist/number-input/grain.js +14 -0
  127. package/dist/number-input/root/number-input-root.d.ts +17 -1
  128. package/dist/number-input/root/number-input-root.js +29 -18
  129. package/dist/object-inspector/root/object-inspector-root.d.ts +13 -6
  130. package/dist/object-inspector/root/object-inspector-root.js +18 -17
  131. package/dist/option/index.d.ts +1 -0
  132. package/dist/option/index.js +1 -0
  133. package/dist/option/separator/option-separator.d.ts +30 -0
  134. package/dist/option/separator/option-separator.js +21 -0
  135. package/dist/outline/root/outline-root.d.ts +12 -0
  136. package/dist/outline/root/outline-root.js +5 -19
  137. package/dist/pagination/size/pagination-size.d.ts +5 -0
  138. package/dist/pagination/size/pagination-size.js +4 -2
  139. package/dist/pin-input/code.d.ts +28 -1
  140. package/dist/pin-input/code.js +12 -0
  141. package/dist/pin-input/root/pin-input-root.d.ts +98 -121
  142. package/dist/pin-input/root/pin-input-root.js +157 -148
  143. package/dist/popover/hide-outside.js +2 -9
  144. package/dist/popover/root/popover-root.d.ts +7 -1
  145. package/dist/popover/root/popover-root.js +9 -1
  146. package/dist/progress/root/progress-root.d.ts +27 -0
  147. package/dist/progress/root/progress-root.js +25 -7
  148. package/dist/qr-code/export.d.ts +19 -0
  149. package/dist/qr-code/export.js +14 -0
  150. package/dist/qr-code/name.d.ts +11 -0
  151. package/dist/qr-code/name.js +7 -0
  152. package/dist/qr-code/root/qr-code-root.d.ts +17 -6
  153. package/dist/qr-code/root/qr-code-root.js +10 -12
  154. package/dist/radio/group/radio-group.d.ts +15 -0
  155. package/dist/radio/root/radio-root.js +2 -2
  156. package/dist/rating/root/rating-root.d.ts +22 -9
  157. package/dist/rating/root/rating-root.js +15 -39
  158. package/dist/scatter-chart/root/scatter-chart-root.d.ts +30 -0
  159. package/dist/scatter-chart/root/scatter-chart-root.js +34 -7
  160. package/dist/scroll-area/scrollbar.d.ts +0 -8
  161. package/dist/scroll-area/scrollbar.js +1 -1
  162. package/dist/select/align.d.ts +47 -0
  163. package/dist/select/align.js +7 -0
  164. package/dist/select/root/select-root.d.ts +59 -3
  165. package/dist/select/root/select-root.js +72 -12
  166. package/dist/slider/root/slider-root.d.ts +18 -1
  167. package/dist/slider/root/slider-root.js +22 -0
  168. package/dist/sparkline/readings.d.ts +30 -12
  169. package/dist/sparkline/readings.js +32 -15
  170. package/dist/sparkline/root/sparkline-root.d.ts +46 -3
  171. package/dist/sparkline/root/sparkline-root.js +60 -21
  172. package/dist/speed-dial/item/speed-dial-item.d.ts +1 -1
  173. package/dist/speed-dial/item/speed-dial-item.js +2 -2
  174. package/dist/speed-dial/root/speed-dial-root.d.ts +6 -0
  175. package/dist/speed-dial/root/speed-dial-root.js +9 -1
  176. package/dist/splitter/root/splitter-root.d.ts +3 -0
  177. package/dist/splitter/root/splitter-root.js +2 -0
  178. package/dist/steps/root/steps-root.d.ts +8 -1
  179. package/dist/steps/root/steps-root.js +6 -1
  180. package/dist/steps/step/step.d.ts +1 -0
  181. package/dist/steps/step/step.js +1 -1
  182. package/dist/table/column/table-column.d.ts +6 -0
  183. package/dist/table/column/table-column.js +5 -1
  184. package/dist/table/root/table-root.js +13 -6
  185. package/dist/table/row/table-row.d.ts +24 -0
  186. package/dist/table/row/table-row.js +37 -1
  187. package/dist/tabs/root/tabs-root.d.ts +5 -3
  188. package/dist/tag/group/tag-group.d.ts +16 -4
  189. package/dist/tag/group/tag-group.js +19 -7
  190. package/dist/textarea/root/textarea-root.d.ts +14 -2
  191. package/dist/textarea/root/textarea-root.js +6 -7
  192. package/dist/time-input/root/time-input-root.d.ts +5 -1
  193. package/dist/time-input/root/time-input-root.js +10 -7
  194. package/dist/time-picker/root/time-picker-root.d.ts +11 -14
  195. package/dist/time-picker/root/time-picker-root.js +15 -23
  196. package/dist/toast/item/toast-item.d.ts +3 -3
  197. package/dist/toast/item/toast-item.js +0 -1
  198. package/dist/toast/root/toast-root.d.ts +28 -4
  199. package/dist/toast/root/toast-root.js +58 -12
  200. package/dist/toast/words.d.ts +12 -0
  201. package/dist/toast/words.js +25 -0
  202. package/dist/toggle/group/toggle-group.d.ts +29 -5
  203. package/dist/toggle/group/toggle-group.js +26 -41
  204. package/dist/toggle/root/toggle-root.js +2 -4
  205. package/dist/toolbar/root/toolbar-root.d.ts +21 -5
  206. package/dist/toolbar/root/toolbar-root.js +52 -8
  207. package/dist/tooltip/root/tooltip-root.d.ts +7 -1
  208. package/dist/tooltip/root/tooltip-root.js +10 -1
  209. package/dist/tour/root/tour-root.d.ts +15 -0
  210. package/dist/tour/root/tour-root.js +20 -1
  211. package/dist/tour/stop/tour-stop.d.ts +3 -0
  212. package/dist/tour/stop/tour-stop.js +3 -3
  213. package/dist/tree/root/tree-root.js +2 -2
  214. package/dist/trend/figure.d.ts +16 -0
  215. package/dist/trend/figure.js +8 -0
  216. package/dist/trend/root/trend-root.d.ts +41 -2
  217. package/dist/trend/root/trend-root.js +29 -3
  218. package/dist/video/root/extra-controls.d.ts +12 -0
  219. package/dist/video/root/extra-controls.js +6 -2
  220. package/dist/video/root/video-root.d.ts +5 -1
  221. package/dist/video-playlist/root/video-playlist-root.d.ts +11 -1
  222. package/dist/video-playlist/root/video-playlist-root.js +3 -0
  223. package/dist/virtual-list/fill.d.ts +11 -0
  224. package/dist/virtual-list/fill.js +9 -0
  225. package/dist/virtual-list/keys.d.ts +16 -7
  226. package/dist/virtual-list/keys.js +17 -6
  227. package/dist/virtual-list/plan.d.ts +17 -1
  228. package/dist/virtual-list/plan.js +13 -2
  229. package/dist/virtual-list/root/virtual-list-root.d.ts +25 -3
  230. package/dist/virtual-list/root/virtual-list-root.js +48 -24
  231. package/lint.json +1 -1
  232. package/package.json +5 -5
  233. package/types/jsx.d.ts +2979 -738
  234. package/types/svelte.d.ts +1 -0
  235. package/types/vue.d.ts +619 -202
  236. package/dist/color-picker/swatches.d.ts +0 -12
  237. package/dist/color-picker/swatches.js +0 -26
  238. package/dist/rating/steps.d.ts +0 -7
  239. package/dist/rating/steps.js +0 -9
@@ -73,6 +73,9 @@ declare const HelixHeadlessSelect_base: (new (...args: any[]) => import("@fusion
73
73
  * @csspart trigger - The closed control, which shows the chosen value.
74
74
  * @csspart value - The chosen option's text inside the trigger.
75
75
  *
76
+ * @cssproperty --floating-anchor-inline-size - The trigger's width, written on the panel while it is open, as in `min-inline-size: var(--floating-anchor-inline-size)`. Read it; a value set here is written over.
77
+ * @cssproperty --floating-available-block-size - The room between the panel and the edge of the screen it opens toward, written on the panel while it is open. Read it; a value set here is written over.
78
+ *
76
79
  * @cssstate invalid - The control has an error to show, from the browser's validation or from your `feedback`.
77
80
  * @cssstate open - While the list is open.
78
81
  */
@@ -89,6 +92,29 @@ export declare class HelixHeadlessSelect extends HelixHeadlessSelect_base {
89
92
  * the filtering, the empty state and the keyboard.
90
93
  */
91
94
  filterable: boolean;
95
+ /**
96
+ * Whether the trigger holds its group's tab stop.
97
+ *
98
+ * A toolbar keeps one tab stop across its controls, and writing `tabindex`
99
+ * onto this element would change markup the consumer rendered, which a React
100
+ * consumer's hydration reports. A toolbar sets this property instead, and
101
+ * the stop moves on the trigger inside the shadow root, as Button's does.
102
+ *
103
+ * No attribute, because it is coordination between elements rather than
104
+ * anything a consumer sets.
105
+ */
106
+ focusable: boolean;
107
+ /**
108
+ * The open list's name where nothing names the Select, read by screen
109
+ * readers. The list has no visible name.
110
+ *
111
+ * A Select named by `label`, `aria-label`, `aria-labelledby` or a `<label>`
112
+ * gives its list that name, and this is not used. Unnamed, the Select is
113
+ * reported once by the missing-name warning, and its list is not reported a
114
+ * second time for a name the page never wrote: the list is this element's,
115
+ * and so is the fallback. Combobox calls its list the same.
116
+ */
117
+ listLabel: string;
92
118
  /**
93
119
  * Whether the options are still loading.
94
120
  *
@@ -109,6 +135,18 @@ export declare class HelixHeadlessSelect extends HelixHeadlessSelect_base {
109
135
  * Shown on the trigger while nothing is chosen.
110
136
  */
111
137
  placeholder: string;
138
+ /**
139
+ * Where the list opens: `floating`, under the trigger and above it where
140
+ * there is no room, or `item-aligned`, over the trigger with the chosen
141
+ * option where the trigger is, as a native select does on macOS and Radix
142
+ * Select does by default.
143
+ *
144
+ * Over the trigger, a reader's eye stays on the answer as the list opens
145
+ * round it, and a short move reaches the options either side. Near an edge
146
+ * of the screen the list moves to stay on it, so the option may sit beside
147
+ * the trigger rather than on it. It follows the page as it scrolls.
148
+ */
149
+ position: 'floating' | 'item-aligned';
112
150
  /**
113
151
  * What the trigger shows for the chosen options: a function given them, in
114
152
  * the order they appear, that returns text, an element or a Lit template.
@@ -215,8 +253,26 @@ export declare class HelixHeadlessSelect extends HelixHeadlessSelect_base {
215
253
  private readonly handleLinkFollowed;
216
254
  connectedCallback(): void;
217
255
  disconnectedCallback(): void;
256
+ /**
257
+ * Puts the open list over the trigger, with the option it opened on where
258
+ * the trigger is, for `position="item-aligned"`.
259
+ *
260
+ * Measured once the list has drawn and scrolled to that option, and again as
261
+ * the page scrolls or the window changes size. The offset of the option in
262
+ * the list is read from the boxes on screen and divided by the list's scale,
263
+ * since a styled list opens from a little under full size. Written as the
264
+ * floating controller writes a position, so the same entrance runs and the
265
+ * controller takes it back on close.
266
+ */
267
+ private readonly alignOver;
268
+ /** The option the open list was put over the trigger by. */
269
+ private alignedTo;
270
+ /** Stops following the page, once the list over the trigger closes. */
271
+ private stopAligning;
218
272
  /** Opens the list with the highlight where the opening key says. */
219
273
  private openAt;
274
+ /** Puts focus on the trigger, which is what a toolbar moving its stop here asks. */
275
+ focus(options?: FocusOptions): void;
220
276
  /** Closes the list and puts focus back where it came from. */
221
277
  private close;
222
278
  private readonly handleChange;
@@ -279,9 +335,9 @@ export declare class HelixHeadlessSelect extends HelixHeadlessSelect_base {
279
335
  */
280
336
  protected renderIndicator(): TemplateResult;
281
337
  /**
282
- * What names the list once it opens: the `label`, the host's `aria-label`,
283
- * or the words of what names the host from outside, its `aria-labelledby`
284
- * or a `<label>` for it.
338
+ * What names the list once it opens, read through core's `hostWords` in
339
+ * the order a browser names the host: its `aria-labelledby`, its
340
+ * `aria-label`, the `label`, then a `<label>` for it.
285
341
  *
286
342
  * **Words rather than a reference**, because the list is inside another
287
343
  * element's shadow root and neither an IDREF nor the host's labels reach it.
@@ -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, afterLinkFollowed, customElement, elementById, HelixFormElement, hostAria, MixinControllerOpen, MixinControllerValue, MixinField, PopoverController, reportValues, typesCharacter, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, afterLinkFollowed, customElement, HelixFormElement, hostAria, hostWords, isRtl, 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';
@@ -12,6 +12,7 @@ import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
12
12
  import { html } from 'lit/static-html.js';
13
13
  import { optionsAmong } from '../../option/root/option-root.js';
14
14
  import '../../listbox/index.js';
15
+ import { over } from '../align.js';
15
16
  let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(MixinControllerValue(MixinField(HelixFormElement))) {
16
17
  constructor() {
17
18
  super(...arguments);
@@ -24,10 +25,13 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
24
25
  });
25
26
  this.emptyText = 'No matches';
26
27
  this.filterable = false;
28
+ this.focusable = true;
29
+ this.listLabel = 'Options';
27
30
  this.loading = false;
28
31
  this.loadingText = 'Loading';
29
32
  this.multiple = false;
30
33
  this.placeholder = 'Choose';
34
+ this.position = 'floating';
31
35
  this.panel = new PopoverController(this, {
32
36
  anchor: () => this.partNamed('trigger'),
33
37
  boundary: () => [
@@ -37,6 +41,7 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
37
41
  offset: () => 4,
38
42
  onDismiss: () => this.close(),
39
43
  open: () => this.open,
44
+ place: () => this.position !== 'item-aligned',
40
45
  placement: 'bottom-start',
41
46
  surface: () => this.partNamed('surface'),
42
47
  });
@@ -46,6 +51,45 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
46
51
  this.handleLinkFollowed = (event) => {
47
52
  afterLinkFollowed(event, () => this.requestOpen(false));
48
53
  };
54
+ this.alignOver = () => {
55
+ const surface = this.partNamed('surface');
56
+ const trigger = this.partNamed('trigger');
57
+ if (!this.open || this.position !== 'item-aligned' || !surface || !trigger) {
58
+ return;
59
+ }
60
+ const margin = 8;
61
+ const at = trigger.getBoundingClientRect();
62
+ surface.style.setProperty('--floating-anchor-inline-size', `${Math.round(at.width)}px`);
63
+ surface.style.setProperty('--floating-available-block-size', `${Math.max(0, window.innerHeight - margin * 2)}px`);
64
+ const shown = optionsAmong(this.knownChildren).filter((option) => !option.hidden);
65
+ this.alignedTo ??= shown.find((one) => one.active) ?? this.chosenOptions[0] ?? shown[0];
66
+ const option = this.alignedTo;
67
+ const box = surface.getBoundingClientRect();
68
+ const scale = surface.offsetHeight > 0 ? box.height / surface.offsetHeight : 1;
69
+ const row = option?.getBoundingClientRect();
70
+ const { left, top } = over({
71
+ margin,
72
+ option: row
73
+ ? { height: row.height / scale, top: (row.top - box.top) / scale }
74
+ : { height: 0, top: 0 },
75
+ panel: { height: surface.offsetHeight, width: surface.offsetWidth },
76
+ rtl: isRtl(this),
77
+ trigger: at,
78
+ viewport: { height: window.innerHeight, width: window.innerWidth },
79
+ });
80
+ for (const [edge, value] of [
81
+ ['bottom', 'auto'],
82
+ ['left', '0'],
83
+ ['right', 'auto'],
84
+ ['top', '0'],
85
+ ]) {
86
+ surface.style.setProperty(edge, value, 'important');
87
+ }
88
+ surface.style.position = 'fixed';
89
+ surface.style.translate = `${left}px ${top}px`;
90
+ surface.setAttribute('data-placed', '');
91
+ };
92
+ this.alignedTo = undefined;
49
93
  this.handleChange = (event) => {
50
94
  const next = event.detail.value;
51
95
  if (!this.requestValue(next)) {
@@ -138,16 +182,24 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
138
182
  }
139
183
  disconnectedCallback() {
140
184
  super.disconnectedCallback();
185
+ this.stopAligning();
141
186
  this.removeEventListener('helix-select', this.handleLinkFollowed);
142
187
  for (const watcher of this.watchers) {
143
188
  watcher.disconnect();
144
189
  }
145
190
  this.watchers = [];
146
191
  }
192
+ stopAligning() {
193
+ window.removeEventListener('resize', this.alignOver);
194
+ window.removeEventListener('scroll', this.alignOver, { capture: true });
195
+ }
147
196
  openAt(where) {
148
197
  this.opening = where;
149
198
  this.requestOpen(true);
150
199
  }
200
+ focus(options) {
201
+ this.partNamed('trigger')?.focus(options);
202
+ }
151
203
  close() {
152
204
  if (!this.open) {
153
205
  return;
@@ -202,9 +254,16 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
202
254
  this.listbox?.highlight(this.opening);
203
255
  this.opening = 'selected';
204
256
  this.listbox?.focus();
257
+ if (this.position === 'item-aligned') {
258
+ this.alignedTo = undefined;
259
+ void this.listbox?.updateComplete.then(() => this.alignOver());
260
+ window.addEventListener('resize', this.alignOver);
261
+ window.addEventListener('scroll', this.alignOver, { capture: true, passive: true });
262
+ }
205
263
  }
206
264
  }
207
265
  else {
266
+ this.stopAligning();
208
267
  this.listbox?.requestFilter('');
209
268
  }
210
269
  }
@@ -222,7 +281,7 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
222
281
  ?multiple=${this.multiple}
223
282
  ?filterable=${this.filterable}
224
283
  manual
225
- aria-label=${this.listName || nothing}
284
+ aria-label=${this.listName || this.listLabel || nothing}
226
285
  @keydown=${this.handleListKeydown}
227
286
  @helix-before-change=${this.handleChange}
228
287
  @change=${this.stopInner}
@@ -241,16 +300,7 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
241
300
  </slot>`;
242
301
  }
243
302
  get listName() {
244
- const own = this.label || this.getAttribute('aria-label');
245
- if (own) {
246
- return own;
247
- }
248
- const ids = (this.getAttribute('aria-labelledby') ?? '').split(/\s+/).filter(Boolean);
249
- const named = ids.length > 0 ? ids.map((id) => elementById(this, id)) : [...(this.labels ?? [])];
250
- return named
251
- .map((one) => (one?.textContent ?? '').replace(/\s+/g, ' ').trim())
252
- .filter(Boolean)
253
- .join(' ');
303
+ return hostWords(this, this.label);
254
304
  }
255
305
  get triggerNames() {
256
306
  const outside = (this.internals.labels?.length ?? 0) > 0 && !this.labeled;
@@ -270,6 +320,7 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
270
320
  aria-expanded=${this.open ? 'true' : 'false'}
271
321
  aria-describedby=${this.describedBy ?? nothing}
272
322
  aria-invalid=${this.invalidity ?? nothing}
323
+ tabindex=${this.focusable ? nothing : '-1'}
273
324
  ?disabled=${this.isDisabled}
274
325
  @click=${() => this.toggleOpen()}
275
326
  @keydown=${this.handleTriggerKeydown}
@@ -294,6 +345,12 @@ __decorate([
294
345
  __decorate([
295
346
  property({ reflect: true, type: Boolean })
296
347
  ], HelixHeadlessSelect.prototype, "filterable", void 0);
348
+ __decorate([
349
+ property({ attribute: false })
350
+ ], HelixHeadlessSelect.prototype, "focusable", void 0);
351
+ __decorate([
352
+ property({ attribute: 'list-label' })
353
+ ], HelixHeadlessSelect.prototype, "listLabel", void 0);
297
354
  __decorate([
298
355
  property({ reflect: true, type: Boolean })
299
356
  ], HelixHeadlessSelect.prototype, "loading", void 0);
@@ -306,6 +363,9 @@ __decorate([
306
363
  __decorate([
307
364
  property()
308
365
  ], HelixHeadlessSelect.prototype, "placeholder", void 0);
366
+ __decorate([
367
+ property()
368
+ ], HelixHeadlessSelect.prototype, "position", void 0);
309
369
  __decorate([
310
370
  property({ attribute: false })
311
371
  ], HelixHeadlessSelect.prototype, "valueContent", void 0);
@@ -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
  declare const HelixHeadlessSlider_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerValueInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinFieldInterface) & typeof HelixFormElement;
4
4
  /**
5
5
  * @summary A value chosen from a range by dragging.
@@ -77,7 +77,10 @@ declare const HelixHeadlessSlider_base: (new (...args: any[]) => import("@fusion
77
77
  * @csspart marker-label - The value a mark stands at, when `marker-labels` is set.
78
78
  * @csspart markers - The row of marks along the track.
79
79
  * @csspart output - The current value, shown beside the track.
80
+ * @csspart tooltip - One thumb's value in a bubble beside it, under `tooltip`. It carries `data-thumb` on a range, `start` or `end`, and `--slider-at`, where its thumb is as a fraction of the track.
81
+ * @csspart tooltips - The layer over the track that holds the bubbles, under `tooltip`.
80
82
  *
83
+ * @cssproperty --slider-at - Where a bubble's thumb is, as a fraction of the track, 0 to 1. Set on each `tooltip` part.
81
84
  * @cssproperty --slider-fraction - The value as a fraction of the range, 0 to 1.
82
85
  * @cssproperty --slider-from - Where the filled span starts, as a fraction of the track, 0 to 1: a range's lower thumb, or a single thumb's `origin`.
83
86
  * @cssproperty --slider-marker-at - Where one marker sits, as a percentage of the track.
@@ -193,6 +196,13 @@ export declare class HelixHeadlessSlider extends HelixHeadlessSlider_base {
193
196
  showValue: boolean;
194
197
  /** How far one press of an arrow key moves the thumb. */
195
198
  step: number;
199
+ /**
200
+ * Whether each thumb shows its value in a bubble beside it, as Web
201
+ * Awesome's `with-tooltip`. The styled slider shows the bubbles while the
202
+ * slider is under the pointer or holds focus. They repeat what each thumb
203
+ * already reports, so a screen reader skips them.
204
+ */
205
+ tooltip: boolean;
196
206
  /**
197
207
  * How many characters the readout has to be able to hold, widest case.
198
208
  *
@@ -307,6 +317,13 @@ export declare class HelixHeadlessSlider extends HelixHeadlessSlider_base {
307
317
  * such as `{value} degrees`. Empty announces the number alone.
308
318
  */
309
319
  valueText: string;
320
+ /**
321
+ * A bubble for each thumb, under `tooltip`, saying its value where it is.
322
+ * After the thumbs, so a stylesheet can reach a bubble from a focused
323
+ * thumb, and hidden from the accessibility tree for the reason the readout
324
+ * is: each thumb already reports its value.
325
+ */
326
+ protected renderTooltips(start: number, end: number): TemplateResult | typeof nothing;
310
327
  protected render(): TemplateResult;
311
328
  }
312
329
  declare global {
@@ -26,6 +26,7 @@ let HelixHeadlessSlider = class HelixHeadlessSlider extends MixinControllerValue
26
26
  this.rangeText = '{start} to {end}';
27
27
  this.showValue = false;
28
28
  this.step = 1;
29
+ this.tooltip = false;
29
30
  this.handleInput = (event) => {
30
31
  event.stopPropagation();
31
32
  const control = event.target;
@@ -181,6 +182,22 @@ let HelixHeadlessSlider = class HelixHeadlessSlider extends MixinControllerValue
181
182
  super.updated(changed);
182
183
  this.sync();
183
184
  }
185
+ renderTooltips(start, end) {
186
+ if (!this.tooltip) {
187
+ return nothing;
188
+ }
189
+ const bubble = (fraction, text, thumb) => html `<span part="tooltip" data-thumb=${thumb ?? nothing} style="--slider-at: ${fraction}"
190
+ >${text}</span
191
+ >`;
192
+ return html `<div part="tooltips" aria-hidden="true">
193
+ ${this.range
194
+ ? [
195
+ bubble(this.along(start), this.said(start), 'start'),
196
+ bubble(this.along(end), this.said(end), 'end'),
197
+ ]
198
+ : bubble(this.fraction, this.said(this.value))}
199
+ </div>`;
200
+ }
184
201
  render() {
185
202
  const [start, end] = this.bounds;
186
203
  const thumb = (which, at) => html `<input
@@ -212,6 +229,7 @@ let HelixHeadlessSlider = class HelixHeadlessSlider extends MixinControllerValue
212
229
  part="field"
213
230
  data-markers=${this.markerValues.length > 0 ? '' : nothing}
214
231
  data-range=${this.range ? '' : nothing}
232
+ data-tooltip=${this.tooltip ? '' : nothing}
215
233
  style="--slider-fraction: ${this.fraction}; --slider-from: ${this.along(start)}; --slider-to: ${this.along(end)}"
216
234
  >
217
235
  ${this.range ? thumb('start', start) : nothing}
@@ -247,6 +265,7 @@ let HelixHeadlessSlider = class HelixHeadlessSlider extends MixinControllerValue
247
265
  >`)}
248
266
  </div>`
249
267
  : nothing}
268
+ ${this.renderTooltips(start, end)}
250
269
  ${this.showValue
251
270
  ? html `<output
252
271
  part="output"
@@ -310,6 +329,9 @@ __decorate([
310
329
  __decorate([
311
330
  property({ type: Number })
312
331
  ], HelixHeadlessSlider.prototype, "step", void 0);
332
+ __decorate([
333
+ property({ type: Boolean })
334
+ ], HelixHeadlessSlider.prototype, "tooltip", void 0);
313
335
  __decorate([
314
336
  property({ attribute: 'value-text' })
315
337
  ], HelixHeadlessSlider.prototype, "valueText", void 0);
@@ -1,18 +1,36 @@
1
- /** Where the highest and the lowest readings are, by place, or none. */
2
- export interface Extremes {
3
- /** The place of the highest reading, the first where several tie. */
4
- high?: number;
5
- /** The place of the lowest reading, the first where several tie. */
6
- low?: number;
7
- }
8
- /**
9
- * Where the highest and the lowest readings stand. A blank slot is passed
10
- * over, and a list with nothing in it has neither.
11
- */
12
- export declare const extremesOf: (slots: readonly (number | undefined)[]) => Extremes;
1
+ import { type Point } from '@fusion.dev/helix-core';
13
2
  /**
14
3
  * A reading in words, for the readout: the row's name where a table gave one,
15
4
  * the reading as it is written, and where it stands in brackets, such as
16
5
  * "Mon: 12 (3 of 7)". The same order every chart's readout uses.
17
6
  */
18
7
  export declare const readingWords: (label: string, text: string, position: string) => string;
8
+ /**
9
+ * The readings in their places: `count` slots, each holding the value read at
10
+ * that place, or nothing where the place is blank. A point outside the places
11
+ * is left out.
12
+ */
13
+ export declare const slotsOf: (points: readonly {
14
+ at: number;
15
+ value: number;
16
+ }[], count: number) => (number | undefined)[];
17
+ /** One unbroken stretch of the line: its drawn path and the points it runs through. */
18
+ export interface Run {
19
+ /** The path the line draws through the points. */
20
+ path: string;
21
+ /** The points, in drawing coordinates. */
22
+ run: readonly Point[];
23
+ }
24
+ /**
25
+ * Each run of the line closed down to `floor`, the path the wash fills: the
26
+ * run's own path, then down from its last point, across and back up to its
27
+ * first. Empty where there are no runs.
28
+ */
29
+ export declare const areaPath: (runs: readonly Run[], floor: number) => string;
30
+ /**
31
+ * What the readings come to, in `template`: `{first}` and `{last}` for the
32
+ * first and the last reading, `{high}` and `{low}` for the highest and the
33
+ * lowest, each written by `write`. Empty where there are fewer than two
34
+ * readings, which make no trend.
35
+ */
36
+ export declare const overviewWords: (template: string, slots: readonly (number | undefined)[], write: (at: number) => string) => string;
@@ -1,19 +1,36 @@
1
- export const extremesOf = (slots) => {
2
- const found = {};
3
- slots.forEach((value, at) => {
4
- if (value === undefined) {
5
- return;
6
- }
7
- if (found.high === undefined || value > slots[found.high]) {
8
- found.high = at;
9
- }
10
- if (found.low === undefined || value < slots[found.low]) {
11
- found.low = at;
12
- }
13
- });
14
- return found;
15
- };
1
+ import { extremesOf, fillTemplate } from '@fusion.dev/helix-core';
16
2
  export const readingWords = (label, text, position) => {
17
3
  const named = label === '' ? text : text === '' ? label : `${label}: ${text}`;
18
4
  return position === '' ? named : `${named} (${position})`;
19
5
  };
6
+ export const slotsOf = (points, count) => {
7
+ const slots = Array.from({ length: count }, () => undefined);
8
+ for (const point of points) {
9
+ if (point.at >= 0 && point.at < count) {
10
+ slots[point.at] = point.value;
11
+ }
12
+ }
13
+ return slots;
14
+ };
15
+ export const areaPath = (runs, floor) => runs
16
+ .map((one) => {
17
+ const first = one.run[0];
18
+ const last = one.run.at(-1);
19
+ return `${one.path}L${last[0]},${floor}L${first[0]},${floor}Z`;
20
+ })
21
+ .join('');
22
+ export const overviewWords = (template, slots, write) => {
23
+ const placed = slots.flatMap((value, at) => (value === undefined ? [] : [at]));
24
+ const first = placed[0];
25
+ const last = placed.at(-1);
26
+ const { high, low } = extremesOf(slots);
27
+ if (placed.length < 2 || first === undefined || last === undefined) {
28
+ return '';
29
+ }
30
+ return fillTemplate(template, {
31
+ first: write(first),
32
+ high: write(high),
33
+ last: write(last),
34
+ low: write(low),
35
+ });
36
+ };
@@ -1,4 +1,4 @@
1
- import { ChartPointerController, type Curve, HelixElement, SourceController } from '@fusion.dev/helix-core';
1
+ import { ChartPointerController, type Curve, HelixElement, type Series, SourceController } from '@fusion.dev/helix-core';
2
2
  import { nothing, type PropertyValues, type TemplateResult } from 'lit';
3
3
  /**
4
4
  * @summary A small chart with no axes that shows the shape of a series beside its number.
@@ -19,10 +19,16 @@ import { nothing, type PropertyValues, type TemplateResult } from 'lit';
19
19
  * the last, Escape puts the reading away, and the `live` part says each
20
20
  * reading with where it stands, such as "Mon: 12 (3 of 7)".
21
21
  *
22
+ * **A named sparkline describes itself.** Beside its name, a screen reader
23
+ * hears what the readings come to, the first and the last, the highest and
24
+ * the lowest, in `overview-label`: "From 12 to 20, highest 30, lowest 8". An
25
+ * `aria-label` on the element names it as `label` does.
26
+ *
22
27
  * A pointer resting on the drawing shows the reading under it: the `cursor`
23
28
  * part marks it and the `readout` part writes it. `marks` adds a mark on the
24
- * highest and the lowest reading. A blank cell in a `for` table is a gap in
25
- * the line.
29
+ * highest and the lowest reading, and `range` draws a band between two values,
30
+ * such as a normal range. A blank cell in a `for` table is a gap in the line,
31
+ * and a reading alone between two blanks is a `lone` mark.
26
32
  *
27
33
  * Place it beside a `Stat`, because a change beside a figure is a second fact,
28
34
  * as `Stat`'s own spec says. `Trend` is composed the same way.
@@ -44,7 +50,10 @@ import { nothing, type PropertyValues, type TemplateResult } from 'lit';
44
50
  * @csspart high - The mark on the highest reading, under `marks="high"`. Placed as `cursor` is.
45
51
  * @csspart line - The line's path.
46
52
  * @csspart live - A polite region that says the reading a key moved to, rendered only with a `label`. Hide it visually.
53
+ * @csspart lone - The mark on a reading no line reaches, with a blank or an end on each side. Placed as `cursor` is.
47
54
  * @csspart low - The mark on the lowest reading, under `marks="low"`. Placed as `cursor` is.
55
+ * @csspart overview - What the readings come to, in `overview-label`, which describes the drawing. Rendered `hidden` with a name, since it is read through the description and not shown.
56
+ * @csspart range - The band between the two values of `range`, drawn behind the line.
48
57
  * @csspart readout - The reading a pointer or a key rests on, as the table or `values` writes it, rendered only while there is one. Placed as `cursor` is.
49
58
  * @csspart svg - The drawing, which takes focus to be read by key where there is a `label`.
50
59
  * @csspart terminus - The mark on the last value.
@@ -120,6 +129,15 @@ export declare class HelixHeadlessSparkline extends HelixElement {
120
129
  * placed at its reading, for a stylesheet to draw.
121
130
  */
122
131
  marks: string;
132
+ /**
133
+ * What the readings come to, said after the name: `{first}` and `{last}`
134
+ * for the first and the last reading, `{high}` and `{low}` for the highest
135
+ * and the lowest, each written as the readout writes it.
136
+ *
137
+ * Set it to the words of the page's language, or to nothing to say no
138
+ * more than the name. Said only where the sparkline is named.
139
+ */
140
+ overviewLabel: string;
123
141
  /**
124
142
  * What the readings are drawn as.
125
143
  *
@@ -159,6 +177,14 @@ export declare class HelixHeadlessSparkline extends HelixElement {
159
177
  * {count}`, or to nothing to say no position.
160
178
  */
161
179
  positionLabel: string;
180
+ /**
181
+ * Two values a band is drawn between, behind the line, separated by a
182
+ * space: `range="60 100"` marks a normal range.
183
+ *
184
+ * The scale stretches to hold the band where the readings do not reach it.
185
+ * Absent draws none.
186
+ */
187
+ range: string;
162
188
  /**
163
189
  * The numbers, separated by whitespace or by commas.
164
190
  *
@@ -174,6 +200,17 @@ export declare class HelixHeadlessSparkline extends HelixElement {
174
200
  * chart, so a sparkline is read the way they are.
175
201
  */
176
202
  readonly pointer: ChartPointerController;
203
+ /**
204
+ * Hears an `aria-label` written on the sparkline, which no property mirrors,
205
+ * because it names the drawing as `label` does, as on every chart.
206
+ */
207
+ static get observedAttributes(): string[];
208
+ /** Called by the browser when an observed attribute changes; a new `aria-label` renames the drawing. */
209
+ attributeChangedCallback(name: string, old: string | null, value: string | null): void;
210
+ /** The drawing's name: an `aria-label` written on the sparkline, then `label`. */
211
+ get named(): string;
212
+ /** The two values of `range`, lower first, or none. */
213
+ private get band();
177
214
  protected willUpdate(changed: PropertyValues<this>): void;
178
215
  /**
179
216
  * The series to draw, and how many readings it has room for: the table's
@@ -193,6 +230,12 @@ export declare class HelixHeadlessSparkline extends HelixElement {
193
230
  */
194
231
  private reading;
195
232
  protected render(): TemplateResult;
233
+ /**
234
+ * A mark on each reading with a blank on either side, which no line can
235
+ * reach, so a line and an area show it as `LineChart` does. Not on a bar
236
+ * plot, whose column already shows it.
237
+ */
238
+ protected renderLone(series: Series | undefined, at: (index: number, value: number) => string): TemplateResult | typeof nothing;
196
239
  /** The marks `marks` asks for, on the highest and the lowest reading. */
197
240
  protected renderMarks(slots: (number | undefined)[], at: (index: number, value: number) => string): TemplateResult | typeof nothing;
198
241
  /**