sygnal 5.3.7 → 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 (438) hide show
  1. package/CHANGELOG.md +646 -0
  2. package/README.md +77 -47
  3. package/dist/astro/client.cjs.js +33 -8114
  4. package/dist/astro/client.cjs.js.map +1 -0
  5. package/dist/astro/client.mjs +33 -8114
  6. package/dist/astro/client.mjs.map +1 -0
  7. package/dist/astro/index.cjs.js +1039 -9
  8. package/dist/astro/index.cjs.js.map +1 -0
  9. package/dist/astro/index.mjs +1038 -9
  10. package/dist/astro/index.mjs.map +1 -0
  11. package/dist/astro/server.cjs.js +3400 -166
  12. package/dist/astro/server.cjs.js.map +1 -0
  13. package/dist/astro/server.mjs +3400 -166
  14. package/dist/astro/server.mjs.map +1 -0
  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 +4033 -0
  20. package/dist/diagnostics.cjs.js.map +1 -0
  21. package/dist/diagnostics.esm.js +4003 -0
  22. package/dist/diagnostics.esm.js.map +1 -0
  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 +15283 -6279
  67. package/dist/index.cjs.js.map +1 -0
  68. package/dist/index.d.ts +3739 -370
  69. package/dist/index.esm.js +15230 -6274
  70. package/dist/index.esm.js.map +1 -0
  71. package/dist/jsx-dev-runtime.cjs.js +27 -329
  72. package/dist/jsx-dev-runtime.cjs.js.map +1 -0
  73. package/dist/jsx-dev-runtime.esm.js +20 -322
  74. package/dist/jsx-dev-runtime.esm.js.map +1 -0
  75. package/dist/jsx-runtime.cjs.js +27 -329
  76. package/dist/jsx-runtime.cjs.js.map +1 -0
  77. package/dist/jsx-runtime.esm.js +20 -322
  78. package/dist/jsx-runtime.esm.js.map +1 -0
  79. package/dist/jsx.cjs.js +11 -309
  80. package/dist/jsx.cjs.js.map +1 -0
  81. package/dist/jsx.esm.js +9 -310
  82. package/dist/jsx.esm.js.map +1 -0
  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 +2 -1
  89. package/dist/sygnal.min.js.map +1 -0
  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 +5 -14
  107. package/dist/vike/ClientOnly.cjs.js.map +1 -0
  108. package/dist/vike/ClientOnly.mjs +5 -14
  109. package/dist/vike/ClientOnly.mjs.map +1 -0
  110. package/dist/vike/{+config.js → config/+config.js} +10 -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 +166 -95
  114. package/dist/vike/onRenderClient.cjs.js.map +1 -0
  115. package/dist/vike/onRenderClient.mjs +166 -95
  116. package/dist/vike/onRenderClient.mjs.map +1 -0
  117. package/dist/vike/onRenderHtml.cjs.js +60 -24
  118. package/dist/vike/onRenderHtml.cjs.js.map +1 -0
  119. package/dist/vike/onRenderHtml.mjs +61 -25
  120. package/dist/vike/onRenderHtml.mjs.map +1 -0
  121. package/dist/vite/plugin.cjs.js +926 -65
  122. package/dist/vite/plugin.cjs.js.map +1 -0
  123. package/dist/vite/plugin.mjs +925 -65
  124. package/dist/vite/plugin.mjs.map +1 -0
  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 +313 -0
  130. package/package.json +109 -16
  131. package/src/astro/client.ts +40 -18
  132. package/src/astro/index.d.ts +48 -1
  133. package/src/astro/index.ts +106 -9
  134. package/src/astro/server.ts +11 -3
  135. package/src/collection.ts +6 -90
  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 +13 -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 +76 -0
  162. package/src/cycle/dom/controlledInputModule.ts +50 -0
  163. package/src/cycle/dom/enrichEventStream.ts +29 -45
  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 -57
  167. package/src/cycle/dom/mockDOMSource.ts +83 -9
  168. package/src/cycle/dom/modules.ts +21 -4
  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 +20 -4
  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 +109 -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 +67 -0
  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 +250 -0
  201. package/src/extra/diagnostics/checks/elementCommands.ts +175 -0
  202. package/src/extra/diagnostics/checks/events.ts +83 -0
  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 +202 -0
  206. package/src/extra/diagnostics/checks/inspect.ts +427 -0
  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 +56 -0
  210. package/src/extra/diagnostics/checks/public.d.ts +302 -0
  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 +88 -0
  214. package/src/extra/diagnostics/checks/shared.ts +203 -0
  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 +148 -0
  218. package/src/extra/diagnostics/checks/statics.ts +48 -0
  219. package/src/extra/diagnostics/checks/strict.ts +55 -0
  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 +121 -0
  225. package/src/extra/diagnostics/codes.ts +477 -0
  226. package/src/extra/diagnostics/index.ts +353 -0
  227. package/src/extra/diagnostics/legacy.ts +76 -0
  228. package/src/extra/driverFactories.ts +172 -60
  229. package/src/extra/elementCommands.ts +53 -0
  230. package/src/extra/eventDriver.ts +4 -0
  231. package/src/extra/fetchDriver.ts +519 -0
  232. package/src/extra/flatten.ts +75 -0
  233. package/src/extra/focusWithin.ts +27 -0
  234. package/src/extra/form.ts +268 -0
  235. package/src/extra/formHelpers.ts +139 -0
  236. package/src/extra/head.ts +106 -0
  237. package/src/extra/hmr.ts +2 -3
  238. package/src/extra/owned.ts +24 -0
  239. package/src/extra/pager.ts +50 -0
  240. package/src/extra/persist.ts +133 -0
  241. package/src/extra/pwa.ts +1 -1
  242. package/src/extra/queryCache.ts +107 -0
  243. package/src/extra/reducers.ts +13 -6
  244. package/src/extra/reduxDevtools.ts +92 -0
  245. package/src/extra/replies.ts +54 -0
  246. package/src/extra/router.ts +323 -0
  247. package/src/extra/run.ts +94 -210
  248. package/src/extra/selection.ts +76 -0
  249. package/src/extra/socketDriver.ts +265 -0
  250. package/src/extra/sortable.ts +377 -0
  251. package/src/extra/ssr.ts +427 -160
  252. package/src/extra/standardSchema.ts +27 -0
  253. package/src/extra/testing.ts +3158 -190
  254. package/src/extra/timers.ts +95 -0
  255. package/src/extra/undo.ts +261 -0
  256. package/src/extra/viewTransitions.ts +38 -0
  257. package/src/extra/virtual.ts +513 -0
  258. package/src/extra/widget.ts +201 -0
  259. package/src/extra/xstreamExtras.ts +269 -0
  260. package/src/index.d.ts +3012 -87
  261. package/src/index.ts +32 -10
  262. package/src/jsx-runtime.ts +15 -1
  263. package/src/jsx.ts +2 -1
  264. package/src/lazy.ts +50 -7
  265. package/src/portal.ts +3 -1
  266. package/src/pragma/index.ts +262 -134
  267. package/src/react-peers.d.ts +5 -0
  268. package/src/react.d.ts +43 -0
  269. package/src/react.ts +110 -0
  270. package/src/shared.ts +48 -0
  271. package/src/slot.ts +1 -1
  272. package/src/suspense.ts +3 -1
  273. package/src/switchable.ts +6 -119
  274. package/src/transition.ts +3 -1
  275. package/src/ui/accordion.ts +64 -0
  276. package/src/ui/dialog.ts +129 -0
  277. package/src/ui/disclosure.ts +35 -0
  278. package/src/ui/popover.ts +45 -0
  279. package/src/ui/shared.ts +94 -0
  280. package/src/ui/tabs.ts +82 -0
  281. package/src/ui/toaster.ts +198 -0
  282. package/src/ui/tooltip.ts +70 -0
  283. package/src/ui/zag/combobox.ts +115 -0
  284. package/src/ui/zag/menu.ts +40 -0
  285. package/src/ui/zag/select.ts +54 -0
  286. package/src/ui/zag/shared.ts +43 -0
  287. package/src/ui-combobox.d.ts +20 -0
  288. package/src/ui-combobox.ts +6 -0
  289. package/src/ui-menu.d.ts +30 -0
  290. package/src/ui-menu.ts +6 -0
  291. package/src/ui-select.d.ts +17 -0
  292. package/src/ui-select.ts +6 -0
  293. package/src/ui-zag-types.d.ts +42 -0
  294. package/src/ui.d.ts +208 -0
  295. package/src/ui.ts +15 -0
  296. package/src/vike/+config.ts +9 -1
  297. package/src/vike/ClientOnly.ts +1 -1
  298. package/src/vike/onRenderClient.ts +144 -96
  299. package/src/vike/onRenderHtml.ts +69 -26
  300. package/src/vike/types.ts +27 -4
  301. package/src/vite/globalthis-shim.ts +18 -0
  302. package/src/vite/globalthis.ts +56 -0
  303. package/src/vite/plugin.d.ts +114 -2
  304. package/src/vite/plugin.ts +920 -56
  305. package/src/zag.d.ts +64 -0
  306. package/src/zag.ts +238 -0
  307. package/dist/astro/astro/client.d.ts +0 -5
  308. package/dist/astro/astro/index.d.ts +0 -16
  309. package/dist/astro/astro/server.d.ts +0 -12
  310. package/dist/astro/client.d.ts +0 -5
  311. package/dist/astro/client.esm.js +0 -4665
  312. package/dist/astro/collection.d.ts +0 -12
  313. package/dist/astro/component.d.ts +0 -22
  314. package/dist/astro/cycle/dom/BodyDOMSource.d.ts +0 -10
  315. package/dist/astro/cycle/dom/DOMSource.d.ts +0 -11
  316. package/dist/astro/cycle/dom/DocumentDOMSource.d.ts +0 -12
  317. package/dist/astro/cycle/dom/ElementFinder.d.ts +0 -8
  318. package/dist/astro/cycle/dom/EventDelegator.d.ts +0 -34
  319. package/dist/astro/cycle/dom/IsolateModule.d.ts +0 -23
  320. package/dist/astro/cycle/dom/MainDOMSource.d.ts +0 -32
  321. package/dist/astro/cycle/dom/PriorityQueue.d.ts +0 -7
  322. package/dist/astro/cycle/dom/ScopeChecker.d.ts +0 -9
  323. package/dist/astro/cycle/dom/SymbolTree.d.ts +0 -9
  324. package/dist/astro/cycle/dom/VNodeWrapper.d.ts +0 -8
  325. package/dist/astro/cycle/dom/enrichEventStream.d.ts +0 -24
  326. package/dist/astro/cycle/dom/fromEvent.d.ts +0 -6
  327. package/dist/astro/cycle/dom/index.d.ts +0 -8
  328. package/dist/astro/cycle/dom/isolate.d.ts +0 -9
  329. package/dist/astro/cycle/dom/makeDOMDriver.d.ts +0 -11
  330. package/dist/astro/cycle/dom/mockDOMSource.d.ts +0 -17
  331. package/dist/astro/cycle/dom/modules.d.ts +0 -5
  332. package/dist/astro/cycle/dom/snabbdom.d.ts +0 -22
  333. package/dist/astro/cycle/dom/styleModule.d.ts +0 -13
  334. package/dist/astro/cycle/dom/thunk.d.ts +0 -11
  335. package/dist/astro/cycle/dom/utils.d.ts +0 -8
  336. package/dist/astro/cycle/isolate/index.d.ts +0 -42
  337. package/dist/astro/cycle/run/adapt.d.ts +0 -6
  338. package/dist/astro/cycle/run/index.d.ts +0 -36
  339. package/dist/astro/cycle/run/internals.d.ts +0 -8
  340. package/dist/astro/cycle/run/types.d.ts +0 -49
  341. package/dist/astro/cycle/state/Collection.d.ts +0 -29
  342. package/dist/astro/cycle/state/StateSource.d.ts +0 -20
  343. package/dist/astro/cycle/state/index.d.ts +0 -5
  344. package/dist/astro/cycle/state/pickCombine.d.ts +0 -3
  345. package/dist/astro/cycle/state/pickMerge.d.ts +0 -3
  346. package/dist/astro/cycle/state/types.d.ts +0 -18
  347. package/dist/astro/cycle/state/withState.d.ts +0 -17
  348. package/dist/astro/extra/classes.d.ts +0 -7
  349. package/dist/astro/extra/devtools.d.ts +0 -78
  350. package/dist/astro/extra/dragDriver.d.ts +0 -20
  351. package/dist/astro/extra/driverFactories.d.ts +0 -12
  352. package/dist/astro/extra/eventDriver.d.ts +0 -9
  353. package/dist/astro/extra/exactState.d.ts +0 -1
  354. package/dist/astro/extra/hmr.d.ts +0 -17
  355. package/dist/astro/extra/logDriver.d.ts +0 -2
  356. package/dist/astro/extra/processDrag.d.ts +0 -19
  357. package/dist/astro/extra/processForm.d.ts +0 -15
  358. package/dist/astro/extra/run.d.ts +0 -13
  359. package/dist/astro/extra/xstreamCompat.d.ts +0 -5
  360. package/dist/astro/index.d.ts +0 -19
  361. package/dist/astro/index.esm.js +0 -27
  362. package/dist/astro/jsx-dev-runtime.d.ts +0 -1
  363. package/dist/astro/jsx-runtime.d.ts +0 -4
  364. package/dist/astro/jsx.d.ts +0 -2
  365. package/dist/astro/pragma/fn.d.ts +0 -7
  366. package/dist/astro/pragma/index.d.ts +0 -7
  367. package/dist/astro/pragma/is.d.ts +0 -10
  368. package/dist/astro/server.d.ts +0 -12
  369. package/dist/astro/server.esm.js +0 -25
  370. package/dist/astro/switchable.d.ts +0 -7
  371. package/dist/collection.d.ts +0 -12
  372. package/dist/component.d.ts +0 -22
  373. package/dist/cycle/dom/BodyDOMSource.d.ts +0 -10
  374. package/dist/cycle/dom/DOMSource.d.ts +0 -11
  375. package/dist/cycle/dom/DocumentDOMSource.d.ts +0 -12
  376. package/dist/cycle/dom/ElementFinder.d.ts +0 -8
  377. package/dist/cycle/dom/EventDelegator.d.ts +0 -34
  378. package/dist/cycle/dom/IsolateModule.d.ts +0 -23
  379. package/dist/cycle/dom/MainDOMSource.d.ts +0 -32
  380. package/dist/cycle/dom/PriorityQueue.d.ts +0 -7
  381. package/dist/cycle/dom/ScopeChecker.d.ts +0 -9
  382. package/dist/cycle/dom/SymbolTree.d.ts +0 -9
  383. package/dist/cycle/dom/VNodeWrapper.d.ts +0 -8
  384. package/dist/cycle/dom/enrichEventStream.d.ts +0 -24
  385. package/dist/cycle/dom/fromEvent.d.ts +0 -6
  386. package/dist/cycle/dom/index.d.ts +0 -8
  387. package/dist/cycle/dom/isolate.d.ts +0 -9
  388. package/dist/cycle/dom/makeDOMDriver.d.ts +0 -11
  389. package/dist/cycle/dom/mockDOMSource.d.ts +0 -17
  390. package/dist/cycle/dom/modules.d.ts +0 -5
  391. package/dist/cycle/dom/snabbdom.d.ts +0 -22
  392. package/dist/cycle/dom/styleModule.d.ts +0 -13
  393. package/dist/cycle/dom/thunk.d.ts +0 -11
  394. package/dist/cycle/dom/utils.d.ts +0 -8
  395. package/dist/cycle/isolate/index.d.ts +0 -42
  396. package/dist/cycle/run/adapt.d.ts +0 -6
  397. package/dist/cycle/run/index.d.ts +0 -36
  398. package/dist/cycle/run/internals.d.ts +0 -8
  399. package/dist/cycle/run/types.d.ts +0 -49
  400. package/dist/cycle/state/Collection.d.ts +0 -29
  401. package/dist/cycle/state/StateSource.d.ts +0 -20
  402. package/dist/cycle/state/index.d.ts +0 -5
  403. package/dist/cycle/state/pickCombine.d.ts +0 -3
  404. package/dist/cycle/state/pickMerge.d.ts +0 -3
  405. package/dist/cycle/state/types.d.ts +0 -18
  406. package/dist/cycle/state/withState.d.ts +0 -17
  407. package/dist/extra/classes.d.ts +0 -7
  408. package/dist/extra/devtools.d.ts +0 -78
  409. package/dist/extra/dragDriver.d.ts +0 -20
  410. package/dist/extra/driverFactories.d.ts +0 -12
  411. package/dist/extra/eventDriver.d.ts +0 -9
  412. package/dist/extra/exactState.d.ts +0 -1
  413. package/dist/extra/hmr.d.ts +0 -17
  414. package/dist/extra/logDriver.d.ts +0 -2
  415. package/dist/extra/processDrag.d.ts +0 -19
  416. package/dist/extra/processForm.d.ts +0 -15
  417. package/dist/extra/run.d.ts +0 -13
  418. package/dist/extra/xstreamCompat.d.ts +0 -5
  419. package/dist/jsx-dev-runtime.d.ts +0 -1
  420. package/dist/jsx-dev-runtime.js +0 -224
  421. package/dist/jsx-runtime.d.ts +0 -4
  422. package/dist/jsx-runtime.js +0 -224
  423. package/dist/jsx.d.ts +0 -2
  424. package/dist/pragma/fn.d.ts +0 -7
  425. package/dist/pragma/index.d.ts +0 -7
  426. package/dist/pragma/is.d.ts +0 -10
  427. package/dist/switchable.d.ts +0 -7
  428. package/dist/vike/+config.cjs.js +0 -65
  429. package/src/component.ts +0 -2256
  430. package/src/cycle/isolate/index.ts +0 -196
  431. package/src/cycle/run/index.ts +0 -151
  432. package/src/cycle/run/internals.ts +0 -143
  433. package/src/cycle/state/Collection.ts +0 -174
  434. package/src/cycle/state/index.ts +0 -5
  435. package/src/cycle/state/pickCombine.ts +0 -167
  436. package/src/cycle/state/pickMerge.ts +0 -122
  437. package/src/cycle/state/withState.ts +0 -47
  438. package/src/pragma/fn.ts +0 -55
