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,134 @@
1
+ /**
2
+ * PLAN-4.6 core: running one action (03-proposal §3, 04 §2.4.1).
3
+ *
4
+ * The action's handlers run in model order with (pre, data, next, props), where `pre` is the
5
+ * state (calculated fields included) from before the action, for every sink:
6
+ * - STATE: applied at once (D165). ABORT, or the object it was given (GS-4), is no change.
7
+ * D205: during an input / change event (an action triggered by typing in a field), no change
8
+ * still re-renders the instance, so a controlled field shows the model's value again
9
+ * - EFFECT: run synchronously (preventDefault on the live event still works); `props.signal`
10
+ * aborts on dispose; a returned value is SYG219, a rejected promise SYG214.
11
+ * - PARENT: straight to the parent's CHILD.select listeners as { name, component, value }.
12
+ * - READY: the instance's ready flag (the parent re-renders it with data-sygnal-ready).
13
+ * - ELEMENT: element commands, run after the next patch (extra/elementCommands).
14
+ * - any other sink (EVENTS, LOG, drivers): stamped and scoped (send), then the app's bus.
15
+ * A dispatch from a handler (a driver answering synchronously, an EFFECT) is queued (FIFO).
16
+ */
17
+ import type {Inst} from './instance'
18
+ import {isAbort} from '../shared'
19
+ import {isObj} from './define'
20
+ import {warn, error as logError, fail} from '../extra/diagnostics/legacy'
21
+ import {runElementCommands} from '../extra/elementCommands'
22
+ import {viewOf} from './view'
23
+ import {checkStatics} from './statics'
24
+ import {objIsEqual} from '../cycle/state/objIsEqual'
25
+ import {dbg} from './debug'
26
+
27
+ export function handle(inst: Inst, type: string, data: any, cause: any, src?: string) {
28
+ const hs = inst.def.handlers.get(type), app = inst.app, H = app.hooks
29
+ // (an action with no model entry is seen too: the action log lists it with no sinks)
30
+ if (H.onAction) H.onAction(viewOf(inst), {type, data, cause, target: viewOf(inst), source: src})
31
+ dbg(inst, () => `<${type}> Action triggered`)
32
+ if (!hs) return
33
+ const def = inst.def
34
+ const pre = inst.cell.get()
35
+ let props: any, outs: any[] | undefined
36
+ const next = (t: string, d?: any, ms: any = 10, effect?: boolean) => {
37
+ if (typeof ms !== 'number') fail('SYG215', inst, `next() delay in '${type}' must be a number`, "Use next('ACTION', data, ms)")
38
+ // G-300: none for an instance being disposed; dispose() clears the pending ones
39
+ if (inst.disposed || inst.dying) return dbg(inst, () => `next(${t}) ignored: disposed`)
40
+ H.onNext?.(viewOf(inst), t, d, ms)
41
+ dbg(inst, () => `<${type}> ${effect ? 'EFFECT triggered' : 'Triggered'} a next() action: <${t}> ${ms}ms delay`)
42
+ const ts = inst.timers ||= new Set()
43
+ const id = setTimeout(() => { ts.delete(id); inst.disposed || app.dispatch(inst, t, d, 'next') }, ms)
44
+ ts.add(id)
45
+ }
46
+ for (const [sink, h0] of hs) {
47
+ const h = H.wrapHandler ? H.wrapHandler(viewOf(inst), type, sink, h0) || h0 : h0
48
+ if (sink == 'EFFECT') {
49
+ if (typeof h != 'function') continue
50
+ const failed = (e: any) => app.caught(inst, 'SYG214', `EFFECT handler '${type}' threw`, e, 'effect', type)
51
+ try {
52
+ const p = {...(props ||= inst_props(inst, pre)), signal: (inst.ac ||= globalThis.AbortController && new AbortController())?.signal}
53
+ const r = h(pre, data, (t: string, d?: any, ms?: any) => next(t, d, ms, true), p)
54
+ if (r?.then) r.then(null, failed)
55
+ else if (r !== undefined && !isAbort(r)) warn('SYG219', inst, `EFFECT handler '${type}' returned a value, which is ignored`, 'Use a STATE or driver sink')
56
+ } catch (e) { failed(e) }
57
+ continue
58
+ }
59
+ let v: any
60
+ if (typeof h == 'function') {
61
+ try { v = h(pre, data, next, props ||= inst_props(inst, pre)) } catch (e) {
62
+ app.caught(inst, 'SYG216', `Reducer for '${type}' threw; ${sink == 'STATE' ? 'state unchanged' : 'nothing sent'}`, e, 'reducer', type)
63
+ continue
64
+ }
65
+ } else v = h === undefined || h === true ? data : h
66
+ if (sink == 'STATE' && (isAbort(v) || v === pre)) {
67
+ // D205: the DOM event being dispatched (window.event) is the one this action came from
68
+ if (/^(input|change)$/.test((globalThis as any).event?.type)) inst.refresh()
69
+ continue
70
+ }
71
+ if (isAbort(v)) continue
72
+ if (sink == 'STATE') {
73
+ H.onReducer?.(viewOf(inst), type, pre, v)
74
+ dbg(inst, () => `<${type}> State reducer added`)
75
+ inst.cell.set(v)
76
+ // GS-12: this app's DOM driver patches inside a View Transition (makeViewTransitionDOMDriver
77
+ // reads the flag on its IsolateModule); includes?.: a non-array static lists nothing (G-228).
78
+ // Only for a state that changed structurally: today an equal one renders nothing (its
79
+ // request expires unused); here it would re-render the same view, so it asks for nothing
80
+ if (def.view.viewTransitions?.includes?.(type) && (v === undefined || !objIsEqual(pre, inst.cell.get()))) { const m = inst.dom?._isolateModule; if (m) m.vt = 1 }
81
+ continue
82
+ }
83
+ if (typeof v == 'symbol') { logError('SYG218', inst, `Reducer for '${type}' returned a symbol; nothing sent`, 'Return a value, or ABORT to send nothing'); continue }
84
+ if (v === undefined) warn('SYG217', inst, `Reducer for '${type}' sent undefined to the driver`, 'Return a value, or ABORT to send nothing')
85
+ if (H.onSink) H.onSink(viewOf(inst), type, sink, v)
86
+ dbg(inst, () => `<${type}> Data sent to [${sink}]: ${JSON.stringify(v)}`)
87
+ if (sink == 'PARENT') {
88
+ const e = {name: def.name, component: def.view, value: v}
89
+ if (inst.parent) inst.parent.toChild(e)
90
+ else app.out('PARENT', e)
91
+ } else if (sink == 'READY') inst.setReady(!!v)
92
+ else if (sink == 'ELEMENT') {
93
+ // onElementCommand returning false: recorded, not run (renderComponent's mock DOM)
94
+ if (H.onElementCommand?.(viewOf(inst), v) !== false) runElementCommands(inst.el ||= {name: def.name, DOMSourceName: 'DOM', sources: {DOM: inst.dom}, get _disposed() { return inst.disposed }}, v)
95
+ } else if (inst.st) (outs ||= []).push(sink, v)
96
+ else send(inst, sink, v)
97
+ }
98
+ // G-158 (D170/Q21): a static this action changed reaches its driver before the action's own
99
+ // driver values (a connection the action opens exists before its first message)
100
+ if (inst.st) {
101
+ checkStatics(inst)
102
+ if (outs) for (let i = 0; i < outs.length; i += 2) send(inst, outs[i], outs[i + 1])
103
+ }
104
+ }
105
+
106
+ /** the handlers' 4th argument: the props, children, slots, context, uid and the pre-action state */
107
+ function inst_props(inst: Inst, pre: any) {
108
+ return {...inst.props, children: inst.children, slots: inst.slots, context: inst.context(), uid: inst.uid, state: pre}
109
+ }
110
+
111
+ /**
112
+ * A sink value of an instance: stamped with its sender (EVENTS; object values for a reply-capable
113
+ * source), scoped through every ancestor's isolation (a driver's value-level isolateValue, else
114
+ * one stream per instance and sink through isolateSink), then onto the app's bus for that sink.
115
+ */
116
+ export function send(inst: Inst, n: string, v: any) {
117
+ const app = inst.app, src = app.sources[n]
118
+ if (n == 'EVENTS' || (src?.__sygnalReplies && isObj(v))) {
119
+ v = Object.defineProperties({...v}, {__emitterId: {value: inst.id, configurable: true}, __emitterName: {value: inst.def.name, configurable: true}})
120
+ }
121
+ if (inst.parent && src && typeof src.isolateSink == 'function') {
122
+ let w = v
123
+ for (let i: Inst = inst; i.parent; i = i.parent) {
124
+ const o = i.parent.src(n)
125
+ if (typeof o?.isolateValue != 'function') return app.pipe(inst, n, v, (s$: any) => {
126
+ for (let j: Inst = inst; j.parent; j = j.parent) { const p = j.parent.src(n); if (typeof p?.isolateSink == 'function') s$ = p.isolateSink(s$, j.scope) }
127
+ return s$
128
+ }, (x: any) => app.out(n, x))
129
+ w = o.isolateValue(w, i.scope)
130
+ }
131
+ v = w
132
+ }
133
+ app.out(n, v)
134
+ }
@@ -0,0 +1,202 @@
1
+ /**
2
+ * PLAN-4.6 core: state cells (03-proposal §4). Pull-based views of the one state tree,
3
+ * memoized on the parent value's identity. A write goes straight to the parent (no reducer
4
+ * wrappers climbing the tree); the root's (or an isolated instance's local) write commits.
5
+ *
6
+ * | cell | get | set |
7
+ * | root | app.state | app.state = v; commit |
8
+ * | key | parent.get()[k] (or a default, isolated) | parent.set({...p, [k]: v}); SYG409 when k is a calculated field of the parent |
9
+ * | lens | lens.get(parent.get()) | parent.set(lens.set(p, v)) |
10
+ * | local | its own value; the parent's until written | own value; commit (isolatedState) |
11
+ *
12
+ * Calculated fields (and `idle` defaults) are a decorator: get() adds them, memoized per input
13
+ * identity and per declared dependency values; set() stores them (the only behaviour after D164).
14
+ * Children read through their parent's decorated cell, so they see its calculated fields, and a
15
+ * child's write stores freshly computed ones (4-F).
16
+ */
17
+ import {uidPart} from '../shared'
18
+ import type {CoreDef} from './define'
19
+ import {isObj} from './define'
20
+ import {warn, error as logError} from '../extra/diagnostics/legacy'
21
+
22
+ export interface Cell {
23
+ get(): any
24
+ set(v: any): void
25
+ }
26
+ export interface CalcCell extends Cell {
27
+ /** the raw value (calculated fields only as stored), for STATE.stream */
28
+ raw(): any
29
+ }
30
+
31
+ export const rootCell = (app: {state: any; commit(): void}): Cell =>
32
+ ({get: () => app.state, set: (v) => { app.state = v; app.commit() }})
33
+
34
+ /** isolatedState without a `state` prop: owned by the instance; the parent's state until first written */
35
+ export function localCell(app: {commit(): void}, parent: Cell): Cell {
36
+ let v: any, own = false
37
+ return {local: true, get: () => (own ? v : parent.get()), set: (n) => { v = n; own = true; app.commit() }} as Cell
38
+ }
39
+
40
+ /**
41
+ * state="key". `owner`: the parent's name when `k` is one of its calculated fields (SYG409:
42
+ * writes ignored; `what` names the writer in the message). `dflt`: an isolated child's
43
+ * initialState, read while the slice is undefined. `has()`: the slice itself is defined (D174).
44
+ */
45
+ export function keyCell(parent: Cell, k: string, owner?: string | false, dflt?: any, what = 'Sub-component'): Cell & {has(): boolean} {
46
+ let lp: any = {}, lv: any
47
+ return {
48
+ get() {
49
+ const p = parent.get()
50
+ if (p !== lp) { lp = p; lv = p == null ? undefined : p[k]; if (lv === undefined) lv = dflt }
51
+ return lv
52
+ },
53
+ set(v) {
54
+ if (owner) return warn('SYG409', owner, `${what} tried to update calculated field '${k}'; ignored`, 'Bind it to a non-calculated field')
55
+ const p = parent.get()
56
+ if (p?.[k] !== v) parent.set({...p, [k]: v})
57
+ },
58
+ has: () => parent.get()?.[k] !== undefined,
59
+ }
60
+ }
61
+
62
+ /**
63
+ * state={{ get, set }} (no get: SYG410, the parent's whole state). G-298: a get() that throws
64
+ * keeps the last value (SYG410, and `onErr` for the app's onError). `dflt`, `has()`: as keyCell's
65
+ * (G-314, D179: an isolated child's initialState read while the lensed slice is undefined)
66
+ */
67
+ export function lensCell(parent: Cell, lens: any, owner: string, onErr?: (e: any) => void, dflt?: any): Cell & {has?(): boolean} {
68
+ if (typeof lens?.get != 'function') {
69
+ logError('SYG410', owner, `Sub-component 'state' prop ${isObj(lens) ? 'has no get()' : `is a ${typeof lens}`}; it gets the parent's whole state`, 'Use a state key string or { get, set }')
70
+ return parent
71
+ }
72
+ let lp: any = {}, lv: any, raw: any
73
+ return {
74
+ get() {
75
+ const p = parent.get()
76
+ if (p !== lp) {
77
+ lp = p
78
+ try { raw = lv = lens.get(p) } catch (e) {
79
+ logError('SYG410', owner, "Sub-component 'state' lens get() threw; it keeps its last value", 'Guard the getter against missing data', e)
80
+ onErr?.(e)
81
+ }
82
+ if (lv === undefined) lv = dflt
83
+ }
84
+ return lv
85
+ },
86
+ has() { this.get(); return raw !== undefined },
87
+ set(v) {
88
+ if (typeof lens.set != 'function') return
89
+ const p = parent.get(), n = lens.set(p, v)
90
+ if (n !== p) parent.set(n)
91
+ },
92
+ }
93
+ }
94
+
95
+ /** calculated fields and `idle` defaults over a base cell */
96
+ export function calcCell(base: Cell, def: CoreDef): CalcCell {
97
+ const calc = def.calculated, idle = def.idle
98
+ const caches = calc ? calc.map(() => ({v: null as any[] | null, r: undefined as any})) : []
99
+ let ls: any = {}, lr: any
100
+ const add = (s: any) => {
101
+ if (!isObj(s)) return s
102
+ if (s === ls) return lr
103
+ ls = s
104
+ let m = idle ? {...idle, ...s} : s
105
+ if (calc) {
106
+ if (m === s) m = {...s}
107
+ for (let i = 0; i < calc.length; i++) {
108
+ const [f, fn, deps] = calc[i], c = caches[i]
109
+ const dv = deps && deps.map(d => m[d])
110
+ if (dv && c.v && dv.every((x, j) => x === c.v![j])) { m[f] = c.r; continue }
111
+ try {
112
+ const r = fn(m)
113
+ if (dv) c.v = dv, c.r = r
114
+ m[f] = r
115
+ } catch (e: any) {
116
+ warn('SYG220', def.name, `Calculated field '${f}' threw (${e instanceof Error ? e.message : e}); skipped this update`, 'Guard against missing data')
117
+ }
118
+ }
119
+ }
120
+ return (lr = m)
121
+ }
122
+ return {
123
+ raw: () => base.get(),
124
+ get: () => add(base.get()),
125
+ set(v) {
126
+ if (!isObj(v) || !(calc || idle)) return base.set(v)
127
+ // stored with its calculated fields (and idle defaults), as cleanupCalculated did
128
+ const c = {...add(v)}
129
+ ls = c; lr = c
130
+ base.set(c)
131
+ },
132
+ }
133
+ }
134
+
135
+
136
+ // ------------------------------------------------------------------ Collection items
137
+
138
+ /**
139
+ * An item's key: the `id` of an object item (anything but undefined / null, compared as a string,
140
+ * as today's stringified keys), else its raw index in the state array (D169: not its
141
+ * filtered/sorted position), in a namespace of its own ('\0' + index: G-306/G-307, so an index
142
+ * never collides with an id).
143
+ */
144
+ export const keyOf = (it: any, i: number): string => (isObj(it) && it.id != null ? '' + it.id : '\0' + i)
145
+ /**
146
+ * the key as it appears in an item's uid: an id through uidPart; an index key as `_i<index>`
147
+ * (G-322: uidPart never makes '_' followed by a letter, so an index never collides with an id,
148
+ * e.g. `[{ t }, { id: 0 }]`; renderToString matches it)
149
+ */
150
+ export const keyName = (k: string) => (k[0] == '\0' ? '_i' + k.slice(1) : uidPart(k))
151
+ const hasId = (it: any) => isObj(it) && it.id != null
152
+
153
+ const EMPTY: any[] = []
154
+ export type Index = () => [items: any[], byKey: Map<any, number>]
155
+
156
+ /** one per Collection: the key -> raw index map (first occurrence), rebuilt once per array identity, shared by its items */
157
+ export function indexer(arr: Cell): Index {
158
+ let la: any, map = new Map<any, number>()
159
+ return () => {
160
+ const g = arr.get(), a = Array.isArray(g) ? g : EMPTY
161
+ if (a !== la) {
162
+ la = a
163
+ map = new Map()
164
+ for (let i = 0; i < a.length; i++) { const k = keyOf(a[i], i); if (!map.has(k)) map.set(k, i) }
165
+ }
166
+ return [a, map]
167
+ }
168
+ }
169
+
170
+ /**
171
+ * A Collection item: the element of its key. An id-less element is a copy with its index as `id`
172
+ * (a primitive `{ value, id }`, written back as the primitive); that `id` is not written back. Writing `undefined` removes the
173
+ * element; the other elements keep their identity (PF-1).
174
+ */
175
+ export function itemCell(arr: Cell, index: Index, key: any): Cell {
176
+ let lraw: any = {}, lval: any
177
+ return {
178
+ get() {
179
+ const [a, m] = index(), j = m.get(key)
180
+ // removed: its last state (a disposing item's DISPOSE / dispose$ handlers read it, as today)
181
+ if (j === undefined) return lval
182
+ const raw = a[j]
183
+ if (raw === lraw) return lval
184
+ lraw = raw
185
+ return (lval = isObj(raw) ? (hasId(raw) ? raw : {...raw, id: j}) : {value: raw, id: j})
186
+ },
187
+ set(v) {
188
+ const [a, m] = index(), j = m.get(key)
189
+ if (j === undefined) return
190
+ if (v === undefined) return arr.set(a.filter((_: any, x: number) => x !== j))
191
+ const cur = a[j]
192
+ if (v === cur || (v === lval && cur === lraw)) return
193
+ const n = a.slice()
194
+ if (isObj(cur) || !isObj(v)) {
195
+ // G-306: the index id an id-less element was given is not stored
196
+ if (isObj(cur) && !hasId(cur) && isObj(v) && v.id === j) { v = {...v}; delete v.id }
197
+ n[j] = v
198
+ } else n[j] = v.value
199
+ arr.set(n)
200
+ },
201
+ }
202
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * PLAN-4.6 core: the documented debug logging (integration/debugging: `Component.debug =
3
+ * true`, `window.SYGNAL_DEBUG = 'true'` / `SYGNAL_DEBUG=true` in Node), as the 5.x core's
4
+ * `log()`: `[<id> | <Name>] <message>` on the console, and to a connected DevTools extension
5
+ * (onDebugLog). `inst.debug`: the DevTools toggle for one instance. The message is built only
6
+ * when logging is on.
7
+ */
8
+ import type {Inst} from './instance'
9
+
10
+ declare const process: {env: Record<string, any>}
11
+ const ENV: any = (typeof window != 'undefined' && window) || (typeof process != 'undefined' && process.env) || {}
12
+
13
+ export function dbg(inst: Inst, msg: () => string) {
14
+ if (!(inst.debug || inst.def.view.debug || ENV.SYGNAL_DEBUG === 'true' || ENV.SYGNAL_DEBUG === true)) return
15
+ // G-331: the message (JSON of a BigInt or circular value) and the DevTools bridge never break
16
+ // the action being logged
17
+ let m: string
18
+ try { m = msg() } catch (e) { m = '(message not shown: ' + e + ')' }
19
+ const text = `[${inst.id} | ${inst.def.name}] ${m}`
20
+ console.log(text)
21
+ const dt = typeof window != 'undefined' && (window as any).__SYGNAL_DEVTOOLS__
22
+ try { if (dt?.connected) dt.onDebugLog(inst.id, text) } catch (_) { /* the bridge's own bug */ }
23
+ }
@@ -0,0 +1,242 @@
1
+ /**
2
+ * PLAN-4.6 core: definitions, normalized once per component function (03-proposal §1.4).
3
+ *
4
+ * defOf(view) reads the component's statics into a DefSource, runs the definition hooks on it
5
+ * (registry.defHooks, then an app's transformDef), and normalizes it into a Def:
6
+ * - model -> Map<action, [sink, handler][]> in model order (a function entry is STATE); the sinks
7
+ * used; INITIALIZE keeps only STATE (SYG210)
8
+ * - calculated: fn | [deps, fn], Kahn order; SYG206 (invalid entry) and SYG209 (cycle, with its
9
+ * path) are thrown here; SYG207 / SYG208 warn
10
+ * - context entries (functions of the state; anything else is SYG403 and skipped)
11
+ * - isolatedState, READY presence, onError, name
12
+ *
13
+ * The removed forms (D162-D164: 'A | S' keys, string/true context entries, `.components`, ...)
14
+ * are not read. The cache is a WeakMap per function; an app with its own transformDef keeps its
15
+ * own cache (the hook is per app).
16
+ */
17
+ import type {ComponentFn, Def, DefSource, Handler} from './hooks'
18
+ import {defHooks} from './registry'
19
+ import {fail, warn, error as logError} from '../extra/diagnostics/legacy'
20
+
21
+ export type Calc = [field: string, fn: (s: any) => any, deps: string[] | null]
22
+
23
+ export interface CoreDef extends Def {
24
+ readonly handlers: Map<string, Handler[]>
25
+ readonly sinks: Set<string>
26
+ readonly calculated: Calc[] | null
27
+ /** the calculated field names (a child bound to one can't write it: SYG409) */
28
+ readonly calcNames: Set<string> | null
29
+ /** the model has a READY entry: the instance starts not ready */
30
+ readonly ready: boolean
31
+ /** the definition has a model (a root without one renders from `initialState || true`) */
32
+ readonly model: boolean
33
+ }
34
+
35
+ export const isObj = (o: any): o is Record<string, any> => o !== null && typeof o == 'object' && !Array.isArray(o)
36
+
37
+ /** a component function's name (the removed `.label` is not read, D164) */
38
+ export const nameOf = (view: any): string => view.componentName || view.name || 'FUNCTION_COMPONENT'
39
+
40
+ function normCalc(name: string, calculated: any, initialState: any): [Calc[], Set<string>] | null {
41
+ if (!calculated) return null
42
+ if (!isObj(calculated)) fail('SYG606', name, 'calculated must be an object', 'Use calculated = { field: state => value }')
43
+ const norm: Record<string, [any, string[] | null]> = {}
44
+ for (const f in calculated) {
45
+ const e = calculated[f]
46
+ if (typeof e == 'function') norm[f] = [e, null]
47
+ else if (Array.isArray(e) && e.length == 2 && Array.isArray(e[0]) && typeof e[1] == 'function') norm[f] = [e[1], e[0]]
48
+ else fail('SYG206', name, `Invalid calculated field '${f}'`, 'Use fn or [deps, fn]')
49
+ if (isObj(initialState) && f in initialState) warn('SYG207', name, `Calculated field '${f}' overwrites the initialState key of the same name`, 'Rename one of them')
50
+ }
51
+ const names = new Set(Object.keys(norm))
52
+ // Kahn: per field the calculated fields it waits for, and its dependents
53
+ const deps: Record<string, string[]> = {}, waiting: Record<string, number> = {}, dependents: Record<string, string[]> = {}
54
+ for (const f of names) {
55
+ for (const d of norm[f][1] || []) {
56
+ if (!names.has(d) && initialState && !(d in initialState)) warn('SYG208', name, `Calculated field '${f}' depends on unknown key '${d}'`, 'Add it to initialState')
57
+ }
58
+ const d = deps[f] = (norm[f][1] || []).filter(x => names.has(x))
59
+ waiting[f] = d.length
60
+ dependents[f] ||= []
61
+ for (const x of d) (dependents[x] ||= []).push(f)
62
+ }
63
+ const sorted = [...names].filter(f => !waiting[f])
64
+ for (let i = 0; i < sorted.length; i++) for (const d of dependents[sorted[i]]) if (!--waiting[d]) sorted.push(d)
65
+ if (sorted.length != names.size) {
66
+ const inCycle = [...names].filter(f => !sorted.includes(f)), path: string[] = []
67
+ const trace = (n: string): boolean => {
68
+ if (path.includes(n)) return path.push(n), true
69
+ path.push(n)
70
+ if (deps[n].some(d => inCycle.includes(d) && trace(d))) return true
71
+ path.pop()
72
+ return false
73
+ }
74
+ trace(inCycle[0])
75
+ const start = path[path.length - 1]
76
+ fail('SYG209', name, `Circular calculated dependency: ${path.slice(path.indexOf(start)).join(' → ')}`, 'Break the cycle')
77
+ }
78
+ return [sorted.map(f => [f, norm[f][0], norm[f][1]] as Calc), names]
79
+ }
80
+
81
+ /** the statics a definition hook may edit, read from the component function */
82
+ export const sourceOf = (view: ComponentFn): DefSource => ({
83
+ model: view.model, intent: view.intent, initialState: view.initialState, idle: null,
84
+ calculated: view.calculated, context: view.context, behaviorActions: {},
85
+ })
86
+
87
+ /**
88
+ * The definition shim (spike finding 5): the instance fields a behavior's existing
89
+ * `merge(component, key)` reads and writes, so behaviors / undo / selection run unchanged, once
90
+ * per function. `isSubComponent: true` sends a slice to `_idle` (the defaults a bound
91
+ * sub-component reads until it writes them); an `isolatedState` definition gets it in its
92
+ * initialState, as today. A root gets the slices in its initialState (rootDef). fromShim()
93
+ * reads the result back.
94
+ */
95
+ export const shimOf = (src: DefSource, view: ComponentFn): any => ({
96
+ model: src.model, intent: src.intent, initialState: src.initialState, view, name: nameOf(view),
97
+ stateSourceName: 'STATE', isSubComponent: true, isolatedState: !!view.isolatedState, _idle: src.idle, _behaviorActions: src.behaviorActions,
98
+ })
99
+ export const fromShim = (shim: any, src: DefSource): DefSource => ({
100
+ ...src, model: shim.model, intent: shim.intent, initialState: shim.initialState,
101
+ idle: shim._idle, behaviorActions: shim._behaviorActions || src.behaviorActions,
102
+ })
103
+
104
+ /** the built-in RESOURCE reducer (a model RESOURCE entry replaces it) */
105
+ const RESOURCE = (s: any, {name, ...r}: any) => ({...s, [name]: r})
106
+
107
+ /**
108
+ * The definition-time built-ins, in the 5.x core's order: `resources` (PLAN-3
109
+ * 3-A: the RESOURCE entry first in the model; each resource reads `{ status: 'idle' }` until
110
+ * written), then `uses` (GS-1: each behavior's own merge() on the shim, D114; the core only
111
+ * loops over `uses`).
112
+ */
113
+ function builtIns(src: DefSource, view: ComponentFn): DefSource {
114
+ const res = view.resources
115
+ if (res) {
116
+ const idle: Record<string, any> = {...src.idle}
117
+ for (const k in res) idle[k] = {status: 'idle'}
118
+ src = {...src, model: {RESOURCE, ...src.model}, idle}
119
+ }
120
+ const uses = view.uses
121
+ if (uses) {
122
+ const shim = shimOf(src, view)
123
+ for (const k in uses) uses[k]?.merge?.(shim, k)
124
+ src = fromShim(shim, src)
125
+ }
126
+ return src
127
+ }
128
+
129
+ export function normalize(view: ComponentFn, src: DefSource): CoreDef {
130
+ const name = nameOf(view)
131
+ const handlers = new Map<string, Handler[]>(), sinks = new Set<string>()
132
+ const model = src.model
133
+ for (const key in model || {}) {
134
+ let e = model![key]
135
+ if (typeof e == 'function') e = {STATE: e}
136
+ else if (!isObj(e)) fail('SYG212', name, `Model entry '${key}' must be a function or an object`, 'Use { STATE: reducer }')
137
+ if (key == 'INITIALIZE') {
138
+ for (const s in e) if (s != 'STATE') warn('SYG210', name, `INITIALIZE only supports the STATE sink; ignoring '${s}'`, 'Use another action')
139
+ if (!('STATE' in e)) continue
140
+ e = {STATE: e.STATE}
141
+ }
142
+ const list: Handler[] = []
143
+ for (const sink in e) list.push([sink, e[sink]]), sinks.add(sink)
144
+ handlers.set(key, list)
145
+ }
146
+ const c = normCalc(name, src.calculated, src.initialState)
147
+ let context: Array<[string, (s: any) => any]> | null = null
148
+ if (src.context !== undefined && src.context !== null) {
149
+ if (!isObj(src.context)) logError('SYG402', name, `context must be an object, got ${typeof src.context}; ignoring it`, 'Use context = { name: state => value }')
150
+ else {
151
+ context = []
152
+ for (const k in src.context) {
153
+ const f = src.context[k]
154
+ if (typeof f == 'function') context.push([k, f])
155
+ else logError('SYG403', name, `Invalid context entry '${k}' (a ${typeof f}); skipping it`, 'Context entries are functions of state only: { name: state => value }. See https://sygnal.js.org/guide/migrating-to-6#leftovers')
156
+ }
157
+ }
158
+ }
159
+ const intent = src.intent
160
+ if (intent !== undefined && typeof intent != 'function') fail('SYG602', name, 'intent must be a function', 'Use intent = sources => ({ ACTION: stream$ })')
161
+ return {
162
+ name, view, intent, handlers, sinks,
163
+ initialState: src.initialState,
164
+ idle: src.idle,
165
+ isolated: !!view.isolatedState,
166
+ calculated: c && c[0], calcNames: c && c[1],
167
+ context: context && context.length ? context : null,
168
+ statics: [],
169
+ behaviorActions: src.behaviorActions,
170
+ ready: sinks.has('READY'),
171
+ model: model !== undefined,
172
+ onError: typeof view.onError == 'function' ? view.onError : undefined,
173
+ }
174
+ }
175
+
176
+ const defs = new WeakMap<ComponentFn, CoreDef>()
177
+ /**
178
+ * the statics a Def was made from: a later assignment (a test, a hot edit) makes a new Def; so
179
+ * does a definition hook registered since (G-383: the browser driver's, added when the first one
180
+ * is made, binds a `browser` declarer's DOM), the last entry of the snapshot
181
+ */
182
+ const KEYS = ['model', 'intent', 'initialState', 'calculated', 'context', 'isolatedState', 'onError', 'componentName', 'uses', 'resources', 'browser']
183
+ const read = new WeakMap<CoreDef, any[]>()
184
+ const stale = (d: CoreDef, view: any) => {
185
+ const r = read.get(d)!
186
+ for (let i = 0; i < KEYS.length; i++) if (r[i] !== view[KEYS[i]]) return true
187
+ return r[KEYS.length] !== defHooks.length
188
+ }
189
+
190
+ /**
191
+ * The Def of a component function. `transform`: an app's own transformDef (its Defs are cached
192
+ * in `cache`, not shared); `override`: renderComponent's root (test intent, initial state, name),
193
+ * normalized uncached.
194
+ */
195
+ export function defOf(view: ComponentFn, transform?: (src: DefSource, view: ComponentFn) => DefSource | void, cache?: WeakMap<ComponentFn, CoreDef>, override?: Partial<DefSource> & {name?: string}): CoreDef {
196
+ const store = transform ? cache! : defs
197
+ let d = override ? undefined : store.get(view)
198
+ if (d && !stale(d, view)) return d
199
+ const r = [...KEYS.map(k => view[k]), defHooks.length]
200
+ d = normalize(view, pipeline(view, transform, override))
201
+ if (override?.name) (d as any).name = override.name
202
+ if (!override) store.set(view, d), read.set(d, r)
203
+ return d
204
+ }
205
+
206
+ type Transform = ((src: DefSource, view: ComponentFn) => DefSource | void) | undefined
207
+
208
+ /** the statics, through the built-ins, a root's own steps, the definition hooks and the app's transformDef */
209
+ function pipeline(view: ComponentFn, transform: Transform, override?: Partial<DefSource> & {name?: string}, root?: (src: DefSource) => DefSource): DefSource {
210
+ let src = sourceOf(view)
211
+ if (override) src = {...src, ...override}
212
+ // G-172: a root without a model renders from `initialState || true`, decided before the
213
+ // built-ins add a model (as in 5.x)
214
+ if (root && src.model === undefined && !src.initialState) src = {...src, initialState: true}
215
+ src = builtIns(src, view)
216
+ if (root) src = root(src)
217
+ for (const h of defHooks) src = h(src, view) || src
218
+ if (transform) src = transform(src, view) || src
219
+ return src
220
+ }
221
+
222
+ /**
223
+ * An app's root Def (uncached; renderComponent's `override` too). As defOf, plus the root-only
224
+ * steps of today's constructor: a behavior's slice is part of the root's initialState
225
+ * (behaviors.ts's root merge: the slice wins over an initialState key of its name), then
226
+ * `setup(src)`: persist's root setup (it may restore into the initialState and rewrites the
227
+ * model; persist.ts, root only, as in 5.x).
228
+ */
229
+ export function rootDef(view: ComponentFn, transform: Transform, override?: Partial<DefSource> & {name?: string; testActions?: string[]}, setup?: (src: DefSource) => DefSource): CoreDef {
230
+ const d = normalize(view, pipeline(view, transform, override, (src) => {
231
+ const uses = view.uses, idle = src.idle
232
+ if (uses && idle) {
233
+ const sl: Record<string, any> = {}
234
+ for (const k in uses) if (k in idle) sl[k] = idle[k]
235
+ src = {...src, initialState: {...(isObj(src.initialState) ? src.initialState : {}), ...sl}}
236
+ }
237
+ return setup ? setup(src) : src
238
+ }))
239
+ if (override?.name) (d as any).name = override.name
240
+ if (override?.testActions) (d as any).testActions = override.testActions
241
+ return d
242
+ }