sygnal 5.4.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (316) hide show
  1. package/CHANGELOG.md +646 -0
  2. package/README.md +24 -9
  3. package/dist/astro/client.cjs.js +16 -9
  4. package/dist/astro/client.cjs.js.map +1 -1
  5. package/dist/astro/client.mjs +16 -9
  6. package/dist/astro/client.mjs.map +1 -1
  7. package/dist/astro/index.cjs.js +362 -91
  8. package/dist/astro/index.cjs.js.map +1 -1
  9. package/dist/astro/index.mjs +362 -92
  10. package/dist/astro/index.mjs.map +1 -1
  11. package/dist/astro/server.cjs.js +3396 -165
  12. package/dist/astro/server.cjs.js.map +1 -1
  13. package/dist/astro/server.mjs +3396 -165
  14. package/dist/astro/server.mjs.map +1 -1
  15. package/dist/devtools.cjs.js +1431 -0
  16. package/dist/devtools.cjs.js.map +1 -0
  17. package/dist/devtools.esm.js +1419 -0
  18. package/dist/devtools.esm.js.map +1 -0
  19. package/dist/diagnostics.cjs.js +3032 -296
  20. package/dist/diagnostics.cjs.js.map +1 -1
  21. package/dist/diagnostics.esm.js +3032 -296
  22. package/dist/diagnostics.esm.js.map +1 -1
  23. package/dist/element.cjs.js +230 -0
  24. package/dist/element.cjs.js.map +1 -0
  25. package/dist/element.esm.js +228 -0
  26. package/dist/element.esm.js.map +1 -0
  27. package/dist/guide/accessibility.md +257 -0
  28. package/dist/guide/adapters.md +221 -0
  29. package/dist/guide/behaviors.md +312 -0
  30. package/dist/guide/browser-sources.md +203 -0
  31. package/dist/guide/drag-and-drop.md +342 -0
  32. package/dist/guide/element-commands.md +306 -0
  33. package/dist/guide/error-boundaries.md +169 -0
  34. package/dist/guide/forms-reference.md +381 -0
  35. package/dist/guide/forms.md +250 -0
  36. package/dist/guide/http.md +299 -0
  37. package/dist/guide/inputs.md +173 -0
  38. package/dist/guide/persistence.md +245 -0
  39. package/dist/guide/recipes/carousel.md +155 -0
  40. package/dist/guide/recipes/charts.md +177 -0
  41. package/dist/guide/recipes/code-editor.md +135 -0
  42. package/dist/guide/recipes/data-grid.md +155 -0
  43. package/dist/guide/recipes/data-table.md +196 -0
  44. package/dist/guide/recipes/i18n.md +222 -0
  45. package/dist/guide/recipes/icons.md +115 -0
  46. package/dist/guide/recipes/overview.md +33 -0
  47. package/dist/guide/recipes/rich-text.md +154 -0
  48. package/dist/guide/resources.md +471 -0
  49. package/dist/guide/ssr.md +255 -0
  50. package/dist/guide/timers.md +178 -0
  51. package/dist/guide/ui/accordion.md +93 -0
  52. package/dist/guide/ui/combobox.md +144 -0
  53. package/dist/guide/ui/dialog.md +124 -0
  54. package/dist/guide/ui/disclosure.md +60 -0
  55. package/dist/guide/ui/menu.md +125 -0
  56. package/dist/guide/ui/overview.md +166 -0
  57. package/dist/guide/ui/popover.md +101 -0
  58. package/dist/guide/ui/select.md +114 -0
  59. package/dist/guide/ui/tabs.md +161 -0
  60. package/dist/guide/ui/toaster.md +152 -0
  61. package/dist/guide/ui/tooltip.md +103 -0
  62. package/dist/guide/undo.md +178 -0
  63. package/dist/guide/virtual-collections.md +183 -0
  64. package/dist/guide/web-components.md +179 -0
  65. package/dist/guide/widgets.md +217 -0
  66. package/dist/index.cjs.js +13178 -5769
  67. package/dist/index.cjs.js.map +1 -1
  68. package/dist/index.d.ts +2908 -276
  69. package/dist/index.esm.js +13149 -5779
  70. package/dist/index.esm.js.map +1 -1
  71. package/dist/jsx-dev-runtime.cjs.js +26 -334
  72. package/dist/jsx-dev-runtime.cjs.js.map +1 -1
  73. package/dist/jsx-dev-runtime.esm.js +19 -327
  74. package/dist/jsx-dev-runtime.esm.js.map +1 -1
  75. package/dist/jsx-runtime.cjs.js +26 -334
  76. package/dist/jsx-runtime.cjs.js.map +1 -1
  77. package/dist/jsx-runtime.esm.js +19 -327
  78. package/dist/jsx-runtime.esm.js.map +1 -1
  79. package/dist/jsx.cjs.js +10 -314
  80. package/dist/jsx.cjs.js.map +1 -1
  81. package/dist/jsx.esm.js +8 -315
  82. package/dist/jsx.esm.js.map +1 -1
  83. package/dist/react.cjs.js +117 -0
  84. package/dist/react.cjs.js.map +1 -0
  85. package/dist/react.esm.js +115 -0
  86. package/dist/react.esm.js.map +1 -0
  87. package/dist/shims/globalthis.cjs +20 -0
  88. package/dist/sygnal.min.js +1 -1
  89. package/dist/sygnal.min.js.map +1 -1
  90. package/dist/ui-combobox.cjs.js +169 -0
  91. package/dist/ui-combobox.cjs.js.map +1 -0
  92. package/dist/ui-combobox.esm.js +148 -0
  93. package/dist/ui-combobox.esm.js.map +1 -0
  94. package/dist/ui-menu.cjs.js +87 -0
  95. package/dist/ui-menu.cjs.js.map +1 -0
  96. package/dist/ui-menu.esm.js +66 -0
  97. package/dist/ui-menu.esm.js.map +1 -0
  98. package/dist/ui-select.cjs.js +104 -0
  99. package/dist/ui-select.cjs.js.map +1 -0
  100. package/dist/ui-select.esm.js +83 -0
  101. package/dist/ui-select.esm.js.map +1 -0
  102. package/dist/ui.cjs.js +738 -0
  103. package/dist/ui.cjs.js.map +1 -0
  104. package/dist/ui.esm.js +727 -0
  105. package/dist/ui.esm.js.map +1 -0
  106. package/dist/vike/ClientOnly.cjs.js +4 -14
  107. package/dist/vike/ClientOnly.cjs.js.map +1 -1
  108. package/dist/vike/ClientOnly.mjs +4 -14
  109. package/dist/vike/ClientOnly.mjs.map +1 -1
  110. package/dist/vike/{+config.js → config/+config.js} +9 -1
  111. package/dist/vike/config/+config.js.map +1 -0
  112. package/dist/vike/config/package.json +6 -0
  113. package/dist/vike/onRenderClient.cjs.js +165 -95
  114. package/dist/vike/onRenderClient.cjs.js.map +1 -1
  115. package/dist/vike/onRenderClient.mjs +165 -95
  116. package/dist/vike/onRenderClient.mjs.map +1 -1
  117. package/dist/vike/onRenderHtml.cjs.js +59 -24
  118. package/dist/vike/onRenderHtml.cjs.js.map +1 -1
  119. package/dist/vike/onRenderHtml.mjs +60 -25
  120. package/dist/vike/onRenderHtml.mjs.map +1 -1
  121. package/dist/vite/plugin.cjs.js +323 -88
  122. package/dist/vite/plugin.cjs.js.map +1 -1
  123. package/dist/vite/plugin.mjs +323 -89
  124. package/dist/vite/plugin.mjs.map +1 -1
  125. package/dist/zag.cjs.js +408 -0
  126. package/dist/zag.cjs.js.map +1 -0
  127. package/dist/zag.esm.js +405 -0
  128. package/dist/zag.esm.js.map +1 -0
  129. package/llms.txt +166 -103
  130. package/package.json +98 -14
  131. package/src/astro/client.ts +27 -16
  132. package/src/astro/index.d.ts +24 -0
  133. package/src/astro/index.ts +69 -3
  134. package/src/astro/server.ts +8 -2
  135. package/src/collection.ts +6 -93
  136. package/src/core/actions.ts +134 -0
  137. package/src/core/cell.ts +202 -0
  138. package/src/core/debug.ts +23 -0
  139. package/src/core/define.ts +242 -0
  140. package/src/core/hooks.ts +314 -0
  141. package/src/core/hosts/collection.ts +325 -0
  142. package/src/core/hosts/switchable.ts +146 -0
  143. package/src/core/instance.ts +592 -0
  144. package/src/core/markers/clientonly.ts +13 -0
  145. package/src/core/markers/lazy.ts +40 -0
  146. package/src/core/markers/portal.ts +96 -0
  147. package/src/core/markers/suspense.ts +41 -0
  148. package/src/core/markers/transition.ts +70 -0
  149. package/src/core/registry.ts +25 -0
  150. package/src/core/runtime.ts +573 -0
  151. package/src/core/statics.ts +104 -0
  152. package/src/core/teardown.ts +60 -0
  153. package/src/core/view.ts +37 -0
  154. package/src/cycle/dom/DocumentDOMSource.ts +13 -8
  155. package/src/cycle/dom/ElementFinder.ts +7 -9
  156. package/src/cycle/dom/EventDelegator.ts +174 -223
  157. package/src/cycle/dom/IsolateModule.ts +65 -35
  158. package/src/cycle/dom/MainDOMSource.ts +9 -2
  159. package/src/cycle/dom/PriorityQueue.ts +4 -2
  160. package/src/cycle/dom/SymbolTree.ts +18 -47
  161. package/src/cycle/dom/classNameModule.ts +9 -7
  162. package/src/cycle/dom/controlledInputModule.ts +23 -6
  163. package/src/cycle/dom/enrichEventStream.ts +25 -47
  164. package/src/cycle/dom/fragment.ts +7 -0
  165. package/src/cycle/dom/isolate.ts +30 -27
  166. package/src/cycle/dom/makeDOMDriver.ts +115 -56
  167. package/src/cycle/dom/mockDOMSource.ts +21 -1
  168. package/src/cycle/dom/modules.ts +16 -3
  169. package/src/cycle/dom/propsModule.ts +65 -0
  170. package/src/cycle/dom/selectModule.ts +22 -17
  171. package/src/cycle/dom/snabbdom.ts +1 -6
  172. package/src/cycle/dom/thunk.ts +3 -0
  173. package/src/cycle/dom/utils.ts +98 -0
  174. package/src/cycle/dom/viewTransition.ts +43 -0
  175. package/src/cycle/state/StateSource.ts +19 -3
  176. package/src/cycle/state/objIsEqual.ts +50 -0
  177. package/src/cycle/state/types.ts +0 -10
  178. package/src/defineComponent.ts +34 -0
  179. package/src/devtools.d.ts +151 -0
  180. package/src/devtools.ts +34 -0
  181. package/src/element.d.ts +59 -0
  182. package/src/element.ts +235 -0
  183. package/src/extra/backoff.ts +9 -0
  184. package/src/extra/behaviors.ts +169 -0
  185. package/src/extra/browserSignals.ts +23 -0
  186. package/src/extra/browserSources.ts +326 -0
  187. package/src/extra/command.ts +3 -3
  188. package/src/extra/controls.ts +47 -0
  189. package/src/extra/copyAsTest.ts +286 -0
  190. package/src/extra/devtools.ts +101 -31
  191. package/src/extra/devtoolsActions.ts +379 -0
  192. package/src/extra/devtoolsHook.ts +9 -0
  193. package/src/extra/devtoolsNext.ts +83 -0
  194. package/src/extra/diagnostics/checks/actionLog.ts +108 -0
  195. package/src/extra/diagnostics/checks/behaviors.ts +49 -0
  196. package/src/extra/diagnostics/checks/browserSources.ts +96 -0
  197. package/src/extra/diagnostics/checks/collections.ts +1 -1
  198. package/src/extra/diagnostics/checks/controls.ts +148 -0
  199. package/src/extra/diagnostics/checks/dataset.ts +57 -0
  200. package/src/extra/diagnostics/checks/dom.ts +20 -11
  201. package/src/extra/diagnostics/checks/elementCommands.ts +175 -0
  202. package/src/extra/diagnostics/checks/events.ts +17 -2
  203. package/src/extra/diagnostics/checks/fetch.ts +135 -0
  204. package/src/extra/diagnostics/checks/forms.ts +163 -0
  205. package/src/extra/diagnostics/checks/index.ts +97 -4
  206. package/src/extra/diagnostics/checks/inspect.ts +140 -32
  207. package/src/extra/diagnostics/checks/next.ts +417 -0
  208. package/src/extra/diagnostics/checks/persist.ts +54 -0
  209. package/src/extra/diagnostics/checks/props.ts +10 -5
  210. package/src/extra/diagnostics/checks/public.d.ts +98 -7
  211. package/src/extra/diagnostics/checks/replies.ts +128 -0
  212. package/src/extra/diagnostics/checks/router.ts +100 -0
  213. package/src/extra/diagnostics/checks/rxjsHints.ts +1 -1
  214. package/src/extra/diagnostics/checks/shared.ts +86 -2
  215. package/src/extra/diagnostics/checks/shorthand.ts +115 -0
  216. package/src/extra/diagnostics/checks/sortable.ts +129 -0
  217. package/src/extra/diagnostics/checks/state.ts +100 -7
  218. package/src/extra/diagnostics/checks/statics.ts +48 -0
  219. package/src/extra/diagnostics/checks/strict.ts +10 -67
  220. package/src/extra/diagnostics/checks/timers.ts +80 -0
  221. package/src/extra/diagnostics/checks/viewTransitions.ts +47 -0
  222. package/src/extra/diagnostics/checks/virtual.ts +73 -0
  223. package/src/extra/diagnostics/checks/widgets.ts +109 -0
  224. package/src/extra/diagnostics/checks/wiring.ts +66 -10
  225. package/src/extra/diagnostics/codes.ts +268 -23
  226. package/src/extra/diagnostics/index.ts +34 -9
  227. package/src/extra/diagnostics/legacy.ts +17 -0
  228. package/src/extra/driverFactories.ts +83 -19
  229. package/src/extra/elementCommands.ts +53 -0
  230. package/src/extra/fetchDriver.ts +519 -0
  231. package/src/extra/focusWithin.ts +27 -0
  232. package/src/extra/form.ts +268 -0
  233. package/src/extra/formHelpers.ts +139 -0
  234. package/src/extra/head.ts +106 -0
  235. package/src/extra/hmr.ts +2 -3
  236. package/src/extra/owned.ts +24 -0
  237. package/src/extra/pager.ts +50 -0
  238. package/src/extra/persist.ts +133 -0
  239. package/src/extra/pwa.ts +1 -1
  240. package/src/extra/queryCache.ts +107 -0
  241. package/src/extra/reducers.ts +1 -1
  242. package/src/extra/reduxDevtools.ts +92 -0
  243. package/src/extra/replies.ts +54 -0
  244. package/src/extra/router.ts +323 -0
  245. package/src/extra/run.ts +90 -217
  246. package/src/extra/selection.ts +76 -0
  247. package/src/extra/socketDriver.ts +265 -0
  248. package/src/extra/sortable.ts +377 -0
  249. package/src/extra/ssr.ts +427 -160
  250. package/src/extra/standardSchema.ts +27 -0
  251. package/src/extra/testing.ts +2433 -175
  252. package/src/extra/timers.ts +95 -0
  253. package/src/extra/undo.ts +261 -0
  254. package/src/extra/viewTransitions.ts +38 -0
  255. package/src/extra/virtual.ts +513 -0
  256. package/src/extra/widget.ts +201 -0
  257. package/src/index.d.ts +2631 -117
  258. package/src/index.ts +25 -4
  259. package/src/jsx-runtime.ts +15 -1
  260. package/src/jsx.ts +2 -1
  261. package/src/lazy.ts +50 -7
  262. package/src/portal.ts +3 -1
  263. package/src/pragma/index.ts +256 -133
  264. package/src/react-peers.d.ts +5 -0
  265. package/src/react.d.ts +43 -0
  266. package/src/react.ts +110 -0
  267. package/src/shared.ts +48 -0
  268. package/src/slot.ts +1 -1
  269. package/src/suspense.ts +3 -1
  270. package/src/switchable.ts +6 -121
  271. package/src/transition.ts +3 -1
  272. package/src/ui/accordion.ts +64 -0
  273. package/src/ui/dialog.ts +129 -0
  274. package/src/ui/disclosure.ts +35 -0
  275. package/src/ui/popover.ts +45 -0
  276. package/src/ui/shared.ts +94 -0
  277. package/src/ui/tabs.ts +82 -0
  278. package/src/ui/toaster.ts +198 -0
  279. package/src/ui/tooltip.ts +70 -0
  280. package/src/ui/zag/combobox.ts +115 -0
  281. package/src/ui/zag/menu.ts +40 -0
  282. package/src/ui/zag/select.ts +54 -0
  283. package/src/ui/zag/shared.ts +43 -0
  284. package/src/ui-combobox.d.ts +20 -0
  285. package/src/ui-combobox.ts +6 -0
  286. package/src/ui-menu.d.ts +30 -0
  287. package/src/ui-menu.ts +6 -0
  288. package/src/ui-select.d.ts +17 -0
  289. package/src/ui-select.ts +6 -0
  290. package/src/ui-zag-types.d.ts +42 -0
  291. package/src/ui.d.ts +208 -0
  292. package/src/ui.ts +15 -0
  293. package/src/vike/+config.ts +9 -1
  294. package/src/vike/ClientOnly.ts +1 -1
  295. package/src/vike/onRenderClient.ts +144 -96
  296. package/src/vike/onRenderHtml.ts +69 -26
  297. package/src/vike/types.ts +7 -1
  298. package/src/vite/globalthis-shim.ts +18 -0
  299. package/src/vite/globalthis.ts +56 -0
  300. package/src/vite/plugin.d.ts +22 -3
  301. package/src/vite/plugin.ts +222 -24
  302. package/src/zag.d.ts +64 -0
  303. package/src/zag.ts +238 -0
  304. package/dist/vike/+config.cjs.js +0 -66
  305. package/dist/vike/+config.cjs.js.map +0 -1
  306. package/dist/vike/+config.js.map +0 -1
  307. package/src/component.ts +0 -2301
  308. package/src/cycle/isolate/index.ts +0 -196
  309. package/src/cycle/run/index.ts +0 -151
  310. package/src/cycle/run/internals.ts +0 -143
  311. package/src/cycle/state/Collection.ts +0 -184
  312. package/src/cycle/state/index.ts +0 -5
  313. package/src/cycle/state/pickCombine.ts +0 -201
  314. package/src/cycle/state/pickMerge.ts +0 -122
  315. package/src/cycle/state/withState.ts +0 -47
  316. package/src/pragma/fn.ts +0 -55
