@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,317 @@
|
|
|
1
|
+
// Declarative `<Routes>`/`<Route>` support — transcribed from
|
|
2
|
+
// react-router@7.18.1 lib/components.tsx with ONE octane-specific mechanism.
|
|
3
|
+
//
|
|
4
|
+
// Upstream walks React children (`createRoutesFromChildren`) reading
|
|
5
|
+
// `element.type === Route`. Octane value-position JSX (argument position, prop
|
|
6
|
+
// position, arrays) lowers to walkable descriptors, so that path ports
|
|
7
|
+
// verbatim. But the natural `.tsrx` authoring — `<Routes><Route/></Routes>` —
|
|
8
|
+
// compiles the children into an OPAQUE render block, so those `<Route>`s are
|
|
9
|
+
// collected by REGISTRATION instead (the recharts Cell precedent): `<Routes>`
|
|
10
|
+
// renders the block invisibly under a collector context, each `<Route>`
|
|
11
|
+
// registers its RouteObject-shaped props in a layout effect (and provides a
|
|
12
|
+
// nested collector for its own block children), and `<Routes>` finalizes the
|
|
13
|
+
// config pre-paint. Registration order is mount order — source order for
|
|
14
|
+
// static trees; a conditionally-mounted `<Route>` between static siblings
|
|
15
|
+
// registers late, which only matters for `matchRoutes` score TIES (documented
|
|
16
|
+
// divergence, pinned by a test).
|
|
17
|
+
import {
|
|
18
|
+
createContext,
|
|
19
|
+
createElement,
|
|
20
|
+
isChildrenBlock,
|
|
21
|
+
useContext,
|
|
22
|
+
useId,
|
|
23
|
+
useLayoutEffect,
|
|
24
|
+
useRef,
|
|
25
|
+
Children,
|
|
26
|
+
isValidElement,
|
|
27
|
+
Fragment,
|
|
28
|
+
} from 'octane';
|
|
29
|
+
import { invariant } from './../router/history';
|
|
30
|
+
import type { RouteObject } from '../router/utils';
|
|
31
|
+
|
|
32
|
+
interface CollectorEntry {
|
|
33
|
+
order: number;
|
|
34
|
+
props: Record<string, any>;
|
|
35
|
+
childCollector: RoutesCollector | null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface RoutesCollector {
|
|
39
|
+
entries: Map<string, CollectorEntry>;
|
|
40
|
+
nextOrder: number;
|
|
41
|
+
onChange: () => void;
|
|
42
|
+
register(id: string, props: Record<string, any>, childCollector: RoutesCollector | null): void;
|
|
43
|
+
unregister(id: string): void;
|
|
44
|
+
collect(parentPath?: number[]): RouteObject[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// The RouteObject-shaped fields a <Route> carries (upstream's route-object
|
|
48
|
+
// construction in createRoutesFromChildren reads exactly these).
|
|
49
|
+
const ROUTE_PROP_KEYS = [
|
|
50
|
+
'id',
|
|
51
|
+
'caseSensitive',
|
|
52
|
+
'element',
|
|
53
|
+
'Component',
|
|
54
|
+
'index',
|
|
55
|
+
'path',
|
|
56
|
+
'middleware',
|
|
57
|
+
'loader',
|
|
58
|
+
'action',
|
|
59
|
+
'hydrateFallbackElement',
|
|
60
|
+
'HydrateFallback',
|
|
61
|
+
'errorElement',
|
|
62
|
+
'ErrorBoundary',
|
|
63
|
+
'hasErrorBoundary',
|
|
64
|
+
'shouldRevalidate',
|
|
65
|
+
'handle',
|
|
66
|
+
'lazy',
|
|
67
|
+
] as const;
|
|
68
|
+
|
|
69
|
+
// Route props re-create their element descriptors on every render
|
|
70
|
+
// (`element={<Home/>}` is a fresh value-position descriptor each pass), so an
|
|
71
|
+
// identity comparison would register a material change — and bump/re-render
|
|
72
|
+
// <Routes> — on every pass, forever. Descriptors compare structurally
|
|
73
|
+
// (type/key + props, recursing through nested descriptors and arrays,
|
|
74
|
+
// depth-bounded); everything else stays Object.is, so a genuinely-changed
|
|
75
|
+
// element still registers as material.
|
|
76
|
+
function routeValueEqual(a: unknown, b: unknown, depth: number): boolean {
|
|
77
|
+
if (Object.is(a, b)) return true;
|
|
78
|
+
if (depth > 8 || a == null || b == null) return false;
|
|
79
|
+
if (Array.isArray(a)) {
|
|
80
|
+
if (!Array.isArray(b) || a.length !== b.length) return false;
|
|
81
|
+
for (let i = 0; i < a.length; i++) {
|
|
82
|
+
if (!routeValueEqual(a[i], b[i], depth + 1)) return false;
|
|
83
|
+
}
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
if (isValidElement(a) || isValidElement(b)) {
|
|
87
|
+
return (
|
|
88
|
+
isValidElement(a) &&
|
|
89
|
+
isValidElement(b) &&
|
|
90
|
+
a.type === b.type &&
|
|
91
|
+
(a as any).key === (b as any).key &&
|
|
92
|
+
routeValueEqual(a.props, b.props, depth + 1)
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
if (typeof a !== 'object' || typeof b !== 'object') return false;
|
|
96
|
+
const pa = Object.getPrototypeOf(a);
|
|
97
|
+
const pb = Object.getPrototypeOf(b);
|
|
98
|
+
if ((pa !== Object.prototype && pa !== null) || (pb !== Object.prototype && pb !== null)) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
const ka = Object.keys(a);
|
|
102
|
+
if (ka.length !== Object.keys(b).length) return false;
|
|
103
|
+
for (const k of ka) {
|
|
104
|
+
if (!routeValueEqual((a as any)[k], (b as any)[k], depth + 1)) return false;
|
|
105
|
+
}
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function routePropsEqual(a: Record<string, any>, b: Record<string, any>): boolean {
|
|
110
|
+
for (const key of ROUTE_PROP_KEYS) {
|
|
111
|
+
if (!routeValueEqual(a[key], b[key], 0)) return false;
|
|
112
|
+
}
|
|
113
|
+
// BLOCK children are opaque compiled render fns whose identity is fresh
|
|
114
|
+
// whenever the enclosing body re-runs (nested `__children$N` helpers are
|
|
115
|
+
// declared inside their parent body) — but their identity is immaterial:
|
|
116
|
+
// buildRoute reads the nested COLLECTOR for block children, and content
|
|
117
|
+
// changes inside the block surface through that collector's own
|
|
118
|
+
// registrations. Descriptor children compare structurally like any value.
|
|
119
|
+
const ac = a.children;
|
|
120
|
+
const bc = b.children;
|
|
121
|
+
if (ac != null && bc != null && isChildrenBlock(ac) && isChildrenBlock(bc)) return true;
|
|
122
|
+
return routeValueEqual(ac, bc, 0);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function createCollector(onChange: () => void): RoutesCollector {
|
|
126
|
+
return {
|
|
127
|
+
entries: new Map(),
|
|
128
|
+
nextOrder: 0,
|
|
129
|
+
onChange,
|
|
130
|
+
register(id, props, childCollector) {
|
|
131
|
+
const existing = this.entries.get(id);
|
|
132
|
+
if (existing) {
|
|
133
|
+
// Only notify on a MATERIAL change — Route re-registers every
|
|
134
|
+
// render (fresh props objects), and an unconditional bump would
|
|
135
|
+
// re-render <Routes> forever.
|
|
136
|
+
const changed =
|
|
137
|
+
!routePropsEqual(existing.props, props) || existing.childCollector !== childCollector;
|
|
138
|
+
existing.props = props;
|
|
139
|
+
existing.childCollector = childCollector;
|
|
140
|
+
if (changed) this.onChange();
|
|
141
|
+
} else {
|
|
142
|
+
this.entries.set(id, { order: this.nextOrder++, props, childCollector });
|
|
143
|
+
this.onChange();
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
unregister(id) {
|
|
147
|
+
if (this.entries.delete(id)) this.onChange();
|
|
148
|
+
},
|
|
149
|
+
collect(parentPath = []) {
|
|
150
|
+
const sorted = [...this.entries.values()].sort((a, b) => a.order - b.order);
|
|
151
|
+
return sorted.map((entry, index) => buildRoute(entry, [...parentPath, index]));
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function buildRoute(entry: CollectorEntry, treePath: number[]): RouteObject {
|
|
157
|
+
const p = entry.props;
|
|
158
|
+
invariant(!p.index || !p.children, 'An index route cannot have child routes.');
|
|
159
|
+
const route: RouteObject = {
|
|
160
|
+
id: p.id || treePath.join('-'),
|
|
161
|
+
caseSensitive: p.caseSensitive,
|
|
162
|
+
element: p.element,
|
|
163
|
+
Component: p.Component,
|
|
164
|
+
index: p.index,
|
|
165
|
+
path: p.path,
|
|
166
|
+
middleware: p.middleware,
|
|
167
|
+
loader: p.loader,
|
|
168
|
+
action: p.action,
|
|
169
|
+
hydrateFallbackElement: p.hydrateFallbackElement,
|
|
170
|
+
HydrateFallback: p.HydrateFallback,
|
|
171
|
+
errorElement: p.errorElement,
|
|
172
|
+
ErrorBoundary: p.ErrorBoundary,
|
|
173
|
+
hasErrorBoundary:
|
|
174
|
+
p.hasErrorBoundary === true || p.ErrorBoundary != null || p.errorElement != null,
|
|
175
|
+
shouldRevalidate: p.shouldRevalidate,
|
|
176
|
+
handle: p.handle,
|
|
177
|
+
lazy: p.lazy,
|
|
178
|
+
} as RouteObject;
|
|
179
|
+
if (entry.childCollector) {
|
|
180
|
+
// Block children — registered into the nested collector.
|
|
181
|
+
route.children = entry.childCollector.collect(treePath);
|
|
182
|
+
} else if (p.children) {
|
|
183
|
+
// Descriptor children — walked directly, upstream-style.
|
|
184
|
+
route.children = createRoutesFromChildren(p.children, treePath);
|
|
185
|
+
}
|
|
186
|
+
return route;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export const RoutesCollectorContext = createContext<RoutesCollector | null>(null);
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Configures an element to render when a pattern matches the current location.
|
|
193
|
+
* It must be rendered within a {@link Routes} element.
|
|
194
|
+
*/
|
|
195
|
+
export function Route(props: Record<string, any>): unknown {
|
|
196
|
+
const collector = useContext(RoutesCollectorContext);
|
|
197
|
+
invariant(
|
|
198
|
+
collector,
|
|
199
|
+
`A <Route> is only ever to be used as the child of <Routes> element, ` +
|
|
200
|
+
`never rendered directly. Please wrap your <Route> in a <Routes>.`,
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
// Plain-.ts component: hooks receive hand-passed stable slot symbols (the
|
|
204
|
+
// runtime keys hook state per component-instance SCOPE, so fixed call-site
|
|
205
|
+
// symbols are exactly what the compiler would inject).
|
|
206
|
+
const id = useId(Symbol.for('rr:route:id') as any);
|
|
207
|
+
const hasBlockChildren = props.children != null && isChildrenBlock(props.children);
|
|
208
|
+
const childCollectorRef = useRef<RoutesCollector | null>(null, Symbol.for('rr:route:cc') as any);
|
|
209
|
+
if (hasBlockChildren && childCollectorRef.current === null) {
|
|
210
|
+
// Nested block children register here; changes bubble to the root
|
|
211
|
+
// collector's onChange.
|
|
212
|
+
childCollectorRef.current = createCollector(collector.onChange);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Register AFTER the children's layout effects (octane runs effects
|
|
216
|
+
// child-first on mount), so a nested collector is fully populated before
|
|
217
|
+
// the parent finalizes. No deps: props may change render to render — the
|
|
218
|
+
// collector's material-change check keeps this loop-free.
|
|
219
|
+
useLayoutEffect(
|
|
220
|
+
() => {
|
|
221
|
+
collector.register(id, props, hasBlockChildren ? childCollectorRef.current : null);
|
|
222
|
+
},
|
|
223
|
+
undefined as any,
|
|
224
|
+
Symbol.for('rr:route:reg') as any,
|
|
225
|
+
);
|
|
226
|
+
useLayoutEffect(
|
|
227
|
+
() => {
|
|
228
|
+
return () => collector.unregister(id);
|
|
229
|
+
},
|
|
230
|
+
[collector, id],
|
|
231
|
+
Symbol.for('rr:route:unreg') as any,
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
// Block children render invisibly under the nested collector so the
|
|
235
|
+
// <Route>s inside register; descriptor children need no rendering (walked
|
|
236
|
+
// at collect time). Route itself renders nothing, as upstream.
|
|
237
|
+
if (hasBlockChildren) {
|
|
238
|
+
return createElement(RoutesCollectorContext.Provider as any, {
|
|
239
|
+
value: childCollectorRef.current,
|
|
240
|
+
children: props.children,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Creates a route config from a React "children" object, which is usually
|
|
248
|
+
* either a `<Route>` element or an array of them. Used internally by
|
|
249
|
+
* `<Routes>` to create a route config from its children.
|
|
250
|
+
*/
|
|
251
|
+
export function createRoutesFromChildren(
|
|
252
|
+
children: unknown,
|
|
253
|
+
parentPath: number[] = [],
|
|
254
|
+
): RouteObject[] {
|
|
255
|
+
const routes: RouteObject[] = [];
|
|
256
|
+
|
|
257
|
+
Children.forEach(children as any, (element: any, index: number) => {
|
|
258
|
+
if (!isValidElement(element)) {
|
|
259
|
+
// Ignore non-elements. This allows people to more easily inline
|
|
260
|
+
// conditionals in their route config.
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const treePath = [...parentPath, index];
|
|
265
|
+
|
|
266
|
+
if ((element.type as unknown) === Fragment) {
|
|
267
|
+
// Transparently support Fragment and its children.
|
|
268
|
+
routes.push.apply(routes, createRoutesFromChildren(element.props.children, treePath));
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
invariant(
|
|
273
|
+
element.type === Route,
|
|
274
|
+
`[${
|
|
275
|
+
typeof element.type === 'string' ? element.type : (element.type as any).name
|
|
276
|
+
}] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>`,
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
invariant(
|
|
280
|
+
!element.props.index || !element.props.children,
|
|
281
|
+
'An index route cannot have child routes.',
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
const route: RouteObject = {
|
|
285
|
+
id: element.props.id || treePath.join('-'),
|
|
286
|
+
caseSensitive: element.props.caseSensitive,
|
|
287
|
+
element: element.props.element,
|
|
288
|
+
Component: element.props.Component,
|
|
289
|
+
index: element.props.index,
|
|
290
|
+
path: element.props.path,
|
|
291
|
+
middleware: element.props.middleware,
|
|
292
|
+
loader: element.props.loader,
|
|
293
|
+
action: element.props.action,
|
|
294
|
+
hydrateFallbackElement: element.props.hydrateFallbackElement,
|
|
295
|
+
HydrateFallback: element.props.HydrateFallback,
|
|
296
|
+
errorElement: element.props.errorElement,
|
|
297
|
+
ErrorBoundary: element.props.ErrorBoundary,
|
|
298
|
+
hasErrorBoundary:
|
|
299
|
+
element.props.hasErrorBoundary === true ||
|
|
300
|
+
element.props.ErrorBoundary != null ||
|
|
301
|
+
element.props.errorElement != null,
|
|
302
|
+
shouldRevalidate: element.props.shouldRevalidate,
|
|
303
|
+
handle: element.props.handle,
|
|
304
|
+
lazy: element.props.lazy,
|
|
305
|
+
} as RouteObject;
|
|
306
|
+
|
|
307
|
+
if (element.props.children) {
|
|
308
|
+
route.children = createRoutesFromChildren(element.props.children, treePath);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
routes.push(route);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
return routes;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export const createRoutesFromElements = createRoutesFromChildren;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// Transcribed from react-router@7.18.1 lib/components.tsx (the non-component
|
|
2
|
+
// utilities) onto octane: mapRouteProperties / hydrationRouteProperties /
|
|
3
|
+
// createMemoryRouter / Deferred / getOptimisticRouterState, plus warnOnce
|
|
4
|
+
// (upstream imports it from lib/server-runtime/warnings.ts — inlined here,
|
|
5
|
+
// the server runtime is out of scope).
|
|
6
|
+
import { createElement } from 'octane';
|
|
7
|
+
import type { InitialEntry } from '../router/history';
|
|
8
|
+
import { createMemoryHistory } from '../router/history';
|
|
9
|
+
import { warning } from '../router/history';
|
|
10
|
+
import type { Router as DataRouter, RouterState } from '../router/router';
|
|
11
|
+
import { createRouter } from '../router/router';
|
|
12
|
+
import type { RouteMatch, RouteObject } from '../router/utils';
|
|
13
|
+
import { ENABLE_DEV_WARNINGS } from '../context';
|
|
14
|
+
import { _renderMatches } from '../hooks';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Renders the result of `matchRoutes()` into a React element.
|
|
18
|
+
*/
|
|
19
|
+
export function renderMatches(matches: RouteMatch[] | null): unknown | null {
|
|
20
|
+
return _renderMatches(matches);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const alreadyWarned: { [message: string]: boolean } = {};
|
|
24
|
+
|
|
25
|
+
export function warnOnce(condition: boolean, message: string): void {
|
|
26
|
+
if (!condition && !alreadyWarned[message]) {
|
|
27
|
+
alreadyWarned[message] = true;
|
|
28
|
+
console.warn(message);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function mapRouteProperties(route: RouteObject) {
|
|
33
|
+
let updates: Partial<RouteObject> & { hasErrorBoundary: boolean } = {
|
|
34
|
+
// Note: this check also occurs in createRoutesFromChildren so update
|
|
35
|
+
// there if you change this -- please and thank you!
|
|
36
|
+
hasErrorBoundary:
|
|
37
|
+
route.hasErrorBoundary || route.ErrorBoundary != null || route.errorElement != null,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
if (route.Component) {
|
|
41
|
+
if (ENABLE_DEV_WARNINGS) {
|
|
42
|
+
if (route.element) {
|
|
43
|
+
warning(
|
|
44
|
+
false,
|
|
45
|
+
'You should not include both `Component` and `element` on your route - ' +
|
|
46
|
+
'`Component` will be used.',
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
Object.assign(updates, {
|
|
51
|
+
element: createElement(route.Component as any),
|
|
52
|
+
Component: undefined,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (route.HydrateFallback) {
|
|
57
|
+
if (ENABLE_DEV_WARNINGS) {
|
|
58
|
+
if (route.hydrateFallbackElement) {
|
|
59
|
+
warning(
|
|
60
|
+
false,
|
|
61
|
+
'You should not include both `HydrateFallback` and `hydrateFallbackElement` on your route - ' +
|
|
62
|
+
'`HydrateFallback` will be used.',
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
Object.assign(updates, {
|
|
67
|
+
hydrateFallbackElement: createElement(route.HydrateFallback as any),
|
|
68
|
+
HydrateFallback: undefined,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (route.ErrorBoundary) {
|
|
73
|
+
if (ENABLE_DEV_WARNINGS) {
|
|
74
|
+
if (route.errorElement) {
|
|
75
|
+
warning(
|
|
76
|
+
false,
|
|
77
|
+
'You should not include both `ErrorBoundary` and `errorElement` on your route - ' +
|
|
78
|
+
'`ErrorBoundary` will be used.',
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
Object.assign(updates, {
|
|
83
|
+
errorElement: createElement(route.ErrorBoundary as any),
|
|
84
|
+
ErrorBoundary: undefined,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return updates;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const hydrationRouteProperties: (keyof RouteObject)[] = [
|
|
92
|
+
'HydrateFallback',
|
|
93
|
+
'hydrateFallbackElement',
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
export interface MemoryRouterOpts {
|
|
97
|
+
basename?: string;
|
|
98
|
+
getContext?: any;
|
|
99
|
+
future?: any;
|
|
100
|
+
hydrationData?: any;
|
|
101
|
+
initialEntries?: InitialEntry[];
|
|
102
|
+
initialIndex?: number;
|
|
103
|
+
dataStrategy?: any;
|
|
104
|
+
patchRoutesOnNavigation?: any;
|
|
105
|
+
instrumentations?: any;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function createMemoryRouter(routes: RouteObject[], opts?: MemoryRouterOpts): DataRouter {
|
|
109
|
+
return createRouter({
|
|
110
|
+
basename: opts?.basename,
|
|
111
|
+
getContext: opts?.getContext,
|
|
112
|
+
future: opts?.future,
|
|
113
|
+
history: createMemoryHistory({
|
|
114
|
+
initialEntries: opts?.initialEntries,
|
|
115
|
+
initialIndex: opts?.initialIndex,
|
|
116
|
+
}),
|
|
117
|
+
hydrationData: opts?.hydrationData,
|
|
118
|
+
routes,
|
|
119
|
+
hydrationRouteProperties,
|
|
120
|
+
mapRouteProperties,
|
|
121
|
+
dataStrategy: opts?.dataStrategy,
|
|
122
|
+
patchRoutesOnNavigation: opts?.patchRoutesOnNavigation,
|
|
123
|
+
instrumentations: opts?.instrumentations,
|
|
124
|
+
}).initialize();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export class Deferred<T> {
|
|
128
|
+
status: 'pending' | 'resolved' | 'rejected' = 'pending';
|
|
129
|
+
promise: Promise<T>;
|
|
130
|
+
// @ts-expect-error - no initializer
|
|
131
|
+
resolve: (value: T) => void;
|
|
132
|
+
// @ts-expect-error - no initializer
|
|
133
|
+
reject: (reason?: unknown) => void;
|
|
134
|
+
constructor() {
|
|
135
|
+
this.promise = new Promise((resolve, reject) => {
|
|
136
|
+
this.resolve = (value) => {
|
|
137
|
+
if (this.status === 'pending') {
|
|
138
|
+
this.status = 'resolved';
|
|
139
|
+
resolve(value);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
this.reject = (reason) => {
|
|
143
|
+
if (this.status === 'pending') {
|
|
144
|
+
this.status = 'rejected';
|
|
145
|
+
reject(reason);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function getOptimisticRouterState(
|
|
153
|
+
currentState: RouterState,
|
|
154
|
+
newState: RouterState,
|
|
155
|
+
): RouterState {
|
|
156
|
+
return {
|
|
157
|
+
// Don't surface "current location specific" stuff mid-navigation
|
|
158
|
+
// (historyAction, location, matches, loaderData, errors, initialized,
|
|
159
|
+
// restoreScroll, preventScrollReset, blockers, etc.)
|
|
160
|
+
...currentState,
|
|
161
|
+
// Only surface "pending/in-flight stuff"
|
|
162
|
+
// (navigation, revalidation, actionData, fetchers, )
|
|
163
|
+
navigation:
|
|
164
|
+
newState.navigation.state !== 'idle' ? newState.navigation : currentState.navigation,
|
|
165
|
+
revalidation:
|
|
166
|
+
newState.revalidation !== 'idle' ? newState.revalidation : currentState.revalidation,
|
|
167
|
+
actionData:
|
|
168
|
+
newState.navigation.state !== 'submitting' ? newState.actionData : currentState.actionData,
|
|
169
|
+
fetchers: newState.fetchers,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// UNSAFE_With*Props wrappers — transcribed from react-router@7.18.1
|
|
2
|
+
// lib/components.tsx. Used by framework-mode codegen upstream; exported here
|
|
3
|
+
// for surface parity. Plain .ts: cloneElement/createElement over descriptors.
|
|
4
|
+
import { cloneElement, createElement } from 'octane';
|
|
5
|
+
import { useActionData, useLoaderData, useMatches, useParams } from '../hooks';
|
|
6
|
+
|
|
7
|
+
function useRouteComponentProps() {
|
|
8
|
+
return {
|
|
9
|
+
params: useParams(),
|
|
10
|
+
loaderData: useLoaderData(),
|
|
11
|
+
actionData: useActionData(),
|
|
12
|
+
matches: useMatches(),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type RouteComponentProps = ReturnType<typeof useRouteComponentProps>;
|
|
17
|
+
|
|
18
|
+
export function WithComponentProps(props: { children: any }) {
|
|
19
|
+
const routeProps = useRouteComponentProps();
|
|
20
|
+
return cloneElement(props.children, routeProps);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function withComponentProps(Component: any) {
|
|
24
|
+
return function WithComponentProps() {
|
|
25
|
+
const routeProps = useRouteComponentProps();
|
|
26
|
+
return createElement(Component, routeProps);
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function useHydrateFallbackProps() {
|
|
31
|
+
return {
|
|
32
|
+
params: useParams(),
|
|
33
|
+
loaderData: useLoaderData(),
|
|
34
|
+
actionData: useActionData(),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type HydrateFallbackProps = ReturnType<typeof useHydrateFallbackProps>;
|
|
39
|
+
|
|
40
|
+
export function WithHydrateFallbackProps(props: { children: any }) {
|
|
41
|
+
const routeProps = useHydrateFallbackProps();
|
|
42
|
+
return cloneElement(props.children, routeProps);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function withHydrateFallbackProps(HydrateFallback: any) {
|
|
46
|
+
return function WithHydrateFallbackProps() {
|
|
47
|
+
const routeProps = useHydrateFallbackProps();
|
|
48
|
+
return createElement(HydrateFallback, routeProps);
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function useErrorBoundaryProps() {
|
|
53
|
+
return {
|
|
54
|
+
params: useParams(),
|
|
55
|
+
loaderData: useLoaderData(),
|
|
56
|
+
actionData: useActionData(),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type ErrorBoundaryProps = ReturnType<typeof useErrorBoundaryProps>;
|
|
61
|
+
|
|
62
|
+
export function WithErrorBoundaryProps(props: { children: any }) {
|
|
63
|
+
const routeProps = useErrorBoundaryProps();
|
|
64
|
+
return cloneElement(props.children, routeProps);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function withErrorBoundaryProps(ErrorBoundary: any) {
|
|
68
|
+
return function WithErrorBoundaryProps() {
|
|
69
|
+
const routeProps = useErrorBoundaryProps();
|
|
70
|
+
return createElement(ErrorBoundary, routeProps);
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Transcribed from react-router@7.18.1 lib/context.ts onto octane. The ten
|
|
2
|
+
// contexts and their shapes are verbatim; octane substitutions: createContext/
|
|
3
|
+
// createElement from 'octane', `__DEV__` → NODE_ENV, no displayName (octane
|
|
4
|
+
// contexts carry none). RSCRouterContext is kept (hardwired default false — no
|
|
5
|
+
// RSC in octane) so `useIsRSCRouterContext` call sites stay verbatim.
|
|
6
|
+
import { createContext, createElement, useContext } from 'octane';
|
|
7
|
+
import type { History, Location, Action as NavigationType, To } from './router/history';
|
|
8
|
+
import type { RelativeRoutingType, Router, StaticHandlerContext } from './router/router';
|
|
9
|
+
import type { TrackedPromise, RouteMatch } from './router/utils';
|
|
10
|
+
|
|
11
|
+
// Upstream: `ClientOnErrorFunction` from ./components (framework-mode error
|
|
12
|
+
// reporting hook) — declared locally to keep the vendored-adjacent type
|
|
13
|
+
// surface without importing the framework layer.
|
|
14
|
+
export type ClientOnErrorFunction = (error: unknown, errorInfo?: unknown) => void;
|
|
15
|
+
|
|
16
|
+
export interface DataRouterContextObject
|
|
17
|
+
// Omit `future` since those can be pulled from the `router`
|
|
18
|
+
// `NavigationContext` needs `future`/`useTransitions` since it doesn't
|
|
19
|
+
// have a `router` in all cases
|
|
20
|
+
extends Omit<NavigationContextObject, 'future' | 'useTransitions'> {
|
|
21
|
+
router: Router;
|
|
22
|
+
staticContext?: StaticHandlerContext;
|
|
23
|
+
onError?: ClientOnErrorFunction;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const DataRouterContext = createContext<DataRouterContextObject | null>(null);
|
|
27
|
+
|
|
28
|
+
export const DataRouterStateContext = createContext<Router['state'] | null>(null);
|
|
29
|
+
|
|
30
|
+
export const RSCRouterContext = createContext<boolean>(false);
|
|
31
|
+
|
|
32
|
+
export function useIsRSCRouterContext(): boolean {
|
|
33
|
+
return useContext(RSCRouterContext);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type ViewTransitionContextObject =
|
|
37
|
+
| {
|
|
38
|
+
isTransitioning: false;
|
|
39
|
+
}
|
|
40
|
+
| {
|
|
41
|
+
isTransitioning: true;
|
|
42
|
+
flushSync: boolean;
|
|
43
|
+
currentLocation: Location;
|
|
44
|
+
nextLocation: Location;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const ViewTransitionContext = createContext<ViewTransitionContextObject>({
|
|
48
|
+
isTransitioning: false,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// TODO: (v7) Change the useFetcher data from `any` to `unknown`
|
|
52
|
+
export type FetchersContextObject = Map<string, any>;
|
|
53
|
+
|
|
54
|
+
export const FetchersContext = createContext<FetchersContextObject>(new Map());
|
|
55
|
+
|
|
56
|
+
export const AwaitContext = createContext<TrackedPromise | null>(null);
|
|
57
|
+
|
|
58
|
+
export const AwaitContextProvider = (props: { value: TrackedPromise | null; children?: unknown }) =>
|
|
59
|
+
createElement(AwaitContext.Provider as any, props);
|
|
60
|
+
|
|
61
|
+
export interface NavigateOptions {
|
|
62
|
+
/** Replace the current entry in the history stack instead of pushing a new one */
|
|
63
|
+
replace?: boolean;
|
|
64
|
+
/** Masked URL */
|
|
65
|
+
mask?: To;
|
|
66
|
+
/** Adds persistent client side routing state to the next location */
|
|
67
|
+
state?: any;
|
|
68
|
+
/** Prevent the scroll position from being reset to the top of the window on navigate */
|
|
69
|
+
preventScrollReset?: boolean;
|
|
70
|
+
/** Defines the relative path behavior for the link */
|
|
71
|
+
relative?: RelativeRoutingType;
|
|
72
|
+
/** Wrap the initial state update in flushSync instead of startTransition */
|
|
73
|
+
flushSync?: boolean;
|
|
74
|
+
/** Enable a View Transition for this navigation */
|
|
75
|
+
viewTransition?: boolean;
|
|
76
|
+
/** Specifies the default revalidation behavior after this submission */
|
|
77
|
+
defaultShouldRevalidate?: boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* A Navigator is a "location changer"; it's how you get to different locations.
|
|
82
|
+
*
|
|
83
|
+
* Every history instance conforms to the Navigator interface, but the
|
|
84
|
+
* distinction is useful primarily when it comes to the low-level `<Router>` API
|
|
85
|
+
* where both the location and a navigator must be provided separately in order
|
|
86
|
+
* to avoid "tearing" that may occur in a suspense-enabled app if the action
|
|
87
|
+
* and/or location were to be read directly from the history instance.
|
|
88
|
+
*/
|
|
89
|
+
export interface Navigator {
|
|
90
|
+
createHref: History['createHref'];
|
|
91
|
+
// Optional for backwards-compat with Router/HistoryRouter usage (edge case)
|
|
92
|
+
encodeLocation?: History['encodeLocation'];
|
|
93
|
+
go: History['go'];
|
|
94
|
+
push(to: To, state?: any, opts?: NavigateOptions): void;
|
|
95
|
+
replace(to: To, state?: any, opts?: NavigateOptions): void;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
interface NavigationContextObject {
|
|
99
|
+
basename: string;
|
|
100
|
+
navigator: Navigator;
|
|
101
|
+
static: boolean;
|
|
102
|
+
useTransitions: boolean | undefined;
|
|
103
|
+
future: {};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export const NavigationContext = createContext<NavigationContextObject>(null!);
|
|
107
|
+
|
|
108
|
+
interface LocationContextObject {
|
|
109
|
+
location: Location;
|
|
110
|
+
navigationType: NavigationType;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export const LocationContext = createContext<LocationContextObject>(null!);
|
|
114
|
+
|
|
115
|
+
export interface RouteContextObject {
|
|
116
|
+
outlet: unknown | null;
|
|
117
|
+
matches: RouteMatch[];
|
|
118
|
+
isDataRoute: boolean;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export const RouteContext = createContext<RouteContextObject>({
|
|
122
|
+
outlet: null,
|
|
123
|
+
matches: [],
|
|
124
|
+
isDataRoute: false,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
export const RouteErrorContext = createContext<any>(null);
|
|
128
|
+
|
|
129
|
+
export const ENABLE_DEV_WARNINGS = process.env.NODE_ENV !== 'production';
|