@fluixi/core 1.0.0-alpha.83 → 1.0.0-alpha.85
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/dist/cdn/core.global.js +2 -1
- package/dist/cli/generate.cjs +4117 -292
- package/dist/cli/generate.mjs +4122 -292
- package/dist/cli/index.cjs +4117 -292
- package/dist/cli/index.mjs +4122 -292
- package/dist/cli/run.cjs +63 -1
- package/dist/cli/run.mjs +39 -1
- package/dist/src/cdn/chunk-MDJLC3PE.mjs +830 -0
- package/dist/src/cdn/core.cjs +1365 -1
- package/dist/src/cdn/core.d.ts +3 -3
- package/dist/src/cdn/core.js +3 -3
- package/dist/src/cdn/core.mjs +642 -1
- package/dist/src/cdn/router-JYFZP3EZ.mjs +70 -0
- package/dist/src/index.cjs +1280 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -11
- package/dist/src/index.mjs +1393 -1
- package/dist/src/jsx-dev-runtime.cjs +26 -1
- package/dist/src/jsx-dev-runtime.mjs +24 -1
- package/dist/src/jsx-runtime.cjs +26 -1
- package/dist/src/jsx-runtime.js +0 -9
- package/dist/src/jsx-runtime.mjs +24 -1
- package/dist/src/lib/client/chunk-7IUN2FLI.mjs +133 -0
- package/dist/src/lib/client/index.cjs +956 -1
- package/dist/src/lib/client/index.d.ts +17 -6
- package/dist/src/lib/client/index.d.ts.map +1 -1
- package/dist/src/lib/client/index.js +26 -19
- package/dist/src/lib/client/index.mjs +222 -1
- package/dist/src/lib/client/router-NOFEMOEH.mjs +631 -0
- package/dist/src/lib/context/context.cjs +118 -1
- package/dist/src/lib/context/context.d.ts +19 -37
- package/dist/src/lib/context/context.d.ts.map +1 -1
- package/dist/src/lib/context/context.js +8 -45
- package/dist/src/lib/context/context.mjs +99 -1
- package/dist/src/lib/context/index.cjs +120 -1
- package/dist/src/lib/context/index.mjs +99 -1
- package/dist/src/lib/control-flow.cjs +35 -1
- package/dist/src/lib/control-flow.mjs +25 -1
- package/dist/src/lib/core.cjs +1279 -1
- package/dist/src/lib/core.d.ts +0 -2
- package/dist/src/lib/core.d.ts.map +1 -1
- package/dist/src/lib/core.js +14 -48
- package/dist/src/lib/core.mjs +1393 -1
- package/dist/src/lib/env/index.cjs +60 -1
- package/dist/src/lib/env/index.d.ts +5 -5
- package/dist/src/lib/env/index.js +5 -5
- package/dist/src/lib/env/index.mjs +39 -1
- package/dist/src/lib/i18n/index.cjs +108 -1
- package/dist/src/lib/i18n/index.d.ts +31 -10
- package/dist/src/lib/i18n/index.d.ts.map +1 -1
- package/dist/src/lib/i18n/index.js +31 -10
- package/dist/src/lib/i18n/index.mjs +88 -1
- package/dist/src/lib/index.cjs +633 -1
- package/dist/src/lib/index.d.ts +0 -2
- package/dist/src/lib/index.d.ts.map +1 -1
- package/dist/src/lib/index.js +5 -11
- package/dist/src/lib/index.mjs +721 -1
- package/dist/src/lib/isomorphic.cjs +85 -1
- package/dist/src/lib/isomorphic.d.ts +41 -8
- package/dist/src/lib/isomorphic.d.ts.map +1 -1
- package/dist/src/lib/isomorphic.js +38 -5
- package/dist/src/lib/isomorphic.mjs +64 -1
- package/dist/src/lib/jsx-runtime/index.cjs +21 -1
- package/dist/src/lib/jsx-runtime/index.d.ts +1 -1
- package/dist/src/lib/jsx-runtime/index.js +1 -11
- package/dist/src/lib/jsx-runtime/index.mjs +4 -1
- package/dist/src/lib/jsx-runtime/jsx-dev-runtime.cjs +21 -1
- package/dist/src/lib/jsx-runtime/jsx-dev-runtime.mjs +4 -1
- package/dist/src/lib/plugins/fluixi-routes-plugin.cjs +302 -8
- package/dist/src/lib/plugins/fluixi-routes-plugin.d.ts +15 -1
- package/dist/src/lib/plugins/fluixi-routes-plugin.d.ts.map +1 -1
- package/dist/src/lib/plugins/fluixi-routes-plugin.js +19 -5
- package/dist/src/lib/plugins/fluixi-routes-plugin.mjs +271 -8
- package/dist/src/lib/plugins/index.cjs +311 -8
- package/dist/src/lib/plugins/index.mjs +279 -8
- package/dist/src/lib/plugins/route-codegen.cjs +73 -8
- package/dist/src/lib/plugins/route-codegen.d.ts +0 -1
- package/dist/src/lib/plugins/route-codegen.d.ts.map +1 -1
- package/dist/src/lib/plugins/route-codegen.js +4 -5
- package/dist/src/lib/plugins/route-codegen.mjs +52 -8
- package/dist/src/lib/plugins/route-scanner.cjs +189 -1
- package/dist/src/lib/plugins/route-scanner.d.ts +1 -2
- package/dist/src/lib/plugins/route-scanner.d.ts.map +1 -1
- package/dist/src/lib/plugins/route-scanner.js +7 -8
- package/dist/src/lib/plugins/route-scanner.mjs +158 -1
- package/dist/src/lib/plugins/vite.cjs +27 -1
- package/dist/src/lib/plugins/vite.mjs +6 -1
- package/dist/src/lib/render/await.cjs +38 -1
- package/dist/src/lib/render/await.d.ts +18 -0
- package/dist/src/lib/render/await.d.ts.map +1 -1
- package/dist/src/lib/render/await.js +20 -2
- package/dist/src/lib/render/await.mjs +17 -1
- package/dist/src/lib/render/component.cjs +221 -1
- package/dist/src/lib/render/component.d.ts +3 -44
- package/dist/src/lib/render/component.d.ts.map +1 -1
- package/dist/src/lib/render/component.js +23 -55
- package/dist/src/lib/render/component.mjs +214 -1
- package/dist/src/lib/render/deferred.cjs +175 -1
- package/dist/src/lib/render/deferred.js +5 -5
- package/dist/src/lib/render/deferred.mjs +158 -1
- package/dist/src/lib/render/index.cjs +573 -1
- package/dist/src/lib/render/index.d.ts +3 -17
- package/dist/src/lib/render/index.d.ts.map +1 -1
- package/dist/src/lib/render/index.js +8 -22
- package/dist/src/lib/render/index.mjs +590 -1
- package/dist/src/lib/render/lazy.cjs +67 -1
- package/dist/src/lib/render/lazy.d.ts +22 -1
- package/dist/src/lib/render/lazy.d.ts.map +1 -1
- package/dist/src/lib/render/lazy.js +26 -5
- package/dist/src/lib/render/lazy.mjs +50 -1
- package/dist/src/lib/render/suspense.cjs +141 -1
- package/dist/src/lib/render/suspense.d.ts +40 -0
- package/dist/src/lib/render/suspense.d.ts.map +1 -1
- package/dist/src/lib/render/suspense.js +46 -6
- package/dist/src/lib/render/suspense.mjs +136 -1
- package/dist/src/lib/render/versions.cjs +35 -1
- package/dist/src/lib/render/versions.js +6 -6
- package/dist/src/lib/render/versions.mjs +14 -1
- package/dist/src/lib/router/data.cjs +230 -1
- package/dist/src/lib/router/data.d.ts +1 -13
- package/dist/src/lib/router/data.d.ts.map +1 -1
- package/dist/src/lib/router/data.js +0 -12
- package/dist/src/lib/router/data.mjs +214 -1
- package/dist/src/lib/router/index.cjs +709 -1
- package/dist/src/lib/router/index.d.ts +78 -23
- package/dist/src/lib/router/index.d.ts.map +1 -1
- package/dist/src/lib/router/index.js +161 -37
- package/dist/src/lib/router/index.mjs +722 -1
- package/dist/src/lib/router/transition.cjs +75 -0
- package/dist/src/lib/router/transition.d.ts +81 -0
- package/dist/src/lib/router/transition.d.ts.map +1 -0
- package/dist/src/lib/router/transition.js +136 -0
- package/dist/src/lib/router/transition.mjs +54 -0
- package/dist/src/lib/server/hydration.cjs +169 -1
- package/dist/src/lib/server/hydration.d.ts +15 -55
- package/dist/src/lib/server/hydration.d.ts.map +1 -1
- package/dist/src/lib/server/hydration.js +23 -62
- package/dist/src/lib/server/hydration.mjs +148 -1
- package/dist/src/lib/server/index.cjs +401 -11
- package/dist/src/lib/server/index.d.ts +30 -6
- package/dist/src/lib/server/index.d.ts.map +1 -1
- package/dist/src/lib/server/index.js +34 -11
- package/dist/src/lib/server/index.mjs +379 -11
- package/dist/src/lib/server/reactive.cjs +197 -1
- package/dist/src/lib/server/reactive.d.ts +18 -77
- package/dist/src/lib/server/reactive.d.ts.map +1 -1
- package/dist/src/lib/server/reactive.js +19 -78
- package/dist/src/lib/server/reactive.mjs +176 -1
- package/dist/src/lib/theme/index.cjs +110 -1
- package/dist/src/lib/theme/index.d.ts +19 -4
- package/dist/src/lib/theme/index.d.ts.map +1 -1
- package/dist/src/lib/theme/index.js +17 -2
- package/dist/src/lib/theme/index.mjs +89 -1
- package/dist/src/lib/utils/index.cjs +209 -1
- package/dist/src/lib/utils/index.d.ts +0 -93
- package/dist/src/lib/utils/index.d.ts.map +1 -1
- package/dist/src/lib/utils/index.js +0 -93
- package/dist/src/lib/utils/index.mjs +188 -1
- package/dist/src/routes.cjs +37 -1
- package/dist/src/routes.d.ts +1 -1
- package/dist/src/routes.js +2 -2
- package/dist/src/routes.mjs +17 -1
- package/dist/src/server.cjs +400 -11
- package/dist/src/server.d.ts +0 -1
- package/dist/src/server.d.ts.map +1 -1
- package/dist/src/server.js +0 -1
- package/dist/src/server.mjs +377 -11
- package/dist/src/utils.cjs +21 -1
- package/dist/src/utils.d.ts +1 -1
- package/dist/src/utils.js +1 -1
- package/dist/src/utils.mjs +4 -1
- package/dist/src/version.generated.cjs +26 -1
- package/dist/src/version.generated.d.ts +1 -1
- package/dist/src/version.generated.js +2 -2
- package/dist/src/version.generated.mjs +5 -1
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +17 -16
- package/src/index.ts +2 -14
- package/dist/src/cdn/chunk-55BT3H6E.mjs +0 -1
- package/dist/src/cdn/router-5JJZBK4E.mjs +0 -1
- package/dist/src/lib/client/chunk-SM4WHBOP.mjs +0 -1
- package/dist/src/lib/client/router-IBD4WPQT.mjs +0 -1
|
@@ -1,17 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* @fileoverview Fluixi adapter for the framework-agnostic @fluixi/router core.
|
|
3
|
-
*
|
|
4
|
-
* Renders the resolved match tree TOP-DOWN. Each layer's component is rendered
|
|
5
|
-
* inside the PARENT layer's <Outlet/> scope, so a child route inherits whatever
|
|
6
|
-
* context a parent layout provides. <Outlet/> is reactive but keyed on the child's
|
|
7
|
-
* component reference, so it only re-renders when the child route at that depth
|
|
8
|
-
* actually changes — sibling navigation reuses the layout, deeper navigation
|
|
9
|
-
* leaves shallower layouts untouched.
|
|
10
|
-
*
|
|
11
|
-
* No parentGate, no deferred content memo: a child is rendered straight from the
|
|
12
|
-
* matched array, never by re-entering a still-building parent, so deeply nested
|
|
13
|
-
* <Outlet/> works at any depth.
|
|
14
|
-
*/
|
|
1
|
+
import { type TransitionMode } from './transition.js';
|
|
15
2
|
import { type GetAtPath } from '@fluixi/utils/object';
|
|
16
3
|
import type { IsUndefined } from '@fluixi/utils/primitive';
|
|
17
4
|
import { type RouteDefinition, type Router as CoreRouter, type HistoryAdapter, type RouterLocation, type NavigateOptions } from '@fluixi/router';
|
|
@@ -25,12 +12,36 @@ export interface RouterProps<C = any> {
|
|
|
25
12
|
base?: string;
|
|
26
13
|
history?: HistoryAdapter;
|
|
27
14
|
/**
|
|
28
|
-
* Rendered when the current URL matches no route
|
|
15
|
+
* Rendered when the current URL matches no route, a component, an element, or any renderable.
|
|
29
16
|
* Defaults to a minimal built-in 404 so an unmatched URL never renders a blank page. For a 404
|
|
30
|
-
* that keeps your layout/chrome, add a catch-all file route instead (`[...slug].tsx`
|
|
17
|
+
* that keeps your layout/chrome, add a catch-all file route instead (`[...slug].tsx` -> `*`).
|
|
31
18
|
*/
|
|
32
19
|
notFound?: any;
|
|
20
|
+
/**
|
|
21
|
+
* How route changes are presented. `'auto'` (the default) hands the DOM swap to the
|
|
22
|
+
* browser's View Transition API where there is one, and navigates directly where
|
|
23
|
+
* there is not; `'none'` always navigates directly.
|
|
24
|
+
*
|
|
25
|
+
* A reader who asked for reduced motion never gets an animation either way. Styling
|
|
26
|
+
* is the native mechanism: `::view-transition-old(root)` and
|
|
27
|
+
* `::view-transition-new(root)`: so the router imposes no visual style of its own.
|
|
28
|
+
*/
|
|
29
|
+
transition?: TransitionMode;
|
|
33
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* The router. Matches the current URL against `routes` and renders what matches.
|
|
33
|
+
*
|
|
34
|
+
* Supplies the router context everything below reaches through `useRouter`,
|
|
35
|
+
* `useNavigate` and `Link`, so those only work inside it. `history` defaults to a
|
|
36
|
+
* browser history; pass a memory history for tests or a server render. An unmatched URL
|
|
37
|
+
* renders `notFound`, which defaults to a minimal built-in page so a bad URL is never
|
|
38
|
+
* blank. For a 404 that keeps your layout, add a catch-all file route instead.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* html`<${Router} routes=${routes} base="/app" />`;
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
34
45
|
export declare function Router<C = any>(props: RouterProps<C>): any;
|
|
35
46
|
/** Renders this layout's child route, inside this layout's context. */
|
|
36
47
|
export declare function Outlet(): any;
|
|
@@ -39,6 +50,14 @@ export declare function Redirect(props: {
|
|
|
39
50
|
to: string;
|
|
40
51
|
replace?: boolean;
|
|
41
52
|
}): null;
|
|
53
|
+
/**
|
|
54
|
+
* The router context: the router itself, the current match, and the location.
|
|
55
|
+
*
|
|
56
|
+
* Safe to call during a server render, where a detached context yields a no-op stub
|
|
57
|
+
* rather than throwing, since an SSR pass has nowhere to navigate to. On the client a
|
|
58
|
+
* missing router is genuine misuse and throws, because the alternative is a component
|
|
59
|
+
* that silently never navigates.
|
|
60
|
+
*/
|
|
42
61
|
export declare function useRouter(): RouterCtxValue;
|
|
43
62
|
/**
|
|
44
63
|
* Reactive merged params across all matched layers, as an accessor. Deep-equal
|
|
@@ -50,7 +69,7 @@ export declare function useParams<T extends Record<string, string> = Record<stri
|
|
|
50
69
|
export declare function useLevelParams(): Record<string, string>;
|
|
51
70
|
/**
|
|
52
71
|
* Reactive accessor for a single route param. Default `===` equality on the
|
|
53
|
-
* string value, so it only re-triggers when
|
|
72
|
+
* string value, so it only re-triggers when this key changes, not when other
|
|
54
73
|
* params at the same level change.
|
|
55
74
|
*/
|
|
56
75
|
export declare function useParam(key: string): () => string | undefined;
|
|
@@ -97,9 +116,11 @@ export declare function useLocation(): RouterLocation;
|
|
|
97
116
|
export interface LinkNavigationOptions {
|
|
98
117
|
/** Replace the current history entry instead of pushing a new one. */
|
|
99
118
|
replace?: boolean;
|
|
119
|
+
/** How this navigation is presented. Defaults to the Router's own setting. */
|
|
120
|
+
transition?: TransitionMode;
|
|
100
121
|
/**
|
|
101
122
|
* Called with the resolved URL before navigating. Return false to leave the click
|
|
102
|
-
* alone
|
|
123
|
+
* alone, for a section of the app that must load as a document (a different
|
|
103
124
|
* bundle, a server-rendered admin area, a file route).
|
|
104
125
|
*/
|
|
105
126
|
shouldNavigate?: (url: URL, anchor: HTMLAnchorElement) => boolean;
|
|
@@ -117,17 +138,35 @@ export interface LinkNavigationOptions {
|
|
|
117
138
|
* <article onClick={onClick} innerHTML={doc.html} />
|
|
118
139
|
* ```
|
|
119
140
|
*
|
|
120
|
-
* The anchors stay real anchors
|
|
141
|
+
* The anchors stay real anchors: middle-click, right-click -> open in new tab, "copy
|
|
121
142
|
* link address" and crawlers all keep working, because the element is untouched and only
|
|
122
143
|
* a plain left-click is intercepted.
|
|
123
144
|
*
|
|
124
145
|
* Left alone: modified clicks, anything but the primary button, an already-prevented
|
|
125
146
|
* event, `target`, `download`, `rel="external"`, a non-http scheme (`mailto:`, `tel:`),
|
|
126
|
-
* another origin, and a hash pointing inside the current page
|
|
147
|
+
* another origin, and a hash pointing inside the current page, that last one is the
|
|
127
148
|
* browser's scroll, and hijacking it breaks in-page anchors.
|
|
128
149
|
*/
|
|
129
150
|
export declare function useLinkNavigation(options?: LinkNavigationOptions): (event: MouseEvent) => void;
|
|
130
|
-
|
|
151
|
+
/**
|
|
152
|
+
* A function that navigates, for use outside a `Link`.
|
|
153
|
+
*
|
|
154
|
+
* Reach for it after an action rather than for a plain link: a form that redirects on
|
|
155
|
+
* success, a guard that bounces an unauthenticated visitor. Prefer `Link` for anything a
|
|
156
|
+
* visitor clicks, since an anchor is what middle-click, copy-link and a crawler expect.
|
|
157
|
+
*
|
|
158
|
+
* Navigation is a no-op during a server render, for the reason `useRouter` explains.
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* ```ts
|
|
162
|
+
* const navigate = useNavigate();
|
|
163
|
+
* await save(form);
|
|
164
|
+
* navigate(`/posts/${id}`, { replace: true });
|
|
165
|
+
* ```
|
|
166
|
+
*/
|
|
167
|
+
export declare function useNavigate(): (to: string, options?: NavigateOptions & {
|
|
168
|
+
transition?: TransitionMode;
|
|
169
|
+
}) => void;
|
|
131
170
|
/**
|
|
132
171
|
* The resolved data for the nearest route that declared a `data` loader. Always
|
|
133
172
|
* returns a callable accessor (matching the old router): it suspends under
|
|
@@ -147,13 +186,28 @@ export interface LinkProps {
|
|
|
147
186
|
slot?: string;
|
|
148
187
|
children?: any;
|
|
149
188
|
/**
|
|
150
|
-
* Everything else is forwarded onto the `<a
|
|
189
|
+
* Everything else is forwarded onto the `<a>`: id, style, title, aria-*,
|
|
151
190
|
* data-*, event handlers. Typed openly because the forwarding is
|
|
152
191
|
* allow-by-default: a prop this interface forgot still reaches the DOM, and
|
|
153
192
|
* refusing it here would only mean the type disagreeing with the runtime.
|
|
154
193
|
*/
|
|
155
194
|
[key: string]: unknown;
|
|
156
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* An anchor that navigates without a full page load.
|
|
198
|
+
*
|
|
199
|
+
* Renders a real `<a href>`, so middle-click, copy-link, and a crawler all behave. A
|
|
200
|
+
* plain left click is intercepted and routed instead.
|
|
201
|
+
*
|
|
202
|
+
* Active state defaults to the current pathname matching `href`, and `activeClass` is
|
|
203
|
+
* added while it holds. Override `active` when a section link should stay lit for its
|
|
204
|
+
* children.
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* ```ts
|
|
208
|
+
* html`<${Link} href="/about" activeClass="current">About</${Link}>`;
|
|
209
|
+
* ```
|
|
210
|
+
*/
|
|
157
211
|
export declare function Link(props: LinkProps): any;
|
|
158
212
|
/** Alias for {@link Link}. */
|
|
159
213
|
export declare const A: typeof Link;
|
|
@@ -180,7 +234,7 @@ export interface FormProps {
|
|
|
180
234
|
export declare function Form(props: FormProps): any;
|
|
181
235
|
/**
|
|
182
236
|
* Download the lazy component chunks for the route(s) matching `pathname` and resolve when
|
|
183
|
-
* they're all loaded. Await this
|
|
237
|
+
* they're all loaded. Await this before hydrating: once each matched lazy file-route is
|
|
184
238
|
* resolved it renders synchronously on the first pass (no Suspense fallback, no late
|
|
185
239
|
* re-render), so hydration adopts the server DOM in place instead of building a duplicate
|
|
186
240
|
* subtree next to it. No-op for routes with no lazy components. Safe to call when the URL
|
|
@@ -192,5 +246,6 @@ export type { RouteDefinition, RouteMatch, MatchedRoute, RouterLocation, Navigat
|
|
|
192
246
|
export { cache, action, revalidate, createAsync, useSubmission, useAction, } from './data.js';
|
|
193
247
|
export type { RouteDataArgs, RouteLoadFunc, Action, Submission, CacheFn, } from './data.js';
|
|
194
248
|
export { lazy } from '../render/lazy.js';
|
|
249
|
+
export { supportsViewTransitions, prefersReducedMotion, type TransitionMode, } from './transition.js';
|
|
195
250
|
export { runWithOwner } from '@fluixi/reactive/signal';
|
|
196
251
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/router/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/router/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Fluixi adapter for the framework-agnostic @fluixi/router core.
|
|
3
2
|
*
|
|
4
3
|
* Renders the resolved match tree TOP-DOWN. Each layer's component is rendered
|
|
5
4
|
* inside the PARENT layer's <Outlet/> scope, so a child route inherits whatever
|
|
6
5
|
* context a parent layout provides. <Outlet/> is reactive but keyed on the child's
|
|
7
6
|
* component reference, so it only re-renders when the child route at that depth
|
|
8
|
-
* actually changes
|
|
7
|
+
* actually changes: sibling navigation reuses the layout, deeper navigation
|
|
9
8
|
* leaves shallower layouts untouched.
|
|
10
9
|
*
|
|
11
10
|
* No parentGate, no deferred content memo: a child is rendered straight from the
|
|
12
11
|
* matched array, never by re-entering a still-building parent, so deeply nested
|
|
13
12
|
* <Outlet/> works at any depth.
|
|
14
13
|
*/
|
|
15
|
-
import { createSignal, createMemo, createResource, createEffect, getOwner, onCleanup, untrack, } from '@fluixi/reactive/signal';
|
|
14
|
+
import { createSignal, createMemo, createResource, createEffect, getOwner, onCleanup, untrack, flush, } from '@fluixi/reactive/signal';
|
|
15
|
+
import { prepareComponents, runTransition, } from './transition.js';
|
|
16
16
|
import { insert, createComponent, createNativeElement, setAttribute, } from '@fluixi/dom';
|
|
17
17
|
// Reactive context (not fluixi createProvider, which destructures children
|
|
18
|
-
// eagerly) so a `get children()` getter renders
|
|
18
|
+
// eagerly) so a `get children()` getter renders after the context is stamped.
|
|
19
19
|
import { createContext, useContext } from '@fluixi/reactive/signal';
|
|
20
20
|
import { get } from '@fluixi/utils/object';
|
|
21
21
|
import { deepEqual } from '@fluixi/utils/compare';
|
|
@@ -59,14 +59,14 @@ function renderLayer(m, depth, base) {
|
|
|
59
59
|
});
|
|
60
60
|
if (!data)
|
|
61
61
|
return buildPage();
|
|
62
|
-
// Gate on the data being resolved
|
|
62
|
+
// Gate on the data being resolved, like the old router, so the page reads
|
|
63
63
|
// ready data (rd() returns the value, not a thrown promise) and works without a
|
|
64
64
|
// Suspense boundary. Signal-gated (`ready` only flips when loading actually
|
|
65
65
|
// changes) so navigation re-renders don't re-run this and rebuild the page.
|
|
66
66
|
const res = data;
|
|
67
67
|
const isLoading = () => res.loading;
|
|
68
68
|
// untrack the initial read so the enclosing render memo doesn't subscribe to the
|
|
69
|
-
// resource state (that would re-run renderLayer and recreate the resource
|
|
69
|
+
// resource state (that would re-run renderLayer and recreate the resource, a loop).
|
|
70
70
|
const [ready, setReady] = createSignal(untrack(() => !isLoading()));
|
|
71
71
|
createEffect(() => setReady(!isLoading()));
|
|
72
72
|
return createMemo(() => (ready() ? buildPage() : null));
|
|
@@ -83,19 +83,23 @@ function renderChild(ctx) {
|
|
|
83
83
|
// signal write (no value check), so a memo that read `matched` would re-run on
|
|
84
84
|
// every navigation and re-render the child. A signal only notifies when its value
|
|
85
85
|
// actually changes, so the render memo below re-runs only when the route at this
|
|
86
|
-
// depth changes
|
|
86
|
+
// depth changes: sibling navigation reuses the layout, deeper navigation leaves
|
|
87
87
|
// this level untouched. The child is rendered INLINE (no detached createRoot), so
|
|
88
88
|
// it stays connected to the Suspense / context owner chain.
|
|
89
89
|
const [childComp, setChildComp] = createSignal(untrack(() => ctx.matched()[childIdx]?.component));
|
|
90
90
|
createEffect(() => {
|
|
91
91
|
const c = ctx.matched()[childIdx]?.component;
|
|
92
|
-
|
|
92
|
+
// `matched` is invalidated more than once per navigation, so this effect runs more
|
|
93
|
+
// than once. Writing only on a real change keeps the redundant runs from touching
|
|
94
|
+
// the graph at all.
|
|
95
|
+
if (untrack(childComp) !== c)
|
|
96
|
+
setChildComp(() => c);
|
|
93
97
|
});
|
|
94
98
|
return createMemo(() => {
|
|
95
99
|
const component = childComp();
|
|
96
100
|
if (!component) {
|
|
97
|
-
// No route at this depth. At the
|
|
98
|
-
// URL matched nothing
|
|
101
|
+
// No route at this depth. At the root (the only ctx carrying `notFound`) that means the
|
|
102
|
+
// URL matched nothing: render the not-found fallback instead of a blank page. Nested
|
|
99
103
|
// Outlets have no `notFound`, so they stay null (a missing child is just an empty Outlet).
|
|
100
104
|
return ctx.notFound ? ctx.notFound() : null;
|
|
101
105
|
}
|
|
@@ -110,8 +114,8 @@ function renderChild(ctx) {
|
|
|
110
114
|
/**
|
|
111
115
|
* Resolve the Router's `notFound` prop to a renderable view. A component is instantiated; an
|
|
112
116
|
* element/value is returned as-is; when omitted, a minimal built-in 404 keeps the page from
|
|
113
|
-
* rendering blank. Prefer a catch-all file route (`[...slug].tsx`
|
|
114
|
-
* inside your layout
|
|
117
|
+
* rendering blank. Prefer a catch-all file route (`[...slug].tsx` -> `*`) when the 404 should sit
|
|
118
|
+
* inside your layout, this prop is the app-level fallback for an otherwise unmatched URL.
|
|
115
119
|
*/
|
|
116
120
|
function renderNotFound(notFound) {
|
|
117
121
|
if (notFound == null)
|
|
@@ -135,6 +139,25 @@ function buildDefaultNotFound() {
|
|
|
135
139
|
el.appendChild(p);
|
|
136
140
|
return el;
|
|
137
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* The presentation for the navigation about to happen, handed from `useNavigate` to the
|
|
144
|
+
* subscriber in `Router`. Read and cleared within the same synchronous call.
|
|
145
|
+
*/
|
|
146
|
+
let pendingTransitionMode;
|
|
147
|
+
/**
|
|
148
|
+
* The router. Matches the current URL against `routes` and renders what matches.
|
|
149
|
+
*
|
|
150
|
+
* Supplies the router context everything below reaches through `useRouter`,
|
|
151
|
+
* `useNavigate` and `Link`, so those only work inside it. `history` defaults to a
|
|
152
|
+
* browser history; pass a memory history for tests or a server render. An unmatched URL
|
|
153
|
+
* renders `notFound`, which defaults to a minimal built-in page so a bad URL is never
|
|
154
|
+
* blank. For a 404 that keeps your layout, add a catch-all file route instead.
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* ```ts
|
|
158
|
+
* html`<${Router} routes=${routes} base="/app" />`;
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
138
161
|
export function Router(props) {
|
|
139
162
|
const history = props.history ?? createBrowserHistory();
|
|
140
163
|
const router = createCoreRouter({
|
|
@@ -143,12 +166,64 @@ export function Router(props) {
|
|
|
143
166
|
history,
|
|
144
167
|
});
|
|
145
168
|
const [state, setState] = createSignal(router.state());
|
|
146
|
-
|
|
169
|
+
/**
|
|
170
|
+
* Which navigation is allowed to commit.
|
|
171
|
+
*
|
|
172
|
+
* Preparation is async, so two navigations can be in flight at once: A→B starts
|
|
173
|
+
* loading B's chunk, the reader clicks through to C before it lands. Each navigation
|
|
174
|
+
* takes the next number and checks it is still the current one before writing the
|
|
175
|
+
* match signal, so B resolving late finds itself superseded and returns without
|
|
176
|
+
* touching anything. Without this, B would commit over C, the URL would say /c and
|
|
177
|
+
* the page would show B.
|
|
178
|
+
*/
|
|
179
|
+
let navigationId = 0;
|
|
180
|
+
/**
|
|
181
|
+
* How to present the next commit, set by `useNavigate` immediately before it calls
|
|
182
|
+
* into the core router.
|
|
183
|
+
*
|
|
184
|
+
* A module-level hand-off rather than a parameter because the core router's
|
|
185
|
+
* `navigate` does not carry options through to its listeners, and it notifies
|
|
186
|
+
* synchronously: the value is written and read within the same call, never left
|
|
187
|
+
* lying around for an unrelated navigation to pick up.
|
|
188
|
+
*/
|
|
189
|
+
const takeMode = () => {
|
|
190
|
+
const mode = pendingTransitionMode ?? (props.transition ?? 'auto');
|
|
191
|
+
pendingTransitionMode = undefined;
|
|
192
|
+
return mode;
|
|
193
|
+
};
|
|
194
|
+
const unsub = router.subscribe((next) => {
|
|
195
|
+
const id = ++navigationId;
|
|
196
|
+
const mode = takeMode();
|
|
197
|
+
// Load the modules the next route needs before anything is torn down, so a lazy
|
|
198
|
+
// route renders straight through instead of suspending into a fallback with the
|
|
199
|
+
// old page already gone.
|
|
200
|
+
const prepared = prepareComponents((next.match?.matched ?? []).map((m) => m.component).filter(Boolean));
|
|
201
|
+
const commit = () => {
|
|
202
|
+
setState(next);
|
|
203
|
+
// The write only queues; effects are scheduled. Flushing here is what lets the
|
|
204
|
+
// browser snapshot a settled DOM rather than a half-built one.
|
|
205
|
+
return flush();
|
|
206
|
+
};
|
|
207
|
+
if (!prepared) {
|
|
208
|
+
// Nothing to wait for: stay on the tick this navigation was already on.
|
|
209
|
+
void runTransition(mode, commit);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
void prepared.then(() => {
|
|
213
|
+
// Superseded while the module loaded. The newer navigation owns the DOM now.
|
|
214
|
+
if (id !== navigationId)
|
|
215
|
+
return;
|
|
216
|
+
return runTransition(mode, commit);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
147
219
|
onCleanup(() => {
|
|
148
220
|
unsub();
|
|
149
221
|
router.destroy();
|
|
222
|
+
// Anything still preparing is now stale, so nothing can commit into a torn-down
|
|
223
|
+
// router.
|
|
224
|
+
navigationId++;
|
|
150
225
|
});
|
|
151
|
-
// Drop pathless layers (no component)
|
|
226
|
+
// Drop pathless layers (no component), their params still appear in the merged
|
|
152
227
|
// params exposed by RouterContext.
|
|
153
228
|
const matched = createMemo(() => (state().match?.matched ?? []).filter((m) => m.component));
|
|
154
229
|
const ctx = {
|
|
@@ -162,12 +237,12 @@ export function Router(props) {
|
|
|
162
237
|
matched,
|
|
163
238
|
params: ctx.params,
|
|
164
239
|
location: ctx.location,
|
|
165
|
-
// Only the root carries this
|
|
240
|
+
// Only the root carries this: renderChild shows it when the URL matched nothing.
|
|
166
241
|
notFound: () => renderNotFound(props.notFound),
|
|
167
242
|
};
|
|
168
243
|
// Build the root view once, lazily, inside the provider's children getter: the
|
|
169
244
|
// getter must run in the provider scope (so components can useRouter), but the
|
|
170
|
-
// reactive provider re-invokes it whenever content changes
|
|
245
|
+
// reactive provider re-invokes it whenever content changes: caching keeps the
|
|
171
246
|
// root chain from being rebuilt on every navigation.
|
|
172
247
|
let rootView;
|
|
173
248
|
return RouterContext.Provider({
|
|
@@ -190,7 +265,7 @@ export function Redirect(props) {
|
|
|
190
265
|
const ctx = useContext(RouterContext);
|
|
191
266
|
// Nothing to do without a router, and nothing to navigate on the server.
|
|
192
267
|
if (ctx && typeof window !== 'undefined') {
|
|
193
|
-
// Navigate
|
|
268
|
+
// Navigate after the current render commits. Navigating during render (an effect
|
|
194
269
|
// running mid-render) updated the location but the route never re-matched, so the
|
|
195
270
|
// target never showed. Guard against firing if this Redirect is torn down first.
|
|
196
271
|
let cancelled = false;
|
|
@@ -204,9 +279,9 @@ export function Redirect(props) {
|
|
|
204
279
|
}
|
|
205
280
|
// ─── Hooks ──────────────────────────────────────────────────────────────────
|
|
206
281
|
// Safe stub returned by useRouter on the SERVER when no RouterContext is on the owner
|
|
207
|
-
// chain. That happens during a late/detached re-render
|
|
208
|
-
// settling
|
|
209
|
-
// root
|
|
282
|
+
// chain. That happens during a late/detached re-render: e.g. a slow data resource
|
|
283
|
+
// settling after renderToStringAsync already serialized the response and disposed the
|
|
284
|
+
// root: whose output is discarded. Returning this (instead of throwing) prevents a
|
|
210
285
|
// floating rejection that crashes the dev server / shows an error overlay seconds
|
|
211
286
|
// after the page already rendered.
|
|
212
287
|
const _ssrRouterStub = {
|
|
@@ -214,12 +289,20 @@ const _ssrRouterStub = {
|
|
|
214
289
|
location: () => ({ pathname: '', search: '', hash: '', query: {} }),
|
|
215
290
|
params: () => ({}),
|
|
216
291
|
};
|
|
292
|
+
/**
|
|
293
|
+
* The router context: the router itself, the current match, and the location.
|
|
294
|
+
*
|
|
295
|
+
* Safe to call during a server render, where a detached context yields a no-op stub
|
|
296
|
+
* rather than throwing, since an SSR pass has nowhere to navigate to. On the client a
|
|
297
|
+
* missing router is genuine misuse and throws, because the alternative is a component
|
|
298
|
+
* that silently never navigates.
|
|
299
|
+
*/
|
|
217
300
|
export function useRouter() {
|
|
218
301
|
const ctx = useContext(RouterContext);
|
|
219
302
|
if (ctx)
|
|
220
303
|
return ctx;
|
|
221
304
|
// On the server, degrade to a no-op stub (see above). On the client, a missing
|
|
222
|
-
// router is genuine misuse
|
|
305
|
+
// router is genuine misuse: throw.
|
|
223
306
|
if (typeof window === 'undefined')
|
|
224
307
|
return _ssrRouterStub;
|
|
225
308
|
throw new Error('[router] useRouter must be used within <Router>');
|
|
@@ -241,7 +324,7 @@ export function useLevelParams() {
|
|
|
241
324
|
}
|
|
242
325
|
/**
|
|
243
326
|
* Reactive accessor for a single route param. Default `===` equality on the
|
|
244
|
-
* string value, so it only re-triggers when
|
|
327
|
+
* string value, so it only re-triggers when this key changes, not when other
|
|
245
328
|
* params at the same level change.
|
|
246
329
|
*/
|
|
247
330
|
export function useParam(key) {
|
|
@@ -331,13 +414,13 @@ export function useLocation() {
|
|
|
331
414
|
* <article onClick={onClick} innerHTML={doc.html} />
|
|
332
415
|
* ```
|
|
333
416
|
*
|
|
334
|
-
* The anchors stay real anchors
|
|
417
|
+
* The anchors stay real anchors: middle-click, right-click -> open in new tab, "copy
|
|
335
418
|
* link address" and crawlers all keep working, because the element is untouched and only
|
|
336
419
|
* a plain left-click is intercepted.
|
|
337
420
|
*
|
|
338
421
|
* Left alone: modified clicks, anything but the primary button, an already-prevented
|
|
339
422
|
* event, `target`, `download`, `rel="external"`, a non-http scheme (`mailto:`, `tel:`),
|
|
340
|
-
* another origin, and a hash pointing inside the current page
|
|
423
|
+
* another origin, and a hash pointing inside the current page, that last one is the
|
|
341
424
|
* browser's scroll, and hijacking it breaks in-page anchors.
|
|
342
425
|
*/
|
|
343
426
|
export function useLinkNavigation(options = {}) {
|
|
@@ -369,15 +452,38 @@ export function useLinkNavigation(options = {}) {
|
|
|
369
452
|
if (options.shouldNavigate && !options.shouldNavigate(url, anchor))
|
|
370
453
|
return;
|
|
371
454
|
event.preventDefault();
|
|
455
|
+
pendingTransitionMode = options.transition;
|
|
372
456
|
ctx.router.navigate(url.pathname + url.search + url.hash, { replace: options.replace });
|
|
373
457
|
};
|
|
374
458
|
}
|
|
459
|
+
/**
|
|
460
|
+
* A function that navigates, for use outside a `Link`.
|
|
461
|
+
*
|
|
462
|
+
* Reach for it after an action rather than for a plain link: a form that redirects on
|
|
463
|
+
* success, a guard that bounces an unauthenticated visitor. Prefer `Link` for anything a
|
|
464
|
+
* visitor clicks, since an anchor is what middle-click, copy-link and a crawler expect.
|
|
465
|
+
*
|
|
466
|
+
* Navigation is a no-op during a server render, for the reason `useRouter` explains.
|
|
467
|
+
*
|
|
468
|
+
* @example
|
|
469
|
+
* ```ts
|
|
470
|
+
* const navigate = useNavigate();
|
|
471
|
+
* await save(form);
|
|
472
|
+
* navigate(`/posts/${id}`, { replace: true });
|
|
473
|
+
* ```
|
|
474
|
+
*/
|
|
375
475
|
export function useNavigate() {
|
|
376
476
|
// useRouter is SSR-safe (returns a no-op stub on the server when the context is
|
|
377
477
|
// detached), so navigation degrades to a no-op during SSR and only throws on
|
|
378
478
|
// genuine client-side misuse outside <Router>.
|
|
379
479
|
const { router } = useRouter();
|
|
380
|
-
return (to, options) =>
|
|
480
|
+
return (to, options) => {
|
|
481
|
+
// Handed to the Router's subscriber, which runs synchronously inside the call
|
|
482
|
+
// below and clears it. `transition: 'none'` on one navigation leaves the router's
|
|
483
|
+
// own setting alone for the next.
|
|
484
|
+
pendingTransitionMode = options?.transition;
|
|
485
|
+
router.navigate(to, options);
|
|
486
|
+
};
|
|
381
487
|
}
|
|
382
488
|
/**
|
|
383
489
|
* The resolved data for the nearest route that declared a `data` loader. Always
|
|
@@ -400,18 +506,33 @@ const LINK_OWN_PROPS = new Set([
|
|
|
400
506
|
'replace',
|
|
401
507
|
'children',
|
|
402
508
|
]);
|
|
509
|
+
/**
|
|
510
|
+
* An anchor that navigates without a full page load.
|
|
511
|
+
*
|
|
512
|
+
* Renders a real `<a href>`, so middle-click, copy-link, and a crawler all behave. A
|
|
513
|
+
* plain left click is intercepted and routed instead.
|
|
514
|
+
*
|
|
515
|
+
* Active state defaults to the current pathname matching `href`, and `activeClass` is
|
|
516
|
+
* added while it holds. Override `active` when a section link should stay lit for its
|
|
517
|
+
* children.
|
|
518
|
+
*
|
|
519
|
+
* @example
|
|
520
|
+
* ```ts
|
|
521
|
+
* html`<${Link} href="/about" activeClass="current">About</${Link}>`;
|
|
522
|
+
* ```
|
|
523
|
+
*/
|
|
403
524
|
export function Link(props) {
|
|
404
525
|
const ctx = useContext(RouterContext);
|
|
405
526
|
const navigate = () => ctx?.router.navigate(read(props.href) ?? '', { replace: read(props.replace) });
|
|
406
|
-
// createNativeElement branches on isServer()
|
|
527
|
+
// createNativeElement branches on isServer() -> a real ServerNode <a> (serialized as a true
|
|
407
528
|
// element, not an escaped string) on the server, a real DOM <a> on the client.
|
|
408
529
|
const a = createNativeElement('a');
|
|
409
530
|
setAttribute(a, 'href', read(props.href) ?? '');
|
|
410
531
|
if (props.slot)
|
|
411
532
|
setAttribute(a, 'slot', read(props.slot));
|
|
412
|
-
// Forward any other author attributes (data-*, aria-*, style, id, title, role,
|
|
533
|
+
// Forward any other author attributes (data-*, aria-*, style, id, title, role, ...)
|
|
413
534
|
// onto the <a>, the way <Form> does. Without this a component composed via
|
|
414
|
-
// `as={Link}` (polymorphic) silently loses its styling hooks
|
|
535
|
+
// `as={Link}` (polymorphic) silently loses its styling hooks: e.g. @fluixi-ui
|
|
415
536
|
// parts carry `data-*` slots + inline styles that would otherwise be dropped, so
|
|
416
537
|
// the element renders unstyled. Own-props handled elsewhere are skipped:
|
|
417
538
|
// href/slot above, class/className/active/activeClass in the effect, and
|
|
@@ -419,7 +540,7 @@ export function Link(props) {
|
|
|
419
540
|
// Keys, not entries: `Object.entries` reads every prop, and `children` is a getter
|
|
420
541
|
// that builds fresh nodes per access. Under hydration that discarded first build
|
|
421
542
|
// consumes the server's nodes, so the real read below produced a second copy of
|
|
422
|
-
// them
|
|
543
|
+
// them, a duplicated logo and pager on every prerendered page.
|
|
423
544
|
for (const k of Object.keys(props)) {
|
|
424
545
|
if (LINK_OWN_PROPS.has(k))
|
|
425
546
|
continue;
|
|
@@ -444,7 +565,7 @@ export function Link(props) {
|
|
|
444
565
|
setAttribute(a, 'class', base);
|
|
445
566
|
}
|
|
446
567
|
// Client only: keep href + active class in sync with reactive props / location.
|
|
447
|
-
// (Skipped on the server
|
|
568
|
+
// (Skipped on the server: `a` is a ServerNode without these DOM setters; the static
|
|
448
569
|
// href/class were set above.)
|
|
449
570
|
if (typeof a.addEventListener === 'function') {
|
|
450
571
|
createEffect(() => {
|
|
@@ -476,18 +597,18 @@ export const A = Link;
|
|
|
476
597
|
*/
|
|
477
598
|
export function Form(props) {
|
|
478
599
|
const action = props.action;
|
|
479
|
-
const cls = (props.className ?? props.class); // compiler passes class
|
|
600
|
+
const cls = (props.className ?? props.class); // compiler passes class -> className
|
|
480
601
|
const url = action.url ?? '';
|
|
481
602
|
// createNativeElement/setAttribute branch on isServer(), so this builds a real ServerNode
|
|
482
|
-
// <form> on the server (serialized as a true element,
|
|
483
|
-
// DOM <form> on the client
|
|
603
|
+
// <form> on the server (serialized as a true element, not an escaped string) and a real
|
|
604
|
+
// DOM <form> on the client: hydration reuses the same element.
|
|
484
605
|
const form = createNativeElement('form');
|
|
485
606
|
setAttribute(form, 'method', 'post');
|
|
486
607
|
if (url)
|
|
487
608
|
setAttribute(form, 'action', url);
|
|
488
609
|
if (cls)
|
|
489
610
|
setAttribute(form, 'class', cls);
|
|
490
|
-
// Keys, not entries
|
|
611
|
+
// Keys, not entries, same reason as Link: reading `children` here would build its
|
|
491
612
|
// nodes once and throw them away.
|
|
492
613
|
for (const k of Object.keys(props)) {
|
|
493
614
|
if (k === 'action' || k === 'children' || k === 'class' || k === 'className')
|
|
@@ -513,7 +634,7 @@ export function Form(props) {
|
|
|
513
634
|
}
|
|
514
635
|
// Read children ONCE: the compiler emits `get children()` as a getter that builds fresh
|
|
515
636
|
// nodes on every access, so reading it twice (here + the null check) created a duplicate
|
|
516
|
-
// set
|
|
637
|
+
// set: under hydration that meant two <input>s in the form.
|
|
517
638
|
const children = props.children;
|
|
518
639
|
if (children != null)
|
|
519
640
|
insert(form, children);
|
|
@@ -522,7 +643,7 @@ export function Form(props) {
|
|
|
522
643
|
// ─── Hydration preload ───────────────────────────────────────────────────────────
|
|
523
644
|
/**
|
|
524
645
|
* Download the lazy component chunks for the route(s) matching `pathname` and resolve when
|
|
525
|
-
* they're all loaded. Await this
|
|
646
|
+
* they're all loaded. Await this before hydrating: once each matched lazy file-route is
|
|
526
647
|
* resolved it renders synchronously on the first pass (no Suspense fallback, no late
|
|
527
648
|
* re-render), so hydration adopts the server DOM in place instead of building a duplicate
|
|
528
649
|
* subtree next to it. No-op for routes with no lazy components. Safe to call when the URL
|
|
@@ -542,9 +663,12 @@ export async function preloadRoutes(routes, pathname) {
|
|
|
542
663
|
}
|
|
543
664
|
// ─── Re-exports ────────────────────────────────────────────────────────────────
|
|
544
665
|
export { createMemoryHistory, createBrowserHistory, matchRoutes, matchPath, } from '@fluixi/router';
|
|
545
|
-
// Action/cache/lazy layer is orthogonal to routing
|
|
666
|
+
// Action/cache/lazy layer is orthogonal to routing: re-export so a route file can
|
|
546
667
|
// swap its import path wholesale. (Routing hooks above are the router ones.)
|
|
547
668
|
export { cache, action, revalidate, createAsync, useSubmission, useAction, } from './data.js';
|
|
548
669
|
export { lazy } from '../render/lazy.js';
|
|
670
|
+
// Navigation presentation. The router owns the integration; these are exported for
|
|
671
|
+
// apps that need to ask what the environment supports.
|
|
672
|
+
export { supportsViewTransitions, prefersReducedMotion, } from './transition.js';
|
|
549
673
|
// Carried over from the barrel this replaced; route codegen imports it from here.
|
|
550
674
|
export { runWithOwner } from '@fluixi/reactive/signal';
|