package/src/extra/ssr.ts CHANGED
@@ -4,6 +4,9 @@
4
4
  * Renders Sygnal component trees to HTML strings without a browser DOM.
5
5
  * Handles sub-components, Collections, Suspense boundaries, and Portals.
6
6
  */
7
+ import {uidPart, vtStyle} from '../shared'
8
+ import {sortFn} from '../core/hosts/collection'
9
+ import {keyOf} from '../core/cell'
7
10
 
8
11
  // Void elements that must not have closing tags
9
12
  const VOID_ELEMENTS = new Set([
@@ -20,8 +23,24 @@ const ESC_MAP: Record<string, string> = {
20
23
  "'": '&#39;',
21
24
  }
22
25
 
23
- function escapeHtml(str: string): string {
24
- return String(str).replace(/[&<>"']/g, (ch) => ESC_MAP[ch])
26
+ // 4-F: renderComponent's t.html() serialises like the browser's innerHTML: text escapes only
27
+ // & < >, attribute values only & and " (SSR output keeps escaping all five)
28
+ let innerHtmlMode = false
29
+ // G-480: the <Slot> marker (its function in `data.m`), not a plain <slot> element
30
+ const isSlot = (v: any): boolean => v?.data?.m && v.sel === 'slot'
31
+
32
+ function escapeHtml(str: string, attr?: boolean): string {
33
+ return String(str).replace(innerHtmlMode ? (attr ? /[&"]/g : /[&<>]/g) : /[&<>"']/g, (ch) => ESC_MAP[ch])
34
+ }
35
+
36
+ /** @internal renderToString serialised like innerHTML (for renderComponent's t.html()) */
37
+ export function renderToInnerHtml(componentDef: any): string {
38
+ innerHtmlMode = true
39
+ try {
40
+ return renderToString(componentDef)
41
+ } finally {
42
+ innerHtmlMode = false
43
+ }
25
44
  }
26
45
 
27
46
  /**
@@ -65,6 +84,124 @@ export interface RenderToStringOptions {
65
84
  * When a string, uses that as the variable name instead.
66
85
  */
67
86
  hydrateState?: boolean | string
87
+ /**
88
+ * PLAN-3 (HEAD driver): an array that receives each rendered component's `head` static
89
+ * value (in render order); `renderHead(list)` turns it into tags.
90
+ */
91
+ head?: any[]
92
+ /**
93
+ * PLAN-3 5-5 (H-7): a queryCache() the components' `resources` render from: a cached entry
94
+ * renders as `{ status: 'success', data }`, any other request as `{ status: 'loading' }`.
95
+ * Nothing is fetched during SSR; seed the cache in a loader (`cache.set(request, data)`)
96
+ */
97
+ cache?: any
98
+ /**
99
+ * PLAN-4 GS-11: the app-level error hook, as run()'s `onError`: called with phase 'view' after
100
+ * the component's onError boundary chose the fallback. Reporting only
101
+ */
102
+ onError?: (error: any, info: {componentName?: string; action?: string; phase: string}) => void
103
+ /**
104
+ * PLAN-4 G-206: the root of the uid() strings (default 'u'), as run()'s `uid` option: pass the
105
+ * same value to both so the hydrated ids match
106
+ */
107
+ uid?: string
108
+ }
109
+
110
+ // the `onError` option of the outermost renderToString call that has one (GS-11)
111
+ let ssrOnError: any
112
+
113
+ // the `head` option of the outermost renderToString call that has one
114
+ let heads: any[] | undefined
115
+ // 5-5: the `cache` option of the outermost renderToString call that has one
116
+ let ssrCache: any
117
+
118
+ /**
119
+ * PLAN-4 GS-1: a host's behaviors: each `uses` key missing from the state is the slice the
120
+ * behavior starts with (its initialState, the options naming its keys, its calculated fields: the
121
+ * factory's precomputed `state`), as the client's merge gives it (behaviors.ts). A host with no
122
+ * state gets an object with just its slices
123
+ */
124
+ function withUses(def: any, state: any): any {
125
+ const uses = def && def.uses
126
+ if (!uses || typeof uses !== 'object') return state
127
+ if (state == null) state = {}
128
+ if (typeof state !== 'object' || Array.isArray(state)) return state
129
+ let out = state
130
+ for (const k in uses) {
131
+ const u = uses[k]
132
+ if (!u) continue
133
+ let v = out[k] === undefined ? u.state : out[k]
134
+ // G-423: a sygnal/ui part (ui/shared.ts keyed()) names its ids by its `uses` key (or its
135
+ // `id` option) when the slice has no id, as the client's merge and calculated `id` do
136
+ if (u.keyed && v && typeof v === 'object' && v.id == null) v = {...v, id: u.options?.id ?? k}
137
+ if (v !== out[k] && v !== undefined) {
138
+ if (out === state) out = {...state}
139
+ out[k] = v
140
+ }
141
+ }
142
+ return out
143
+ }
144
+
145
+ /**
146
+ * 5-5 (H-7): the state a component's view renders with: each `resources` entry missing from the
147
+ * state reads from the cache ('success' with the cached data), else 'loading' ('idle' for a
148
+ * falsy request), as the client's first paint does with the same (hydrated) cache
149
+ */
150
+ function withResources(def: any, state: any): any {
151
+ state = withUses(def, state)
152
+ const res = def && def.resources
153
+ if (!res || !state || typeof state !== 'object' || Array.isArray(state)) return state
154
+ const out = {...state}
155
+ for (const n of Object.keys(res)) {
156
+ if (out[n] !== undefined) continue
157
+ let q: any
158
+ try { q = typeof res[n] === 'function' ? res[n](state) : res[n] } catch (_) {}
159
+ const E = q && ssrCache ? ssrCache.get(ssrCache.key(q, 1)) : undefined
160
+ out[n] = E && E.at > 0 ? {status: 'success', data: E.v} : {status: q ? 'loading' : 'idle'}
161
+ }
162
+ return out
163
+ }
164
+
165
+ // PLAN-4 GS-9: uid(name?) as on the client: the root is 'u'; a child component adds its path in
166
+ // the parent's view (or its `id` prop), as getComponentIdFromElement and instantiateSubComponents
167
+ // in the core do; a Collection item adds its key, a Switchable page its name; anything but
168
+ // each part encoded by uidPart (G-214); in the root option anything but [A-Za-z0-9_-] becomes '_'
169
+ // (as run() does). SSR ids = hydration ids
170
+ const makeUid = (base: string) => (n?: string) => n ? base + '-' + n : base
171
+ function childUid(uid: string, vnode: any, path: string): string {
172
+ const id = vnode.data?.props?.id
173
+ return uid + '-' + uidPart(('::' + ((id && JSON.stringify(id).replaceAll('"', '')) || path)).replace(/.*::(r\.)?/, ''))
174
+ }
175
+
176
+ // G-567: a view that returns a string or a number renders it as text (core/instance.ts view());
177
+ // another falsy value, an empty <div>
178
+ const textOr = (r: any): any => typeof r == 'string' || typeof r == 'number' ? {text: '' + r} : r
179
+ const viewOut = (r: any): any => textOr(r) || {sel: 'div', data: {}, children: [], text: undefined, elm: undefined, key: undefined}
180
+ const errorDiv = (): any => ({sel: 'div', data: {attrs: {'data-sygnal-error': ''}}, children: [], text: undefined, elm: undefined, key: undefined})
181
+
182
+ /**
183
+ * A view threw: the component's onError boundary picks the fallback (the error <div> without
184
+ * one, or when it throws), then the app's onError hook is told (GS-11)
185
+ */
186
+ function viewFailed(def: any, err: any, name: string): any {
187
+ let vnode: any = errorDiv()
188
+ if (typeof def.onError === 'function') {
189
+ try {
190
+ vnode = def.onError(err, {componentName: name})
191
+ } catch (_) {
192
+ vnode = errorDiv()
193
+ }
194
+ }
195
+ // reporting only: a throwing hook is logged and swallowed (no diagnostics import: the Astro and
196
+ // Vike server bundles carry this file without the core)
197
+ try { ssrOnError && ssrOnError(err, {componentName: name, phase: 'view'}) } catch (e) { console.error(e) }
198
+ return vnode
199
+ }
200
+
201
+ function collectHead(def: any, state: any): void {
202
+ const h = def && def.head
203
+ if (!heads || !h) return
204
+ try { heads.push(typeof h === 'function' ? h(state) : h) } catch (_) {}
68
205
  }
69
206
 
70
207
  /**
@@ -77,16 +214,39 @@ export interface RenderToStringOptions {
77
214
  * import { renderToString } from 'sygnal'
78
215
  *
79
216
  * const html = renderToString(App, { state: { count: 0 } })
80
- * // → '<div><h1>Count: 0</h1></div>'
217
+ * // → '<div data-sygnal-ssr=""><h1>Count: 0</h1></div>'
81
218
  * ```
219
+ *
220
+ * The root element (a fragment's first element) carries an empty `data-sygnal-ssr` attribute,
221
+ * which marks the markup as Sygnal's server HTML (persist() under plain run() then restores after
222
+ * the first render); the first client render removes it.
82
223
  */
83
224
  export function renderToString(
84
225
  componentDef: any,
85
226
  options: RenderToStringOptions = {}
86
227
  ): string {
228
+ const prevHeads = heads, prevCache = ssrCache, prevOnError = ssrOnError
229
+ if (options.head) heads = options.head
230
+ if (options.cache) ssrCache = options.cache
231
+ if (options.onError) ssrOnError = options.onError
232
+ try {
233
+ return renderRoot(componentDef, options)
234
+ } finally {
235
+ heads = prevHeads
236
+ ssrCache = prevCache
237
+ ssrOnError = prevOnError
238
+ }
239
+ }
240
+
241
+ function renderRoot(componentDef: any, options: RenderToStringOptions): string {
87
242
  const {state, props = {}, context = {}, hydrateState} = options
243
+ // G-206: the uid root (sanitized as run() does)
244
+ const uid = (options.uid || 'u').replace(/[^\w-]+/g, '_')
88
245
 
89
- const resolvedState = state !== undefined ? state : componentDef.initialState
246
+ const ownState = state !== undefined ? state : componentDef.initialState
247
+ // 5-5: the view sees its resources; the hydration script keeps the state as it was given
248
+ const resolvedState = withResources(componentDef, ownState)
249
+ collectHead(componentDef, resolvedState)
90
250
 
91
251
  // Build context: merge parent context with component's own context definitions
92
252
  const componentContext = componentDef.context || {}
@@ -109,20 +269,11 @@ export function renderToString(
109
269
  children: props.children || [],
110
270
  slots: props.slots || {},
111
271
  context: mergedContext,
112
- peers: {},
113
- }, resolvedState, mergedContext, {})
272
+ uid: makeUid(uid),
273
+ })
114
274
  } catch (err: any) {
115
275
  // Error boundary
116
- if (typeof componentDef.onError === 'function') {
117
- const name = componentDef.componentName || componentDef.name || 'Component'
118
- try {
119
- vnode = componentDef.onError(err, {componentName: name})
120
- } catch (_) {
121
- vnode = {sel: 'div', data: {attrs: {'data-sygnal-error': ''}}, children: [], text: undefined, elm: undefined, key: undefined}
122
- }
123
- } else {
124
- vnode = {sel: 'div', data: {attrs: {'data-sygnal-error': ''}}, children: [], text: undefined, elm: undefined, key: undefined}
125
- }
276
+ vnode = viewFailed(componentDef, err, componentDef.componentName || componentDef.name || 'Component')
126
277
  }
127
278
 
128
279
  if (!vnode) {
@@ -130,16 +281,27 @@ export function renderToString(
130
281
  }
131
282
 
132
283
  // Process special components in the VNode tree
133
- vnode = processSSRTree(vnode, mergedContext, resolvedState)
284
+ vnode = processSSRTree(vnode, mergedContext, resolvedState, uid, 'r')
285
+
286
+ // PLAN-4 3-R: mark the root element (a fragment's first element) as Sygnal's server markup, so
287
+ // persist() under plain run() can tell it from a client-only app's loading placeholder. The
288
+ // first client render drops the attribute (the new vnode doesn't have it)
289
+ // (4-H: through nested fragments, e.g. a Collection's)
290
+ const firstEl = (v: any): any => {
291
+ if (v?.sel) return v.sel !== '!' && v
292
+ if (Array.isArray(v?.children)) for (const c of v.children) { const f = firstEl(c); if (f) return f }
293
+ }
294
+ const first = vnode?.sel ? vnode : firstEl(vnode)
295
+ if (first && !innerHtmlMode) first.data = {...first.data, attrs: {'data-sygnal-ssr': '', ...first.data?.attrs}}
134
296
 
135
297
  // Serialize to HTML
136
298
  let html = vnodeToHtml(vnode)
137
299
 
138
300
  // Optionally embed state for hydration
139
- if (hydrateState && resolvedState != null) {
301
+ if (hydrateState && ownState != null) {
140
302
  const varName = typeof hydrateState === 'string' ? hydrateState : '__SYGNAL_STATE__'
141
- const serialized = escapeHtml(JSON.stringify(resolvedState))
142
- html += `<script>window.${varName}=${JSON.stringify(resolvedState)}</script>`
303
+ const serialized = escapeHtml(JSON.stringify(ownState))
304
+ html += `<script>window.${varName}=${JSON.stringify(ownState)}</script>`
143
305
  }
144
306
 
145
307
  return html
@@ -149,7 +311,7 @@ export function renderToString(
149
311
  * Walk the VNode tree and recursively render sub-components,
150
312
  * handle Suspense/Portal/Transition markers, and process Collections.
151
313
  */
152
- function processSSRTree(vnode: any, context: Record<string, any>, parentState?: any): any {
314
+ function processSSRTree(vnode: any, context: Record<string, any>, parentState: any, uid: string, path: string): any {
153
315
  if (!vnode) return vnode
154
316
  if (typeof vnode === 'string' || vnode.text != null) return vnode
155
317
 
@@ -158,7 +320,7 @@ function processSSRTree(vnode: any, context: Record<string, any>, parentState?:
158
320
  // Fragment: no selector, recurse into children
159
321
  if (!sel && vnode.children && Array.isArray(vnode.children)) {
160
322
  vnode.children = vnode.children
161
- .map((c: any) => processSSRTree(c, context, parentState))
323
+ .map((c: any, i: number) => processSSRTree(c, context, parentState, uid, `${path}.${i}`))
162
324
  .filter((c: any) => c != null)
163
325
  return vnode
164
326
  }
@@ -167,11 +329,13 @@ function processSSRTree(vnode: any, context: Record<string, any>, parentState?:
167
329
  if (sel === 'portal') {
168
330
  const children = vnode.children || []
169
331
  if (children.length === 0) return null
170
- if (children.length === 1) return processSSRTree(children[0], context, parentState)
332
+ if (children.length === 1) return processSSRTree(children[0], context, parentState, uid, `${path}.0`)
333
+ // P46-Q (D187): the client placeholder's selector (div.sygnal-portal), so hydration patches
334
+ // this element in place and the Portal mounts from its postpatch (G-318)
171
335
  return {
172
- sel: 'div',
173
- data: {attrs: {'data-sygnal-portal': ''}},
174
- children: children.map((c: any) => processSSRTree(c, context, parentState)),
336
+ sel: 'div.sygnal-portal',
337
+ data: {attrs: {'data-sygnal-portal': vnode.data?.props?.target ?? ''}},
338
+ children: children.map((c: any, i: number) => processSSRTree(c, context, parentState, uid, `${path}.${i}`)),
175
339
  text: undefined,
176
340
  elm: undefined,
177
341
  key: undefined,
@@ -183,18 +347,18 @@ function processSSRTree(vnode: any, context: Record<string, any>, parentState?:
183
347
  const children = vnode.children || []
184
348
  const child = children[0]
185
349
  if (!child) return null
186
- return processSSRTree(child, context, parentState)
350
+ return processSSRTree(child, context, parentState, uid, path)
187
351
  }
188
352
 
189
353
  // Suspense: render children (SSR always shows content, not fallback)
190
354
  if (sel === 'suspense') {
191
355
  const children = vnode.children || []
192
356
  if (children.length === 0) return null
193
- if (children.length === 1) return processSSRTree(children[0], context, parentState)
357
+ if (children.length === 1) return processSSRTree(children[0], context, parentState, uid, `${path}.0`)
194
358
  return {
195
359
  sel: 'div',
196
360
  data: {attrs: {'data-sygnal-suspense': 'resolved'}},
197
- children: children.map((c: any) => processSSRTree(c, context, parentState)),
361
+ children: children.map((c: any, i: number) => processSSRTree(c, context, parentState, uid, `${path}.${i}`)),
198
362
  text: undefined,
199
363
  elm: undefined,
200
364
  key: undefined,
@@ -207,7 +371,7 @@ function processSSRTree(vnode: any, context: Record<string, any>, parentState?:
207
371
  const fallback = props.fallback
208
372
  if (fallback) {
209
373
  // fallback can be a VNode or a string
210
- return processSSRTree(fallback, context, parentState)
374
+ return processSSRTree(fallback, context, parentState, uid, path)
211
375
  }
212
376
  // No fallback — render an empty placeholder div
213
377
  return {
@@ -220,15 +384,23 @@ function processSSRTree(vnode: any, context: Record<string, any>, parentState?:
220
384
  }
221
385
  }
222
386
 
387
+ // PLAN-5 W-1 widget marker: the host element with the widget's `fallback` inside (processed like
388
+ // any children); the client patch replaces the fallback and mounts the widget (extra/widget.ts)
389
+ if (sel === 'widget' && vnode.data?.ww) {
390
+ const {ww, wp, h} = vnode.data, fb = ww.def.fallback, host = ww.$ssr(vnode)
391
+ if (fb != null) host.children = [].concat(typeof fb == 'function' ? fb(wp, h) : fb).map((c: any) => typeof c == 'object' ? c : {text: String(c)})
392
+ return processSSRTree(host, context, parentState, uid, path)
393
+ }
394
+
223
395
  // Slot: unwrap to children
224
- if (sel === 'slot') {
396
+ if (isSlot(vnode)) {
225
397
  const children = vnode.children || []
226
398
  if (children.length === 0) return null
227
- if (children.length === 1) return processSSRTree(children[0], context, parentState)
399
+ if (children.length === 1) return processSSRTree(children[0], context, parentState, uid, `${path}.0`)
228
400
  return {
229
401
  sel: 'div',
230
402
  data: {},
231
- children: children.map((c: any) => processSSRTree(c, context, parentState)),
403
+ children: children.map((c: any, i: number) => processSSRTree(c, context, parentState, uid, `${path}.${i}`)),
232
404
  text: undefined,
233
405
  elm: undefined,
234
406
  key: undefined,
@@ -237,18 +409,24 @@ function processSSRTree(vnode: any, context: Record<string, any>, parentState?:
237
409
 
238
410
  // Sub-component: render recursively
239
411
  const props = vnode.data?.props || {}
240
- if (props.sygnalOptions || typeof props.sygnalFactory === 'function') {
241
- return renderSubComponent(vnode, context, parentState)
412
+ // (PLAN-4.6: the pragma's `data.c`, the component function)
413
+ if (typeof vnode.data?.c === 'function') {
414
+ return renderSubComponent(vnode, context, parentState, childUid(uid, vnode, path))
242
415
  }
243
416
 
244
417
  // Collection: render each item
245
418
  if (sel === 'collection') {
246
- return renderCollection(vnode, context, parentState)
419
+ return renderCollection(vnode, context, parentState, childUid(uid, vnode, path))
420
+ }
421
+
422
+ // PLAN-5 V-1: a VirtualCollection renders its no-layout window (the first rows) in its container
423
+ if (sel === 'virtual-collection') {
424
+ return renderVirtual(vnode, context, parentState, childUid(uid, vnode, path))
247
425
  }
248
426
 
249
427
  // Switchable: render the active component
250
428
  if (sel === 'switchable') {
251
- return renderSwitchable(vnode, context, parentState)
429
+ return renderSwitchable(vnode, context, parentState, childUid(uid, vnode, path))
252
430
  }
253
431
 
254
432
  // Regular element: recurse into children
@@ -256,12 +434,12 @@ function processSSRTree(vnode: any, context: Record<string, any>, parentState?:
256
434
  if (Array.isArray(vnode.children)) {
257
435
  if (vnode.children.length > 0) {
258
436
  vnode.children = vnode.children
259
- .map((c: any) => processSSRTree(c, context, parentState))
437
+ .map((c: any, i: number) => processSSRTree(c, context, parentState, uid, `${path}.${i}`))
260
438
  .filter((c: any) => c != null)
261
439
  }
262
440
  } else if (vnode.children && typeof vnode.children === 'object') {
263
441
  // Single child object (text element)
264
- vnode.children = processSSRTree(vnode.children, context, parentState)
442
+ vnode.children = processSSRTree(vnode.children, context, parentState, uid, `${path}.0`)
265
443
  }
266
444
  }
267
445
 
@@ -269,39 +447,11 @@ function processSSRTree(vnode: any, context: Record<string, any>, parentState?:
269
447
  }
270
448
 
271
449
  /**
272
- * Render a sub-component (identified by sygnalOptions or sygnalFactory in props).
450
+ * Render a sub-component (the pragma's `data.c`: the component function, with its statics).
273
451
  */
274
- function renderSubComponent(vnode: any, context: Record<string, any>, parentState?: any): any {
275
- const props = vnode.data?.props || {}
276
- const {sygnalOptions, sygnalFactory, ...childProps} = props
277
-
278
- // Get the component definition (view function with static properties)
279
- let componentDef: any
280
- if (sygnalOptions) {
281
- componentDef = sygnalOptions.view
282
- // Copy static properties
283
- if (!componentDef.initialState && sygnalOptions.initialState) {
284
- componentDef = Object.assign(componentDef, {
285
- initialState: sygnalOptions.initialState,
286
- model: sygnalOptions.model,
287
- intent: sygnalOptions.intent,
288
- context: sygnalOptions.context,
289
- onError: sygnalOptions.onError,
290
- calculated: sygnalOptions.calculated,
291
- })
292
- }
293
- } else if (sygnalFactory && sygnalFactory.componentName) {
294
- // Factory-created component — we can't easily extract the view from an already-wrapped factory.
295
- // Return a placeholder.
296
- return {
297
- sel: 'div',
298
- data: {attrs: {'data-sygnal-ssr': vnode.sel || 'component'}},
299
- children: vnode.children || [],
300
- text: undefined,
301
- elm: undefined,
302
- key: undefined,
303
- }
304
- }
452
+ function renderSubComponent(vnode: any, context: Record<string, any>, parentState: any, uid: string): any {
453
+ const childProps = vnode.data?.props || {}
454
+ const componentDef: any = vnode.data?.c
305
455
 
306
456
  if (!componentDef || typeof componentDef !== 'function') {
307
457
  // Can't render — return children or empty div
@@ -324,6 +474,7 @@ function renderSubComponent(vnode: any, context: Record<string, any>, parentStat
324
474
  // State passed directly
325
475
  childState = stateProp
326
476
  }
477
+ childState = withResources(componentDef, childState)
327
478
 
328
479
  // Build child context
329
480
  const childContext: Record<string, any> = {...context}
@@ -342,7 +493,7 @@ function renderSubComponent(vnode: any, context: Record<string, any>, parentStat
342
493
  const defaultSlotChildren: any[] = []
343
494
  const vnodeChildren = vnode.children || []
344
495
  for (const child of vnodeChildren) {
345
- if (child && child.sel === 'slot') {
496
+ if (isSlot(child)) {
346
497
  const slotName = child.data?.props?.name || 'default'
347
498
  if (!slots[slotName]) slots[slotName] = []
348
499
  const slotChildren = child.children || []
@@ -356,6 +507,8 @@ function renderSubComponent(vnode: any, context: Record<string, any>, parentStat
356
507
  slots.default.push(...defaultSlotChildren)
357
508
  }
358
509
 
510
+ collectHead(componentDef, childState)
511
+
359
512
  // Call the view function
360
513
  let result: any
361
514
  try {
@@ -365,46 +518,88 @@ function renderSubComponent(vnode: any, context: Record<string, any>, parentStat
365
518
  children: slots.default || vnodeChildren,
366
519
  slots,
367
520
  context: childContext,
368
- peers: {},
369
- }, childState, childContext, {})
521
+ uid: makeUid(uid),
522
+ })
370
523
  } catch (err: any) {
371
- if (typeof componentDef.onError === 'function') {
372
- const name = componentDef.componentName || componentDef.name || 'Component'
373
- try {
374
- result = componentDef.onError(err, {componentName: name})
375
- } catch (_) {
376
- result = {sel: 'div', data: {attrs: {'data-sygnal-error': ''}}, children: [], text: undefined, elm: undefined, key: undefined}
377
- }
378
- } else {
379
- result = {sel: 'div', data: {attrs: {'data-sygnal-error': ''}}, children: [], text: undefined, elm: undefined, key: undefined}
380
- }
381
- }
382
-
383
- if (!result) {
384
- result = {sel: 'div', data: {}, children: [], text: undefined, elm: undefined, key: undefined}
524
+ result = viewFailed(componentDef, err, componentDef.componentName || componentDef.name || 'Component')
385
525
  }
386
526
 
387
527
  // Recursively process the rendered sub-tree
388
- return processSSRTree(result, childContext, childState)
528
+ return processSSRTree(viewOut(result), childContext, childState, uid, 'r')
389
529
  }
390
530
 
531
+ /** the Collection's own props (core/hosts/collection.ts OWN): the others go to every item */
532
+ const COLLECTION_OWN = new Set(['of', 'from', 'filter', 'sort', 'idfield', 'className', 'viewTransitionName'])
533
+ /** the VirtualCollection container's props (extra/virtual.ts OWN) */
534
+ const VIRTUAL_OWN = /^(estimateSize|overscan|role|tabIndex|style|id|aria-(label|labelledby|describedby))$/
535
+
391
536
  /**
392
- * Render a Collection by iterating over the state array.
537
+ * PLAN-5 2-S G-396: the items a Collection shows, as the client's host lists them
538
+ * (core/hosts/collection.ts arrayCell + list()): `from` a state key (missing or not an array:
539
+ * none, D178), a `{ get }` lens, or none (the owner's state is the array, or its `value`); then
540
+ * `filter` (item, index, array) and `sort` (every form sortFn takes); a duplicate key once (D177).
541
+ * Each entry: [the item, its raw index]
393
542
  */
394
- function renderCollection(vnode: any, context: Record<string, any>, parentState?: any): any {
395
- const props = vnode.data?.props || {}
396
- const {of: itemComponent, from, className} = props
397
-
398
- if (!itemComponent || !from || !parentState) {
399
- return {sel: 'div', data: {}, children: [], text: undefined, elm: undefined, key: undefined}
400
- }
543
+ function collectionItems(props: any, parentState: any): any[][] {
544
+ const {from, filter, sort} = props
545
+ let a: any
546
+ if (from === undefined) a = !Array.isArray(parentState) && Array.isArray(parentState?.value) ? parentState.value : parentState
547
+ else if (typeof from === 'string') a = parentState?.[from]
548
+ else if (from && typeof from === 'object' && typeof from.get === 'function') {
549
+ try { a = from.get(parentState) } catch (_) { a = undefined }
550
+ }
551
+ if (!Array.isArray(a)) return []
552
+ let idx: number[] = a.map((_: any, i: number) => i)
553
+ try {
554
+ if (typeof filter === 'function') idx = idx.filter((i) => filter(a[i], i, a))
555
+ const cmp = sort ? sortFn(sort) : undefined
556
+ if (cmp) idx.sort((x, y) => cmp(a[x], a[y]))
557
+ } catch (_) { /* as the client: a throwing filter / sort renders the array as it is */ }
558
+ const first = new Map<string, number>(), out: any[][] = []
559
+ for (let i = 0; i < a.length; i++) { const k = keyOf(a[i], i); if (!first.has(k)) first.set(k, i) }
560
+ for (const i of idx) if (first.get(keyOf(a[i], i)) === i) out.push([a[i], i])
561
+ return out
562
+ }
401
563
 
402
- const items = parentState[from]
403
- if (!Array.isArray(items)) {
404
- return {sel: 'div', data: {}, children: [], text: undefined, elm: undefined, key: undefined}
564
+ /** a component's children split into slots (as renderSubComponent: `<Slot name>` children by name, the rest `default`) */
565
+ function slotsOf(children: any[]): Record<string, any[]> {
566
+ const slots: Record<string, any[]> = {}
567
+ for (const child of children) {
568
+ const name = isSlot(child) ? child.data?.props?.name || 'default' : 'default'
569
+ ;(slots[name] ||= []).push(...(isSlot(child) ? child.children || [] : [child]))
405
570
  }
571
+ return slots
572
+ }
406
573
 
407
- const renderedItems = items.map((itemState: any, index: number) => {
574
+ /**
575
+ * Render a Collection: its items (collectionItems: from, filter, sort), each with the other
576
+ * props and the Collection's children, as the client's host gives them (G-396). `limit`: only
577
+ * the first that many (a VirtualCollection's window); `skip`: props that aren't the items'.
578
+ * 4-H (D229): a fragment of the items (no wrapper element), as the client's.
579
+ */
580
+ function renderCollection(vnode: any, context: Record<string, any>, parentState: any, uid: string, limit?: number, skip?: RegExp, deco?: (row: any, i: number) => any): any {
581
+ const props = vnode.data?.props || {}
582
+ const {of: itemComponent, viewTransitionName: vtn} = props
583
+ const frag = (children: any[]) => ({sel: undefined, data: {}, children, text: undefined, elm: undefined, key: undefined})
584
+
585
+ if (!itemComponent || typeof itemComponent !== 'function') return frag([])
586
+
587
+ let items = collectionItems(props, parentState)
588
+ const count = items.length
589
+ if (limit !== undefined) items = items.slice(0, limit)
590
+ const itemProps: Record<string, any> = {}
591
+ for (const k in props) if (!COLLECTION_OWN.has(k) && !(skip && skip.test(k)) && k !== 'key' && k !== 'children') itemProps[k] = props[k]
592
+ const kids = vnode.children || []
593
+ const slots = slotsOf(kids)
594
+
595
+ const renderedItems = items.map(([raw, index]: any[], pos: number) => {
596
+ let itemState = raw
597
+ // GS-9: the key the client's Collection gives this item
598
+ const isItemObj = itemState && typeof itemState === 'object' && !Array.isArray(itemState)
599
+ // PLAN-4.6 G-322: an id-less item's uid part is `_i<index>` (core/cell.ts keyName)
600
+ const itemUid = uid + '-' + (isItemObj && itemState.id != null ? uidPart(itemState.id) : '_i' + index)
601
+ // GS-1: an item host's behavior slices (the client reads them as defaults, behaviors.ts)
602
+ itemState = withUses(itemComponent, itemState)
408
603
  // Build context for this item
409
604
  const itemContext: Record<string, any> = {...context}
410
605
  const componentContext = itemComponent.context || {}
@@ -420,46 +615,68 @@ function renderCollection(vnode: any, context: Record<string, any>, parentState?
420
615
  let itemVnode: any
421
616
  try {
422
617
  itemVnode = itemComponent({
618
+ ...itemProps,
423
619
  state: itemState,
424
- children: [],
425
- slots: {},
620
+ children: slots.default || kids,
621
+ slots,
426
622
  context: itemContext,
427
- peers: {},
428
- }, itemState, itemContext, {})
623
+ uid: makeUid(itemUid),
624
+ })
429
625
  } catch (err: any) {
430
- if (typeof itemComponent.onError === 'function') {
431
- try {
432
- itemVnode = itemComponent.onError(err, {componentName: itemComponent.name || 'CollectionItem'})
433
- } catch (_) {
434
- itemVnode = {sel: 'div', data: {attrs: {'data-sygnal-error': ''}}, children: [], text: undefined, elm: undefined, key: undefined}
435
- }
436
- } else {
437
- itemVnode = {sel: 'div', data: {attrs: {'data-sygnal-error': ''}}, children: [], text: undefined, elm: undefined, key: undefined}
438
- }
626
+ itemVnode = viewFailed(itemComponent, err, itemComponent.name || 'CollectionItem')
439
627
  }
440
628
 
441
- return processSSRTree(itemVnode, itemContext, itemState)
629
+ const out = processSSRTree(textOr(itemVnode), itemContext, itemState, itemUid, 'r')
630
+ // PLAN-5 A-1: the names the client's Collection gives a keyed item's root element
631
+ const named = vtn && isItemObj && itemState.id != null && out?.sel
632
+ ? {...out, data: {...out.data, style: vtStyle(vtn, itemState.id, out.data?.style)}}
633
+ : out
634
+ // a VirtualCollection's row decoration, with the row's position in the list (G-431: counted
635
+ // before the rows that render nothing are left out, as the client counts them)
636
+ return deco && named?.sel ? deco(named, pos) : named
442
637
  }).filter((v: any) => v != null)
443
638
 
444
- const containerData: any = {}
445
- if (className) {
446
- containerData.props = {className}
447
- }
639
+ return frag(renderedItems)
640
+ }
448
641
 
449
- return {
450
- sel: 'div',
451
- data: containerData,
452
- children: renderedItems,
453
- text: undefined,
454
- elm: undefined,
455
- key: undefined,
456
- }
642
+ /**
643
+ * PLAN-5 V-1: a VirtualCollection on the server: the client's markup (extra/virtual.ts) for the
644
+ * window it computes without layout, the first 10 rows + overscan, with the spacer as tall as
645
+ * every row's estimate. The client measures and moves the window once it has layout. G-396: the
646
+ * rows are the Collection's (from, filter, sort, the other props and the children to every
647
+ * item); aria-setsize and the spacer count the filtered list.
648
+ */
649
+ function renderVirtual(vnode: any, context: Record<string, any>, parentState: any, uid: string): any {
650
+ const p = vnode.data?.props || {}, est = p.estimateSize, size = est > 0 ? est : 32
651
+ const n = 10 + (p.overscan >= 0 ? p.overscan : 5)
652
+ const items = typeof p.of === 'function' ? collectionItems(p, parentState) : []
653
+ const role = p.role === undefined ? 'list' : p.role
654
+ const rows = renderCollection(vnode, context, parentState, uid, n, VIRTUAL_OWN, (r: any, i: number) => {
655
+ const own = r.data?.attrs?.role || r.data?.props?.role, attrs: any = {...r.data?.attrs, 'data-index': i}
656
+ if (role == 'list' || own) attrs['aria-posinset'] = i + 1, attrs['aria-setsize'] = items.length
657
+ if (role == 'list' && !own) attrs.role = 'listitem'
658
+ return {...r, data: {...r.data, attrs}}
659
+ }).children
660
+ const attrs: any = {tabindex: p.tabIndex ?? 0}
661
+ if (role != null) attrs.role = role
662
+ for (const k of ['id', 'aria-label', 'aria-labelledby', 'aria-describedby']) if (p[k] != null) attrs[k] = p[k]
663
+ let total = 0
664
+ for (let i = 0; i < items.length; i++) {
665
+ const s = typeof est == 'function' ? est(items[i][0], i) : size
666
+ total += s > 0 ? s : 32
667
+ }
668
+ const div = (data: any, children: any[]) => ({sel: 'div', data, children, text: undefined, elm: undefined, key: undefined})
669
+ return div({props: p.className ? {className: p.className} : {}, attrs, style: {overflowY: 'auto', overflowAnchor: 'none', ...p.style}}, [
670
+ div({style: {position: 'relative', width: '100%', height: total + 'px'}}, [
671
+ div({style: {position: 'absolute', top: '0', left: '0', width: '100%', transform: 'translateY(0px)'}}, rows),
672
+ ]),
673
+ ])
457
674
  }
458
675
 
459
676
  /**
460
677
  * Render a Switchable by determining the active component from state.
461
678
  */
462
- function renderSwitchable(vnode: any, context: Record<string, any>, parentState?: any): any {
679
+ function renderSwitchable(vnode: any, context: Record<string, any>, parentState: any, uid: string): any {
463
680
  const props = vnode.data?.props || {}
464
681
  const {components, active, initial} = props
465
682
 
@@ -489,14 +706,14 @@ function renderSwitchable(vnode: any, context: Record<string, any>, parentState?
489
706
  return {sel: 'div', data: {}, children: [], text: undefined, elm: undefined, key: undefined}
490
707
  }
491
708
 
492
- return renderToStringInternal(activeComponent, parentState, context)
709
+ return renderToStringInternal(activeComponent, parentState, context, uid + '-' + uidPart(activeName))
493
710
  }
494
711
 
495
712
  /**
496
713
  * Internal helper: render a component def to a VNode (not HTML string).
497
714
  */
498
- function renderToStringInternal(componentDef: any, state: any, context: Record<string, any>): any {
499
- const resolvedState = state !== undefined ? state : componentDef.initialState
715
+ function renderToStringInternal(componentDef: any, state: any, context: Record<string, any>, uid: string): any {
716
+ const resolvedState = withResources(componentDef, state !== undefined ? state : componentDef.initialState)
500
717
 
501
718
  const componentContext = componentDef.context || {}
502
719
  const mergedContext: Record<string, any> = {...context}
@@ -509,6 +726,7 @@ function renderToStringInternal(componentDef: any, state: any, context: Record<s
509
726
  }
510
727
  }
511
728
 
729
+ collectHead(componentDef, resolvedState)
512
730
  let vnode: any
513
731
  try {
514
732
  vnode = componentDef({
@@ -516,31 +734,38 @@ function renderToStringInternal(componentDef: any, state: any, context: Record<s
516
734
  children: [],
517
735
  slots: {},
518
736
  context: mergedContext,
519
- peers: {},
520
- }, resolvedState, mergedContext, {})
737
+ uid: makeUid(uid),
738
+ })
521
739
  } catch (err: any) {
522
- if (typeof componentDef.onError === 'function') {
523
- try {
524
- vnode = componentDef.onError(err, {componentName: componentDef.name || 'Component'})
525
- } catch (_) {
526
- vnode = {sel: 'div', data: {attrs: {'data-sygnal-error': ''}}, children: [], text: undefined, elm: undefined, key: undefined}
527
- }
528
- } else {
529
- vnode = {sel: 'div', data: {attrs: {'data-sygnal-error': ''}}, children: [], text: undefined, elm: undefined, key: undefined}
530
- }
740
+ vnode = viewFailed(componentDef, err, componentDef.name || 'Component')
531
741
  }
532
742
 
533
- if (!vnode) {
534
- vnode = {sel: 'div', data: {}, children: [], text: undefined, elm: undefined, key: undefined}
535
- }
743
+ return processSSRTree(viewOut(vnode), mergedContext, resolvedState, uid, 'r')
744
+ }
536
745
 
537
- return processSSRTree(vnode, mergedContext, resolvedState)
746
+ // G-465: a form field's `value` (a DOM property) has no attribute on <textarea> / <select>: the
747
+ // textarea's goes in as its text, the select's marks the matching <option> (`sv`: the values, as
748
+ // strings, of the <select> the option is in, and whether it is multiple) as selected. G-495: as
749
+ // the browser does: an option without a value matches by its text with ASCII whitespace stripped
750
+ // and collapsed; a single select marks only its first match; an explicit `selected` (true or
751
+ // false) is the option's own. G-496: not in t.html()'s innerHTML mode (a property, which
752
+ // innerHTML doesn't show)
753
+ type SV = {v: string[]; m: boolean}
754
+ const ws = (t: string): string => t.replace(/[\t\n\f\r ]+/g, ' ').replace(/^ | $/g, '')
755
+ const fieldValue = (data: any): any => data?.props?.value ?? data?.attrs?.value
756
+ const withoutValue = (data: any): any => {
757
+ const out = {...data}
758
+ if (out.props) { out.props = {...out.props}; delete out.props.value }
759
+ if (out.attrs) { out.attrs = {...out.attrs}; delete out.attrs.value }
760
+ return out
538
761
  }
762
+ const textOf = (v: any): string => v == null ? '' : typeof v != 'object' ? String(v)
763
+ : v.text != null ? String(v.text) : Array.isArray(v.children) ? v.children.map(textOf).join('') : ''
539
764
 
540
765
  /**
541
766
  * Serialize a VNode tree to an HTML string.
542
767
  */
543
- function vnodeToHtml(vnode: any): string {
768
+ function vnodeToHtml(vnode: any, sv?: SV): string {
544
769
  if (vnode == null) return ''
545
770
 
546
771
  // Text node
@@ -553,7 +778,7 @@ function vnodeToHtml(vnode: any): string {
553
778
  if (vnode.text != null) return escapeHtml(String(vnode.text))
554
779
  // Fragment: no selector, but has children — concatenate child HTML
555
780
  if (vnode.children && Array.isArray(vnode.children)) {
556
- return vnode.children.map((c: any) => vnodeToHtml(c)).join('')
781
+ return vnode.children.map((c: any) => vnodeToHtml(c, sv)).join('')
557
782
  }
558
783
  return ''
559
784
  }
@@ -561,8 +786,28 @@ function vnodeToHtml(vnode: any): string {
561
786
  // Parse selector: tag#id.class1.class2
562
787
  const {tag, id, selectorClasses} = parseSelector(sel)
563
788
 
789
+ let data = vnode.data || {}, content: string | undefined
790
+ const value = fieldValue(data)
791
+ if (innerHtmlMode) {
792
+ // (G-496)
793
+ } else if (tag == 'textarea' && value != null) {
794
+ data = withoutValue(data)
795
+ // the HTML parser drops a leading newline (a CR or CRLF is one: G-495), so one is added
796
+ const t = String(value)
797
+ content = escapeHtml((t[0] == '\n' || t[0] == '\r' ? '\n' : '') + t)
798
+ } else if (tag == 'select' && value != null) {
799
+ data = withoutValue(data)
800
+ sv = {v: ([] as any[]).concat(value).map(String), m: !!(data.props?.multiple ?? data.attrs?.multiple)}
801
+ } else if (tag == 'option' && sv && data.props?.selected == null && data.attrs?.selected == null) {
802
+ if (sv.v.includes(value != null ? String(value) : ws(textOf(vnode)))) {
803
+ data = {...data, attrs: {...data.attrs, selected: true}}
804
+ if (!sv.m) sv.v = []
805
+ }
806
+ }
807
+ if (tag != 'select' && tag != 'optgroup' && tag != 'option') sv = undefined
808
+
564
809
  // Build attributes from VNode data
565
- const attrs = buildAttributes(vnode.data || {}, id, selectorClasses)
810
+ const attrs = buildAttributes(data, id, selectorClasses, tag.includes('-'))
566
811
 
567
812
  // Opening tag
568
813
  let html = `<${tag}`
@@ -570,7 +815,7 @@ function vnodeToHtml(vnode: any): string {
570
815
  if (val === true) {
571
816
  html += ` ${key}`
572
817
  } else if (val !== false && val != null) {
573
- html += ` ${key}="${escapeHtml(String(val))}"`
818
+ html += ` ${key}="${escapeHtml(String(val), true)}"`
574
819
  }
575
820
  }
576
821
  html += '>'
@@ -589,13 +834,15 @@ function vnodeToHtml(vnode: any): string {
589
834
 
590
835
  // Children — snabbdom uses `text` for single text children (even when
591
836
  // `children` holds a text element object). Prioritize `text` when set.
592
- if (vnode.text != null) {
837
+ if (content !== undefined) {
838
+ html += content
839
+ } else if (vnode.text != null) {
593
840
  html += escapeHtml(String(vnode.text))
594
841
  } else if (vnode.children) {
595
842
  // children can be an array or a single text element object
596
843
  const kids = Array.isArray(vnode.children) ? vnode.children : [vnode.children]
597
844
  for (const child of kids) {
598
- html += vnodeToHtml(child)
845
+ html += vnodeToHtml(child, sv)
599
846
  }
600
847
  }
601
848
 
@@ -644,29 +891,49 @@ function parseSelector(sel: string): {tag: string; id: string | null; selectorCl
644
891
  /**
645
892
  * Build an ordered list of [attrName, attrValue] from VNode data.
646
893
  */
894
+ // 1-R G-365: camelCase HTML IDL properties whose content attribute is their lowercase name
895
+ const IDL_ATTRS = /^(tabIndex|readOnly|maxLength|minLength|contentEditable|accessKey|inputMode|enterKeyHint|spellCheck|autoFocus|autoComplete|autoCapitalize|autoPlay|noValidate|formAction|formEnctype|formMethod|formNoValidate|formTarget|crossOrigin|referrerPolicy|useMap|isMap|dateTime|colSpan|rowSpan|srcSet|allowFullscreen|playsInline|noModule|popoverTarget|popoverTargetAction|encType)$/
896
+ const kebab = (k: string): string => k.replace(/[A-Z]/g, (c) => '-' + c.toLowerCase())
897
+
647
898
  function buildAttributes(
648
899
  data: any,
649
900
  selectorId: string | null,
650
- selectorClasses: string[]
901
+ selectorClasses: string[],
902
+ custom?: boolean
651
903
  ): Array<[string, any]> {
652
904
  const result: Array<[string, any]> = []
653
905
  const classNames: string[] = [...selectorClasses]
654
906
 
655
907
  // From data.props (DOM properties like className, htmlFor, etc.)
656
908
  if (data.props) {
657
- for (const [key, val] of Object.entries(data.props)) {
909
+ for (let [key, val] of Object.entries(data.props)) {
658
910
  if (key === 'className') {
659
911
  if (typeof val === 'string' && val) classNames.push(val)
660
912
  } else if (key === 'htmlFor') {
661
913
  result.push(['for', val])
662
914
  } else if (key === 'innerHTML' || key === 'textContent') {
663
915
  // Skip — handled separately if needed
664
- } else if (key === 'sygnalOptions' || key === 'sygnalFactory') {
665
- // Internal — skip
666
- } else if (typeof val === 'boolean') {
667
- if (val) result.push([key, true])
668
- } else if (val != null) {
669
- result.push([key, val])
916
+ } else if (custom && (typeof val === 'function' || (val && typeof val === 'object'))) {
917
+ // PLAN-5 D199: a custom element's function / object property has no attribute form
918
+ } else {
919
+ // PLAN-5 D199 / 1-R G-365: a custom element's camelCase property as attributes. A
920
+ // reflected HTML IDL name gets its attribute (tabIndex → tabindex, readOnly → readonly),
921
+ // an ARIA reflection its aria-* one (ariaLabel → aria-label). Any other camelCase name is
922
+ // written twice: kebab-case (withClear → with-clear: Web Awesome / Shoelace's declared
923
+ // attributes, Stencil's default, sygnal/element's defineElement) and lowercase
924
+ // (withclear: Lit's and FAST's default attribute name); the element reads the one it
925
+ // observes and ignores the other
926
+ const keys = !custom || !/[A-Z]/.test(key) ? [key]
927
+ : IDL_ATTRS.test(key) ? [key.toLowerCase()]
928
+ : /^aria[A-Z]/.test(key) ? [kebab(key)]
929
+ : [kebab(key), key.toLowerCase()]
930
+ for (const k of keys) {
931
+ if (typeof val === 'boolean') {
932
+ if (val) result.push([k, true])
933
+ } else if (val != null) {
934
+ result.push([k, val])
935
+ }
936
+ }
670
937
  }
671
938
  }
672
939
  }