@void/react 1.2.1 → 1.3.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/dist/index.d.mts +3 -1
- package/dist/index.mjs +31 -11
- package/dist/plugin.mjs +26 -36
- package/dist/runtime/pages-client.d.mts +2 -1
- package/dist/runtime/pages-client.mjs +18 -3
- package/package.json +3 -3
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AnchorHTMLAttributes, ButtonHTMLAttributes, Context, FormHTMLAttributes, JSX, ReactElement, ReactNode } from "react";
|
|
2
|
-
import { ActionClient, FormMethod, FormModel, NavigationState, VoidActionError, VoidRouter } from "void/pages-client";
|
|
2
|
+
import { ActionClient, FormMethod, FormModel, FormResetOptions, NavigationState, VoidActionError, VoidRouter } from "void/pages-client";
|
|
3
3
|
import { CloudContextVariables, InferProps } from "void";
|
|
4
4
|
import { ActionFormOptions, ActionUrl, ResolveActionBody } from "void/routes";
|
|
5
5
|
//#region src/runtime/form.d.ts
|
|
@@ -54,7 +54,9 @@ export declare function useForm<U extends ActionUrl & string>(url: U, defaults:
|
|
|
54
54
|
put(formData: FormData): void;
|
|
55
55
|
patch(formData: FormData): void;
|
|
56
56
|
delete(formData: FormData): void;
|
|
57
|
+
reset(options: FormResetOptions, ...fields: ReadonlyArray<keyof ResolveActionBody<U> & string>): void;
|
|
57
58
|
reset(...fields: ReadonlyArray<keyof ResolveActionBody<U> & string>): void;
|
|
59
|
+
setError(field: keyof ResolveActionBody<U> & string, message: string): void;
|
|
58
60
|
clearErrors(...fields: ReadonlyArray<keyof ResolveActionBody<U> & string>): void;
|
|
59
61
|
clearError(): void;
|
|
60
62
|
};
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { a as RouterContext, i as ParamsContext, n as FrontmatterContext, o as SharedContext, r as NavigationContext, t as ErrorsContext } from "./context-D2ctU4yT.mjs";
|
|
2
2
|
import { n as setActionRouter, t as action } from "./action-4EvQppia.mjs";
|
|
3
3
|
import { useActionState, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
-
import { VoidActionError, categorizeActionError, formErrorMessage, formRetryMessage, formSubmissionBlocked, getValidationErrors, interpolateRouteParams, isAbortError, isEqualFormValue, isModifiedEvent, mergeDataIntoHref, scheduleFormRetry, ssrProxy, submitAction } from "void/pages-client";
|
|
4
|
+
import { VoidActionError, categorizeActionError, cloneFormValue, formErrorMessage, formRetryMessage, formSubmissionBlocked, getValidationErrors, interpolateRouteParams, isAbortError, isEqualFormValue, isModifiedEvent, mergeDataIntoHref, parseFormResetArgs, scheduleFormRetry, ssrProxy, submitAction } from "void/pages-client";
|
|
5
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
//#region src/runtime/form.tsx
|
|
7
7
|
function Form({ form, method = "post", renderError, children, ...props }) {
|
|
@@ -231,13 +231,14 @@ function useShared() {
|
|
|
231
231
|
//#region src/runtime/use-form.ts
|
|
232
232
|
function useForm(url, defaults, options) {
|
|
233
233
|
const router = useContext(RouterContext);
|
|
234
|
-
const [data, setDataState] = useState(() => (
|
|
235
|
-
const [defaultsState, setDefaultsState] = useState(() => (
|
|
234
|
+
const [data, setDataState] = useState(() => cloneFormValue(defaults));
|
|
235
|
+
const [defaultsState, setDefaultsState] = useState(() => cloneFormValue(defaults));
|
|
236
236
|
const [errors, setErrors] = useState({});
|
|
237
|
-
const [error,
|
|
237
|
+
const [error, setActionError] = useState(null);
|
|
238
238
|
const [wasSuccessful, setWasSuccessful] = useState(false);
|
|
239
239
|
const [recentlySuccessful, setRecentlySuccessful] = useState(false);
|
|
240
240
|
const defaultsRef = useRef(defaultsState);
|
|
241
|
+
const initialRef = useRef(defaultsState);
|
|
241
242
|
const dataRef = useRef(data);
|
|
242
243
|
const successTimeoutRef = useRef(null);
|
|
243
244
|
const hasChanges = useMemo(() => {
|
|
@@ -249,12 +250,20 @@ function useForm(url, defaults, options) {
|
|
|
249
250
|
[field]: value
|
|
250
251
|
};
|
|
251
252
|
setDataState(dataRef.current);
|
|
253
|
+
setErrors((prev) => {
|
|
254
|
+
if (!(field in prev)) return prev;
|
|
255
|
+
const next = { ...prev };
|
|
256
|
+
delete next[field];
|
|
257
|
+
return next;
|
|
258
|
+
});
|
|
252
259
|
}, []);
|
|
253
|
-
const reset = useCallback((...
|
|
254
|
-
|
|
260
|
+
const reset = useCallback((...args) => {
|
|
261
|
+
const { initial, fields } = parseFormResetArgs(args);
|
|
262
|
+
const source = initial ? initialRef.current : defaultsRef.current;
|
|
263
|
+
if (fields.length === 0) dataRef.current = cloneFormValue(source);
|
|
255
264
|
else {
|
|
256
265
|
const next = { ...dataRef.current };
|
|
257
|
-
for (const field of fields) next[field] =
|
|
266
|
+
for (const field of fields) next[field] = cloneFormValue(source[field]);
|
|
258
267
|
dataRef.current = next;
|
|
259
268
|
}
|
|
260
269
|
setDataState(dataRef.current);
|
|
@@ -267,8 +276,14 @@ function useForm(url, defaults, options) {
|
|
|
267
276
|
return next;
|
|
268
277
|
});
|
|
269
278
|
}, []);
|
|
279
|
+
const setError = useCallback((field, message) => {
|
|
280
|
+
setErrors((prev) => ({
|
|
281
|
+
...prev,
|
|
282
|
+
[field]: message
|
|
283
|
+
}));
|
|
284
|
+
}, []);
|
|
270
285
|
const clearError = useCallback(() => {
|
|
271
|
-
|
|
286
|
+
setActionError(null);
|
|
272
287
|
}, []);
|
|
273
288
|
const [, dispatchAction, pending] = useActionState(async (previousSuccessVersion, payload) => {
|
|
274
289
|
if (!router) throw new Error("useForm(): requires the Void Router.");
|
|
@@ -280,7 +295,7 @@ function useForm(url, defaults, options) {
|
|
|
280
295
|
successTimeoutRef.current = null;
|
|
281
296
|
}
|
|
282
297
|
setErrors({});
|
|
283
|
-
|
|
298
|
+
setActionError(null);
|
|
284
299
|
let result;
|
|
285
300
|
try {
|
|
286
301
|
result = await submitAction(router, interpolateRouteParams(url, options?.params), {
|
|
@@ -295,10 +310,14 @@ function useForm(url, defaults, options) {
|
|
|
295
310
|
if (!result.ok) {
|
|
296
311
|
const validationErrors = getValidationErrors(result.error.body);
|
|
297
312
|
if (validationErrors) setErrors(validationErrors);
|
|
298
|
-
else
|
|
313
|
+
else setActionError(result.error);
|
|
299
314
|
return previousSuccessVersion;
|
|
300
315
|
}
|
|
301
|
-
|
|
316
|
+
if (options?.resetOnSuccess) {
|
|
317
|
+
dataRef.current = cloneFormValue(initialRef.current);
|
|
318
|
+
setDataState(dataRef.current);
|
|
319
|
+
}
|
|
320
|
+
const nextDefaults = cloneFormValue(dataRef.current);
|
|
302
321
|
defaultsRef.current = nextDefaults;
|
|
303
322
|
setDefaultsState(nextDefaults);
|
|
304
323
|
setWasSuccessful(true);
|
|
@@ -334,6 +353,7 @@ function useForm(url, defaults, options) {
|
|
|
334
353
|
patch: useMemo(() => dispatchMethodAction("PATCH"), [dispatchMethodAction]),
|
|
335
354
|
delete: useMemo(() => dispatchMethodAction("DELETE"), [dispatchMethodAction]),
|
|
336
355
|
reset,
|
|
356
|
+
setError,
|
|
337
357
|
clearErrors,
|
|
338
358
|
clearError
|
|
339
359
|
};
|
package/dist/plugin.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import react from "@vitejs/plugin-react";
|
|
2
2
|
import { generateComponentManifest, resolveEffectiveLayoutChain, resolvePagesDir, resolveStaticPageDataBuildOptions, scanPages } from "void/pages";
|
|
3
|
-
import { existsSync, readFileSync
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
4
|
import nodePath from "node:path";
|
|
5
5
|
import { normalizePath } from "vite";
|
|
6
6
|
import { ISLANDS_CLIENT_ENTRY_ID, ISLANDS_RENDERER_ID, computePagesNeedingClientJS, editIslandSource, findIslandImports, findStaticCssImports, hasIslandPages, islandStylesPlugin, recordIslandStyles, recordedIslandStyles, resolveSpecifier, rewriteCssImportForVirtualModule, watchPagesForRescan } from "void/pages-islands-plugin";
|
|
@@ -68,21 +68,6 @@ const REACT_REFRESH_PREAMBLE = `
|
|
|
68
68
|
window.$RefreshSig$ = () => (type) => type
|
|
69
69
|
window.__vite_plugin_react_preamble_installed__ = true
|
|
70
70
|
<\/script>`;
|
|
71
|
-
/** Check if the pages directory has any non-island page files (.tsx/.jsx without .island.). */
|
|
72
|
-
function hasNonIslandPages(dir) {
|
|
73
|
-
try {
|
|
74
|
-
for (const entry of readdirSync(dir, {
|
|
75
|
-
withFileTypes: true,
|
|
76
|
-
recursive: true
|
|
77
|
-
})) {
|
|
78
|
-
if (!entry.isFile()) continue;
|
|
79
|
-
const name = entry.name;
|
|
80
|
-
if (name.startsWith("_") || name.startsWith("layout.")) continue;
|
|
81
|
-
if (/\.(tsx|jsx)$/.test(name) && !name.includes(".island.")) return true;
|
|
82
|
-
}
|
|
83
|
-
} catch {}
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
71
|
function pagesPlugin(pagesDir, options) {
|
|
87
72
|
let pageScan = null;
|
|
88
73
|
let isDev = false;
|
|
@@ -92,7 +77,10 @@ function pagesPlugin(pagesDir, options) {
|
|
|
92
77
|
const pagesOptimizeEntries = `${relative(root, pagesDir)}/**/*.{js,jsx,ts,tsx}`;
|
|
93
78
|
let pendingRescan = null;
|
|
94
79
|
const rescanPages = async () => {
|
|
95
|
-
pageScan = existsSync(pagesDir) ? await scanPages(root, {
|
|
80
|
+
pageScan = existsSync(pagesDir) ? await scanPages(root, {
|
|
81
|
+
pagesDir,
|
|
82
|
+
environment: isDev ? "dev" : "prod"
|
|
83
|
+
}) : null;
|
|
96
84
|
};
|
|
97
85
|
const runRescan = () => {
|
|
98
86
|
pendingRescan ??= rescanPages().finally(() => {
|
|
@@ -155,25 +143,24 @@ function pagesPlugin(pagesDir, options) {
|
|
|
155
143
|
configResolved(config) {
|
|
156
144
|
viteConfig = config;
|
|
157
145
|
},
|
|
158
|
-
config(_, env) {
|
|
146
|
+
async config(_, env) {
|
|
147
|
+
isDev = env.command === "serve";
|
|
159
148
|
if (!existsSync(pagesDir)) return;
|
|
160
|
-
if (
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
if (!hasNonIslandPages(pagesDir)) return;
|
|
149
|
+
if (isDev) return {
|
|
150
|
+
resolve: { dedupe: REACT_DEDUPE },
|
|
151
|
+
optimizeDeps: {
|
|
152
|
+
exclude: ["@void/react"],
|
|
153
|
+
include: CLIENT_OPTIMIZE_DEPS
|
|
154
|
+
},
|
|
155
|
+
ssr: { optimizeDeps: { include: [...CLIENT_OPTIMIZE_DEPS, "react-dom/server"] } },
|
|
156
|
+
environments: { [WORKER_ENVIRONMENT_NAME]: { optimizeDeps: {
|
|
157
|
+
exclude: ["@void/react"],
|
|
158
|
+
entries: [pagesOptimizeEntries],
|
|
159
|
+
include: WORKER_OPTIMIZE_DEPS
|
|
160
|
+
} } }
|
|
161
|
+
};
|
|
162
|
+
await runRescan();
|
|
163
|
+
if (!pageScan?.pages.some((page) => !page.island)) return;
|
|
177
164
|
return {
|
|
178
165
|
resolve: { dedupe: REACT_DEDUPE },
|
|
179
166
|
build: {
|
|
@@ -272,7 +259,10 @@ function islandsPlugin(pagesDir, options) {
|
|
|
272
259
|
},
|
|
273
260
|
async buildStart() {
|
|
274
261
|
if (existsSync(pagesDir)) {
|
|
275
|
-
pageScan = await scanPages(root, {
|
|
262
|
+
pageScan = await scanPages(root, {
|
|
263
|
+
pagesDir,
|
|
264
|
+
environment: isDev ? "dev" : "prod"
|
|
265
|
+
});
|
|
276
266
|
const filesToScan = [
|
|
277
267
|
...pageScan.pages.map((page) => ({
|
|
278
268
|
filePath: page.componentPath,
|
|
@@ -12,7 +12,8 @@ export type ReactPagesConfig = Readonly<{
|
|
|
12
12
|
routeMeta: Record<string, {
|
|
13
13
|
island?: boolean;
|
|
14
14
|
}>;
|
|
15
|
-
|
|
15
|
+
/** Page id for a decoded pathname. */
|
|
16
|
+
matchRoute(path: string): string | null;
|
|
16
17
|
staticPageData: boolean;
|
|
17
18
|
staticPageDataFastPath: boolean;
|
|
18
19
|
viewTransitions: boolean;
|
|
@@ -249,6 +249,7 @@ function App({ engine, router, viewTransitions, navigationFallback: NavigationFa
|
|
|
249
249
|
prerenderedUrl = null;
|
|
250
250
|
React.startTransition(() => setHydrated(true));
|
|
251
251
|
}
|
|
252
|
+
return true;
|
|
252
253
|
},
|
|
253
254
|
dispose() {}
|
|
254
255
|
}), [engine, initialPageData]);
|
|
@@ -263,7 +264,7 @@ function App({ engine, router, viewTransitions, navigationFallback: NavigationFa
|
|
|
263
264
|
const finishNavigation = useCallback((resource, result) => {
|
|
264
265
|
if (committedResources.has(resource.id)) return;
|
|
265
266
|
committedResources.add(resource.id);
|
|
266
|
-
resource.commit().then(() => {
|
|
267
|
+
resource.commit().then((committed) => {
|
|
267
268
|
const resolveViewTransition = viewTransitionWaiters.get(resource.id);
|
|
268
269
|
if (resolveViewTransition) {
|
|
269
270
|
viewTransitionWaiters.delete(resource.id);
|
|
@@ -272,8 +273,11 @@ function App({ engine, router, viewTransitions, navigationFallback: NavigationFa
|
|
|
272
273
|
const visitSettler = pendingVisitSettlers.get(resource.id);
|
|
273
274
|
if (visitSettler) {
|
|
274
275
|
pendingVisitSettlers.delete(resource.id);
|
|
275
|
-
visitSettler.resolve(result.pageData);
|
|
276
|
+
if (committed) visitSettler.resolve(result.pageData);
|
|
277
|
+
else if (resource.method.toUpperCase() === "GET") visitSettler.resolve(void 0);
|
|
278
|
+
else visitSettler.reject(new DOMException("Navigation aborted", "AbortError"));
|
|
276
279
|
}
|
|
280
|
+
if (!committed) return;
|
|
277
281
|
if (activePendingNavigationId === resource.id) activePendingNavigationId = null;
|
|
278
282
|
dispatch({
|
|
279
283
|
type: "finish",
|
|
@@ -281,11 +285,22 @@ function App({ engine, router, viewTransitions, navigationFallback: NavigationFa
|
|
|
281
285
|
location: currentUrl()
|
|
282
286
|
});
|
|
283
287
|
}).catch((error) => {
|
|
288
|
+
const resolveViewTransition = viewTransitionWaiters.get(resource.id);
|
|
289
|
+
if (resolveViewTransition) {
|
|
290
|
+
viewTransitionWaiters.delete(resource.id);
|
|
291
|
+
resolveViewTransition();
|
|
292
|
+
}
|
|
284
293
|
const visitSettler = pendingVisitSettlers.get(resource.id);
|
|
285
294
|
if (visitSettler) {
|
|
286
295
|
pendingVisitSettlers.delete(resource.id);
|
|
287
296
|
visitSettler.reject(error);
|
|
288
|
-
}
|
|
297
|
+
} else reportError(error);
|
|
298
|
+
if (activePendingNavigationId === resource.id) activePendingNavigationId = null;
|
|
299
|
+
dispatch({
|
|
300
|
+
type: "fail",
|
|
301
|
+
resource,
|
|
302
|
+
location: currentUrl()
|
|
303
|
+
});
|
|
289
304
|
});
|
|
290
305
|
}, []);
|
|
291
306
|
const schedule = useCallback((url, options = {}) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@void/react",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "React pages, server rendering, forms, and navigation for Void.",
|
|
5
5
|
"homepage": "https://void.cloud",
|
|
6
6
|
"bugs": {
|
|
@@ -49,13 +49,13 @@
|
|
|
49
49
|
"react-dom": "^19.3.1",
|
|
50
50
|
"vite": "npm:@voidzero-dev/vite-plus-core@1.3.0",
|
|
51
51
|
"vite-plus": "1.3.0",
|
|
52
|
-
"void": "1.
|
|
52
|
+
"void": "1.3.0"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"react": "^19.0.0",
|
|
56
56
|
"react-dom": "^19.0.0",
|
|
57
57
|
"vite": "^8.0.0",
|
|
58
|
-
"void": "1.
|
|
58
|
+
"void": "1.3.0"
|
|
59
59
|
},
|
|
60
60
|
"scripts": {
|
|
61
61
|
"build": "vp pack",
|