@fusion.dev/helix-headless 0.143.0 → 0.145.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 (314) hide show
  1. package/custom-elements.json +16990 -1383
  2. package/dist/accordion/item/accordion-item.d.ts +48 -12
  3. package/dist/accordion/item/accordion-item.js +26 -5
  4. package/dist/accordion/root/accordion-root.d.ts +6 -0
  5. package/dist/accordion/root/accordion-root.js +7 -1
  6. package/dist/announcement/root/announcement-root.d.ts +21 -1
  7. package/dist/announcement/root/announcement-root.js +36 -1
  8. package/dist/app-header/root/app-header-root.d.ts +7 -0
  9. package/dist/app-header/root/app-header-root.js +5 -1
  10. package/dist/app-header/sectioning.d.ts +7 -0
  11. package/dist/app-header/sectioning.js +26 -0
  12. package/dist/area-chart/root/area-chart-root.d.ts +12 -4
  13. package/dist/area-chart/root/area-chart-root.js +24 -7
  14. package/dist/avatar/root/avatar-root.d.ts +14 -0
  15. package/dist/avatar/root/avatar-root.js +23 -9
  16. package/dist/badge/root/badge-root.d.ts +11 -7
  17. package/dist/badge/root/badge-root.js +1 -1
  18. package/dist/bar-chart/root/bar-chart-root.d.ts +12 -3
  19. package/dist/bar-chart/root/bar-chart-root.js +17 -10
  20. package/dist/breadcrumbs/root/breadcrumbs-root.d.ts +32 -1
  21. package/dist/breadcrumbs/root/breadcrumbs-root.js +25 -2
  22. package/dist/button/root/button-root.d.ts +47 -1
  23. package/dist/button/root/button-root.js +38 -4
  24. package/dist/callout/root/callout-root.d.ts +18 -1
  25. package/dist/callout/root/callout-root.js +15 -4
  26. package/dist/canvas/root/canvas-root.d.ts +38 -3
  27. package/dist/canvas/root/canvas-root.js +121 -40
  28. package/dist/card/root/card-root.d.ts +30 -1
  29. package/dist/card/root/card-root.js +38 -5
  30. package/dist/carousel/root/carousel-root.d.ts +90 -0
  31. package/dist/carousel/root/carousel-root.js +134 -12
  32. package/dist/chat/composer/chat-composer.d.ts +38 -6
  33. package/dist/chat/composer/chat-composer.js +42 -9
  34. package/dist/chat/message/chat-message.d.ts +32 -1
  35. package/dist/chat/message/chat-message.js +25 -9
  36. package/dist/chat/naming.d.ts +22 -0
  37. package/dist/chat/naming.js +12 -0
  38. package/dist/chat/relative-clock.d.ts +39 -0
  39. package/dist/chat/relative-clock.js +31 -0
  40. package/dist/chat/root/chat-root.d.ts +36 -0
  41. package/dist/chat/root/chat-root.js +24 -1
  42. package/dist/checkbox/root/checkbox-root.js +2 -0
  43. package/dist/checkbox-group/root/checkbox-group-root.d.ts +53 -3
  44. package/dist/checkbox-group/root/checkbox-group-root.js +71 -22
  45. package/dist/code-block/lines.d.ts +13 -0
  46. package/dist/code-block/lines.js +2 -0
  47. package/dist/code-block/root/code-block-root.d.ts +38 -1
  48. package/dist/code-block/root/code-block-root.js +28 -7
  49. package/dist/color-input/root/color-input-root.d.ts +34 -1
  50. package/dist/color-input/root/color-input-root.js +23 -4
  51. package/dist/color-picker/channels.d.ts +26 -0
  52. package/dist/color-picker/channels.js +84 -0
  53. package/dist/color-picker/root/color-picker-root.d.ts +81 -5
  54. package/dist/color-picker/root/color-picker-root.js +103 -10
  55. package/dist/combobox/root/combobox-root.d.ts +38 -3
  56. package/dist/combobox/root/combobox-root.js +64 -2
  57. package/dist/command/root/command-root.d.ts +30 -4
  58. package/dist/command/root/command-root.js +33 -3
  59. package/dist/copy-button/from.d.ts +22 -0
  60. package/dist/copy-button/from.js +28 -0
  61. package/dist/copy-button/root/copy-button-root.d.ts +47 -1
  62. package/dist/copy-button/root/copy-button-root.js +44 -8
  63. package/dist/data-table/root/data-table-root.d.ts +150 -2
  64. package/dist/data-table/root/data-table-root.js +303 -29
  65. package/dist/data-table/tree.d.ts +40 -0
  66. package/dist/data-table/tree.js +55 -0
  67. package/dist/date-input/root/date-input-root.d.ts +73 -4
  68. package/dist/date-input/root/date-input-root.js +93 -43
  69. package/dist/date-picker/root/date-picker-root.d.ts +145 -1
  70. package/dist/date-picker/root/date-picker-root.js +239 -48
  71. package/dist/description-list/item/description-list-item.d.ts +17 -2
  72. package/dist/description-list/item/description-list-item.js +15 -2
  73. package/dist/description-list/root/description-list-root.d.ts +27 -1
  74. package/dist/description-list/root/description-list-root.js +26 -1
  75. package/dist/dialog/description.d.ts +9 -0
  76. package/dist/dialog/description.js +14 -0
  77. package/dist/dialog/root/dialog-root.d.ts +64 -1
  78. package/dist/dialog/root/dialog-root.js +49 -23
  79. package/dist/donut-chart/root/donut-chart-root.d.ts +25 -4
  80. package/dist/donut-chart/root/donut-chart-root.js +21 -6
  81. package/dist/drawer/root/drawer-root.d.ts +63 -2
  82. package/dist/drawer/root/drawer-root.js +95 -19
  83. package/dist/drawer/swipe-start.d.ts +9 -0
  84. package/dist/drawer/swipe-start.js +12 -0
  85. package/dist/drilldown/item/drilldown-item.d.ts +11 -6
  86. package/dist/drilldown/item/drilldown-item.js +8 -5
  87. package/dist/drilldown/root/drilldown-root.d.ts +11 -0
  88. package/dist/drilldown/root/drilldown-root.js +8 -3
  89. package/dist/element-events.generated.js +61 -13
  90. package/dist/empty/root/empty-root.d.ts +1 -1
  91. package/dist/file-input/root/file-input-root.d.ts +47 -0
  92. package/dist/file-input/root/file-input-root.js +60 -22
  93. package/dist/file-input/screen.d.ts +35 -0
  94. package/dist/file-input/screen.js +49 -0
  95. package/dist/filter-input/menu/filter-menu.d.ts +3 -0
  96. package/dist/filter-input/menu/filter-menu.js +8 -4
  97. package/dist/filter-input/root/filter-input-root.d.ts +38 -4
  98. package/dist/filter-input/root/filter-input-root.js +67 -3
  99. package/dist/filter-input/suggest.d.ts +17 -0
  100. package/dist/filter-input/suggest.js +23 -0
  101. package/dist/format-date/options.d.ts +33 -0
  102. package/dist/format-date/options.js +42 -0
  103. package/dist/format-date/root/format-date-root.d.ts +51 -0
  104. package/dist/format-date/root/format-date-root.js +58 -38
  105. package/dist/heatmap/root/heatmap-root.d.ts +23 -6
  106. package/dist/heatmap/root/heatmap-root.js +24 -13
  107. package/dist/icon/index.d.ts +2 -0
  108. package/dist/icon/index.js +2 -0
  109. package/dist/icon/registry.d.ts +37 -0
  110. package/dist/icon/registry.js +83 -0
  111. package/dist/icon/root/icon-root.d.ts +30 -4
  112. package/dist/icon/root/icon-root.js +41 -6
  113. package/dist/icon/root/index.d.ts +2 -0
  114. package/dist/icon/root/index.js +2 -0
  115. package/dist/icon/set.d.ts +1 -0
  116. package/dist/icon/set.js +3 -0
  117. package/dist/image/root/image-root.d.ts +14 -0
  118. package/dist/image/root/image-root.js +23 -2
  119. package/dist/input/root/input-root.d.ts +11 -0
  120. package/dist/input/root/input-root.js +16 -1
  121. package/dist/kbd/root/kbd-root.d.ts +20 -13
  122. package/dist/kbd/root/kbd-root.js +35 -37
  123. package/dist/kbd/shortcut.d.ts +18 -0
  124. package/dist/kbd/shortcut.js +35 -0
  125. package/dist/legend/hidden-series.d.ts +8 -0
  126. package/dist/legend/hidden-series.js +13 -0
  127. package/dist/legend/root/legend-root.d.ts +56 -0
  128. package/dist/legend/root/legend-root.js +54 -5
  129. package/dist/lightbox/item/lightbox-item.d.ts +15 -0
  130. package/dist/lightbox/item/lightbox-item.js +10 -1
  131. package/dist/lightbox/root/lightbox-root.d.ts +61 -1
  132. package/dist/lightbox/root/lightbox-root.js +94 -4
  133. package/dist/line-chart/root/line-chart-root.d.ts +11 -3
  134. package/dist/line-chart/root/line-chart-root.js +19 -5
  135. package/dist/list/item/list-item.d.ts +68 -1
  136. package/dist/list/item/list-item.js +66 -4
  137. package/dist/list/numbering.d.ts +14 -0
  138. package/dist/list/numbering.js +8 -0
  139. package/dist/list/root/list-root.d.ts +106 -9
  140. package/dist/list/root/list-root.js +189 -4
  141. package/dist/list/selection.d.ts +19 -0
  142. package/dist/list/selection.js +21 -0
  143. package/dist/listbox/root/listbox-root.d.ts +74 -3
  144. package/dist/listbox/root/listbox-root.js +86 -9
  145. package/dist/loader/busy.d.ts +17 -0
  146. package/dist/loader/busy.js +24 -0
  147. package/dist/loader/root/loader-root.d.ts +37 -0
  148. package/dist/loader/root/loader-root.js +52 -3
  149. package/dist/map/root/map-root.d.ts +6 -2
  150. package/dist/map/root/map-root.js +5 -2
  151. package/dist/megamenu/root/megamenu-root.d.ts +21 -0
  152. package/dist/megamenu/root/megamenu-root.js +16 -2
  153. package/dist/menu/index.d.ts +1 -0
  154. package/dist/menu/index.js +1 -0
  155. package/dist/menu/item/menu-item.d.ts +38 -11
  156. package/dist/menu/item/menu-item.js +33 -1
  157. package/dist/menu/root/menu-root.d.ts +25 -1
  158. package/dist/menu/root/menu-root.js +63 -6
  159. package/dist/menu/section/menu-section.d.ts +55 -0
  160. package/dist/menu/section/menu-section.js +60 -0
  161. package/dist/meter/root/meter-root.d.ts +7 -0
  162. package/dist/meter/root/meter-root.js +6 -3
  163. package/dist/navigation/root/navigation-root.d.ts +16 -1
  164. package/dist/navigation/root/navigation-root.js +27 -1
  165. package/dist/notifications/feed-keys.d.ts +10 -0
  166. package/dist/notifications/feed-keys.js +14 -0
  167. package/dist/notifications/notification/notification.d.ts +17 -0
  168. package/dist/notifications/notification/notification.js +21 -2
  169. package/dist/notifications/root/notifications-root.d.ts +60 -0
  170. package/dist/notifications/root/notifications-root.js +76 -1
  171. package/dist/number-input/root/number-input-root.d.ts +47 -0
  172. package/dist/number-input/root/number-input-root.js +58 -22
  173. package/dist/object-inspector/root/object-inspector-root.d.ts +98 -16
  174. package/dist/object-inspector/root/object-inspector-root.js +180 -80
  175. package/dist/object-inspector/rows.d.ts +57 -0
  176. package/dist/object-inspector/rows.js +86 -0
  177. package/dist/option/root/option-root.d.ts +54 -5
  178. package/dist/option/root/option-root.js +34 -5
  179. package/dist/outline/levels.d.ts +31 -0
  180. package/dist/outline/levels.js +23 -0
  181. package/dist/outline/root/outline-root.d.ts +68 -3
  182. package/dist/outline/root/outline-root.js +73 -8
  183. package/dist/pagination/size/pagination-size.d.ts +11 -2
  184. package/dist/pagination/size/pagination-size.js +19 -4
  185. package/dist/pin-input/format.d.ts +7 -0
  186. package/dist/pin-input/format.js +16 -0
  187. package/dist/pin-input/root/pin-input-root.d.ts +21 -0
  188. package/dist/pin-input/root/pin-input-root.js +29 -2
  189. package/dist/popover/root/popover-root.d.ts +62 -4
  190. package/dist/popover/root/popover-root.js +53 -5
  191. package/dist/progress/root/progress-root.d.ts +7 -0
  192. package/dist/progress/root/progress-root.js +9 -3
  193. package/dist/qr-code/export.d.ts +31 -0
  194. package/dist/qr-code/export.js +10 -0
  195. package/dist/qr-code/root/qr-code-root.d.ts +26 -0
  196. package/dist/qr-code/root/qr-code-root.js +49 -2
  197. package/dist/radio/group/radio-group.d.ts +17 -0
  198. package/dist/radio/group/radio-group.js +14 -1
  199. package/dist/rating/root/rating-root.d.ts +25 -0
  200. package/dist/rating/root/rating-root.js +46 -4
  201. package/dist/scatter-chart/root/scatter-chart-root.d.ts +17 -3
  202. package/dist/scatter-chart/root/scatter-chart-root.js +25 -7
  203. package/dist/scroll-area/root/scroll-area-root.d.ts +113 -0
  204. package/dist/scroll-area/root/scroll-area-root.js +218 -3
  205. package/dist/scroll-area/scrollbar.d.ts +65 -0
  206. package/dist/scroll-area/scrollbar.js +24 -0
  207. package/dist/select/root/select-root.d.ts +24 -0
  208. package/dist/select/root/select-root.js +21 -7
  209. package/dist/skeleton/root/skeleton-root.d.ts +25 -0
  210. package/dist/skeleton/root/skeleton-root.js +17 -2
  211. package/dist/slider/root/slider-root.d.ts +39 -0
  212. package/dist/slider/root/slider-root.js +45 -9
  213. package/dist/splitter/root/splitter-root.d.ts +13 -0
  214. package/dist/splitter/root/splitter-root.js +20 -2
  215. package/dist/splitter/snap.d.ts +9 -0
  216. package/dist/splitter/snap.js +12 -0
  217. package/dist/stat/root/stat-root.d.ts +2 -0
  218. package/dist/stat/root/stat-root.js +1 -1
  219. package/dist/steps/reach.d.ts +15 -0
  220. package/dist/steps/reach.js +12 -0
  221. package/dist/steps/root/steps-root.d.ts +30 -0
  222. package/dist/steps/root/steps-root.js +53 -0
  223. package/dist/steps/step/step.d.ts +39 -2
  224. package/dist/steps/step/step.js +29 -7
  225. package/dist/switch/root/switch-root.d.ts +12 -2
  226. package/dist/switch/root/switch-root.js +6 -1
  227. package/dist/table/column/table-column.d.ts +11 -0
  228. package/dist/table/column/table-column.js +6 -1
  229. package/dist/table/root/table-root.d.ts +101 -0
  230. package/dist/table/root/table-root.js +166 -5
  231. package/dist/table/row/table-row.d.ts +47 -1
  232. package/dist/table/row/table-row.js +39 -2
  233. package/dist/tabs/panel/tab-panel.d.ts +9 -5
  234. package/dist/tabs/panel/tab-panel.js +3 -4
  235. package/dist/tabs/root/tabs-root.d.ts +25 -1
  236. package/dist/tabs/root/tabs-root.js +21 -3
  237. package/dist/tabs/tab/tab.d.ts +36 -4
  238. package/dist/tabs/tab/tab.js +46 -3
  239. package/dist/tag/group/tag-group.d.ts +107 -0
  240. package/dist/tag/group/tag-group.js +157 -0
  241. package/dist/tag/index.d.ts +1 -0
  242. package/dist/tag/index.js +1 -0
  243. package/dist/tag/root/tag-root.d.ts +60 -0
  244. package/dist/tag/root/tag-root.js +74 -18
  245. package/dist/tag/selection.d.ts +10 -0
  246. package/dist/tag/selection.js +12 -0
  247. package/dist/textarea/grow.d.ts +22 -0
  248. package/dist/textarea/grow.js +20 -0
  249. package/dist/textarea/root/textarea-root.d.ts +9 -0
  250. package/dist/textarea/root/textarea-root.js +9 -1
  251. package/dist/time-input/root/time-input-root.d.ts +44 -5
  252. package/dist/time-input/root/time-input-root.js +69 -39
  253. package/dist/time-picker/root/time-picker-root.d.ts +54 -8
  254. package/dist/time-picker/root/time-picker-root.js +121 -91
  255. package/dist/timeline/group/timeline-group.d.ts +46 -0
  256. package/dist/timeline/group/timeline-group.js +37 -0
  257. package/dist/timeline/index.d.ts +1 -0
  258. package/dist/timeline/index.js +1 -0
  259. package/dist/timeline/root/timeline-root.d.ts +10 -3
  260. package/dist/toast/hotkey.d.ts +22 -0
  261. package/dist/toast/hotkey.js +23 -0
  262. package/dist/toast/item/toast-item.d.ts +26 -18
  263. package/dist/toast/item/toast-item.js +55 -48
  264. package/dist/toast/root/toast-root.d.ts +42 -8
  265. package/dist/toast/root/toast-root.js +64 -4
  266. package/dist/toggle/group/toggle-group.d.ts +57 -15
  267. package/dist/toggle/group/toggle-group.js +69 -9
  268. package/dist/toggle/root/toggle-root.d.ts +12 -1
  269. package/dist/toggle/root/toggle-root.js +11 -3
  270. package/dist/toolbar/root/toolbar-root.d.ts +13 -0
  271. package/dist/toolbar/root/toolbar-root.js +15 -5
  272. package/dist/tooltip/focus-target.d.ts +6 -0
  273. package/dist/tooltip/focus-target.js +9 -0
  274. package/dist/tooltip/root/tooltip-root.d.ts +42 -17
  275. package/dist/tooltip/root/tooltip-root.js +50 -9
  276. package/dist/tour/naming.d.ts +30 -0
  277. package/dist/tour/naming.js +10 -0
  278. package/dist/tour/root/tour-root.d.ts +35 -0
  279. package/dist/tour/root/tour-root.js +56 -5
  280. package/dist/tour/stop/tour-stop.d.ts +53 -8
  281. package/dist/tour/stop/tour-stop.js +45 -15
  282. package/dist/tour/wait.d.ts +11 -0
  283. package/dist/tour/wait.js +19 -0
  284. package/dist/tree/item/tree-item.d.ts +54 -1
  285. package/dist/tree/item/tree-item.js +89 -20
  286. package/dist/tree/root/tree-root.d.ts +37 -0
  287. package/dist/tree/root/tree-root.js +51 -6
  288. package/dist/tree/selection.d.ts +24 -0
  289. package/dist/tree/selection.js +26 -0
  290. package/dist/trend/root/trend-root.d.ts +13 -0
  291. package/dist/trend/root/trend-root.js +14 -9
  292. package/dist/video/media-events.d.ts +49 -0
  293. package/dist/video/media-events.js +39 -0
  294. package/dist/video/root/video-root.d.ts +99 -13
  295. package/dist/video/root/video-root.js +84 -47
  296. package/dist/video-playlist/menu/video-playlist-menu.d.ts +3 -0
  297. package/dist/video-playlist/menu/video-playlist-menu.js +2 -1
  298. package/dist/video-playlist/order.d.ts +41 -0
  299. package/dist/video-playlist/order.js +34 -0
  300. package/dist/video-playlist/root/video-playlist-root.d.ts +70 -2
  301. package/dist/video-playlist/root/video-playlist-root.js +60 -16
  302. package/dist/virtual-list/controls.d.ts +4 -0
  303. package/dist/virtual-list/controls.js +30 -0
  304. package/dist/virtual-list/fill.d.ts +30 -0
  305. package/dist/virtual-list/fill.js +39 -0
  306. package/dist/virtual-list/keys.d.ts +15 -0
  307. package/dist/virtual-list/keys.js +24 -0
  308. package/dist/virtual-list/root/virtual-list-root.d.ts +155 -23
  309. package/dist/virtual-list/root/virtual-list-root.js +175 -34
  310. package/lint.json +1 -1
  311. package/package.json +5 -5
  312. package/types/jsx.d.ts +8871 -1286
  313. package/types/svelte.d.ts +3 -0
  314. package/types/vue.d.ts +1918 -269
