sygnal 5.4.0 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +646 -0
- package/README.md +24 -9
- package/dist/astro/client.cjs.js +16 -9
- package/dist/astro/client.cjs.js.map +1 -1
- package/dist/astro/client.mjs +16 -9
- package/dist/astro/client.mjs.map +1 -1
- package/dist/astro/index.cjs.js +362 -91
- package/dist/astro/index.cjs.js.map +1 -1
- package/dist/astro/index.mjs +362 -92
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/server.cjs.js +3396 -165
- package/dist/astro/server.cjs.js.map +1 -1
- package/dist/astro/server.mjs +3396 -165
- package/dist/astro/server.mjs.map +1 -1
- package/dist/devtools.cjs.js +1431 -0
- package/dist/devtools.cjs.js.map +1 -0
- package/dist/devtools.esm.js +1419 -0
- package/dist/devtools.esm.js.map +1 -0
- package/dist/diagnostics.cjs.js +3032 -296
- package/dist/diagnostics.cjs.js.map +1 -1
- package/dist/diagnostics.esm.js +3032 -296
- package/dist/diagnostics.esm.js.map +1 -1
- package/dist/element.cjs.js +230 -0
- package/dist/element.cjs.js.map +1 -0
- package/dist/element.esm.js +228 -0
- package/dist/element.esm.js.map +1 -0
- package/dist/guide/accessibility.md +257 -0
- package/dist/guide/adapters.md +221 -0
- package/dist/guide/behaviors.md +312 -0
- package/dist/guide/browser-sources.md +203 -0
- package/dist/guide/drag-and-drop.md +342 -0
- package/dist/guide/element-commands.md +306 -0
- package/dist/guide/error-boundaries.md +169 -0
- package/dist/guide/forms-reference.md +381 -0
- package/dist/guide/forms.md +250 -0
- package/dist/guide/http.md +299 -0
- package/dist/guide/inputs.md +173 -0
- package/dist/guide/persistence.md +245 -0
- package/dist/guide/recipes/carousel.md +155 -0
- package/dist/guide/recipes/charts.md +177 -0
- package/dist/guide/recipes/code-editor.md +135 -0
- package/dist/guide/recipes/data-grid.md +155 -0
- package/dist/guide/recipes/data-table.md +196 -0
- package/dist/guide/recipes/i18n.md +222 -0
- package/dist/guide/recipes/icons.md +115 -0
- package/dist/guide/recipes/overview.md +33 -0
- package/dist/guide/recipes/rich-text.md +154 -0
- package/dist/guide/resources.md +471 -0
- package/dist/guide/ssr.md +255 -0
- package/dist/guide/timers.md +178 -0
- package/dist/guide/ui/accordion.md +93 -0
- package/dist/guide/ui/combobox.md +144 -0
- package/dist/guide/ui/dialog.md +124 -0
- package/dist/guide/ui/disclosure.md +60 -0
- package/dist/guide/ui/menu.md +125 -0
- package/dist/guide/ui/overview.md +166 -0
- package/dist/guide/ui/popover.md +101 -0
- package/dist/guide/ui/select.md +114 -0
- package/dist/guide/ui/tabs.md +161 -0
- package/dist/guide/ui/toaster.md +152 -0
- package/dist/guide/ui/tooltip.md +103 -0
- package/dist/guide/undo.md +178 -0
- package/dist/guide/virtual-collections.md +183 -0
- package/dist/guide/web-components.md +179 -0
- package/dist/guide/widgets.md +217 -0
- package/dist/index.cjs.js +13178 -5769
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +2908 -276
- package/dist/index.esm.js +13149 -5779
- package/dist/index.esm.js.map +1 -1
- package/dist/jsx-dev-runtime.cjs.js +26 -334
- package/dist/jsx-dev-runtime.cjs.js.map +1 -1
- package/dist/jsx-dev-runtime.esm.js +19 -327
- package/dist/jsx-dev-runtime.esm.js.map +1 -1
- package/dist/jsx-runtime.cjs.js +26 -334
- package/dist/jsx-runtime.cjs.js.map +1 -1
- package/dist/jsx-runtime.esm.js +19 -327
- package/dist/jsx-runtime.esm.js.map +1 -1
- package/dist/jsx.cjs.js +10 -314
- package/dist/jsx.cjs.js.map +1 -1
- package/dist/jsx.esm.js +8 -315
- package/dist/jsx.esm.js.map +1 -1
- package/dist/react.cjs.js +117 -0
- package/dist/react.cjs.js.map +1 -0
- package/dist/react.esm.js +115 -0
- package/dist/react.esm.js.map +1 -0
- package/dist/shims/globalthis.cjs +20 -0
- package/dist/sygnal.min.js +1 -1
- package/dist/sygnal.min.js.map +1 -1
- package/dist/ui-combobox.cjs.js +169 -0
- package/dist/ui-combobox.cjs.js.map +1 -0
- package/dist/ui-combobox.esm.js +148 -0
- package/dist/ui-combobox.esm.js.map +1 -0
- package/dist/ui-menu.cjs.js +87 -0
- package/dist/ui-menu.cjs.js.map +1 -0
- package/dist/ui-menu.esm.js +66 -0
- package/dist/ui-menu.esm.js.map +1 -0
- package/dist/ui-select.cjs.js +104 -0
- package/dist/ui-select.cjs.js.map +1 -0
- package/dist/ui-select.esm.js +83 -0
- package/dist/ui-select.esm.js.map +1 -0
- package/dist/ui.cjs.js +738 -0
- package/dist/ui.cjs.js.map +1 -0
- package/dist/ui.esm.js +727 -0
- package/dist/ui.esm.js.map +1 -0
- package/dist/vike/ClientOnly.cjs.js +4 -14
- package/dist/vike/ClientOnly.cjs.js.map +1 -1
- package/dist/vike/ClientOnly.mjs +4 -14
- package/dist/vike/ClientOnly.mjs.map +1 -1
- package/dist/vike/{+config.js → config/+config.js} +9 -1
- package/dist/vike/config/+config.js.map +1 -0
- package/dist/vike/config/package.json +6 -0
- package/dist/vike/onRenderClient.cjs.js +165 -95
- package/dist/vike/onRenderClient.cjs.js.map +1 -1
- package/dist/vike/onRenderClient.mjs +165 -95
- package/dist/vike/onRenderClient.mjs.map +1 -1
- package/dist/vike/onRenderHtml.cjs.js +59 -24
- package/dist/vike/onRenderHtml.cjs.js.map +1 -1
- package/dist/vike/onRenderHtml.mjs +60 -25
- package/dist/vike/onRenderHtml.mjs.map +1 -1
- package/dist/vite/plugin.cjs.js +323 -88
- package/dist/vite/plugin.cjs.js.map +1 -1
- package/dist/vite/plugin.mjs +323 -89
- package/dist/vite/plugin.mjs.map +1 -1
- package/dist/zag.cjs.js +408 -0
- package/dist/zag.cjs.js.map +1 -0
- package/dist/zag.esm.js +405 -0
- package/dist/zag.esm.js.map +1 -0
- package/llms.txt +166 -103
- package/package.json +98 -14
- package/src/astro/client.ts +27 -16
- package/src/astro/index.d.ts +24 -0
- package/src/astro/index.ts +69 -3
- package/src/astro/server.ts +8 -2
- package/src/collection.ts +6 -93
- package/src/core/actions.ts +134 -0
- package/src/core/cell.ts +202 -0
- package/src/core/debug.ts +23 -0
- package/src/core/define.ts +242 -0
- package/src/core/hooks.ts +314 -0
- package/src/core/hosts/collection.ts +325 -0
- package/src/core/hosts/switchable.ts +146 -0
- package/src/core/instance.ts +592 -0
- package/src/core/markers/clientonly.ts +13 -0
- package/src/core/markers/lazy.ts +40 -0
- package/src/core/markers/portal.ts +96 -0
- package/src/core/markers/suspense.ts +41 -0
- package/src/core/markers/transition.ts +70 -0
- package/src/core/registry.ts +25 -0
- package/src/core/runtime.ts +573 -0
- package/src/core/statics.ts +104 -0
- package/src/core/teardown.ts +60 -0
- package/src/core/view.ts +37 -0
- package/src/cycle/dom/DocumentDOMSource.ts +13 -8
- package/src/cycle/dom/ElementFinder.ts +7 -9
- package/src/cycle/dom/EventDelegator.ts +174 -223
- package/src/cycle/dom/IsolateModule.ts +65 -35
- package/src/cycle/dom/MainDOMSource.ts +9 -2
- package/src/cycle/dom/PriorityQueue.ts +4 -2
- package/src/cycle/dom/SymbolTree.ts +18 -47
- package/src/cycle/dom/classNameModule.ts +9 -7
- package/src/cycle/dom/controlledInputModule.ts +23 -6
- package/src/cycle/dom/enrichEventStream.ts +25 -47
- package/src/cycle/dom/fragment.ts +7 -0
- package/src/cycle/dom/isolate.ts +30 -27
- package/src/cycle/dom/makeDOMDriver.ts +115 -56
- package/src/cycle/dom/mockDOMSource.ts +21 -1
- package/src/cycle/dom/modules.ts +16 -3
- package/src/cycle/dom/propsModule.ts +65 -0
- package/src/cycle/dom/selectModule.ts +22 -17
- package/src/cycle/dom/snabbdom.ts +1 -6
- package/src/cycle/dom/thunk.ts +3 -0
- package/src/cycle/dom/utils.ts +98 -0
- package/src/cycle/dom/viewTransition.ts +43 -0
- package/src/cycle/state/StateSource.ts +19 -3
- package/src/cycle/state/objIsEqual.ts +50 -0
- package/src/cycle/state/types.ts +0 -10
- package/src/defineComponent.ts +34 -0
- package/src/devtools.d.ts +151 -0
- package/src/devtools.ts +34 -0
- package/src/element.d.ts +59 -0
- package/src/element.ts +235 -0
- package/src/extra/backoff.ts +9 -0
- package/src/extra/behaviors.ts +169 -0
- package/src/extra/browserSignals.ts +23 -0
- package/src/extra/browserSources.ts +326 -0
- package/src/extra/command.ts +3 -3
- package/src/extra/controls.ts +47 -0
- package/src/extra/copyAsTest.ts +286 -0
- package/src/extra/devtools.ts +101 -31
- package/src/extra/devtoolsActions.ts +379 -0
- package/src/extra/devtoolsHook.ts +9 -0
- package/src/extra/devtoolsNext.ts +83 -0
- package/src/extra/diagnostics/checks/actionLog.ts +108 -0
- package/src/extra/diagnostics/checks/behaviors.ts +49 -0
- package/src/extra/diagnostics/checks/browserSources.ts +96 -0
- package/src/extra/diagnostics/checks/collections.ts +1 -1
- package/src/extra/diagnostics/checks/controls.ts +148 -0
- package/src/extra/diagnostics/checks/dataset.ts +57 -0
- package/src/extra/diagnostics/checks/dom.ts +20 -11
- package/src/extra/diagnostics/checks/elementCommands.ts +175 -0
- package/src/extra/diagnostics/checks/events.ts +17 -2
- package/src/extra/diagnostics/checks/fetch.ts +135 -0
- package/src/extra/diagnostics/checks/forms.ts +163 -0
- package/src/extra/diagnostics/checks/index.ts +97 -4
- package/src/extra/diagnostics/checks/inspect.ts +140 -32
- package/src/extra/diagnostics/checks/next.ts +417 -0
- package/src/extra/diagnostics/checks/persist.ts +54 -0
- package/src/extra/diagnostics/checks/props.ts +10 -5
- package/src/extra/diagnostics/checks/public.d.ts +98 -7
- package/src/extra/diagnostics/checks/replies.ts +128 -0
- package/src/extra/diagnostics/checks/router.ts +100 -0
- package/src/extra/diagnostics/checks/rxjsHints.ts +1 -1
- package/src/extra/diagnostics/checks/shared.ts +86 -2
- package/src/extra/diagnostics/checks/shorthand.ts +115 -0
- package/src/extra/diagnostics/checks/sortable.ts +129 -0
- package/src/extra/diagnostics/checks/state.ts +100 -7
- package/src/extra/diagnostics/checks/statics.ts +48 -0
- package/src/extra/diagnostics/checks/strict.ts +10 -67
- package/src/extra/diagnostics/checks/timers.ts +80 -0
- package/src/extra/diagnostics/checks/viewTransitions.ts +47 -0
- package/src/extra/diagnostics/checks/virtual.ts +73 -0
- package/src/extra/diagnostics/checks/widgets.ts +109 -0
- package/src/extra/diagnostics/checks/wiring.ts +66 -10
- package/src/extra/diagnostics/codes.ts +268 -23
- package/src/extra/diagnostics/index.ts +34 -9
- package/src/extra/diagnostics/legacy.ts +17 -0
- package/src/extra/driverFactories.ts +83 -19
- package/src/extra/elementCommands.ts +53 -0
- package/src/extra/fetchDriver.ts +519 -0
- package/src/extra/focusWithin.ts +27 -0
- package/src/extra/form.ts +268 -0
- package/src/extra/formHelpers.ts +139 -0
- package/src/extra/head.ts +106 -0
- package/src/extra/hmr.ts +2 -3
- package/src/extra/owned.ts +24 -0
- package/src/extra/pager.ts +50 -0
- package/src/extra/persist.ts +133 -0
- package/src/extra/pwa.ts +1 -1
- package/src/extra/queryCache.ts +107 -0
- package/src/extra/reducers.ts +1 -1
- package/src/extra/reduxDevtools.ts +92 -0
- package/src/extra/replies.ts +54 -0
- package/src/extra/router.ts +323 -0
- package/src/extra/run.ts +90 -217
- package/src/extra/selection.ts +76 -0
- package/src/extra/socketDriver.ts +265 -0
- package/src/extra/sortable.ts +377 -0
- package/src/extra/ssr.ts +427 -160
- package/src/extra/standardSchema.ts +27 -0
- package/src/extra/testing.ts +2433 -175
- package/src/extra/timers.ts +95 -0
- package/src/extra/undo.ts +261 -0
- package/src/extra/viewTransitions.ts +38 -0
- package/src/extra/virtual.ts +513 -0
- package/src/extra/widget.ts +201 -0
- package/src/index.d.ts +2631 -117
- package/src/index.ts +25 -4
- package/src/jsx-runtime.ts +15 -1
- package/src/jsx.ts +2 -1
- package/src/lazy.ts +50 -7
- package/src/portal.ts +3 -1
- package/src/pragma/index.ts +256 -133
- package/src/react-peers.d.ts +5 -0
- package/src/react.d.ts +43 -0
- package/src/react.ts +110 -0
- package/src/shared.ts +48 -0
- package/src/slot.ts +1 -1
- package/src/suspense.ts +3 -1
- package/src/switchable.ts +6 -121
- package/src/transition.ts +3 -1
- package/src/ui/accordion.ts +64 -0
- package/src/ui/dialog.ts +129 -0
- package/src/ui/disclosure.ts +35 -0
- package/src/ui/popover.ts +45 -0
- package/src/ui/shared.ts +94 -0
- package/src/ui/tabs.ts +82 -0
- package/src/ui/toaster.ts +198 -0
- package/src/ui/tooltip.ts +70 -0
- package/src/ui/zag/combobox.ts +115 -0
- package/src/ui/zag/menu.ts +40 -0
- package/src/ui/zag/select.ts +54 -0
- package/src/ui/zag/shared.ts +43 -0
- package/src/ui-combobox.d.ts +20 -0
- package/src/ui-combobox.ts +6 -0
- package/src/ui-menu.d.ts +30 -0
- package/src/ui-menu.ts +6 -0
- package/src/ui-select.d.ts +17 -0
- package/src/ui-select.ts +6 -0
- package/src/ui-zag-types.d.ts +42 -0
- package/src/ui.d.ts +208 -0
- package/src/ui.ts +15 -0
- package/src/vike/+config.ts +9 -1
- package/src/vike/ClientOnly.ts +1 -1
- package/src/vike/onRenderClient.ts +144 -96
- package/src/vike/onRenderHtml.ts +69 -26
- package/src/vike/types.ts +7 -1
- package/src/vite/globalthis-shim.ts +18 -0
- package/src/vite/globalthis.ts +56 -0
- package/src/vite/plugin.d.ts +22 -3
- package/src/vite/plugin.ts +222 -24
- package/src/zag.d.ts +64 -0
- package/src/zag.ts +238 -0
- package/dist/vike/+config.cjs.js +0 -66
- package/dist/vike/+config.cjs.js.map +0 -1
- package/dist/vike/+config.js.map +0 -1
- package/src/component.ts +0 -2301
- package/src/cycle/isolate/index.ts +0 -196
- package/src/cycle/run/index.ts +0 -151
- package/src/cycle/run/internals.ts +0 -143
- package/src/cycle/state/Collection.ts +0 -184
- package/src/cycle/state/index.ts +0 -5
- package/src/cycle/state/pickCombine.ts +0 -201
- package/src/cycle/state/pickMerge.ts +0 -122
- package/src/cycle/state/withState.ts +0 -47
- package/src/pragma/fn.ts +0 -55
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import {Stream} from 'xstream';
|
|
2
|
+
import {senderOf, makeReplies} from './replies';
|
|
3
|
+
import {SET} from '../core/teardown';
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
* makeRouter({ routes, base?, mode?, scroll?, focus?, prefetch?, navigate?, window?, ... })
|
|
7
|
+
* (PLAN-3 §1.7, D81, D84). Public docs: src/index.d.ts and docs guide/router.md. Summary:
|
|
8
|
+
*
|
|
9
|
+
* - `routes`: { name: '/path/:param' | '*' }. First match wins; '*' is the not-found route.
|
|
10
|
+
* Matching ignores a trailing slash and empty segments; the reported `path` is normalised
|
|
11
|
+
* (no trailing slash, base removed). `href()` never adds a trailing slash.
|
|
12
|
+
* - The returned object: `href(name, params?, query?, hash?)` and `match(url)` are pure (SSR);
|
|
13
|
+
* `current(url?)` reads the location (or `url`: SSR passes the request URL); `driver` is the
|
|
14
|
+
* driver for `run(App, { ROUTER: router.driver })`; `options` is what the 5-4c fake reuses.
|
|
15
|
+
* - Reading: a component declares `App.route = 'ROUTE'` (a string static, `__sygnalStatic:
|
|
16
|
+
* 'route'`); the driver replies ROUTE with `{ name, params, query, hash, path }` on
|
|
17
|
+
* declaration and on every change. `{ route }` is the declaration: commands never use it.
|
|
18
|
+
* - Redirect order (G-168): the FIRST declarer (the outermost one: declarations are kept in
|
|
19
|
+
* mount order) is the guard owner. It gets each new route at once; the other declarers get it
|
|
20
|
+
* once its ROUTE ran (in the same flush, G-565), and only if no newer navigation (a redirect
|
|
21
|
+
* from the guard's ROUTE entry, e.g. `ROUTER: { to: 'login', replace: true }`) happened.
|
|
22
|
+
* G-571: past 32 navigations before a microtask (a redirect loop) each next one waits a task.
|
|
23
|
+
* - Commands: `{ to: name, params?, query?, hash?, replace?, scroll? }`, `{ url, replace? }`,
|
|
24
|
+
* `{ back: true }`, `{ forward: true }`, `{ go: n }`, `{ block: 'ACTION' | false }`,
|
|
25
|
+
* `{ prefetch: name | url, params?, query? }` (calls options.prefetch(route, url); else a
|
|
26
|
+
* no-op hook until the fetch cache, 5-5). `force: true` skips a block. `block` may come with a
|
|
27
|
+
* navigation: `{ ...proceed, block: false }` clears the block and makes it.
|
|
28
|
+
* - Block: while a component has `{ block: 'ACTION' }`, an attempted navigation (link click,
|
|
29
|
+
* command, back/forward) is not made; the last blocker gets ACTION with `{ to, route,
|
|
30
|
+
* proceed }` (`proceed` is the command that makes it anyway). A back/forward is undone with
|
|
31
|
+
* history.go(-delta) first. beforeunload is prevented while any block is set.
|
|
32
|
+
* - Links: document-level click interception of same-origin `<a href>` (HTML and SVG,
|
|
33
|
+
* inside shadow roots via composedPath(), resolved against <base>), except modified or
|
|
34
|
+
* non-left clicks, `target` other than _self, `download`, `rel="external"`,
|
|
35
|
+
* `data-router-ignore`, defaultPrevented, outside `base`, and (history mode) a hash-only link
|
|
36
|
+
* to the current page. Forms are left alone.
|
|
37
|
+
* - Scroll (option `scroll`, default true): `history.state` carries `{ key, i }`; positions are
|
|
38
|
+
* kept per key in memory. Push: top (or the hash target after render); back/forward: the saved
|
|
39
|
+
* position after render; replace: unchanged (or the hash target). Reload: the browser's own
|
|
40
|
+
* (scrollRestoration goes back to 'auto' on pagehide).
|
|
41
|
+
* - Focus (option `focus`, default '[data-router-focus],main h1,h1'; false disables): after a push or a
|
|
42
|
+
* back/forward, once the DOM has been quiet for `settleMs` (MutationObserver; capped at 1 s),
|
|
43
|
+
* the first match of the comma-separated selectors, tried in order, gets focus (tabindex=-1
|
|
44
|
+
* added when needed, preventScroll). Content that renders later (data still loading) is
|
|
45
|
+
* missed; the scroll restore has the same limit.
|
|
46
|
+
* - No window (SSR): no listeners, no history, no replies; href/match/current(url) still work.
|
|
47
|
+
* - Vike (`navigate` option, Vike's navigate()): no link interception (Vike owns links),
|
|
48
|
+
* navigation calls navigate(url, { overwriteLastHistoryEntry }), scroll is left to Vike, and
|
|
49
|
+
* the route is re-read on popstate and on the 'sygnal:navigate' event the Vike client hook
|
|
50
|
+
* dispatches before each client-side render.
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
type Routes = Record<string, string>;
|
|
54
|
+
|
|
55
|
+
const enc = encodeURIComponent;
|
|
56
|
+
const dec = (s: string) => { try { return decodeURIComponent(s); } catch (_) { return s; } };
|
|
57
|
+
const g: any = globalThis;
|
|
58
|
+
const PARAM = /:(\w+)/g;
|
|
59
|
+
// G-571: synchronous navigations (redirects) before a microtask, after which each waits a task
|
|
60
|
+
const HOPS = 32;
|
|
61
|
+
|
|
62
|
+
/** the names of the params a pattern requires */
|
|
63
|
+
export const paramsOf = (pat: string) => (pat.match(PARAM) || []).map(p => p.slice(1));
|
|
64
|
+
|
|
65
|
+
// the core's coded error() (published on the diagnostics bridge): no import, so the router
|
|
66
|
+
// costs nothing more standalone; prints in production too (like SYG611)
|
|
67
|
+
const bad = (v: any, msg: string, fix: string) => (g.__SYGNAL_DIAGNOSTICS__?.error || console.error)('SYG620', v.__emitterName, `router: ${msg}; not navigated`, fix, v);
|
|
68
|
+
|
|
69
|
+
export function makeRouter(options: any = {}) {
|
|
70
|
+
const {mode, prefetch, navigate} = options, routes: Routes = options.routes || {};
|
|
71
|
+
const base = (options.base || '').replace(/\/$/, '');
|
|
72
|
+
const hashMode = mode == 'hash';
|
|
73
|
+
const focusSel = options.focus ?? '[data-router-focus],main h1,h1';
|
|
74
|
+
const doScroll = options.scroll ?? !navigate;
|
|
75
|
+
|
|
76
|
+
const href = (name: string, params: any = {}, query?: any, hash?: string) => {
|
|
77
|
+
// dev entry only (SYG130: unknown name, missing or extra params)
|
|
78
|
+
g.__SYGNAL_DIAGNOSTICS__?.routerHref?.(routes, name, params);
|
|
79
|
+
const pat = routes[name];
|
|
80
|
+
const path = pat == null || pat == '*' ? '/' : pat.replace(PARAM, (_, k) => enc(params[k] ?? ''));
|
|
81
|
+
const q = query && new URLSearchParams(Object.entries(query).filter(([, v]) => v != null) as any) + '';
|
|
82
|
+
return base + (hashMode ? (base ? '/#' : '#') : '') + path + (q ? '?' + q : '') + (hash ? '#' + hash : '');
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const matchPath = (path: string) => {
|
|
86
|
+
const segs = path.split('/').filter(Boolean);
|
|
87
|
+
let notFound: any = null;
|
|
88
|
+
for (const name in routes) {
|
|
89
|
+
const pat = routes[name];
|
|
90
|
+
if (pat == '*') { notFound ??= name; continue; }
|
|
91
|
+
const ps = pat.split('/').filter(Boolean), params: any = {};
|
|
92
|
+
if (ps.length == segs.length && ps.every((p, i) => p[0] == ':' ? (params[p.slice(1)] = dec(segs[i]), 1) : p == segs[i])) return {name, params};
|
|
93
|
+
}
|
|
94
|
+
return {name: notFound, params: {}};
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/** `{ name, params, query, hash, path }` for a URL (a path, or absolute) */
|
|
98
|
+
const match = (url: string) => {
|
|
99
|
+
let u = new URL(url, 'http://x');
|
|
100
|
+
if (hashMode) u = new URL(u.hash.slice(1) || '/', 'http://x');
|
|
101
|
+
let p = u.pathname;
|
|
102
|
+
if (!hashMode && base && (p == base || p.startsWith(base + '/'))) p = p.slice(base.length);
|
|
103
|
+
const path = '/' + p.split('/').filter(Boolean).join('/');
|
|
104
|
+
return {...matchPath(path), query: Object.fromEntries(new URLSearchParams(u.search)), hash: dec(u.hash.slice(1)), path};
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const win = () => options.window || (typeof window != 'undefined' ? window : null);
|
|
108
|
+
const current = (url?: string) => {
|
|
109
|
+
const w = win();
|
|
110
|
+
return match(url ?? (w ? (options.location || w.location).href : '/'));
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const driver = (sink$: Stream<any>) => {
|
|
114
|
+
const w: any = win();
|
|
115
|
+
const L: any = w && (options.location || w.location);
|
|
116
|
+
const H: any = w && (options.history || w.history);
|
|
117
|
+
const D: any = w && (options.document || w.document);
|
|
118
|
+
// sender → reply action; insertion (mount) order: the first is the guard owner (G-168)
|
|
119
|
+
const declared = new Map<any, string>();
|
|
120
|
+
// sender → block action; the last one set gets the attempted navigation
|
|
121
|
+
const blocks = new Map<any, string>();
|
|
122
|
+
const pos = new Map<string, number[]>();
|
|
123
|
+
let ver = 0, idx = 0, key = '', last = '', force = 0, undo = 0, n = 0, hops = 0, warned = 0;
|
|
124
|
+
let cur: any = null, cancel: any = null, pendingKind = '';
|
|
125
|
+
|
|
126
|
+
const newKey = () => Date.now().toString(36) + (n++);
|
|
127
|
+
const stamp = (i: number) => ({key: (key = newKey()), i: (idx = i)});
|
|
128
|
+
|
|
129
|
+
const settle = (kind: string) => {
|
|
130
|
+
if (!D) return;
|
|
131
|
+
pendingKind = cancel && kind == 'replace' ? pendingKind : kind;
|
|
132
|
+
cancel?.();
|
|
133
|
+
let t: any, mo: any;
|
|
134
|
+
const done = () => {
|
|
135
|
+
cancel?.();
|
|
136
|
+
const k = pendingKind, h = cur.hash && D.getElementById(cur.hash), p = k == 'pop' && pos.get(key);
|
|
137
|
+
if (doScroll) p ? w.scrollTo(p[0], p[1]) : h?.scrollIntoView?.();
|
|
138
|
+
if (focusSel && k != 'replace') for (const sel of focusSel.split(',')) {
|
|
139
|
+
const el = D.querySelector(sel);
|
|
140
|
+
if (el) {
|
|
141
|
+
if (el.tabIndex < 0 && !el.hasAttribute('tabindex')) el.setAttribute('tabindex', '-1');
|
|
142
|
+
el.focus({preventScroll: true});
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
const quiet = () => { clearTimeout(t); t = setTimeout(done, options.settleMs ?? 30); };
|
|
148
|
+
const cap = setTimeout(done, 1000);
|
|
149
|
+
if (w.MutationObserver) (mo = new w.MutationObserver(quiet)).observe(D.body || D.documentElement, {childList: true, subtree: true, characterData: true});
|
|
150
|
+
cancel = () => { clearTimeout(t); clearTimeout(cap); mo?.disconnect(); cancel = null; };
|
|
151
|
+
quiet();
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// G-565: `f` runs once the actions queued so far ran: a state write that changes nothing,
|
|
155
|
+
// queued behind them through the replies of the instances `ss` (one listening, else a task
|
|
156
|
+
// later). The first declarer's ROUTE (and any redirect it makes) runs first; the others get
|
|
157
|
+
// theirs in the same flush, before the patch, as long as no redirect happened (G-168).
|
|
158
|
+
// G-570: queued on every listening one and run by the first that is still there (the queue
|
|
159
|
+
// skips a disposed instance: the first declarer navigating from its DISPOSE / dispose$)
|
|
160
|
+
const after = (ss: any[], f: any) => {
|
|
161
|
+
let d = 0;
|
|
162
|
+
const l = ss.filter(listening), g = (x: any) => (d++ || f(), x);
|
|
163
|
+
l.length ? l.forEach(s => reply(s, SET, g)) : setTimeout(f);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const emit = (kind: string) => {
|
|
167
|
+
last = L.href;
|
|
168
|
+
const r = (cur = current()), list = [...declared], v = ++ver;
|
|
169
|
+
const send = () => {
|
|
170
|
+
if (ver != v || !list.length) return;
|
|
171
|
+
reply(list[0][0], list[0][1], r);
|
|
172
|
+
after(list.map(x => x[0]), () => { if (ver == v) for (const [s, a] of list.slice(1)) if (declared.get(s) == a) reply(s, a, r); });
|
|
173
|
+
};
|
|
174
|
+
// G-571: a redirect made from a ROUTE entry is delivered at once (a chain /secret → /login
|
|
175
|
+
// → /other is one flush, one patch); past HOPS of them before a microtask (a redirect
|
|
176
|
+
// loop) each next one waits a task, so the loop can't hang the page (a warning, once, when the
|
|
177
|
+
// diagnostics are on: in development)
|
|
178
|
+
if (!hops++) queueMicrotask(() => { hops = 0; });
|
|
179
|
+
if (hops > HOPS) {
|
|
180
|
+
if (g.__SYGNAL_DIAGNOSTICS__?.hooks?.on?.() && !warned++) console.warn(`[Sygnal] router: more than ${HOPS} navigations in one flush (a redirect loop?); each next one waits a task (reported once)`, r.path);
|
|
181
|
+
setTimeout(send);
|
|
182
|
+
} else send();
|
|
183
|
+
if (kind != 'start') settle(kind);
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const blocked = (data: any) => {
|
|
187
|
+
const b = [...blocks].pop();
|
|
188
|
+
return b && (reply(b[0], b[1], data), 1);
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const go = (url: string, replace?: boolean, scroll?: boolean, f?: any) => {
|
|
192
|
+
if (!f && blocked({to: url, route: match(url), proceed: {url, replace, force: true}})) return;
|
|
193
|
+
if (navigate) return navigate(url, {overwriteLastHistoryEntry: !!replace});
|
|
194
|
+
pos.set(key, [w.scrollX, w.scrollY]);
|
|
195
|
+
H[replace ? 'replaceState' : 'pushState'](stamp(replace ? idx : idx + 1), '', url);
|
|
196
|
+
if (doScroll && !replace && scroll !== false) w.scrollTo(0, 0);
|
|
197
|
+
emit(replace ? 'replace' : 'push');
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const onPop = () => {
|
|
201
|
+
const st = H.state, i = st?.i ?? idx + 1;
|
|
202
|
+
if (undo) return void (undo = 0);
|
|
203
|
+
if (!force && blocked({to: L.href, route: current(), proceed: {go: i - idx, force: true}})) {
|
|
204
|
+
undo = 1;
|
|
205
|
+
return H.go(idx - i);
|
|
206
|
+
}
|
|
207
|
+
force = 0;
|
|
208
|
+
pos.set(key, [w.scrollX, w.scrollY]);
|
|
209
|
+
if (st?.key) { key = st.key; idx = i; }
|
|
210
|
+
else H.replaceState({...st, ...stamp(i)}, '');
|
|
211
|
+
if (L.href != last) emit('pop');
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const onClick = (e: any) => {
|
|
215
|
+
if (e.defaultPrevented || e.button || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
|
|
216
|
+
const a = (e.composedPath?.() || []).find((x: any) => x.localName == 'a') || e.target?.closest?.('a');
|
|
217
|
+
const at = (k: string) => a.getAttribute(k);
|
|
218
|
+
const raw = a && (at('href') ?? a.getAttributeNS?.('http://www.w3.org/1999/xlink', 'href'));
|
|
219
|
+
if (raw == null || (at('target') || '_self') != '_self' || a.hasAttribute('download') ||
|
|
220
|
+
a.hasAttribute('data-router-ignore') || /(^|\s)external(\s|$)/i.test(at('rel') || '')) return;
|
|
221
|
+
const u = new URL(raw, D.baseURI || L.href), same = u.pathname == L.pathname && u.search == L.search;
|
|
222
|
+
if (u.origin != L.origin) return;
|
|
223
|
+
if (hashMode ? !(same && u.hash.startsWith('#/')) : (base && u.pathname != base && !u.pathname.startsWith(base + '/')) || (u.hash && same)) return;
|
|
224
|
+
e.preventDefault();
|
|
225
|
+
go(u.pathname + u.search + u.hash);
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const onUnload = (e: any) => { e.preventDefault(); e.returnValue = ''; };
|
|
229
|
+
const onHide = () => { H.scrollRestoration = 'auto'; };
|
|
230
|
+
const listen = (on: boolean) => {
|
|
231
|
+
const m = on ? 'addEventListener' : 'removeEventListener';
|
|
232
|
+
w[m]('popstate', onPop);
|
|
233
|
+
if (hashMode) w[m]('hashchange', onPop);
|
|
234
|
+
if (navigate) w[m]('sygnal:navigate', onNav);
|
|
235
|
+
else D[m]('click', onClick);
|
|
236
|
+
if (doScroll) w[m]('pagehide', onHide);
|
|
237
|
+
};
|
|
238
|
+
const syncUnload = () => w?.[blocks.size ? 'addEventListener' : 'removeEventListener']('beforeunload', onUnload);
|
|
239
|
+
// Vike: the client hook says a navigation happened (Vike already updated the URL)
|
|
240
|
+
// (once the hook's microtasks ran, and again a task later in case the URL changes after it)
|
|
241
|
+
const onNav = () => { const f = () => L.href != last && emit('push'); queueMicrotask(f); setTimeout(f); };
|
|
242
|
+
|
|
243
|
+
const {replies, reply, listening} = makeReplies(s => {
|
|
244
|
+
declared.delete(s);
|
|
245
|
+
if (blocks.delete(s)) syncUnload();
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
const stop = () => {
|
|
249
|
+
if (!w) return;
|
|
250
|
+
listen(false);
|
|
251
|
+
blocks.clear();
|
|
252
|
+
syncUnload();
|
|
253
|
+
cancel?.();
|
|
254
|
+
onHide();
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
if (w) {
|
|
258
|
+
const st = H.state;
|
|
259
|
+
if (st?.key) { key = st.key; idx = st.i; }
|
|
260
|
+
else H.replaceState({...st, ...stamp(0)}, '');
|
|
261
|
+
if (doScroll && 'scrollRestoration' in H) H.scrollRestoration = 'manual';
|
|
262
|
+
listen(true);
|
|
263
|
+
emit('start');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const target = (v: any, name: any) => {
|
|
267
|
+
if (!(name in routes) || routes[name] == '*') return bad(v, `no route named '${name}'`, `Use one of: ${Object.keys(routes).filter(k => routes[k] != '*').join(', ')}`);
|
|
268
|
+
const missing = paramsOf(routes[name]).filter(k => v.params?.[k] == null);
|
|
269
|
+
if (missing.length) return bad(v, `route '${name}' (${routes[name]}) needs params: ${missing.join(', ')}`, `{ to: '${name}', params: { ${missing.join(', ')} } }`);
|
|
270
|
+
return href(name, v.params, v.query, v.hash);
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
sink$.addListener({
|
|
274
|
+
next: (v: any) => {
|
|
275
|
+
if (!v || typeof v != 'object') return;
|
|
276
|
+
const s = senderOf(v);
|
|
277
|
+
if ('route' in v) {
|
|
278
|
+
// the core sends { route: 'ACTION' } (or a falsy value) for a component's `route` static
|
|
279
|
+
if (Object.keys(v).length > 1 || v.route in routes) return bad(v, '`route` is the declaration key', "Navigate with { to: 'name', params }");
|
|
280
|
+
if (!v.route || s === undefined) return void declared.delete(s);
|
|
281
|
+
declared.set(s, v.route);
|
|
282
|
+
// G-555: the guard owner (first declarer) gets it at once when it listens to its replies
|
|
283
|
+
// (the core subscribes them at creation, before its statics), so its ROUTE is part of
|
|
284
|
+
// the flush that declared it: a task later came after the first render and undid what
|
|
285
|
+
// happened in between (a field typed into). G-565: the others too, once the actions
|
|
286
|
+
// queued before (the guard owner's ROUTE) ran, as for a navigation; a redirect in
|
|
287
|
+
// between supersedes the route (G-168). A declarer not listening yet (a Vike Layout):
|
|
288
|
+
// a task later
|
|
289
|
+
const v0 = ver, f = () => declared.get(s) == v.route && ver == v0 && reply(s, v.route, cur);
|
|
290
|
+
if (cur) declared.keys().next().value === s && listening(s) ? f() : after([s], f);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
// `block` combines with a navigation: { ...proceed, block: false } clears it and goes
|
|
294
|
+
const nav = v.back || v.forward || v.go || v.to != null || v.url != null;
|
|
295
|
+
if ('block' in v) {
|
|
296
|
+
v.block ? blocks.set(s, v.block) : blocks.delete(s);
|
|
297
|
+
syncUnload();
|
|
298
|
+
if (!nav) return;
|
|
299
|
+
}
|
|
300
|
+
if ('prefetch' in v) {
|
|
301
|
+
const url = v.prefetch in routes ? target({...v, to: v.prefetch}, v.prefetch) : v.prefetch;
|
|
302
|
+
if (url) prefetch?.(match(url), url);
|
|
303
|
+
} else if (!w) return;
|
|
304
|
+
else if (v.back || v.forward || v.go) {
|
|
305
|
+
force = v.force ? 1 : 0;
|
|
306
|
+
H.go(v.back ? -1 : v.forward ? 1 : v.go);
|
|
307
|
+
} else if (nav) {
|
|
308
|
+
const url = v.url ?? target(v, v.to);
|
|
309
|
+
if (url) go(url, v.replace, v.scroll, v.force);
|
|
310
|
+
} else bad(v, 'unknown command', "{ to: 'name', params?, query?, hash?, replace? }, { back: true }, { forward: true }, { block: 'ACTION' } or { prefetch: 'name' }");
|
|
311
|
+
},
|
|
312
|
+
error: (e: any) => console.error('[Sygnal] router', e),
|
|
313
|
+
complete: stop,
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
return {...replies, __sygnalStatic: 'route', __sygnalRouter: options, current: () => cur, href, dispose: stop};
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
return {routes, href, match, current, driver, options};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** `makeRouter(options).driver`: for a router whose href() the app doesn't need */
|
|
323
|
+
export const makeRouterDriver = (options: any) => makeRouter(options).driver;
|
package/src/extra/run.ts
CHANGED
|
@@ -1,249 +1,122 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {makeDOMDriver} from '../cycle/dom/index';
|
|
4
|
-
import eventBusDriver from './eventDriver';
|
|
5
|
-
import logDriver from './logDriver';
|
|
6
|
-
import component, {ABORT} from '../component';
|
|
7
|
-
import {getDevTools} from './devtools';
|
|
8
|
-
import {configureDiagnostics} from './diagnostics/index';
|
|
1
|
+
import {configureDiagnostics, isDiagnosticsEnabled} from './diagnostics/index';
|
|
2
|
+
import {warn} from './diagnostics/legacy';
|
|
9
3
|
import type {DiagnosticsMode, DiagnosticsOptions} from './diagnostics/index';
|
|
4
|
+
import {start} from '../core/runtime';
|
|
5
|
+
|
|
6
|
+
interface RunDiagnosticsOptions extends DiagnosticsOptions {
|
|
7
|
+
/** Strict (canonical-form) runtime checks; needs the 'sygnal/diagnostics' dev entry (G-036). */
|
|
8
|
+
strict?: boolean;
|
|
9
|
+
}
|
|
10
10
|
|
|
11
11
|
interface RunOptions {
|
|
12
12
|
mountPoint?: string;
|
|
13
|
+
/** @deprecated No effect since 6.0 (3-Q): fragments always work (the DOM driver flattens them) */
|
|
13
14
|
fragments?: boolean;
|
|
14
15
|
useDefaultDrivers?: boolean;
|
|
15
|
-
/** Diagnostics mode (or mode + ignore list). Overrides globalThis.__SYGNAL_DEV__. */
|
|
16
|
-
diagnostics?: DiagnosticsMode |
|
|
16
|
+
/** Diagnostics mode (or mode + ignore list + strict). Overrides globalThis.__SYGNAL_DEV__. */
|
|
17
|
+
diagnostics?: DiagnosticsMode | RunDiagnosticsOptions;
|
|
18
|
+
/** PLAN-4 GS-11: app-level error hook, reporting only (after the component's onError boundary) */
|
|
19
|
+
onError?: (error: any, info: {componentName?: string; action?: string; phase: string; driver?: string}) => void;
|
|
20
|
+
/** PLAN-4 G-206: the root of this app's uid() strings (default 'u'); give each app on a page its own */
|
|
21
|
+
uid?: string;
|
|
17
22
|
}
|
|
18
23
|
|
|
24
|
+
let warnedStrict = false;
|
|
25
|
+
// G-212: apps running now (an HMR swap disposes, then starts the new one)
|
|
26
|
+
let liveApps = 0;
|
|
27
|
+
|
|
19
28
|
interface SygnalRunResult {
|
|
20
29
|
sources: any;
|
|
21
30
|
sinks: any;
|
|
22
31
|
dispose: () => void;
|
|
23
32
|
hmr?: (newComponent: any, explicitState?: any) => void;
|
|
33
|
+
/** internal: the app's runtime API (devtools, HMR, sygnal/element, Vike) */
|
|
34
|
+
__runtime?: any;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function resolveHotModule(incoming: any): any {
|
|
38
|
+
if (!incoming) return null;
|
|
39
|
+
if (Array.isArray(incoming)) return resolveHotModule(incoming.find(Boolean));
|
|
40
|
+
if (incoming.default && typeof incoming.default === 'function') return incoming;
|
|
41
|
+
if (typeof incoming === 'function') return {default: incoming};
|
|
42
|
+
return null;
|
|
24
43
|
}
|
|
25
44
|
|
|
26
45
|
export default function run(
|
|
27
46
|
app: any,
|
|
28
47
|
drivers: Record<string, any> = {},
|
|
29
|
-
options: RunOptions = {}
|
|
48
|
+
options: RunOptions = {},
|
|
49
|
+
// G-216 (internal): a hot swap's { u: swapping, s: state to keep }, this app's `__hmr` source
|
|
50
|
+
hmrSwap?: {u: boolean; s: any}
|
|
30
51
|
): SygnalRunResult {
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
const dt = getDevTools();
|
|
34
|
-
dt.init();
|
|
35
|
-
}
|
|
52
|
+
// D77: the DevTools bridge is not installed here; 'sygnal/devtools' (injected by
|
|
53
|
+
// sygnal/vite in dev) installs window.__SYGNAL_DEVTOOLS__ before run() is called.
|
|
36
54
|
|
|
37
55
|
// Resolve diagnostics mode: explicit option > globalThis.__SYGNAL_DEV__ > 'off'.
|
|
38
|
-
// Each run() is authoritative
|
|
39
|
-
//
|
|
56
|
+
// Each run() with the option is authoritative. Without it, mode and ignore list reset to
|
|
57
|
+
// the defaults (no leakage from an earlier run()/configureDiagnostics()), unless another
|
|
58
|
+
// app is still running (G-212: a second app or a custom element keeps the host's mode).
|
|
40
59
|
const {diagnostics} = options;
|
|
41
|
-
const diagOptions:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
calculated,
|
|
57
|
-
storeCalculatedInState,
|
|
58
|
-
DOMSourceName,
|
|
59
|
-
stateSourceName,
|
|
60
|
-
onError,
|
|
61
|
-
debug,
|
|
62
|
-
} = app;
|
|
63
|
-
const componentOptions = {
|
|
64
|
-
name,
|
|
65
|
-
view,
|
|
66
|
-
model,
|
|
67
|
-
intent,
|
|
68
|
-
hmrActions,
|
|
69
|
-
context,
|
|
70
|
-
peers,
|
|
71
|
-
components,
|
|
72
|
-
initialState,
|
|
73
|
-
calculated,
|
|
74
|
-
storeCalculatedInState,
|
|
75
|
-
DOMSourceName,
|
|
76
|
-
stateSourceName,
|
|
77
|
-
onError,
|
|
78
|
-
debug,
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
app = component(componentOptions);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (
|
|
85
|
-
typeof window !== 'undefined' &&
|
|
86
|
-
window.__SYGNAL_HMR_UPDATING === true &&
|
|
87
|
-
typeof window.__SYGNAL_HMR_PERSISTED_STATE !== 'undefined'
|
|
88
|
-
) {
|
|
89
|
-
app.initialState = window.__SYGNAL_HMR_PERSISTED_STATE;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const wrapped = withState(app, 'STATE');
|
|
93
|
-
|
|
94
|
-
const baseDrivers = useDefaultDrivers
|
|
95
|
-
? {
|
|
96
|
-
EVENTS: eventBusDriver,
|
|
97
|
-
DOM: makeDOMDriver(mountPoint, {snabbdomOptions: {experimental: {fragments}}} as any),
|
|
98
|
-
LOG: logDriver,
|
|
60
|
+
const diagOptions: RunDiagnosticsOptions = typeof diagnostics === 'string' ? {mode: diagnostics} : diagnostics || {};
|
|
61
|
+
const {mode, strict} = diagOptions;
|
|
62
|
+
if (diagnostics || !liveApps) configureDiagnostics({mode, ignore: diagOptions.ignore || []});
|
|
63
|
+
// G-036: strict is applied only when given (an earlier configureStrict() is kept). It sets
|
|
64
|
+
// the flag the dev entry's strict checks read; strict without a mode turns diagnostics on.
|
|
65
|
+
// G-093: dispose() restores the previous value (an HMR swap disposes, then re-applies it).
|
|
66
|
+
const core = (globalThis as any).__SYGNAL_DIAGNOSTICS__;
|
|
67
|
+
const prevStrict = core.strict;
|
|
68
|
+
if (strict !== undefined) {
|
|
69
|
+
if ((core.strict = strict)) {
|
|
70
|
+
if (!mode && !isDiagnosticsEnabled()) configureDiagnostics({mode: 'warn'});
|
|
71
|
+
// SYG608 (once): the long explanation is in the docs / sygnal-check explain
|
|
72
|
+
if (!core.__uninstallChecks && !warnedStrict) {
|
|
73
|
+
warnedStrict = true;
|
|
74
|
+
warn('SYG608', 'run', "strict needs 'sygnal/diagnostics'");
|
|
99
75
|
}
|
|
100
|
-
: {};
|
|
101
|
-
|
|
102
|
-
const combinedDrivers = {...baseDrivers, ...drivers};
|
|
103
|
-
|
|
104
|
-
const {sources, sinks, run: _run} = setup(wrapped, combinedDrivers as any);
|
|
105
|
-
const rawDispose = _run();
|
|
106
|
-
let persistListener: any = null;
|
|
107
|
-
|
|
108
|
-
if (
|
|
109
|
-
typeof window !== 'undefined' &&
|
|
110
|
-
(sources as any)?.STATE?.stream &&
|
|
111
|
-
typeof (sources as any).STATE.stream.addListener === 'function'
|
|
112
|
-
) {
|
|
113
|
-
persistListener = {
|
|
114
|
-
next: (state: any) => {
|
|
115
|
-
window.__SYGNAL_HMR_PERSISTED_STATE = state;
|
|
116
|
-
},
|
|
117
|
-
error: () => {},
|
|
118
|
-
complete: () => {},
|
|
119
|
-
};
|
|
120
|
-
(sources as any).STATE.stream.addListener(persistListener);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const dispose = () => {
|
|
124
|
-
if (
|
|
125
|
-
persistListener &&
|
|
126
|
-
(sources as any)?.STATE?.stream &&
|
|
127
|
-
typeof (sources as any).STATE.stream.removeListener === 'function'
|
|
128
|
-
) {
|
|
129
|
-
(sources as any).STATE.stream.removeListener(persistListener);
|
|
130
|
-
persistListener = null;
|
|
131
|
-
}
|
|
132
|
-
// Trigger the component's dispose() which fires the DISPOSE action and dispose$ stream
|
|
133
|
-
if (typeof (sinks as any).__dispose === 'function') {
|
|
134
|
-
try { (sinks as any).__dispose(); } catch (_) {}
|
|
135
76
|
}
|
|
136
|
-
rawDispose();
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
const exposed: SygnalRunResult = {sources, sinks, dispose};
|
|
140
|
-
|
|
141
|
-
// Store app reference for time-travel
|
|
142
|
-
if (typeof window !== 'undefined') {
|
|
143
|
-
window.__SYGNAL_DEVTOOLS_APP__ = exposed;
|
|
144
77
|
}
|
|
145
78
|
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
79
|
+
const {uid} = options;
|
|
80
|
+
// (R4) an HMR swap: the kept state is the new root's first state, and the instances made at
|
|
81
|
+
// its start get no BOOTSTRAP (as the 5.x core's `__hmr` source); no 0/20 ms re-sends
|
|
82
|
+
const started = start(app, drivers, {...options, __hooks: (options as any).__hooks, __state: hmrSwap?.s, __swap: !!hmrSwap} as any);
|
|
83
|
+
liveApps++;
|
|
84
|
+
let off = false;
|
|
85
|
+
const exposed: SygnalRunResult = {
|
|
86
|
+
sources: started.sources,
|
|
87
|
+
sinks: started.sinks,
|
|
88
|
+
dispose: () => {
|
|
89
|
+
if (off) return;
|
|
90
|
+
off = true;
|
|
91
|
+
liveApps--;
|
|
92
|
+
// G-212: unregister from DevTools (a later app, or this app's hot-swapped successor, registers)
|
|
93
|
+
if (typeof window !== 'undefined' && window.__SYGNAL_DEVTOOLS_APP__ === exposed) window.__SYGNAL_DEVTOOLS_APP__ = undefined;
|
|
94
|
+
started.dispose();
|
|
95
|
+
if (strict !== undefined) core.strict = prevStrict;
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
(exposed as any).__runtime = started.api;
|
|
99
|
+
// G-214: the uid option, as the 5.x core's __uid source
|
|
100
|
+
if (uid !== undefined) Object.defineProperty(exposed.sources, '__uid', {value: uid.replace(/[^\w-]+/g, '_'), enumerable: false, configurable: true});
|
|
101
|
+
if (typeof window !== 'undefined') window.__SYGNAL_DEVTOOLS_APP__ ||= exposed;
|
|
102
|
+
// (R4, 04 §3.12) hmr(): this app's current state through the runtime API (runtime.getState(),
|
|
103
|
+
// no STATE.stream._v), then the new component started with it
|
|
104
|
+
let current = app;
|
|
105
|
+
exposed.hmr = (newComponent: any, explicitState?: any) => {
|
|
106
|
+
const mod = resolveHotModule(newComponent) || {default: current};
|
|
107
|
+
const state = explicitState !== undefined ? explicitState : (exposed as any).__runtime.getState();
|
|
108
|
+
if (typeof window !== 'undefined') window.__SYGNAL_HMR_LAST_CAPTURED_STATE = state;
|
|
109
|
+
const wasRegistered = typeof window !== 'undefined' && window.__SYGNAL_DEVTOOLS_APP__ === exposed;
|
|
156
110
|
exposed.dispose();
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const updated = run(App, drivers, options);
|
|
111
|
+
current = mod.default;
|
|
112
|
+
const updated: any = run(current, drivers, options, state === undefined ? undefined : {u: true, s: state});
|
|
160
113
|
exposed.sources = updated.sources;
|
|
161
114
|
exposed.sinks = updated.sinks;
|
|
162
|
-
exposed.
|
|
163
|
-
|
|
164
|
-
if (
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
typeof updated.sinks.STATE.shamefullySendNext === 'function'
|
|
168
|
-
) {
|
|
169
|
-
const restore = () => updated.sinks.STATE.shamefullySendNext(() => ({...resolvedState}));
|
|
170
|
-
setTimeout(restore, 0);
|
|
171
|
-
setTimeout(restore, 20);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (
|
|
175
|
-
typeof window !== 'undefined' &&
|
|
176
|
-
updated?.sources?.STATE?.stream &&
|
|
177
|
-
typeof updated.sources.STATE.stream.setDebugListener === 'function'
|
|
178
|
-
) {
|
|
179
|
-
updated.sources.STATE.stream.setDebugListener({
|
|
180
|
-
next: () => {
|
|
181
|
-
updated.sources.STATE.stream.setDebugListener(null);
|
|
182
|
-
window.__SYGNAL_HMR_STATE = undefined;
|
|
183
|
-
setTimeout(() => {
|
|
184
|
-
window.__SYGNAL_HMR_UPDATING = false;
|
|
185
|
-
}, 100);
|
|
186
|
-
},
|
|
187
|
-
});
|
|
188
|
-
} else if (typeof window !== 'undefined') {
|
|
189
|
-
window.__SYGNAL_HMR_STATE = undefined;
|
|
190
|
-
window.__SYGNAL_HMR_UPDATING = false;
|
|
191
|
-
}
|
|
115
|
+
(exposed as any).__runtime = updated.__runtime;
|
|
116
|
+
// the registration follows the swap (the successor is this same object)
|
|
117
|
+
if (typeof window !== 'undefined' && (window.__SYGNAL_DEVTOOLS_APP__ === updated || wasRegistered)) window.__SYGNAL_DEVTOOLS_APP__ = exposed;
|
|
118
|
+
const d = updated.dispose;
|
|
119
|
+
exposed.dispose = () => { if (typeof window !== 'undefined' && window.__SYGNAL_DEVTOOLS_APP__ === exposed) window.__SYGNAL_DEVTOOLS_APP__ = undefined; d(); };
|
|
192
120
|
};
|
|
193
|
-
|
|
194
|
-
const resolveHotModule = (incoming: any): any => {
|
|
195
|
-
if (!incoming) return null;
|
|
196
|
-
if (Array.isArray(incoming)) return resolveHotModule(incoming.find(Boolean));
|
|
197
|
-
if (incoming.default && typeof incoming.default === 'function') return incoming;
|
|
198
|
-
if (typeof incoming === 'function') return {default: incoming};
|
|
199
|
-
return null;
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
const hmr = (newComponent: any, explicitState?: any) => {
|
|
203
|
-
const normalizedModule = resolveHotModule(newComponent);
|
|
204
|
-
const moduleToUse = normalizedModule || {default: app};
|
|
205
|
-
|
|
206
|
-
if (typeof explicitState !== 'undefined') {
|
|
207
|
-
if (typeof window !== 'undefined')
|
|
208
|
-
window.__SYGNAL_HMR_LAST_CAPTURED_STATE = explicitState;
|
|
209
|
-
swapToComponent(moduleToUse, explicitState);
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const persistedState =
|
|
214
|
-
typeof window !== 'undefined' ? window.__SYGNAL_HMR_PERSISTED_STATE : undefined;
|
|
215
|
-
if (typeof persistedState !== 'undefined') {
|
|
216
|
-
if (typeof window !== 'undefined')
|
|
217
|
-
window.__SYGNAL_HMR_LAST_CAPTURED_STATE = persistedState;
|
|
218
|
-
swapToComponent(moduleToUse, persistedState);
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const sourceState = exposed?.sources?.STATE?.stream?._v;
|
|
223
|
-
if (typeof sourceState !== 'undefined') {
|
|
224
|
-
if (typeof window !== 'undefined')
|
|
225
|
-
window.__SYGNAL_HMR_LAST_CAPTURED_STATE = sourceState;
|
|
226
|
-
swapToComponent(moduleToUse, sourceState);
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
if (
|
|
231
|
-
exposed?.sinks?.STATE &&
|
|
232
|
-
typeof exposed.sinks.STATE.shamefullySendNext === 'function'
|
|
233
|
-
) {
|
|
234
|
-
exposed.sinks.STATE.shamefullySendNext((state: any) => {
|
|
235
|
-
if (typeof window !== 'undefined')
|
|
236
|
-
window.__SYGNAL_HMR_LAST_CAPTURED_STATE = state;
|
|
237
|
-
swapToComponent(moduleToUse, state);
|
|
238
|
-
return ABORT;
|
|
239
|
-
});
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
swapToComponent(moduleToUse);
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
exposed.hmr = hmr;
|
|
247
|
-
|
|
248
121
|
return exposed;
|
|
249
122
|
}
|