@octanejs/remix-router 0.1.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/LICENSE +21 -0
- package/README.md +90 -0
- package/package.json +53 -0
- package/src/dom.ts +16 -0
- package/src/index.ts +267 -0
- package/src/internal.ts +37 -0
- package/src/lib/Await.tsrx +145 -0
- package/src/lib/Await.tsrx.d.ts +6 -0
- package/src/lib/DefaultErrorComponent.tsrx +47 -0
- package/src/lib/DefaultErrorComponent.tsrx.d.ts +2 -0
- package/src/lib/RenderErrorBoundary.tsrx +117 -0
- package/src/lib/RenderErrorBoundary.tsrx.d.ts +14 -0
- package/src/lib/actions.ts +90 -0
- package/src/lib/components/MemoryRouter.tsrx +42 -0
- package/src/lib/components/MemoryRouter.tsrx.d.ts +10 -0
- package/src/lib/components/Navigate.ts +60 -0
- package/src/lib/components/Router.tsrx +88 -0
- package/src/lib/components/Router.tsrx.d.ts +13 -0
- package/src/lib/components/RouterProvider.tsrx +320 -0
- package/src/lib/components/RouterProvider.tsrx.d.ts +21 -0
- package/src/lib/components/Routes.tsrx +53 -0
- package/src/lib/components/Routes.tsrx.d.ts +7 -0
- package/src/lib/components/routes-collector.ts +317 -0
- package/src/lib/components/utils.ts +171 -0
- package/src/lib/components/with-props.ts +72 -0
- package/src/lib/context.ts +129 -0
- package/src/lib/dom/Form.tsrx +76 -0
- package/src/lib/dom/Form.tsrx.d.ts +22 -0
- package/src/lib/dom/Link.tsrx +91 -0
- package/src/lib/dom/Link.tsrx.d.ts +22 -0
- package/src/lib/dom/NavLink.tsrx +118 -0
- package/src/lib/dom/NavLink.tsrx.d.ts +33 -0
- package/src/lib/dom/dom.ts +344 -0
- package/src/lib/dom/hooks.ts +93 -0
- package/src/lib/dom/lib.ts +822 -0
- package/src/lib/dom/routers.tsrx +104 -0
- package/src/lib/dom/routers.tsrx.d.ts +23 -0
- package/src/lib/dom/server.tsrx +350 -0
- package/src/lib/dom/server.tsrx.d.ts +25 -0
- package/src/lib/errors.ts +84 -0
- package/src/lib/framework-stubs.ts +103 -0
- package/src/lib/hooks.ts +1797 -0
- package/src/lib/href.ts +71 -0
- package/src/lib/react-types.ts +10 -0
- package/src/lib/router/history.ts +763 -0
- package/src/lib/router/instrumentation.ts +496 -0
- package/src/lib/router/links.ts +192 -0
- package/src/lib/router/router.ts +7165 -0
- package/src/lib/router/server-runtime-types.ts +12 -0
- package/src/lib/router/url.ts +8 -0
- package/src/lib/router/utils.ts +2179 -0
- package/src/lib/server-runtime/cookies.ts +240 -0
- package/src/lib/server-runtime/crypto.ts +55 -0
- package/src/lib/server-runtime/mode.ts +16 -0
- package/src/lib/server-runtime/sessions/cookieStorage.ts +54 -0
- package/src/lib/server-runtime/sessions/memoryStorage.ts +59 -0
- package/src/lib/server-runtime/sessions.ts +291 -0
- package/src/lib/server-runtime/warnings.ts +10 -0
- package/src/lib/types/future.ts +16 -0
- package/src/lib/types/params.ts +8 -0
- package/src/lib/types/register.ts +39 -0
- package/src/lib/types/route-module.ts +17 -0
- package/src/lib/types/utils.ts +37 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// DefaultErrorComponent — port of react-router@7.18.1 lib/hooks.tsx's default
|
|
2
|
+
// error UI. Markup-identical (differential fixtures still avoid it — the dev
|
|
3
|
+
// stack trace can never byte-match across runtimes).
|
|
4
|
+
import { useContext } from 'octane';
|
|
5
|
+
import { RouteErrorContext, ENABLE_DEV_WARNINGS } from './context.ts';
|
|
6
|
+
import { isRouteErrorResponse } from './router/utils.ts';
|
|
7
|
+
|
|
8
|
+
export function DefaultErrorComponent() @{
|
|
9
|
+
const error = useContext(RouteErrorContext);
|
|
10
|
+
const message =
|
|
11
|
+
isRouteErrorResponse(error)
|
|
12
|
+
? error.status + ' ' + error.statusText
|
|
13
|
+
: error instanceof Error
|
|
14
|
+
? error.message
|
|
15
|
+
: JSON.stringify(error);
|
|
16
|
+
const stack =
|
|
17
|
+
error instanceof Error ? error.stack : null;
|
|
18
|
+
const lightgrey = 'rgba(200,200,200, 0.5)';
|
|
19
|
+
|
|
20
|
+
if (ENABLE_DEV_WARNINGS) {
|
|
21
|
+
console.error('Error handled by React Router default ErrorBoundary:', error);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
<>
|
|
25
|
+
<h2>Unexpected Application Error!</h2>
|
|
26
|
+
<h3 style={{ fontStyle: 'italic' }}>
|
|
27
|
+
{message as string}
|
|
28
|
+
</h3>
|
|
29
|
+
@if (stack) {
|
|
30
|
+
<pre style={{ padding: '0.5rem', backgroundColor: lightgrey }}>
|
|
31
|
+
{stack as string}
|
|
32
|
+
</pre>
|
|
33
|
+
}
|
|
34
|
+
@if (ENABLE_DEV_WARNINGS) {
|
|
35
|
+
<>
|
|
36
|
+
<p>💿 Hey developer 👋</p>
|
|
37
|
+
<p>
|
|
38
|
+
{'You can provide a way better UX than this when your app throws errors by providing your own '}
|
|
39
|
+
<code style={{ padding: '2px 4px', backgroundColor: lightgrey }}>ErrorBoundary</code>
|
|
40
|
+
{' or '}
|
|
41
|
+
<code style={{ padding: '2px 4px', backgroundColor: lightgrey }}>errorElement</code>
|
|
42
|
+
{' prop on your route.'}
|
|
43
|
+
</p>
|
|
44
|
+
</>
|
|
45
|
+
}
|
|
46
|
+
</>
|
|
47
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// RenderErrorBoundary — port of the class boundary in react-router@7.18.1
|
|
2
|
+
// lib/hooks.tsx onto octane's @try/@catch (octane has no class components).
|
|
3
|
+
// RSC digest branches are dropped (RSCRouterContext is hardwired false here).
|
|
4
|
+
//
|
|
5
|
+
// Upstream semantics reproduced:
|
|
6
|
+
// - getDerivedStateFromProps: the retained error resets when the location
|
|
7
|
+
// changes or a revalidation returns to "idle"; otherwise a NEW props.error
|
|
8
|
+
// surfaces while a stale one is retained even if app state cleared it.
|
|
9
|
+
// - getDerivedStateFromError: render errors thrown below become the error.
|
|
10
|
+
// - componentDidCatch → onError (or the upstream console.error) once per
|
|
11
|
+
// distinct caught error, DURING the catch render — so a rethrow escapes to
|
|
12
|
+
// the parent boundary.
|
|
13
|
+
// - Render: error !== undefined → RouteContext(routeContext) →
|
|
14
|
+
// RouteErrorContext(error) → component; else children.
|
|
15
|
+
//
|
|
16
|
+
// Octane mapping: DATA errors (props.error, from router state.errors) never
|
|
17
|
+
// throw — the error branch renders directly. RENDER errors land in @catch;
|
|
18
|
+
// the catch view applies the same precedence (props.error wins) and leaves
|
|
19
|
+
// the catch state via reset() one commit after the location/revalidation
|
|
20
|
+
// derivation says the error cleared (documented divergence: upstream clears
|
|
21
|
+
// in a render-phase derivation; same observable outcome).
|
|
22
|
+
import { useRef, useLayoutEffect } from 'octane';
|
|
23
|
+
import { RouteContext, RouteErrorContext } from './context.ts';
|
|
24
|
+
|
|
25
|
+
export function RenderErrorBoundary(props) @{
|
|
26
|
+
// getDerivedStateFromProps, run during render (pure derivation).
|
|
27
|
+
const state = useRef({
|
|
28
|
+
location: props.location,
|
|
29
|
+
revalidation: props.revalidation,
|
|
30
|
+
error: props.error,
|
|
31
|
+
});
|
|
32
|
+
{
|
|
33
|
+
const s = state.current;
|
|
34
|
+
if (
|
|
35
|
+
s.location !== props.location || s.revalidation !== 'idle' && props.revalidation === 'idle'
|
|
36
|
+
) {
|
|
37
|
+
state.current = {
|
|
38
|
+
error: props.error,
|
|
39
|
+
location: props.location,
|
|
40
|
+
revalidation: props.revalidation,
|
|
41
|
+
};
|
|
42
|
+
} else {
|
|
43
|
+
state.current = {
|
|
44
|
+
error: props.error !== undefined ? props.error : s.error,
|
|
45
|
+
location: s.location,
|
|
46
|
+
revalidation: props.revalidation || s.revalidation,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@try {
|
|
52
|
+
@if (state.current.error !== undefined) {
|
|
53
|
+
<RouteContext.Provider value={props.routeContext}>
|
|
54
|
+
<RouteErrorContext.Provider
|
|
55
|
+
value={state.current.error}
|
|
56
|
+
>{props.component}</RouteErrorContext.Provider>
|
|
57
|
+
</RouteContext.Provider>
|
|
58
|
+
} @else {
|
|
59
|
+
<>
|
|
60
|
+
{props.children}
|
|
61
|
+
</>
|
|
62
|
+
}
|
|
63
|
+
} @catch (caught, reset) {
|
|
64
|
+
<CaughtErrorView
|
|
65
|
+
caught={caught}
|
|
66
|
+
reset={reset}
|
|
67
|
+
error={props.error}
|
|
68
|
+
location={props.location}
|
|
69
|
+
revalidation={props.revalidation}
|
|
70
|
+
routeContext={props.routeContext}
|
|
71
|
+
component={props.component}
|
|
72
|
+
onError={props.onError}
|
|
73
|
+
/>
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function CaughtErrorView(props) @{
|
|
78
|
+
// componentDidCatch parity — once per distinct caught error, during render
|
|
79
|
+
// so a rethrowing onError escapes to the parent boundary.
|
|
80
|
+
const reported = useRef(null);
|
|
81
|
+
if (reported.current !== props.caught) {
|
|
82
|
+
reported.current = props.caught;
|
|
83
|
+
if (props.onError) {
|
|
84
|
+
props.onError(props.caught, { componentStack: '' });
|
|
85
|
+
} else {
|
|
86
|
+
console.error('React Router caught the following error during render', props.caught);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Error precedence per upstream derivation: a defined props.error (data
|
|
91
|
+
// error for this location) wins over the caught render error.
|
|
92
|
+
const error =
|
|
93
|
+
props.error !== undefined ? props.error : props.caught;
|
|
94
|
+
|
|
95
|
+
// Leave the catch state when the location changes or a revalidation
|
|
96
|
+
// completes — the octane analogue of upstream's render-phase reset.
|
|
97
|
+
// `location` stays pinned at the catch baseline (upstream's state.location
|
|
98
|
+
// only moves on reset), but `revalidation` must TRACK every render the way
|
|
99
|
+
// upstream's derived state does (`props.revalidation || state.revalidation`)
|
|
100
|
+
// — a render error is usually caught while revalidation is already "idle",
|
|
101
|
+
// and the recovery signal is the SUBSEQUENT loading → idle transition, not
|
|
102
|
+
// a transition relative to the frozen catch-time value.
|
|
103
|
+
const baseline = useRef({ location: props.location, revalidation: props.revalidation });
|
|
104
|
+
useLayoutEffect(() => {
|
|
105
|
+
const b = baseline.current;
|
|
106
|
+
const shouldReset =
|
|
107
|
+
b.location !== props.location || b.revalidation !== 'idle' && props.revalidation === 'idle';
|
|
108
|
+
b.revalidation = props.revalidation || b.revalidation;
|
|
109
|
+
if (shouldReset) {
|
|
110
|
+
props.reset();
|
|
111
|
+
}
|
|
112
|
+
}, [props.location, props.revalidation]);
|
|
113
|
+
|
|
114
|
+
<RouteContext.Provider value={props.routeContext}>
|
|
115
|
+
<RouteErrorContext.Provider value={error}>{props.component}</RouteErrorContext.Provider>
|
|
116
|
+
</RouteContext.Provider>
|
|
117
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Type declaration for the .tsrx component (resolved by relative path).
|
|
2
|
+
import type { Location } from './router/history';
|
|
3
|
+
import type { RevalidationState } from './router/router';
|
|
4
|
+
import type { RouteContextObject } from './context';
|
|
5
|
+
|
|
6
|
+
export declare const RenderErrorBoundary: (props: {
|
|
7
|
+
location: Location;
|
|
8
|
+
revalidation: RevalidationState;
|
|
9
|
+
error: any;
|
|
10
|
+
component: unknown;
|
|
11
|
+
routeContext: RouteContextObject;
|
|
12
|
+
onError?: (error: unknown, errorInfo?: unknown) => void;
|
|
13
|
+
children?: unknown;
|
|
14
|
+
}) => unknown;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Vendored from react-router@7.18.1 packages/react-router/lib/actions.ts — unmodified.
|
|
2
|
+
// Re-vendor with `node scripts/vendor-remix-router.mjs`; never hand-edit.
|
|
3
|
+
export function throwIfPotentialCSRFAttack(
|
|
4
|
+
request: Request,
|
|
5
|
+
allowedActionOrigins: string[] | undefined,
|
|
6
|
+
) {
|
|
7
|
+
let originHeader = request.headers.get('origin');
|
|
8
|
+
let originDomain: string | null = null;
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
originDomain =
|
|
12
|
+
typeof originHeader === 'string' && originHeader !== 'null'
|
|
13
|
+
? new URL(originHeader).host
|
|
14
|
+
: originHeader;
|
|
15
|
+
} catch {
|
|
16
|
+
throw new Error(`\`origin\` header is not a valid URL. Aborting the action.`);
|
|
17
|
+
}
|
|
18
|
+
let host = new URL(request.url).host;
|
|
19
|
+
|
|
20
|
+
if (originDomain && originDomain !== host) {
|
|
21
|
+
if (!isAllowedOrigin(originDomain, allowedActionOrigins)) {
|
|
22
|
+
// This seems to be an CSRF attack. We should not proceed with the action.
|
|
23
|
+
throw new Error(
|
|
24
|
+
'The `request.url` host does not match `origin` header from a forwarded ' +
|
|
25
|
+
'action request. Aborting the action.',
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Implementation of micromatch by Next.js https://github.com/vercel/next.js/blob/ea927b583d24f42e538001bf13370e38c91d17bf/packages/next/src/server/app-render/csrf-protection.ts#L6
|
|
32
|
+
function matchWildcardDomain(domain: string, pattern: string) {
|
|
33
|
+
const domainParts = domain.split('.');
|
|
34
|
+
const patternParts = pattern.split('.');
|
|
35
|
+
|
|
36
|
+
if (patternParts.length < 1) {
|
|
37
|
+
// pattern is empty and therefore invalid to match against
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (domainParts.length < patternParts.length) {
|
|
42
|
+
// domain has too few segments and thus cannot match
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
while (patternParts.length) {
|
|
47
|
+
const patternPart = patternParts.pop();
|
|
48
|
+
const domainPart = domainParts.pop();
|
|
49
|
+
|
|
50
|
+
switch (patternPart) {
|
|
51
|
+
case '': {
|
|
52
|
+
// invalid pattern. pattern segments must be non empty
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
case '*': {
|
|
56
|
+
// wildcard matches anything so we continue if the domain part is non-empty
|
|
57
|
+
if (domainPart) {
|
|
58
|
+
continue;
|
|
59
|
+
} else {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
case '**': {
|
|
64
|
+
// if this is not the last item in the pattern the pattern is invalid
|
|
65
|
+
if (patternParts.length > 0) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
// recursive wildcard matches anything so we terminate here if the domain part is non empty
|
|
69
|
+
return domainPart !== undefined;
|
|
70
|
+
}
|
|
71
|
+
case undefined:
|
|
72
|
+
default: {
|
|
73
|
+
if (domainPart !== patternPart) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// We exhausted the pattern. If we also exhausted the domain we have a match
|
|
81
|
+
return domainParts.length === 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function isAllowedOrigin(originDomain: string, allowedActionOrigins: string[] | undefined = []) {
|
|
85
|
+
return allowedActionOrigins.some(
|
|
86
|
+
(allowedOrigin) =>
|
|
87
|
+
allowedOrigin &&
|
|
88
|
+
(allowedOrigin === originDomain || matchWildcardDomain(originDomain, allowedOrigin)),
|
|
89
|
+
);
|
|
90
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// MemoryRouter — transcribed from react-router@7.18.1 lib/components.tsx.
|
|
2
|
+
// A declarative Router over an in-memory history stack.
|
|
3
|
+
import { startTransition, useCallback, useLayoutEffect, useRef, useState } from 'octane';
|
|
4
|
+
import { createMemoryHistory } from '../router/history.ts';
|
|
5
|
+
import { Router } from './Router.tsrx';
|
|
6
|
+
|
|
7
|
+
export function MemoryRouter(props) @{
|
|
8
|
+
const { basename, children, initialEntries, initialIndex, useTransitions } = props;
|
|
9
|
+
|
|
10
|
+
const historyRef = useRef(null);
|
|
11
|
+
if (historyRef.current == null) {
|
|
12
|
+
historyRef.current = createMemoryHistory({
|
|
13
|
+
initialEntries,
|
|
14
|
+
initialIndex,
|
|
15
|
+
v5Compat: true,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const history = historyRef.current;
|
|
20
|
+
const [state, setStateImpl] = useState({
|
|
21
|
+
action: history.action,
|
|
22
|
+
location: history.location,
|
|
23
|
+
});
|
|
24
|
+
const setState = useCallback((newState) => {
|
|
25
|
+
if (useTransitions === false) {
|
|
26
|
+
setStateImpl(newState);
|
|
27
|
+
} else {
|
|
28
|
+
startTransition(() => setStateImpl(newState));
|
|
29
|
+
}
|
|
30
|
+
}, [useTransitions]);
|
|
31
|
+
|
|
32
|
+
useLayoutEffect(() => history.listen(setState), [history, setState]);
|
|
33
|
+
|
|
34
|
+
<Router
|
|
35
|
+
basename={basename}
|
|
36
|
+
location={state.location}
|
|
37
|
+
navigationType={state.action}
|
|
38
|
+
navigator={history}
|
|
39
|
+
useTransitions={useTransitions}
|
|
40
|
+
children={children}
|
|
41
|
+
/>
|
|
42
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Type declaration for the .tsrx component (resolved by relative path).
|
|
2
|
+
import type { InitialEntry } from '../router/history';
|
|
3
|
+
|
|
4
|
+
export declare const MemoryRouter: (props: {
|
|
5
|
+
basename?: string;
|
|
6
|
+
children?: unknown;
|
|
7
|
+
initialEntries?: InitialEntry[];
|
|
8
|
+
initialIndex?: number;
|
|
9
|
+
useTransitions?: boolean;
|
|
10
|
+
}) => unknown;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Navigate — transcribed from react-router@7.18.1 lib/components.tsx.
|
|
2
|
+
// Renders nothing; navigates from an effect on mount/update. Plain .ts (no
|
|
3
|
+
// JSX needed); the hooks it composes handle a slotless caller via bare-tag
|
|
4
|
+
// sub-slots.
|
|
5
|
+
import { useContext, useEffect } from 'octane';
|
|
6
|
+
import { NavigationContext, RouteContext } from '../context';
|
|
7
|
+
import { invariant, warning } from '../router/history';
|
|
8
|
+
import type { To } from '../router/history';
|
|
9
|
+
import type { RelativeRoutingType } from '../router/router';
|
|
10
|
+
import { getResolveToMatches, resolveTo } from '../router/utils';
|
|
11
|
+
import { useInRouterContext, useLocation, useNavigate } from '../hooks';
|
|
12
|
+
|
|
13
|
+
export interface NavigateProps {
|
|
14
|
+
to: To;
|
|
15
|
+
replace?: boolean;
|
|
16
|
+
state?: any;
|
|
17
|
+
relative?: RelativeRoutingType;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A component-based version of `useNavigate` to use in a render-prop-less
|
|
22
|
+
* context — navigates to the given `to` value whenever it renders.
|
|
23
|
+
*/
|
|
24
|
+
export function Navigate(props: NavigateProps): null {
|
|
25
|
+
const { to, replace, state, relative } = props;
|
|
26
|
+
invariant(
|
|
27
|
+
useInRouterContext(),
|
|
28
|
+
`<Navigate> may be used only in the context of a <Router> component.`,
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const { static: isStatic } = useContext(NavigationContext);
|
|
32
|
+
|
|
33
|
+
warning(
|
|
34
|
+
!isStatic,
|
|
35
|
+
`<Navigate> must not be used on the initial render in a <StaticRouter>. ` +
|
|
36
|
+
`This is a no-op, but you should modify your code so the <Navigate> is ` +
|
|
37
|
+
`only ever rendered in response to some user interaction or state change.`,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const { matches } = useContext(RouteContext);
|
|
41
|
+
const { pathname: locationPathname } = useLocation();
|
|
42
|
+
const navigate = useNavigate() as (to: To, opts?: any) => void;
|
|
43
|
+
|
|
44
|
+
// Resolve the path outside of the effect so repeat effect runs navigate to
|
|
45
|
+
// the same place (upstream: StrictMode double-invoke safety).
|
|
46
|
+
const path = resolveTo(to, getResolveToMatches(matches), locationPathname, relative === 'path');
|
|
47
|
+
const jsonPath = JSON.stringify(path);
|
|
48
|
+
|
|
49
|
+
// Plain-.ts component: hand-passed stable slot (state is keyed per
|
|
50
|
+
// component-instance scope).
|
|
51
|
+
useEffect(
|
|
52
|
+
() => {
|
|
53
|
+
navigate(JSON.parse(jsonPath), { replace, state, relative });
|
|
54
|
+
},
|
|
55
|
+
[navigate, jsonPath, relative, replace, state],
|
|
56
|
+
Symbol.for('rr:navigate:eff') as any,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Router — transcribed from react-router@7.18.1 lib/components.tsx. The
|
|
2
|
+
// low-level context provider every router variant renders through.
|
|
3
|
+
import { useMemo } from 'octane';
|
|
4
|
+
import { LocationContext, NavigationContext } from '../context.ts';
|
|
5
|
+
import { Action as NavigationType, invariant, parsePath, warning } from '../router/history.ts';
|
|
6
|
+
import { stripBasename } from '../router/utils.ts';
|
|
7
|
+
import { useInRouterContext } from '../hooks.ts';
|
|
8
|
+
|
|
9
|
+
export function Router(props) @{
|
|
10
|
+
const {
|
|
11
|
+
basename: basenameProp = '/',
|
|
12
|
+
children = null,
|
|
13
|
+
location: locationPropIn,
|
|
14
|
+
navigationType = NavigationType.Pop,
|
|
15
|
+
navigator,
|
|
16
|
+
static: staticProp = false,
|
|
17
|
+
useTransitions,
|
|
18
|
+
} = props;
|
|
19
|
+
|
|
20
|
+
invariant(
|
|
21
|
+
!useInRouterContext(),
|
|
22
|
+
`You cannot render a <Router> inside another <Router>.` +
|
|
23
|
+
` You should never have more than one in your app.`,
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
// Preserve trailing slashes on basename, so we can let the user control
|
|
27
|
+
// the enforcement of trailing slashes throughout the app
|
|
28
|
+
const basename = basenameProp.replace(/^\/*/, '/');
|
|
29
|
+
const navigationContext = useMemo(
|
|
30
|
+
() => ({
|
|
31
|
+
basename,
|
|
32
|
+
navigator,
|
|
33
|
+
static: staticProp,
|
|
34
|
+
useTransitions,
|
|
35
|
+
future: {},
|
|
36
|
+
}),
|
|
37
|
+
[basename, navigator, staticProp, useTransitions],
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
let locationProp = locationPropIn;
|
|
41
|
+
if (typeof locationProp === 'string') {
|
|
42
|
+
locationProp = parsePath(locationProp);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const {
|
|
46
|
+
pathname = '/',
|
|
47
|
+
search = '',
|
|
48
|
+
hash = '',
|
|
49
|
+
state = null,
|
|
50
|
+
key = 'default',
|
|
51
|
+
mask,
|
|
52
|
+
} = locationProp;
|
|
53
|
+
|
|
54
|
+
const locationContext = useMemo(() => {
|
|
55
|
+
const trailingPathname = stripBasename(pathname, basename);
|
|
56
|
+
|
|
57
|
+
if (trailingPathname == null) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
location: {
|
|
63
|
+
pathname: trailingPathname,
|
|
64
|
+
search,
|
|
65
|
+
hash,
|
|
66
|
+
state,
|
|
67
|
+
key,
|
|
68
|
+
mask,
|
|
69
|
+
},
|
|
70
|
+
navigationType,
|
|
71
|
+
};
|
|
72
|
+
}, [basename, pathname, search, hash, state, key, navigationType, mask]);
|
|
73
|
+
|
|
74
|
+
warning(
|
|
75
|
+
locationContext != null,
|
|
76
|
+
`<Router basename="${basename}"> is not able to match the URL ` +
|
|
77
|
+
`"${pathname}${search}${hash}" because it does not start with the ` +
|
|
78
|
+
`basename, so the <Router> won't render anything.`,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
@if (locationContext == null) {
|
|
82
|
+
<></>
|
|
83
|
+
} @else {
|
|
84
|
+
<NavigationContext.Provider value={navigationContext}>
|
|
85
|
+
<LocationContext.Provider value={locationContext}>{children}</LocationContext.Provider>
|
|
86
|
+
</NavigationContext.Provider>
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Type declaration for the .tsrx component (resolved by relative path).
|
|
2
|
+
import type { Location } from '../router/history';
|
|
3
|
+
import type { Navigator } from '../context';
|
|
4
|
+
|
|
5
|
+
export declare const Router: (props: {
|
|
6
|
+
basename?: string;
|
|
7
|
+
children?: unknown;
|
|
8
|
+
location: Partial<Location> | string;
|
|
9
|
+
navigationType?: any;
|
|
10
|
+
navigator: Navigator;
|
|
11
|
+
static?: boolean;
|
|
12
|
+
useTransitions?: boolean;
|
|
13
|
+
}) => unknown;
|