@@ -1,5 +1,6 @@
1
1
  import { HelixElement } from '@fusion.dev/helix-core';
2
2
  import { type PropertyValues, type TemplateResult } from 'lit';
3
+ import { type Repeat } from '../order.js';
3
4
  declare const HelixHeadlessVideoPlaylist_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerActiveInterface) & typeof HelixElement;
4
5
  /**
5
6
  * @summary A list of video clips that play one after another in a single player.
@@ -22,7 +23,11 @@ declare const HelixHeadlessVideoPlaylist_base: (new (...args: any[]) => import("
22
23
  * - the player reaches the end.
23
24
  *
24
25
  * On `ended` the playlist advances and the next clip starts playing, because
25
- * the user already pressed play once.
26
+ * the user already pressed play once. `repeat` decides what happens at the
27
+ * end: `one` plays the same entry again, and `all` goes from the last entry
28
+ * back to the first. Without it, the playlist stops on the last entry and
29
+ * sends `helix-end`. `shuffle` plays the entries in a random order, starting
30
+ * from the one that is current, and `previous` retraces that order.
26
31
  *
27
32
  * @since 0.1.0
28
33
  * @status stable
@@ -35,6 +40,7 @@ declare const HelixHeadlessVideoPlaylist_base: (new (...args: any[]) => import("
35
40
  *
36
41
  * @event {HelixActivateEvent} helix-activate - The current entry changed. The playlist already holds the new entry when this is sent.
37
42
  * @event {HelixBeforeActivateEvent} helix-before-activate - The current entry is about to change. Cancelable: cancel it to keep the current entry.
43
+ * @event {HelixEndEvent} helix-end - The last entry finished playing, and the playlist stopped there. Not sent while `repeat` is set.
38
44
  *
39
45
  * @csspart items - The list of entries.
40
46
  * @csspart player - The region that holds the player.
@@ -44,6 +50,30 @@ export declare class HelixHeadlessVideoPlaylist extends HelixHeadlessVideoPlayli
44
50
  * The list's name, read by screen readers.
45
51
  */
