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.
- package/CHANGELOG.md +646 -0
- package/README.md +24 -9
- package/dist/astro/client.cjs.js +16 -9
- package/dist/astro/client.cjs.js.map +1 -1
- package/dist/astro/client.mjs +16 -9
- package/dist/astro/client.mjs.map +1 -1
- package/dist/astro/index.cjs.js +362 -91
- package/dist/astro/index.cjs.js.map +1 -1
- package/dist/astro/index.mjs +362 -92
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/server.cjs.js +3396 -165
- package/dist/astro/server.cjs.js.map +1 -1
- package/dist/astro/server.mjs +3396 -165
- package/dist/astro/server.mjs.map +1 -1
- package/dist/devtools.cjs.js +1431 -0
- package/dist/devtools.cjs.js.map +1 -0
- package/dist/devtools.esm.js +1419 -0
- package/dist/devtools.esm.js.map +1 -0
- package/dist/diagnostics.cjs.js +3032 -296
- package/dist/diagnostics.cjs.js.map +1 -1
- package/dist/diagnostics.esm.js +3032 -296
- package/dist/diagnostics.esm.js.map +1 -1
- package/dist/element.cjs.js +230 -0
- package/dist/element.cjs.js.map +1 -0
- package/dist/element.esm.js +228 -0
- package/dist/element.esm.js.map +1 -0
- package/dist/guide/accessibility.md +257 -0
- package/dist/guide/adapters.md +221 -0
- package/dist/guide/behaviors.md +312 -0
- package/dist/guide/browser-sources.md +203 -0
- package/dist/guide/drag-and-drop.md +342 -0
- package/dist/guide/element-commands.md +306 -0
- package/dist/guide/error-boundaries.md +169 -0
- package/dist/guide/forms-reference.md +381 -0
- package/dist/guide/forms.md +250 -0
- package/dist/guide/http.md +299 -0
- package/dist/guide/inputs.md +173 -0
- package/dist/guide/persistence.md +245 -0
- package/dist/guide/recipes/carousel.md +155 -0
- package/dist/guide/recipes/charts.md +177 -0
- package/dist/guide/recipes/code-editor.md +135 -0
- package/dist/guide/recipes/data-grid.md +155 -0
- package/dist/guide/recipes/data-table.md +196 -0
- package/dist/guide/recipes/i18n.md +222 -0
- package/dist/guide/recipes/icons.md +115 -0
- package/dist/guide/recipes/overview.md +33 -0
- package/dist/guide/recipes/rich-text.md +154 -0
- package/dist/guide/resources.md +471 -0
- package/dist/guide/ssr.md +255 -0
- package/dist/guide/timers.md +178 -0
- package/dist/guide/ui/accordion.md +93 -0
- package/dist/guide/ui/combobox.md +144 -0
- package/dist/guide/ui/dialog.md +124 -0
- package/dist/guide/ui/disclosure.md +60 -0
- package/dist/guide/ui/menu.md +125 -0
- package/dist/guide/ui/overview.md +166 -0
- package/dist/guide/ui/popover.md +101 -0
- package/dist/guide/ui/select.md +114 -0
- package/dist/guide/ui/tabs.md +161 -0
- package/dist/guide/ui/toaster.md +152 -0
- package/dist/guide/ui/tooltip.md +103 -0
- package/dist/guide/undo.md +178 -0
- package/dist/guide/virtual-collections.md +183 -0
- package/dist/guide/web-components.md +179 -0
- package/dist/guide/widgets.md +217 -0
- package/dist/index.cjs.js +13178 -5769
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +2908 -276
- package/dist/index.esm.js +13149 -5779
- package/dist/index.esm.js.map +1 -1
- package/dist/jsx-dev-runtime.cjs.js +26 -334
- package/dist/jsx-dev-runtime.cjs.js.map +1 -1
- package/dist/jsx-dev-runtime.esm.js +19 -327
- package/dist/jsx-dev-runtime.esm.js.map +1 -1
- package/dist/jsx-runtime.cjs.js +26 -334
- package/dist/jsx-runtime.cjs.js.map +1 -1
- package/dist/jsx-runtime.esm.js +19 -327
- package/dist/jsx-runtime.esm.js.map +1 -1
- package/dist/jsx.cjs.js +10 -314
- package/dist/jsx.cjs.js.map +1 -1
- package/dist/jsx.esm.js +8 -315
- package/dist/jsx.esm.js.map +1 -1
- package/dist/react.cjs.js +117 -0
- package/dist/react.cjs.js.map +1 -0
- package/dist/react.esm.js +115 -0
- package/dist/react.esm.js.map +1 -0
- package/dist/shims/globalthis.cjs +20 -0
- package/dist/sygnal.min.js +1 -1
- package/dist/sygnal.min.js.map +1 -1
- package/dist/ui-combobox.cjs.js +169 -0
- package/dist/ui-combobox.cjs.js.map +1 -0
- package/dist/ui-combobox.esm.js +148 -0
- package/dist/ui-combobox.esm.js.map +1 -0
- package/dist/ui-menu.cjs.js +87 -0
- package/dist/ui-menu.cjs.js.map +1 -0
- package/dist/ui-menu.esm.js +66 -0
- package/dist/ui-menu.esm.js.map +1 -0
- package/dist/ui-select.cjs.js +104 -0
- package/dist/ui-select.cjs.js.map +1 -0
- package/dist/ui-select.esm.js +83 -0
- package/dist/ui-select.esm.js.map +1 -0
- package/dist/ui.cjs.js +738 -0
- package/dist/ui.cjs.js.map +1 -0
- package/dist/ui.esm.js +727 -0
- package/dist/ui.esm.js.map +1 -0
- package/dist/vike/ClientOnly.cjs.js +4 -14
- package/dist/vike/ClientOnly.cjs.js.map +1 -1
- package/dist/vike/ClientOnly.mjs +4 -14
- package/dist/vike/ClientOnly.mjs.map +1 -1
- package/dist/vike/{+config.js → config/+config.js} +9 -1
- package/dist/vike/config/+config.js.map +1 -0
- package/dist/vike/config/package.json +6 -0
- package/dist/vike/onRenderClient.cjs.js +165 -95
- package/dist/vike/onRenderClient.cjs.js.map +1 -1
- package/dist/vike/onRenderClient.mjs +165 -95
- package/dist/vike/onRenderClient.mjs.map +1 -1
- package/dist/vike/onRenderHtml.cjs.js +59 -24
- package/dist/vike/onRenderHtml.cjs.js.map +1 -1
- package/dist/vike/onRenderHtml.mjs +60 -25
- package/dist/vike/onRenderHtml.mjs.map +1 -1
- package/dist/vite/plugin.cjs.js +323 -88
- package/dist/vite/plugin.cjs.js.map +1 -1
- package/dist/vite/plugin.mjs +323 -89
- package/dist/vite/plugin.mjs.map +1 -1
- package/dist/zag.cjs.js +408 -0
- package/dist/zag.cjs.js.map +1 -0
- package/dist/zag.esm.js +405 -0
- package/dist/zag.esm.js.map +1 -0
- package/llms.txt +166 -103
- package/package.json +98 -14
- package/src/astro/client.ts +27 -16
- package/src/astro/index.d.ts +24 -0
- package/src/astro/index.ts +69 -3
- package/src/astro/server.ts +8 -2
- package/src/collection.ts +6 -93
- package/src/core/actions.ts +134 -0
- package/src/core/cell.ts +202 -0
- package/src/core/debug.ts +23 -0
- package/src/core/define.ts +242 -0
- package/src/core/hooks.ts +314 -0
- package/src/core/hosts/collection.ts +325 -0
- package/src/core/hosts/switchable.ts +146 -0
- package/src/core/instance.ts +592 -0
- package/src/core/markers/clientonly.ts +13 -0
- package/src/core/markers/lazy.ts +40 -0
- package/src/core/markers/portal.ts +96 -0
- package/src/core/markers/suspense.ts +41 -0
- package/src/core/markers/transition.ts +70 -0
- package/src/core/registry.ts +25 -0
- package/src/core/runtime.ts +573 -0
- package/src/core/statics.ts +104 -0
- package/src/core/teardown.ts +60 -0
- package/src/core/view.ts +37 -0
- package/src/cycle/dom/DocumentDOMSource.ts +13 -8
- package/src/cycle/dom/ElementFinder.ts +7 -9
- package/src/cycle/dom/EventDelegator.ts +174 -223
- package/src/cycle/dom/IsolateModule.ts +65 -35
- package/src/cycle/dom/MainDOMSource.ts +9 -2
- package/src/cycle/dom/PriorityQueue.ts +4 -2
- package/src/cycle/dom/SymbolTree.ts +18 -47
- package/src/cycle/dom/classNameModule.ts +9 -7
- package/src/cycle/dom/controlledInputModule.ts +23 -6
- package/src/cycle/dom/enrichEventStream.ts +25 -47
- package/src/cycle/dom/fragment.ts +7 -0
- package/src/cycle/dom/isolate.ts +30 -27
- package/src/cycle/dom/makeDOMDriver.ts +115 -56
- package/src/cycle/dom/mockDOMSource.ts +21 -1
- package/src/cycle/dom/modules.ts +16 -3
- package/src/cycle/dom/propsModule.ts +65 -0
- package/src/cycle/dom/selectModule.ts +22 -17
- package/src/cycle/dom/snabbdom.ts +1 -6
- package/src/cycle/dom/thunk.ts +3 -0
- package/src/cycle/dom/utils.ts +98 -0
- package/src/cycle/dom/viewTransition.ts +43 -0
- package/src/cycle/state/StateSource.ts +19 -3
- package/src/cycle/state/objIsEqual.ts +50 -0
- package/src/cycle/state/types.ts +0 -10
- package/src/defineComponent.ts +34 -0
- package/src/devtools.d.ts +151 -0
- package/src/devtools.ts +34 -0
- package/src/element.d.ts +59 -0
- package/src/element.ts +235 -0
- package/src/extra/backoff.ts +9 -0
- package/src/extra/behaviors.ts +169 -0
- package/src/extra/browserSignals.ts +23 -0
- package/src/extra/browserSources.ts +326 -0
- package/src/extra/command.ts +3 -3
- package/src/extra/controls.ts +47 -0
- package/src/extra/copyAsTest.ts +286 -0
- package/src/extra/devtools.ts +101 -31
- package/src/extra/devtoolsActions.ts +379 -0
- package/src/extra/devtoolsHook.ts +9 -0
- package/src/extra/devtoolsNext.ts +83 -0
- package/src/extra/diagnostics/checks/actionLog.ts +108 -0
- package/src/extra/diagnostics/checks/behaviors.ts +49 -0
- package/src/extra/diagnostics/checks/browserSources.ts +96 -0
- package/src/extra/diagnostics/checks/collections.ts +1 -1
- package/src/extra/diagnostics/checks/controls.ts +148 -0
- package/src/extra/diagnostics/checks/dataset.ts +57 -0
- package/src/extra/diagnostics/checks/dom.ts +20 -11
- package/src/extra/diagnostics/checks/elementCommands.ts +175 -0
- package/src/extra/diagnostics/checks/events.ts +17 -2
- package/src/extra/diagnostics/checks/fetch.ts +135 -0
- package/src/extra/diagnostics/checks/forms.ts +163 -0
- package/src/extra/diagnostics/checks/index.ts +97 -4
- package/src/extra/diagnostics/checks/inspect.ts +140 -32
- package/src/extra/diagnostics/checks/next.ts +417 -0
- package/src/extra/diagnostics/checks/persist.ts +54 -0
- package/src/extra/diagnostics/checks/props.ts +10 -5
- package/src/extra/diagnostics/checks/public.d.ts +98 -7
- package/src/extra/diagnostics/checks/replies.ts +128 -0
- package/src/extra/diagnostics/checks/router.ts +100 -0
- package/src/extra/diagnostics/checks/rxjsHints.ts +1 -1
- package/src/extra/diagnostics/checks/shared.ts +86 -2
- package/src/extra/diagnostics/checks/shorthand.ts +115 -0
- package/src/extra/diagnostics/checks/sortable.ts +129 -0
- package/src/extra/diagnostics/checks/state.ts +100 -7
- package/src/extra/diagnostics/checks/statics.ts +48 -0
- package/src/extra/diagnostics/checks/strict.ts +10 -67
- package/src/extra/diagnostics/checks/timers.ts +80 -0
- package/src/extra/diagnostics/checks/viewTransitions.ts +47 -0
- package/src/extra/diagnostics/checks/virtual.ts +73 -0
- package/src/extra/diagnostics/checks/widgets.ts +109 -0
- package/src/extra/diagnostics/checks/wiring.ts +66 -10
- package/src/extra/diagnostics/codes.ts +268 -23
- package/src/extra/diagnostics/index.ts +34 -9
- package/src/extra/diagnostics/legacy.ts +17 -0
- package/src/extra/driverFactories.ts +83 -19
- package/src/extra/elementCommands.ts +53 -0
- package/src/extra/fetchDriver.ts +519 -0
- package/src/extra/focusWithin.ts +27 -0
- package/src/extra/form.ts +268 -0
- package/src/extra/formHelpers.ts +139 -0
- package/src/extra/head.ts +106 -0
- package/src/extra/hmr.ts +2 -3
- package/src/extra/owned.ts +24 -0
- package/src/extra/pager.ts +50 -0
- package/src/extra/persist.ts +133 -0
- package/src/extra/pwa.ts +1 -1
- package/src/extra/queryCache.ts +107 -0
- package/src/extra/reducers.ts +1 -1
- package/src/extra/reduxDevtools.ts +92 -0
- package/src/extra/replies.ts +54 -0
- package/src/extra/router.ts +323 -0
- package/src/extra/run.ts +90 -217
- package/src/extra/selection.ts +76 -0
- package/src/extra/socketDriver.ts +265 -0
- package/src/extra/sortable.ts +377 -0
- package/src/extra/ssr.ts +427 -160
- package/src/extra/standardSchema.ts +27 -0
- package/src/extra/testing.ts +2433 -175
- package/src/extra/timers.ts +95 -0
- package/src/extra/undo.ts +261 -0
- package/src/extra/viewTransitions.ts +38 -0
- package/src/extra/virtual.ts +513 -0
- package/src/extra/widget.ts +201 -0
- package/src/index.d.ts +2631 -117
- package/src/index.ts +25 -4
- package/src/jsx-runtime.ts +15 -1
- package/src/jsx.ts +2 -1
- package/src/lazy.ts +50 -7
- package/src/portal.ts +3 -1
- package/src/pragma/index.ts +256 -133
- package/src/react-peers.d.ts +5 -0
- package/src/react.d.ts +43 -0
- package/src/react.ts +110 -0
- package/src/shared.ts +48 -0
- package/src/slot.ts +1 -1
- package/src/suspense.ts +3 -1
- package/src/switchable.ts +6 -121
- package/src/transition.ts +3 -1
- package/src/ui/accordion.ts +64 -0
- package/src/ui/dialog.ts +129 -0
- package/src/ui/disclosure.ts +35 -0
- package/src/ui/popover.ts +45 -0
- package/src/ui/shared.ts +94 -0
- package/src/ui/tabs.ts +82 -0
- package/src/ui/toaster.ts +198 -0
- package/src/ui/tooltip.ts +70 -0
- package/src/ui/zag/combobox.ts +115 -0
- package/src/ui/zag/menu.ts +40 -0
- package/src/ui/zag/select.ts +54 -0
- package/src/ui/zag/shared.ts +43 -0
- package/src/ui-combobox.d.ts +20 -0
- package/src/ui-combobox.ts +6 -0
- package/src/ui-menu.d.ts +30 -0
- package/src/ui-menu.ts +6 -0
- package/src/ui-select.d.ts +17 -0
- package/src/ui-select.ts +6 -0
- package/src/ui-zag-types.d.ts +42 -0
- package/src/ui.d.ts +208 -0
- package/src/ui.ts +15 -0
- package/src/vike/+config.ts +9 -1
- package/src/vike/ClientOnly.ts +1 -1
- package/src/vike/onRenderClient.ts +144 -96
- package/src/vike/onRenderHtml.ts +69 -26
- package/src/vike/types.ts +7 -1
- package/src/vite/globalthis-shim.ts +18 -0
- package/src/vite/globalthis.ts +56 -0
- package/src/vite/plugin.d.ts +22 -3
- package/src/vite/plugin.ts +222 -24
- package/src/zag.d.ts +64 -0
- package/src/zag.ts +238 -0
- package/dist/vike/+config.cjs.js +0 -66
- package/dist/vike/+config.cjs.js.map +0 -1
- package/dist/vike/+config.js.map +0 -1
- package/src/component.ts +0 -2301
- package/src/cycle/isolate/index.ts +0 -196
- package/src/cycle/run/index.ts +0 -151
- package/src/cycle/run/internals.ts +0 -143
- package/src/cycle/state/Collection.ts +0 -184
- package/src/cycle/state/index.ts +0 -5
- package/src/cycle/state/pickCombine.ts +0 -201
- package/src/cycle/state/pickMerge.ts +0 -122
- package/src/cycle/state/withState.ts +0 -47
- 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
|
+
})
|