@fusion.dev/helix-headless 0.146.0 → 0.148.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 (288) hide show
  1. package/custom-elements.json +9287 -1084
  2. package/dist/accordion/item/accordion-item.d.ts +43 -21
  3. package/dist/accordion/item/accordion-item.js +37 -11
  4. package/dist/accordion/root/accordion-root.d.ts +8 -2
  5. package/dist/accordion/root/accordion-root.js +6 -8
  6. package/dist/announcement/root/announcement-root.d.ts +66 -6
  7. package/dist/announcement/root/announcement-root.js +144 -13
  8. package/dist/area-chart/root/area-chart-root.d.ts +18 -1
  9. package/dist/area-chart/root/area-chart-root.js +14 -7
  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/count.d.ts +14 -0
  13. package/dist/badge/count.js +20 -0
  14. package/dist/badge/root/badge-root.d.ts +57 -6
  15. package/dist/badge/root/badge-root.js +77 -8
  16. package/dist/bar-chart/root/bar-chart-root.js +11 -20
  17. package/dist/bar-chart/stack.d.ts +10 -0
  18. package/dist/bar-chart/stack.js +8 -0
  19. package/dist/blockquote/root/blockquote-root.d.ts +66 -8
  20. package/dist/blockquote/root/blockquote-root.js +85 -8
  21. package/dist/breadcrumbs/breadcrumb/breadcrumb.d.ts +14 -0
  22. package/dist/breadcrumbs/breadcrumb/breadcrumb.js +12 -2
  23. package/dist/breadcrumbs/root/breadcrumbs-root.js +2 -2
  24. package/dist/button/root/button-root.d.ts +24 -38
  25. package/dist/button/root/button-root.js +6 -37
  26. package/dist/button/submitter.d.ts +27 -0
  27. package/dist/button/submitter.js +25 -0
  28. package/dist/button-group/arrows.d.ts +14 -0
  29. package/dist/button-group/arrows.js +10 -0
  30. package/dist/button-group/root/button-group-root.d.ts +141 -3
  31. package/dist/button-group/root/button-group-root.js +142 -2
  32. package/dist/canvas/edge-pan.d.ts +61 -0
  33. package/dist/canvas/edge-pan.js +63 -0
  34. package/dist/canvas/node/canvas-node.d.ts +12 -2
  35. package/dist/canvas/node/canvas-node.js +6 -2
  36. package/dist/canvas/root/canvas-document.d.ts +10 -0
  37. package/dist/canvas/root/canvas-document.js +1 -0
  38. package/dist/canvas/root/canvas-root.d.ts +107 -5
  39. package/dist/canvas/root/canvas-root.js +140 -36
  40. package/dist/canvas/snap.d.ts +42 -0
  41. package/dist/canvas/snap.js +32 -0
  42. package/dist/card/root/card-root.d.ts +12 -1
  43. package/dist/card/root/card-root.js +11 -4
  44. package/dist/carousel/root/carousel-root.d.ts +4 -3
  45. package/dist/carousel/root/carousel-root.js +2 -6
  46. package/dist/chat/root/chat-root.d.ts +28 -4
  47. package/dist/chat/root/chat-root.js +12 -7
  48. package/dist/checkbox/root/checkbox-root.js +5 -7
  49. package/dist/checkbox-group/root/checkbox-group-root.d.ts +13 -0
  50. package/dist/checkbox-group/root/checkbox-group-root.js +16 -7
  51. package/dist/checkbox-group/select-all.d.ts +9 -4
  52. package/dist/checkbox-group/select-all.js +1 -1
  53. package/dist/code-block/root/code-block-root.d.ts +41 -18
  54. package/dist/code-block/root/code-block-root.js +21 -6
  55. package/dist/color-input/root/color-input-root.d.ts +11 -0
  56. package/dist/color-input/root/color-input-root.js +12 -5
  57. package/dist/color-picker/channels.js +1 -2
  58. package/dist/color-picker/root/color-picker-root.d.ts +32 -24
  59. package/dist/color-picker/root/color-picker-root.js +99 -55
  60. package/dist/color-picker/wheel.d.ts +28 -0
  61. package/dist/color-picker/wheel.js +15 -0
  62. package/dist/combobox/root/combobox-root.d.ts +83 -11
  63. package/dist/combobox/root/combobox-root.js +81 -18
  64. package/dist/command/root/command-root.d.ts +7 -4
  65. package/dist/command/root/command-root.js +2 -19
  66. package/dist/copy-button/root/copy-button-root.d.ts +16 -1
  67. package/dist/copy-button/root/copy-button-root.js +7 -2
  68. package/dist/data-table/keys.d.ts +0 -18
  69. package/dist/data-table/keys.js +1 -11
  70. package/dist/data-table/root/data-table-root.js +29 -50
  71. package/dist/date-input/root/date-input-root.d.ts +8 -1
  72. package/dist/date-input/root/date-input-root.js +8 -5
  73. package/dist/date-picker/root/date-picker-root.d.ts +14 -9
  74. package/dist/date-picker/root/date-picker-root.js +21 -16
  75. package/dist/description-list/item/description-list-item.d.ts +22 -4
  76. package/dist/description-list/item/description-list-item.js +14 -4
  77. package/dist/divider/root/divider-root.d.ts +45 -16
  78. package/dist/divider/root/divider-root.js +28 -4
  79. package/dist/donut-chart/root/donut-chart-root.js +13 -6
  80. package/dist/donut-chart/slice-label.d.ts +35 -0
  81. package/dist/donut-chart/slice-label.js +11 -0
  82. package/dist/drawer/root/drawer-root.d.ts +4 -2
  83. package/dist/drawer/root/drawer-root.js +15 -3
  84. package/dist/drilldown/item/drilldown-item.d.ts +21 -1
  85. package/dist/drilldown/item/drilldown-item.js +38 -4
  86. package/dist/drilldown/root/drilldown-root.js +3 -2
  87. package/dist/element-events.generated.js +5 -1
  88. package/dist/empty/root/empty-root.d.ts +46 -2
  89. package/dist/empty/root/empty-root.js +50 -5
  90. package/dist/file-input/root/file-input-root.d.ts +34 -6
  91. package/dist/file-input/root/file-input-root.js +55 -21
  92. package/dist/file-input/status.d.ts +24 -0
  93. package/dist/file-input/status.js +11 -0
  94. package/dist/filter-input/chip/filter-chip.d.ts +23 -0
  95. package/dist/filter-input/chip/filter-chip.js +10 -2
  96. package/dist/filter-input/root/filter-input-root.d.ts +8 -0
  97. package/dist/filter-input/root/filter-input-root.js +17 -27
  98. package/dist/format-date/fields.d.ts +20 -0
  99. package/dist/format-date/fields.js +14 -0
  100. package/dist/format-date/options.d.ts +5 -5
  101. package/dist/format-date/options.js +10 -39
  102. package/dist/format-date/root/format-date-root.d.ts +53 -4
  103. package/dist/format-date/root/format-date-root.js +38 -50
  104. package/dist/graph/root/graph-root.d.ts +23 -5
  105. package/dist/graph/root/graph-root.js +17 -20
  106. package/dist/heatmap/root/buckets.d.ts +21 -0
  107. package/dist/heatmap/root/buckets.js +34 -0
  108. package/dist/heatmap/root/heatmap-root.d.ts +76 -3
  109. package/dist/heatmap/root/heatmap-root.js +105 -20
  110. package/dist/icon/index.d.ts +1 -1
  111. package/dist/icon/index.js +1 -1
  112. package/dist/icon/registry.d.ts +26 -1
  113. package/dist/icon/registry.js +12 -1
  114. package/dist/icon/root/icon-root.d.ts +13 -0
  115. package/dist/icon/root/icon-root.js +23 -2
  116. package/dist/icon/root/index.d.ts +1 -1
  117. package/dist/icon/root/index.js +1 -1
  118. package/dist/image/root/image-root.d.ts +20 -0
  119. package/dist/image/root/image-root.js +6 -1
  120. package/dist/input/root/input-root.d.ts +23 -2
  121. package/dist/input/root/input-root.js +12 -8
  122. package/dist/kbd/root/kbd-root.d.ts +20 -3
  123. package/dist/kbd/root/kbd-root.js +23 -50
  124. package/dist/legend/hidden-series.d.ts +10 -2
  125. package/dist/legend/hidden-series.js +5 -2
  126. package/dist/legend/root/legend-root.d.ts +25 -7
  127. package/dist/legend/root/legend-root.js +25 -26
  128. package/dist/lightbox/root/lightbox-root.d.ts +50 -5
  129. package/dist/lightbox/root/lightbox-root.js +76 -8
  130. package/dist/lightbox/zoom.d.ts +2 -0
  131. package/dist/lightbox/zoom.js +5 -3
  132. package/dist/line-chart/root/line-chart-root.d.ts +16 -0
  133. package/dist/line-chart/root/line-chart-root.js +10 -2
  134. package/dist/list/root/list-root.d.ts +10 -2
  135. package/dist/list/root/list-root.js +31 -24
  136. package/dist/list/selection.d.ts +17 -0
  137. package/dist/list/selection.js +16 -0
  138. package/dist/listbox/root/listbox-root.d.ts +125 -26
  139. package/dist/listbox/root/listbox-root.js +156 -166
  140. package/dist/map/marker/map-marker.d.ts +2 -1
  141. package/dist/map/root/map-root.d.ts +40 -2
  142. package/dist/map/root/map-root.js +64 -23
  143. package/dist/megamenu/hand-over.d.ts +75 -0
  144. package/dist/megamenu/hand-over.js +67 -0
  145. package/dist/megamenu/root/megamenu-root.d.ts +38 -14
  146. package/dist/megamenu/root/megamenu-root.js +29 -50
  147. package/dist/menu/item/menu-item.js +4 -4
  148. package/dist/menu/root/menu-root.d.ts +5 -2
  149. package/dist/meter/root/meter-root.d.ts +42 -0
  150. package/dist/meter/root/meter-root.js +33 -7
  151. package/dist/navigation/root/navigation-root.js +5 -8
  152. package/dist/notifications/group/notification-group.d.ts +8 -1
  153. package/dist/notifications/group/notification-group.js +12 -6
  154. package/dist/notifications/notification/notification.d.ts +12 -1
  155. package/dist/notifications/notification/notification.js +3 -1
  156. package/dist/notifications/root/notifications-root.d.ts +48 -3
  157. package/dist/notifications/root/notifications-root.js +36 -6
  158. package/dist/number-input/grain.d.ts +28 -0
  159. package/dist/number-input/grain.js +14 -0
  160. package/dist/number-input/root/number-input-root.d.ts +17 -1
  161. package/dist/number-input/root/number-input-root.js +29 -18
  162. package/dist/object-inspector/root/object-inspector-root.d.ts +17 -6
  163. package/dist/object-inspector/root/object-inspector-root.js +31 -20
  164. package/dist/option/index.d.ts +1 -0
  165. package/dist/option/index.js +1 -0
  166. package/dist/option/separator/option-separator.d.ts +30 -0
  167. package/dist/option/separator/option-separator.js +21 -0
  168. package/dist/outline/root/outline-root.d.ts +12 -0
  169. package/dist/outline/root/outline-root.js +5 -19
  170. package/dist/pagination/size/pagination-size.d.ts +6 -0
  171. package/dist/pagination/size/pagination-size.js +5 -3
  172. package/dist/pin-input/code.d.ts +51 -1
  173. package/dist/pin-input/code.js +37 -0
  174. package/dist/pin-input/root/pin-input-root.d.ts +98 -121
  175. package/dist/pin-input/root/pin-input-root.js +151 -156
  176. package/dist/popover/hide-outside.js +2 -9
  177. package/dist/popover/root/popover-root.d.ts +7 -1
  178. package/dist/popover/root/popover-root.js +9 -1
  179. package/dist/progress/root/progress-root.d.ts +27 -0
  180. package/dist/progress/root/progress-root.js +25 -7
  181. package/dist/qr-code/export.d.ts +19 -0
  182. package/dist/qr-code/export.js +14 -0
  183. package/dist/qr-code/name.d.ts +11 -0
  184. package/dist/qr-code/name.js +7 -0
  185. package/dist/qr-code/root/qr-code-root.d.ts +31 -9
  186. package/dist/qr-code/root/qr-code-root.js +29 -17
  187. package/dist/radio/group/radio-group.d.ts +15 -0
  188. package/dist/radio/root/radio-root.js +2 -2
  189. package/dist/rating/root/rating-root.d.ts +22 -9
  190. package/dist/rating/root/rating-root.js +15 -39
  191. package/dist/scatter-chart/measures.d.ts +22 -0
  192. package/dist/scatter-chart/measures.js +11 -0
  193. package/dist/scatter-chart/root/scatter-chart-root.d.ts +26 -0
  194. package/dist/scatter-chart/root/scatter-chart-root.js +21 -7
  195. package/dist/scroll-area/scrollbar.d.ts +0 -8
  196. package/dist/scroll-area/scrollbar.js +1 -1
  197. package/dist/select/align.d.ts +47 -0
  198. package/dist/select/align.js +7 -0
  199. package/dist/select/root/select-root.d.ts +115 -11
  200. package/dist/select/root/select-root.js +104 -20
  201. package/dist/slider/root/slider-root.d.ts +18 -1
  202. package/dist/slider/root/slider-root.js +22 -0
  203. package/dist/sparkline/readings.d.ts +30 -12
  204. package/dist/sparkline/readings.js +32 -15
  205. package/dist/sparkline/root/sparkline-root.d.ts +46 -3
  206. package/dist/sparkline/root/sparkline-root.js +60 -21
  207. package/dist/speed-dial/group/speed-dial-group.d.ts +48 -0
  208. package/dist/speed-dial/group/speed-dial-group.js +43 -0
  209. package/dist/speed-dial/index.d.ts +1 -0
  210. package/dist/speed-dial/index.js +1 -0
  211. package/dist/speed-dial/item/speed-dial-item.d.ts +27 -2
  212. package/dist/speed-dial/item/speed-dial-item.js +43 -5
  213. package/dist/speed-dial/long-press.d.ts +50 -0
  214. package/dist/speed-dial/long-press.js +48 -0
  215. package/dist/speed-dial/root/speed-dial-root.d.ts +86 -1
  216. package/dist/speed-dial/root/speed-dial-root.js +123 -6
  217. package/dist/splitter/root/splitter-root.d.ts +9 -2
  218. package/dist/splitter/root/splitter-root.js +18 -3
  219. package/dist/steps/root/steps-root.d.ts +8 -1
  220. package/dist/steps/root/steps-root.js +6 -1
  221. package/dist/steps/step/step.d.ts +1 -0
  222. package/dist/steps/step/step.js +1 -1
  223. package/dist/table/cell/table-cell.d.ts +8 -1
  224. package/dist/table/cell/table-cell.js +6 -2
  225. package/dist/table/column/table-column.d.ts +6 -0
  226. package/dist/table/column/table-column.js +5 -1
  227. package/dist/table/grid-controller.d.ts +117 -0
  228. package/dist/table/grid-controller.js +293 -0
  229. package/dist/table/root/table-root.d.ts +47 -3
  230. package/dist/table/root/table-root.js +93 -29
  231. package/dist/table/row/table-row.d.ts +36 -0
  232. package/dist/table/row/table-row.js +52 -2
  233. package/dist/tabs/root/tabs-root.d.ts +5 -3
  234. package/dist/tag/group/tag-group.d.ts +16 -4
  235. package/dist/tag/group/tag-group.js +23 -20
  236. package/dist/textarea/root/textarea-root.d.ts +14 -2
  237. package/dist/textarea/root/textarea-root.js +6 -7
  238. package/dist/time-input/root/time-input-root.d.ts +5 -1
  239. package/dist/time-input/root/time-input-root.js +10 -7
  240. package/dist/time-picker/root/time-picker-root.d.ts +12 -14
  241. package/dist/time-picker/root/time-picker-root.js +18 -23
  242. package/dist/toast/item/toast-item.d.ts +9 -6
  243. package/dist/toast/item/toast-item.js +1 -1
  244. package/dist/toast/root/toast-root.d.ts +40 -4
  245. package/dist/toast/root/toast-root.js +68 -16
  246. package/dist/toast/words.d.ts +12 -0
  247. package/dist/toast/words.js +25 -0
  248. package/dist/toggle/group/toggle-group.d.ts +29 -5
  249. package/dist/toggle/group/toggle-group.js +26 -43
  250. package/dist/toggle/root/toggle-root.js +2 -4
  251. package/dist/toolbar/root/toolbar-root.d.ts +52 -5
  252. package/dist/toolbar/root/toolbar-root.js +88 -13
  253. package/dist/tooltip/root/tooltip-root.d.ts +7 -1
  254. package/dist/tooltip/root/tooltip-root.js +10 -2
  255. package/dist/tour/root/tour-root.d.ts +15 -0
  256. package/dist/tour/root/tour-root.js +20 -1
  257. package/dist/tour/stop/tour-stop.d.ts +3 -0
  258. package/dist/tour/stop/tour-stop.js +3 -3
  259. package/dist/tree/root/tree-root.js +2 -2
  260. package/dist/trend/figure.d.ts +16 -0
  261. package/dist/trend/figure.js +8 -0
  262. package/dist/trend/root/trend-root.d.ts +41 -2
  263. package/dist/trend/root/trend-root.js +29 -3
  264. package/dist/video/root/extra-controls.d.ts +12 -0
  265. package/dist/video/root/extra-controls.js +6 -2
  266. package/dist/video/root/video-root.d.ts +16 -1
  267. package/dist/video/root/video-root.js +15 -1
  268. package/dist/video-playlist/root/video-playlist-root.d.ts +11 -1
  269. package/dist/video-playlist/root/video-playlist-root.js +3 -0
  270. package/dist/virtual-list/fill.d.ts +11 -0
  271. package/dist/virtual-list/fill.js +9 -0
  272. package/dist/virtual-list/keys.d.ts +16 -7
  273. package/dist/virtual-list/keys.js +17 -6
  274. package/dist/virtual-list/plan.d.ts +17 -1
  275. package/dist/virtual-list/plan.js +13 -2
  276. package/dist/virtual-list/root/virtual-list-root.d.ts +25 -3
  277. package/dist/virtual-list/root/virtual-list-root.js +53 -41
  278. package/lint.json +1 -1
  279. package/package.json +5 -5
  280. package/types/jsx.d.ts +4934 -954
  281. package/types/svelte.d.ts +2 -0
  282. package/types/vue.d.ts +946 -253
  283. package/dist/color-picker/swatches.d.ts +0 -12
  284. package/dist/color-picker/swatches.js +0 -26
  285. package/dist/rating/steps.d.ts +0 -7
  286. package/dist/rating/steps.js +0 -9
  287. package/dist/tooltip/focus-target.d.ts +0 -6
  288. package/dist/tooltip/focus-target.js +0 -9