46
52
  label: string;
53
+ /**
54
+ * What happens when an entry ends with no next one to go to. `one` plays the
55
+ * same entry again, and `all` goes from the last entry back to the first.
56
+ * Absent, the playlist stops on the last entry and sends `helix-end`.
57
+ *
58
+ * `all` also lets the next and previous controls go round, so next on the
59
+ * last entry is the first. `one` changes only what an ending does: next and
60
+ * previous still move, being a reader asking for another entry.
61
+ */
62
+ repeat?: Repeat;
63
+ /**
64
+ * Whether the entries play in a random order. The order starts from the
65
+ * entry that is current, and holds until shuffle is turned off or an entry
66
+ * arrives or leaves, so previous goes back through what has played.
67
+ */
68
+ shuffle: boolean;
69
+ /**
70
+ * The shuffled order of the entries' values, while `shuffle` is on.
71
+ *
72
+ * Held rather than drawn on each step, so previous retraces the way next
73
+ * came. Dropped when shuffle turns off, so turning it on again deals a new
74
+ * order from wherever the reader is.
75
+ */
76
+ private dealt;
47
77
  /**
48
78
  * The entries, in the order the consumer wrote them.
49
79
  *
@@ -82,14 +112,52 @@ export declare class HelixHeadlessVideoPlaylist extends HelixHeadlessVideoPlayli
82
112
  * contract.
83
113
  */
