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
@@ -0,0 +1,268 @@
1
+ /*
2
+ * PLAN-5 F-1 (D193): the `form` behavior, the canonical form recipe.
3
+ *
4
+ * Signup.uses = { form: form(signupSchema, {
5
+ * values: { email: '', password: '', addresses: [{ id: 1, street: '', city: '' }] },
6
+ * submit: 'SIGN_UP', // the host action a valid submit dispatches (data: the schema's output)
7
+ * check: { email: { request: (email) => ({ url: '/api/email-free', query: { email } }), error: (body) => !body.free && 'Taken' } },
8
+ * }) }
9
+ *
10
+ * Fields are addressed by their `name=` attribute (the path in `values`; rows of an array by
11
+ * id: `addresses.7.street`), heard on the form element (option `form`, default 'form'): input
12
+ * and focusout bubble to it, also from fields rendered by Collection items. Nothing is wired per
13
+ * field.
14
+ *
15
+ * Options: `values` (the start values; D239: or a function of the host's state, called when the
16
+ * form starts), `submit` (host action), `check` (async per-field checks through a driver: a
17
+ * request with a reply action, `latest`), `show` ('blur' default: a schema error shows once its
18
+ * field was blurred; 'input'; 'submit'), `form` (selector), `http` (the driver sink of the
19
+ * checks, 'HTTP'), `resetOnShow` (D239).
20
+ *
21
+ * D239 (G-578): `resetOnShow: true` starts the form over (the start values, as a new form: touched,
22
+ * errors, server errors, checks and the submit state cleared) each time it is shown: when its
23
+ * host starts (VALIDATE: a component mounted again, a Switchable page made again for another
24
+ * `instance`, a slice kept in the parent's state) and each time the form element appears again
25
+ * in the host's DOM after being absent (a Switchable page shown again: hidden pages stay alive;
26
+ * a form rendered conditionally). Re-renders while it is shown keep what the user typed. Off by
27
+ * default: a wizard step keeps its values when the user comes back. Without a real DOM (the mock
28
+ * DOM, SSR) only the host start counts. A `values` function is called with the host's state at
29
+ * each start over (without resetOnShow: once, when the host starts with the slice untouched);
30
+ * before that (a child's first render) the fields come from calling it with a blank state.
31
+ *
32
+ * Slice (state.form): values, initial, errors (all current schema errors by name), touched,
33
+ * server (server errors), remote (async check results: '' passed), pending (name -> value being
34
+ * checked), submitting, submitted, submitCount, queued (a submit waits for a check or an async
35
+ * schema), validating (an async schema runs), validated (the schema has answered since the start
36
+ * or the last reset); calculated: fields (per name: { name, value, error, invalid, touched,
37
+ * dirty, pending }; `error` is what to show), valid (G-382: the last answer's, kept while an async
38
+ * schema re-validates), dirty, error (form-level message).
39
+ *
40
+ * Actions: form.CHANGE ({ name, value, item? }: G-376: a checkbox gives `checked`, and `item`, its
41
+ * value, which toggles membership when the field is an array; <select multiple> an array of the
42
+ * selected values; type=file is ignored), form.BLUR (name), form.SUBMIT, form.ADD ({ field,
43
+ * value }), form.REMOVE ({ field, id }), form.ERRORS (an error reply or a map: server errors),
44
+ * form.DONE (saved), form.RESET (values?); internal: form.RESULT (an async schema's result),
45
+ * form.CHECKED_<name> (a check's reply; a failed check passes: the server checks on submit),
46
+ * form.VALIDATE (the first validation, when the host starts: G-375; `valid` is false until the
47
+ * schema answered; with resetOnShow also each time the form element appears, D239).
48
+ *
49
+ * G-575 (D236): a valid submit is `submitting` (until form.DONE / form.ERRORS, a second submit
50
+ * dropped) only when the host's submit entry has the `http` sink; any other submit (STATE, PARENT,
51
+ * EVENTS, EFFECT, ...) sends no request and is done at once (submitted, as form.DONE would). A
52
+ * slice kept in parent state from a host that unmounted mid-submit starts unstuck: VALIDATE (each
53
+ * host start) clears submitting, queued and pending.
54
+ *
55
+ * Each action is one step function, `(slice, data, key) => outcome | null` (null: no change),
56
+ * the outcome `{ s: new slice, focus: ELEMENT command, req: a check request, send: 1 (dispatch
57
+ * submit), wait: { values, submit? } (an async schema to await) }`. Its sinks (STATE, EFFECT,
58
+ * ELEMENT, and the checks' driver) read that outcome, computed once per (slice, data). D197:
59
+ * the handlers get the `uses` key, which names the reply actions.
60
+ *
61
+ * Dev diagnostics (checks/forms.ts, through the core bridge's `form` hook; nothing in
62
+ * production): SYG230 a field name not in values, SYG231 not a Standard Schema (throws), SYG232
63
+ * a submit dropped, SYG233 a values key missing from the schema's output, SYG234 a submit that
64
+ * dispatched one of the form's own actions (G-577), SYG235 a check request that sets reply
65
+ * fields. The host checks (SYG234 submit, SYG235 check names, SYG236 row ids)
66
+ * run when the host is created.
67
+ */
68
+ import xs from './xstreamCompat'
69
+ import {defineBehavior} from './behaviors'
70
+ import {ABORT} from '../shared'
71
+ import {isStandardSchema} from './standardSchema'
72
+ import {checkForm, fieldNames, getField, hasField, setField, replyErrors, focusInvalid} from './formHelpers'
73
+
74
+ const dev = (...a: any[]): any => (globalThis as any).__SYGNAL_DIAGNOSTICS__?.form?.(...a)
75
+ const keys = (o: any) => Object.keys(o).filter(k => o[k])
76
+ const drop = (o: any, p: string) => {
77
+ const r: any = {}
78
+ for (const k in o) if (k != p && !k.startsWith(p + '.')) r[k] = o[k]
79
+ return r
80
+ }
81
+
82
+ export const form = (schema: any, o: any = {}): any => {
83
+ isStandardSchema(schema) || dev(231, schema)
84
+ const {values: vo = {}, submit, check: checks = {}, show = 'blur', http = 'HTTP', form: sel = 'form', resetOnShow: again} = o
85
+ // D239: `values` can be a function of the host's state, called when the form starts (and each
86
+ // show, resetOnShow). Before that (the host's first render, before its first action) the
87
+ // fields come from calling it with a blank state: every read gives '' in the result
88
+ const fn = typeof vo == 'function', B: any = new Proxy(() => B, {get: (_, k) => k == Symbol.toPrimitive ? () => '' : B})
89
+ const clean = (x: any): any => x === B ? '' : Array.isArray(x) ? x.map(clean) : x && typeof x == 'object' ? Object.fromEntries(Object.entries(x).map(([k, y]) => [k, clean(y)])) : x
90
+ let values: any = vo
91
+ if (fn) try { values = clean(vo(B)) } catch (_) { values = {} }
92
+ const cache = new WeakMap(), checked = Object.keys(checks)
93
+ // G-575: a submit is pending (until form.DONE / form.ERRORS) only when the host's submit entry
94
+ // sends a request on the `http` sink; any other submit is done once its entry has run. Set when
95
+ // the form merges into its host
96
+ let req = true
97
+ // the schema's result, once per values object; an async one replaces its Promise when it settles
98
+ const v = (vals: any): any => {
99
+ let r = cache.get(vals)
100
+ if (!r) {
101
+ cache.set(vals, r = checkForm(schema, vals))
102
+ r.then?.((x: any) => cache.set(vals, x))
103
+ }
104
+ return r
105
+ }
106
+ // G-379: a field is a path of values (an undefined value too); `quiet`: no SYG230 (a focusout
107
+ // from a named button)
108
+ const known = (s: any, n: any, quiet?: any) => {
109
+ const k = n && hasField(s.values, n)
110
+ n && !k && !quiet && dev(230, n, s.values)
111
+ return k
112
+ }
113
+ // new values: errors now (sync schema), or after the schema's Promise (RESULT)
114
+ const edit = (s: any, vals: any, x?: any) => {
115
+ const r = v(vals), a = !!r.then
116
+ return {s: {...s, ...x, values: vals, validating: a, ...(!a && {errors: r.errors, validated: true})}, wait: a && {values: vals}}
117
+ }
118
+ // the first check due: no schema error, a value, and not checked or running for it
119
+ const due = (s: any, only?: string) => checked.find(f => {
120
+ const x = getField(s.values, f)
121
+ return (!only || f == only) && !s.errors[f] && x !== '' && x != null && !(f in s.pending) && !(f in s.remote)
122
+ })
123
+ const ask = (s: any, f: string, k: string, queued: boolean) => {
124
+ const x = getField(s.values, f), q = checks[f].request(x), ok = `${k}.CHECKED_${f}`
125
+ dev(235, f, q)
126
+ return {s: {...s, queued, pending: {...s.pending, [f]: x}}, req: {...q, ok, error: ok, latest: true}}
127
+ }
128
+ // a submit with s's errors: blocked (focus the first), a check to run or to wait for, or sent
129
+ const attempt = (s: any, k: string) => {
130
+ const bad = [...keys(s.errors), ...keys(s.remote)], f = due(s)
131
+ return bad.length ? {s: {...s, queued: false}, focus: focusInvalid(bad, sel)}
132
+ : f ? ask(s, f, k, true)
133
+ : Object.keys(s.pending).length ? {s: {...s, queued: true}}
134
+ : {s: req ? {...s, queued: false, submitting: true} : done({...s, queued: false}), send: 1}
135
+ }
136
+ const base = (vals: any) => ({initial: vals, values: vals, touched: {}, server: {}, remote: {}, pending: {},
137
+ submitting: false, submitted: false, submitCount: 0, queued: false, errors: {}, validated: false})
138
+ const fresh = (vals: any) => edit(base(vals), vals)
139
+ const done = (s: any) => ({...s, submitting: false, submitted: true, initial: s.values, server: {}, touched: {}})
140
+ const group = (cur: any, d: any) => {
141
+ if (!Array.isArray(cur) || !('item' in d)) return d.value
142
+ const r = cur.filter(x => x !== d.item)
143
+ return d.value ? [...r, d.item] : r
144
+ }
145
+
146
+ const steps: Record<string, (s: any, d: any, k: string, h?: any) => any> = {
147
+ // G-376: a checkbox (`item`: its value) on an array field is one of a group: checked adds
148
+ // its value, unchecked removes it
149
+ CHANGE: (s, d) => known(s, d?.name) ? edit(s, setField(s.values, d.name, group(getField(s.values, d.name), d)), {
150
+ server: drop(drop(s.server, d.name), ''), remote: drop(s.remote, d.name), pending: drop(s.pending, d.name), queued: false,
151
+ touched: show == 'input' ? {...s.touched, [d.name]: true} : s.touched,
152
+ }) : null,
153
+ BLUR: (s, n, k) => {
154
+ if (!known(s, n, 1)) return null
155
+ const t = show == 'submit' || s.touched[n] ? s.touched : {...s.touched, [n]: true}, x = {...s, touched: t}
156
+ return due(s, n) ? ask(x, n, k, s.queued) : t != s.touched ? {s: x} : null
157
+ },
158
+ SUBMIT: (s, _, k) => {
159
+ if (s.submitting || s.queued) return void dev(232, s)
160
+ const n = {...s, submitCount: s.submitCount + 1}, r = v(s.values)
161
+ // G-371: a submit waiting for an async schema is queued (a second one is dropped)
162
+ return r.then ? {s: {...n, validating: true, queued: true}, wait: {values: s.values, submit: 1}} : attempt({...n, errors: r.errors}, k)
163
+ },
164
+ RESULT: (s, d, k) => {
165
+ if (d.values !== s.values) return
166
+ const n = {...s, errors: v(d.values).errors, validating: false, validated: true}
167
+ // the submit it was queued for (G-575: a done local submit isn't `submitting`, so not that)
168
+ return d.submit && s.queued ? attempt(n, k) : {s: n}
169
+ },
170
+ ADD: (s, {field, value}) => {
171
+ const rows = getField(s.values, field) || []
172
+ return edit(s, setField(s.values, field, [...rows, {id: rows.reduce((m: number, r: any) => Math.max(m, +r.id || 0), 0) + 1, ...value}]))
173
+ },
174
+ REMOVE: (s, {field, id}) => {
175
+ const p = field + '.' + id
176
+ return edit(s, setField(s.values, field, getField(s.values, field).filter((r: any) => r.id + '' != id)),
177
+ {touched: drop(s.touched, p), server: drop(s.server, p)})
178
+ },
179
+ ERRORS: (s, d) => {
180
+ const e = replyErrors(d, s.values)
181
+ return {s: {...s, server: e, submitting: false, queued: false}, focus: focusInvalid(e, sel)}
182
+ },
183
+ DONE: (s) => ({s: done(s)}),
184
+ RESET: (s, d) => fresh(d && typeof d == 'object' ? d : s.initial),
185
+ // G-375: the first validation, when the host starts (form() doesn't validate at module load).
186
+ // G-575: a slice kept in parent state from a host that unmounted mid-submit starts unstuck.
187
+ // D239: with resetOnShow (also each time the form element appears again), or a `values`
188
+ // function not called yet, the slice starts over from the start values (h: the host state)
189
+ VALIDATE: (s, _, _k, h) => again || fn && s.values === values ? fresh(fn ? vo(h) : vo)
190
+ : edit({...s, submitting: false, queued: false, pending: {}}, s.values),
191
+ }
192
+ // a check's reply (ok and error: an error reply carries an Error and passes)
193
+ for (const f of checked) steps['CHECKED_' + f] = (s, d, k) => {
194
+ if (!(f in s.pending)) return
195
+ const n = {...s, pending: drop(s.pending, f), remote: {...s.remote, [f]: d?.error instanceof Error ? '' : checks[f].error?.(d) || ''}}
196
+ return s.queued ? attempt(n, k) : {s: n}
197
+ }
198
+
199
+ const model: any = {}
200
+ for (const a in steps) {
201
+ let last: any[] = []
202
+ // (HOST, with the host state h, always computes: one slice object starts every app's host)
203
+ const out = (s: any, d: any, k: string, h?: any) => !h && last[0] === s && last[1] === d ? last[2] : (last = [s, d, steps[a](s, d, k, h)])[2]
204
+ model[a] = {
205
+ // VALIDATE: on the host's state (HOST), for a `values` function; the slice's own steps on it
206
+ ...a == 'VALIDATE' ? {HOST: (h: any, d: any, _n: any, _p: any, _o: any, k: string) => { const x = out(h[k], d, k, h); return x ? {...h, [k]: x.s} : h }}
207
+ : {STATE: (s: any, d: any, _n: any, _p: any, _o: any, k: string) => out(s, d, k)?.s || ABORT},
208
+ EFFECT: (s: any, d: any, next: any, _p: any, _o: any, k: string) => {
209
+ const x = out(s, d, k), w = x?.wait
210
+ if (x?.send) {
211
+ const r = v(x.s.values).value
212
+ dev(233, s.values, r)
213
+ // G-577: a submit named after one of the form's own actions goes to that one
214
+ submit in steps && dev(234, submit, k, b)
215
+ next(submit, r, 0)
216
+ }
217
+ w && Promise.resolve(v(w.values)).then(() => next('RESULT', w, 0))
218
+ },
219
+ ELEMENT: (s: any, d: any, _n: any, _p: any, _o: any, k: string) => out(s, d, k)?.focus || ABORT,
220
+ ...(checked.length && {[http]: (s: any, d: any, _n: any, _p: any, _o: any, k: string) => out(s, d, k)?.req || ABORT}),
221
+ }
222
+ }
223
+
224
+ const b = defineBehavior({
225
+ form: schema,
226
+ // validating until VALIDATE (sync) or its RESULT (async): not valid yet (G-375, not validated)
227
+ initialState: {...base(values), validating: true},
228
+ intent: ({DOM}: any) => {
229
+ const f = DOM.select(sel)
230
+ let on = 0
231
+ return {
232
+ // D239: resetOnShow also starts over each time the form element appears again (a
233
+ // Switchable page shown again: hidden pages stay alive; a form rendered again)
234
+ VALIDATE: again ? xs.merge(xs.of(0), f.elements().filter((e: any) => { const r = e.length && !on; on = e.length; return r })) : xs.of(0),
235
+ // G-376: a checkbox (native, or a hyphenated tag with a boolean `checked`: a
236
+ // form-associated custom checkbox / switch) gives `checked` and its value as `item`; a
237
+ // <select multiple> the selected values; type=file is left alone; others give `value`
238
+ CHANGE: f.events('input').filter(({target: t}: any) => t.type != 'file').map(({target: t}: any) =>
239
+ t.type == 'checkbox' || /-/.test(t.tagName) && typeof t.checked == 'boolean' ? {name: t.name, value: t.checked, item: t.value}
240
+ : {name: t.name, value: t.type == 'select-multiple' ? [...t.selectedOptions].map((o: any) => o.value) : t.value}),
241
+ BLUR: f.events('focusout').map((e: any) => e.target.name),
242
+ SUBMIT: f.events('submit', {preventDefault: true}),
243
+ }
244
+ },
245
+ model,
246
+ calculated: {
247
+ fields: (s: any) => {
248
+ const out: any = {}
249
+ for (const n of fieldNames(s.values)) {
250
+ const value = getField(s.values, n)
251
+ const error = s.server[n] || s.remote[n] || ((s.submitCount || show != 'submit' && s.touched[n]) && s.errors[n]) || ''
252
+ out[n] = {name: n, value, error, invalid: !!error, touched: !!s.touched[n], dirty: value !== getField(s.initial, n), pending: n in s.pending}
253
+ }
254
+ return out
255
+ },
256
+ // G-382: an async schema re-validating keeps the last answer's validity (no flicker)
257
+ valid: (s: any) => s.validated && !keys(s.errors).length && !keys(s.remote).length,
258
+ dirty: (s: any) => JSON.stringify(s.values) != JSON.stringify(s.initial),
259
+ error: (s: any) => s.server[''] || (s.submitCount && s.errors['']) || '',
260
+ },
261
+ })({...o, values}), merge = b.merge
262
+ b.merge = (c: any, k: string) => {
263
+ const e = c.model?.[submit]
264
+ req = !!e && typeof e == 'object' && http in e
265
+ merge(c, k)
266
+ }
267
+ return b
268
+ }
@@ -0,0 +1,139 @@
1
+ /*
2
+ * PLAN-5 F-1: form helpers. The `form` behavior (./form.ts) is built on them, and they are
3
+ * exported as the escape hatch for a form written with plain model actions (D193, shape B).
4
+ * Pure functions over a plain `values` object, for model reducers; 0 B core.
5
+ *
6
+ * Field names: a field's `name=` attribute is its path in `values`, dot-separated. A segment
7
+ * inside an array of objects is the row's `id` (not its index), so touched/errors follow a row
8
+ * when another row is removed: `addresses.7.street` is `values.addresses.find(r => r.id == 7).street`.
9
+ * An array of non-objects (or rows without an id) uses the index; a row with an id is named by
10
+ * its id only (G-380: `addresses.0.city` is no field when the rows have ids).
11
+ *
12
+ * Validation: any Standard Schema (`schema['~standard'].validate`: zod, valibot, arktype, a
13
+ * hand-written object), no dependency. A sync schema validates inside reducers; an async one
14
+ * (validate returns a Promise) returns a Promise here, for an EFFECT.
15
+ */
16
+ import type {SchemaIssue} from './standardSchema'
17
+ import {focusWithin} from './focusWithin'
18
+ import {ABORT} from '../shared'
19
+
20
+ export type FieldErrors = Record<string, string>
21
+ export type FormCheck = {errors: FieldErrors; value: any}
22
+
23
+ const isRow = (r: any) => r?.id != null
24
+ // the key of segment `s` in `v`: an array's row with that id, else the index unless the element
25
+ // there is a row (G-380: a row is named by its id only; -1, no element), an object's key
26
+ const key = (v: any, s: string): any => {
27
+ if (!Array.isArray(v)) return s
28
+ const i = v.findIndex(r => isRow(r) && r.id + '' == s)
29
+ return i < 0 && !isRow(v[+s]) ? +s : i
30
+ }
31
+
32
+ /** the field name (id-based path) of a schema issue path (index-based) */
33
+ export const fieldName = (values: any, path: ReadonlyArray<any> = []): string => {
34
+ let v = values
35
+ return path.map(p => {
36
+ p = p?.key ?? p
37
+ const r = Array.isArray(v) && isRow(v[p]) ? v[p].id : p
38
+ v = v?.[p]
39
+ return r
40
+ }).join('.')
41
+ }
42
+
43
+ /** the value at a field name (undefined when there is none) */
44
+ export const getField = (values: any, name: string): any =>
45
+ name.split('.').reduce((v, s) => v?.[key(v, s)], values)
46
+
47
+ /** whether `name` is a field of `values`: its path exists (G-379: also when the value is undefined) */
48
+ export const hasField = (values: any, name: string): boolean => {
49
+ let v = values
50
+ for (const s of name.split('.')) {
51
+ const k = v && typeof v == 'object' ? key(v, s) : -1
52
+ if (!(k in Object(v))) return false
53
+ v = v[k]
54
+ }
55
+ return true
56
+ }
57
+
58
+ /** `values` with the field at `name` set to `value` (immutable; an unknown row is left alone) */
59
+ export const setField = (values: any, name: string, value: any): any => {
60
+ const [s, ...rest] = name.split('.'), k = key(values, s), a = Array.isArray(values)
61
+ const x = rest.length ? setField(values?.[k] ?? {}, rest.join('.'), value) : value
62
+ if (x === values?.[k] || a && !(k in values)) return values
63
+ const c = a ? values.slice() : {...values}
64
+ c[k] = x
65
+ return c
66
+ }
67
+
68
+ /**
69
+ * every field name of `values`, in object order: each leaf, and each array itself (for an
70
+ * array-level error such as "at least one address"); a row's `id` is not a field
71
+ */
72
+ export const fieldNames = (v: any, at = '', row?: boolean): string[] => {
73
+ if (!v || typeof v != 'object') return at ? [at] : []
74
+ const a = Array.isArray(v), out = a ? [at] : []
75
+ for (const k in v) {
76
+ const x = (v as any)[k]
77
+ if (!row || k != 'id') out.push(...fieldNames(x, (at && at + '.') + (a && isRow(x) ? x.id : k), a))
78
+ }
79
+ return out
80
+ }
81
+
82
+ const errorsOf = (values: any, issues: ReadonlyArray<SchemaIssue> = []): FieldErrors => {
83
+ const e: FieldErrors = {}
84
+ for (const i of issues) {
85
+ const n = fieldName(values, i.path)
86
+ n in e || (e[n] = i.message)
87
+ }
88
+ return e
89
+ }
90
+
91
+ /**
92
+ * Validates `values` with a Standard Schema: `{ errors, value }` (errors by field name, '' for
93
+ * a form-level issue, the first message per field; `value` is the schema's output, transformed,
94
+ * when there are none). A Promise of it when the schema is async. `schema` must be a Standard
95
+ * Schema object (the `form` behavior reports SYG231 in dev).
96
+ */
97
+ export const checkForm = (schema: any, values: any): FormCheck | Promise<FormCheck> => {
98
+ const r = schema['~standard'].validate(values)
99
+ const done = (r: any): FormCheck => ({errors: errorsOf(values, r.issues), value: r.issues ? undefined : r.value})
100
+ return r.then ? r.then(done) : done(r)
101
+ }
102
+
103
+ /** the errors only, by field name ({} when valid); a Promise of them for an async schema */
104
+ export const formErrors = (schema: any, values: any): any => {
105
+ const r: any = checkForm(schema, values)
106
+ return r.then ? r.then((x: FormCheck) => x.errors) : r.errors
107
+ }
108
+
109
+ /**
110
+ * Server errors as field errors: an error reply (a driver's `{ error, status?, body?, request }`
111
+ * or `{ status, body }`; the body `{ errors }` or the map itself), a map `{ name: message |
112
+ * messages }`, or a list of Standard-Schema-like issues `[{ path, message }]` (index paths
113
+ * become field names with `values`). With `values`, a message for a name that isn't one of its
114
+ * fields (`message`, `error`) is the form-level message (''); a reply without any becomes
115
+ * 'Request failed (status)' under ''.
116
+ */
117
+ export const replyErrors = (reply: any, values?: any): FieldErrors => {
118
+ let e = reply && ('request' in reply || 'body' in reply && 'status' in reply) ? reply.body : reply
119
+ e = e?.errors ?? e
120
+ const out: FieldErrors = Array.isArray(e) ? errorsOf(values, e) : {}
121
+ if (e && typeof e == 'object' && !Array.isArray(e)) for (const k in e) {
122
+ const m = [].concat(e[k])[0] + ''
123
+ k && values && !hasField(values, k) ? out[''] ||= m : out[k] = m
124
+ }
125
+ return Object.keys(out).length ? out : {'': 'Request failed' + (reply?.status ? ` (${reply.status})` : '')}
126
+ }
127
+
128
+ /**
129
+ * An ELEMENT command that focuses the first field, in DOM order, whose `name` is one of `names`
130
+ * (a list, or an errors map: its names with a message; '' is skipped), anywhere in the sender's view, fields
131
+ * of child components and Collection items included (`focusWithin`, D194). With `within` (a
132
+ * selector: the form element's), only fields inside a matching element (G-373: a second form,
133
+ * or a child component's field of the same name outside the form, isn't focused). ABORT when
134
+ * there is nothing to focus: `ELEMENT: (state) => focusInvalid(state.errors, '.signup')`.
135
+ */
136
+ export const focusInvalid = (names: string[] | FieldErrors, within?: string): any => {
137
+ const list = (Array.isArray(names) ? names : Object.keys(names).filter(n => names[n])).filter(n => n)
138
+ return list.length ? {focus: focusWithin(list.map(n => `${within ? within + ' ' : ''}[name=${JSON.stringify(n)}]`).join())} : ABORT
139
+ }
@@ -0,0 +1,106 @@
1
+ import {Stream} from 'xstream';
2
+ import {senderOf, makeReplies} from './replies';
3
+
4
+ /*
5
+ * makeHeadDriver({ titleTemplate?, document? }) (PLAN-3 §1.7, X-1). Docs: src/index.d.ts and
6
+ * docs guide/head.md. Summary:
7
+ *
8
+ * - Values `{ title?, meta?: { [name | property]: content | null }, link?: [{ rel, href, key?, ... }] }`
9
+ * come from a component's `head` static (`App.head = state => ({ title })`, sent as
10
+ * `{ head }`, `__sygnalStatic: 'head'`) or from a model entry (`HEAD: (s, d) => ({ title })`).
11
+ * Each component instance has one entry, replaced by its next value; a falsy value or the
12
+ * instance's dispose removes it.
13
+ * - Merge, in mount order (first declaration; a removed entry that comes back goes last): a
14
+ * later `title` wins, `meta` keys merge (null removes a key), `link`s merge by `key`, else by
15
+ * `rel` for canonical, else by `rel` + `href`. Meta keys with an `og:`/`article:`/... prefix
16
+ * use `property`, the others `name`.
17
+ * - DOM: `document.title` (the page's original title when no entry sets one); meta and link
18
+ * tags it creates carry `data-sygnal-head="<key>"`. An existing unmarked `<meta>` with the same
19
+ * name is updated and restored when no entry sets it. Marked tags from SSR (`renderHead()`)
20
+ * are adopted, or removed once no entry sets them.
21
+ * - SSR: `renderToString(App, { head: list })` pushes each rendered component's `head` static
22
+ * into `list`; `renderHead(list, { titleTemplate })` gives the tags. No document: no-op.
23
+ */
24
+
25
+ const PROPERTY = /^(og|fb|article|book|profile|music|video|al):/;
26
+ const esc = (s: any) => String(s).replace(/[&<>"]/g, c => `&#${c.charCodeAt(0)};`);
27
+ const attrOf = (k: string) => PROPERTY.test(k) ? 'property' : 'name';
28
+
29
+ /** merged `{ title, meta: [[key, content]], link: [[key, attrs]] }` of head values, in order */
30
+ export function mergeHead(list: any[], titleTemplate?: string) {
31
+ let title: any;
32
+ const meta: Record<string, any> = {}, link: Record<string, any> = {};
33
+ for (const h of list) {
34
+ if (!h || typeof h != 'object') continue;
35
+ if (h.title != null) title = h.title;
36
+ Object.assign(meta, h.meta);
37
+ for (const l of h.link || []) if (l) link['l:' + (l.key ?? (l.rel == 'canonical' ? l.rel : l.rel + ' ' + l.href))] = l;
38
+ }
39
+ return {
40
+ title: title != null && titleTemplate ? titleTemplate.replace('%s', title) : title,
41
+ meta: Object.keys(meta).filter(k => meta[k] != null).map(k => ['m:' + k, k, meta[k]]),
42
+ link: Object.entries(link),
43
+ };
44
+ }
45
+
46
+ /** the `<title>`, `<meta>` and `<link>` tags of head values (SSR) */
47
+ export function renderHead(list: any[], options: {titleTemplate?: string} = {}): string {
48
+ const m = mergeHead(list, options.titleTemplate);
49
+ const attrs = (o: any) => Object.keys(o).filter(k => k != 'key' && o[k] != null && o[k] !== false).map(k => ` ${k}="${esc(o[k])}"`).join('');
50
+ return (m.title != null ? `<title>${esc(m.title)}</title>` : '') +
51
+ m.meta.map(([key, k, c]) => `<meta${attrs({[attrOf(k)]: k, content: c})} data-sygnal-head="${esc(key)}">`).join('') +
52
+ m.link.map(([key, l]) => `<link${attrs(l)} data-sygnal-head="${esc(key)}">`).join('');
53
+ }
54
+
55
+ export function makeHeadDriver(options: {titleTemplate?: string, document?: any} = {}) {
56
+ return (sink$: Stream<any>) => {
57
+ const d: any = options.document || (typeof document != 'undefined' ? document : null);
58
+ const entries = new Map<any, any>();
59
+ // key → element this driver manages; adopted: element → its original content
60
+ const made = new Map<string, any>(), adopted = new Map<any, any>();
61
+ const title0 = d?.title;
62
+ d?.querySelectorAll('[data-sygnal-head]').forEach((el: any) => made.set(el.getAttribute('data-sygnal-head'), el));
63
+
64
+ const set = (el: any, o: any) => { for (const k in o) if (k != 'key') o[k] == null || o[k] === false ? el.removeAttribute(k) : el.setAttribute(k, o[k]); };
65
+ const apply = () => {
66
+ if (!d) return;
67
+ const m = mergeHead([...entries.values()], options.titleTemplate), want = new Set<string>();
68
+ d.title = m.title ?? title0;
69
+ const put = (key: string, tag: string, o: any, find?: string) => {
70
+ want.add(key);
71
+ let el = made.get(key);
72
+ if (!el) {
73
+ el = find && d.head.querySelector(find);
74
+ if (el) adopted.set(el, el.getAttribute('content'));
75
+ else (el = d.createElement(tag)).setAttribute('data-sygnal-head', key), d.head.appendChild(el);
76
+ made.set(key, el);
77
+ }
78
+ set(el, o);
79
+ };
80
+ for (const [key, k, c] of m.meta) put(key, 'meta', {[attrOf(k)]: k, content: c}, `meta[${attrOf(k)}="${k}"]`);
81
+ for (const [key, l] of m.link) put(key, 'link', l);
82
+ made.forEach((el, key) => {
83
+ if (want.has(key)) return;
84
+ made.delete(key);
85
+ if (adopted.has(el)) set(el, {content: adopted.get(el)}), adopted.delete(el);
86
+ else el.remove();
87
+ });
88
+ };
89
+
90
+ const {replies} = makeReplies(s => { if (entries.delete(s)) apply(); });
91
+
92
+ sink$.addListener({
93
+ next: (v: any) => {
94
+ if (!v || typeof v != 'object') return;
95
+ const s = senderOf(v), h = 'head' in v ? v.head : v;
96
+ if (h && typeof h == 'object') entries.set(s, h);
97
+ else entries.delete(s);
98
+ apply();
99
+ },
100
+ error: (e: any) => console.error('[Sygnal] head', e),
101
+ });
102
+
103
+ const dispose = () => { entries.clear(); apply(); };
104
+ return {...replies, __sygnalStatic: 'head', dispose};
105
+ };
106
+ }
package/src/extra/hmr.ts CHANGED
@@ -10,7 +10,6 @@ interface HotModule {
10
10
  interface SygnalApp {
11
11
  hmr: (next: any, state?: any) => void;
12
12
  dispose?: () => void;
13
- sources?: {STATE?: {stream?: {_v?: any}}};
14
13
  }
15
14
 
16
15
  function normalizeModule(maybeModule: any): ModuleWithDefault | null {
@@ -44,8 +43,8 @@ export default function enableHMR(
44
43
  next = normalizeModule(loaded);
45
44
  }
46
45
  if (next) {
47
- const explicitState = app?.sources?.STATE?.stream?._v;
48
- app.hmr(next, explicitState);
46
+ // (hmr() keeps the app's current state, read through the runtime API)
47
+ app.hmr(next);
49
48
  }
50
49
  } finally {
51
50
  applying = false;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * PLAN-4.5 G-275: marks an initial state that a wrapper built from its caller's values (an
3
+ * element's host props, Vike's pageContext.data or hydrated state, renderComponent's
4
+ * initialState option). The dev statics freeze (D152, checks/statics.ts) leaves a marked object
5
+ * and everything in it alone: the caller may still change those values. A non-enumerable symbol
6
+ * key: spreads, JSON and Object.keys don't see it.
7
+ */
8
+ export const OWNED = Symbol.for('sygnal.owned')
9
+
10
+ export const owned = <T>(v: T): T => {
11
+ // G-289: an object that refuses the key (a Proxy) is returned as is
12
+ try { if (v && typeof v == 'object' && Object.isExtensible(v)) Object.defineProperty(v, OWNED, {value: 1}) } catch {}
13
+ return v
14
+ }
15
+
16
+ /**
17
+ * G-289: owned() of a shallow copy of a plain object or array (the caller's own object stays
18
+ * unmarked: it may be a component's static, which the freeze must still cover, or sealed, which
19
+ * can't be marked); anything else as owned()
20
+ */
21
+ export const ownedCopy = <T>(v: T): T => {
22
+ const p = v && typeof v == 'object' ? Object.getPrototypeOf(v) : 0
23
+ return owned(Array.isArray(v) ? [...v] as any : p == Object.prototype || p === null ? {...v} : v)
24
+ }
@@ -0,0 +1,50 @@
1
+ /*
2
+ * PLAN-4 GS-1: the first-party `pager` behavior.
3
+ *
4
+ * const { Older, Newer } = controls({ Older: 'button', Newer: 'button' })
5
+ * List.uses = { pager: pager({ pageSize: 10, next: Newer, prev: Older }) }
6
+ *
7
+ * Slice: { page, pageSize, total } plus the calculated offset, pages, hasPrev, hasNext.
8
+ * Actions: pager.NEXT, pager.PREV (ABORT at the bounds), pager.GOTO (page number, clamped),
9
+ * pager.SET_TOTAL (item count; keeps the page in range). `total` is optional: without it NEXT
10
+ * has no upper bound and `pages` is null. A host reducer may also write `state.pager.total`;
11
+ * the calculated fields follow.
12
+ */
13
+ import { defineBehavior } from './behaviors'
14
+ import { ABORT } from '../shared'
15
+
16
+ const pagesOf = (p: any) => p.total == null ? null : Math.max(1, Math.ceil(p.total / p.pageSize))
17
+ const to = (p: any, page: number) => {
18
+ const n = pagesOf(p)
19
+ page = Math.max(0, Math.min(page | 0, n == null ? page : n - 1))
20
+ return page === p.page ? ABORT : {...p, page}
21
+ }
22
+
23
+ /**
24
+ * A page cursor over a list (GS-1 behavior). Options: `pageSize` (20), `page` (0), `total`
25
+ * (null: unknown), and the controls `next` / `prev` whose clicks move the page. Use it as
26
+ * `uses = { pager: pager({ pageSize: 10, next: Newer, prev: Older }) }`, then render
27
+ * `items.slice(state.pager.offset, state.pager.offset + state.pager.pageSize)`.
28
+ */
29
+ export const pager = /*#__PURE__*/ defineBehavior({
30
+ initialState: {page: 0, pageSize: 20, total: null},
31
+ intent: ({DOM}: any, {next, prev}: any) => ({
32
+ ...(next && {NEXT: DOM.click(next)}),
33
+ ...(prev && {PREV: DOM.click(prev)}),
34
+ }),
35
+ model: {
36
+ NEXT: (p: any) => to(p, p.page + 1),
37
+ PREV: (p: any) => to(p, p.page - 1),
38
+ GOTO: (p: any, page: number) => to(p, page),
39
+ SET_TOTAL: (p: any, total: number) => {
40
+ const q = {...p, total}, r = to(q, p.page)
41
+ return r === ABORT ? q : r
42
+ },
43
+ },
44
+ calculated: {
45
+ offset: (p: any) => p.page * p.pageSize,
46
+ pages: pagesOf,
47
+ hasPrev: (p: any) => p.page > 0,
48
+ hasNext: (p: any) => p.total == null || p.page + 1 < pagesOf(p)!,
49
+ },
50
+ })