@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
@@ -29,7 +29,10 @@ let HelixHeadlessVideoPlaylist = class HelixHeadlessVideoPlaylist extends MixinC
29
29
  this.chased = true;
30
30
  this.step(-1);
31
31
  };
32
- this.handleEnded = () => {
32
+ this.handleEnded = (event) => {
33
+ if (event.target !== this.player) {
34
+ return;
35
+ }
33
36
  this.chased = true;
34
37
  this.step(1);
35
38
  };
@@ -55,14 +58,14 @@ let HelixHeadlessVideoPlaylist = class HelixHeadlessVideoPlaylist extends MixinC
55
58
  this.addEventListener('helix-select', this.handleSelect);
56
59
  this.addEventListener('helix-next', this.handleNext);
57
60
  this.addEventListener('helix-previous', this.handlePrevious);
58
- this.addEventListener('ended', this.handleEnded);
61
+ this.addEventListener('ended', this.handleEnded, true);
59
62
  }
60
63
  disconnectedCallback() {
61
64
  super.disconnectedCallback();
62
65
  this.removeEventListener('helix-select', this.handleSelect);
63
66
  this.removeEventListener('helix-next', this.handleNext);
64
67
  this.removeEventListener('helix-previous', this.handlePrevious);
65
- this.removeEventListener('ended', this.handleEnded);
68
+ this.removeEventListener('ended', this.handleEnded, true);
66
69
  }