84
114
  private get player();
85
- /** Moves by one, and does nothing at either end. */
115
+ /**
116
+ * The entry a step of `by` lands on, in the order the playlist plays in, or
117
+ * `undefined` past an end. Disabled entries are not in the order, so a step
118
+ * passes over them.
119
+ */
120
+ private following;
121
+ /**
122
+ * Moves by one in the playing order and plays what it lands on. At an end it
123
+ * stays, unless `repeat` is `all`.
124
+ *
125
+ * A move a consumer refuses with `helix-before-activate` plays nothing, and
126
+ * leaves nothing behind to play on a later change.
127
+ */
86
128
  private step;
129
+ /**
130
+ * Plays the current entry again from its start, which is what an ending
131
+ * under `repeat="one"` asks for, and a list of one under `repeat="all"`.
132
+ */
133
+ private replay;
134
+ /**
135
+ * Moves to the next entry and plays it, as the player's next control does.
136
+ * Follows `shuffle`, goes round under `repeat="all"`, and stays on the last
137
+ * entry otherwise.
138
+ */
139
+ next(): void;
140
+ /**
141
+ * Moves to the previous entry and plays it, as the player's previous control
142
+ * does. Under `shuffle` it goes back through the order that played.
143
+ */
144
+ previous(): void;
87
145
  private readonly handleSelect;
