sygnal 5.4.0 → 6.0.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 (316) hide show
  1. package/CHANGELOG.md +646 -0
  2. package/README.md +24 -9
  3. package/dist/astro/client.cjs.js +16 -9
  4. package/dist/astro/client.cjs.js.map +1 -1
  5. package/dist/astro/client.mjs +16 -9
  6. package/dist/astro/client.mjs.map +1 -1
  7. package/dist/astro/index.cjs.js +362 -91
  8. package/dist/astro/index.cjs.js.map +1 -1
  9. package/dist/astro/index.mjs +362 -92
  10. package/dist/astro/index.mjs.map +1 -1
  11. package/dist/astro/server.cjs.js +3396 -165
  12. package/dist/astro/server.cjs.js.map +1 -1
  13. package/dist/astro/server.mjs +3396 -165
  14. package/dist/astro/server.mjs.map +1 -1
  15. package/dist/devtools.cjs.js +1431 -0
  16. package/dist/devtools.cjs.js.map +1 -0
  17. package/dist/devtools.esm.js +1419 -0
  18. package/dist/devtools.esm.js.map +1 -0
  19. package/dist/diagnostics.cjs.js +3032 -296
  20. package/dist/diagnostics.cjs.js.map +1 -1
  21. package/dist/diagnostics.esm.js +3032 -296
  22. package/dist/diagnostics.esm.js.map +1 -1
  23. package/dist/element.cjs.js +230 -0
  24. package/dist/element.cjs.js.map +1 -0
  25. package/dist/element.esm.js +228 -0
  26. package/dist/element.esm.js.map +1 -0
  27. package/dist/guide/accessibility.md +257 -0
  28. package/dist/guide/adapters.md +221 -0
  29. package/dist/guide/behaviors.md +312 -0
  30. package/dist/guide/browser-sources.md +203 -0
  31. package/dist/guide/drag-and-drop.md +342 -0
  32. package/dist/guide/element-commands.md +306 -0
  33. package/dist/guide/error-boundaries.md +169 -0
  34. package/dist/guide/forms-reference.md +381 -0
  35. package/dist/guide/forms.md +250 -0
  36. package/dist/guide/http.md +299 -0
  37. package/dist/guide/inputs.md +173 -0
  38. package/dist/guide/persistence.md +245 -0
  39. package/dist/guide/recipes/carousel.md +155 -0
  40. package/dist/guide/recipes/charts.md +177 -0
  41. package/dist/guide/recipes/code-editor.md +135 -0
  42. package/dist/guide/recipes/data-grid.md +155 -0
  43. package/dist/guide/recipes/data-table.md +196 -0
  44. package/dist/guide/recipes/i18n.md +222 -0
  45. package/dist/guide/recipes/icons.md +115 -0
  46. package/dist/guide/recipes/overview.md +33 -0
  47. package/dist/guide/recipes/rich-text.md +154 -0
  48. package/dist/guide/resources.md +471 -0
  49. package/dist/guide/ssr.md +255 -0
  50. package/dist/guide/timers.md +178 -0
  51. package/dist/guide/ui/accordion.md +93 -0
  52. package/dist/guide/ui/combobox.md +144 -0
  53. package/dist/guide/ui/dialog.md +124 -0
  54. package/dist/guide/ui/disclosure.md +60 -0
  55. package/dist/guide/ui/menu.md +125 -0
  56. package/dist/guide/ui/overview.md +166 -0
  57. package/dist/guide/ui/popover.md +101 -0
  58. package/dist/guide/ui/select.md +114 -0
  59. package/dist/guide/ui/tabs.md +161 -0
  60. package/dist/guide/ui/toaster.md +152 -0
  61. package/dist/guide/ui/tooltip.md +103 -0
  62. package/dist/guide/undo.md +178 -0
  63. package/dist/guide/virtual-collections.md +183 -0
  64. package/dist/guide/web-components.md +179 -0
  65. package/dist/guide/widgets.md +217 -0
  66. package/dist/index.cjs.js +13178 -5769
  67. package/dist/index.cjs.js.map +1 -1
  68. package/dist/index.d.ts +2908 -276
  69. package/dist/index.esm.js +13149 -5779
  70. package/dist/index.esm.js.map +1 -1
  71. package/dist/jsx-dev-runtime.cjs.js +26 -334
  72. package/dist/jsx-dev-runtime.cjs.js.map +1 -1
  73. package/dist/jsx-dev-runtime.esm.js +19 -327
  74. package/dist/jsx-dev-runtime.esm.js.map +1 -1
  75. package/dist/jsx-runtime.cjs.js +26 -334
  76. package/dist/jsx-runtime.cjs.js.map +1 -1
  77. package/dist/jsx-runtime.esm.js +19 -327
  78. package/dist/jsx-runtime.esm.js.map +1 -1
  79. package/dist/jsx.cjs.js +10 -314
  80. package/dist/jsx.cjs.js.map +1 -1
  81. package/dist/jsx.esm.js +8 -315
  82. package/dist/jsx.esm.js.map +1 -1
  83. package/dist/react.cjs.js +117 -0
  84. package/dist/react.cjs.js.map +1 -0
  85. package/dist/react.esm.js +115 -0
  86. package/dist/react.esm.js.map +1 -0
  87. package/dist/shims/globalthis.cjs +20 -0
  88. package/dist/sygnal.min.js +1 -1
  89. package/dist/sygnal.min.js.map +1 -1
  90. package/dist/ui-combobox.cjs.js +169 -0
  91. package/dist/ui-combobox.cjs.js.map +1 -0
  92. package/dist/ui-combobox.esm.js +148 -0
  93. package/dist/ui-combobox.esm.js.map +1 -0
  94. package/dist/ui-menu.cjs.js +87 -0
  95. package/dist/ui-menu.cjs.js.map +1 -0
  96. package/dist/ui-menu.esm.js +66 -0
  97. package/dist/ui-menu.esm.js.map +1 -0
  98. package/dist/ui-select.cjs.js +104 -0
  99. package/dist/ui-select.cjs.js.map +1 -0
  100. package/dist/ui-select.esm.js +83 -0
  101. package/dist/ui-select.esm.js.map +1 -0
  102. package/dist/ui.cjs.js +738 -0
  103. package/dist/ui.cjs.js.map +1 -0
  104. package/dist/ui.esm.js +727 -0
  105. package/dist/ui.esm.js.map +1 -0
  106. package/dist/vike/ClientOnly.cjs.js +4 -14
  107. package/dist/vike/ClientOnly.cjs.js.map +1 -1
  108. package/dist/vike/ClientOnly.mjs +4 -14
  109. package/dist/vike/ClientOnly.mjs.map +1 -1
  110. package/dist/vike/{+config.js → config/+config.js} +9 -1
  111. package/dist/vike/config/+config.js.map +1 -0
  112. package/dist/vike/config/package.json +6 -0
  113. package/dist/vike/onRenderClient.cjs.js +165 -95
  114. package/dist/vike/onRenderClient.cjs.js.map +1 -1
  115. package/dist/vike/onRenderClient.mjs +165 -95
  116. package/dist/vike/onRenderClient.mjs.map +1 -1
  117. package/dist/vike/onRenderHtml.cjs.js +59 -24
  118. package/dist/vike/onRenderHtml.cjs.js.map +1 -1
  119. package/dist/vike/onRenderHtml.mjs +60 -25
  120. package/dist/vike/onRenderHtml.mjs.map +1 -1
  121. package/dist/vite/plugin.cjs.js +323 -88
  122. package/dist/vite/plugin.cjs.js.map +1 -1
  123. package/dist/vite/plugin.mjs +323 -89
  124. package/dist/vite/plugin.mjs.map +1 -1
  125. package/dist/zag.cjs.js +408 -0
  126. package/dist/zag.cjs.js.map +1 -0
  127. package/dist/zag.esm.js +405 -0
  128. package/dist/zag.esm.js.map +1 -0
  129. package/llms.txt +166 -103
  130. package/package.json +98 -14
  131. package/src/astro/client.ts +27 -16
  132. package/src/astro/index.d.ts +24 -0
  133. package/src/astro/index.ts +69 -3
  134. package/src/astro/server.ts +8 -2
  135. package/src/collection.ts +6 -93
  136. package/src/core/actions.ts +134 -0
  137. package/src/core/cell.ts +202 -0
  138. package/src/core/debug.ts +23 -0
  139. package/src/core/define.ts +242 -0
  140. package/src/core/hooks.ts +314 -0
  141. package/src/core/hosts/collection.ts +325 -0
  142. package/src/core/hosts/switchable.ts +146 -0
  143. package/src/core/instance.ts +592 -0
  144. package/src/core/markers/clientonly.ts +13 -0
  145. package/src/core/markers/lazy.ts +40 -0
  146. package/src/core/markers/portal.ts +96 -0
  147. package/src/core/markers/suspense.ts +41 -0
  148. package/src/core/markers/transition.ts +70 -0
  149. package/src/core/registry.ts +25 -0
  150. package/src/core/runtime.ts +573 -0
  151. package/src/core/statics.ts +104 -0
  152. package/src/core/teardown.ts +60 -0
  153. package/src/core/view.ts +37 -0
  154. package/src/cycle/dom/DocumentDOMSource.ts +13 -8
  155. package/src/cycle/dom/ElementFinder.ts +7 -9
  156. package/src/cycle/dom/EventDelegator.ts +174 -223
  157. package/src/cycle/dom/IsolateModule.ts +65 -35
  158. package/src/cycle/dom/MainDOMSource.ts +9 -2
  159. package/src/cycle/dom/PriorityQueue.ts +4 -2
  160. package/src/cycle/dom/SymbolTree.ts +18 -47
  161. package/src/cycle/dom/classNameModule.ts +9 -7
  162. package/src/cycle/dom/controlledInputModule.ts +23 -6
  163. package/src/cycle/dom/enrichEventStream.ts +25 -47
  164. package/src/cycle/dom/fragment.ts +7 -0
  165. package/src/cycle/dom/isolate.ts +30 -27
  166. package/src/cycle/dom/makeDOMDriver.ts +115 -56
  167. package/src/cycle/dom/mockDOMSource.ts +21 -1
  168. package/src/cycle/dom/modules.ts +16 -3
  169. package/src/cycle/dom/propsModule.ts +65 -0
  170. package/src/cycle/dom/selectModule.ts +22 -17
  171. package/src/cycle/dom/snabbdom.ts +1 -6
  172. package/src/cycle/dom/thunk.ts +3 -0
  173. package/src/cycle/dom/utils.ts +98 -0
  174. package/src/cycle/dom/viewTransition.ts +43 -0
  175. package/src/cycle/state/StateSource.ts +19 -3
  176. package/src/cycle/state/objIsEqual.ts +50 -0
  177. package/src/cycle/state/types.ts +0 -10
  178. package/src/defineComponent.ts +34 -0
  179. package/src/devtools.d.ts +151 -0
  180. package/src/devtools.ts +34 -0
  181. package/src/element.d.ts +59 -0
  182. package/src/element.ts +235 -0
  183. package/src/extra/backoff.ts +9 -0
  184. package/src/extra/behaviors.ts +169 -0
  185. package/src/extra/browserSignals.ts +23 -0
  186. package/src/extra/browserSources.ts +326 -0
  187. package/src/extra/command.ts +3 -3
  188. package/src/extra/controls.ts +47 -0
  189. package/src/extra/copyAsTest.ts +286 -0
  190. package/src/extra/devtools.ts +101 -31
  191. package/src/extra/devtoolsActions.ts +379 -0
  192. package/src/extra/devtoolsHook.ts +9 -0
  193. package/src/extra/devtoolsNext.ts +83 -0
  194. package/src/extra/diagnostics/checks/actionLog.ts +108 -0
  195. package/src/extra/diagnostics/checks/behaviors.ts +49 -0
  196. package/src/extra/diagnostics/checks/browserSources.ts +96 -0
  197. package/src/extra/diagnostics/checks/collections.ts +1 -1
  198. package/src/extra/diagnostics/checks/controls.ts +148 -0
  199. package/src/extra/diagnostics/checks/dataset.ts +57 -0
  200. package/src/extra/diagnostics/checks/dom.ts +20 -11
  201. package/src/extra/diagnostics/checks/elementCommands.ts +175 -0
  202. package/src/extra/diagnostics/checks/events.ts +17 -2
  203. package/src/extra/diagnostics/checks/fetch.ts +135 -0
  204. package/src/extra/diagnostics/checks/forms.ts +163 -0
  205. package/src/extra/diagnostics/checks/index.ts +97 -4
  206. package/src/extra/diagnostics/checks/inspect.ts +140 -32
  207. package/src/extra/diagnostics/checks/next.ts +417 -0
  208. package/src/extra/diagnostics/checks/persist.ts +54 -0
  209. package/src/extra/diagnostics/checks/props.ts +10 -5
  210. package/src/extra/diagnostics/checks/public.d.ts +98 -7
  211. package/src/extra/diagnostics/checks/replies.ts +128 -0
  212. package/src/extra/diagnostics/checks/router.ts +100 -0
  213. package/src/extra/diagnostics/checks/rxjsHints.ts +1 -1
  214. package/src/extra/diagnostics/checks/shared.ts +86 -2
  215. package/src/extra/diagnostics/checks/shorthand.ts +115 -0
  216. package/src/extra/diagnostics/checks/sortable.ts +129 -0
  217. package/src/extra/diagnostics/checks/state.ts +100 -7
  218. package/src/extra/diagnostics/checks/statics.ts +48 -0
  219. package/src/extra/diagnostics/checks/strict.ts +10 -67
  220. package/src/extra/diagnostics/checks/timers.ts +80 -0
  221. package/src/extra/diagnostics/checks/viewTransitions.ts +47 -0
  222. package/src/extra/diagnostics/checks/virtual.ts +73 -0
  223. package/src/extra/diagnostics/checks/widgets.ts +109 -0
  224. package/src/extra/diagnostics/checks/wiring.ts +66 -10
  225. package/src/extra/diagnostics/codes.ts +268 -23
  226. package/src/extra/diagnostics/index.ts +34 -9
  227. package/src/extra/diagnostics/legacy.ts +17 -0
  228. package/src/extra/driverFactories.ts +83 -19
  229. package/src/extra/elementCommands.ts +53 -0
  230. package/src/extra/fetchDriver.ts +519 -0
  231. package/src/extra/focusWithin.ts +27 -0
  232. package/src/extra/form.ts +268 -0
  233. package/src/extra/formHelpers.ts +139 -0
  234. package/src/extra/head.ts +106 -0
  235. package/src/extra/hmr.ts +2 -3
  236. package/src/extra/owned.ts +24 -0
  237. package/src/extra/pager.ts +50 -0
  238. package/src/extra/persist.ts +133 -0
  239. package/src/extra/pwa.ts +1 -1
  240. package/src/extra/queryCache.ts +107 -0
  241. package/src/extra/reducers.ts +1 -1
  242. package/src/extra/reduxDevtools.ts +92 -0
  243. package/src/extra/replies.ts +54 -0
  244. package/src/extra/router.ts +323 -0
  245. package/src/extra/run.ts +90 -217
  246. package/src/extra/selection.ts +76 -0
  247. package/src/extra/socketDriver.ts +265 -0
  248. package/src/extra/sortable.ts +377 -0
  249. package/src/extra/ssr.ts +427 -160
  250. package/src/extra/standardSchema.ts +27 -0
  251. package/src/extra/testing.ts +2433 -175
  252. package/src/extra/timers.ts +95 -0
  253. package/src/extra/undo.ts +261 -0
  254. package/src/extra/viewTransitions.ts +38 -0
  255. package/src/extra/virtual.ts +513 -0
  256. package/src/extra/widget.ts +201 -0
  257. package/src/index.d.ts +2631 -117
  258. package/src/index.ts +25 -4
  259. package/src/jsx-runtime.ts +15 -1
  260. package/src/jsx.ts +2 -1
  261. package/src/lazy.ts +50 -7
  262. package/src/portal.ts +3 -1
  263. package/src/pragma/index.ts +256 -133
  264. package/src/react-peers.d.ts +5 -0
  265. package/src/react.d.ts +43 -0
  266. package/src/react.ts +110 -0
  267. package/src/shared.ts +48 -0
  268. package/src/slot.ts +1 -1
  269. package/src/suspense.ts +3 -1
  270. package/src/switchable.ts +6 -121
  271. package/src/transition.ts +3 -1
  272. package/src/ui/accordion.ts +64 -0
  273. package/src/ui/dialog.ts +129 -0
  274. package/src/ui/disclosure.ts +35 -0
  275. package/src/ui/popover.ts +45 -0
  276. package/src/ui/shared.ts +94 -0
  277. package/src/ui/tabs.ts +82 -0
  278. package/src/ui/toaster.ts +198 -0
  279. package/src/ui/tooltip.ts +70 -0
  280. package/src/ui/zag/combobox.ts +115 -0
  281. package/src/ui/zag/menu.ts +40 -0
  282. package/src/ui/zag/select.ts +54 -0
  283. package/src/ui/zag/shared.ts +43 -0
  284. package/src/ui-combobox.d.ts +20 -0
  285. package/src/ui-combobox.ts +6 -0
  286. package/src/ui-menu.d.ts +30 -0
  287. package/src/ui-menu.ts +6 -0
  288. package/src/ui-select.d.ts +17 -0
  289. package/src/ui-select.ts +6 -0
  290. package/src/ui-zag-types.d.ts +42 -0
  291. package/src/ui.d.ts +208 -0
  292. package/src/ui.ts +15 -0
  293. package/src/vike/+config.ts +9 -1
  294. package/src/vike/ClientOnly.ts +1 -1
  295. package/src/vike/onRenderClient.ts +144 -96
  296. package/src/vike/onRenderHtml.ts +69 -26
  297. package/src/vike/types.ts +7 -1
  298. package/src/vite/globalthis-shim.ts +18 -0
  299. package/src/vite/globalthis.ts +56 -0
  300. package/src/vite/plugin.d.ts +22 -3
  301. package/src/vite/plugin.ts +222 -24
  302. package/src/zag.d.ts +64 -0
  303. package/src/zag.ts +238 -0
  304. package/dist/vike/+config.cjs.js +0 -66
  305. package/dist/vike/+config.cjs.js.map +0 -1
  306. package/dist/vike/+config.js.map +0 -1
  307. package/src/component.ts +0 -2301
  308. package/src/cycle/isolate/index.ts +0 -196
  309. package/src/cycle/run/index.ts +0 -151
  310. package/src/cycle/run/internals.ts +0 -143
  311. package/src/cycle/state/Collection.ts +0 -184
  312. package/src/cycle/state/index.ts +0 -5
  313. package/src/cycle/state/pickCombine.ts +0 -201
  314. package/src/cycle/state/pickMerge.ts +0 -122
  315. package/src/cycle/state/withState.ts +0 -47
  316. package/src/pragma/fn.ts +0 -55
