@fluixi/core 1.0.0-alpha.92 → 1.0.0-alpha.94
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 -2
- package/dist/src/cdn/{chunk-MDJLC3PE.mjs → chunk-7XM7Q5VR.mjs} +271 -229
- package/dist/src/cdn/core.cjs +360 -253
- package/dist/src/cdn/core.mjs +92 -20
- package/dist/src/cdn/{router-JYFZP3EZ.mjs → router-VOY43YNM.mjs} +7 -3
- package/dist/src/index.cjs +548 -232
- package/dist/src/index.mjs +549 -224
- package/dist/src/lib/client/chunk-TSXM2PNY.mjs +53 -0
- package/dist/src/lib/client/index.cjs +310 -176
- package/dist/src/lib/client/index.d.ts.map +1 -1
- package/dist/src/lib/client/index.js +33 -18
- package/dist/src/lib/client/index.mjs +86 -15
- package/dist/src/lib/client/{router-PHLUYVQU.mjs → router-BDVPLLYX.mjs} +244 -99
- package/dist/src/lib/context/context.cjs +35 -72
- package/dist/src/lib/context/context.d.ts +45 -291
- package/dist/src/lib/context/context.d.ts.map +1 -1
- package/dist/src/lib/context/context.js +65 -397
- package/dist/src/lib/context/context.mjs +38 -73
- package/dist/src/lib/context/index.cjs +35 -72
- package/dist/src/lib/context/index.d.ts +2 -2
- package/dist/src/lib/context/index.d.ts.map +1 -1
- package/dist/src/lib/context/index.js +1 -5
- package/dist/src/lib/context/index.mjs +38 -73
- package/dist/src/lib/core.cjs +547 -231
- package/dist/src/lib/core.d.ts +5 -3
- package/dist/src/lib/core.d.ts.map +1 -1
- package/dist/src/lib/core.js +6 -4
- package/dist/src/lib/core.mjs +548 -223
- package/dist/src/lib/env/index.cjs +1 -17
- package/dist/src/lib/env/index.d.ts +2 -14
- package/dist/src/lib/env/index.d.ts.map +1 -1
- package/dist/src/lib/env/index.js +2 -26
- package/dist/src/lib/env/index.mjs +1 -17
- package/dist/src/lib/i18n/index.cjs +17 -2
- package/dist/src/lib/i18n/index.d.ts +10 -0
- package/dist/src/lib/i18n/index.d.ts.map +1 -1
- package/dist/src/lib/i18n/index.js +17 -1
- package/dist/src/lib/i18n/index.mjs +17 -2
- package/dist/src/lib/index.cjs +64 -99
- package/dist/src/lib/index.mjs +43 -76
- package/dist/src/lib/isomorphic.cjs +9 -4
- package/dist/src/lib/isomorphic.d.ts +9 -2
- package/dist/src/lib/isomorphic.d.ts.map +1 -1
- package/dist/src/lib/isomorphic.js +11 -4
- package/dist/src/lib/isomorphic.mjs +9 -4
- package/dist/src/lib/jsx-runtime/index.cjs +1 -1
- package/dist/src/lib/jsx-runtime/index.mjs +1 -1
- package/dist/src/lib/jsx-runtime/jsx-dev-runtime.cjs +1 -1
- package/dist/src/lib/jsx-runtime/jsx-dev-runtime.mjs +1 -1
- package/dist/src/lib/plugins/fluixi-routes-plugin.cjs +2 -2
- package/dist/src/lib/plugins/fluixi-routes-plugin.mjs +2 -2
- package/dist/src/lib/plugins/index.cjs +3 -3
- package/dist/src/lib/plugins/index.mjs +3 -3
- package/dist/src/lib/plugins/route-codegen.cjs +2 -2
- package/dist/src/lib/plugins/route-codegen.js +1 -1
- package/dist/src/lib/plugins/route-codegen.mjs +2 -2
- package/dist/src/lib/render/index.cjs +1 -1
- package/dist/src/lib/render/index.mjs +1 -1
- package/dist/src/lib/render/versions.cjs +1 -1
- package/dist/src/lib/render/versions.mjs +1 -1
- package/dist/src/lib/router/data.cjs +113 -115
- package/dist/src/lib/router/data.d.ts +63 -10
- package/dist/src/lib/router/data.d.ts.map +1 -1
- package/dist/src/lib/router/data.js +158 -68
- package/dist/src/lib/router/data.mjs +112 -112
- package/dist/src/lib/router/index.cjs +260 -163
- package/dist/src/lib/router/index.d.ts +22 -2
- package/dist/src/lib/router/index.d.ts.map +1 -1
- package/dist/src/lib/router/index.js +165 -22
- package/dist/src/lib/router/index.mjs +244 -143
- package/dist/src/lib/server/index.cjs +1 -1
- package/dist/src/lib/server/index.mjs +1 -1
- package/dist/src/lib/storage/cache.cjs +51 -0
- package/dist/src/lib/storage/cache.d.ts +18 -0
- package/dist/src/lib/storage/cache.d.ts.map +1 -0
- package/dist/src/lib/storage/cache.js +42 -0
- package/dist/src/lib/storage/cache.mjs +30 -0
- package/dist/src/lib/storage/index.cjs +246 -0
- package/dist/src/lib/storage/index.d.ts +83 -0
- package/dist/src/lib/storage/index.d.ts.map +1 -0
- package/dist/src/lib/storage/index.js +179 -0
- package/dist/src/lib/storage/index.mjs +231 -0
- package/dist/src/lib/storage/persisted.cjs +91 -0
- package/dist/src/lib/storage/persisted.d.ts +53 -0
- package/dist/src/lib/storage/persisted.d.ts.map +1 -0
- package/dist/src/lib/storage/persisted.js +75 -0
- package/dist/src/lib/storage/persisted.mjs +70 -0
- package/dist/src/lib/theme/color-scheme.cjs +281 -0
- package/dist/src/lib/theme/color-scheme.d.ts +61 -0
- package/dist/src/lib/theme/color-scheme.d.ts.map +1 -0
- package/dist/src/lib/theme/color-scheme.js +120 -0
- package/dist/src/lib/theme/color-scheme.mjs +265 -0
- package/dist/src/lib/theme/index.cjs +194 -37
- package/dist/src/lib/theme/index.d.ts +23 -11
- package/dist/src/lib/theme/index.d.ts.map +1 -1
- package/dist/src/lib/theme/index.js +73 -32
- package/dist/src/lib/theme/index.mjs +197 -35
- package/dist/src/lib/theme/provider.cjs +346 -0
- package/dist/src/lib/theme/provider.d.ts +34 -0
- package/dist/src/lib/theme/provider.d.ts.map +1 -0
- package/dist/src/lib/theme/provider.js +60 -0
- package/dist/src/lib/theme/provider.mjs +332 -0
- package/dist/src/routes.cjs +1 -1
- package/dist/src/routes.mjs +1 -1
- package/dist/src/utils.cjs +1 -1
- package/dist/src/utils.mjs +1 -1
- package/dist/src/version.generated.cjs +1 -1
- package/dist/src/version.generated.d.ts +1 -1
- package/dist/src/version.generated.js +1 -1
- package/dist/src/version.generated.mjs +1 -1
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +18 -11
- package/dist/src/lib/client/chunk-VB567ABU.mjs +0 -133
|
@@ -6,6 +6,10 @@ interface RouterCtxValue {
|
|
|
6
6
|
router: CoreRouter<any>;
|
|
7
7
|
location: () => RouterLocation;
|
|
8
8
|
params: () => Record<string, string>;
|
|
9
|
+
/** True from a navigation's start until the route it lands on has its modules and data. */
|
|
10
|
+
isRouting: () => boolean;
|
|
11
|
+
/** Counts a layer's data load toward `isRouting` until the promise settles. */
|
|
12
|
+
trackLoad: <T>(result: T) => T;
|
|
9
13
|
}
|
|
10
14
|
export interface RouterProps<C = any> {
|
|
11
15
|
routes: RouteDefinition<C>[];
|
|
@@ -45,6 +49,22 @@ export interface RouterProps<C = any> {
|
|
|
45
49
|
export declare function Router<C = any>(props: RouterProps<C>): any;
|
|
46
50
|
/** Renders this layout's child route, inside this layout's context. */
|
|
47
51
|
export declare function Outlet(): any;
|
|
52
|
+
/**
|
|
53
|
+
* Whether a navigation is in progress: from the moment it starts until the route it lands on
|
|
54
|
+
* has loaded its modules and its route data. For a progress bar or a busy cursor.
|
|
55
|
+
*
|
|
56
|
+
* `delay` holds the `true` back until the navigation has lasted that many milliseconds, so a
|
|
57
|
+
* navigation that settles at once shows nothing. It still turns `false` the moment it ends.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```tsx
|
|
61
|
+
* const routing = useIsRouting({ delay: 120 });
|
|
62
|
+
* <div class="progress" data-active={() => (routing() ? '' : undefined)} />
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export declare function useIsRouting(options?: {
|
|
66
|
+
delay?: number;
|
|
67
|
+
}): () => boolean;
|
|
48
68
|
/** Navigate to `to` on mount (replace by default). Renders nothing. */
|
|
49
69
|
export declare function Redirect(props: {
|
|
50
70
|
to: string;
|
|
@@ -243,8 +263,8 @@ export declare function Form(props: FormProps): any;
|
|
|
243
263
|
export declare function preloadRoutes<C = any>(routes: RouteDefinition<C>[], pathname: string): Promise<void>;
|
|
244
264
|
export { createMemoryHistory, createBrowserHistory, matchRoutes, matchPath, } from '@fluixi/router';
|
|
245
265
|
export type { RouteDefinition, RouteMatch, MatchedRoute, RouterLocation, NavigateOptions, HistoryAdapter, } from '@fluixi/router';
|
|
246
|
-
export { cache, action, revalidate, createAsync, useSubmission, useAction, } from './data.js';
|
|
247
|
-
export type { RouteDataArgs, RouteLoadFunc, Action, Submission, CacheFn, } from './data.js';
|
|
266
|
+
export { cache, action, revalidate, createAsync, useSubmission, useSubmissions, useAction, } from './data.js';
|
|
267
|
+
export type { RouteDataArgs, RouteLoadFunc, Action, ActionOptions, CacheOptions, Submission, CacheFn, } from './data.js';
|
|
248
268
|
export { lazy } from '../render/lazy.js';
|
|
249
269
|
export { supportsViewTransitions, prefersReducedMotion, type TransitionMode, } from './transition.js';
|
|
250
270
|
export { runWithOwner } from '@fluixi/reactive/signal';
|
|
@@ -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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"}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* matched array, never by re-entering a still-building parent, so deeply nested
|
|
12
12
|
* <Outlet/> works at any depth.
|
|
13
13
|
*/
|
|
14
|
-
import { createSignal, createMemo, createResource, createEffect, getOwner, onCleanup, untrack, flush, } from '@fluixi/reactive/signal';
|
|
14
|
+
import { createSignal, createMemo, createResource, createEffect, createRoot, getOwner, onCleanup, runWithOwner, untrack, flush, } from '@fluixi/reactive/signal';
|
|
15
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
|
|
@@ -24,7 +24,6 @@ const RouterContext = createContext(undefined, 'router');
|
|
|
24
24
|
const OutletContext = createContext(undefined, 'outlet');
|
|
25
25
|
const LevelParamsContext = createContext(undefined, 'route-params');
|
|
26
26
|
const RouteDataContext = createContext(undefined, 'route-data');
|
|
27
|
-
// ─── Rendering ────────────────────────────────────────────────────────────────
|
|
28
27
|
/** Render one matched layer: load its data, stamp its contexts, run its component. */
|
|
29
28
|
function renderLayer(m, depth, base) {
|
|
30
29
|
// Load this layer's data once. createResource integrates with the nearest
|
|
@@ -38,8 +37,41 @@ function renderLayer(m, depth, base) {
|
|
|
38
37
|
cancelled = true;
|
|
39
38
|
});
|
|
40
39
|
const owner = getOwner();
|
|
41
|
-
const
|
|
42
|
-
|
|
40
|
+
const router = useContext(RouterContext);
|
|
41
|
+
// The route's `routeDeps`, once its module has loaded: a change loads the layer again.
|
|
42
|
+
let watching = false;
|
|
43
|
+
const trackDeps = (deps) => {
|
|
44
|
+
if (watching || cancelled)
|
|
45
|
+
return;
|
|
46
|
+
watching = true;
|
|
47
|
+
runWithOwner(owner, () => {
|
|
48
|
+
let first = true;
|
|
49
|
+
createEffect(() => {
|
|
50
|
+
deps();
|
|
51
|
+
if (first)
|
|
52
|
+
first = false;
|
|
53
|
+
else
|
|
54
|
+
untrack(() => void refetch());
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
const [resource, { refetch }] = createResource(() => {
|
|
59
|
+
const result = loader({ params: base.params, location: base.location }, owner, () => cancelled, trackDeps);
|
|
60
|
+
return router ? router.trackLoad(result) : result;
|
|
61
|
+
});
|
|
62
|
+
// A revalidated cache entry: load again. The page stays on screen with its current data.
|
|
63
|
+
onCleanup(onRevalidate(() => void refetch()));
|
|
64
|
+
// A failed load throws where the page reads it, so the nearest ErrorBoundary shows it.
|
|
65
|
+
// A resource reads as undefined once it has failed, which left the page empty and gave
|
|
66
|
+
// nothing a chance to catch the error.
|
|
67
|
+
data = () => {
|
|
68
|
+
const error = resource.error;
|
|
69
|
+
if (error !== undefined && error !== null)
|
|
70
|
+
throw error;
|
|
71
|
+
return resource();
|
|
72
|
+
};
|
|
73
|
+
Object.defineProperty(data, 'loading', { get: () => resource.loading });
|
|
74
|
+
Object.defineProperty(data, 'state', { get: () => resource.state });
|
|
43
75
|
}
|
|
44
76
|
const buildPage = () => RouteDataContext.Provider({
|
|
45
77
|
value: data,
|
|
@@ -58,18 +90,22 @@ function renderLayer(m, depth, base) {
|
|
|
58
90
|
},
|
|
59
91
|
});
|
|
60
92
|
if (!data)
|
|
61
|
-
return buildPage();
|
|
93
|
+
return { view: buildPage(), ready: () => true };
|
|
62
94
|
// Gate on the data being resolved, like the old router, so the page reads
|
|
63
95
|
// ready data (rd() returns the value, not a thrown promise) and works without a
|
|
64
96
|
// Suspense boundary. Signal-gated (`ready` only flips when loading actually
|
|
65
97
|
// changes) so navigation re-renders don't re-run this and rebuild the page.
|
|
66
98
|
const res = data;
|
|
67
|
-
|
|
99
|
+
// The first load only. A refetch after revalidate keeps the page, showing the old data until the new arrives.
|
|
100
|
+
const isLoading = () => {
|
|
101
|
+
const state = res.state;
|
|
102
|
+
return state === undefined ? res.loading : state === 'pending' || state === 'unresolved';
|
|
103
|
+
};
|
|
68
104
|
// untrack the initial read so the enclosing render memo doesn't subscribe to the
|
|
69
105
|
// resource state (that would re-run renderLayer and recreate the resource, a loop).
|
|
70
106
|
const [ready, setReady] = createSignal(untrack(() => !isLoading()));
|
|
71
107
|
createEffect(() => setReady(!isLoading()));
|
|
72
|
-
return createMemo(() => (ready() ? buildPage() : null));
|
|
108
|
+
return { view: createMemo(() => (ready() ? buildPage() : null)), ready };
|
|
73
109
|
}
|
|
74
110
|
/**
|
|
75
111
|
* Renders the next matched layer, reactively but keyed on its component ref so the
|
|
@@ -84,8 +120,8 @@ function renderChild(ctx) {
|
|
|
84
120
|
// every navigation and re-render the child. A signal only notifies when its value
|
|
85
121
|
// actually changes, so the render memo below re-runs only when the route at this
|
|
86
122
|
// depth changes: sibling navigation reuses the layout, deeper navigation leaves
|
|
87
|
-
// this level untouched.
|
|
88
|
-
// it stays connected to the Suspense / context owner chain.
|
|
123
|
+
// this level untouched. Each child renders in a root attached to this owner (below),
|
|
124
|
+
// so it stays connected to the Suspense / context owner chain.
|
|
89
125
|
const [childComp, setChildComp] = createSignal(untrack(() => ctx.matched()[childIdx]?.component));
|
|
90
126
|
createEffect(() => {
|
|
91
127
|
const c = ctx.matched()[childIdx]?.component;
|
|
@@ -95,20 +131,53 @@ function renderChild(ctx) {
|
|
|
95
131
|
if (untrack(childComp) !== c)
|
|
96
132
|
setChildComp(() => c);
|
|
97
133
|
});
|
|
98
|
-
|
|
134
|
+
// Each layer lives in its own root under this Outlet's owner: attached, so context and
|
|
135
|
+
// Suspense reach it, but not owned by the memo below, so a newer layer does not dispose it.
|
|
136
|
+
// That is what lets the page on screen stay there while the next one loads its data.
|
|
137
|
+
const owner = getOwner();
|
|
138
|
+
let shown = null;
|
|
139
|
+
let pending = null;
|
|
140
|
+
const drop = (layer) => layer?.dispose();
|
|
141
|
+
onCleanup(() => {
|
|
142
|
+
drop(shown);
|
|
143
|
+
drop(pending);
|
|
144
|
+
shown = pending = null;
|
|
145
|
+
});
|
|
146
|
+
const incoming = createMemo(() => {
|
|
99
147
|
const component = childComp();
|
|
100
|
-
if (!component)
|
|
148
|
+
if (!component)
|
|
149
|
+
return null;
|
|
150
|
+
const m = untrack(() => ctx.matched())[childIdx];
|
|
151
|
+
return runWithOwner(owner, () => createRoot((dispose) => ({
|
|
152
|
+
...renderLayer(m, childIdx, { matched: ctx.matched, params: ctx.params, location: ctx.location }),
|
|
153
|
+
dispose,
|
|
154
|
+
})));
|
|
155
|
+
});
|
|
156
|
+
return createMemo(() => {
|
|
157
|
+
const next = incoming();
|
|
158
|
+
if (!next) {
|
|
159
|
+
drop(shown);
|
|
160
|
+
drop(pending);
|
|
161
|
+
shown = pending = null;
|
|
101
162
|
// No route at this depth. At the root (the only ctx carrying `notFound`) that means the
|
|
102
163
|
// URL matched nothing: render the not-found fallback instead of a blank page. Nested
|
|
103
164
|
// Outlets have no `notFound`, so they stay null (a missing child is just an empty Outlet).
|
|
104
165
|
return ctx.notFound ? ctx.notFound() : null;
|
|
105
166
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
167
|
+
if (next === shown)
|
|
168
|
+
return shown.view;
|
|
169
|
+
// A newer navigation superseded the one still loading.
|
|
170
|
+
if (pending && pending !== next)
|
|
171
|
+
drop(pending);
|
|
172
|
+
pending = next;
|
|
173
|
+
// Keep the current page until the next one has its data. Nothing on screen yet (first
|
|
174
|
+
// render, hydration): show the next one straight away, its own gate handles loading.
|
|
175
|
+
if (shown && !next.ready())
|
|
176
|
+
return shown.view;
|
|
177
|
+
drop(shown);
|
|
178
|
+
shown = next;
|
|
179
|
+
pending = null;
|
|
180
|
+
return shown.view;
|
|
112
181
|
});
|
|
113
182
|
}
|
|
114
183
|
/**
|
|
@@ -166,6 +235,10 @@ export function Router(props) {
|
|
|
166
235
|
history,
|
|
167
236
|
});
|
|
168
237
|
const [state, setState] = createSignal(router.state());
|
|
238
|
+
// A navigation in flight, and the layer data loads it started: together they are
|
|
239
|
+
// `useIsRouting()`, what a progress bar reads.
|
|
240
|
+
const [navigating, setNavigating] = createSignal(false);
|
|
241
|
+
const [loads, setLoads] = createSignal(0);
|
|
169
242
|
/**
|
|
170
243
|
* Which navigation is allowed to commit.
|
|
171
244
|
*
|
|
@@ -194,6 +267,7 @@ export function Router(props) {
|
|
|
194
267
|
const unsub = router.subscribe((next) => {
|
|
195
268
|
const id = ++navigationId;
|
|
196
269
|
const mode = takeMode();
|
|
270
|
+
setNavigating(true);
|
|
197
271
|
// Load the modules the next route needs before anything is torn down, so a lazy
|
|
198
272
|
// route renders straight through instead of suspending into a fallback with the
|
|
199
273
|
// old page already gone.
|
|
@@ -201,8 +275,12 @@ export function Router(props) {
|
|
|
201
275
|
const commit = () => {
|
|
202
276
|
setState(next);
|
|
203
277
|
// 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
|
-
|
|
278
|
+
// browser snapshot a settled DOM rather than a half-built one. The layers built by
|
|
279
|
+
// the flush have registered their data loads, so `isRouting` stays true through them.
|
|
280
|
+
const flushed = flush();
|
|
281
|
+
if (id === navigationId)
|
|
282
|
+
setNavigating(false);
|
|
283
|
+
return flushed;
|
|
206
284
|
};
|
|
207
285
|
if (!prepared) {
|
|
208
286
|
// Nothing to wait for: stay on the tick this navigation was already on.
|
|
@@ -230,6 +308,16 @@ export function Router(props) {
|
|
|
230
308
|
router: router,
|
|
231
309
|
location: createMemo(() => state().location),
|
|
232
310
|
params: createMemo(() => state().match?.params ?? {}),
|
|
311
|
+
isRouting: () => navigating() || loads() > 0,
|
|
312
|
+
// Counted by the promise, not by an owner: a load a newer navigation superseded still
|
|
313
|
+
// settles and uncounts itself, and a resource seeded from the server never calls it.
|
|
314
|
+
trackLoad(result) {
|
|
315
|
+
if (result && typeof result.finally === 'function') {
|
|
316
|
+
setLoads((n) => n + 1);
|
|
317
|
+
void result.finally(() => setLoads((n) => n - 1)).catch(() => { });
|
|
318
|
+
}
|
|
319
|
+
return result;
|
|
320
|
+
},
|
|
233
321
|
};
|
|
234
322
|
// Render depth 0 (a virtual parent at depth -1 so the root reuses the same path).
|
|
235
323
|
const root = {
|
|
@@ -260,6 +348,35 @@ export function Outlet() {
|
|
|
260
348
|
return null;
|
|
261
349
|
return renderChild(ctx);
|
|
262
350
|
}
|
|
351
|
+
/**
|
|
352
|
+
* Whether a navigation is in progress: from the moment it starts until the route it lands on
|
|
353
|
+
* has loaded its modules and its route data. For a progress bar or a busy cursor.
|
|
354
|
+
*
|
|
355
|
+
* `delay` holds the `true` back until the navigation has lasted that many milliseconds, so a
|
|
356
|
+
* navigation that settles at once shows nothing. It still turns `false` the moment it ends.
|
|
357
|
+
*
|
|
358
|
+
* @example
|
|
359
|
+
* ```tsx
|
|
360
|
+
* const routing = useIsRouting({ delay: 120 });
|
|
361
|
+
* <div class="progress" data-active={() => (routing() ? '' : undefined)} />
|
|
362
|
+
* ```
|
|
363
|
+
*/
|
|
364
|
+
export function useIsRouting(options = {}) {
|
|
365
|
+
const routing = useContext(RouterContext)?.isRouting ?? (() => false);
|
|
366
|
+
const delay = options.delay ?? 0;
|
|
367
|
+
if (delay <= 0)
|
|
368
|
+
return routing;
|
|
369
|
+
const [late, setLate] = createSignal(false);
|
|
370
|
+
createEffect(() => {
|
|
371
|
+
if (!routing()) {
|
|
372
|
+
setLate(false);
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const timer = setTimeout(() => setLate(true), delay);
|
|
376
|
+
onCleanup(() => clearTimeout(timer));
|
|
377
|
+
});
|
|
378
|
+
return () => routing() && late();
|
|
379
|
+
}
|
|
263
380
|
/** Navigate to `to` on mount (replace by default). Renders nothing. */
|
|
264
381
|
export function Redirect(props) {
|
|
265
382
|
const ctx = useContext(RouterContext);
|
|
@@ -288,6 +405,8 @@ const _ssrRouterStub = {
|
|
|
288
405
|
router: { navigate: () => { } },
|
|
289
406
|
location: () => ({ pathname: '', search: '', hash: '', query: {} }),
|
|
290
407
|
params: () => ({}),
|
|
408
|
+
isRouting: () => false,
|
|
409
|
+
trackLoad: (result) => result,
|
|
291
410
|
};
|
|
292
411
|
/**
|
|
293
412
|
* The router context: the router itself, the current match, and the location.
|
|
@@ -523,7 +642,7 @@ const LINK_OWN_PROPS = new Set([
|
|
|
523
642
|
*/
|
|
524
643
|
export function Link(props) {
|
|
525
644
|
const ctx = useContext(RouterContext);
|
|
526
|
-
const navigate = () => ctx
|
|
645
|
+
const navigate = () => ctx.router.navigate(read(props.href) ?? '', { replace: read(props.replace) });
|
|
527
646
|
// createNativeElement branches on isServer() -> a real ServerNode <a> (serialized as a true
|
|
528
647
|
// element, not an escaped string) on the server, a real DOM <a> on the client.
|
|
529
648
|
const a = createNativeElement('a');
|
|
@@ -545,7 +664,26 @@ export function Link(props) {
|
|
|
545
664
|
if (LINK_OWN_PROPS.has(k))
|
|
546
665
|
continue;
|
|
547
666
|
const v = props[k];
|
|
548
|
-
if (
|
|
667
|
+
if (typeof v === 'function') {
|
|
668
|
+
// An accessor (`title={() => t('…')}`, `aria-current={() => …}`): followed on the client,
|
|
669
|
+
// read once for the server's markup. Event handlers are not attributes.
|
|
670
|
+
if (/^on[A-Z]/.test(k))
|
|
671
|
+
continue;
|
|
672
|
+
const read = v;
|
|
673
|
+
const apply = () => {
|
|
674
|
+
const value = read();
|
|
675
|
+
if (value == null || value === false)
|
|
676
|
+
a.removeAttribute?.(k);
|
|
677
|
+
else
|
|
678
|
+
setAttribute(a, k, String(value));
|
|
679
|
+
};
|
|
680
|
+
if (typeof a.addEventListener === 'function')
|
|
681
|
+
createEffect(apply);
|
|
682
|
+
else
|
|
683
|
+
apply();
|
|
684
|
+
continue;
|
|
685
|
+
}
|
|
686
|
+
if (v != null)
|
|
549
687
|
setAttribute(a, k, String(v));
|
|
550
688
|
}
|
|
551
689
|
// Client only (ServerNode has no addEventListener). Direct listener (not delegated) so
|
|
@@ -554,6 +692,10 @@ export function Link(props) {
|
|
|
554
692
|
a.addEventListener('click', (e) => {
|
|
555
693
|
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey)
|
|
556
694
|
return;
|
|
695
|
+
// No router above this link: let the browser follow it rather than cancel the click
|
|
696
|
+
// and go nowhere.
|
|
697
|
+
if (!ctx)
|
|
698
|
+
return;
|
|
557
699
|
e.preventDefault();
|
|
558
700
|
navigate();
|
|
559
701
|
});
|
|
@@ -584,6 +726,7 @@ export function Link(props) {
|
|
|
584
726
|
}
|
|
585
727
|
/** Alias for {@link Link}. */
|
|
586
728
|
export const A = Link;
|
|
729
|
+
import { onRevalidate } from './data.js';
|
|
587
730
|
/**
|
|
588
731
|
* A `<form>` wired to an action: on submit it builds `FormData` from the form and calls
|
|
589
732
|
* `action(formData)` (which tracks submission state via `useSubmission`), without a full
|
|
@@ -665,7 +808,7 @@ export async function preloadRoutes(routes, pathname) {
|
|
|
665
808
|
export { createMemoryHistory, createBrowserHistory, matchRoutes, matchPath, } from '@fluixi/router';
|
|
666
809
|
// Action/cache/lazy layer is orthogonal to routing: re-export so a route file can
|
|
667
810
|
// swap its import path wholesale. (Routing hooks above are the router ones.)
|
|
668
|
-
export { cache, action, revalidate, createAsync, useSubmission, useAction, } from './data.js';
|
|
811
|
+
export { cache, action, revalidate, createAsync, useSubmission, useSubmissions, useAction, } from './data.js';
|
|
669
812
|
export { lazy } from '../render/lazy.js';
|
|
670
813
|
// Navigation presentation. The router owns the integration; these are exported for
|
|
671
814
|
// apps that need to ask what the environment supports.
|