88
146
  private readonly handleNext;
89
147
  private readonly handlePrevious;
148
+ /**
149
+ * An entry ended: the same one again under `repeat="one"`, the next one in
150
+ * the order, or, past the last, `helix-end`.
151
+ *
152
+ * **A notification with no class of its own**, sent as a plain event and
153
+ * carrying nothing: the playlist is still on the last entry, and `active`
154
+ * says which. Not sent under `repeat`, where a list never runs out.
155
+ */
90
156
  private readonly handleEnded;
91
157
  connectedCallback(): void;
92
158
  disconnectedCallback(): void;
159
+ /** A shuffle turned off forgets its order, so the next one is new. */
160
+ protected willUpdate(changed: PropertyValues): void;
93
161
  /**
94
162
  * Points the player at the current entry, and tells the entries which is on.
95
163
  *
@@ -4,14 +4,16 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { customElement, HelixElement, MixinControllerActive } from '@fusion.dev/helix-core';
7
+ import { customElement, HelixElement, HelixEndEvent, MixinControllerActive, } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  import { HelixHeadlessVideoPlaylistItem } from '../item/video-playlist-item.js';
11
+ import { playOrder, stepTo } from '../order.js';
11
12
  let HelixHeadlessVideoPlaylist = class HelixHeadlessVideoPlaylist extends MixinControllerActive(HelixElement) {
12
13
  constructor() {
13
14
  super(...arguments);
14
15
  this.label = 'Playlist';
16
+ this.shuffle = false;
15
17
  this.chased = false;
16
18
  this.handleSelect = (event) => {
17
19
  event.stopImmediatePropagation();
@@ -22,18 +24,23 @@ let HelixHeadlessVideoPlaylist = class HelixHeadlessVideoPlaylist extends MixinC
22
24
  this.requestActive(event.detail.value);
23
25
  };
24
26
  this.handleNext = () => {
25
- this.chased = true;
26
- this.step(1);
27
+ this.next();
27
28
  };
28
29
  this.handlePrevious = () => {
29
- this.chased = true;
30
- this.step(-1);
30
+ this.previous();
31
31
  };
32
32
  this.handleEnded = (event) => {
33
33
  if (event.target !== this.player) {
34
34
  return;
35
35
  }
36
- this.chased = true;
36
+ if (this.repeat === 'one') {
37
+ this.replay();
38
+ return;
39
+ }
40
+ if (this.following(1).next === undefined) {
41
+ this.dispatchEvent(new HelixEndEvent());
42
+ return;
43
+ }
37
44
  this.step(1);
38
45
  };
39
46
  }
@@ -42,17 +49,44 @@ let HelixHeadlessVideoPlaylist = class HelixHeadlessVideoPlaylist extends MixinC
42
49
  }
43
50
  get player() {
44
51
  const slot = this.renderRoot?.querySelector('slot[name="player"]');
45
- const first = slot?.assignedElements()[0];
46
- return (first ?? null);
52
+ return slot?.assignedElements()[0] ?? null;
47
53
  }
48
- step(by) {
54
+ following(by) {
49
55
  const items = this.items.filter((one) => !one.disabled);
50
- const at = items.findIndex((one) => one.value === this.resolveActive(items)?.value);
51
- const next = items[at + by];
52
- if (next) {
53
- this.requestActive(next.value);
56
+ const values = items.map((one) => one.value);
57
+ const current = this.resolveActive(items)?.value;
58
+ this.dealt = this.shuffle ? playOrder(values, true, this.dealt, current) : undefined;
59
+ const order = this.dealt ?? values;
60
+ return { current, next: stepTo(order, current, by, this.repeat === 'all') };
61
+ }
62
+ step(by) {
63
+ const { current, next } = this.following(by);
64
+ if (next === undefined) {
65
+ return;
66
+ }
67
+ if (next === current) {
68
+ this.replay();
69
+ return;
70
+ }
71
+ this.chased = true;
72
+ if (!this.requestActive(next)) {
73
+ this.chased = false;
54
74
  }
55
75
  }
76
+ replay() {
77
+ const player = this.player;
78
+ if (!player) {
79
+ return;
80
+ }
81
+ player.currentTime = 0;
82
+ void Promise.resolve(player.play()).catch(() => { });
83
+ }
84
+ next() {
85
+ this.step(1);
86
+ }
87
+ previous() {
88
+ this.step(-1);
89
+ }
56
90
  connectedCallback() {
57
91
  super.connectedCallback();
58
92
  this.addEventListener('helix-select', this.handleSelect);
@@ -67,6 +101,12 @@ let HelixHeadlessVideoPlaylist = class HelixHeadlessVideoPlaylist extends MixinC
67
101
  this.removeEventListener('helix-previous', this.handlePrevious);
68
102
  this.removeEventListener('ended', this.handleEnded, true);
69
103
  }
104
+ willUpdate(changed) {
105
+ super.willUpdate(changed);
106
+ if (changed.has('shuffle') && !this.shuffle) {
107
+ this.dealt = undefined;
108
+ }
109
+ }
70
110
  updated(changed) {
71
111
  super.updated?.(changed);
72
112
  const items = this.items;
@@ -83,9 +123,7 @@ let HelixHeadlessVideoPlaylist = class HelixHeadlessVideoPlaylist extends MixinC
83
123
  player.poster = current.poster;
84
124
  player.label = current.text;
85
125
  if (this.chased) {
86
- void player.updateComplete?.then(() => {
87
- player.play();
88
- });
126
+ void player.updateComplete?.then(() => player.play()).catch(() => { });
89
127
  }
90
128
  }
91
129
  this.chased = false;
@@ -100,6 +138,12 @@ let HelixHeadlessVideoPlaylist = class HelixHeadlessVideoPlaylist extends MixinC
100
138
  __decorate([
101
139
  property()
102
140
  ], HelixHeadlessVideoPlaylist.prototype, "label", void 0);
141
+ __decorate([
142
+ property()
143
+ ], HelixHeadlessVideoPlaylist.prototype, "repeat", void 0);
144
+ __decorate([
145
+ property({ type: Boolean })
146
+ ], HelixHeadlessVideoPlaylist.prototype, "shuffle", void 0);
103
147
  HelixHeadlessVideoPlaylist = __decorate([
104
148
  customElement('helix-headless-video-playlist')
105
149
  ], HelixHeadlessVideoPlaylist);
@@ -0,0 +1,4 @@
1
+ /** Takes the controls in `rows` out of the tab order. */
2
+ export declare const holdControls: (rows: Element[]) => void;
3
+ /** Puts the controls in `rows` back in the tab order, as they were written. */
4
+ export declare const releaseControls: (rows: Element[]) => void;
@@ -0,0 +1,30 @@
1
+ import { focusableElements } from '@fusion.dev/helix-core';
2
+ const held = new WeakMap();
3
+ export const holdControls = (rows) => {
4
+ for (const row of rows) {
5
+ if (!(row instanceof HTMLElement)) {
6
+ continue;
7
+ }
8
+ for (const control of focusableElements(row)) {
9
+ held.set(control, control.getAttribute('tabindex'));
10
+ control.setAttribute('tabindex', '-1');
11
+ }
12
+ }
13
+ };
14
+ export const releaseControls = (rows) => {
15
+ for (const row of rows) {
16
+ for (const control of row.querySelectorAll('[tabindex]')) {
17
+ if (!held.has(control)) {
18
+ continue;
19
+ }
20
+ const was = held.get(control);
21
+ held.delete(control);
22
+ if (was === null || was === undefined) {
23
+ control.removeAttribute('tabindex');
24
+ }
25
+ else {
26
+ control.setAttribute('tabindex', was);
27
+ }
28
+ }
29
+ }
30
+ };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Filling a row from an item, for a list whose rows come from a `<template>`.
3
+ *
4
+ * A row template marks its holes with data attributes, so it stays markup a
5
+ * server can send and a framework can write as a string:
6
+ *
7
+ * - `data-field="name"` takes the item's `name` as its text.
8
+ * - `data-field-href="url"` takes the item's `url` as its `href`, and the same
9
+ * for any attribute: `data-field-aria-label`, `data-field-src`,
10
+ * `data-field-title`.
11
+ *
12
+ * A field is a key, or a path of keys joined by dots (`author.name`).
13
+ */
14
+ /**
15
+ * The value at `path` in `item`, or `undefined` where the path leads nowhere.
16
+ *
17
+ * A dot separates keys, so `author.name` reads `item.author.name`. A key that
18
+ * holds a dot itself is read whole first, which keeps a flat record with a key
19
+ * such as `"v1.2"` working.
20
+ */
21
+ export declare const valueAt: (item: unknown, path: string) => unknown;
22
+ /**
23
+ * The text a value takes in a row.
24
+ *
25
+ * Nothing for a missing value, so a hole whose field is absent is empty and
26
+ * does not read "undefined".
27
+ */
28
+ export declare const textOf: (value: unknown) => string;
29
+ /** Fills one element's holes, its own and those below it, from an item. */
30
+ export declare const fillHoles: (root: Element, item: unknown) => void;
@@ -0,0 +1,39 @@
1
+ export const valueAt = (item, path) => {
2
+ if (item === null || typeof item !== 'object') {
3
+ return undefined;
4
+ }
5
+ const record = item;
6
+ if (path in record) {
7
+ return record[path];
8
+ }
9
+ let value = record;
10
+ for (const key of path.split('.')) {
11
+ if (value === null || typeof value !== 'object') {
12
+ return undefined;
13
+ }
14
+ value = value[key];
15
+ }
16
+ return value;
17
+ };
18
+ export const textOf = (value) => value === undefined || value === null || typeof value === 'object' ? '' : String(value);
19
+ const ATTRIBUTE = 'data-field-';
20
+ const writeAttribute = (element, name, value) => {
21
+ if (value === undefined || value === null || value === false) {
22
+ element.removeAttribute(name);
23
+ return;
24
+ }
25
+ element.setAttribute(name, value === true ? '' : textOf(value));
26
+ };
27
+ export const fillHoles = (root, item) => {
28
+ const holes = [...(root.matches('[data-field]') ? [root] : []), ...root.querySelectorAll('*')];
29
+ for (const hole of holes) {
30
+ for (const { name, value } of [...hole.attributes]) {
31
+ if (name === 'data-field') {
32
+ hole.textContent = textOf(valueAt(item, value));
33
+ }
34
+ else if (name.startsWith(ATTRIBUTE)) {
35
+ writeAttribute(hole, name.slice(ATTRIBUTE.length), valueAt(item, value));
36
+ }
37
+ }
38
+ }
39
+ };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Which item a key moves to in a vertical list of `count` items, from `at`.
3
+ *
4
+ * Index arithmetic, because most of the items are not drawn: the list moves to
5
+ * an index, scrolls it into view, and focuses the row once it is there. The
6
+ * keys are the ones a listbox answers:
7
+ *
8
+ * - Up and Down move one item.
9
+ * - Home and End move to the first and the last.
10
+ * - Page Up and Page Down move by `page`, the number of items on screen less
11
+ * one, so the item at one edge of the box lands at the other.
12
+ *
13
+ * `undefined` for a key the list does not take, and for an empty list.
14
+ */
15
+ export declare const stepIndex: (key: string, at: number, count: number, page: number) => number | undefined;
@@ -0,0 +1,24 @@
1
+ export const stepIndex = (key, at, count, page) => {
2
+ if (count <= 0) {
3
+ return undefined;
4
+ }
5
+ const last = count - 1;
6
+ const from = Math.max(0, Math.min(at, last));
7
+ const jump = Math.max(1, page);
8
+ switch (key) {
9
+ case 'ArrowDown':
10
+ return Math.min(last, from + 1);
11
+ case 'ArrowUp':
12
+ return Math.max(0, from - 1);
13
+ case 'End':
14
+ return last;
15
+ case 'Home':
16
+ return 0;
17
+ case 'PageDown':
18
+ return Math.min(last, from + jump);
19
+ case 'PageUp':
20
+ return Math.max(0, from - jump);
21
+ default:
22
+ return undefined;
23
+ }
24
+ };
@@ -1,30 +1,70 @@
1
1
  import { AriaForwardController, HelixElement } from '@fusion.dev/helix-core';
