@bleedingdev/modern-js-plugin-tanstack 3.2.0-ultramodern.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/dist/cjs/cli/index.js +268 -0
- package/dist/cjs/cli/tanstackTypes.js +388 -0
- package/dist/cjs/cli.js +65 -0
- package/dist/cjs/runtime/DefaultNotFound.js +47 -0
- package/dist/cjs/runtime/basepathRewrite.js +62 -0
- package/dist/cjs/runtime/dataMutation.js +345 -0
- package/dist/cjs/runtime/hooks.js +57 -0
- package/dist/cjs/runtime/index.js +114 -0
- package/dist/cjs/runtime/lifecycle.js +125 -0
- package/dist/cjs/runtime/plugin.js +250 -0
- package/dist/cjs/runtime/plugin.node.js +304 -0
- package/dist/cjs/runtime/prefetchLink.js +55 -0
- package/dist/cjs/runtime/routeTree.js +492 -0
- package/dist/cjs/runtime/rsc/ClientSlot.js +53 -0
- package/dist/cjs/runtime/rsc/CompositeComponent.js +75 -0
- package/dist/cjs/runtime/rsc/ReplayableStream.js +141 -0
- package/dist/cjs/runtime/rsc/RscNodeRenderer.js +65 -0
- package/dist/cjs/runtime/rsc/SlotContext.js +54 -0
- package/dist/cjs/runtime/rsc/client.js +93 -0
- package/dist/cjs/runtime/rsc/createRscProxy.js +141 -0
- package/dist/cjs/runtime/rsc/index.js +42 -0
- package/dist/cjs/runtime/rsc/payloadRouter.js +211 -0
- package/dist/cjs/runtime/rsc/server.js +246 -0
- package/dist/cjs/runtime/rsc/slotUsageSanitizer.js +65 -0
- package/dist/cjs/runtime/rsc/symbols.js +72 -0
- package/dist/cjs/runtime/types.js +18 -0
- package/dist/cjs/runtime/utils.js +142 -0
- package/dist/cjs/runtime.js +58 -0
- package/dist/esm/cli/index.mjs +201 -0
- package/dist/esm/cli/tanstackTypes.mjs +341 -0
- package/dist/esm/cli.mjs +2 -0
- package/dist/esm/rslib-runtime.mjs +18 -0
- package/dist/esm/runtime/DefaultNotFound.mjs +13 -0
- package/dist/esm/runtime/basepathRewrite.mjs +28 -0
- package/dist/esm/runtime/dataMutation.mjs +305 -0
- package/dist/esm/runtime/hooks.mjs +8 -0
- package/dist/esm/runtime/index.mjs +6 -0
- package/dist/esm/runtime/lifecycle.mjs +82 -0
- package/dist/esm/runtime/plugin.mjs +214 -0
- package/dist/esm/runtime/plugin.node.mjs +268 -0
- package/dist/esm/runtime/prefetchLink.mjs +18 -0
- package/dist/esm/runtime/routeTree.mjs +452 -0
- package/dist/esm/runtime/rsc/ClientSlot.mjs +19 -0
- package/dist/esm/runtime/rsc/CompositeComponent.mjs +41 -0
- package/dist/esm/runtime/rsc/ReplayableStream.mjs +104 -0
- package/dist/esm/runtime/rsc/RscNodeRenderer.mjs +31 -0
- package/dist/esm/runtime/rsc/SlotContext.mjs +17 -0
- package/dist/esm/runtime/rsc/client.mjs +53 -0
- package/dist/esm/runtime/rsc/createRscProxy.mjs +107 -0
- package/dist/esm/runtime/rsc/index.mjs +1 -0
- package/dist/esm/runtime/rsc/payloadRouter.mjs +162 -0
- package/dist/esm/runtime/rsc/server.mjs +200 -0
- package/dist/esm/runtime/rsc/slotUsageSanitizer.mjs +31 -0
- package/dist/esm/runtime/rsc/symbols.mjs +17 -0
- package/dist/esm/runtime/types.mjs +0 -0
- package/dist/esm/runtime/utils.mjs +89 -0
- package/dist/esm/runtime.mjs +1 -0
- package/dist/esm-node/cli/index.mjs +205 -0
- package/dist/esm-node/cli/tanstackTypes.mjs +342 -0
- package/dist/esm-node/cli.mjs +3 -0
- package/dist/esm-node/rslib-runtime.mjs +19 -0
- package/dist/esm-node/runtime/DefaultNotFound.mjs +14 -0
- package/dist/esm-node/runtime/basepathRewrite.mjs +29 -0
- package/dist/esm-node/runtime/dataMutation.mjs +306 -0
- package/dist/esm-node/runtime/hooks.mjs +9 -0
- package/dist/esm-node/runtime/index.mjs +7 -0
- package/dist/esm-node/runtime/lifecycle.mjs +83 -0
- package/dist/esm-node/runtime/plugin.mjs +215 -0
- package/dist/esm-node/runtime/plugin.node.mjs +269 -0
- package/dist/esm-node/runtime/prefetchLink.mjs +19 -0
- package/dist/esm-node/runtime/routeTree.mjs +453 -0
- package/dist/esm-node/runtime/rsc/ClientSlot.mjs +20 -0
- package/dist/esm-node/runtime/rsc/CompositeComponent.mjs +42 -0
- package/dist/esm-node/runtime/rsc/ReplayableStream.mjs +105 -0
- package/dist/esm-node/runtime/rsc/RscNodeRenderer.mjs +32 -0
- package/dist/esm-node/runtime/rsc/SlotContext.mjs +18 -0
- package/dist/esm-node/runtime/rsc/client.mjs +54 -0
- package/dist/esm-node/runtime/rsc/createRscProxy.mjs +108 -0
- package/dist/esm-node/runtime/rsc/index.mjs +2 -0
- package/dist/esm-node/runtime/rsc/payloadRouter.mjs +163 -0
- package/dist/esm-node/runtime/rsc/server.mjs +201 -0
- package/dist/esm-node/runtime/rsc/slotUsageSanitizer.mjs +32 -0
- package/dist/esm-node/runtime/rsc/symbols.mjs +18 -0
- package/dist/esm-node/runtime/types.mjs +1 -0
- package/dist/esm-node/runtime/utils.mjs +90 -0
- package/dist/esm-node/runtime.mjs +2 -0
- package/dist/types/cli/index.d.ts +20 -0
- package/dist/types/cli/tanstackTypes.d.ts +11 -0
- package/dist/types/cli.d.ts +2 -0
- package/dist/types/runtime/DefaultNotFound.d.ts +2 -0
- package/dist/types/runtime/basepathRewrite.d.ts +8 -0
- package/dist/types/runtime/dataMutation.d.ts +29 -0
- package/dist/types/runtime/hooks.d.ts +18 -0
- package/dist/types/runtime/index.d.ts +9 -0
- package/dist/types/runtime/lifecycle.d.ts +22 -0
- package/dist/types/runtime/plugin.d.ts +17 -0
- package/dist/types/runtime/plugin.node.d.ts +17 -0
- package/dist/types/runtime/prefetchLink.d.ts +11 -0
- package/dist/types/runtime/routeTree.d.ts +11 -0
- package/dist/types/runtime/rsc/ClientSlot.d.ts +5 -0
- package/dist/types/runtime/rsc/CompositeComponent.d.ts +3 -0
- package/dist/types/runtime/rsc/ReplayableStream.d.ts +24 -0
- package/dist/types/runtime/rsc/RscNodeRenderer.d.ts +5 -0
- package/dist/types/runtime/rsc/SlotContext.d.ts +11 -0
- package/dist/types/runtime/rsc/client.d.ts +11 -0
- package/dist/types/runtime/rsc/createRscProxy.d.ts +7 -0
- package/dist/types/runtime/rsc/index.d.ts +2 -0
- package/dist/types/runtime/rsc/payloadRouter.d.ts +24 -0
- package/dist/types/runtime/rsc/server.d.ts +14 -0
- package/dist/types/runtime/rsc/slotUsageSanitizer.d.ts +2 -0
- package/dist/types/runtime/rsc/symbols.d.ts +46 -0
- package/dist/types/runtime/types.d.ts +68 -0
- package/dist/types/runtime/utils.d.ts +36 -0
- package/dist/types/runtime.d.ts +1 -0
- package/dist/types-direct/cli/index.d.ts +20 -0
- package/dist/types-direct/cli/tanstackTypes.d.ts +11 -0
- package/dist/types-direct/cli.d.ts +2 -0
- package/dist/types-direct/runtime/DefaultNotFound.d.ts +2 -0
- package/dist/types-direct/runtime/basepathRewrite.d.ts +8 -0
- package/dist/types-direct/runtime/dataMutation.d.ts +29 -0
- package/dist/types-direct/runtime/hooks.d.ts +18 -0
- package/dist/types-direct/runtime/index.d.ts +9 -0
- package/dist/types-direct/runtime/lifecycle.d.ts +22 -0
- package/dist/types-direct/runtime/plugin.d.ts +17 -0
- package/dist/types-direct/runtime/plugin.node.d.ts +17 -0
- package/dist/types-direct/runtime/prefetchLink.d.ts +11 -0
- package/dist/types-direct/runtime/routeTree.d.ts +11 -0
- package/dist/types-direct/runtime/rsc/ClientSlot.d.ts +5 -0
- package/dist/types-direct/runtime/rsc/CompositeComponent.d.ts +3 -0
- package/dist/types-direct/runtime/rsc/ReplayableStream.d.ts +24 -0
- package/dist/types-direct/runtime/rsc/RscNodeRenderer.d.ts +5 -0
- package/dist/types-direct/runtime/rsc/SlotContext.d.ts +11 -0
- package/dist/types-direct/runtime/rsc/client.d.ts +11 -0
- package/dist/types-direct/runtime/rsc/createRscProxy.d.ts +7 -0
- package/dist/types-direct/runtime/rsc/index.d.ts +2 -0
- package/dist/types-direct/runtime/rsc/payloadRouter.d.ts +24 -0
- package/dist/types-direct/runtime/rsc/server.d.ts +14 -0
- package/dist/types-direct/runtime/rsc/slotUsageSanitizer.d.ts +2 -0
- package/dist/types-direct/runtime/rsc/symbols.d.ts +46 -0
- package/dist/types-direct/runtime/types.d.ts +68 -0
- package/dist/types-direct/runtime/utils.d.ts +36 -0
- package/dist/types-direct/runtime.d.ts +1 -0
- package/package.json +126 -0
- package/rslib.config.mts +4 -0
- package/rstest.config.mts +43 -0
- package/src/cli/index.ts +388 -0
- package/src/cli/tanstackTypes.ts +503 -0
- package/src/cli.ts +2 -0
- package/src/runtime/DefaultNotFound.tsx +15 -0
- package/src/runtime/basepathRewrite.ts +59 -0
- package/src/runtime/dataMutation.tsx +517 -0
- package/src/runtime/hooks.ts +34 -0
- package/src/runtime/index.tsx +30 -0
- package/src/runtime/lifecycle.ts +150 -0
- package/src/runtime/plugin.node.tsx +534 -0
- package/src/runtime/plugin.tsx +395 -0
- package/src/runtime/prefetchLink.tsx +87 -0
- package/src/runtime/routeTree.ts +942 -0
- package/src/runtime/rsc/ClientSlot.tsx +25 -0
- package/src/runtime/rsc/CompositeComponent.tsx +65 -0
- package/src/runtime/rsc/ReplayableStream.ts +155 -0
- package/src/runtime/rsc/RscNodeRenderer.tsx +45 -0
- package/src/runtime/rsc/SlotContext.tsx +31 -0
- package/src/runtime/rsc/client.tsx +90 -0
- package/src/runtime/rsc/createRscProxy.tsx +189 -0
- package/src/runtime/rsc/index.ts +10 -0
- package/src/runtime/rsc/payloadRouter.ts +318 -0
- package/src/runtime/rsc/server.tsx +303 -0
- package/src/runtime/rsc/slotUsageSanitizer.ts +76 -0
- package/src/runtime/rsc/symbols.ts +106 -0
- package/src/runtime/ssr-shim.d.ts +12 -0
- package/src/runtime/types.ts +83 -0
- package/src/runtime/utils.tsx +161 -0
- package/src/runtime.ts +1 -0
- package/tests/router/cli.test.ts +386 -0
- package/tests/router/dataMutation.test.tsx +396 -0
- package/tests/router/prefetchLink.test.tsx +43 -0
- package/tests/router/routeTree.test.ts +502 -0
- package/tests/router/rsc.test.tsx +256 -0
- package/tests/router/tanstackTypes.test.ts +62 -0
- package/tsconfig.json +12 -0
- package/tsconfig.tsgo.json +6 -0
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
import type { AnyRouter } from '@tanstack/react-router';
|
|
2
|
+
import { useRouter } from '@tanstack/react-router';
|
|
3
|
+
import type React from 'react';
|
|
4
|
+
import { useCallback, useRef, useState } from 'react';
|
|
5
|
+
|
|
6
|
+
type SubmitTarget =
|
|
7
|
+
| HTMLFormElement
|
|
8
|
+
| FormData
|
|
9
|
+
| URLSearchParams
|
|
10
|
+
| Record<string, string | number | boolean | null | undefined>;
|
|
11
|
+
type SubmitterElement = HTMLButtonElement | HTMLInputElement;
|
|
12
|
+
|
|
13
|
+
export type SubmitOptions = {
|
|
14
|
+
action?: string;
|
|
15
|
+
method?: string;
|
|
16
|
+
encType?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type FetcherState = 'idle' | 'submitting' | 'loading';
|
|
20
|
+
|
|
21
|
+
export class RouteActionResponseError<TData = unknown> extends Error {
|
|
22
|
+
readonly response: Response;
|
|
23
|
+
readonly data: TData;
|
|
24
|
+
|
|
25
|
+
constructor(response: Response, data: TData) {
|
|
26
|
+
super(`Route action failed with status ${response.status}`);
|
|
27
|
+
this.name = 'RouteActionResponseError';
|
|
28
|
+
this.response = response;
|
|
29
|
+
this.data = data;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
type RouteAction = (args: {
|
|
34
|
+
request: Request;
|
|
35
|
+
params: Record<string, string>;
|
|
36
|
+
context?: unknown;
|
|
37
|
+
}) => Promise<unknown> | unknown;
|
|
38
|
+
|
|
39
|
+
type RouteLoader = (args: {
|
|
40
|
+
request: Request;
|
|
41
|
+
params: Record<string, string>;
|
|
42
|
+
context?: unknown;
|
|
43
|
+
}) => Promise<unknown> | unknown;
|
|
44
|
+
|
|
45
|
+
function formDataToUrlSearchParams(formData: FormData) {
|
|
46
|
+
const searchParams = new URLSearchParams();
|
|
47
|
+
formData.forEach((value, key) => {
|
|
48
|
+
if (typeof value === 'string') {
|
|
49
|
+
searchParams.append(key, value);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
return searchParams;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function formDataToTextPlain(formData: FormData) {
|
|
56
|
+
return Array.from(formData.entries())
|
|
57
|
+
.map(([key, value]) => `${key}=${String(value)}`)
|
|
58
|
+
.join('\n');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function toFormData(target: SubmitTarget): FormData {
|
|
62
|
+
if (target instanceof HTMLFormElement) {
|
|
63
|
+
return new FormData(target);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (target instanceof FormData) {
|
|
67
|
+
return target;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (target instanceof URLSearchParams) {
|
|
71
|
+
const formData = new FormData();
|
|
72
|
+
target.forEach((value, key) => {
|
|
73
|
+
formData.append(key, value);
|
|
74
|
+
});
|
|
75
|
+
return formData;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const formData = new FormData();
|
|
79
|
+
Object.entries(target).forEach(([key, value]) => {
|
|
80
|
+
if (typeof value === 'undefined' || value === null) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
formData.append(key, String(value));
|
|
84
|
+
});
|
|
85
|
+
return formData;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function getSubmitter(event: React.FormEvent<HTMLFormElement>) {
|
|
89
|
+
const nativeEvent = event.nativeEvent as SubmitEvent | undefined;
|
|
90
|
+
const submitter = nativeEvent?.submitter;
|
|
91
|
+
if (
|
|
92
|
+
submitter instanceof HTMLButtonElement ||
|
|
93
|
+
submitter instanceof HTMLInputElement
|
|
94
|
+
) {
|
|
95
|
+
return submitter;
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function createFormDataFromSubmit({
|
|
101
|
+
form,
|
|
102
|
+
submitter,
|
|
103
|
+
}: {
|
|
104
|
+
form: HTMLFormElement;
|
|
105
|
+
submitter: SubmitterElement | null;
|
|
106
|
+
}) {
|
|
107
|
+
if (submitter) {
|
|
108
|
+
try {
|
|
109
|
+
return new FormData(form, submitter);
|
|
110
|
+
} catch {}
|
|
111
|
+
}
|
|
112
|
+
return new FormData(form);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function resolveSubmitOptionsFromForm({
|
|
116
|
+
form,
|
|
117
|
+
submitter,
|
|
118
|
+
action,
|
|
119
|
+
method,
|
|
120
|
+
encType,
|
|
121
|
+
}: {
|
|
122
|
+
form: HTMLFormElement;
|
|
123
|
+
submitter: SubmitterElement | null;
|
|
124
|
+
action?: string;
|
|
125
|
+
method?: string;
|
|
126
|
+
encType?: string;
|
|
127
|
+
}): Required<SubmitOptions> {
|
|
128
|
+
const resolvedAction =
|
|
129
|
+
submitter?.getAttribute('formaction') ||
|
|
130
|
+
action ||
|
|
131
|
+
form.getAttribute('action') ||
|
|
132
|
+
'.';
|
|
133
|
+
const resolvedMethod = (
|
|
134
|
+
submitter?.getAttribute('formmethod') ||
|
|
135
|
+
method ||
|
|
136
|
+
form.getAttribute('method') ||
|
|
137
|
+
'get'
|
|
138
|
+
).toLowerCase();
|
|
139
|
+
const resolvedEncType =
|
|
140
|
+
submitter?.getAttribute('formenctype') ||
|
|
141
|
+
encType ||
|
|
142
|
+
form.getAttribute('enctype') ||
|
|
143
|
+
'application/x-www-form-urlencoded';
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
action: resolvedAction,
|
|
147
|
+
method: resolvedMethod,
|
|
148
|
+
encType: resolvedEncType,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function resolveRouteHandlers(router: AnyRouter, actionTo: string) {
|
|
153
|
+
const builtLocation = router.buildLocation({
|
|
154
|
+
to: actionTo as any,
|
|
155
|
+
} as any);
|
|
156
|
+
const href = router.getParsedLocationHref(builtLocation as any);
|
|
157
|
+
const matchedRoutes = router.getMatchedRoutes(
|
|
158
|
+
(builtLocation as any).pathname,
|
|
159
|
+
);
|
|
160
|
+
const routeStaticData = matchedRoutes.foundRoute?.options?.staticData as
|
|
161
|
+
| Record<string, unknown>
|
|
162
|
+
| undefined;
|
|
163
|
+
const action = routeStaticData?.modernRouteAction as RouteAction | undefined;
|
|
164
|
+
const loader = routeStaticData?.modernRouteLoader as RouteLoader | undefined;
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
action,
|
|
168
|
+
loader,
|
|
169
|
+
href,
|
|
170
|
+
params: (matchedRoutes.routeParams || {}) as Record<string, string>,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function isRedirectResponse(value: unknown): value is Response {
|
|
175
|
+
if (!(value instanceof Response)) {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
return [301, 302, 303, 307, 308].includes(value.status);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function parseResponseData(response: Response) {
|
|
182
|
+
if (response.status === 204) {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const contentType = response.headers.get('Content-Type') || '';
|
|
187
|
+
if (contentType.includes('application/json')) {
|
|
188
|
+
return response.json();
|
|
189
|
+
}
|
|
190
|
+
return response.text();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function parseResponseResultOrThrow(response: Response) {
|
|
194
|
+
const parsed = await parseResponseData(response);
|
|
195
|
+
if (!response.ok) {
|
|
196
|
+
throw new RouteActionResponseError(response, parsed);
|
|
197
|
+
}
|
|
198
|
+
return parsed;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function submitRouteAction({
|
|
202
|
+
router,
|
|
203
|
+
target,
|
|
204
|
+
options = {},
|
|
205
|
+
isFetcher = false,
|
|
206
|
+
onInvalidateStart,
|
|
207
|
+
}: {
|
|
208
|
+
router: AnyRouter;
|
|
209
|
+
target: SubmitTarget;
|
|
210
|
+
options?: SubmitOptions;
|
|
211
|
+
isFetcher?: boolean;
|
|
212
|
+
onInvalidateStart?: () => void;
|
|
213
|
+
}) {
|
|
214
|
+
const method = (options.method || 'post').toLowerCase();
|
|
215
|
+
const encType = options.encType || 'application/x-www-form-urlencoded';
|
|
216
|
+
const actionTo = options.action || '.';
|
|
217
|
+
const formData = toFormData(target);
|
|
218
|
+
const resolved = resolveRouteHandlers(router, actionTo);
|
|
219
|
+
|
|
220
|
+
if (method === 'get') {
|
|
221
|
+
const search = formDataToUrlSearchParams(formData).toString();
|
|
222
|
+
const requestUrl = new URL(resolved.href, window.location.origin);
|
|
223
|
+
requestUrl.search = search;
|
|
224
|
+
|
|
225
|
+
if (isFetcher && resolved.loader) {
|
|
226
|
+
const result = await resolved.loader({
|
|
227
|
+
request: new Request(requestUrl, {
|
|
228
|
+
method: 'GET',
|
|
229
|
+
}),
|
|
230
|
+
params: resolved.params,
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
if (result instanceof Response) {
|
|
234
|
+
const redirectTo =
|
|
235
|
+
result.headers.get('X-Modernjs-Redirect') ||
|
|
236
|
+
result.headers.get('Location');
|
|
237
|
+
if (redirectTo || isRedirectResponse(result)) {
|
|
238
|
+
await router.navigate({
|
|
239
|
+
to: (redirectTo || '/') as any,
|
|
240
|
+
} as any);
|
|
241
|
+
return parseResponseData(result);
|
|
242
|
+
}
|
|
243
|
+
return parseResponseResultOrThrow(result);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return result;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
await router.navigate({
|
|
250
|
+
href: search ? `${resolved.href}?${search}` : resolved.href,
|
|
251
|
+
} as any);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (!resolved.action) {
|
|
256
|
+
throw new Error(`No route action found for "${actionTo}"`);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const headers = new Headers();
|
|
260
|
+
let body: BodyInit | null = null;
|
|
261
|
+
if (encType.includes('application/json')) {
|
|
262
|
+
headers.set('Content-Type', 'application/json');
|
|
263
|
+
body = JSON.stringify(
|
|
264
|
+
Object.fromEntries(formDataToUrlSearchParams(formData).entries()),
|
|
265
|
+
);
|
|
266
|
+
} else if (encType.includes('text/plain')) {
|
|
267
|
+
headers.set('Content-Type', 'text/plain;charset=UTF-8');
|
|
268
|
+
body = formDataToTextPlain(formData);
|
|
269
|
+
} else if (encType.includes('application/x-www-form-urlencoded')) {
|
|
270
|
+
headers.set(
|
|
271
|
+
'Content-Type',
|
|
272
|
+
'application/x-www-form-urlencoded;charset=UTF-8',
|
|
273
|
+
);
|
|
274
|
+
body = formDataToUrlSearchParams(formData);
|
|
275
|
+
} else {
|
|
276
|
+
body = formData;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const request = new Request(new URL(resolved.href, window.location.origin), {
|
|
280
|
+
method: method.toUpperCase(),
|
|
281
|
+
headers,
|
|
282
|
+
body,
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
const result = await resolved.action({
|
|
286
|
+
request,
|
|
287
|
+
params: resolved.params,
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
if (result instanceof Response) {
|
|
291
|
+
const redirectTo =
|
|
292
|
+
result.headers.get('X-Modernjs-Redirect') ||
|
|
293
|
+
result.headers.get('Location');
|
|
294
|
+
if (redirectTo || isRedirectResponse(result)) {
|
|
295
|
+
await router.navigate({
|
|
296
|
+
to: (redirectTo || '/') as any,
|
|
297
|
+
} as any);
|
|
298
|
+
return parseResponseData(result);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const parsed = isFetcher
|
|
302
|
+
? await parseResponseResultOrThrow(result)
|
|
303
|
+
: await parseResponseData(result);
|
|
304
|
+
onInvalidateStart?.();
|
|
305
|
+
await router.invalidate();
|
|
306
|
+
return parsed;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
onInvalidateStart?.();
|
|
310
|
+
await router.invalidate();
|
|
311
|
+
return result;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export type FormProps = Omit<
|
|
315
|
+
React.FormHTMLAttributes<HTMLFormElement>,
|
|
316
|
+
'onSubmit' | 'action'
|
|
317
|
+
> & {
|
|
318
|
+
action?: string;
|
|
319
|
+
onSubmit?: React.FormEventHandler<HTMLFormElement>;
|
|
320
|
+
reloadDocument?: boolean;
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
export function Form({
|
|
324
|
+
action,
|
|
325
|
+
method = 'get',
|
|
326
|
+
encType,
|
|
327
|
+
reloadDocument,
|
|
328
|
+
onSubmit,
|
|
329
|
+
...rest
|
|
330
|
+
}: FormProps) {
|
|
331
|
+
const router = useRouter();
|
|
332
|
+
|
|
333
|
+
const handleSubmit = useCallback(
|
|
334
|
+
async (event: React.FormEvent<HTMLFormElement>) => {
|
|
335
|
+
onSubmit?.(event);
|
|
336
|
+
if (event.defaultPrevented || reloadDocument) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
event.preventDefault();
|
|
341
|
+
const submitter = getSubmitter(event);
|
|
342
|
+
const formData = createFormDataFromSubmit({
|
|
343
|
+
form: event.currentTarget,
|
|
344
|
+
submitter,
|
|
345
|
+
});
|
|
346
|
+
const normalizedOptions = resolveSubmitOptionsFromForm({
|
|
347
|
+
form: event.currentTarget,
|
|
348
|
+
submitter,
|
|
349
|
+
action,
|
|
350
|
+
method,
|
|
351
|
+
encType,
|
|
352
|
+
});
|
|
353
|
+
await submitRouteAction({
|
|
354
|
+
router,
|
|
355
|
+
target: formData,
|
|
356
|
+
options: normalizedOptions,
|
|
357
|
+
});
|
|
358
|
+
},
|
|
359
|
+
[action, encType, method, onSubmit, reloadDocument, router],
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
return (
|
|
363
|
+
<form
|
|
364
|
+
{...rest}
|
|
365
|
+
action={action}
|
|
366
|
+
method={method}
|
|
367
|
+
encType={encType}
|
|
368
|
+
onSubmit={handleSubmit}
|
|
369
|
+
/>
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export type FetcherSubmitOptions = SubmitOptions;
|
|
374
|
+
|
|
375
|
+
export type Fetcher = {
|
|
376
|
+
state: FetcherState;
|
|
377
|
+
data: unknown;
|
|
378
|
+
error: unknown;
|
|
379
|
+
Form: React.ComponentType<FormProps>;
|
|
380
|
+
submit: (
|
|
381
|
+
target: SubmitTarget,
|
|
382
|
+
options?: FetcherSubmitOptions,
|
|
383
|
+
) => Promise<void>;
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
export function useFetcher(): Fetcher {
|
|
387
|
+
const router = useRouter();
|
|
388
|
+
const [state, setState] = useState<FetcherState>('idle');
|
|
389
|
+
const [data, setData] = useState<unknown>(undefined);
|
|
390
|
+
const [error, setError] = useState<unknown>(undefined);
|
|
391
|
+
const requestStatesRef = useRef<Map<number, Exclude<FetcherState, 'idle'>>>(
|
|
392
|
+
new Map(),
|
|
393
|
+
);
|
|
394
|
+
const requestIdRef = useRef(0);
|
|
395
|
+
|
|
396
|
+
const syncStateFromRequests = useCallback(() => {
|
|
397
|
+
let hasSubmitting = false;
|
|
398
|
+
let hasLoading = false;
|
|
399
|
+
|
|
400
|
+
requestStatesRef.current.forEach(requestState => {
|
|
401
|
+
if (requestState === 'submitting') {
|
|
402
|
+
hasSubmitting = true;
|
|
403
|
+
} else if (requestState === 'loading') {
|
|
404
|
+
hasLoading = true;
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
if (hasSubmitting) {
|
|
409
|
+
setState('submitting');
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
if (hasLoading) {
|
|
413
|
+
setState('loading');
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
setState('idle');
|
|
417
|
+
}, []);
|
|
418
|
+
|
|
419
|
+
const setRequestState = useCallback(
|
|
420
|
+
(requestId: number, requestState: Exclude<FetcherState, 'idle'>) => {
|
|
421
|
+
requestStatesRef.current.set(requestId, requestState);
|
|
422
|
+
syncStateFromRequests();
|
|
423
|
+
},
|
|
424
|
+
[syncStateFromRequests],
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
const clearRequestState = useCallback(
|
|
428
|
+
(requestId: number) => {
|
|
429
|
+
requestStatesRef.current.delete(requestId);
|
|
430
|
+
syncStateFromRequests();
|
|
431
|
+
},
|
|
432
|
+
[syncStateFromRequests],
|
|
433
|
+
);
|
|
434
|
+
|
|
435
|
+
const submit = useCallback(
|
|
436
|
+
async (target: SubmitTarget, options?: FetcherSubmitOptions) => {
|
|
437
|
+
setError(undefined);
|
|
438
|
+
const requestId = ++requestIdRef.current;
|
|
439
|
+
const normalizedMethod = (options?.method || 'post').toLowerCase();
|
|
440
|
+
const isLoaderSubmit = normalizedMethod === 'get';
|
|
441
|
+
setRequestState(requestId, isLoaderSubmit ? 'loading' : 'submitting');
|
|
442
|
+
|
|
443
|
+
try {
|
|
444
|
+
const result = await submitRouteAction({
|
|
445
|
+
router,
|
|
446
|
+
target,
|
|
447
|
+
options,
|
|
448
|
+
isFetcher: true,
|
|
449
|
+
onInvalidateStart: () => {
|
|
450
|
+
if (!isLoaderSubmit) {
|
|
451
|
+
setRequestState(requestId, 'loading');
|
|
452
|
+
}
|
|
453
|
+
},
|
|
454
|
+
});
|
|
455
|
+
setData(result);
|
|
456
|
+
} catch (err) {
|
|
457
|
+
setError(err);
|
|
458
|
+
throw err;
|
|
459
|
+
} finally {
|
|
460
|
+
clearRequestState(requestId);
|
|
461
|
+
}
|
|
462
|
+
},
|
|
463
|
+
[clearRequestState, router, setRequestState],
|
|
464
|
+
);
|
|
465
|
+
|
|
466
|
+
const FetcherForm = useCallback(
|
|
467
|
+
({
|
|
468
|
+
action,
|
|
469
|
+
method = 'get',
|
|
470
|
+
encType,
|
|
471
|
+
reloadDocument,
|
|
472
|
+
onSubmit,
|
|
473
|
+
...rest
|
|
474
|
+
}: FormProps) => {
|
|
475
|
+
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
|
476
|
+
onSubmit?.(event);
|
|
477
|
+
if (event.defaultPrevented || reloadDocument) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
event.preventDefault();
|
|
482
|
+
const submitter = getSubmitter(event);
|
|
483
|
+
const formData = createFormDataFromSubmit({
|
|
484
|
+
form: event.currentTarget,
|
|
485
|
+
submitter,
|
|
486
|
+
});
|
|
487
|
+
const normalizedOptions = resolveSubmitOptionsFromForm({
|
|
488
|
+
form: event.currentTarget,
|
|
489
|
+
submitter,
|
|
490
|
+
action,
|
|
491
|
+
method,
|
|
492
|
+
encType,
|
|
493
|
+
});
|
|
494
|
+
await submit(formData, normalizedOptions);
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
return (
|
|
498
|
+
<form
|
|
499
|
+
{...rest}
|
|
500
|
+
action={action}
|
|
501
|
+
method={method}
|
|
502
|
+
encType={encType}
|
|
503
|
+
onSubmit={handleSubmit}
|
|
504
|
+
/>
|
|
505
|
+
);
|
|
506
|
+
},
|
|
507
|
+
[submit],
|
|
508
|
+
);
|
|
509
|
+
|
|
510
|
+
return {
|
|
511
|
+
state,
|
|
512
|
+
data,
|
|
513
|
+
error,
|
|
514
|
+
Form: FetcherForm,
|
|
515
|
+
submit,
|
|
516
|
+
};
|
|
517
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createSyncHook } from '@modern-js/plugin';
|
|
2
|
+
import type { TRuntimeContext } from '@modern-js/runtime/context';
|
|
3
|
+
import type { RouteObject } from '@modern-js/runtime-utils/router';
|
|
4
|
+
import type { RouterLifecycleContext } from './lifecycle';
|
|
5
|
+
|
|
6
|
+
const modifyRoutes = createSyncHook<(routes: RouteObject[]) => RouteObject[]>();
|
|
7
|
+
const onBeforeCreateRoutes =
|
|
8
|
+
createSyncHook<(context: TRuntimeContext) => void>();
|
|
9
|
+
const onBeforeCreateRouter =
|
|
10
|
+
createSyncHook<(context: RouterLifecycleContext) => void>();
|
|
11
|
+
const onAfterCreateRouter =
|
|
12
|
+
createSyncHook<(context: RouterLifecycleContext) => void>();
|
|
13
|
+
const onBeforeHydrateRouter =
|
|
14
|
+
createSyncHook<(context: RouterLifecycleContext) => void>();
|
|
15
|
+
const onAfterHydrateRouter =
|
|
16
|
+
createSyncHook<(context: RouterLifecycleContext) => void>();
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
modifyRoutes,
|
|
20
|
+
onAfterCreateRouter,
|
|
21
|
+
onAfterHydrateRouter,
|
|
22
|
+
onBeforeCreateRouter,
|
|
23
|
+
onBeforeCreateRoutes,
|
|
24
|
+
onBeforeHydrateRouter,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type RouterExtendsHooks = {
|
|
28
|
+
modifyRoutes: typeof modifyRoutes;
|
|
29
|
+
onBeforeCreateRoutes: typeof onBeforeCreateRoutes;
|
|
30
|
+
onBeforeCreateRouter: typeof onBeforeCreateRouter;
|
|
31
|
+
onAfterCreateRouter: typeof onAfterCreateRouter;
|
|
32
|
+
onBeforeHydrateRouter: typeof onBeforeHydrateRouter;
|
|
33
|
+
onAfterHydrateRouter: typeof onAfterHydrateRouter;
|
|
34
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export * from '@tanstack/react-router';
|
|
2
|
+
export { useMatch } from '@tanstack/react-router';
|
|
3
|
+
export type {
|
|
4
|
+
Fetcher,
|
|
5
|
+
FetcherState,
|
|
6
|
+
FetcherSubmitOptions,
|
|
7
|
+
FormProps,
|
|
8
|
+
SubmitOptions,
|
|
9
|
+
} from './dataMutation';
|
|
10
|
+
export {
|
|
11
|
+
Form,
|
|
12
|
+
RouteActionResponseError,
|
|
13
|
+
useFetcher,
|
|
14
|
+
} from './dataMutation';
|
|
15
|
+
export {
|
|
16
|
+
tanstackRouterPlugin,
|
|
17
|
+
tanstackRouterPlugin as default,
|
|
18
|
+
} from './plugin';
|
|
19
|
+
export type {
|
|
20
|
+
LinkProps,
|
|
21
|
+
NavLinkProps,
|
|
22
|
+
PrefetchBehavior,
|
|
23
|
+
} from './prefetchLink';
|
|
24
|
+
export { Link, NavLink } from './prefetchLink';
|
|
25
|
+
export type {
|
|
26
|
+
AnyCompositeComponent,
|
|
27
|
+
AnyRenderableServerComponent,
|
|
28
|
+
CompositeComponentProps,
|
|
29
|
+
} from './rsc/client';
|
|
30
|
+
export { CompositeComponent } from './rsc/client';
|