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