2
2
  import { type PropertyValues, type TemplateResult } from 'lit';
3
+ /**
4
+ * Draws one item into its row, for a list given `renderItem`.
5
+ *
6
+ * Returns anything Lit renders: a template from `html`, a string, or a DOM
7
+ * node the function built.
8
+ */
9
+ export type VirtualListRenderer = (item: unknown, index: number) => unknown;
10
+ /** Where `scrollToIndex` puts an item, and how it gets there. */
11
+ export interface VirtualListScrollOptions {
12
+ /**
13
+ * Where the item lands: the least distance that shows it whole (`nearest`,
14
+ * the default), or at the box's `start`, `center` or `end`.
15
+ */
16
+ align?: 'center' | 'end' | 'nearest' | 'start';
17
+ /** The platform's scroll behavior. Instant when left out. */
18
+ behavior?: ScrollBehavior;
19
+ }
20
+ /** The items on screen: `items.slice(first, last)`. */
21
+ export interface VirtualListRange {
22
+ /** The index of the first item on screen. */
23
+ first: number;
24
+ /** One past the index of the last item on screen. */
25
+ last: number;
26
+ }
3
27
  /**
4
28
  * @summary A list that renders only the rows of a long set that are on screen.
5
29
  *
6
30
  * @description
7
- * The items arrive as data:
31
+ * The items arrive as data, in one of two ways:
32
+ *
33
+ * - `items` takes an array. A framework sets it as a property, and React
34
+ * hydration keeps it.
35
+ * - `for` names a `<script type="application/json">` that holds an array, which
36
+ * a server sends as markup.
37
+ *
38
+ * Each item is drawn into a row in one of two ways:
8
39
  *
9
- * - `for` names a `<script type="application/json">` that holds an array of
10
- * objects.
11
- * - A `<template>` child defines one row. Elements with `data-field="name"` are
12
- * filled with that field of the item.
40
+ * - `renderItem` is a function from an item and its index to the row's
41
+ * content: a Lit template, a string, or a DOM node.
42
+ * - A `<template>` child defines one row. `data-field="name"` fills an element
43
+ * with the item's `name` as text, and `data-field-href="url"` writes the
44
+ * item's `url` into `href`, the same for any attribute. A field may be a path
45
+ * of keys, such as `author.name`.
13
46
  *
14
- * Both are markup. React hydration skips every property it would have to set
15
- * imperatively, so an `items` property would be lost, and light-DOM children
16
- * would all exist before anything could window them. The JSON text is one node
17
- * whatever the row count.
47
+ * Rows are your markup in the light DOM, so your stylesheet styles them.
18
48
  *
19
- * For assistive technology the list exposes the rendered window with
20
- * `aria-setsize`. Charts that use the same `for="<id>"` shape render an
21
- * accessible `<table>`, and ten thousand rows in one is the cost this component
22
- * avoids.
49
+ * **Keyboard.** The list is one tab stop. Up and Down move between items, Home
50
+ * and End go to the first and the last, and Page Up and Page Down move by a
51
+ * screen. An item that is not drawn is scrolled into view and focused, so a
52
+ * keyboard reaches the whole set. Tab moves on into the focused row's own links
53
+ * and buttons. When the row holding the tab stop scrolls away, the stop moves
54
+ * to the first row on screen.
23
55
  *
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.
56
+ * Each row says its place in the whole set with `aria-posinset` and
57
+ * `aria-setsize`, so a screen reader says "4,000 of 10,000". The row holding
58
+ * focus stays drawn when it scrolls out.
59
+ *
60
+ * **Loading more.** `helix-range-change` fires as the items on screen change,
61
+ * and `range` holds them. `helix-load-more` fires once a reader is within one
62
+ * screen of the end, once for each length of the set: add to `items` and it can
63
+ * fire again. `scrollToIndex` takes a reader to an item.
64
+ *
65
+ * The list follows its source. A new `items` array, an edit to the script's
66
+ * text, or a script replaced under the same id draws the new rows. A row that
67
+ * stays in view keeps its element as the list scrolls.
28
68
  *
29
69
  * The viewport scrolls on this tier. It carries `data-scroller`, and `core`
30
70
  * ships `[data-scroller] { overflow: auto }` into every shadow root, so
@@ -37,6 +77,9 @@ import { type PropertyValues, type TemplateResult } from 'lit';
37
77
  *
38
78
  * @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
79
  *
80
+ * @event {HelixLoadMoreEvent} helix-load-more - Fired when a reader comes within one screen of the last item, once for each length of the set.
81
+ * @event {HelixRangeChangeEvent} helix-range-change - Fired when the items on screen change, with the new `first` and `last`, which `range` also holds.
82
+ *
40
83
  * @csspart after - The space that stands in for the rows below the rendered ones.
41
84
  * @csspart before - The space that stands in for the rows above the rendered ones.
42
85
  * @csspart viewport - The scrolling box. On this tier, it needs only a block size.
@@ -50,9 +93,18 @@ export declare class HelixHeadlessVirtualList extends HelixElement {
50
93
  */