67
70
  updated(changed) {
68
71
  super.updated?.(changed);
@@ -1,4 +1,4 @@
1
- import { HelixElement } from '@fusion.dev/helix-core';
1
+ import { AriaForwardController, HelixElement } from '@fusion.dev/helix-core';
2
2
  import { type PropertyValues, type TemplateResult } from 'lit';
3
3
  /**
4
4
  * @summary A list that renders only the rows of a long set that are on screen.
@@ -21,6 +21,11 @@ import { type PropertyValues, type TemplateResult } from 'lit';
21
21
  * accessible `<table>`, and ten thousand rows in one is the cost this component
22
22
  * avoids.
23
23
  *
24
+ * The list follows the source: editing the script's text, or replacing the
25
+ * script under the same id, draws the new rows. A row that stays in view keeps
26
+ * its element as the list scrolls, and the row holding focus stays drawn when
27
+ * it scrolls out, so a link or a button in a row keeps focus.
28
+ *
24
29
  * The viewport scrolls on this tier. It carries `data-scroller`, and `core`
25
30
  * ships `[data-scroller] { overflow: auto }` into every shadow root, so
26
31
  * `scrollTop` works with no stylesheet. You still need to set a block size.
@@ -30,6 +35,8 @@ import { type PropertyValues, type TemplateResult } from 'lit';
30
35
  * @category layout
31
36
  * @documentation https://fusion.dev/helix/components/virtual-list
32
37
  *
38
+ * @slot - The `<template>` that defines one row. The list writes the rows it renders beside it, and this slot shows them between the two spacers.
39
+ *
33
40
  * @csspart after - The space that stands in for the rows below the rendered ones.
34
41
  * @csspart before - The space that stands in for the rows above the rendered ones.
35
42
  * @csspart viewport - The scrolling box. On this tier, it needs only a block size.
@@ -49,6 +56,9 @@ export declare class HelixHeadlessVirtualList extends HelixElement {
49
56
  /**
50
57
  * The list's name, read by screen readers, because only some of its rows
51
58
  * are rendered at a time.
59
+ *
60
+ * An `aria-label` or `aria-labelledby` on the host names the list too, and
61
+ * wins over this.
52
62
  */
53
63
  label: string;
54
64
  /**
@@ -70,6 +80,21 @@ export declare class HelixHeadlessVirtualList extends HelixElement {
70
80
  private rows;
71
81
  /** The `for` that `rows` came from, so it is read again only when that changes. */
72
82
  private readFor;
83
+ /** The rows the drawn elements were filled from, so new data fills them again. */
84
+ private filledFrom;
85
+ /** The script the rows were read from, which the watcher follows. */
86
+ private source;
87
+ /**
88
+ * Watches the source and the tree around it.
89
+ *
90
+ * **The list read its source once per `for`**, so a page that wrote new JSON
91
+ * into the script, the way a framework updates a text child, kept the old
92
+ * rows. The script's own text is watched, and the tree it sits in, for a
93
+ * script replaced under the same id.
94
+ */
95
+ private watcher;
96
+ /** Carries a host `aria-label` and `aria-labelledby` to the element with the `list` role. */
97
+ protected readonly aria: AriaForwardController;
73
98
  private readonly virtualize;
74
99
  /**
75
100
  * The rows in the tree, each with the item it drew.
@@ -98,15 +123,38 @@ export declare class HelixHeadlessVirtualList extends HelixElement {
98
123
  * so by itself.
99
124
  */
100
125
  protected willUpdate(changed: PropertyValues): void;
126
+ connectedCallback(): void;
127
+ disconnectedCallback(): void;
128
+ /**
129
+ * Reads the source again when its text changed or another element took its
130
+ * id. Rows this list drew are its own writes and are not news.
131
+ */
132
+ private handleMutations;
101
133
  protected firstUpdated(changed: PropertyValues): void;
102
134
  protected updated(changed: PropertyValues): void;
103
135
  private read;
136
+ /**
137
+ * The item whose row holds focus, where one does.
138
+ *
139
+ * Asked of the root this list is in, which retargets focus inside a row's
140
+ * own shadow root to the row's element.
141
+ */
142
+ private focusedIndex;
143
+ /** The item drawn outside the window to keep focus, if there is one. */
144
+ private keptIndex;
145
+ /** Fills a row's holes and its place in the set from an item. */
146
+ private fill;
104
147
  /**
105
148
  * Puts the windowed rows into the light DOM, and takes the rest out.
106
149
  *
107
- * Cleared and re-cloned rather than reconciled: a window is twenty-odd rows
108
- * and cloning that many is well under a frame. A reconciler here would carry
109
- * keys and identity for a set nothing selects into.
150
+ * **A row that stays in the window keeps its element.** Clearing and
151
+ * cloning every row on every move replaced the node a reader had focused,
152
+ * so a link in a row lost focus to the body on any scroll, even with its row
153
+ * still on screen. Rows are keyed by item: the ones leaving go, the ones
154
+ * arriving are cloned in their place, and the ones staying are not moved.
155
+ *
156
+ * **The row holding focus is drawn outside the window too**, beside it on the
157
+ * side it belongs, and the spacer on that side gives up its room.
110
158
  */
111
159
  private place;
112
160
  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 { customElement, elementById, HelixElement, searchableById, VirtualizeController, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, customElement, elementById, HelixElement, searchableById, VirtualizeController, } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
9
  import { property, state } from 'lit/decorators.js';
10
10
  let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixElement {
@@ -15,6 +15,11 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
15
15
  this.label = '';
16
16
  this.measured = false;
17
17
  this.rows = [];
18
+ this.source = null;
19
+ this.aria = new AriaForwardController(this, {
20
+ label: true,
21
+ target: () => this.partNamed('viewport'),
22
+ });
18
23
  this.virtualize = new VirtualizeController(this, {
19
24
  container: () => this.partNamed('viewport'),
20
25
  count: () => this.rows.length,
@@ -36,6 +41,33 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
36
41
  this.read();
37
42
  }
38
43
  }
44
+ connectedCallback() {
45
+ super.connectedCallback();
46
+ if (typeof MutationObserver === 'undefined') {
47
+ return;
48
+ }
49
+ this.watcher = new MutationObserver((records) => this.handleMutations(records));
50
+ this.watcher.observe(this.getRootNode(), {
51
+ characterData: true,
52
+ childList: true,
53
+ subtree: true,
54
+ });
55
+ }
56
+ disconnectedCallback() {
57
+ super.disconnectedCallback();
58
+ this.watcher?.disconnect();
59
+ this.watcher = undefined;
60
+ }
61
+ handleMutations(records) {
62
+ if (this.for === '' || this.hydrating || this.readFor === undefined) {
63
+ return;
64
+ }
65
+ const found = elementById(this, this.for);
66
+ const edited = records.some((record) => found !== null && (record.target === found || found.contains(record.target)));
67
+ if (found !== this.source || edited) {
68
+ this.read();
69
+ }
70
+ }
39
71
  firstUpdated(changed) {
40
72
  super.firstUpdated?.(changed);
41
73
  this.finishHydration();
@@ -47,6 +79,7 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
47
79
  }
48
80
  read() {
49
81
  const source = this.for === '' ? null : elementById(this, this.for);
82
+ this.source = source;
50
83
  const text = source?.textContent ?? '';
51
84
  if (text.trim() === '') {
52
85
  this.rows = [];
@@ -60,40 +93,89 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
60
93
  this.rows = [];
61
94
  }
62
95
  }
96
+ focusedIndex() {
97
+ const root = this.getRootNode();
98
+ let node = root.activeElement;
99
+ while (node && node.parentElement !== this) {
100
+ node = node.parentElement;
101
+ }
102
+ const index = node instanceof HTMLElement ? node.dataset.index : undefined;
103
+ return index === undefined ? undefined : Number(index);
104
+ }
105
+ keptIndex() {
106
+ const index = this.focusedIndex();
107
+ const { first, last } = this.virtualize.window;
108
+ return index !== undefined && index < this.rows.length && (index < first || index >= last)
109
+ ? index
110
+ : undefined;
111
+ }
112
+ fill(elements, row, at) {
113
+ const total = String(this.rows.length);
114
+ for (const element of elements) {
115
+ for (const hole of [
116
+ ...(element.matches('[data-field]') ? [element] : []),
117
+ ...element.querySelectorAll('[data-field]'),
118
+ ]) {
119
+ hole.textContent = row[hole.dataset.field ?? ''] ?? '';
120
+ }
121
+ element.setAttribute('role', 'listitem');
122
+ element.setAttribute('aria-posinset', String(at + 1));
123
+ element.setAttribute('aria-setsize', total);
124
+ element.setAttribute('data-index', String(at));
125
+ }
126
+ }
63
127
  place() {
64
128
  const template = this.querySelector('template');
65
129
  if (!template) {
66
130
  return;
67
131
  }
68
- for (const stale of [...this.children]) {
69
- if (stale !== template) {
70
- stale.remove();
132
+ const { first, last } = this.virtualize.window;
133
+ const kept = this.keptIndex();
134
+ const wanted = [];
135
+ for (let at = first; at < Math.min(last, this.rows.length); at += 1) {
136
+ wanted.push(at);
137
+ }
138
+ if (kept !== undefined) {
139
+ wanted.push(kept);
140
+ wanted.sort((a, b) => a - b);
141
+ }
142
+ const keep = new Set(wanted);
143
+ const drawn = new Map();
144
+ for (const child of [...this.children]) {
145
+ if (child === template) {
146
+ continue;
147
+ }
148
+ const index = Number(child.dataset.index);
149
+ if (!Number.isInteger(index) || !keep.has(index)) {
150
+ child.remove();
151
+ continue;
71
152
  }
153
+ drawn.set(index, [...(drawn.get(index) ?? []), child]);
72
154
  }
73
- const { first, last } = this.virtualize.window;
74
- const total = this.rows.length;
75
- const fragment = this.ownerDocument.createDocumentFragment();
76
- for (let at = first; at < last; at += 1) {
155
+ const refill = this.filledFrom !== this.rows;
156
+ this.filledFrom = this.rows;
157
+ let anchor = drawn.values().next().value?.[0] ?? null;
158
+ for (const at of wanted) {
77
159
  const row = this.rows[at];
78
- if (!row) {
160
+ const group = drawn.get(at);
161
+ if (group) {
162
+ if (refill) {
163
+ this.fill(group, row, at);
164
+ }
165
+ anchor = group.at(-1).nextElementSibling;
79
166
  continue;
80
167
  }
81
168
  const clone = template.content.cloneNode(true);
82
- for (const hole of clone.querySelectorAll('[data-field]')) {
83
- hole.textContent = row[hole.dataset.field ?? ''] ?? '';
84
- }
85
- for (const element of clone.children) {
86
- element.setAttribute('role', 'listitem');
87
- element.setAttribute('aria-posinset', String(at + 1));
88
- element.setAttribute('aria-setsize', String(total));
89
- element.setAttribute('data-index', String(at));
90
- }
91
- fragment.append(clone);
169
+ this.fill([...clone.children], row, at);
170
+ this.insertBefore(clone, anchor);
92
171
  }
93
- this.append(fragment);
94
172
  }
95
173
  render() {
96
- const { after, before } = this.virtualize.window;
174
+ const window = this.virtualize.window;
175
+ const kept = this.hasUpdated ? this.keptIndex() : undefined;
176
+ const room = kept === undefined ? 0 : this.virtualize.sizeOf(kept);
177
+ const before = window.before - (kept !== undefined && kept < window.first ? room : 0);
178
+ const after = window.after - (kept !== undefined && kept >= window.last ? room : 0);
97
179
  return html `<div
98
180
  data-scroller
99
181
  part="viewport"