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,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser sources (PLAN-5 B-3, src/extra/browserSources.ts). Dev-entry-only codes
|
|
3
|
+
* (DEV_CODE_SEVERITY), reported when the driver calls
|
|
4
|
+
* `__SYGNAL_DIAGNOSTICS__.browserSource(code, name, spec, componentName, extra)`:
|
|
5
|
+
*
|
|
6
|
+
* SYG663 — a `browser` entry the driver can't start (no known kind, no `action`, an intersection
|
|
7
|
+
* / resize target that is neither a selector nor `true`), or a BROWSER sink command
|
|
8
|
+
* with an unknown method. It isn't started / run.
|
|
9
|
+
* SYG664 — a kind the driver wasn't made with (`makeBrowserDriverWith(...)` without its source).
|
|
10
|
+
* SYG665 — a source or command failed (permission denied, API missing, storage blocked) and its
|
|
11
|
+
* spec names no `error` action, so nothing else would say so.
|
|
12
|
+
* SYG668 — an intersection / resize declaration with nothing to observe: no DOM source reached
|
|
13
|
+
* the driver for the instance (extra 'dom'), or, in renderComponent's fake with
|
|
14
|
+
* `dom: 'real'`, its selector matches no element of the component (extra 'none').
|
|
15
|
+
*
|
|
16
|
+
* A component declaring `browser` with no browser driver is SYG643 (checks/timers.ts).
|
|
17
|
+
*/
|
|
18
|
+
import {bridge, devReport, once} from './shared'
|
|
19
|
+
|
|
20
|
+
const KINDS = ['intersection', 'resize', 'media', 'storage', 'visibility', 'online', 'geolocation']
|
|
21
|
+
const SOURCE: Record<string, string> = {
|
|
22
|
+
intersection: 'intersectionSource', resize: 'resizeSource', media: 'mediaSource', storage: 'storageSource',
|
|
23
|
+
visibility: 'visibilitySource', online: 'onlineSource', geolocation: 'geolocationSource',
|
|
24
|
+
copy: 'clipboardSource', paste: 'clipboardSource', setItem: 'storageSource', removeItem: 'storageSource',
|
|
25
|
+
}
|
|
26
|
+
const brief = (v: any) => { try { return JSON.stringify(v) } catch (_) { return String(v) } }
|
|
27
|
+
|
|
28
|
+
/** why a declared spec can't start ('' when it can; mirrors browserDriver's checks) */
|
|
29
|
+
export function browserSpecProblem(s: any): string {
|
|
30
|
+
if (!s || typeof s != 'object') return `is not an object ({ ${KINDS.join(' | ')}: …, action })`
|
|
31
|
+
const k = KINDS.find(k => k in s)
|
|
32
|
+
if (!k) return `names no source (one of ${KINDS.join(', ')})`
|
|
33
|
+
if (!s.action || typeof s.action != 'string') return 'has no action (a string)'
|
|
34
|
+
if ((k == 'intersection' || k == 'resize') && s[k] !== true && (typeof s[k] != 'string' || !s[k])) return `${k} must be a selector in the component or true (its root element), got ${brief(s[k])}`
|
|
35
|
+
return ''
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function onBrowserSource(code: string, name: string, spec: any, component?: string, extra?: any, commands?: string[]) {
|
|
39
|
+
if (code == 'SYG663' && extra === 0) {
|
|
40
|
+
// an unknown command (extra 0; `commands`: the driver's)
|
|
41
|
+
if (!once(`SYG663:${component}:cmd:${name}`)) return
|
|
42
|
+
devReport('SYG663', {
|
|
43
|
+
component,
|
|
44
|
+
message: `BROWSER command '${name}' is unknown; it is not run`,
|
|
45
|
+
fix: `Name the method as a key of the command: ${(commands || []).map(c => `{ ${c}: … }`).join(', ') || 'this driver takes no commands'}`,
|
|
46
|
+
data: {name, command: spec},
|
|
47
|
+
})
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
if (code == 'SYG663') {
|
|
51
|
+
if (!once(`SYG663:${component}:${name}:${brief(spec)}`)) return
|
|
52
|
+
devReport('SYG663', {
|
|
53
|
+
component,
|
|
54
|
+
message: `browser entry '${name}' ${browserSpecProblem(spec) || 'is invalid'}; it is not started`,
|
|
55
|
+
fix: `Declare ${name}: { media: '(prefers-color-scheme: dark)', action: 'DARK' }, { intersection: '.card', action: 'SEEN' }, … (falsy to stop it)`,
|
|
56
|
+
data: {name, spec},
|
|
57
|
+
})
|
|
58
|
+
} else if (code == 'SYG664') {
|
|
59
|
+
if (!once(`SYG664:${component}:${name}`)) return
|
|
60
|
+
devReport('SYG664', {
|
|
61
|
+
component,
|
|
62
|
+
message: `browser entry '${name}' uses the ${extra} source, which this browser driver wasn't made with; it is not started`,
|
|
63
|
+
fix: `Add it: makeBrowserDriverWith(${SOURCE[extra]}, …), or use makeBrowserDriver() (every source)`,
|
|
64
|
+
data: {name, spec, kind: extra},
|
|
65
|
+
})
|
|
66
|
+
} else if (code == 'SYG668') {
|
|
67
|
+
if (!once(`SYG668:${component}:${name}:${extra}`)) return
|
|
68
|
+
const k = KINDS.find(k => spec && k in spec) || 'intersection', target = spec?.[k]
|
|
69
|
+
devReport('SYG668', {
|
|
70
|
+
component,
|
|
71
|
+
message: extra == 'none'
|
|
72
|
+
? `browser entry '${name}' observes ${brief(target)}, which matches no element of the component; it reports nothing`
|
|
73
|
+
: `browser entry '${name}' (${k}) has no DOM to observe: the component's DOM source didn't reach the browser driver; it reports nothing`,
|
|
74
|
+
fix: extra == 'none'
|
|
75
|
+
? `Use a selector the component's own view renders (or true for its root element), and declare the entry only while that element exists`
|
|
76
|
+
: `Run the app with a DOM driver (run() with a mount point) and declare intersection / resize in a component's own \`browser\` static`,
|
|
77
|
+
data: {name, spec, reason: extra},
|
|
78
|
+
})
|
|
79
|
+
} else if (code == 'SYG665') {
|
|
80
|
+
if (!once(`SYG665:${component}:${name}:${extra?.name ?? extra?.code}`)) return
|
|
81
|
+
devReport('SYG665', {
|
|
82
|
+
component,
|
|
83
|
+
message: `browser ${spec && 'action' in spec ? `entry '${name}'` : `command '${name}'`} failed (${extra?.message || extra?.name || 'error'}) and names no error action`,
|
|
84
|
+
fix: `Name one to handle it, e.g. { ..., error: 'FAILED' }, and a model entry FAILED: (state, { message }) => ...`,
|
|
85
|
+
data: {name, spec, failure: extra},
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** install the hook the browser driver calls (removed by the returned function) */
|
|
91
|
+
export function installBrowserSourceHooks(): () => void {
|
|
92
|
+
const core = bridge()
|
|
93
|
+
if (!core) return () => {}
|
|
94
|
+
core.browserSource = onBrowserSource
|
|
95
|
+
return () => { if (core.browserSource === onBrowserSource) core.browserSource = undefined }
|
|
96
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SYG401 — <Collection> has no `from` and the whole state is not an array (warn).
|
|
3
|
+
* A defined `from` is checked by the core (D22).
|
|
4
|
+
*
|
|
5
|
+
* Reported through diagnostics in addition to the existing console output.
|
|
6
|
+
*
|
|
7
|
+
* Mechanism: the core has no hook at Collection instantiation, and the
|
|
8
|
+
* Collection marker vnode is gone by the time onRender sees the tree. So, at
|
|
9
|
+
* onModel (construction time, before the first render), this check wraps the
|
|
10
|
+
* component INSTANCE's instantiateCollection method (an own property that
|
|
11
|
+
* shadows the prototype method; Component calls `this.instantiateCollection`
|
|
12
|
+
* for every new Collection). The wrapper inspects the marker's props and the
|
|
13
|
+
* component's current state, then always calls the original. This only
|
|
14
|
+
* happens when the dev-only 'sygnal/diagnostics' entry is loaded and
|
|
15
|
+
* diagnostics are on. Reported once per component name and `from` value.
|
|
16
|
+
*/
|
|
17
|
+
import type {DiagnosticCheck} from '../index'
|
|
18
|
+
import {reportSafely, once, nameOf, isPlainObject} from './shared'
|
|
19
|
+
|
|
20
|
+
const describe = (v: any): string =>
|
|
21
|
+
v === null ? 'null' : v === undefined ? 'undefined' : Array.isArray(v) ? 'an array' : typeof v === 'object' ? 'an object' : `a ${typeof v}`
|
|
22
|
+
|
|
23
|
+
export function checkCollection(component: any, el: any): void {
|
|
24
|
+
const props = (el && el.data && el.data.props) || {}
|
|
25
|
+
const from = props.from
|
|
26
|
+
const of = props.of
|
|
27
|
+
const ofName = typeof of === 'function' ? (of.componentName || of.label || of.name || 'component') : String(of)
|
|
28
|
+
const name = nameOf(component)
|
|
29
|
+
|
|
30
|
+
let state = component.currentState
|
|
31
|
+
try { if (typeof component.addCalculated === 'function') state = component.addCalculated(state) } catch (_) {}
|
|
32
|
+
|
|
33
|
+
let problem: string | undefined
|
|
34
|
+
let fix = `Pass the name of an array field in ${name}'s state, e.g. <Collection of={${ofName}} from="items" />, and initialize that field to [] in initialState`
|
|
35
|
+
// Every defined `from` (string field, lens object, or an invalid value) is reported by
|
|
36
|
+
// the core itself (SYG401/SYG412, retrofitted in 1E) in every diagnostics mode (D22), so
|
|
37
|
+
// this check only covers a missing `from`.
|
|
38
|
+
if (from !== undefined) return
|
|
39
|
+
if (!Array.isArray(state) && !(state && Array.isArray(state.value))) {
|
|
40
|
+
problem = `has no 'from' prop, so it uses ${name}'s whole state, which is ${describe(state)}, not an array`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!problem || !once(`SYG401:${name}:undefined`)) return
|
|
44
|
+
reportSafely('SYG401', {
|
|
45
|
+
component,
|
|
46
|
+
message: `<Collection of={${ofName}}> ${problem}, so it renders no items`,
|
|
47
|
+
fix,
|
|
48
|
+
data: {from: 'undefined', of: ofName},
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const WRAPPED = '__sygnalDiagWrapped'
|
|
53
|
+
|
|
54
|
+
export const collectionsCheck: DiagnosticCheck = {
|
|
55
|
+
id: 'collections',
|
|
56
|
+
|
|
57
|
+
onModel(component) {
|
|
58
|
+
const original = component && component.instantiateCollection
|
|
59
|
+
if (typeof original !== 'function' || original[WRAPPED]) return
|
|
60
|
+
const wrapped = function (this: any, el: any, ...rest: any[]) {
|
|
61
|
+
try { checkCollection(this, el) } catch (_) {}
|
|
62
|
+
return original.call(this, el, ...rest)
|
|
63
|
+
}
|
|
64
|
+
;(wrapped as any)[WRAPPED] = true
|
|
65
|
+
component.instantiateCollection = wrapped
|
|
66
|
+
},
|
|
67
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PLAN-4 CT-1, runtime control checks (dev entry only; the static ones are in sygnal-check).
|
|
3
|
+
*
|
|
4
|
+
* SYG124 (error): a component passed where a control or selector is expected, as in
|
|
5
|
+
* `DOM.click(TodoItem)` or `DOM.select(TodoItem)`. Mechanism: the `sources` hook wraps the
|
|
6
|
+
* component's DOM source in a Proxy; `select()` (also chained: `DOM.select('.x').select(C)`)
|
|
7
|
+
* and every event shorthand check their first argument. A function that isn't a control is a
|
|
8
|
+
* component. Reported once per component and argument; the call then goes on to the DOM
|
|
9
|
+
* source, whose select() still throws ("expects the argument to be a string").
|
|
10
|
+
*
|
|
11
|
+
* SYG125 (error): controls are elements, not components.
|
|
12
|
+
* - a control given `.intent`, `.model` or `.initialState` (reported when it first renders,
|
|
13
|
+
* the statics are set after controls() returns);
|
|
14
|
+
* - a spec object whose `vnode()` returns anything but one element vnode (text, nothing, an
|
|
15
|
+
* array, a component). With diagnostics on, the render then throws an Error with the
|
|
16
|
+
* SYG125 text, so the component's error boundary names the control instead of failing on
|
|
17
|
+
* the stamp.
|
|
18
|
+
* Mechanism: controls() (src/extra/controls.ts) calls `__SYGNAL_DIAGNOSTICS__.control(control,
|
|
19
|
+
* vnode)` on every render; installControlHooks() publishes it on the core bridge.
|
|
20
|
+
*
|
|
21
|
+
* The same hook remembers which control rendered each vnode (controlOfVnode), so inspect() can
|
|
22
|
+
* list a component's controls with their element or kind.
|
|
23
|
+
*/
|
|
24
|
+
import type {DiagnosticCheck} from '../index'
|
|
25
|
+
import {bridge, devReport, once, onReset, nameOf, report} from './shared'
|
|
26
|
+
import {DEV_CODE_SEVERITY} from '../codes'
|
|
27
|
+
|
|
28
|
+
/** the key of a control, from its selector `[data-control="<Key>"]` */
|
|
29
|
+
export const controlKey = (control: any): string => String(control).slice(15, -2)
|
|
30
|
+
|
|
31
|
+
/** a control selector `[data-control="<Key>"]` as its key, else undefined */
|
|
32
|
+
export const keyOfSelector = (selector: string): string | undefined => {
|
|
33
|
+
const m = /^\[data-control="([^"]+)"\]$/.exec(selector)
|
|
34
|
+
return m ? m[1] : undefined
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const isControl = (x: any): boolean => typeof x == 'function' && !!x.__sygnalControl
|
|
38
|
+
|
|
39
|
+
const componentName = (fn: any): string =>
|
|
40
|
+
fn.componentName || fn.label || fn.name || 'the component'
|
|
41
|
+
|
|
42
|
+
let vnodeControls = new WeakMap<object, any>()
|
|
43
|
+
let staticsSeen = new WeakSet<object>()
|
|
44
|
+
onReset(() => {
|
|
45
|
+
vnodeControls = new WeakMap()
|
|
46
|
+
staticsSeen = new WeakSet()
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
/** the control that rendered a vnode (only while the dev entry is installed) */
|
|
50
|
+
export const controlOfVnode = (vnode: any): any =>
|
|
51
|
+
vnode && typeof vnode == 'object' ? vnodeControls.get(vnode) : undefined
|
|
52
|
+
|
|
53
|
+
const STATICS = ['intent', 'model', 'initialState']
|
|
54
|
+
|
|
55
|
+
const describe = (v: any): string =>
|
|
56
|
+
v === undefined || v === null ? String(v)
|
|
57
|
+
: Array.isArray(v) ? `an array of ${v.length}`
|
|
58
|
+
: typeof v != 'object' ? `a ${typeof v} (${JSON.stringify(v)})`
|
|
59
|
+
: typeof v.sel != 'string' ? (v.text !== undefined ? 'a text vnode' : 'a fragment')
|
|
60
|
+
// the pragma's component placeholder (the component function in data.c)
|
|
61
|
+
: v.data && typeof v.data.c == 'function' ? `the component <${v.sel}>`
|
|
62
|
+
: 'an element vnode'
|
|
63
|
+
|
|
64
|
+
function onControl(control: any, vnode: any): void {
|
|
65
|
+
const key = controlKey(control)
|
|
66
|
+
if (vnode && typeof vnode == 'object') vnodeControls.set(vnode, control)
|
|
67
|
+
|
|
68
|
+
if (!staticsSeen.has(control)) {
|
|
69
|
+
staticsSeen.add(control)
|
|
70
|
+
const given = STATICS.filter(s => control[s] != null)
|
|
71
|
+
if (given.length) {
|
|
72
|
+
devReport('SYG125', {
|
|
73
|
+
message: `The control ${key} has ${given.map(s => '.' + s).join(', ')}, but controls are elements, not components: ${given.length > 1 ? 'these statics are' : 'it is'} never used`,
|
|
74
|
+
fix: `Put the intent and model on the component that renders <${key}>, and listen to the control in its intent: DOM.click(${key}). For something with its own state and actions, write a component instead`,
|
|
75
|
+
data: {control: key, statics: given},
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const what = describe(vnode)
|
|
81
|
+
if (what == 'an element vnode') return
|
|
82
|
+
const kind = control.kind
|
|
83
|
+
// report() (not devReport): in 'error' mode its DiagnosticError is thrown into the render
|
|
84
|
+
const d = report('SYG125', {
|
|
85
|
+
severity: DEV_CODE_SEVERITY.SYG125,
|
|
86
|
+
message: `The control ${key}'s spec${kind ? ` (kind '${kind}')` : ''} vnode() returned ${what}, but it must return one element vnode`,
|
|
87
|
+
fix: `Return a single element built with the h argument: vnode(props, children, h) { return h('div', props, ...children) }`,
|
|
88
|
+
data: {control: key, kind, returned: what},
|
|
89
|
+
})
|
|
90
|
+
if (d) throw new Error(d.text)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Publish the control hook on the core bridge. Returns an uninstall function. */
|
|
94
|
+
export function installControlHooks(): () => void {
|
|
95
|
+
const core = bridge()
|
|
96
|
+
if (!core) return () => {}
|
|
97
|
+
core.control = onControl
|
|
98
|
+
return () => { if (core.control === onControl) core.control = undefined }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const wrapped = new WeakMap<object, any>()
|
|
102
|
+
|
|
103
|
+
/** a DOM source whose select() and event shorthands report SYG124 for a component argument */
|
|
104
|
+
function checkedDOM(component: any, dom: any): any {
|
|
105
|
+
if (!dom || typeof dom != 'object') return dom
|
|
106
|
+
let proxy = wrapped.get(dom)
|
|
107
|
+
if (proxy) return proxy
|
|
108
|
+
proxy = new Proxy(dom, {
|
|
109
|
+
get(t: any, k: any) {
|
|
110
|
+
const v = t[k]
|
|
111
|
+
if (typeof k != 'string' || typeof v != 'function' || (k != 'select' && k in t)) return v
|
|
112
|
+
return function (this: any, arg: any, ...rest: any[]) {
|
|
113
|
+
if (typeof arg == 'function' && !isControl(arg)) {
|
|
114
|
+
const name = nameOf(component), child = componentName(arg)
|
|
115
|
+
if (once(`SYG124:${name}:${child}:${k}`)) {
|
|
116
|
+
const call = k == 'select' ? `DOM.select(${child})` : `DOM.${k}(${child})`
|
|
117
|
+
devReport('SYG124', {
|
|
118
|
+
component,
|
|
119
|
+
message: `${name}.intent passes the component ${child} to ${call}, but a component is not an element: it has no element of its own to listen on, and its elements are isolated from ${name}`,
|
|
120
|
+
fix: `Handle the event in ${child} and send it up with PARENT, then read it here with CHILD.select(${child}); or render a control ${name} owns around it: const { ${child}Box } = controls({ ${child}Box: 'div' }), <${child}Box><${child} /></${child}Box>, DOM.click(${child}Box)`,
|
|
121
|
+
data: {component: child, method: k},
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const out = v.call(t, arg, ...rest)
|
|
126
|
+
return k == 'select' ? checkedDOM(component, out) : out
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
})
|
|
130
|
+
wrapped.set(dom, proxy)
|
|
131
|
+
return proxy
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export const controlsCheck: DiagnosticCheck = {
|
|
135
|
+
id: 'controls',
|
|
136
|
+
|
|
137
|
+
sources(component, sources) {
|
|
138
|
+
if (!sources || typeof Proxy != 'function') return
|
|
139
|
+
const domName = component?.DOMSourceName || 'DOM'
|
|
140
|
+
if (!sources[domName]) return
|
|
141
|
+
return new Proxy(sources, {
|
|
142
|
+
get(t: any, k: any) {
|
|
143
|
+
const v = t[k]
|
|
144
|
+
return k === domName ? checkedDOM(component, v) : v
|
|
145
|
+
},
|
|
146
|
+
})
|
|
147
|
+
},
|
|
148
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SYG421 — invalid `data` (dataset) key in a view (error, G-143).
|
|
3
|
+
*
|
|
4
|
+
* JSX `data={{ ... }}` becomes the vnode's `data.dataset` (a `data-x-y="..."` attribute is
|
|
5
|
+
* camelCased to 'xY' by the pragma, G-152), which the DOM driver writes with
|
|
6
|
+
* `element.dataset[key] = value`. The browser throws a SyntaxError DOMException for a key with
|
|
7
|
+
* a hyphen followed by a lower-case letter (`'task-id'`), and setAttribute() throws for a name
|
|
8
|
+
* with characters an attribute can't have (spaces, quotes, `=`, ...). The patch fails, so the
|
|
9
|
+
* app stops rendering, and the console shows only a bare "DOMException {}".
|
|
10
|
+
*
|
|
11
|
+
* Mechanism: onRender (before the DOM driver patches) walks the rendered vnode tree and checks
|
|
12
|
+
* every dataset key. Reported once per key; the mock DOM of renderComponent never throws, so
|
|
13
|
+
* this is also the only signal there.
|
|
14
|
+
*/
|
|
15
|
+
import type {DiagnosticCheck} from '../index'
|
|
16
|
+
import {devReport, once, nameOf} from './shared'
|
|
17
|
+
|
|
18
|
+
const camel = (key: string): string => key.replace(/-([a-z])/g, (_, c) => c.toUpperCase())
|
|
19
|
+
const BAD_CHAR = /[\s"'<>\/=`]/
|
|
20
|
+
|
|
21
|
+
function problem(key: string): string | undefined {
|
|
22
|
+
if (/-[a-z]/.test(key)) return `has a hyphen followed by a lower-case letter, which a dataset name can't have`
|
|
23
|
+
if (BAD_CHAR.test(key)) return `contains a character an HTML attribute name can't have`
|
|
24
|
+
return undefined
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function walk(vnode: any, visit: (dataset: Record<string, any>, sel: string) => void, depth = 0): void {
|
|
28
|
+
if (!vnode || typeof vnode != 'object' || depth > 500) return
|
|
29
|
+
const ds = vnode.data && vnode.data.dataset
|
|
30
|
+
if (ds && typeof ds == 'object') visit(ds, vnode.sel)
|
|
31
|
+
const children = vnode.children
|
|
32
|
+
if (Array.isArray(children)) for (const c of children) walk(c, visit, depth + 1)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const datasetCheck: DiagnosticCheck = {
|
|
36
|
+
id: 'dataset',
|
|
37
|
+
|
|
38
|
+
onRender(component, rootVnode) {
|
|
39
|
+
walk(rootVnode, (dataset, sel) => {
|
|
40
|
+
for (const key of Object.keys(dataset)) {
|
|
41
|
+
const why = problem(key)
|
|
42
|
+
if (!why || !once(`SYG421:${key}`)) continue
|
|
43
|
+
const good = camel(key).replace(new RegExp(BAD_CHAR.source, 'g'), '')
|
|
44
|
+
const attr = good.replace(/[A-Z]/g, c => '-' + c.toLowerCase())
|
|
45
|
+
const name = good || 'name'
|
|
46
|
+
devReport('SYG421', {
|
|
47
|
+
component,
|
|
48
|
+
message: `The view of ${nameOf(component)} renders the data key '${key}'${sel ? ` on <${String(sel).split(/[.#]/)[0]}>` : ''} ` +
|
|
49
|
+
`(from data={{ '${key}': … }}), but '${key}' ${why}. ` +
|
|
50
|
+
`Writing it throws a DOMException, which can stop rendering`,
|
|
51
|
+
fix: `Use a camelCase key in the data prop: data={{ ${name}: … }} renders the attribute data-${attr || 'name'}; read it with .data('${name}')`,
|
|
52
|
+
data: {key, suggested: good, attribute: `data-${attr}`},
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
},
|
|
57
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SYG104 — isolation boundary: an intent selector matches nothing in the
|
|
3
|
+
* component's own scope, but matches elements inside a child
|
|
4
|
+
* component / Collection item (warn).
|
|
5
|
+
* SYG103 — an intent selector has never matched a rendered element (info,
|
|
6
|
+
* escalated to warn after >= 3 renders AND 2 s of render-idle time
|
|
7
|
+
* without a match).
|
|
8
|
+
*
|
|
9
|
+
* Mechanism:
|
|
10
|
+
* 1. MainDOMSource.select() calls the onSelector hook with the source it was
|
|
11
|
+
* called on. Its namespace = the component's isolation scopes + any
|
|
12
|
+
* earlier select() selectors. Selectors are queued as "pending".
|
|
13
|
+
* 2. onIntent (right after the intent function ran, still inside the
|
|
14
|
+
* component's constructor) claims the pending selectors whose isolation
|
|
15
|
+
* scopes equal the component's DOM source scopes, and records
|
|
16
|
+
* scopes -> component name (used to name the child for SYG104).
|
|
17
|
+
* 3. onRender schedules a sweep after `settleMs` (onRender sees the vnode
|
|
18
|
+
* before the DOM patch; nested components patch a few ms later). The
|
|
19
|
+
* sweep finds the component's root element through the DOM driver's
|
|
20
|
+
* IsolateModule and runs each selector with querySelectorAll, splitting
|
|
21
|
+
* the matches with the DOM driver's own ScopeChecker (the same test the
|
|
22
|
+
* EventDelegator uses to deliver events) into "in scope" and "inside a
|
|
23
|
+
* total-isolation boundary" (child component).
|
|
24
|
+
* 4. "Ever matched" is tracked per component NAME + selector, so a selector
|
|
25
|
+
* that matches in one Collection item is fine for all of them.
|
|
26
|
+
*
|
|
27
|
+
* SYG103 noise control (conditional rendering is normal): a selector whose
|
|
28
|
+
* class/id/tag tokens all appear in the component's view source is assumed
|
|
29
|
+
* to be conditionally rendered and is not reported. A selector that does not
|
|
30
|
+
* appear in the view (typo, or an element rendered elsewhere) is reported as
|
|
31
|
+
* info on the first sweep, and escalated per the rule above.
|
|
32
|
+
*
|
|
33
|
+
* Only real DOM sources (with an IsolateModule) are checked; renderComponent's
|
|
34
|
+
* mock DOM never calls onSelector.
|
|
35
|
+
*/
|
|
36
|
+
import type {DiagnosticCheck} from '../index'
|
|
37
|
+
import {ScopeChecker} from '../../../cycle/dom/ScopeChecker'
|
|
38
|
+
import type {Scope} from '../../../cycle/dom/isolate'
|
|
39
|
+
import {reportSafely, once, onReset, timing, nameOf} from './shared'
|
|
40
|
+
import {keyOfSelector} from './controls'
|
|
41
|
+
|
|
42
|
+
interface Tracked {
|
|
43
|
+
name: string
|
|
44
|
+
source: any
|
|
45
|
+
selectors: string[]
|
|
46
|
+
renders: number
|
|
47
|
+
settleTimer?: any
|
|
48
|
+
idleTimer?: any
|
|
49
|
+
/** selectors already explained by SYG104 */
|
|
50
|
+
crossed: Set<string>
|
|
51
|
+
disposed?: boolean
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const scopesOf = (ns: Scope[]): Scope[] => ns.filter(s => s.type !== 'selector')
|
|
55
|
+
const keyOf = (scopes: Scope[]): string => scopes.map(s => `${s.type[0]}:${s.scope}`).join('/')
|
|
56
|
+
|
|
57
|
+
let pending: Array<{key: string; selector: string}> = []
|
|
58
|
+
let tracked = new WeakMap<object, Tracked>()
|
|
59
|
+
/** isolation-scope key -> the component instance that owns it (latest wins) */
|
|
60
|
+
let owners = new Map<string, any>()
|
|
61
|
+
const ownerKeys = new WeakMap<object, string>()
|
|
62
|
+
let matched = new Set<string>()
|
|
63
|
+
let viewTokens = new WeakMap<object, string>()
|
|
64
|
+
|
|
65
|
+
onReset(() => {
|
|
66
|
+
pending = []
|
|
67
|
+
tracked = new WeakMap()
|
|
68
|
+
owners = new Map()
|
|
69
|
+
matched = new Set()
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
const unref = (t: any) => { if (t && typeof t.unref === 'function') t.unref(); return t }
|
|
73
|
+
|
|
74
|
+
function viewSource(component: any): string {
|
|
75
|
+
const view = component && component.view
|
|
76
|
+
if (typeof view !== 'function') return ''
|
|
77
|
+
let src = viewTokens.get(view)
|
|
78
|
+
if (src === undefined) {
|
|
79
|
+
try { src = Function.prototype.toString.call(view) } catch (_) { src = '' }
|
|
80
|
+
viewTokens.set(view, src as string)
|
|
81
|
+
}
|
|
82
|
+
return src as string
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** class / id / tag tokens of a selector: '.a .b-c > #d' -> ['a', 'b-c', 'd'] */
|
|
86
|
+
const tokensOf = (selector: string): string[] =>
|
|
87
|
+
(selector.replace(/\[[^\]]*\]|:[\w-]+(\([^)]*\))?/g, ' ').match(/[A-Za-z_][\w-]*/g) || [] as string[])
|
|
88
|
+
// CT-1: a control's key (`<Add>` / `h(Add)` in the view source)
|
|
89
|
+
.concat(Array.from(selector.matchAll(/\[data-control="([^"]+)"\]/g), m => m[1]))
|
|
90
|
+
|
|
91
|
+
function appearsInView(component: any, selector: string): boolean {
|
|
92
|
+
const src = viewSource(component)
|
|
93
|
+
if (!src) return false
|
|
94
|
+
const tokens = tokensOf(selector)
|
|
95
|
+
if (!tokens.length) return false
|
|
96
|
+
return tokens.every(t => new RegExp(`(^|[^\\w-])${t.replace(/[-]/g, '\\-')}($|[^\\w-])`).test(src))
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// G-144: a fragment-rooted component has several root elements
|
|
100
|
+
function rootElementsOf(t: Tracked): Element[] {
|
|
101
|
+
try {
|
|
102
|
+
const im = t.source._isolateModule
|
|
103
|
+
return (im && im.getElements(scopesOf(t.source.namespace))) || []
|
|
104
|
+
} catch (_) {
|
|
105
|
+
return []
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function childNameFor(t: Tracked, elements: Element[]): string | undefined {
|
|
110
|
+
const im = t.source._isolateModule
|
|
111
|
+
const depth = scopesOf(t.source.namespace).length
|
|
112
|
+
for (const el of elements) {
|
|
113
|
+
let ns: Scope[] | undefined
|
|
114
|
+
try { ns = im.getNamespace(el) } catch (_) { ns = undefined }
|
|
115
|
+
if (!ns) continue
|
|
116
|
+
for (let i = depth; i < ns.length; i++) {
|
|
117
|
+
if (ns[i].type !== 'total') continue
|
|
118
|
+
const owner = owners.get(keyOf(ns.slice(0, i + 1)))
|
|
119
|
+
if (owner) return nameOf(owner)
|
|
120
|
+
break
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return undefined
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function report103(component: any, t: Tracked, selector: string, severity: 'info' | 'warn'): void {
|
|
127
|
+
if (!once(`SYG103:${severity}:${t.name}:${selector}`)) return
|
|
128
|
+
reportSafely('SYG103', {
|
|
129
|
+
component,
|
|
130
|
+
severity,
|
|
131
|
+
message: `DOM.select('${selector}') in ${t.name} has not matched any element ${t.name} rendered` +
|
|
132
|
+
(severity === 'warn' ? ` (after ${t.renders} renders)` : ''),
|
|
133
|
+
fix: `Check the selector against the view's className/id. If the element is rendered by a child component, handle the event inside that child`,
|
|
134
|
+
data: {selector, renders: t.renders},
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function sweep(component: any, t: Tracked, escalate: boolean): void {
|
|
139
|
+
if (t.disposed) return
|
|
140
|
+
const roots = rootElementsOf(t).filter(r => typeof r.querySelectorAll === 'function')
|
|
141
|
+
if (!roots.length) return
|
|
142
|
+
const im = t.source._isolateModule
|
|
143
|
+
const checker = new ScopeChecker(t.source.namespace, im)
|
|
144
|
+
let unmatched = false
|
|
145
|
+
|
|
146
|
+
for (const selector of t.selectors) {
|
|
147
|
+
const matchKey = `${t.name}\u0000${selector}`
|
|
148
|
+
if (matched.has(matchKey) || t.crossed.has(selector)) continue
|
|
149
|
+
let all: Element[]
|
|
150
|
+
try {
|
|
151
|
+
all = []
|
|
152
|
+
for (const root of roots) {
|
|
153
|
+
all.push(...Array.prototype.slice.call(root.querySelectorAll(selector)))
|
|
154
|
+
if (root.matches(selector)) all.push(root)
|
|
155
|
+
}
|
|
156
|
+
} catch (_) {
|
|
157
|
+
continue // invalid selector: the DOM driver reports that itself
|
|
158
|
+
}
|
|
159
|
+
const own = all.filter(el => { try { return checker.isDirectlyInScope(el) } catch (_) { return false } })
|
|
160
|
+
if (own.length) { matched.add(matchKey); continue }
|
|
161
|
+
|
|
162
|
+
if (all.length) {
|
|
163
|
+
t.crossed.add(selector)
|
|
164
|
+
if (!once(`SYG104:${t.name}:${selector}`)) continue
|
|
165
|
+
const child = childNameFor(t, all) || 'a child component'
|
|
166
|
+
// CT-1: a control is named by its identifier
|
|
167
|
+
const control = keyOfSelector(selector)
|
|
168
|
+
reportSafely('SYG104', {
|
|
169
|
+
component,
|
|
170
|
+
message: `${control ? `The control ${control}` : `DOM.select('${selector}')`} in ${t.name} matches elements inside ${child} (isolated), so ${t.name} never receives their events`,
|
|
171
|
+
fix: `Handle the event in ${child}${control ? ` (DOM.<event>(${control}) in its intent)` : ''} and send it up with PARENT (read it here with CHILD.select(${child})), or use EVENTS`,
|
|
172
|
+
data: control ? {selector, child, control} : {selector, child},
|
|
173
|
+
})
|
|
174
|
+
continue
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (appearsInView(component, selector)) continue
|
|
178
|
+
unmatched = true
|
|
179
|
+
if (escalate && t.renders >= timing.minRenders) report103(component, t, selector, 'warn')
|
|
180
|
+
else report103(component, t, selector, 'info')
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (unmatched && !escalate) {
|
|
184
|
+
clearTimeout(t.idleTimer)
|
|
185
|
+
t.idleTimer = unref(setTimeout(() => { t.idleTimer = undefined; sweep(component, t, true) }, timing.idleMs))
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* inspect (2B, read-only): a component's intent selectors on a real DOM source
|
|
191
|
+
* and whether a sweep has seen them match (true), only match inside a child
|
|
192
|
+
* (crossed), or neither yet. undefined when the component isn't tracked.
|
|
193
|
+
*/
|
|
194
|
+
export function selectorStatus(component: any): Array<{selector: string; matched: boolean | null; crossed: boolean}> | undefined {
|
|
195
|
+
const t = tracked.get(component)
|
|
196
|
+
return t && t.selectors.map(selector => ({
|
|
197
|
+
selector,
|
|
198
|
+
matched: matched.has(`${t.name}\u0000${selector}`) ? true : t.crossed.has(selector) ? false : null,
|
|
199
|
+
crossed: t.crossed.has(selector),
|
|
200
|
+
}))
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export const domCheck: DiagnosticCheck = {
|
|
204
|
+
id: 'dom',
|
|
205
|
+
|
|
206
|
+
onSelector(source, selector) {
|
|
207
|
+
const ns: Scope[] | undefined = source && source.namespace
|
|
208
|
+
if (!Array.isArray(ns) || typeof selector !== 'string' || !selector) return
|
|
209
|
+
const prefix = ns.filter(s => s.type === 'selector').map(s => s.scope).join(' ')
|
|
210
|
+
pending.push({key: keyOf(scopesOf(ns)), selector: prefix ? `${prefix} ${selector}` : selector})
|
|
211
|
+
if (pending.length > 1000) pending.shift()
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
onIntent(component) {
|
|
215
|
+
const claimed = pending
|
|
216
|
+
pending = []
|
|
217
|
+
const source = component && component.sources && component.sources[component.DOMSourceName || 'DOM']
|
|
218
|
+
if (!source || !source._isolateModule || !Array.isArray(source.namespace)) return
|
|
219
|
+
const key = keyOf(scopesOf(source.namespace))
|
|
220
|
+
const name = nameOf(component)
|
|
221
|
+
owners.set(key, component)
|
|
222
|
+
ownerKeys.set(component, key)
|
|
223
|
+
const selectors = [...new Set(claimed.filter(p => p.key === key).map(p => p.selector))]
|
|
224
|
+
if (selectors.length) tracked.set(component, {name, source, selectors, renders: 0, crossed: new Set()})
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
onRender(component) {
|
|
228
|
+
const t = tracked.get(component)
|
|
229
|
+
if (!t || t.disposed) return
|
|
230
|
+
t.renders++
|
|
231
|
+
clearTimeout(t.idleTimer)
|
|
232
|
+
t.idleTimer = undefined
|
|
233
|
+
if (t.settleTimer) return
|
|
234
|
+
t.settleTimer = unref(setTimeout(() => {
|
|
235
|
+
t.settleTimer = undefined
|
|
236
|
+
sweep(component, t, false)
|
|
237
|
+
}, timing.settleMs))
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
onDispose(component) {
|
|
241
|
+
const key = ownerKeys.get(component)
|
|
242
|
+
if (key !== undefined && owners.get(key) === component) owners.delete(key)
|
|
243
|
+
const t = tracked.get(component)
|
|
244
|
+
if (!t) return
|
|
245
|
+
t.disposed = true
|
|
246
|
+
clearTimeout(t.settleTimer)
|
|
247
|
+
clearTimeout(t.idleTimer)
|
|
248
|
+
tracked.delete(component)
|
|
249
|
+
},
|
|
250
|
+
}
|