51
94
  estimate: number;
52
95
  /**
53
- * The id of the script element that holds the rows as JSON.
96
+ * The id of the script element that holds the items as JSON. `items` wins
97
+ * where both are set.
54
98
  */
55
99
  for: string;
100
+ /**
101
+ * The items, as an array. Set it as a property.
102
+ *
103
+ * Each item is drawn by `renderItem`, or into the row `<template>` by its
104
+ * fields. A new array draws the new rows, so replace the array to change it.
105
+ * Left unset, the list reads the script that `for` names.
106
+ */
107
+ items: readonly unknown[] | undefined;
56
108
  /**
57
109
  * The list's name, read by screen readers, because only some of its rows
58
110
  * are rendered at a time.
@@ -73,17 +125,41 @@ export declare class HelixHeadlessVirtualList extends HelixElement {
73
125
  */
74
126
  measured: boolean;
75
127
  /**
76
- * The parsed source.
128
+ * Draws one item into its row, given the item and its index. Set it as a
129
+ * property.
130
+ *
131
+ * It returns what Lit renders: a template from `html`, a string, or a DOM
132
+ * node. The list wraps each result in a `<div>` that carries the row's role
133
+ * and place in the set. Where it is unset, the row `<template>` is used.
134
+ */
135
+ renderItem: VirtualListRenderer | undefined;
136
+ /**
137
+ * The parsed script.
77
138
  *
78
139
  * Empty on a server, which has nothing to read.
79
140
  */