@@ -0,0 +1,513 @@
1
+ /**
2
+ * PLAN-5 V-1: `<VirtualCollection>`, a Collection that renders only the rows in view.
3
+ *
4
+ * <VirtualCollection of={Row} from="rows" className="rows" estimateSize={32} />
5
+ * // a fixed height in CSS: .rows { height: 400px }
6
+ * // jump: JUMP: { ELEMENT: (s, i) => ({ scrollToIndex: '.rows', index: i }) }
7
+ * // FIND: { ELEMENT: (s, id) => ({ scrollToId: '.rows', id, align: 'center' }) }
8
+ *
9
+ * The same `of` / `from` / `filter` / `sort` as Collection (it is the core's Collection host with
10
+ * a window: D169 keys, D177 duplicates, D178 a missing `from`, SYG401/411/412/418), and the other
11
+ * props go to every item. Measurement and the window come from `@tanstack/virtual-core`, a regular
12
+ * dependency of sygnal (D209; tree-shaken when unused).
13
+ *
14
+ * 0 B when unused: nothing runs on import. The tag is a function carrying the pragma's render
15
+ * hook (`__sygnalControl`, as `defineWidget` tags), so its first render registers the host
16
+ * (`hosts['virtual-collection']`) and returns the marker the core's reconcile hands to it.
17
+ *
18
+ * - The element is its own scroll container: a `div` (className, id, style, aria-label/-labelledby/
19
+ * -describedby, `role` (default `list`), tabIndex (default 0, so the keyboard scrolls it)) with
20
+ * `overflow-y: auto` and `overflow-anchor: none` (the virtualizer corrects the offset itself).
21
+ * Inside: a spacer as tall as every row (measured or estimated), and a window moved down to the
22
+ * first rendered row; the rows are in normal flow inside it, so a row's height is its own.
23
+ * - Only the rows in view (+ `overscan`, default 5) have instances. A row scrolled out is disposed
24
+ * like a removed Collection item and made again when it comes back. Its state is its element
25
+ * of the array, so nothing in state is lost; what lives only in the DOM (an uncontrolled input's
26
+ * text, a running EFFECT) is. Keep row state in the array (the canonical Collection item).
27
+ * - G-401: rows are keyed (a row's element is its own). The row holding the focus stays rendered
28
+ * while scrolled out (pinned at its own offset, out of the window's flow) until the focus leaves
29
+ * the list, and no row element is moved in the DOM (a move blurs it in every engine): a window
30
+ * that loses rows at its front while its end changes is patched in two steps. G-424: the pin
31
+ * follows the row's key through a reorder (filter, sort, a new array), and a row element a
32
+ * reorder moves gets the focus back (when it went nowhere; preventScroll; G-462: the element
33
+ * inside a shadow root in the row when the focus is there).
34
+ * - Row heights: `estimateSize` (a number, or `(item, index) => number`, default 32) until a row is
35
+ * rendered; then its measured height (ResizeObserver follows later changes, applied in the next
36
+ * frame: G-402). A 0 measurement (no layout: jsdom) keeps the estimate. A new function at each
37
+ * render is the same estimate (G-394); a number that changes, or a switch between a number and a
38
+ * function, re-measures.
39
+ * - Each row's root element gets `data-index`, `aria-setsize` and `aria-posinset` (1-based, over the
40
+ * filtered list), and `role="listitem"` when the container's role is `list` and the row's root has
41
+ * no role of its own. A row must render one element (SYG432 for a fragment or text).
42
+ * - `scrollToIndex` / `scrollToId` are element commands (methods of the container element):
43
+ * `{ scrollToIndex: '.rows', index, align?, behavior? }`, `{ scrollToId: '.rows', id, align?,
44
+ * behavior? }` (align: 'auto' (default: only when not in view) | 'start' | 'center' | 'end').
45
+ * The row needs no element: the virtualizer scrolls to its offset and keeps correcting it while
46
+ * the rows around it are measured. An id not in the (filtered) list: SYG433 in dev, no scroll.
47
+ * - Without layout (SSR, the mock DOM, jsdom), the window is the first 10 rows' estimate + overscan.
48
+ * - The container must have a bounded height. When it has none (0 tall: SYG430) or grows with its
49
+ * rows (it would render every row: SYG430), the window is clamped to the viewport's height.
50
+ * Whether it grows is measured (G-395, G-427): a max-height that fits the rows bounds it; a
51
+ * percentage of an unbounded parent, calc() or fit-content that follows the rows doesn't.
52
+ * G-462: also under an ancestor's CSS zoom (G-525: not a transform), and the measurement
53
+ * doesn't move the page (scroll anchoring is off around it meanwhile).
54
+ * Items without `id`: SYG431 (index keys: rows and measured heights follow the position).
55
+ * - `viewTransitionName="row"` names each keyed row's root element as Collection does (`row-<id>`,
56
+ * class `row`; G-417), as its SSR markup does.
57
+ *
58
+ * Dev text lives in 'sygnal/diagnostics' (checks/virtual.ts), reached through the core bridge as
59
+ * `virtual(code, owner, extra)`; production reports nothing.
60
+ */
61
+ import {hosts} from '../core/registry'
62
+ import {CollectionHost, named} from '../core/hosts/collection'
63
+ import {Inst} from '../core/instance'
64
+ import {itemCell, keyName, keyOf} from '../core/cell'
65
+ import {chainHooks} from '../pragma/index'
66
+ import {Virtualizer, defaultRangeExtractor, elementScroll, observeElementOffset, observeElementRect} from '@tanstack/virtual-core'
67
+
68
+ const SEL = 'virtual-collection'
69
+ /** the container's props (not passed to the items) */
70
+ const OWN = /^(estimateSize|overscan|role|tabIndex|style|id|aria-(label|labelledby|describedby))$/
71
+ const NONE: any[] = []
72
+ /** rows in the window when there is no layout (SSR, mock DOM, jsdom) */
73
+ const ROWS0 = 10
74
+
75
+ const dev = (code: number, owner: any, x?: any): any => (globalThis as any).__SYGNAL_DIAGNOSTICS__?.virtual?.(code, owner, x)
76
+
77
+ const px = (n: number) => n + 'px'
78
+ /** the element with the focus in `el`'s root (its shadow root: G-426), if any */
79
+ const act = (el: any): any => el && (el.getRootNode?.() || el.ownerDocument)?.activeElement
80
+ /** G-462: the focused element itself when it is inside open shadow roots (`a` is their host) */
81
+ const deep = (a: any): any => {
82
+ while (a?.shadowRoot?.activeElement) a = a.shadowRoot.activeElement
83
+ return a
84
+ }
85
+ /** the props the container's vnode data is made from (not estimateSize / overscan: G-394) */
86
+ const BOX = (x: string) => x != 'estimateSize' && x != 'overscan'
87
+ const same = (a: any, b: any, d?: any): boolean => {
88
+ const k = Object.keys(a).filter(x => d || BOX(x))
89
+ return k.length == Object.keys(b).filter(x => d || BOX(x)).length && k.every(x => a[x] === b[x] || !d && x == 'style' && a[x] && b[x] && typeof a[x] == 'object' && same(a[x], b[x], 1))
90
+ }
91
+ /** an estimate's kind: a number (by value) or 'f' (any function: an inline one is new each render, G-394) */
92
+ const kind = (e: any) => (typeof e == 'function' ? 'f' : e)
93
+
94
+ export class VirtualHost extends CollectionHost {
95
+ // (no field initializers: the base constructor calls setProps() before they would run)
96
+ declare vp: Record<string, any>
97
+ /** every visible key in order (filter, sort applied); the virtualizer's items */
98
+ declare all: any[]
99
+ declare v: Virtualizer<any, any> | undefined
100
+ /** the scroll container, once on the page */
101
+ declare el: any
102
+ /** the last window: start offset, total size, first index, rows; the container's data */
103
+ declare lw: any
104
+ declare cd: any
105
+ declare gk: (i: number) => any
106
+ /** per item instance: [its vnode, index, set size, role, pinned top, the decorated vnode] */
107
+ declare dc: WeakMap<Inst, any[]>
108
+ /** the shown rows' indexes (aligned with `shown`) */
109
+ declare si: number[]
110
+ /** the focused row's key and index (G-401: kept in the range), and the index pinned outside the window */
111
+ declare fk: any
112
+ declare fi: number
113
+ declare pin: number
114
+ /** the range extractor (a new one when the focused row changes: the virtualizer memoizes the range on it) */
115
+ declare re: (r: any) => number[]
116
+ /** the stable estimateSize the virtualizer calls (it reads the current prop, G-394) */
117
+ declare sz: (i: number) => number
118
+ /** a first patch step that only removed rows ran (G-401) */
119
+ declare p1: boolean
120
+ /** the element in the list with the focus before a patch (G-424) */
121
+ declare fa: any
122
+ declare warned: number
123
+ declare un: (() => void) | undefined
124
+
125
+ setProps(props: Record<string, any>, children: any[], marker?: any, id?: string) {
126
+ const vp: Record<string, any> = {}, rest: Record<string, any> = {}
127
+ for (const k in props) (OWN.test(k) ? vp : rest)[k] = props[k]
128
+ rest.className = props.className
129
+ if (!this.dc) { this.dc = new WeakMap(); this.all = NONE; this.warned = 0; this.si = []; this.fi = this.pin = -1 }
130
+ const was = this.vp, cls = this.props?.className
131
+ this.vp = vp
132
+ super.setProps(rest, children, marker, id)
133
+ // G-394: the virtualizer reads the estimate through a stable function, so a new inline function
134
+ // keeps the measurements; a number that changes, or a switch between a number and a function,
135
+ // re-measures
136
+ const re = was && kind(vp.estimateSize) !== kind(was.estimateSize)
137
+ if (!was || re || vp.overscan !== was.overscan) this.opts(re)
138
+ // the container's data is made again when its props change (a style object one level deep)
139
+ if (!was || cls !== rest.className || !same(vp, was)) this.cd = undefined
140
+ }
141
+
142
+ /** the virtualizer, (re)configured from the props and the current list */
143
+ opts(remeasure?: any) {
144
+ const p = this.vp, est = p.estimateSize, o = p.overscan
145
+ if (est !== undefined && !(typeof est == 'function' || est > 0)) dev(434, this.owner, {prop: 'estimateSize', value: est})
146
+ if (o !== undefined && !(o >= 0)) dev(434, this.owner, {prop: 'overscan', value: o})
147
+ this.sz ||= (i: number) => {
148
+ const e = this.vp.estimateSize, s = typeof e == 'function' ? e(this.itemAt(i), i) : e
149
+ return s > 0 ? s : 32
150
+ }
151
+ this.re ||= (r: any) => this.range(r)
152
+ const opts: any = {
153
+ count: this.all.length,
154
+ getItemKey: this.gk ||= (i: number) => this.all[i],
155
+ getScrollElement: () => this.el,
156
+ estimateSize: this.sz,
157
+ rangeExtractor: this.re,
158
+ // G-402: ResizeObserver results are applied in the next frame (no ResizeObserver loop error)
159
+ useAnimationFrameWithResizeObserver: typeof requestAnimationFrame == 'function',
160
+ overscan: o >= 0 ? o : 5,
161
+ initialRect: {width: 0, height: (est > 0 ? est : 32) * ROWS0},
162
+ scrollToFn: elementScroll,
163
+ observeElementOffset,
164
+ observeElementRect: (v: any, cb: any) => observeElementRect(v, (r: any) => cb(this.bound(r))),
165
+ // the height unrounded (rows are in normal flow: rounded sizes would drift from where the
166
+ // browser puts them, by up to half a pixel a row); 0 (no layout: jsdom) is no measurement
167
+ measureElement: (e: any, entry: any, v: any) =>
168
+ entry?.borderBoxSize?.[0]?.blockSize || e.getBoundingClientRect().height || e.offsetHeight || v.options.estimateSize(v.indexFromElement(e)),
169
+ onChange: () => this.disposed || this.owner.app.commit(),
170
+ }
171
+ if (this.v) {
172
+ this.v.setOptions(opts)
173
+ if (remeasure) this.v.measure()
174
+ } else this.v = new Virtualizer(opts)
175
+ }
176
+
177
+ /**
178
+ * G-401: the default range, plus the focused row when it is outside it (its element must stay:
179
+ * removing it loses the focus). It is pinned at its own offset, out of the window's flow.
180
+ */
181
+ range(r: any): number[] {
182
+ const out = defaultRangeExtractor(r), f = this.fk === undefined ? -1 : this.fi
183
+ this.pin = -1
184
+ if (f >= 0 && f < r.count && out.length && (f < out[0] || f > out[out.length - 1])) {
185
+ this.pin = f
186
+ f < out[0] ? out.unshift(f) : out.push(f)
187
+ }
188
+ return out
189
+ }
190
+
191
+ /** the row element (container > spacer > window > row) holding `t`, if any */
192
+ rowOf(t: any): any {
193
+ const el = this.el
194
+ for (let n = t; n && n !== el; n = n.parentNode) if (n.parentNode?.parentNode?.parentNode === el && n.hasAttribute?.('data-index')) return n
195
+ }
196
+
197
+ /** the focused row's key changed: a new range (the virtualizer memoizes it on the extractor) */
198
+ focus(k: any) {
199
+ if (k === this.fk || this.disposed) return
200
+ this.fk = k
201
+ this.fi = k === undefined ? -1 : this.all.indexOf(k)
202
+ this.re = (r: any) => this.range(r)
203
+ this.opts()
204
+ this.owner.app.commit()
205
+ }
206
+
207
+ /** the raw array element shown at index i */
208
+ itemAt(i: number) {
209
+ const [a, m] = this.index!()
210
+ return a[m.get(this.all[i])!]
211
+ }
212
+
213
+ /**
214
+ * The rect the window is computed from. No height: SYG430 when the element is laid out (in jsdom
215
+ * nothing is), and the no-layout window. As tall as all the rows and taller than the viewport:
216
+ * the container grows with its rows (no bounded height), so the window is the viewport's height
217
+ */
218
+ bound(r: {width: number; height: number}) {
219
+ const el = this.el, v = this.v!, total = v.getTotalSize(), win = el?.ownerDocument?.defaultView
220
+ if (!r.height) {
221
+ if (el?.getClientRects?.().length) this.warn(1, {reason: 'height'})
222
+ return {width: r.width, height: v.options.initialRect!.height}
223
+ }
224
+ const vh = win?.innerHeight || 0
225
+ // G-462: (the rect is scaled by an ancestor's CSS zoom, clientHeight isn't, as the sizes)
226
+ if (vh && total > vh && Math.max(r.height, el.clientHeight || 0) >= total - 1 && this.grows(el)) {
227
+ this.warn(2, {reason: 'grows', height: r.height})
228
+ // G-502: the viewport's height in the rows' px under an ancestor's CSS zoom. G-525: the zoom
229
+ // only (currentCSSZoom), not a transform (scale(0.02) would make it 50 viewports, and no
230
+ // resize is observed when it goes); without currentCSSZoom the rect / offsetHeight ratio,
231
+ // capped (0.1 to 10)
232
+ const z = el.currentCSSZoom || Math.min(Math.max(el.getBoundingClientRect().height / el.offsetHeight || 1, 0.1), 10)
233
+ return {width: r.width, height: vh / z}
234
+ }
235
+ return r
236
+ }
237
+
238
+ /**
239
+ * G-395 / G-427: as tall as its rows and taller than the viewport is "grows" only when its
240
+ * height follows its content. Measured, not parsed (a max-height that is a percentage of an
241
+ * unbounded parent, calc(), fit-content, a flex or grid item): the spacer is made 1e6 px taller
242
+ * for one forced layout and put back before anything renders (no resize is observed); a
243
+ * container that grows with it is unbounded, one bounded by a max-height that fits its rows
244
+ * stops at it. G-462: with scroll anchoring off in the scrollers around it meanwhile (the
245
+ * content after the list moves by 1e6 px and back: the page would move with it, or in WebKit not
246
+ * quite back)
247
+ */
248
+ grows(el: any): boolean {
249
+ const s = el.firstElementChild?.style
250
+ if (!s) return true
251
+ const up: any[] = [], O = 'overflow-anchor'
252
+ // (G-498: a slotted element's scroller can be in the shadow tree: its slot first)
253
+ for (let n = el.assignedSlot || el.parentElement || el.getRootNode?.().host; n?.style; n = n.assignedSlot || n.parentElement || n.getRootNode?.().host) {
254
+ up.push(n, n.style.getPropertyValue(O), n.style.getPropertyPriority(O), n.hasAttribute('style'))
255
+ n.style.setProperty(O, 'none')
256
+ }
257
+ const h = s.height, a = el.offsetHeight
258
+ s.height = (parseFloat(h) || 0) + 1e6 + 'px'
259
+ const b = el.offsetHeight
260
+ s.height = h
261
+ // (laid out again before anchoring is back)
262
+ void el.offsetHeight
263
+ // G-498: put back through the CSSOM (setting the style attribute is blocked under a CSP
264
+ // style-src without 'unsafe-inline'); no style attribute stays none (read first: Chromium and
265
+ // WebKit write the attribute lazily, and would add style="" after the removal)
266
+ for (let i = 0; i < up.length; i += 4) {
267
+ const n = up[i]
268
+ up[i + 1] ? n.style.setProperty(O, up[i + 1], up[i + 2]) : n.style.removeProperty(O)
269
+ if (!up[i + 3]) n.getAttribute('style'), n.removeAttribute('style')
270
+ }
271
+ return b - a > 5e5
272
+ }
273
+
274
+ warn(bit: number, x: any) {
275
+ if (this.warned & bit) return
276
+ this.warned |= bit
277
+ dev(430, this.owner, x)
278
+ }
279
+
280
+ /** the list of keys (as a Collection's: filter, sort, the first of a duplicate key); true when it changed */
281
+ keys(): boolean {
282
+ if (!this.index) return false
283
+ const [a, m] = this.index(), {filter, sort} = this.props
284
+ if (a === this.la && filter === this.lf && sort === this.ls) return false
285
+ // no filter, no sort and no duplicate key: the index's keys are the list, in order (100k rows:
286
+ // no second pass making keys)
287
+ let all: any[]
288
+ if (typeof filter != 'function' && !sort && m.size == a.length) { all = [...m.keys()]; this.ls = sort; this.cmp = undefined }
289
+ else all = this.list(a, m)
290
+ this.la = a; this.lf = filter
291
+ // SYG431 (dev only: a pass over the array)
292
+ if (a.length && !(this.warned & 4) && (globalThis as any).__SYGNAL_DIAGNOSTICS__?.virtual && a.some((x, i) => keyOf(x, i)[0] == '\0')) { this.warned |= 4; dev(431, this.owner, {count: a.length}) }
293
+ // G-407: the same keys in the same order (a row edited): the virtualizer keeps its key
294
+ // function, so it doesn't rebuild every row's measurement
295
+ const old = this.all
296
+ if (all.length == old.length && all.every((x, i) => x === old[i])) return false
297
+ this.all = all
298
+ if (this.fk !== undefined) {
299
+ if ((this.fi = all.indexOf(this.fk)) < 0) this.fk = undefined
300
+ // G-424: the focused row's new index (or none): a new extractor, since the virtualizer
301
+ // memoizes the indexes on it and on the range (a reorder can keep both)
302
+ this.re = (r: any) => this.range(r)
303
+ }
304
+ // a new key function: the virtualizer re-reads the keys (its measurements are by key)
305
+ this.gk = (i: number) => this.all[i]
306
+ this.opts()
307
+ return true
308
+ }
309
+
310
+ /** the item instances in the window; true when they changed */
311
+ items_(): boolean {
312
+ const kc = this.keys()
313
+ const v = this.v!, app = this.owner.app, o = this.owner, all = this.all
314
+ const vis = all.length ? v.getVirtualIndexes() : NONE
315
+ const want = new Set<any>()
316
+ for (const i of vis) want.add(all[i])
317
+ const prev = this.shown
318
+ // G-401: rows are keyed, and snabbdom moves (detaches: the focus is lost) every kept row when
319
+ // rows leave the front while the end changes too. Then the rows leaving the front are removed
320
+ // in a patch of their own first (the ends match: nothing moves), the rest in the next one
321
+ // (the starts match): a microtask after the first patch, before the frame is painted. The
322
+ // flush may render again before it patches: the first step's rows stay until then.
323
+ if (this.p1 && !kc) return false
324
+ if (!kc && prev.length && vis.length) {
325
+ const k = (j: number) => (prev[j] as any).k
326
+ let h = 0, e = 0
327
+ while (h < prev.length && h < vis.length && k(h) === all[vis[h]]) h++
328
+ for (e = h; e < prev.length && !want.has(k(e)); e++);
329
+ if (e > h && e < prev.length && k(prev.length - 1) !== all[vis[vis.length - 1]]) {
330
+ for (let j = h; j < e; j++) { prev[j].dispose(); this.items.delete(k(j)) }
331
+ this.shown = prev.slice(0, h).concat(prev.slice(e))
332
+ this.si = this.si.slice(0, h).concat(this.si.slice(e))
333
+ this.p1 = true
334
+ queueMicrotask(() => { this.p1 = false; this.disposed || app.commit() })
335
+ return true
336
+ }
337
+ }
338
+ this.p1 = false
339
+ for (const [k, inst] of this.items) if (!want.has(k)) { inst.dispose(); this.items.delete(k) }
340
+ const shown: Inst[] = []
341
+ for (const i of vis) {
342
+ const k = all[i]
343
+ let inst = this.items.get(k)
344
+ if (!inst) {
345
+ const scope = app.scope()
346
+ inst = new Inst(app, this.def, o, itemCell(this.arr!, this.index!, k), o.dom && o.dom.isolateSource(o.dom, scope),
347
+ this.ip, this.kids, scope, this.uidBase + '-' + keyName(k), 'item')
348
+ this.items.set(k, inst);
349
+ (inst as any).k = k
350
+ }
351
+ shown.push(inst)
352
+ }
353
+ const changed = shown.length != prev.length || shown.some((s, i) => s !== prev[i])
354
+ this.shown = shown
355
+ this.si = vis as number[]
356
+ return changed
357
+ }
358
+
359
+ clear() {
360
+ super.clear()
361
+ this.all = NONE
362
+ this.si = []
363
+ }
364
+
365
+ /** a row's root with its index, ARIA position and measuring hook (the same vnode while nothing changed) */
366
+ deco(inst: Inst, x: any, i: number, n: number, list: boolean, top?: string): any {
367
+ const c = this.dc.get(inst)
368
+ if (c && c[0] === x && c[1] === i && c[2] === n && c[3] === list && c[4] === top) return c[5]
369
+ let out = x
370
+ if (!x.sel) {
371
+ // a fragment or text: nothing to measure or label
372
+ if (!(this.warned & 8)) { this.warned |= 8; dev(432, this.owner, {component: this.def.name}) }
373
+ } else {
374
+ // the position for a row with a role (its own, or listitem in a list): not on a generic element
375
+ const d = x.data || {}, role = d.attrs?.role || d.props?.role, attrs: any = {...d.attrs, 'data-index': i}
376
+ if (list || role) attrs['aria-posinset'] = i + 1, attrs['aria-setsize'] = n
377
+ if (list && !role) attrs.role = 'listitem'
378
+ const data: any = {...d, attrs}
379
+ // G-401: the focused row out of the window, at its own offset (relative to the window)
380
+ if (top !== undefined) data.style = {...d.style, position: 'absolute', top, left: '0', width: '100%'}
381
+ chainHooks(data, {insert: (y: any) => this.v?.measureElement(y.elm)})
382
+ // keyed (G-401): a row's element is its own, not the one at its position before
383
+ out = {...x, data, key: (inst as any).k}
384
+ }
385
+ this.dc.set(inst, [x, i, n, list, top, out])
386
+ return out
387
+ }
388
+
389
+ /** the container's vnode data: its props, attributes, style and the hooks that attach the virtualizer */
390
+ box() {
391
+ if (this.cd) return this.cd
392
+ const p = this.vp, attrs: any = {role: p.role === undefined ? 'list' : p.role, tabindex: p.tabIndex ?? 0}
393
+ for (const k of ['id', 'aria-label', 'aria-labelledby', 'aria-describedby']) if (p[k] != null) attrs[k] = p[k]
394
+ if (attrs.role == null) delete attrs.role
395
+ return (this.cd = {
396
+ props: this.props.className ? {className: this.props.className} : {},
397
+ attrs,
398
+ style: {overflowY: 'auto', overflowAnchor: 'none', ...p.style},
399
+ // (the mock DOM's command check: these methods are the container's)
400
+ vc: {commands: {scrollToIndex: 1, scrollToId: 1}},
401
+ hook: {
402
+ // (`s`: Sygnal's own, so hydration adopts the container: postpatch attaches it, G-521)
403
+ insert: Object.assign((y: any) => this.attach(y.elm), {s: 1}),
404
+ // G-424: a row element the patch moves (a reorder) loses the focus in every engine: the
405
+ // element in the list that had it gets it back when it is still on the page and the
406
+ // focus went nowhere
407
+ // G-462: none when the cached vnode is patched again (snabbdom calls no postpatch then:
408
+ // nothing would let go of the element); in a shadow root in a row, its focused element
409
+ prepatch: (o: any, y: any) => { const a = o !== y && act(this.el); this.fa = a && this.el.contains(a) ? deep(a) : null },
410
+ // G-500: "went nowhere" is the document's active element (body or none): in a shadow root,
411
+ // the root's own is none when the focus went to an element outside it
412
+ postpatch: (_: any, y: any) => {
413
+ const a = this.fa, b = a?.ownerDocument.activeElement
414
+ this.fa = null
415
+ if (a?.isConnected && (!b || b === a.ownerDocument.body)) a.focus({preventScroll: true})
416
+ y.elm !== this.el && this.attach(y.elm)
417
+ },
418
+ destroy: () => this.detach(),
419
+ },
420
+ })
421
+ }
422
+
423
+ attach(el: any) {
424
+ if (this.el) this.detach()
425
+ this.el = el
426
+ el.scrollToIndex = (x: any) => this.scrollTo(typeof x == 'number' ? x : x?.index, x)
427
+ el.scrollToId = (x: any) => {
428
+ const id = typeof x == 'object' && x ? x.id : x, i = id == null ? -1 : this.all.indexOf('' + id)
429
+ if (i < 0) return void dev(433, this.owner, {id, count: this.all.length})
430
+ this.scrollTo(i, x)
431
+ }
432
+ // G-401: which row holds the focus (a focusout to outside the list: none)
433
+ const fin = (e: any) => this.focus(this.all[this.rowOf(e.target)?.getAttribute('data-index')])
434
+ // G-428: no relatedTarget while the focus is still in the list: the window lost the focus
435
+ // (another window, devtools), and it comes back to the same element
436
+ const fout = (e: any) => el.contains(e.relatedTarget || act(el)) || this.focus(undefined)
437
+ el.addEventListener('focusin', fin)
438
+ el.addEventListener('focusout', fout)
439
+ const un = this.v!._didMount()
440
+ this.un = () => { un(); el.removeEventListener('focusin', fin); el.removeEventListener('focusout', fout) }
441
+ this.v!._willUpdate()
442
+ }
443
+
444
+ scrollTo(i: any, x: any) {
445
+ const n = this.all.length
446
+ if (!(i >= 0 && i < n) || i % 1) return void dev(433, this.owner, {index: i, count: n})
447
+ this.v!.scrollToIndex(i, {align: x?.align, behavior: x?.behavior})
448
+ }
449
+
450
+ detach() {
451
+ const el = this.el
452
+ if (!el) return
453
+ this.el = undefined
454
+ delete el.scrollToIndex
455
+ delete el.scrollToId
456
+ this.un?.()
457
+ }
458
+
459
+ render(): any {
460
+ if (this.disposed) return this.outv
461
+ const app = this.owner.app
462
+ let changed = !this.outv || this.ep !== app.ep
463
+ this.ep = app.ep
464
+ if (this.items_()) changed = true
465
+ const v = this.v!, all = this.all, n = all.length, shown = this.shown, si = this.si
466
+ const ms: any[] = n && si.length ? (v as any).getMeasurements() : NONE, pin = this.pin
467
+ // the window starts at its first row in the flow (a pinned row is out of it)
468
+ const w0 = si[0] === pin && si.length > 1 ? si[1] : si[0]
469
+ const start = ms[w0] ? ms[w0].start : 0, total = v.getTotalSize()
470
+ const list = (this.vp.role === undefined ? 'list' : this.vp.role) == 'list'
471
+ const lw = this.lw
472
+ if (!lw || lw[0] !== start || lw[1] !== total || lw[2] !== si[0]) changed = true
473
+ const out: any[] = []
474
+ const vn = this.props.viewTransitionName
475
+ for (let j = 0; j < shown.length; j++) {
476
+ const inst = shown[j], x = inst.render(), i = si[j]
477
+ if (x !== inst.last) { inst.last = x; changed = true }
478
+ // an item without state yet (or a removed one) is left out; G-417: named as a Collection's
479
+ if (x !== undefined) out.push(this.deco(inst, vn ? named(inst, x, vn) : x, i, n, list, i === pin && ms[i] ? px(ms[i].start - start) : undefined))
480
+ }
481
+ const data = this.box()
482
+ if (data !== lw?.[3]) changed = true
483
+ if (!changed) return this.outv
484
+ this.lw = [start, total, si[0], data]
485
+ const win = {sel: 'div', data: {style: {position: 'absolute', top: '0', left: '0', width: '100%', transform: `translateY(${px(start)})`}}, children: out, text: undefined, elm: undefined, key: undefined}
486
+ const spacer = {sel: 'div', data: {style: {position: 'relative', width: '100%', height: px(total)}}, children: [win], text: undefined, elm: undefined, key: undefined}
487
+ return (this.outv = {sel: 'div', data, children: [spacer], text: undefined, elm: undefined, key: this.key})
488
+ }
489
+
490
+ dispose() {
491
+ if (this.disposed) return
492
+ super.dispose()
493
+ this.detach()
494
+ }
495
+ }
496
+
497
+ const make = (owner: any, props: Record<string, any>, children: any[], id: string, marker: any) => new VirtualHost(owner, props, children, id, marker)
498
+
499
+ /**
500
+ * `<VirtualCollection of={Row} from="rows" className="rows" estimateSize={32} overscan={5} />`:
501
+ * a Collection that instantiates only the rows in view (see the module comment)
502
+ */
503
+ export const VirtualCollection: any = () => {}
504
+ VirtualCollection.__sygnalControl = (props: any, children: any) => {
505
+ hosts[SEL] ||= make
506
+ const {key, children: c, ...p} = props || {}
507
+ const kids = children?.length ? children : c ? [].concat(c) : NONE
508
+ // (G-493: `m`, a marker mark as the pragma's marker vnodes carry (there, the function): SYG669
509
+ // tells it from a plain element. Not the function itself: the self-reference keeps the module
510
+ // in bundles that don't use it)
511
+ return {sel: SEL, data: {props: p, m: 1}, children: kids, text: undefined, elm: undefined, key}
512
+ }
513
+ VirtualCollection.componentName = SEL