@ecopages/react-router 0.2.0-alpha.5 → 0.2.0-alpha.8
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 +9 -2
- package/README.md +46 -120
- package/package.json +3 -2
- package/src/head-morpher.js +37 -1
- package/src/head-morpher.ts +45 -1
- package/src/navigation.d.ts +21 -8
- package/src/navigation.js +59 -33
- package/src/navigation.ts +86 -36
- package/src/props-script.d.ts +1 -1
- package/src/props-script.ts +1 -1
- package/src/router.d.ts +3 -1
- package/src/router.js +295 -59
- package/src/router.ts +392 -70
package/src/router.js
CHANGED
|
@@ -11,11 +11,21 @@ import {
|
|
|
11
11
|
} from "react";
|
|
12
12
|
import { DEFAULT_OPTIONS } from "./types.js";
|
|
13
13
|
import { RouterContext } from "./context.js";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
fetchPageDocument,
|
|
16
|
+
getInterceptDecision,
|
|
17
|
+
loadPageModuleFromDocument,
|
|
18
|
+
shouldInterceptClick
|
|
19
|
+
} from "./navigation.js";
|
|
15
20
|
import { morphHead } from "./head-morpher.js";
|
|
16
21
|
import { applyViewTransitionNames } from "./view-transition-utils.js";
|
|
17
22
|
import { manageScroll } from "./manage-scroll.js";
|
|
18
23
|
import { saveScrollPositions, restoreScrollPositions } from "./scroll-persist.js";
|
|
24
|
+
import { getEcoNavigationRuntime } from "@ecopages/core/router/navigation-coordinator";
|
|
25
|
+
import {
|
|
26
|
+
getAnchorFromNavigationEvent,
|
|
27
|
+
recoverPendingNavigationHref
|
|
28
|
+
} from "@ecopages/core/router/link-intent";
|
|
19
29
|
const PageContext = createContext(null);
|
|
20
30
|
const PersistLayoutsContext = createContext(false);
|
|
21
31
|
function getLayoutFromPage(Page) {
|
|
@@ -57,6 +67,7 @@ const PageContent = () => {
|
|
|
57
67
|
const { Component: Page, props } = pageContext;
|
|
58
68
|
const Layout = getLayoutFromPage(Page);
|
|
59
69
|
const pageElement = createElement(Page, props);
|
|
70
|
+
const layoutProps = props?.locals ? { locals: props.locals } : null;
|
|
60
71
|
if (!Layout) {
|
|
61
72
|
return pageElement;
|
|
62
73
|
}
|
|
@@ -69,9 +80,9 @@ const PageContent = () => {
|
|
|
69
80
|
layoutCache.set(layoutKey, Layout);
|
|
70
81
|
}
|
|
71
82
|
const CachedLayout = layoutCache.get(layoutKey);
|
|
72
|
-
return createElement(CachedLayout, { key: layoutKey }, pageElement);
|
|
83
|
+
return createElement(CachedLayout, { key: layoutKey, ...layoutProps ?? {} }, pageElement);
|
|
73
84
|
}
|
|
74
|
-
return createElement(Layout,
|
|
85
|
+
return createElement(Layout, layoutProps, pageElement);
|
|
75
86
|
};
|
|
76
87
|
function createDeferred() {
|
|
77
88
|
let resolve;
|
|
@@ -80,31 +91,65 @@ function createDeferred() {
|
|
|
80
91
|
});
|
|
81
92
|
return { promise, resolve };
|
|
82
93
|
}
|
|
83
|
-
function
|
|
94
|
+
function useNavigationCoordinator(navigate, activeNavigationRef, isNavigatingRef, runtimeActiveRef) {
|
|
84
95
|
useEffect(() => {
|
|
85
96
|
if (typeof window === "undefined") return;
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
97
|
+
const navigationRuntime = getEcoNavigationRuntime(window);
|
|
98
|
+
let unregisterRuntime = null;
|
|
99
|
+
const unregister = navigationRuntime.register({
|
|
100
|
+
owner: "react-router",
|
|
101
|
+
navigate: async (request) => {
|
|
102
|
+
await navigate(request.href, {
|
|
103
|
+
isPopState: request.direction === "back",
|
|
104
|
+
pushHistory: request.direction === "forward",
|
|
105
|
+
skipViewTransition: request.source === "browser-router"
|
|
106
|
+
});
|
|
107
|
+
return true;
|
|
108
|
+
},
|
|
109
|
+
reloadCurrentPage: async (request) => {
|
|
110
|
+
if (activeNavigationRef.current || isNavigatingRef.current) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (request?.clearCache) {
|
|
114
|
+
clearLayoutCache();
|
|
115
|
+
}
|
|
116
|
+
const currentUrl = window.location.pathname + window.location.search;
|
|
117
|
+
await navigate(currentUrl);
|
|
118
|
+
},
|
|
119
|
+
cleanupBeforeHandoff: async () => {
|
|
120
|
+
runtimeActiveRef.current = false;
|
|
121
|
+
unregisterRuntime?.();
|
|
122
|
+
unregisterRuntime = null;
|
|
123
|
+
window.__ECO_PAGES__?.react?.cleanupPageRoot?.();
|
|
91
124
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
125
|
+
});
|
|
126
|
+
unregisterRuntime = unregister;
|
|
127
|
+
navigationRuntime.claimOwnership("react-router");
|
|
128
|
+
runtimeActiveRef.current = true;
|
|
95
129
|
return () => {
|
|
96
|
-
|
|
130
|
+
runtimeActiveRef.current = false;
|
|
131
|
+
navigationRuntime.releaseOwnership("react-router");
|
|
132
|
+
unregisterRuntime?.();
|
|
133
|
+
unregisterRuntime = null;
|
|
97
134
|
};
|
|
98
|
-
}, [navigate]);
|
|
135
|
+
}, [activeNavigationRef, isNavigatingRef, navigate, runtimeActiveRef]);
|
|
99
136
|
}
|
|
100
137
|
const EcoRouter = ({ page, pageProps, options: userOptions, children }) => {
|
|
101
138
|
const options = useMemo(() => ({ ...DEFAULT_OPTIONS, ...userOptions }), [userOptions]);
|
|
102
139
|
const [currentPage, setCurrentPage] = useState({ Component: page, props: pageProps });
|
|
103
140
|
const [isNavigating, setIsNavigating] = useState(false);
|
|
104
|
-
const
|
|
105
|
-
const
|
|
141
|
+
const pendingRenderRef = useRef(null);
|
|
142
|
+
const activeNavigationRef = useRef(null);
|
|
143
|
+
const isNavigatingRef = useRef(false);
|
|
144
|
+
const runtimeActiveRef = useRef(true);
|
|
145
|
+
const pendingPointerNavigationRef = useRef(null);
|
|
146
|
+
const pendingHoverNavigationRef = useRef(null);
|
|
147
|
+
const queuedNavigationHrefRef = useRef(null);
|
|
106
148
|
const pendingScrollRestoreRef = useRef(null);
|
|
107
149
|
const previousUrlRef = useRef(typeof window !== "undefined" ? window.location.href : "");
|
|
150
|
+
useEffect(() => {
|
|
151
|
+
isNavigatingRef.current = isNavigating;
|
|
152
|
+
}, [isNavigating]);
|
|
108
153
|
useEffect(() => {
|
|
109
154
|
setCurrentPage({ Component: page, props: pageProps });
|
|
110
155
|
}, [page, pageProps]);
|
|
@@ -112,12 +157,20 @@ const EcoRouter = ({ page, pageProps, options: userOptions, children }) => {
|
|
|
112
157
|
applyViewTransitionNames();
|
|
113
158
|
}, [currentPage]);
|
|
114
159
|
useEffect(() => {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
160
|
+
const pendingRender = pendingRenderRef.current;
|
|
161
|
+
if (pendingRender && currentPage.Component === pendingRender.page.Component && currentPage.props === pendingRender.page.props) {
|
|
162
|
+
pendingRender.resolve();
|
|
163
|
+
pendingRenderRef.current = null;
|
|
119
164
|
}
|
|
120
|
-
}, [currentPage
|
|
165
|
+
}, [currentPage]);
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
return () => {
|
|
168
|
+
activeNavigationRef.current?.cancel();
|
|
169
|
+
pendingRenderRef.current?.resolve();
|
|
170
|
+
pendingRenderRef.current = null;
|
|
171
|
+
queuedNavigationHrefRef.current = null;
|
|
172
|
+
};
|
|
173
|
+
}, []);
|
|
121
174
|
useEffect(() => {
|
|
122
175
|
if (typeof window === "undefined") return;
|
|
123
176
|
const url = new URL(window.location.href);
|
|
@@ -136,49 +189,215 @@ const EcoRouter = ({ page, pageProps, options: userOptions, children }) => {
|
|
|
136
189
|
}
|
|
137
190
|
}, [currentPage, options.scrollBehavior, options.smoothScroll]);
|
|
138
191
|
const navigate = useCallback(
|
|
139
|
-
async (url,
|
|
140
|
-
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
192
|
+
async (url, navigationOptions = {}) => {
|
|
193
|
+
const { isPopState = false, pushHistory = false, skipViewTransition = false } = navigationOptions;
|
|
194
|
+
const navigationRuntime = getEcoNavigationRuntime(window);
|
|
195
|
+
const navigation = navigationRuntime.beginNavigationTransaction();
|
|
196
|
+
activeNavigationRef.current = navigation;
|
|
197
|
+
const navigationId = navigation.id;
|
|
198
|
+
const isStale = () => !navigation.isCurrent();
|
|
199
|
+
const commitPageData = (moduleUrl, props) => {
|
|
200
|
+
window.__ECO_PAGES__ = window.__ECO_PAGES__ || {};
|
|
201
|
+
window.__ECO_PAGES__.page = {
|
|
202
|
+
module: moduleUrl,
|
|
203
|
+
props
|
|
204
|
+
};
|
|
205
|
+
};
|
|
206
|
+
let navigationCommitPromise = null;
|
|
207
|
+
try {
|
|
208
|
+
setIsNavigating(true);
|
|
209
|
+
const fetchedPage = await fetchPageDocument(url, { signal: navigation.signal });
|
|
210
|
+
if (isStale()) return;
|
|
211
|
+
if (!fetchedPage) {
|
|
212
|
+
window.location.href = url;
|
|
213
|
+
return;
|
|
149
214
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if (
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
215
|
+
const result = await loadPageModuleFromDocument(fetchedPage.doc, fetchedPage.finalPath);
|
|
216
|
+
if (isStale()) return;
|
|
217
|
+
if (result) {
|
|
218
|
+
const { Component, props, doc, finalPath, moduleUrl } = result;
|
|
219
|
+
const nextPage = { Component, props };
|
|
220
|
+
const cleanupHead = await morphHead(doc);
|
|
221
|
+
if (isStale()) {
|
|
222
|
+
cleanupHead();
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
applyViewTransitionNames();
|
|
226
|
+
if (pushHistory) {
|
|
227
|
+
window.history.pushState(null, "", finalPath);
|
|
228
|
+
} else if (finalPath !== url) {
|
|
229
|
+
window.history.replaceState(null, "", finalPath);
|
|
230
|
+
}
|
|
231
|
+
saveScrollPositions();
|
|
232
|
+
pendingScrollRestoreRef.current = { url, isPopState };
|
|
233
|
+
if (!skipViewTransition && options.viewTransitions && document.startViewTransition) {
|
|
234
|
+
pendingRenderRef.current?.resolve();
|
|
235
|
+
const renderDfd = createDeferred();
|
|
236
|
+
pendingRenderRef.current = {
|
|
237
|
+
navigationId,
|
|
238
|
+
page: nextPage,
|
|
239
|
+
resolve: renderDfd.resolve
|
|
240
|
+
};
|
|
241
|
+
navigationCommitPromise = new Promise((resolve) => {
|
|
242
|
+
document.startViewTransition(async () => {
|
|
243
|
+
try {
|
|
244
|
+
if (isStale()) {
|
|
245
|
+
if (pendingRenderRef.current?.navigationId === navigationId) {
|
|
246
|
+
pendingRenderRef.current.resolve();
|
|
247
|
+
pendingRenderRef.current = null;
|
|
248
|
+
}
|
|
249
|
+
cleanupHead();
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
startTransition(() => {
|
|
253
|
+
commitPageData(moduleUrl, props);
|
|
254
|
+
setCurrentPage(nextPage);
|
|
255
|
+
});
|
|
256
|
+
await renderDfd.promise;
|
|
257
|
+
if (isStale()) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
cleanupHead();
|
|
261
|
+
applyViewTransitionNames();
|
|
262
|
+
} finally {
|
|
263
|
+
resolve();
|
|
264
|
+
}
|
|
265
|
+
});
|
|
158
266
|
});
|
|
159
|
-
await
|
|
267
|
+
await navigationCommitPromise;
|
|
268
|
+
} else {
|
|
269
|
+
commitPageData(moduleUrl, props);
|
|
270
|
+
setCurrentPage(nextPage);
|
|
160
271
|
cleanupHead();
|
|
161
272
|
applyViewTransitionNames();
|
|
162
|
-
}
|
|
273
|
+
}
|
|
163
274
|
} else {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
275
|
+
if (isStale()) return;
|
|
276
|
+
const handled = await navigationRuntime.requestHandoff({
|
|
277
|
+
href: url,
|
|
278
|
+
finalHref: fetchedPage.finalPath,
|
|
279
|
+
direction: isPopState ? "back" : pushHistory ? "forward" : "replace",
|
|
280
|
+
source: "react-router",
|
|
281
|
+
targetOwner: "browser-router",
|
|
282
|
+
document: fetchedPage.doc,
|
|
283
|
+
html: fetchedPage.html,
|
|
284
|
+
isStaleSourceNavigation: isStale
|
|
285
|
+
});
|
|
286
|
+
if (!handled) {
|
|
287
|
+
window.location.assign(fetchedPage.finalPath);
|
|
288
|
+
}
|
|
167
289
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
290
|
+
if (!isStale()) {
|
|
291
|
+
setIsNavigating(false);
|
|
292
|
+
}
|
|
293
|
+
} finally {
|
|
294
|
+
const shouldReplayQueuedNavigation = activeNavigationRef.current?.id === navigationId;
|
|
295
|
+
const queuedNavigationHref = shouldReplayQueuedNavigation ? queuedNavigationHrefRef.current : null;
|
|
296
|
+
navigation.complete();
|
|
297
|
+
if (activeNavigationRef.current?.id === navigationId) {
|
|
298
|
+
activeNavigationRef.current = null;
|
|
299
|
+
}
|
|
300
|
+
if (queuedNavigationHref && queuedNavigationHref !== window.location.pathname + window.location.search) {
|
|
301
|
+
queuedNavigationHrefRef.current = null;
|
|
302
|
+
if (runtimeActiveRef.current) {
|
|
303
|
+
void navigate(queuedNavigationHref, { pushHistory: true });
|
|
304
|
+
} else {
|
|
305
|
+
void navigationRuntime.requestNavigation({
|
|
306
|
+
href: queuedNavigationHref,
|
|
307
|
+
direction: "forward",
|
|
308
|
+
source: "react-router"
|
|
309
|
+
}).then((handled) => {
|
|
310
|
+
if (!handled) {
|
|
311
|
+
window.location.assign(queuedNavigationHref);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
}
|
|
171
315
|
}
|
|
172
|
-
window.location.href = url;
|
|
173
316
|
}
|
|
174
|
-
setIsNavigating(false);
|
|
175
317
|
},
|
|
176
|
-
[options.viewTransitions
|
|
318
|
+
[options.viewTransitions]
|
|
177
319
|
);
|
|
178
320
|
useEffect(() => {
|
|
321
|
+
const getLinkFromEvent = (event) => getAnchorFromNavigationEvent(event, options.linkSelector);
|
|
322
|
+
const getRecoveredPointerHref = () => {
|
|
323
|
+
const href = recoverPendingNavigationHref(
|
|
324
|
+
pendingPointerNavigationRef.current,
|
|
325
|
+
!!activeNavigationRef.current || isNavigatingRef.current,
|
|
326
|
+
performance.now()
|
|
327
|
+
);
|
|
328
|
+
if (!href) {
|
|
329
|
+
pendingPointerNavigationRef.current = null;
|
|
330
|
+
}
|
|
331
|
+
return href;
|
|
332
|
+
};
|
|
333
|
+
const getRecoveredHoverHref = () => {
|
|
334
|
+
const href = recoverPendingNavigationHref(
|
|
335
|
+
pendingHoverNavigationRef.current,
|
|
336
|
+
!!activeNavigationRef.current || isNavigatingRef.current,
|
|
337
|
+
performance.now()
|
|
338
|
+
);
|
|
339
|
+
if (!href) {
|
|
340
|
+
pendingHoverNavigationRef.current = null;
|
|
341
|
+
}
|
|
342
|
+
return href;
|
|
343
|
+
};
|
|
344
|
+
const handleHoverIntent = (event) => {
|
|
345
|
+
if (!runtimeActiveRef.current) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
const link = getLinkFromEvent(event);
|
|
349
|
+
if (!link) {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
const decision = getInterceptDecision(event, link, options);
|
|
353
|
+
if (!decision.shouldIntercept) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
pendingHoverNavigationRef.current = {
|
|
357
|
+
href: link.getAttribute("href"),
|
|
358
|
+
timestamp: performance.now()
|
|
359
|
+
};
|
|
360
|
+
queuedNavigationHrefRef.current = link.getAttribute("href");
|
|
361
|
+
};
|
|
362
|
+
const handlePointerDown = (event) => {
|
|
363
|
+
if (!runtimeActiveRef.current) {
|
|
364
|
+
pendingPointerNavigationRef.current = null;
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
const link = getLinkFromEvent(event);
|
|
368
|
+
if (!link) {
|
|
369
|
+
pendingPointerNavigationRef.current = null;
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
const decision = getInterceptDecision(event, link, options);
|
|
373
|
+
pendingPointerNavigationRef.current = decision.shouldIntercept ? {
|
|
374
|
+
href: link.getAttribute("href"),
|
|
375
|
+
timestamp: performance.now()
|
|
376
|
+
} : null;
|
|
377
|
+
if (decision.shouldIntercept && (activeNavigationRef.current || isNavigatingRef.current)) {
|
|
378
|
+
queuedNavigationHrefRef.current = link.getAttribute("href");
|
|
379
|
+
}
|
|
380
|
+
};
|
|
179
381
|
const handleClick = (event) => {
|
|
180
|
-
|
|
181
|
-
|
|
382
|
+
if (!runtimeActiveRef.current) {
|
|
383
|
+
pendingPointerNavigationRef.current = null;
|
|
384
|
+
pendingHoverNavigationRef.current = null;
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
const link = getLinkFromEvent(event);
|
|
388
|
+
if (!link) {
|
|
389
|
+
const recoveredHref = getRecoveredPointerHref() ?? getRecoveredHoverHref();
|
|
390
|
+
pendingPointerNavigationRef.current = null;
|
|
391
|
+
pendingHoverNavigationRef.current = null;
|
|
392
|
+
if (!recoveredHref) {
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
event.preventDefault();
|
|
396
|
+
queuedNavigationHrefRef.current = null;
|
|
397
|
+
const recoveredUrl = new URL(recoveredHref, window.location.origin);
|
|
398
|
+
navigate(recoveredUrl.pathname + recoveredUrl.search, { pushHistory: true });
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
182
401
|
if (!shouldInterceptClick(event, link, options)) {
|
|
183
402
|
if (options.debug) {
|
|
184
403
|
const decision = getInterceptDecision(event, link, options);
|
|
@@ -186,28 +405,45 @@ const EcoRouter = ({ page, pageProps, options: userOptions, children }) => {
|
|
|
186
405
|
console.debug("[EcoRouter] Not intercepting link click:", decision.reason, link.href);
|
|
187
406
|
}
|
|
188
407
|
}
|
|
408
|
+
pendingPointerNavigationRef.current = null;
|
|
409
|
+
pendingHoverNavigationRef.current = null;
|
|
189
410
|
return;
|
|
190
411
|
}
|
|
412
|
+
pendingPointerNavigationRef.current = null;
|
|
413
|
+
pendingHoverNavigationRef.current = null;
|
|
191
414
|
event.preventDefault();
|
|
415
|
+
queuedNavigationHrefRef.current = null;
|
|
192
416
|
const href = link.getAttribute("href");
|
|
193
417
|
const url = new URL(href, window.location.origin);
|
|
194
418
|
if (options.debug) {
|
|
195
419
|
console.debug("[EcoRouter] Intercepting navigation:", url.pathname + url.search);
|
|
196
420
|
}
|
|
197
|
-
|
|
198
|
-
navigate(url.pathname + url.search);
|
|
421
|
+
navigate(url.pathname + url.search, { pushHistory: true });
|
|
199
422
|
};
|
|
200
423
|
const handlePopState = () => {
|
|
201
|
-
|
|
424
|
+
if (!runtimeActiveRef.current) {
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
navigate(window.location.pathname + window.location.search, { isPopState: true });
|
|
202
428
|
};
|
|
203
|
-
document.addEventListener("
|
|
429
|
+
document.addEventListener("mouseover", handleHoverIntent, true);
|
|
430
|
+
document.addEventListener("pointerover", handleHoverIntent, true);
|
|
431
|
+
document.addEventListener("mousemove", handleHoverIntent, true);
|
|
432
|
+
document.addEventListener("pointermove", handleHoverIntent, true);
|
|
433
|
+
document.addEventListener("pointerdown", handlePointerDown, true);
|
|
434
|
+
document.addEventListener("click", handleClick, true);
|
|
204
435
|
window.addEventListener("popstate", handlePopState);
|
|
205
436
|
return () => {
|
|
206
|
-
document.removeEventListener("
|
|
437
|
+
document.removeEventListener("mouseover", handleHoverIntent, true);
|
|
438
|
+
document.removeEventListener("pointerover", handleHoverIntent, true);
|
|
439
|
+
document.removeEventListener("mousemove", handleHoverIntent, true);
|
|
440
|
+
document.removeEventListener("pointermove", handleHoverIntent, true);
|
|
441
|
+
document.removeEventListener("pointerdown", handlePointerDown, true);
|
|
442
|
+
document.removeEventListener("click", handleClick, true);
|
|
207
443
|
window.removeEventListener("popstate", handlePopState);
|
|
208
444
|
};
|
|
209
|
-
}, [navigate, options]);
|
|
210
|
-
|
|
445
|
+
}, [navigate, options, runtimeActiveRef]);
|
|
446
|
+
useNavigationCoordinator(navigate, activeNavigationRef, isNavigatingRef, runtimeActiveRef);
|
|
211
447
|
return createElement(
|
|
212
448
|
RouterContext.Provider,
|
|
213
449
|
{ value: { navigate, isNavigating } },
|