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
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared plumbing for the runtime checks (PLAN-1 workstream 1A).
|
|
3
|
+
*
|
|
4
|
+
* The checks are bundled separately from the main 'sygnal' entry
|
|
5
|
+
* ('sygnal/diagnostics'). They must talk to the SAME diagnostics core
|
|
6
|
+
* instance the components use, so they never import the core's functions
|
|
7
|
+
* directly: `import '../index'` only makes sure the core is loaded (in the
|
|
8
|
+
* dist build that import is rewritten to the external 'sygnal' package, see
|
|
9
|
+
* rollup.config.mjs), and the functions are read from the bridge object the
|
|
10
|
+
* core publishes on globalThis.__SYGNAL_DIAGNOSTICS__.
|
|
11
|
+
*/
|
|
12
|
+
import '../index'
|
|
13
|
+
import type {DiagnosticCheck, DiagnosticDetails, Diagnostic} from '../index'
|
|
14
|
+
import {CODE_TITLES, DEV_CODE_SEVERITY, registerCodes} from '../codes'
|
|
15
|
+
import type {DiagnosticSeverity} from '../codes'
|
|
16
|
+
|
|
17
|
+
interface CoreBridge {
|
|
18
|
+
registerCheck(check: DiagnosticCheck): () => void
|
|
19
|
+
report(code: string, details: DiagnosticDetails): Diagnostic | undefined
|
|
20
|
+
[key: string]: any
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const bridge = (): CoreBridge => (globalThis as any).__SYGNAL_DIAGNOSTICS__
|
|
24
|
+
|
|
25
|
+
export const report = (code: string, details: DiagnosticDetails): Diagnostic | undefined =>
|
|
26
|
+
bridge().report(code, details)
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* report() for code that is NOT running inside a core hook (timers, wrapped
|
|
30
|
+
* methods): never throws synchronously; an 'error'-mode DiagnosticError is
|
|
31
|
+
* rethrown asynchronously, like the core does for hooks.
|
|
32
|
+
*/
|
|
33
|
+
export const reportSafely = (code: string, details: DiagnosticDetails): Diagnostic | undefined => {
|
|
34
|
+
try {
|
|
35
|
+
return report(code, details)
|
|
36
|
+
} catch (err) {
|
|
37
|
+
const raise = () => { throw err }
|
|
38
|
+
typeof queueMicrotask === 'function' ? queueMicrotask(raise) : setTimeout(raise)
|
|
39
|
+
return undefined
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
registerCodes(Object.keys(DEV_CODE_SEVERITY).map(code =>
|
|
44
|
+
[code, DEV_CODE_SEVERITY[code], CODE_TITLES[code]] as [string, DiagnosticSeverity, string]))
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* reportSafely() for the dev-entry-only codes (DEV_CODE_SEVERITY, G-143): the main bundle's
|
|
48
|
+
* core doesn't know their severity, so it is passed explicitly.
|
|
49
|
+
*/
|
|
50
|
+
export const devReport = (code: string, details: DiagnosticDetails): Diagnostic | undefined =>
|
|
51
|
+
reportSafely(code, {severity: DEV_CODE_SEVERITY[code], ...details})
|
|
52
|
+
|
|
53
|
+
/** Tunables (test seam: configureChecks()). */
|
|
54
|
+
export const timing = {
|
|
55
|
+
/** delay after a render before DOM checks run (lets nested renders patch) */
|
|
56
|
+
settleMs: 50,
|
|
57
|
+
/** SYG103 escalates to warn after this much render-idle time ... */
|
|
58
|
+
idleMs: 2000,
|
|
59
|
+
/** ... and only once the component has rendered at least this often */
|
|
60
|
+
minRenders: 3,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Built-in actions Sygnal dispatches itself; never "missing" or "unreachable".
|
|
65
|
+
* (HYDRATE is not one since 6.0, D66: nothing dispatches it, so it is an ordinary action.)
|
|
66
|
+
*/
|
|
67
|
+
export const BUILTIN_ACTIONS = new Set(['BOOTSTRAP', 'INITIALIZE', 'DISPOSE', 'READY', 'RESOURCE'])
|
|
68
|
+
|
|
69
|
+
/** Sinks the core handles itself: their values never go to a reply-action driver. */
|
|
70
|
+
// (PLAN-4 GS-2: ELEMENT too; `{ scrollIntoView: Row, block: 'center' }` names no action)
|
|
71
|
+
const NON_REPLY_SINK = /^(STATE|EFFECT|EVENTS|PARENT|READY|DOM|CHILD|ELEMENT|PERSIST)$/
|
|
72
|
+
|
|
73
|
+
// `ok: 'X'` / `"error": "X"` in function source (minified code keeps string literals and keys)
|
|
74
|
+
const keyedNames = (keys: string) => new RegExp(`(?:^|[{,\\s])["']?(?:${keys})["']?\\s*:\\s*(["'\`])([\\w$.:/-]+)\\1`, 'g')
|
|
75
|
+
// (PLAN-3 5-4b: and a router `{ block: 'ACTION' }`)
|
|
76
|
+
const REPLY_IN_SOURCE = keyedNames('ok|error|block')
|
|
77
|
+
const CONNECTION_IN_SOURCE = keyedNames('message|open|close|error')
|
|
78
|
+
// G-224: a `timers` static's `action: 'TICK'` / `frame: 'FRAME'`
|
|
79
|
+
const TIMER_IN_SOURCE = keyedNames('action|frame')
|
|
80
|
+
// PLAN-5 B-3: a `browser` static's `action: 'SEEN'` / `error: 'GEO_FAILED'`
|
|
81
|
+
const BROWSER_IN_SOURCE = keyedNames('action|error')
|
|
82
|
+
|
|
83
|
+
const namesIn = (fn: any, re: RegExp, out: Set<string>) => {
|
|
84
|
+
if (typeof fn !== 'function') return
|
|
85
|
+
let src = ''
|
|
86
|
+
try { src = Function.prototype.toString.call(fn) } catch (_) { return }
|
|
87
|
+
re.lastIndex = 0
|
|
88
|
+
for (let m = re.exec(src); m; m = re.exec(src)) out.add(m[2])
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Reply actions a component's requests and `connections` static can name, read from the
|
|
93
|
+
* source of its non-STATE sink functions (`HTTP: (s) => ({ url, ok: 'LOADED', error: 'FAILED' })`)
|
|
94
|
+
* and of `connections` (message/open/close/error). A heuristic for SYG102 (PLAN-3): a name
|
|
95
|
+
* built at run time is missed (SYG102 stays info, so that only costs a false hint).
|
|
96
|
+
*/
|
|
97
|
+
export function replyNamesOf(component: any): Set<string> {
|
|
98
|
+
const out = new Set<string>()
|
|
99
|
+
const model = component && component.model
|
|
100
|
+
if (model && typeof model === 'object') {
|
|
101
|
+
for (const key of Object.keys(model)) {
|
|
102
|
+
const value = model[key]
|
|
103
|
+
const bar = key.indexOf('|')
|
|
104
|
+
if (bar >= 0) {
|
|
105
|
+
if (!NON_REPLY_SINK.test(key.slice(bar + 1).trim())) namesIn(value, REPLY_IN_SOURCE, out)
|
|
106
|
+
} else if (value && typeof value === 'object') {
|
|
107
|
+
for (const sink of Object.keys(value)) {
|
|
108
|
+
if (!NON_REPLY_SINK.test(sink)) namesIn(value[sink], REPLY_IN_SOURCE, out)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
namesIn(component?.view?.connections, CONNECTION_IN_SOURCE, out)
|
|
114
|
+
// G-224: makeTimerDriver() replies the actions the `timers` static names (a function of the
|
|
115
|
+
// state, or an object of them)
|
|
116
|
+
const timers = component?.view?.timers
|
|
117
|
+
if (timers && typeof timers === 'object') for (const k in timers) namesIn(timers[k], TIMER_IN_SOURCE, out)
|
|
118
|
+
else namesIn(timers, TIMER_IN_SOURCE, out)
|
|
119
|
+
// PLAN-5 B-3: the browser driver replies the actions the `browser` static names
|
|
120
|
+
const browser = component?.view?.browser
|
|
121
|
+
if (browser && typeof browser === 'object') for (const k in browser) namesIn(browser[k], BROWSER_IN_SOURCE, out)
|
|
122
|
+
else namesIn(browser, BROWSER_IN_SOURCE, out)
|
|
123
|
+
// PLAN-3 5-4b: the router replies the action a `route` static names
|
|
124
|
+
const route = component?.view?.route
|
|
125
|
+
if (typeof route === 'string') out.add(route)
|
|
126
|
+
// PLAN-4 GS-5: persist() adds RESTORE (sent after hydration and by sync)
|
|
127
|
+
if (component?.view?.persist) out.add('RESTORE')
|
|
128
|
+
return out
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Reply actions a component instance's requests were seen naming (replies.ts → inspect). */
|
|
132
|
+
export const replySeen = new WeakMap<object, Set<string>>()
|
|
133
|
+
|
|
134
|
+
/** Synthetic/internal actions (`__TEST_ACTION__`, `__NOOP_ACTION__`, ...) and built-ins. */
|
|
135
|
+
export const isInternalAction = (action: string): boolean =>
|
|
136
|
+
typeof action !== 'string' || action.startsWith('__') || BUILTIN_ACTIONS.has(action)
|
|
137
|
+
|
|
138
|
+
let seen = new Set<string>()
|
|
139
|
+
|
|
140
|
+
/** true the first time `key` is seen (dedupes reports, e.g. per component name). */
|
|
141
|
+
export const once = (key: string): boolean => (seen.has(key) ? false : (seen.add(key), true))
|
|
142
|
+
/** undo once(key): the report it guarded wasn't made (G-470: diagnostics off) */
|
|
143
|
+
export const unsee = (key: string): void => void seen.delete(key)
|
|
144
|
+
/** whether once(`key`) has reported already (a check can skip its work: SYG435) */
|
|
145
|
+
export const onced = (key: string): boolean => seen.has(key)
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Forget only the once() dedupe set (G-051: renderComponent calls it through the core bridge
|
|
149
|
+
* when an outermost instance starts, so findings are reported again in every test). Unlike
|
|
150
|
+
* resetCheckState() it keeps the checks' registries, e.g. inspect()'s live records.
|
|
151
|
+
*/
|
|
152
|
+
export const resetOnce = (): void => { seen = new Set() }
|
|
153
|
+
{
|
|
154
|
+
const core = bridge()
|
|
155
|
+
if (core) core.resetOnce = resetOnce
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const resetters: Array<() => void> = []
|
|
159
|
+
export const onReset = (fn: () => void): void => { resetters.push(fn) }
|
|
160
|
+
|
|
161
|
+
/** Clear every check's dedupe/registry state (tests). */
|
|
162
|
+
export function resetCheckState(): void {
|
|
163
|
+
seen = new Set()
|
|
164
|
+
resetters.forEach(fn => fn())
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export const nameOf = (component: any): string => (component && component.name) || 'component'
|
|
168
|
+
|
|
169
|
+
export const isPlainObject = (value: any): boolean =>
|
|
170
|
+
!!value && typeof value === 'object' && !Array.isArray(value) &&
|
|
171
|
+
(Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null)
|
|
172
|
+
|
|
173
|
+
/** Levenshtein distance (small strings only). */
|
|
174
|
+
function distance(a: string, b: string): number {
|
|
175
|
+
const row = Array.from({length: b.length + 1}, (_, i) => i)
|
|
176
|
+
for (let i = 1; i <= a.length; i++) {
|
|
177
|
+
let prev = row[0]
|
|
178
|
+
row[0] = i
|
|
179
|
+
for (let j = 1; j <= b.length; j++) {
|
|
180
|
+
const tmp = row[j]
|
|
181
|
+
row[j] = Math.min(row[j] + 1, row[j - 1] + 1, prev + (a[i - 1] === b[j - 1] ? 0 : 1))
|
|
182
|
+
prev = tmp
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return row[b.length]
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Closest candidate to `name` (case-insensitive match or edit distance <= 2), if any. */
|
|
189
|
+
export function suggest(name: string, candidates: Iterable<string>): string | undefined {
|
|
190
|
+
let best: string | undefined
|
|
191
|
+
let bestScore = Infinity
|
|
192
|
+
for (const c of candidates) {
|
|
193
|
+
if (c === name) continue
|
|
194
|
+
const score = c.toLowerCase() === name.toLowerCase() ? 0 : distance(c, name)
|
|
195
|
+
if (score < bestScore) { best = c; bestScore = score }
|
|
196
|
+
}
|
|
197
|
+
return bestScore <= Math.min(2, Math.floor(name.length / 3)) ? best : undefined
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export const didYouMean = (name: string, candidates: Iterable<string>): string => {
|
|
201
|
+
const s = suggest(name, candidates)
|
|
202
|
+
return s ? ` (did you mean '${s}'?)` : ''
|
|
203
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SYG115 — unknown DOM event shorthand (warn, G-143).
|
|
3
|
+
*
|
|
4
|
+
* The core's DOM source Proxy (the DOM source wrapper) turns ANY unknown property
|
|
5
|
+
* into an event shorthand: `DOM.<name>(sel)` = `DOM.select(sel).events('<name>')`. A made-up
|
|
6
|
+
* name such as `DOM.key('.x')` or `DOM.enter('.x')` therefore listens for an event the browser
|
|
7
|
+
* never fires, and the action silently never happens.
|
|
8
|
+
*
|
|
9
|
+
* Mechanism: the `sources` hook (called before each component's intent) wraps the DOM source
|
|
10
|
+
* in a second Proxy. Calling a shorthand whose name is not a known DOM event reports SYG115
|
|
11
|
+
* (once per component name and event), then behaves exactly like the core shorthand. Only the
|
|
12
|
+
* call is reported, not the property read, so tools that inspect the source (pretty-printers)
|
|
13
|
+
* don't trigger it. `DOM.select(sel).events(name)` is never checked: it is the explicit form
|
|
14
|
+
* for custom events.
|
|
15
|
+
*/
|
|
16
|
+
import type {DiagnosticCheck} from '../index'
|
|
17
|
+
import {devReport, once, nameOf, suggest} from './shared'
|
|
18
|
+
|
|
19
|
+
// Standard DOM / UI event names (lower case). In a browser, `on<name>` on an element or the
|
|
20
|
+
// window also counts, so newer events are accepted there too.
|
|
21
|
+
const KNOWN = new Set((
|
|
22
|
+
'abort afterprint animationcancel animationend animationiteration animationstart auxclick ' +
|
|
23
|
+
'beforeinput beforeprint beforetoggle beforeunload blur cancel canplay canplaythrough change click close ' +
|
|
24
|
+
'compositionend compositionstart compositionupdate contextlost contextmenu contextrestored copy cuechange cut ' +
|
|
25
|
+
'dblclick drag dragend dragenter dragleave dragover dragstart drop durationchange emptied ended error ' +
|
|
26
|
+
'focus focusin focusout formdata fullscreenchange fullscreenerror gotpointercapture hashchange input invalid ' +
|
|
27
|
+
'keydown keypress keyup languagechange load loadeddata loadedmetadata loadstart lostpointercapture message ' +
|
|
28
|
+
'messageerror mousedown mouseenter mouseleave mousemove mouseout mouseover mouseup offline online pagehide ' +
|
|
29
|
+
'pageshow paste pause play playing pointercancel pointerdown pointerenter pointerleave pointermove pointerout ' +
|
|
30
|
+
'pointerover pointerrawupdate pointerup popstate progress ratechange rejectionhandled reset resize scroll ' +
|
|
31
|
+
'scrollend search securitypolicyviolation seeked seeking select selectionchange selectstart slotchange stalled ' +
|
|
32
|
+
'storage submit suspend timeupdate toggle touchcancel touchend touchmove touchstart transitioncancel ' +
|
|
33
|
+
'transitionend transitionrun transitionstart unhandledrejection unload visibilitychange volumechange waiting wheel'
|
|
34
|
+
).split(' '))
|
|
35
|
+
|
|
36
|
+
const isKnown = (name: string): boolean => {
|
|
37
|
+
// PLAN-5 0-S6: a hyphenated name is a custom element's event (DOM['wa-hover']('.food')): no
|
|
38
|
+
// standard DOM event has a hyphen, and a bracketed name is no typo for one
|
|
39
|
+
if (KNOWN.has(name) || name.includes('-')) return true
|
|
40
|
+
const on = 'on' + name
|
|
41
|
+
const g: any = globalThis
|
|
42
|
+
try {
|
|
43
|
+
return !!((g.HTMLElement && on in g.HTMLElement.prototype) || (g.window && on in g.window) ||
|
|
44
|
+
(g.Document && on in g.Document.prototype))
|
|
45
|
+
} catch (_) { return false }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Names agents reach for that are enriched-stream methods or key names, not events
|
|
49
|
+
const KEY_FIX = (sel: string, key?: string) =>
|
|
50
|
+
`Listen for the real event and read the key: DOM.keydown(${sel}).key()` +
|
|
51
|
+
(key ? `.filter(k => k === '${key}')` : '')
|
|
52
|
+
const SPECIAL: Record<string, (sel: string) => string> = {
|
|
53
|
+
key: sel => KEY_FIX(sel),
|
|
54
|
+
keys: sel => KEY_FIX(sel),
|
|
55
|
+
enter: sel => KEY_FIX(sel, 'Enter'),
|
|
56
|
+
escape: sel => KEY_FIX(sel, 'Escape'),
|
|
57
|
+
esc: sel => KEY_FIX(sel, 'Escape'),
|
|
58
|
+
value: sel => `Listen for the real event and read the value: DOM.input(${sel}).value()`,
|
|
59
|
+
checked: sel => `Listen for the real event and read the box: DOM.change(${sel}).checked()`,
|
|
60
|
+
data: sel => `Listen for the real event and read the attribute: DOM.click(${sel}).data('name')`,
|
|
61
|
+
doubleclick: sel => `Use DOM.dblclick(${sel})`,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const fixFor = (name: string, selector: any): string => {
|
|
65
|
+
const sel = typeof selector === 'string' ? `'${selector}'` : 'sel'
|
|
66
|
+
const special = SPECIAL[name.toLowerCase()]
|
|
67
|
+
if (special) return special(sel)
|
|
68
|
+
const s = suggest(name, KNOWN)
|
|
69
|
+
return (s ? `Did you mean DOM.${s}(${sel})? ` : '') +
|
|
70
|
+
`If '${name}' is a custom event you dispatch yourself, use the explicit form DOM.select(${sel}).events('${name}')`
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const wrapped = new WeakMap<object, any>()
|
|
74
|
+
|
|
75
|
+
function checkedDOM(component: any, dom: any): any {
|
|
76
|
+
if (!dom || typeof dom != 'object') return dom
|
|
77
|
+
let proxy = wrapped.get(dom)
|
|
78
|
+
if (proxy) return proxy
|
|
79
|
+
const name = nameOf(component)
|
|
80
|
+
proxy = new Proxy(dom, {
|
|
81
|
+
get(t: any, k: any) {
|
|
82
|
+
const v = t[k]
|
|
83
|
+
if (typeof k != 'string' || k in t || typeof v != 'function' || isKnown(k)) return v
|
|
84
|
+
return function (this: any, selector: any, ...rest: any[]) {
|
|
85
|
+
if (once(`SYG115:${name}:${k}`)) {
|
|
86
|
+
devReport('SYG115', {
|
|
87
|
+
component,
|
|
88
|
+
message: `${name}.intent uses DOM.${k}(${typeof selector == 'string' ? `'${selector}'` : '…'}), but '${k}' is not a DOM event, so it never fires`,
|
|
89
|
+
fix: fixFor(k, selector),
|
|
90
|
+
data: {event: k, selector},
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
return v.call(this, selector, ...rest)
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
})
|
|
97
|
+
wrapped.set(dom, proxy)
|
|
98
|
+
return proxy
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export const shorthandCheck: DiagnosticCheck = {
|
|
102
|
+
id: 'shorthand',
|
|
103
|
+
|
|
104
|
+
sources(component, sources) {
|
|
105
|
+
if (!sources || typeof Proxy != 'function') return
|
|
106
|
+
const domName = component?.DOMSourceName || 'DOM'
|
|
107
|
+
if (!sources[domName]) return
|
|
108
|
+
return new Proxy(sources, {
|
|
109
|
+
get(t: any, k: any) {
|
|
110
|
+
const v = t[k]
|
|
111
|
+
return k === domName ? checkedDOM(component, v) : v
|
|
112
|
+
},
|
|
113
|
+
})
|
|
114
|
+
},
|
|
115
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PLAN-5 B-1 (3-D): `sortable` behavior diagnostics (dev entry; src/extra/sortable.ts carries no
|
|
3
|
+
* text). The behavior calls `__SYGNAL_DIAGNOSTICS__.sortable(code, ...args)`:
|
|
4
|
+
* 145 (item element, item selector, attr) a press or key on an item whose element has no
|
|
5
|
+
* `attr`: SYG145 (warn, once per selector); the event is ignored
|
|
6
|
+
* 146 (host root, item, handle, attr) the host's first pointerdown / keydown: SYG146 (warn,
|
|
7
|
+
* once per selector) when elements carry `attr` under the root but none matches `item`,
|
|
8
|
+
* or items exist but none contains `handle`. Returns true once it could decide (an empty
|
|
9
|
+
* list can't tell: the check runs again at the next interaction)
|
|
10
|
+
* 147 (from key, host state, uses key) when the host starts: a `from` key whose value isn't
|
|
11
|
+
* an array: SYG147 (warn, once per key)
|
|
12
|
+
* 435 (3-H G-449) (fn, uses key, from keys, idOf): the host's own items' ids as shown, per
|
|
13
|
+
* container (fn() returns [the item elements, the root]; the behavior only asks in dev), or nothing when
|
|
14
|
+
* every list has been reported (3-L G-478: no scan). A row's place is its `data-index`
|
|
15
|
+
* when it has one (VirtualCollection's pinned focused row is out of DOM order). (shown
|
|
16
|
+
* groups, host state, from keys, idField, uses key) at a keyboard pick-up or a pointer
|
|
17
|
+
* press: SYG435 (warn, once per uses key and list) when a list's shown items (those in
|
|
18
|
+
* the container holding most of them: an element elsewhere with a matching id is no list
|
|
19
|
+
* item; a row's container is its nearest ancestor holding another row, 3-O G-505) aren't
|
|
20
|
+
* one run of the array in its order (a Collection's sort, or a filter hiding entries
|
|
21
|
+
* between shown ones)
|
|
22
|
+
*/
|
|
23
|
+
import {devReport, once, onced, suggest} from './shared'
|
|
24
|
+
|
|
25
|
+
const all = (root: any, sel: string): any[] => {
|
|
26
|
+
try { return [...root.querySelectorAll(sel)] } catch (_) { return [] }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function reportSortable(code: number, a?: any, b?: any, c?: any, d?: any, e?: any): any {
|
|
30
|
+
if (code == 145) {
|
|
31
|
+
if (once(`SYG145:${b}:${c}`)) devReport('SYG145', {
|
|
32
|
+
component: 'sortable',
|
|
33
|
+
message: `sortable: an item ('${b}') has no ${c} attribute, so it can't be moved (the behavior finds each item in the list by that id)`,
|
|
34
|
+
fix: `Render the item's id on its element: <li className="${String(b).replace(/^\./, '')}" ${c}={item.id}>, or name the attribute: sortable({ ..., attr: 'data-key' })`,
|
|
35
|
+
data: {item: b, attr: c},
|
|
36
|
+
})
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
if (code == 146) {
|
|
40
|
+
const root = a, item = b, handle = c, attr = d
|
|
41
|
+
if (!root?.querySelectorAll) return true
|
|
42
|
+
const items = all(root, item).filter(el => el !== root)
|
|
43
|
+
if (!items.length) {
|
|
44
|
+
// nothing carries the id attribute either: an empty list (decide later)
|
|
45
|
+
if (!all(root, `[${attr}]`).length) return false
|
|
46
|
+
report146('item', item, `no element under the host matches it, though elements with ${attr} are rendered`, `Use the selector of the items' elements (the element with ${attr}), e.g. item: '.task'`)
|
|
47
|
+
return true
|
|
48
|
+
}
|
|
49
|
+
if (handle && !items.some(el => all(el, handle).length)) {
|
|
50
|
+
report146('handle', handle, `no item ('${item}') contains an element that matches it`, `Use the selector of the drag handle inside each item, e.g. handle: '.grip', or leave handle out to drag the whole item`)
|
|
51
|
+
}
|
|
52
|
+
return true
|
|
53
|
+
}
|
|
54
|
+
if (code == 435) {
|
|
55
|
+
// (fn, uses key, from keys, idOf) the shown ids per container: computed only here (dev)
|
|
56
|
+
if (typeof a == 'function') {
|
|
57
|
+
if ((c || []).every((l: string) => onced(`SYG435:${b}:${l}`))) return
|
|
58
|
+
try {
|
|
59
|
+
const by = new Map<any, any[]>(), [els, root] = a(), n = new Map<any, number>()
|
|
60
|
+
// a row's place: its data-index (a VirtualCollection row below the host's root; its
|
|
61
|
+
// pinned row isn't in DOM order)
|
|
62
|
+
const rows = els.map((el: any) => {
|
|
63
|
+
const r0 = el.closest?.('[data-index]'), row = r0 && r0 !== root && root.contains?.(r0) ? r0 : el
|
|
64
|
+
return [el, row, Number(row === el && !r0 ? NaN : row.getAttribute('data-index'))]
|
|
65
|
+
})
|
|
66
|
+
// 3-O G-505: a row's container is its nearest ancestor holding another row (items each in
|
|
67
|
+
// their own wrapper, <li><div data-id>, a table cell, a Collection item's root, are still
|
|
68
|
+
// one list); `n` counts the rows below each ancestor
|
|
69
|
+
const up = (row: any, f: (p: any) => any) => { for (let p = row.parentElement; p; p = p === root ? null : p.parentElement) if (f(p)) return p }
|
|
70
|
+
for (const [, row] of rows) up(row, p => void n.set(p, (n.get(p) || 0) + 1))
|
|
71
|
+
for (const [el, row, at] of rows) {
|
|
72
|
+
const p = up(row, p => n.get(p)! > 1) || row.parentElement
|
|
73
|
+
by.has(p) || by.set(p, [])
|
|
74
|
+
by.get(p)!.push([isNaN(at) ? by.get(p)!.length : at, String(d(el))])
|
|
75
|
+
}
|
|
76
|
+
return [...by.values()].map(g => g.sort((x, y) => x[0] - y[0]).map(x => x[1]))
|
|
77
|
+
} catch (_) { return }
|
|
78
|
+
}
|
|
79
|
+
// (shown groups, host state, from keys, idField, uses key) at a keyboard pick-up or a press
|
|
80
|
+
const groups: string[][] = Array.isArray(a?.[0]) ? a : [a || []]
|
|
81
|
+
for (const l of c || []) {
|
|
82
|
+
if (onced(`SYG435:${e}:${l}`)) continue
|
|
83
|
+
const at = new Map<string, number>()
|
|
84
|
+
if (Array.isArray(b?.[l])) b[l].forEach((x: any, i: number) => at.set(String(x?.[d]), i))
|
|
85
|
+
// the list's container: the one holding most of its entries (others: stray matches)
|
|
86
|
+
let dom: number[] = []
|
|
87
|
+
for (const g of groups) {
|
|
88
|
+
const ix = g.filter(i => at.has(i)).map(i => at.get(i)!)
|
|
89
|
+
if (ix.length > dom.length) dom = ix
|
|
90
|
+
}
|
|
91
|
+
// a contiguous run in the array's order is fine (all of it, or a window of it)
|
|
92
|
+
if (!dom.some((v, j) => v !== dom[0] + j)) continue
|
|
93
|
+
if (once(`SYG435:${e}:${l}`)) devReport('SYG435', {
|
|
94
|
+
component: 'sortable',
|
|
95
|
+
message: `sortable '${e}': state.${l} is shown ${dom.length < at.size ? 'with entries hidden between the shown ones' : 'in another order'} (a Collection with ${dom.length < at.size ? 'filter' : 'sort'}?), but moves and the announced positions follow the array: a keyboard step can pass entries nobody sees, or change nothing visible`,
|
|
96
|
+
fix: `Reorder a list that is shown as it is stored: drop the Collection's sort (sort state.${l} itself), and hide the drag handles while a filter hides entries`,
|
|
97
|
+
data: {from: l, key: e},
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
if (code == 147) {
|
|
103
|
+
const st = b || {}, arrays = Object.keys(st).filter(k => Array.isArray(st[k]))
|
|
104
|
+
const near = suggest(a, arrays)
|
|
105
|
+
if (once(`SYG147:${c}:${a}`)) devReport('SYG147', {
|
|
106
|
+
component: 'sortable',
|
|
107
|
+
message: `sortable '${c}': from names '${a}', but state.${a} is ${st[a] === undefined ? 'missing' : 'not an array'} when the host starts${near ? ` (did you mean '${near}'?)` : ''}; nothing in it can be moved`,
|
|
108
|
+
fix: `Name the host state's list: sortable({ from: '${near || arrays[0] || 'items'}', ... })${arrays.length ? ` (arrays in the state: ${arrays.join(', ')})` : `, and start it as an array (initialState: { ${a}: [] })`}`,
|
|
109
|
+
data: {from: a, key: c},
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const report146 = (option: string, selector: string, why: string, fix: string) => {
|
|
115
|
+
if (once(`SYG146:${option}:${selector}`)) devReport('SYG146', {
|
|
116
|
+
component: 'sortable',
|
|
117
|
+
message: `sortable: ${option} '${selector}' matches nothing: ${why}, so no item can be dragged`,
|
|
118
|
+
fix,
|
|
119
|
+
data: {option, selector},
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Install the `sortable` hook on the core bridge (sortable.ts calls it). */
|
|
124
|
+
export function installSortableHooks(): () => void {
|
|
125
|
+
const c = (globalThis as any).__SYGNAL_DIAGNOSTICS__
|
|
126
|
+
if (!c) return () => {}
|
|
127
|
+
c.sortable = reportSortable
|
|
128
|
+
return () => { if (c.sortable === reportSortable) c.sortable = undefined }
|
|
129
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SYG201 — STATE reducer dropped keys that existed in the previous state (warn)
|
|
3
|
+
* SYG202 — STATE reducer returned undefined (warn)
|
|
4
|
+
* SYG221 — set() called with a string (error, G-143): the string is spread
|
|
5
|
+
* into the state as keys '0', '1', ... (one per character)
|
|
6
|
+
* SYG238 — G-393: a root with a model but no initialState (and no state from renderComponent's
|
|
7
|
+
* `initialState` or an HMR swap): its state is undefined, so it renders nothing until an
|
|
8
|
+
* action sets one (the 5.x / PLAN-4 behaviour; a root without a model renders from
|
|
9
|
+
* `initialState || true`, G-172). onModel, at construction, once per component name.
|
|
10
|
+
* SYG222 — STATE reducer returned the object it got after mutating it in place (warn, dev;
|
|
11
|
+
* PLAN-4 GS-4): since 6.0 the same object back means "no change", so the mutation
|
|
12
|
+
* is ignored. Mechanism: onIntent (called before the model is read) wraps the
|
|
13
|
+
* instance's STATE reducers; the wrapper takes a shallow snapshot (keys and top-level
|
|
14
|
+
* values) before the reducer runs and compares it when the reducer returns the same
|
|
15
|
+
* reference; a behavior slice (state[key] of a `uses` entry) is snapshotted the same
|
|
16
|
+
* way (keys reported as 'key.field', G-214). Nested mutations (state.list.push) are
|
|
17
|
+
* below the snapshot. Zero bytes
|
|
18
|
+
* in the core: the wrapping only happens with the dev entry and diagnostics on.
|
|
19
|
+
*
|
|
20
|
+
* Mechanism: onReducer(component, action, prevState, nextState). Reported once
|
|
21
|
+
* per action per component name. Not reported for:
|
|
22
|
+
* - synthetic actions (`__*`) and INITIALIZE (whole-state replacement; HYDRATE is an
|
|
23
|
+
* ordinary action since 6.0, D66)
|
|
24
|
+
* - SYG202: a Collection item (`() => undefined` is its documented self-removal, G-357)
|
|
25
|
+
* - keys that are calculated fields (they are re-derived after the reducer)
|
|
26
|
+
* - keys whose previous value was undefined
|
|
27
|
+
* The set()/toggle() helpers always spread the previous state, so they can't
|
|
28
|
+
* drop keys and never trigger SYG201.
|
|
29
|
+
*/
|
|
30
|
+
import type {DiagnosticCheck} from '../index'
|
|
31
|
+
import {report, devReport, once, nameOf, isPlainObject} from './shared'
|
|
32
|
+
import {ORIGINAL} from '../../../shared'
|
|
33
|
+
|
|
34
|
+
const SKIP = new Set(['INITIALIZE'])
|
|
35
|
+
|
|
36
|
+
// SYG222: the instance's STATE reducers, wrapped (see the header)
|
|
37
|
+
const WRAPPED = Symbol('SYG222')
|
|
38
|
+
|
|
39
|
+
export function watchMutation(component: any, action: string, fn: any): any {
|
|
40
|
+
if (typeof fn !== 'function' || fn[WRAPPED]) return fn
|
|
41
|
+
const wrapped: any = (state: any, ...rest: any[]) => {
|
|
42
|
+
if (!isPlainObject(state)) return fn(state, ...rest)
|
|
43
|
+
// the state's top level, and (G-214) each behavior slice's: a behavior reducer that mutates
|
|
44
|
+
// its slice gets the host state back unchanged (behaviors.ts), so state[key] looks the same
|
|
45
|
+
const snaps = [[state, ''], ...(component._uses || []).map(([k]: any) => [state[k], k + '.'])]
|
|
46
|
+
.filter(([o]) => isPlainObject(o)).map(([o, p]) => [o, p, Object.keys(o), Object.values(o)])
|
|
47
|
+
const out = fn(state, ...rest)
|
|
48
|
+
if (out === state) {
|
|
49
|
+
const changed = snaps.flatMap(([o, p, keys, values]: any) => keys.filter((k: string, i: number) => !(k in o) || o[k] !== values[i])
|
|
50
|
+
.concat(Object.keys(o).filter(k => !keys.includes(k))).map((k: string) => p + k))
|
|
51
|
+
const name = nameOf(component)
|
|
52
|
+
if (changed.length && once(`SYG222:${name}:${action}`)) {
|
|
53
|
+
const list = changed.map(k => `'${k}'`).join(', ')
|
|
54
|
+
devReport('SYG222', {
|
|
55
|
+
component,
|
|
56
|
+
message: `The STATE reducer for '${action}' changed ${list} in place and returned the same object. Returning the object a reducer got means "no change" (as ABORT), so the change is ignored and nothing re-renders`,
|
|
57
|
+
fix: `Return a new object: ${changed[0].includes('.') ? `(slice, data) => ({ ...slice, ${changed[0].split('.')[1]}: … })` : `(state, data) => ({ ...state, ${changed[0]}: … })`}. To write updates as mutations, wrap the reducer in immer's produce()`,
|
|
58
|
+
data: {action, keys: changed},
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return out
|
|
63
|
+
}
|
|
64
|
+
wrapped[WRAPPED] = true
|
|
65
|
+
// checks that read reducer source (reply actions) see the original; so do the action log
|
|
66
|
+
// and t.explain() (ORIGINAL)
|
|
67
|
+
wrapped.toString = () => fn.toString()
|
|
68
|
+
wrapped[ORIGINAL] = fn
|
|
69
|
+
return wrapped
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const stateCheck: DiagnosticCheck = {
|
|
73
|
+
id: 'state',
|
|
74
|
+
|
|
75
|
+
// SYG222: checks/next.ts wraps the STATE reducers (watchMutation) through wrapHandler
|
|
76
|
+
|
|
77
|
+
onModel(component) {
|
|
78
|
+
// SYG238 (G-393): the initial state is set at construction (D165), before onModel
|
|
79
|
+
const n = component?.__next
|
|
80
|
+
if (!n?.isRoot || !n.def?.model || n.state !== undefined) return
|
|
81
|
+
const name = nameOf(component)
|
|
82
|
+
if (!once(`SYG238:${name}`)) return
|
|
83
|
+
devReport('SYG238', {
|
|
84
|
+
component,
|
|
85
|
+
message: `${name} is a root with a model but no initialState: its state is undefined, so it renders nothing until an action sets one`,
|
|
86
|
+
fix: `Add ${name}.initialState = { … } (the root's start state; a child gets its state from its parent instead)`,
|
|
87
|
+
data: {},
|
|
88
|
+
})
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
onReducer(component, action, prevState, nextState) {
|
|
92
|
+
if (typeof action !== 'string' || action.startsWith('__') || SKIP.has(action)) return
|
|
93
|
+
const name = nameOf(component)
|
|
94
|
+
|
|
95
|
+
if (nextState === undefined) {
|
|
96
|
+
// G-357: returning undefined from a Collection item's reducer is the documented way to
|
|
97
|
+
// remove the item: not reported. In another sub-component it is info, in the root (where
|
|
98
|
+
// it wipes the whole app state) a warning.
|
|
99
|
+
if (component?.__next?.kind === 'item' || !once(`SYG202:${name}:${action}`)) return
|
|
100
|
+
const sub = !!component?.isSubComponent
|
|
101
|
+
report('SYG202', {
|
|
102
|
+
component,
|
|
103
|
+
severity: sub ? 'info' : 'warn',
|
|
104
|
+
message: `The STATE reducer for '${action}' returned undefined` +
|
|
105
|
+
(sub ? ' (only a Collection item removes itself this way)' : ''),
|
|
106
|
+
fix: `Return the new state, e.g. (state, data) => ({ ...state, ... }), or return ABORT to leave the state unchanged`,
|
|
107
|
+
data: {action},
|
|
108
|
+
})
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!isPlainObject(prevState) || !isPlainObject(nextState)) return
|
|
113
|
+
|
|
114
|
+
// SYG221 (G-143): set('city') spreads the string into the state: { ...state, 0: 'c', 1: 'i', ... }
|
|
115
|
+
const chars: string[] = []
|
|
116
|
+
while (typeof nextState[chars.length] === 'string' && nextState[chars.length].length === 1 && !(chars.length in prevState)) {
|
|
117
|
+
chars.push(nextState[chars.length])
|
|
118
|
+
}
|
|
119
|
+
if (chars.length > 0 && !(chars.length in nextState)) {
|
|
120
|
+
if (!once(`SYG221:${name}:${action}`)) return
|
|
121
|
+
const field = chars.join('')
|
|
122
|
+
const update = /^[A-Za-z_$][\w$]*$/.test(field)
|
|
123
|
+
? `set((state, ${field}) => ({ ${field} }))`
|
|
124
|
+
: `set((state, value) => ({ ['${field}']: value }))`
|
|
125
|
+
devReport('SYG221', {
|
|
126
|
+
component,
|
|
127
|
+
message: `The STATE reducer for '${action}' added the keys ${chars.map((_, i) => `'${i}'`).slice(0, 4).join(', ')}${chars.length > 4 ? ', …' : ''} ` +
|
|
128
|
+
`(one per character of '${field}'): set() was called with a string, set('${field}'). set() takes an object or a function`,
|
|
129
|
+
fix: `To store the action data in '${field}', pass a function: ${update}. For a fixed value pass an object: set({ ${field}: … })`,
|
|
130
|
+
data: {action, field},
|
|
131
|
+
})
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const calculated: Set<string> | null = component?._calculatedFieldNames || null
|
|
136
|
+
const dropped = Object.keys(prevState).filter(key =>
|
|
137
|
+
!(key in nextState) && prevState[key] !== undefined && !(calculated && calculated.has(key)))
|
|
138
|
+
if (dropped.length === 0 || !once(`SYG201:${name}:${action}`)) return
|
|
139
|
+
|
|
140
|
+
const list = dropped.map(k => `'${k}'`).join(', ')
|
|
141
|
+
report('SYG201', {
|
|
142
|
+
component,
|
|
143
|
+
message: `The STATE reducer for '${action}' returned a state without ${list}, which ${dropped.length === 1 ? 'was' : 'were'} in the previous state (likely a missing ...state spread)`,
|
|
144
|
+
fix: `Spread the previous state: (state, data) => ({ ...state, ... }). If removing ${dropped.length === 1 ? 'the key' : 'these keys'} is intended, ignore this`,
|
|
145
|
+
data: {action, droppedKeys: dropped},
|
|
146
|
+
})
|
|
147
|
+
},
|
|
148
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PLAN-4.5 D152: shared statics are frozen in dev (dev entry only; the core never freezes).
|
|
3
|
+
*
|
|
4
|
+
* Since P45-B a component's `initialState`, `model`, `context` and `calculated` statics are
|
|
5
|
+
* shared by all its instances (no per-instance deep copy). Mutating one in place (a reducer's
|
|
6
|
+
* `state.items.push(x)` on the initial state, a view that writes into state) would change every
|
|
7
|
+
* instance, and every later one. With diagnostics on, `initialState` is deep-frozen (its plain
|
|
8
|
+
* objects and arrays; class instances such as Map or Date are left as they are) and the other
|
|
9
|
+
* statics are frozen at the top level, so such a mutation throws a TypeError where it happens
|
|
10
|
+
* (in a reducer: reported as SYG216 with the error attached; in a view: SYG406).
|
|
11
|
+
*
|
|
12
|
+
* Frozen: the component function's own statics, and the options given to component() (G-280:
|
|
13
|
+
* `component({ view, model, initialState })`; the instance's `_o`, the values before behaviors or
|
|
14
|
+
* persist() made per-instance copies). Not frozen: the instance's values (G-268), and an
|
|
15
|
+
* initial state a wrapper built from its caller's objects (G-275: marked with `owned()` by
|
|
16
|
+
* sygnal/element (host props), Vike (pageContext.data, hydrated state) and renderComponent's
|
|
17
|
+
* `initialState` option; the caller may still change those). Mechanism: onIntent, which runs
|
|
18
|
+
* during construction, before the model is wired.
|
|
19
|
+
*/
|
|
20
|
+
import type {DiagnosticCheck} from '../index'
|
|
21
|
+
import {OWNED} from '../../owned'
|
|
22
|
+
|
|
23
|
+
const plain = (v: any): boolean => {
|
|
24
|
+
if (!v || typeof v != 'object') return false
|
|
25
|
+
const p = Object.getPrototypeOf(v)
|
|
26
|
+
return Array.isArray(v) || p === Object.prototype || p === null
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const deepFreeze = (v: any): void => {
|
|
30
|
+
if (!plain(v) || Object.isFrozen(v) || v[OWNED]) return
|
|
31
|
+
Object.freeze(v)
|
|
32
|
+
for (const k of Object.keys(v)) deepFreeze(v[k])
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const freeze = (o: any): void => {
|
|
36
|
+
if (!o || (typeof o != 'object' && typeof o != 'function')) return
|
|
37
|
+
deepFreeze(o.initialState)
|
|
38
|
+
for (const k of ['model', 'context', 'calculated']) if (plain(o[k])) Object.freeze(o[k])
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const staticsCheck: DiagnosticCheck = {
|
|
42
|
+
id: 'statics',
|
|
43
|
+
|
|
44
|
+
onIntent(component) {
|
|
45
|
+
if (typeof component?.view == 'function') freeze(component.view)
|
|
46
|
+
freeze(component?._o)
|
|
47
|
+
},
|
|
48
|
+
}
|