@@ -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, deepContains, focusableElements, HelixElement, HelixLoadMoreEvent, HelixReadEvent, hostAria, MixinCollection, markRegion, rovingStop, whenHydrated, } from '@fusion.dev/helix-core';
7
+ import { assignSlots, ChildrenController, customElement, deepContains, focusableElements, HelixElement, HelixLoadMoreEvent, HelixReadEvent, hostAria, MixinCollection, markRegion, rovingStop, whenHydrated, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  import { feedStep } from '../feed-keys.js';
@@ -12,6 +12,7 @@ let HelixHeadlessNotifications = class HelixHeadlessNotifications extends MixinC
12
12
  constructor() {
13
13
  super(...arguments);
14
14
  this.label = '';
15
+ this.loadError = '';
15
16
  this.loadMore = false;
16
17
  this.loadMoreLabel = 'Load more';
17
18
  this.markAll = false;
@@ -20,6 +21,7 @@ let HelixHeadlessNotifications = class HelixHeadlessNotifications extends MixinC
20
21
  this.assign();
21
22
  this.requestUpdate();
22
23
  };
24
+ this.childWatch = new ChildrenController(this, this.handleSlotChange);
23
25
  this.handleRead = () => {
24
26
  queueMicrotask(() => this.requestUpdate());
25
27
  };
@@ -138,11 +140,8 @@ let HelixHeadlessNotifications = class HelixHeadlessNotifications extends MixinC
138
140
  }