80
141
  private rows;
81
142
  /** The `for` that `rows` came from, so it is read again only when that changes. */
82
143
  private readFor;
83
- /** The rows the drawn elements were filled from, so new data fills them again. */
144
+ /** The items the drawn rows were filled from, so new data fills them again. */
84
145
  private filledFrom;
146
+ /** The function the drawn rows were filled by, so a new one draws them again. */
147
+ private filledBy;
85
148
  /** The script the rows were read from, which the watcher follows. */
86
149
  private source;
150
+ /**
151
+ * The item whose row holds the tab stop.
152
+ *
153
+ * A plain field, because the pass that places the rows is the one that keeps
154
+ * it on a drawn row and writes the `tabindex` that follows from it.
155
+ */
156
+ private active;
157
+ /** The item whose row's own controls are in the tab order. */
158
+ private released;
159
+ /** The range last reported, so `helix-range-change` fires only on a change. */
160
+ private said;
161
+ /** The length of the set when `helix-load-more` last fired. */
162
+ private askedAt;
87
163
  /**
88
164
  * Watches the source and the tree around it.
89
165
  *
@@ -96,6 +172,23 @@ export declare class HelixHeadlessVirtualList extends HelixElement {
96
172
  /** Carries a host `aria-label` and `aria-labelledby` to the element with the `list` role. */
97
173
  protected readonly aria: AriaForwardController;
98
174
  private readonly virtualize;
175
+ /**
176
+ * The items on screen, as `items.slice(first, last)` reads them.
177
+ *
178
+ * Read from the scroll offset when asked. Both are 0 before the list has a
179
+ * box to measure.
180
+ */
181
+ get range(): VirtualListRange;
182
+ /**
183
+ * Scrolls the list to show the item at `index`.
184
+ *
185
+ * By default the least distance that shows it whole. `align` puts it at the
186
+ * `start`, `center` or `end` of the box, and `behavior: 'smooth'` animates the
187
+ * scroll. An index past either end goes to the first or the last item.
188
+ */
189
+ scrollToIndex(index: number, options?: VirtualListScrollOptions): void;
190
+ /** The items in force: `items` where it is set, and the script otherwise. */
191
+ private get data();
99
192
  /**
100
193
  * The rows in the tree, each with the item it drew.
101
194
  *
@@ -133,6 +226,35 @@ export declare class HelixHeadlessVirtualList extends HelixElement {
133
226
  protected firstUpdated(changed: PropertyValues): void;
134
227
  protected updated(changed: PropertyValues): void;
135
228
  private read;
229
+ /**
230
+ * Fires `helix-range-change` when the items on screen moved, and
231
+ * `helix-load-more` when a reader is within one screen of the end.
232
+ *
233
+ * After a render and on every scroll, which are the two times either can
234
+ * change. `helix-load-more` waits for a box with a height, so a list that
235
+ * has not been laid out yet asks for nothing.
236
+ */
237
+ private report;
238
+ /** The item a row element drew, for an element that is one of the rows. */
239
+ private indexOf;
240
+ /** The row that holds the tab stop for item `index`, where it is drawn. */
241
+ private rowFor;
242
+ /**
243
+ * Moves between items with the keys `stepIndex` names.
244
+ *
245
+ * Only from a row itself. A key pressed in a row's own link, button or field
246
+ * is that control's, so a field in a row keeps its arrows.
247
+ */
248
+ private readonly handleKeydown;
249
+ /**
250
+ * Focuses the row for item `index`, scrolling it into view and drawing it
251
+ * first when it is not drawn.
252
+ */
253
+ private focusIndex;
254
+ /** The row kept for focus is let go once focus has left it. */
255
+ private readonly handleFocusOut;
256
+ /** A row that takes focus, by a press or by Tab, takes the tab stop with it. */
257
+ private readonly handleFocusIn;
136
258
  /**
137
259
  * The item whose row holds focus, where one does.
138
260
  *
@@ -140,10 +262,16 @@ export declare class HelixHeadlessVirtualList extends HelixElement {
140
262
  * own shadow root to the row's element.
141
263
  */
142
264
  private focusedIndex;
143
- /** The item drawn outside the window to keep focus, if there is one. */
265
+ /** The item a key is moving focus to, drawn until it has it. */
266
+ private focusing;
267
+ /** The item drawn outside the window to keep focus, or to take it, if there is one. */
144
268
  private keptIndex;
145
- /** Fills a row's holes and its place in the set from an item. */
269
+ /** Writes a row's role and its place in the set. */
270
+ private stamp;
271
+ /** Fills a drawn row from its item, by `renderItem` or by the template's holes. */
146
272
  private fill;
273
+ /** New elements for item `at`: a `<div>` for `renderItem`, or the template's clone. */
274
+ private draw;
147
275
  /**
148
276
  * Puts the windowed rows into the light DOM, and takes the rest out.
149
277
  *
@@ -151,10 +279,14 @@ export declare class HelixHeadlessVirtualList extends HelixElement {
151
279
  * cloning every row on every move replaced the node a reader had focused,
152
280
  * so a link in a row lost focus to the body on any scroll, even with its row
153
281
  * 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.
282
+ * arriving are drawn in their place, and the ones staying are not moved.
155
283
  *
156
284
  * **The row holding focus is drawn outside the window too**, beside it on the
157
285
  * side it belongs, and the spacer on that side gives up its room.
286
+ *
287
+ * **One row holds the tab stop**, the one the reader last focused while it is
288
+ * drawn, and the first row on screen once it is not. Its own links and
289
+ * buttons are in the tab order, and those of every other row wait.
158
290
  */
159
291
  private place;
160
292
  protected render(): TemplateResult;