@@ -0,0 +1,592 @@
1
+ /**
2
+ * PLAN-4.6 core: a component instance (03-proposal §5).
3
+ *
4
+ * - Created synchronously: the state cell, the initial state written, onCreate, INITIALIZE
5
+ * queued, the intent subscribed (an emission on subscribe only queues: it is drained after the
6
+ * construction, or in the flush that created the instance). BOOTSTRAP is dispatched a
7
+ * microtask after the flush that rendered it first (D165).
8
+ * - Sources at the edge (lazy getters): every driver source isolated to the instance, DOM with
9
+ * the event shorthand, STATE (stream: identity, watch: deep, as today, D170), CHILD.select(Fn),
10
+ * props$ / children$ / dispose$ for sub-components.
11
+ * - render(): the cached vnode unless its state, props, children, a context key its view read
12
+ * (D168), or a child's vnode / READY changed. Children are keyed in a Map by `name::id|path`.
13
+ * - dispose(): DISPOSE, onDispose, dispose$, the EFFECT signal aborted, the subtree, then the
14
+ * streams (the scoped _remove swap: no xstream stop timers, D165/Q18).
15
+ */
16
+ import xs from '../extra/xstreamCompat'
17
+ import {StateSource} from '../cycle/state/StateSource'
18
+ import {uidPart} from '../shared'
19
+ import {warn, error as logError, fail} from '../extra/diagnostics/legacy'
20
+ import type {App} from './runtime'
21
+ import {tearDown, INST, SEED} from './teardown'
22
+ import {CoreDef, defOf, isObj} from './define'
23
+ import {Cell, CalcCell, calcCell, keyCell, lensCell, localCell} from './cell'
24
+ import {hosts, posts, pres, resolvers} from './registry'
25
+ import {handle} from './actions'
26
+ import {viewOf} from './view'
27
+ import {makeCommandSource} from '../extra/command'
28
+ import {attach, detach} from './statics'
29
+ import {dbg} from './debug'
30
+ import {sameData} from '../cycle/dom/utils'
31
+
32
+ const ERR_FIX = 'See the attached error'
33
+
34
+ // 4-I G-557: a fragment root (a Collection's): each of its top-level elements is marked.
35
+ // 4-K G-572: a text root (no element; an attribute would land on a Text node) gets `ready: false`
36
+ // (read by Suspense)
37
+ const notReady = (v: any): any => v?.sel ? {...v, data: {...v.data, attrs: {...v.data?.attrs, 'data-sygnal-ready': 'false'}}}
38
+ : v?.children ? {...v, children: v.children.map(notReady)} : v && {...v, ready: false}
39
+ const errorDiv = (name: string) => ({sel: 'div', data: {attrs: {'data-sygnal-error': name}}, children: [], text: undefined, elm: undefined, key: undefined})
40
+
41
+ /** <Slot name="x"> children -> slots.x; the rest -> slots.default and `children` */
42
+ function extractSlots(children: any[]): [any[], Record<string, any[]>] {
43
+ const slots: Record<string, any[]> = {}, rest: any[] = []
44
+ for (const c of children) {
45
+ // G-480: the <Slot> marker (`data.m`), not a plain <slot> element (a shadow-DOM component's)
46
+ if (c?.data?.m && c.sel === 'slot') {
47
+ const k = c.children
48
+ ;(slots[c.data?.props?.name || 'default'] ||= []).push(...(Array.isArray(k) ? k : k ? [k] : []))
49
+ } else rest.push(c)
50
+ }
51
+ if (rest.length) (slots.default ||= []).push(...rest)
52
+ return [slots.default || [], slots]
53
+ }
54
+
55
+ /** an instance's id among its parent's children: its `id` prop, else its path in the view */
56
+ const idOf = (n: any, path: string) => {
57
+ const id = n.data?.props?.id
58
+ let s: any
59
+ // (G-298: an id JSON can't encode, a BigInt, is used as its string)
60
+ if (id) try { s = JSON.stringify(id) } catch (_) { s = String(id) }
61
+ return `${n.sel}::${(s && s.replace(/"/g, '')) || path}`
62
+ }
63
+
64
+ /**
65
+ * a component vnode's props, without `resetState` (D174: read at creation, like `state`, never a
66
+ * prop of the child)
67
+ */
68
+ function propsOf(p: any) {
69
+ if (!p) return {}
70
+ if (!('resetState' in p)) return p
71
+ const {resetState, ...rest} = p
72
+ return rest
73
+ }
74
+
75
+ export function sameKids(a: any, b: any) {
76
+ if (a === b) return true
77
+ if (!a || !b || a.length !== b.length) return false
78
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false
79
+ return true
80
+ }
81
+ export function shallowEq(a: any, b: any) {
82
+ if (a === b) return true
83
+ if (!isObj(a) || !isObj(b)) return false
84
+ const ka = Object.keys(a)
85
+ if (ka.length !== Object.keys(b).length) return false
86
+ for (const k of ka) if (a[k] !== b[k]) return false
87
+ return true
88
+ }
89
+
90
+ /**
91
+ * P46-P: two pragma-plain trees (no component, form field or foreign vnode) that render the same:
92
+ * the same tags, keys, text and data (each bucket shallow-equal). A re-run view whose output is
93
+ * the same keeps its last vnode, so the patch skips that subtree by identity.
94
+ * G-349: a vnode with a `hook` is never the same (its update/postpatch run on every patch, a
95
+ * hoisted hook object too; refs and autoFocus chain their hooks there).
96
+ * G-353: at most SAME_MAX vnodes are compared; a larger tree is not the same (it patches, as
97
+ * before P46-P), so a big view whose last row changed doesn't walk the whole tree before the patch
98
+ */
99
+ const SAME_MAX = 1000
100
+ let left = 0
101
+ export const sameTree = (a: any, b: any): boolean => (left = SAME_MAX, same(a, b))
102
+ function same(a: any, b: any): boolean {
103
+ if (a === b) return true
104
+ if (--left < 0 || !a || !b || a.sel !== b.sel || a.key !== b.key || a.text !== b.text || b.data?.hook || !sameData(a.data, b.data)) return false
105
+ const x = a.children, y = b.children
106
+ if (x === y) return true
107
+ if (!x || !y || x.length !== y.length) return false
108
+ for (let i = 0; i < x.length; i++) if (!same(x[i], y[i])) return false
109
+ return true
110
+ }
111
+
112
+ const EMPTY_CTX: any = Object.freeze({})
113
+ /** a view read every key (spread, Object.keys): any context change re-renders it */
114
+ const ALL = '\u0000all'
115
+
116
+ let IDS = 0
117
+
118
+ /** a child that failed to instantiate: renders the owner's error fallback (SYG408) */
119
+ class Failed {
120
+ last: any; lr = true; ready = true; def: any = null; disposed = false; shown = true
121
+ constructor(public outv: any) {}
122
+ render() { return this.outv }
123
+ setProps() {}
124
+ dispose() {}
125
+ }
126
+
127
+ export class Inst {
128
+ readonly id = IDS++
129
+ disposed = false
130
+ /** debug logging for this instance (the DevTools toggle; core/debug.ts) */
131
+ debug = false
132
+ /** in dispose(), while dispose$ emits: its actions run at once */
133
+ dying = false
134
+ ready: boolean
135
+ shown = true
136
+ /** the decorated cell (calculated fields, idle defaults) and the raw value */
137
+ cell: CalcCell
138
+ props: Record<string, any>
139
+ children!: any[]
140
+ /** the children as the parent passed them (slots included) */
141
+ raw!: any[]
142
+ slots: Record<string, any[]> = {}
143
+ kids = new Map<string, any>()
144
+ uidBase: string
145
+ /** the parent's view of this instance's output (last vnode / READY it injected) */
146
+ last: any; lr = true
147
+ outv: any
148
+ // the last render's inputs: state, props, children, context, the context keys its view read
149
+ ls: any = {}; lp: any; lc: any; lctx: any; keys: Set<string> | null = null; tmpl: any; forced = true
150
+ postSels: string[] | null = null
151
+ // context memo: the app's write version it was computed at, the value
152
+ cver = -1; cv: any
153
+ subs: any[] = []
154
+ srcs: Record<string, any> | null = null
155
+ fb: Record<string, any> | null = null
156
+ ac: AbortController | null = null
157
+ disp$: any
158
+ st$: any; stLast: any; stateSrc: any
159
+ props$: any; children$: any
160
+ /** CHILD.select listeners (the children's PARENT values) */
161
+ hub: Set<(e: any) => void> | null = null
162
+ wd: any; so: any; el: any
163
+ iv: any
164
+ cmds: any
165
+ /** the app's render epoch it last rendered at (G-311) */
166
+ ep = 0
167
+ /** pending next() timers (G-300) */
168
+ timers: Set<any> | null = null
169
+ // statics.ts: the [sink, static] pairs it declares, the last values sent, the state / shown
170
+ // flag they were computed from; its reply streams
171
+ st: Array<[string, string]> | null = null
172
+ sv: Record<string, any> | null = null
173
+ sS: any; sH: any
174
+ rep: any[] | null = null
175
+
176
+ constructor(
177
+ public app: App, public def: CoreDef, public parent: Inst | null, base: Cell,
178
+ public dom: any, props: Record<string, any>, children: any[], public scope: string | undefined,
179
+ uidBase: string, public kind: 'root' | 'child' | 'item' | 'page' = parent ? 'child' : 'root', reset = false,
180
+ ) {
181
+ this.props = props
182
+ this.setChildren(children)
183
+ this.uidBase = uidBase
184
+ this.ready = !def.ready
185
+ this.cell = def.calculated || def.idle ? calcCell(base, def) : {get: () => base.get(), set: (v) => base.set(v), raw: () => base.get()}
186
+ const H = app.hooks
187
+ // the initial state, synchronously (INITIALIZE at construction, D165): a root (or the state an
188
+ // HMR swap keeps), an isolated child's local state. D174: an isolated child bound to a slice
189
+ // (state="key" or a lens) seeds it with initialState only while it is undefined (with a model,
190
+ // as today's INITIALIZE; without one it reads initialState while the slice is missing), and
191
+ // `resetState` replaces it at creation; an existing slice is kept (onStateSeed: R4's warning)
192
+ let init = !parent && app.initState !== undefined ? app.initState : def.initialState
193
+ // a root without a model renders from `initialState || true` (G-172, as today)
194
+ if (!parent && !def.model && !init) init = true
195
+ // R4: a shallow copy, as today's INITIALIZE reducer made ({...initialState}): the static is
196
+ // shared by every instance (and the dev entry freezes it, D152), the state is the instance's
197
+ else if (isObj(init)) init = {...init}
198
+ if (init !== undefined) {
199
+ if (!parent || (def.isolated && (base as any).local)) this.cell.set(init)
200
+ else if (def.isolated) {
201
+ const b: any = base, has = b.has ? b.has() : b.get() !== undefined
202
+ // the parent's slice is written as a queued action (before INITIALIZE), so a child that
203
+ // fails to start (G-295) never touches it: the drain skips a disposed instance
204
+ if (reset || (!has && def.model)) app.dispatch(this, SEED, {v: init, r: reset, b}, 'built-in')
205
+ else if (has) H.onStateSeed?.(viewOf(this), b.get(), init)
206
+ }
207
+ } else if (def.idle && isObj(this.cell.raw()) && !parent) this.cell.set(this.cell.raw())
208
+ H.onCreate?.(viewOf(this))
209
+ dbg(this, () => 'Instantiated')
210
+ if (def.handlers.has('INITIALIZE')) app.dispatch(this, 'INITIALIZE', init, 'built-in')
211
+ if (app.stat.length || app.rep.length) attach(this)
212
+ if (def.intent) {
213
+ try { this.subscribe() } catch (e) {
214
+ // G-295: undone (its queued actions are skipped, no STATE watcher, onDispose pairs onCreate)
215
+ this.disposed = true
216
+ app.watchers.delete(this)
217
+ detach(this)
218
+ H.onDispose?.(viewOf(this))
219
+ throw e
220
+ }
221
+ }
222
+ if (def.handlers.has('BOOTSTRAP')) app.born.push(this)
223
+ }
224
+
225
+ get name() { return this.def.name }
226
+
227
+ // ---------------------------------------------------------------- sources (the stream edge)
228
+ subscribe() {
229
+ const app = this.app, H = app.hooks, def = this.def
230
+ // an emission on subscribe (startWith, xs.of) only queues; drained after the construction
231
+ const was = app.draining
232
+ app.draining = true
233
+ try {
234
+ let so = this.sources()
235
+ if (H.wrapSources) so = H.wrapSources(viewOf(this), so) || so
236
+ const actions = def.intent!(so)
237
+ // G-338: one stream of { type, data } (a form 6.0 removed), not an object of streams
238
+ if (actions && typeof actions.addListener == 'function') fail('SYG603', this, 'intent returned a single stream, a form Sygnal 6.0 removed', 'Return an object of streams, one per action: { ACTION: stream$ }. See https://sygnal.js.org/guide/migrating-to-6#leftovers')
239
+ if (!isObj(actions)) fail('SYG603', this, 'intent must return an object of streams', 'Return { ACTION: stream$ }')
240
+ H.onIntent?.(viewOf(this), Object.keys(actions))
241
+ for (const type in actions) {
242
+ const s = actions[type]
243
+ if (s == null) continue
244
+ if (typeof s.subscribe != 'function') fail('SYG603', this, `intent entry '${type}' is not a stream`, 'Return { ACTION: stream$ }')
245
+ this.subs.push(s.subscribe({
246
+ next: (d: any) => this.dying ? handle(this, type, d, 'intent') : app.dispatch(this, type, d, 'intent'),
247
+ // (no code of its own yet: R4's diagnostics may add one)
248
+ error: (e: any) => { console.error(`[Sygnal] ${def.name}: intent stream '${type}' errored; it stops emitting`, e); app.appError(this, e, 'intent', type) },
249
+ }))
250
+ }
251
+ } catch (e) {
252
+ // a failed instance keeps no subscription (its parent renders the error fallback)
253
+ for (const s of this.subs) s.unsubscribe()
254
+ this.subs = []
255
+ throw e
256
+ } finally { app.draining = was }
257
+ if (!was && !app.rendering && app.queue.length) app.drain()
258
+ }
259
+
260
+ sources() {
261
+ if (this.so) return this.so
262
+ const so = this.so = Object.defineProperties({}, this.app.srcDesc(!!this.parent))
263
+ Object.defineProperty(so, INST, {value: this})
264
+ return so
265
+ }
266
+ /** a driver source isolated to this instance (the parent's, isolated to this scope; cached) */
267
+ src(n: string): any {
268
+ const c = this.srcs ||= {}
269
+ if (n in c) return c[n]
270
+ let s = this.parent ? this.parent.src(n) : this.app.sources[n]
271
+ if (this.scope !== undefined && s && typeof s.isolateSource == 'function') s = s.isolateSource(s, this.scope)
272
+ return (c[n] = s)
273
+ }
274
+ /** STATE: today's StateSource over a memory stream fed on changes (identity; undefined skipped) */
275
+ stateSource() {
276
+ if (this.stateSrc) return this.stateSrc
277
+ this.st$ = xs.createWithMemory()
278
+ const v = this.stLast = this.cell.raw()
279
+ if (v !== undefined) this.st$.shamefullySendNext(v)
280
+ this.app.watchers.add(this)
281
+ return (this.stateSrc = new StateSource(this.st$, 'STATE', this.dispose$(), 1))
282
+ }
283
+ /** called after each action and before each render pass: STATE.stream emits a new state */
284
+ notify() {
285
+ const v = this.cell.raw()
286
+ if (v !== this.stLast && v !== undefined) { this.stLast = v; this.st$.shamefullySendNext(v) }
287
+ }
288
+ dispose$() {
289
+ return this.disp$ ||= xs.create()
290
+ }
291
+ childSource() {
292
+ return {
293
+ select: (fn: any) => {
294
+ let f: any
295
+ return xs.create({
296
+ start: (l: any) => { (this.hub ||= new Set()).add(f = (e: any) => e.component === fn && l.next(e.value)) },
297
+ stop: () => { this.hub?.delete(f) },
298
+ })
299
+ },
300
+ }
301
+ }
302
+ setChildren(children: any[]) {
303
+ // G-294: the raw children (before the slots are split out) decide whether the view re-runs
304
+ this.raw = children
305
+ const [kids, slots] = children.length ? extractSlots(children) : [children, {}]
306
+ this.children = kids
307
+ this.slots = slots
308
+ }
309
+ setProps(props: Record<string, any>, children: any[]) {
310
+ this.props = props
311
+ this.setChildren(children)
312
+ this.app.hooks.onPropsChanged?.(viewOf(this), props)
313
+ this.props$?.shamefullySendNext(props)
314
+ this.children$?.shamefullySendNext(this.children)
315
+ }
316
+ /** commands$: the source of the first Command among the props (undefined without one) */
317
+ commands(): any {
318
+ if (this.cmds !== undefined) return this.cmds
319
+ for (const k in this.props) {
320
+ const c = this.props[k]
321
+ if (c && c.__sygnalCommand) {
322
+ c._targetComponentName = this.def.name
323
+ c._targetComponentId = this.id
324
+ return (this.cmds = makeCommandSource(c))
325
+ }
326
+ }
327
+ return (this.cmds = undefined)
328
+ }
329
+ /** a PARENT value from a child: to this instance's CHILD.select listeners */
330
+ toChild(e: any) {
331
+ this.hub?.forEach(f => f(e))
332
+ }
333
+
334
+ uid = (n?: string) => (n ? this.uidBase + '-' + n : this.uidBase)
335
+
336
+ // ---------------------------------------------------------------- context (D168)
337
+ context(): any {
338
+ const app = this.app
339
+ if (this.cver === app.ver) return this.cv
340
+ const par = this.parent ? this.parent.context() : EMPTY_CTX
341
+ let v = par
342
+ const entries = this.def.context
343
+ if (entries) {
344
+ const s = this.cell.get()
345
+ v = {...par}
346
+ for (const [k, f] of entries) {
347
+ try { v[k] = f(s) } catch (e) {
348
+ logError('SYG404', this, `Context entry '${k}' threw; it keeps its last value`, ERR_FIX, e)
349
+ app.appError(this, e, 'context')
350
+ if (this.cv) v[k] = this.cv[k]
351
+ }
352
+ }
353
+ // the same values: the same object, so readers see no change
354
+ if (this.cv && shallowEq(v, this.cv)) v = this.cv
355
+ else if (this.cv && app.hooks.onContextChanged) app.hooks.onContextChanged(viewOf(this), v, Object.keys(v).filter(k => v[k] !== this.cv[k]))
356
+ }
357
+ this.cver = app.ver
358
+ return (this.cv = v)
359
+ }
360
+ /** a context change touches a key the last view read */
361
+ ctxChanged(ctx: any): boolean {
362
+ const last = this.lctx
363
+ if (ctx === last) return false
364
+ const keys = this.keys
365
+ if (keys) {
366
+ if (keys.has(ALL)) return true
367
+ for (const k of keys) if (ctx[k] !== last[k]) return true
368
+ }
369
+ // D168: skipped. The dev check (onContextSkip) re-runs a sample of these views
370
+ this.app.ctxSkip?.(this, last, ctx)
371
+ this.lctx = ctx
372
+ return false
373
+ }
374
+
375
+ /** render again in the next flush though no input changed (a lazy component loaded) */
376
+ refresh() {
377
+ if (this.disposed) return
378
+ this.forced = true
379
+ this.app.commit()
380
+ }
381
+
382
+ setReady(r: boolean) {
383
+ if (r !== this.ready) {
384
+ this.ready = r
385
+ if (this.parent) this.app.hooks.onReady?.(viewOf(this.parent), viewOf(this), r)
386
+ this.app.commit()
387
+ }
388
+ }
389
+
390
+ // ---------------------------------------------------------------- render
391
+ render(): any {
392
+ if (this.disposed) return this.outv
393
+ const state = this.cell.get(), ctx = this.context()
394
+ let same = false, viewDirty = this.forced || state !== this.ls || !shallowEq(this.props, this.lp) || !sameKids(this.raw, this.lc) || this.ctxChanged(ctx)
395
+ // no state (yet): the view isn't called; it keeps its last render (as today's state stream,
396
+ // which skips undefined). A child with none is left out of its parent's vnode
397
+ if (state === undefined && viewDirty) {
398
+ if (!this.tmpl) return this.outv
399
+ viewDirty = false
400
+ }
401
+ if (viewDirty) {
402
+ const H = this.app.hooks
403
+ if (H.onStateChanged && state !== this.ls && !this.forced) H.onStateChanged(viewOf(this), state)
404
+ this.forced = false
405
+ this.ls = state; this.lp = this.props; this.lc = this.raw; this.lctx = ctx
406
+ const prev = this.tmpl
407
+ this.view(state, ctx)
408
+ dbg(this, () => 'View rendered')
409
+ // P46-P: the same plain output as last time: the last vnode, by identity
410
+ if (prev?.$p && this.tmpl.$p && this.outv && sameTree(prev, this.tmpl)) { this.tmpl = prev; same = this.app.ran = true }
411
+ else this.reconcile()
412
+ }
413
+ // G-311: after a render that threw (the app's epoch moved), the kids are injected again once
414
+ let kidsDirty = this.ep !== this.app.ep
415
+ this.ep = this.app.ep
416
+ for (const k of this.kids.values()) {
417
+ const v = k.render()
418
+ if (v !== k.last || k.ready !== k.lr) { k.last = v; k.lr = k.ready; kidsDirty = true }
419
+ }
420
+ if (!viewDirty && !kidsDirty) return this.outv
421
+ if (same && !kidsDirty) { this.app.hooks.onRender?.(viewOf(this), this.tmpl); return this.outv }
422
+ let v = this.kids.size ? this.inject(this.tmpl, 'r') : this.tmpl
423
+ if (this.postSels) for (const s of this.postSels) v = posts[s](v, this)
424
+ this.app.hooks.onRender?.(viewOf(this), v)
425
+ return (this.outv = this.parent ? this.app.scopeValue(this.parent, 'DOM', v, this) : v)
426
+ }
427
+
428
+ /**
429
+ * G-319 (a hidden Switchable page, or below one): no render, but its Collections follow their
430
+ * arrays, so removed items stop and new ones start (statics, replies) while hidden
431
+ */
432
+ sync() {
433
+ if (!this.disposed) for (const k of this.kids.values()) k.sync?.()
434
+ }
435
+
436
+ /** calls the view with one argument (D164); the onError boundary */
437
+ view(state: any, ctx: any) {
438
+ const def = this.def
439
+ let keys: Set<string> | null = null, context = ctx
440
+ if (ctx !== EMPTY_CTX) {
441
+ const ks = keys = new Set<string>()
442
+ context = new Proxy(ctx, {
443
+ get: (t, k) => (typeof k == 'string' && ks.add(k), t[k as any]),
444
+ has: (t, k) => (typeof k == 'string' && ks.add(k), k in t),
445
+ ownKeys: (t) => (ks.add(ALL), Reflect.ownKeys(t)),
446
+ })
447
+ }
448
+ try {
449
+ this.tmpl = def.view({...this.props, state, children: this.children, slots: this.slots, context, uid: this.uid})
450
+ } catch (err) {
451
+ const e = err instanceof Error ? err : new Error(String(err))
452
+ let out: any = errorDiv(def.name)
453
+ if (def.onError) {
454
+ try {
455
+ out = def.onError(e, {componentName: def.name})
456
+ warn('SYG406', this, 'View threw; rendered the onError fallback', undefined, e)
457
+ } catch (fe) {
458
+ logError('SYG406', this, 'View threw; rendering the error fallback', undefined, e)
459
+ logError('SYG407', this, 'onError threw; rendering an empty error <div>', 'Make onError return a vnode', fe)
460
+ }
461
+ } else logError('SYG406', this, 'View threw; rendering the error fallback', 'Add .onError for a custom fallback', e)
462
+ this.app.appError(this, e, 'view')
463
+ this.tmpl = out
464
+ }
465
+ const t = this.tmpl
466
+ // G-567: a view that returns a string or a number renders it as text (as renderToString)
467
+ if (typeof t == 'string' || typeof t == 'number') this.tmpl = {text: '' + t}
468
+ else if (!t) this.tmpl = {sel: 'div', data: {}, children: [], text: undefined, elm: undefined, key: undefined}
469
+ this.keys = keys
470
+ }
471
+
472
+ /** the components in the template, by id: kept (new props), created, or disposed */
473
+ reconcile() {
474
+ const seen = new Set<string>()
475
+ let ps: string[] | null = null
476
+ const walk = (n: any, path: string, parent: any, idx: number): void => {
477
+ if (!n || n.$p) return
478
+ const sel = n.sel
479
+ // a fragment (no sel): its children are walked in place (G-256)
480
+ if (sel && pres[sel]) {
481
+ const r = pres[sel](n, this, path)
482
+ if (r === n) return
483
+ if (parent) (parent.children = parent.children.slice())[idx] = r
484
+ else this.tmpl = r
485
+ return walk(r, path, parent, idx)
486
+ }
487
+ const data = n.data, host = sel && hosts[sel]
488
+ if (data?.c || host) {
489
+ const id = idOf(n, path)
490
+ seen.add(id)
491
+ const props = propsOf(data.props), children = n.children || (n.text != null ? [{text: n.text}] : [])
492
+ let view = data.c
493
+ if (view) for (const r of resolvers) view = r(view, this) || view
494
+ let k = this.kids.get(id)
495
+ if (k && view && k.def && k.def.view !== view) { k.dispose(); this.kids.delete(id); k = undefined }
496
+ if (k) k.setProps(props, children, n, id)
497
+ else this.kids.set(id, host ? this.host(host, props, children, id, n) : this.child(view, props, children, id, !!data.props?.resetState))
498
+ return
499
+ }
500
+ if (posts[sel] && !(ps ||= []).includes(sel)) ps.push(sel)
501
+ const c = n.children
502
+ if (c) for (let i = 0; i < c.length; i++) walk(c[i], path + '.' + i, n, i)
503
+ }
504
+ walk(this.tmpl, 'r', null, 0)
505
+ this.postSels = ps
506
+ if (this.kids.size > seen.size) for (const [id, k] of this.kids) if (!seen.has(id)) { k.dispose(); this.kids.delete(id) }
507
+ }
508
+
509
+ /** a host (Collection, Switchable); one that throws renders the owner's error fallback (SYG408) */
510
+ host(make: any, props: Record<string, any>, children: any[], id: string, n: any): any {
511
+ try { return make(this, props, children, id, n) } catch (err) { return this.failed(err) }
512
+ }
513
+
514
+ /** the owner's error fallback for a child that failed to instantiate (SYG408) */
515
+ failed(err: any): Failed {
516
+ const e = err instanceof Error ? err : new Error(String(err))
517
+ let out: any = errorDiv(this.def.name)
518
+ if (this.def.onError) {
519
+ try { out = this.def.onError(e, {componentName: this.def.name}) || out }
520
+ catch (fe) { logError('SYG407', this, 'onError threw; rendering an empty error <div>', 'Make onError return a vnode', fe) }
521
+ }
522
+ this.app.caught(this, 'SYG408', 'Sub-component threw; rendering the error fallback', e, 'instantiate')
523
+ return new Failed(out)
524
+ }
525
+
526
+ /** a tag child: state="key" | state={lens} | none (the parent's state) | isolatedState (D174: `resetState`) */
527
+ child(view: any, props: Record<string, any>, children: any[], id: string, reset = false): any {
528
+ const app = this.app
529
+ try {
530
+ const def = app.def(view)
531
+ const st = props.state, calcOf = this.def.calcNames
532
+ if (def.initialState && !def.isolated) {
533
+ fail('SYG405', def.name, 'Sub-component initialState replaces the state its parent passes in', 'Remove initialState, or set isolatedState = true')
534
+ }
535
+ const dflt = def.isolated ? def.initialState : undefined
536
+ const cell = typeof st == 'string' ? keyCell(this.cell, st, calcOf?.has(st) && this.def.name, dflt)
537
+ : st !== undefined ? lensCell(this.cell, st, this.def.name, (e) => app.appError(this, e, 'view'), dflt)
538
+ : def.isolated ? localCell(app, this.cell) : this.cell
539
+ const scope = app.scope()
540
+ return new Inst(app, def, this, cell, this.dom && this.dom.isolateSource(this.dom, scope), props, children, scope,
541
+ this.uid(uidPart(id.replace(/.*::(r\.)?/, ''))), 'child', reset)
542
+ } catch (err) {
543
+ return this.failed(err)
544
+ }
545
+ }
546
+
547
+ inject(n: any, path: string): any {
548
+ if (!n || n.$p) return n
549
+ if (n.data?.c || (n.sel && hosts[n.sel])) {
550
+ const k = this.kids.get(idOf(n, path))
551
+ return k && (k.ready ? k.outv : notReady(k.outv))
552
+ }
553
+ const c = n.children
554
+ if (!c) return n
555
+ let out: any
556
+ for (let i = 0; i < c.length; i++) {
557
+ const v = this.inject(c[i], path + '.' + i)
558
+ if (v !== c[i]) (out ||= c.slice())[i] = v
559
+ }
560
+ return out ? {...n, children: out} : n
561
+ }
562
+
563
+ // ---------------------------------------------------------------- actions
564
+ handle(type: string, data: any, cause: any, src?: string) {
565
+ handle(this, type, data, cause, src)
566
+ }
567
+
568
+ // ---------------------------------------------------------------- dispose
569
+ dispose() {
570
+ if (this.disposed) return
571
+ const app = this.app
572
+ if (this.def.handlers.has('DISPOSE')) handle(this, 'DISPOSE', undefined, 'built-in')
573
+ // dispose$: the actions it drives run now, while the instance still exists (as today: a
574
+ // `CLEANUP: dispose$` action's driver sinks go out)
575
+ if (this.disp$) { this.dying = true; try { this.disp$.shamefullySendNext(true) } finally { this.dying = false } }
576
+ this.disposed = true
577
+ app.hooks.onDispose?.(viewOf(this))
578
+ if (this.disp$) this.disp$.shamefullySendComplete()
579
+ // G-144: the replies end now (after DISPOSE and dispose$), so the drivers drop its timers and
580
+ // abort its requests; its statics are no longer recomputed
581
+ if (this.st || this.rep) detach(this)
582
+ this.ac?.abort()
583
+ if (this.timers) { this.timers.forEach(clearTimeout); this.timers = null }
584
+ this.kids.forEach(k => k.dispose())
585
+ this.kids.clear()
586
+ const subs = this.subs
587
+ if (subs.length) tearDown(() => { for (const s of subs) s.unsubscribe() }, app.dq, this)
588
+ this.subs = []
589
+ if (this.st$) app.watchers.delete(this)
590
+ this.hub = null
591
+ }
592
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * PLAN-4.6 core: the ClientOnly marker (R2), registered on import by the public `ClientOnly`
3
+ * module (sygnal/vike). On the client it is unwrapped, as today: one child stands in its place
4
+ * (at the marker's path), none renders an empty div, several are wrapped in a div. SSR renders
5
+ * its fallback (extra/ssr.ts, unchanged).
6
+ */
7
+ import {pres} from '../registry'
8
+
9
+ pres.clientonly = (n) => {
10
+ const c = n.children || []
11
+ if (c.length < 2) return c.length ? c[0] : {sel: 'div', data: {}, children: [], text: undefined, elm: undefined, key: undefined}
12
+ return {sel: 'div', data: {}, children: c, text: undefined, elm: undefined, key: undefined}
13
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * PLAN-4.6 core: lazy() components (R2), registered on import by the public `lazy` module.
3
+ * A view resolver (registry `resolvers`): until the import resolves, the LazyWrapper itself is
4
+ * the component (it renders the data-sygnal-lazy="loading" placeholder Suspense waits for, or
5
+ * the error placeholder); once it has, the loaded component replaces it under the same id (a
6
+ * new instance, its state slice as the wrapper's). When the promise settles the owner renders
7
+ * again (Inst.refresh: no state write, unlike the 5.x core's `__sygnalLazyTick`); so does it
8
+ * when a deferred import (`when`) starts (G-385: its placeholder, "deferred" until then, is then the
9
+ * "loading" one Suspense waits for).
10
+ * G-386: the owners waiting are a set per wrapper with one `.then` each; an owner leaves it when
11
+ * disposed, so a `when` that never triggers keeps no disposed owner.
12
+ */
13
+ import {resolvers} from '../registry'
14
+
15
+ /** wrapper -> the owners waiting for it (`done`: settled, nobody waits any more) */
16
+ export const waiting = new WeakMap<any, Set<any> & {done?: boolean}>()
17
+
18
+ resolvers.push((view, owner) => {
19
+ if (!view.__sygnalLazy) return
20
+ if (view.__sygnalLazyLoaded()) return view.__sygnalLazyLoadedComponent || undefined
21
+ let w = waiting.get(view)
22
+ if (!w) {
23
+ const set: Set<any> & {done?: boolean} = w = new Set()
24
+ waiting.set(view, set)
25
+ // the owners render again, and so do the wrapper's own instances (a failed load renders its
26
+ // error placeholder: G-310; a started deferred one, its placeholder without `when`)
27
+ const again = () => set.forEach(o => {
28
+ const force = (k: any) => { if (k.def?.view === view && !k.disposed) k.forced = true }
29
+ for (const k of o.kids.values()) k.insts ? k.insts().forEach(force) : force(k)
30
+ o.refresh()
31
+ })
32
+ view.__sygnalLazyStarted?.then(again)
33
+ view.__sygnalLazyPromise?.then(() => { set.done = true; again(); set.clear() })
34
+ }
35
+ const set = w
36
+ if (!set.done && !set.has(owner)) {
37
+ set.add(owner)
38
+ owner.dispose$?.().addListener({next: () => set.delete(owner)})
39
+ }
40
+ })