139
141
  return;
140
142
  }
141
- list.assign(...this.rows);
142
- for (const name of ['header', 'footer']) {
143
- const slot = root?.querySelector?.(`slot[name="${name}"]`);
144
- slot?.assign?.(...[...this.children].filter((child) => child.getAttribute('slot') === name));
145
- }
143
+ const rows = this.rows;
144
+ assignSlots(root, this.children, (child) => rows.includes(child) ? 'items' : child.getAttribute('slot') === 'items' ? '' : undefined);
146
145
  }
147
146
  get reachable() {
148
147
  return this.items.filter((row) => !('assignedSlot' in row) || row.assignedSlot !== null);
@@ -178,6 +177,34 @@ let HelixHeadlessNotifications = class HelixHeadlessNotifications extends MixinC
178
177
  willUpdate(changed) {
179
178
  super.willUpdate(changed);
180
179
  hostAria(this.internals, { label: this.label || 'Notifications', role: 'region' });
180
+ const failed = this.failed;
181
+ this.toggleState('error', failed);
182
+ this.toggleState('loading', this.loading);
183
+ this.toggleState('empty', !this.loading && !failed && this.childrenKnown && this.items.length === 0);
184
+ }
185
+ renderStatus(hasContent) {
186
+ if (this.failed) {
187
+ return html `<p part="error"><slot name="error">${this.loadError}</slot></p>`;
188
+ }
189
+ if (!this.loading && hasContent) {
190
+ return nothing;
191
+ }
192
+ return this.loading
193
+ ? html `<p part="loading"><slot name="loading">${this.loadingText}</slot></p>`
194
+ : html `<p part="empty"><slot name="empty">${this.emptyText}</slot></p>`;
195
+ }
196
+ renderLive(hasContent) {
197
+ const said = this.failed
198
+ ? this.loadError
199
+ : this.loading
200
+ ? this.loadingText
201
+ : hasContent
202
+ ? this.announced
203
+ : this.emptyText;
204
+ return html `<span part="live" role="status">${said}</span>`;
205
+ }
206
+ get failed() {
207
+ return Boolean(this.loadError);
181
208
  }
182
209
  markMedia() {
183
210
  this.toggleState('media', this.items.some((item) => item.querySelector(':scope > [slot="media"]') !== null));
@@ -238,6 +265,9 @@ let HelixHeadlessNotifications = class HelixHeadlessNotifications extends MixinC
238
265
  __decorate([
239
266
  property()
240
267
  ], HelixHeadlessNotifications.prototype, "label", void 0);
268
+ __decorate([
269
+ property({ attribute: 'load-error' })
270
+ ], HelixHeadlessNotifications.prototype, "loadError", void 0);
241
271
  __decorate([
242
272
  property({ attribute: 'load-more', type: Boolean })
243
273
  ], HelixHeadlessNotifications.prototype, "loadMore", void 0);
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The steps a number field takes when `step` is not set, read off its format.
3
+ *
4
+ * React Aria's NumberField does not round a typed number to whole steps when
5
+ * no step is given: it keeps the fraction the format shows. A field showing
6
+ * dollars keeps cents, and one showing a percent steps by one percent.
7
+ */
8
+ /**
9
+ * The smallest amount a format shows, which is the grid a typed number is
10
+ * rounded to when no `step` is set: `0.01` for a currency in dollars, `0.001`
11
+ * for a plain decimal, and `0.01` for a whole percent, since a percent shows
12
+ * the number a hundred times over.
13
+ */
14
+ export declare const formatGrain: (locale: string, options: Intl.NumberFormatOptions) => number;
15
+ /**
16
+ * How far one press moves when no `step` is set: one percent for a percent,
17
+ * as React Aria does, and one otherwise.
18
+ */
19
+ export declare const strideOf: (options: Intl.NumberFormatOptions | undefined) => number;
20
+ /**
21
+ * How many decimal places a number is written with.
22
+ *
23
+ * Read off the string rather than computed, because the question is about how
24
+ * the consumer wrote the step and not about what the binary can represent.
25
+ * `1e-7` is handled separately: its string form has no dot at all, so counting
26
+ * after one would say zero and round every result to a whole number.
27
+ */
28
+ export declare const decimals: (value: number) => number;
@@ -0,0 +1,14 @@
1
+ export const formatGrain = (locale, options) => {
2
+ const { maximumFractionDigits = 0 } = new Intl.NumberFormat(locale || undefined, options).resolvedOptions();
3
+ const places = maximumFractionDigits + (options.style === 'percent' ? 2 : 0);
4
+ return Number(`1e-${places}`);
5
+ };
6
+ export const strideOf = (options) => options?.style === 'percent' ? 0.01 : 1;
7
+ export const decimals = (value) => {
8
+ const text = String(value);
9
+ const exponent = /e-(\d+)$/.exec(text);
10
+ if (exponent) {
11
+ return Number(exponent[1]);
12
+ }
13
+ return (text.split('.')[1] ?? '').length;
14
+ };
@@ -77,6 +77,7 @@ declare const HelixHeadlessNumberInput_base: (new (...args: any[]) => import("@f
77
77
  * @csspart label - The label above the field. It is also the grip you drag when `scrub` is set.
78
78
  *
79
79
  * @cssstate invalid - The control has an error to show, from the browser's validation or from your `feedback`.
80
+ * @cssstate scrubbing - The label is being dragged to change the value, under `scrub`, as Zag's scrubber says while it is held.
80
81
  */
81
82
  export declare class HelixHeadlessNumberInput extends HelixHeadlessNumberInput_base {
82
83
  protected readonly aria: AriaForwardController;
@@ -182,8 +183,14 @@ export declare class HelixHeadlessNumberInput extends HelixHeadlessNumberInput_b
182
183
  /**
183
184
  * How far one step moves. A value off the step grid, counted from `min`, is
184
185
  * invalid, as it is in a native number field.
186
+ *
187
+ * Unset, a step moves by one, or by one percent under a percent format, and
188
+ * a typed number is rounded to the smallest amount `format-options` shows
189
+ * rather than to whole steps, as React Aria's NumberField does: a field
190
+ * showing dollars keeps the cents typed into it. Without `format-options`
191
+ * the grid is whole numbers, as in a native number field.
185
192
  */
186
- step: number;
193
+ step?: number;
187
194
  /**
188
195
  * The message the browser shows when the number is off the step grid, with
189
196
  * `{below}` and `{above}` filled in with the nearest valid values.
@@ -222,6 +229,15 @@ export declare class HelixHeadlessNumberInput extends HelixHeadlessNumberInput_b
222
229
  private get control();
223
230
  /** The value as a number, or `null` where the field holds nothing usable. */
224
231
  private get numeric();
232
+ /** The grain last read off the format, with the locale and options it was read for. */
233
+ private grainOf?;
234
+ /** How far one press moves: `step`, or the stride the format implies. */
235
+ private get stride();
236
+ /**
237
+ * The grid a typed number is rounded to: `step` where it is set, and
238
+ * otherwise the smallest amount the format shows, or whole numbers.
239
+ */
240
+ private get grid();
225
241
  /** Whether `value` is on the step grid, counted from `min`. */
226
242
  private onStep;
227
243
  /**
@@ -9,6 +9,7 @@ import { minus, plus } from '@fusion.dev/helix-icons';
9
9
  import { html, nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
11
11
  import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
12
+ import { decimals, formatGrain, strideOf } from '../grain.js';
12
13
  const SCRUB_PITCH = 4;
13
14
  const SCRUB_FINE = 16;
14
15
  let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinControllerValue(MixinField(HelixFormElement)) {
@@ -25,7 +26,6 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
25
26
  this.placeholder = '';
26
27
  this.readOnly = false;
27
28
  this.scrub = false;
28
- this.step = 1;
29
29
  this.valueText = '';
30
30
  this.wheel = false;
31
31
  this.editing = false;
@@ -57,6 +57,7 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
57
57
  this.scrubFrom = event.clientX;
58
58
  this.scrubValue = this.numeric ?? this.min ?? 0;
59
59
  this.scrubStart = this.value;
60
+ this.toggleState('scrubbing', true);
60
61
  event.preventDefault();
61
62
  };
62
63
  this.handleScrubMove = (event) => {
@@ -64,7 +65,7 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
64
65
  if (!grip.hasPointerCapture(event.pointerId)) {
65
66
  return;
66
67
  }
67
- const step = Math.abs(this.step) || 1;
68
+ const step = this.stride;
68
69
  const unit = event.shiftKey ? (this.largeStep ?? 10) * step : step;
69
70
  const pitch = event.altKey ? SCRUB_FINE : SCRUB_PITCH;
70
71
  const steps = Math.round((event.clientX - this.scrubFrom) / pitch);
@@ -75,7 +76,7 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
75
76
  this.requestValue(next);
76
77
  const control = this.control;
77
78
  if (control) {
78
- restoreValue(control, this.value);
79
+ restoreValue(control, this.shown);
79
80
  }
80
81
  };
81
82
  this.handleScrubEnd = (event) => {
@@ -84,6 +85,7 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
84
85
  return;
85
86
  }
86
87
  grip.releasePointerCapture(event.pointerId);
88
+ this.toggleState('scrubbing', false);
87
89
  if (this.value !== this.scrubStart) {
88
90
  this.announceChange();
89
91
  }
@@ -92,7 +94,7 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
92
94
  event.stopPropagation();
93
95
  const control = event.target;
94
96
  this.requestValue(control.value);
95
- restoreValue(control, this.value);
97
+ restoreValue(control, this.shown);
96
98
  };
97
99
  this.handleBlur = () => {
98
100
  this.editing = false;
@@ -158,14 +160,31 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
158
160
  }
159
161
  return parseNumber(text, this.locale, this.formatOptions);
160
162
  }
163
+ get stride() {
164
+ return Math.abs(this.step ?? 0) || strideOf(this.formatOptions);
165
+ }
166
+ get grid() {
167
+ const step = Math.abs(this.step ?? 0);
168
+ const options = this.formatOptions;
169
+ if (step || !options) {
170
+ return step || 1;
171
+ }
172
+ const [locale, read, grain] = this.grainOf ?? [];
173
+ if (locale === this.locale && read === options && grain !== undefined) {
174
+ return grain;
175
+ }
176
+ const fresh = formatGrain(this.locale, options);
177
+ this.grainOf = [this.locale, options, fresh];
178
+ return fresh;
179
+ }
161
180
  onStep(value) {
162
- const step = Math.abs(this.step) || 1;
181
+ const step = this.grid;
163
182
  const anchor = this.min ?? 0;
164
183
  const steps = (value - anchor) / step;
165
184
  return Math.abs(steps - Math.round(steps)) < 1e-9;
166
185
  }
167
186
  clamp(value) {
168
- const step = Math.abs(this.step) || 1;
187
+ const step = this.grid;
169
188
  const anchor = this.min ?? 0;
170
189
  const stepped = anchor + Math.round((value - anchor) / step) * step;
171
190
  const floored = this.min === undefined ? stepped : Math.max(this.min, stepped);
@@ -177,7 +196,7 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
177
196
  if (this.isDisabled || this.readOnly) {
178
197
  return;
179
198
  }
180
- const step = Math.abs(this.step) || 1;
199
+ const step = this.stride;
181
200
  const from = this.numeric ?? this.min ?? 0;
182
201
  const next = this.clamp(this.numeric === null ? from : from + steps * step);
183
202
  this.land(String(next));
@@ -187,7 +206,7 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
187
206
  const applied = this.requestValue(next);
188
207
  const control = this.control;
189
208
  if (control) {
190
- restoreValue(control, this.value);
209
+ restoreValue(control, this.shown);
191
210
  }
192
211
  if (applied && this.value !== before) {
193
212
  this.announceChange();
@@ -223,7 +242,7 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
223
242
  }
224
243
  const before = this.value;
225
244
  this.requestValue(String(this.clamp(parsed)));
226
- restoreValue(control, this.value);
245
+ restoreValue(control, this.shown);
227
246
  return this.value !== before;
228
247
  }
229
248
  sync() {
@@ -253,7 +272,7 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
253
272
  }), control);
254
273
  }
255
274
  else if (parsed !== null && !this.onStep(parsed)) {
256
- const step = Math.abs(this.step) || 1;
275
+ const step = this.grid;
257
276
  const anchor = this.min ?? 0;
258
277
  const below = this.clamp(anchor + Math.floor((parsed - anchor) / step) * step);
259
278
  const above = this.clamp(anchor + Math.ceil((parsed - anchor) / step) * step);
@@ -431,11 +450,3 @@ HelixHeadlessNumberInput = __decorate([
431
450
  customElement('helix-headless-number-input')
432
451
  ], HelixHeadlessNumberInput);
433
452
  export { HelixHeadlessNumberInput };
434
- const decimals = (value) => {
435
- const text = String(value);
436
- const exponent = /e-(\d+)$/.exec(text);
437
- if (exponent) {
438
- return Number(exponent[1]);
439
- }
440
- return (text.split('.')[1] ?? '').length;
441
- };
@@ -23,6 +23,7 @@ import { type Row } from '../rows.js';
23
23
  * | Right | Opens a closed branch, or moves into an open one. Left on a right-to-left page. |
24
24
  * | Left | Closes an open branch, or moves to the parent. Right on a right-to-left page. |
25
25
  * | Home, End | Moves to the first or the last row. |
26
+ * | Page Down, Page Up | Moves a box's height of rows, so the row at one edge lands at the other. |
26
27
  * | Enter, Space | Opens or closes a branch. |
27
28
  * | `*` | Opens every branch beside the active row. |
28
29
  * | A letter or a word | Moves to the next row whose key starts with what you typed. |
@@ -90,6 +91,7 @@ import { type Row } from '../rows.js';
90
91
  * @csspart copy-button - The button inside the copy control.
91
92
  * @csspart copy-live - The live region inside the copy button, which says its `pending-label` when the copy becomes pending under focus.
92
93
  * @csspart copy-mark - The clipboard icon, and the confirmation icon that replaces it.
94
+ * @csspart copy-spinner - The ring the copy button turns while it is pending.
93
95
  * @csspart copy-text - The words on that button, trimmed to their capitals and baseline.
94
96
  * @csspart copy-tooltip - The tooltip, forwarded from the copy button's tooltip.
95
97
  * @csspart copy-tooltip-arrow - The tooltip's arrow, pointing at the copy button.
@@ -254,7 +256,7 @@ export declare class HelixHeadlessObjectInspector extends HelixElement {
254
256
  /** Whether a row held focus as this update began, so a row the render moved gets it back. */
255
257
  private hadFocus;
256
258
  /** What has been typed toward a key, and when the last letter came. */
257
- private typed;
259
+ private readonly typed;
258
260
  /** The branches of the last value seen, by path, so a new value can tell old ones from new. */
259
261
  private branches;
260
262
  /** Carries a host `aria-label` and `aria-labelledby` to the tree, which is what takes focus. */
@@ -338,13 +340,18 @@ export declare class HelixHeadlessObjectInspector extends HelixElement {
338
340
  private move;
339
341
  private readonly handleKeydown;
340
342
  /**
341
- * Moves to the next row whose key starts with what was typed.
342
- *
343
- * Letters typed within half a second of each other run together, so `me`
344
- * finds `meta` past `message`. A repeated single letter cycles through the
345
- * keys that start with it.
343
+ * Moves to the next row whose key starts with what was typed, through core's
344
+ * `Typeahead`: letters within half a second run together, so `me` finds
345
+ * `meta` past `message`, and a repeated letter cycles through the keys that
346
+ * start with it.
346
347
  */
347
348
  private typeahead;
349
+ /**
350
+ * How many rows Page Down and Page Up move: the rows that fit in the tree's
351
+ * box, or in the window where the box does not scroll, less one, so the row
352
+ * at one edge lands at the other.
353
+ */
354
+ private pageStep;
348
355
  /** A press opens or closes the row it lands on, once that row is the active one. */
349
356
  private handleRowClick;
350
357
  /** Text typed in the filter field, asked for before the tree narrows to it. */
@@ -373,6 +380,10 @@ export declare class HelixHeadlessObjectInspector extends HelixElement {
373
380
  private renderRow;
374
381
  /** Whether a row of the tree holds focus. */
375
382
  private hasFocusedRow;
383
+ /** Where the row holding focus is among the visible rows, or nothing while none does. */
384
+ private focusedIndex;
385
+ /** Focus on the tree itself, its stop while the active row is not drawn, goes on to that row. */
386
+ private readonly handleTreeFocus;
376
387
  /**
377
388
  * A row that takes focus some other way than the keys, from a script or a
378
389
  * screen reader, becomes the active row, so focus and the keys agree.
@@ -5,7 +5,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
7
  var HelixHeadlessObjectInspector_1;
8
- import { AriaForwardController, CopyController, countLabel, customElement, drawStatus, elementById, fillTemplate, formatNumbers, HelixActivateEvent, HelixBeforeActivateEvent, HelixBeforeCloseEvent, HelixBeforeOpenEvent, HelixElement, HelixFilterEvent, HelixFilteredEvent, inlineStep, requestChange, StatusController, scrollItemIntoView, searchableById, treeArrow, VirtualizeController, } from '@fusion.dev/helix-core';
8
+ import { AriaForwardController, CopyController, countLabel, customElement, drawStatus, elementById, fillTemplate, formatNumbers, HelixActivateEvent, HelixBeforeActivateEvent, HelixBeforeCloseEvent, HelixBeforeOpenEvent, HelixElement, HelixFilterEvent, HelixFilteredEvent, inlineStep, pageRows, requestChange, StatusController, scrollItemIntoView, scrollRoom, searchableById, Typeahead, treeArrow, VirtualizeController, } from '@fusion.dev/helix-core';
9
9
  import { caretDown, caretRight } from '@fusion.dev/helix-icons';
10
10
  import { html, nothing } from 'lit';
11
11
  import { property, state } from 'lit/decorators.js';
@@ -41,7 +41,7 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
41
41
  this.revealing = false;
42
42
  this.focusing = false;
43
43
  this.hadFocus = false;
44
- this.typed = { at: 0, text: '' };
44
+ this.typed = new Typeahead();
45
45
  this.branches = new Set();
46
46
  this.aria = new AriaForwardController(this, {
47
47
  label: true,
@@ -84,6 +84,12 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
84
84
  case 'End':
85
85
  this.move(rows.length - 1);
86
86
  break;
87
+ case 'PageDown':
88
+ this.move(this.cursor + this.pageStep());
89
+ break;
90
+ case 'PageUp':
91
+ this.move(this.cursor - this.pageStep());
92
+ break;
87
93
  case 'ArrowRight':
88
94
  case 'ArrowLeft':
89
95
  switch (treeArrow(row, inlineStep(this, event.key) === 1 ? 1 : -1)) {
@@ -147,6 +153,11 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
147
153
  this.requestUpdate();
148
154
  }
149
155
  };
156
+ this.handleTreeFocus = () => {
157
+ this.revealing = true;
158
+ this.focusing = true;
159
+ this.requestUpdate();
160
+ };
150
161
  this.handleFocusIn = (event) => {
151
162
  const index = Number(event.target.dataset?.index);
152
163
  if (Number.isInteger(index) && index !== this.cursor) {
@@ -361,25 +372,18 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
361
372
  }
362
373
  this.revealing = true;
363
374
  this.focusing = true;
375
+ this.requestUpdate();
364
376
  return true;
365
377
  }
366
378
  typeahead(letter) {
367
- const now = Date.now();
368
- const text = now - this.typed.at < 500 ? this.typed.text + letter : letter;
369
- this.typed = { at: now, text };
370
- const wanted = text.toLocaleLowerCase();
371
- const repeated = [...wanted].every((one) => one === wanted[0]);
372
- const rows = this.rows;
373
- const start = wanted.length === 1 || repeated ? this.cursor + 1 : this.cursor;
374
- for (let step = 0; step < rows.length; step += 1) {
375
- const at = (start + step) % rows.length;
376
- const key = rows[at]?.key?.toLocaleLowerCase();
377
- if (key?.startsWith(repeated ? (wanted[0] ?? '') : wanted)) {
378
- this.move(at);
379
- return;
380
- }
379
+ const at = this.typed.match(letter, this.rows.map((row) => row.key ?? ''), this.cursor);
380
+ if (at >= 0) {
381
+ this.move(at);
381
382
  }
382
383
  }
384
+ pageStep() {
385
+ return pageRows(scrollRoom(this.partNamed('tree')), this.virtualize.sizeOf(this.cursor) || HelixHeadlessObjectInspector_1.ESTIMATE);
386
+ }
383
387
  handleRowClick(index, row) {
384
388
  if (this.move(index) && row.branch) {
385
389
  this.toggle(row);
@@ -445,13 +449,16 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
445
449
  </div>`;
446
450
  }
447
451
  hasFocusedRow() {
452
+ return this.focusedIndex() !== undefined;
453
+ }
454
+ focusedIndex() {
448
455
  const focused = this.shadowRoot?.activeElement;
449
- return focused?.getAttribute?.('role') === 'row';
456
+ return focused?.getAttribute?.('role') === 'row' ? Number(focused.dataset.index) : undefined;
450
457
  }
451
458
  renderCopyButton(row) {
452
459
  return staticHtml `<${this.tag('copy-button')}
453
460
  part="copy"
454
- exportparts="button: copy-button, live: copy-live, text: copy-text, mark: copy-mark, tooltip: copy-tooltip,
461
+ exportparts="button: copy-button, live: copy-live, spinner: copy-spinner, text: copy-text, mark: copy-mark, tooltip: copy-tooltip,
455
462
  tooltip-arrow: copy-tooltip-arrow, tooltip-arrow-bottom: copy-tooltip-arrow-bottom,
456
463
  tooltip-arrow-left: copy-tooltip-arrow-left, tooltip-arrow-right: copy-tooltip-arrow-right,
457
464
  tooltip-arrow-top: copy-tooltip-arrow-top, tooltip-bottom: copy-tooltip-bottom,
@@ -463,8 +470,9 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
463
470
  render() {
464
471
  const rows = this.rows;
465
472
  const { first, last } = this.windowed ? this.virtualize.window : { first: 0, last: rows.length };
466
- const kept = this.windowed && (this.cursor < first || this.cursor >= last) && this.hasFocusedRow()
467
- ? this.cursor
473
+ const focused = this.focusedIndex();
474
+ const kept = this.windowed && focused !== undefined && (focused < first || focused >= last)
475
+ ? focused
468
476
  : undefined;
469
477
  const { after, before } = this.windowed
470
478
  ? this.virtualize.spacers(kept)
@@ -474,6 +482,7 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
474
482
  if (kept !== undefined && keptRow) {
475
483
  shown.splice(kept < first ? 0 : shown.length, 0, [keptRow, kept]);
476
484
  }
485
+ const stopless = rows.length > 0 && !shown.some(([, at]) => at === this.cursor);
477
486
  return html `${this.label ? html `<div part="header"><span part="label">${this.label}</span></div>` : nothing}${this.filterable
478
487
  ? html `<input
479
488
  part="search-field"
@@ -493,8 +502,10 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
493
502
  id="tree"
494
503
  role="treegrid"
495
504
  ?data-scroller=${this.windowed}
505
+ tabindex=${stopless ? '0' : nothing}
496
506
  aria-label=${this.label || this.treeLabel}
497
507
  @keydown=${this.handleKeydown}
508
+ @focus=${this.handleTreeFocus}
498
509
  @focusin=${this.handleFocusIn}
499
510
  @focusout=${this.handleFocusOut}
500
511
  @scroll=${() => this.virtualize.measure()}
@@ -1,2 +1,3 @@
1
1
  export * from './group/option-group.js';
2
2
  export * from './root/option-root.js';
3
+ export * from './separator/option-separator.js';
@@ -1,2 +1,3 @@
1
1
  export * from './group/option-group.js';
2
2
  export * from './root/option-root.js';
3
+ export * from './separator/option-separator.js';
@@ -0,0 +1,30 @@
1
+ import { HelixElement } from '@fusion.dev/helix-core';
2
+ import { nothing, type PropertyValues } from 'lit';
3
+ /**
4
+ * @summary A rule between options or groups inside a listbox.
5
+ *
6
+ * @description
7
+ * Sets one run of options apart from the next where a heading would say too
8
+ * much, as React Aria's ListBox takes a `Separator`: actions apart from
9
+ * choices in a command palette, say, or the options a reader uses most apart
10
+ * from the rest.
11
+ *
12
+ * It has `role="separator"`, so a screen reader can say where one run ends,
13
+ * and it is no stop: the arrow keys move from the option before it to the one
14
+ * after. The list hides it while a filter is narrowing the options, since a
15
+ * rule between two runs of which only some options are left separates nothing.
16
+ *
17
+ * @since 0.1.0
18
+ * @status stable
19
+ * @aliases Divider, ListBoxSeparator
20
+ * @documentation https://fusion.dev/helix/components/option
21
+ */
22
+ export declare class HelixHeadlessOptionSeparator extends HelixElement {
23
+ protected willUpdate(changed: PropertyValues): void;
24
+ protected render(): typeof nothing;
25
+ }
26
+ declare global {
27
+ interface HTMLElementTagNameMap {
28
+ 'helix-headless-option-separator': HelixHeadlessOptionSeparator;
29
+ }
30
+ }
@@ -0,0 +1,21 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
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
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { customElement, HelixElement, hostAria } from '@fusion.dev/helix-core';
8
+ import { nothing } from 'lit';
9
+ let HelixHeadlessOptionSeparator = class HelixHeadlessOptionSeparator extends HelixElement {
10
+ willUpdate(changed) {
11
+ super.willUpdate(changed);
12
+ hostAria(this.internals, { role: 'separator' });
13
+ }
14
+ render() {
15
+ return nothing;
16
+ }
17
+ };
18
+ HelixHeadlessOptionSeparator = __decorate([
19
+ customElement('helix-headless-option-separator')
20
+ ], HelixHeadlessOptionSeparator);
21
+ export { HelixHeadlessOptionSeparator };
@@ -23,6 +23,18 @@ import { type Branch } from '../levels.js';
23
23
  * In a contents rail with a scrollbar of its own, `follow` keeps the link of
24
24
  * the section in view showing as the reader scrolls the page.
25
25
  *
26
+ * **Keyboard.** The links are your anchors, in the tab order as you wrote
27
+ * them, so the outline adds no keys of its own: Tab moves through the links
28
+ * and Enter follows one. Following a link moves focus to its section, so the
29
+ * next Tab goes on from the heading, also with `behavior` set.
30
+ *
31
+ * **What a screen reader hears.** A navigation landmark named by `label`, "On
32
+ * this page", or by the slotted heading. The links are lists, nested by level,
33
+ * so a reader hears how many links a level holds and where each one is. The
34
+ * link to the section in view carries `aria-current="location"`, read as
35
+ * "current location", and it moves as the page scrolls without announcing,
36
+ * which would interrupt the reading it follows.
37
+ *
26
38
  * The current section is decided by the headings' positions. An
27
39
  * `IntersectionObserver` with a band near the top fails both ways: when
28
40
  * headings are far apart, none is in the band and no section is marked, and
@@ -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, HelixActivateEvent, HelixElement, IndicatorController, plainClick, reflectElements, scrollItemIntoView, } from '@fusion.dev/helix-core';
7
+ import { customElement, elementById, HelixActivateEvent, HelixElement, IndicatorController, plainClick, reflectElements, scrollItemIntoView, scrollingAncestor, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  import { levelOf, nest } from '../levels.js';
@@ -148,16 +148,9 @@ let HelixHeadlessOutline = class HelixHeadlessOutline extends HelixElement {
148
148
  }
149
149
  reveal(link) {
150
150
  const sections = this.scroller;
151
- const page = this.page;
152
- for (let node = link.parentElement; node && node !== page; node = node.parentElement) {
153
- const { overflowY } = getComputedStyle(node);
154
- if (!/auto|scroll|overlay/.test(overflowY) || node.scrollHeight <= node.clientHeight) {
155
- continue;
156
- }
157
- if (node !== sections && !node.contains(sections)) {
158
- scrollItemIntoView(link, { scroller: node });
159
- }
160
- return;
151
+ const node = scrollingAncestor(link, { axis: 'y', overflowing: true, stop: this.page });
152
+ if (node && node !== sections && !node.contains(sections)) {
153
+ scrollItemIntoView(link, { scroller: node });
161
154
  }
162
155
  }
163
156
  get atEnd() {
@@ -169,15 +162,8 @@ let HelixHeadlessOutline = class HelixHeadlessOutline extends HelixElement {
169
162
  return (doc.scrollingElement ?? doc.documentElement);
170
163
  }
171
164
  get scroller() {
172
- const doc = globalThis.document;
173
165
  const first = this.sections[0]?.target;
174
- for (let node = first?.parentElement; node && node !== doc.body && node !== doc.documentElement; node = node.parentElement) {
175
- const { overflowY } = getComputedStyle(node);
176
- if (/auto|scroll|overlay/.test(overflowY) && node.scrollHeight > node.clientHeight) {
177
- return node;
178
- }
179
- }
180
- return this.page;
166
+ return (first && scrollingAncestor(first, { axis: 'y', overflowing: true })) || this.page;
181
167
  }
182
168
  reach() {
183
169
  for (const { target } of this.sections) {
@@ -14,6 +14,11 @@ import '../../select/index.js';
14
14
  * `currentColor`, and the label is a real `<label for>` pointing at the
15
15
  * trigger's id.
16
16
  *
17
+ * A `<label for>` pointing at this element, or its own `aria-label` or
18
+ * `aria-labelledby`, names the select in place of `label`, so a reader hears
19
+ * the page's words for it, and the select draws no label of its own beside
20
+ * the page's.
21
+ *
17
22
  * The label sits on the same row as the select. `Select`'s host is a column,
18
23
  * and an outer-tree rule overrides a `:host` rule, so the row layout needs no
19
24
  * property on `Select` and nothing added to your markup.
@@ -60,6 +65,7 @@ import '../../select/index.js';
60
65
  * @csspart control-listbox-loading - The text shown while the options load.
61
66
  * @csspart control-listbox-search - The region that holds the filter field.
62
67
  * @csspart control-listbox-search-field - The filter field.
68
+ * @csspart control-live - The select's `live` part, the region a screen reader hears a choice made on the closed trigger from.
63
69
  * @csspart control-surface - The floating panel.
64
70
  * @csspart control-trigger - The closed control, which shows the chosen size.
65
71
  * @csspart control-value - The chosen size inside the trigger.