@void/react 0.0.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/README.md +216 -0
- package/dist/action-BFWtavbf.mjs +29 -0
- package/dist/context-BCeFV8Jy.mjs +9 -0
- package/dist/index-6hxGVqsE.d.mts +125 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +4 -0
- package/dist/plugin.d.mts +16 -0
- package/dist/plugin.mjs +553 -0
- package/dist/runtime/index.d.mts +2 -0
- package/dist/runtime/index.mjs +4 -0
- package/dist/runtime/pages-client.d.mts +27 -0
- package/dist/runtime/pages-client.mjs +417 -0
- package/dist/runtime/pages-server.d.mts +27 -0
- package/dist/runtime/pages-server.mjs +112 -0
- package/dist/runtime/prefetch.d.mts +2 -0
- package/dist/runtime/prefetch.mjs +2 -0
- package/dist/runtime-C24S_Dlg.mjs +492 -0
- package/package.json +62 -0
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
import { i as SharedContext, n as NavigationContext, r as RouterContext } from "./context-BCeFV8Jy.mjs";
|
|
2
|
+
import "./action-BFWtavbf.mjs";
|
|
3
|
+
import { useActionState, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { VoidActionError, categorizeActionError, isAbortError, isEqualFormValue, ssrProxy, submitAction } from "void/pages-client";
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
//#region src/runtime/link.tsx
|
|
7
|
+
function appendQueryValue(params, key, value) {
|
|
8
|
+
if (value === null || value === void 0) return false;
|
|
9
|
+
if (value instanceof Date) {
|
|
10
|
+
params.append(key, value.toISOString());
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
if (Array.isArray(value)) {
|
|
14
|
+
let appended = false;
|
|
15
|
+
for (const item of value) appended = appendQueryValue(params, key, item) || appended;
|
|
16
|
+
return appended;
|
|
17
|
+
}
|
|
18
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
19
|
+
params.append(key, String(value));
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
throw new Error(`Link: GET data only supports primitive values and arrays. Remove nested data for '${key}'.`);
|
|
23
|
+
}
|
|
24
|
+
function mergeDataIntoHref(href, data) {
|
|
25
|
+
if (!data) return href;
|
|
26
|
+
const hashIndex = href.indexOf("#");
|
|
27
|
+
const beforeHash = hashIndex === -1 ? href : href.slice(0, hashIndex);
|
|
28
|
+
const hash = hashIndex === -1 ? "" : href.slice(hashIndex);
|
|
29
|
+
const searchIndex = beforeHash.indexOf("?");
|
|
30
|
+
const path = searchIndex === -1 ? beforeHash : beforeHash.slice(0, searchIndex);
|
|
31
|
+
const search = searchIndex === -1 ? "" : beforeHash.slice(searchIndex + 1);
|
|
32
|
+
const params = new URLSearchParams(search);
|
|
33
|
+
let appended = false;
|
|
34
|
+
for (const [key, value] of Object.entries(data)) appended = appendQueryValue(params, key, value) || appended;
|
|
35
|
+
if (!appended) return href;
|
|
36
|
+
const query = params.toString();
|
|
37
|
+
return `${path}${query ? `?${query}` : ""}${hash}`;
|
|
38
|
+
}
|
|
39
|
+
function hasPrefetch(prefetch) {
|
|
40
|
+
return prefetch !== false;
|
|
41
|
+
}
|
|
42
|
+
function isModifiedEvent(e) {
|
|
43
|
+
const target = e.currentTarget.getAttribute("target");
|
|
44
|
+
return target !== null && target !== "_self" || e.ctrlKey || e.metaKey || e.shiftKey || e.altKey || e.button !== 0;
|
|
45
|
+
}
|
|
46
|
+
function Link({ href, method = "GET", data, preserveScroll = false, preserveState = false, replace = false, reloadDocument = false, viewTransition, prefetch: prefetchProp = false, cacheFor, onNavigate, children, style, onClick: onClickProp, onMouseEnter: onMouseEnterProp, onMouseLeave: onMouseLeaveProp, onFocus: onFocusProp, onMouseDown: onMouseDownProp, onTouchStart: onTouchStartProp, ...rest }) {
|
|
47
|
+
const router = useContext(RouterContext);
|
|
48
|
+
const elementRef = useRef(null);
|
|
49
|
+
const hoverTimerRef = useRef(null);
|
|
50
|
+
const normalizedMethod = useMemo(() => method.toUpperCase(), [method]);
|
|
51
|
+
const isGet = normalizedMethod === "GET";
|
|
52
|
+
const hrefWithData = useMemo(() => isGet ? mergeDataIntoHref(href, data) : href, [
|
|
53
|
+
data,
|
|
54
|
+
href,
|
|
55
|
+
isGet
|
|
56
|
+
]);
|
|
57
|
+
const strategies = useMemo(() => {
|
|
58
|
+
if (prefetchProp === false || reloadDocument) return [];
|
|
59
|
+
if (prefetchProp === true) return ["hover"];
|
|
60
|
+
if (typeof prefetchProp === "string") return [prefetchProp];
|
|
61
|
+
return prefetchProp;
|
|
62
|
+
}, [prefetchProp, reloadDocument]);
|
|
63
|
+
const hoverDelay = router?._hoverDelay ?? 75;
|
|
64
|
+
if (hasPrefetch(prefetchProp) && !isGet) throw new Error("Link: prefetch only supports GET links. Remove `prefetch` or use method=\"GET\".");
|
|
65
|
+
if (reloadDocument && !isGet) throw new Error("Link: reloadDocument only supports GET links.");
|
|
66
|
+
const doPrefetch = useCallback(() => {
|
|
67
|
+
if (!router?.prefetch) return;
|
|
68
|
+
if (hrefWithData === window.location.pathname + window.location.search) return;
|
|
69
|
+
const options = {};
|
|
70
|
+
if (cacheFor !== void 0) options.cacheFor = cacheFor;
|
|
71
|
+
if (normalizedMethod) options.method = normalizedMethod;
|
|
72
|
+
router.prefetch(hrefWithData, options);
|
|
73
|
+
}, [
|
|
74
|
+
cacheFor,
|
|
75
|
+
hrefWithData,
|
|
76
|
+
normalizedMethod,
|
|
77
|
+
router
|
|
78
|
+
]);
|
|
79
|
+
const shouldHandleClick = useCallback((event) => {
|
|
80
|
+
if (reloadDocument || !router || event.defaultPrevented || isModifiedEvent(event)) return false;
|
|
81
|
+
if (event.currentTarget.hasAttribute("download")) return false;
|
|
82
|
+
if (new URL(hrefWithData, window.location.origin).origin !== window.location.origin) return false;
|
|
83
|
+
return true;
|
|
84
|
+
}, [
|
|
85
|
+
hrefWithData,
|
|
86
|
+
reloadDocument,
|
|
87
|
+
router
|
|
88
|
+
]);
|
|
89
|
+
const onClick = useCallback((event) => {
|
|
90
|
+
onClickProp?.(event);
|
|
91
|
+
if (!shouldHandleClick(event)) return;
|
|
92
|
+
let navigatePrevented = false;
|
|
93
|
+
onNavigate?.({ preventDefault() {
|
|
94
|
+
navigatePrevented = true;
|
|
95
|
+
} });
|
|
96
|
+
if (navigatePrevented) {
|
|
97
|
+
event.preventDefault();
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
event.preventDefault();
|
|
101
|
+
router?.visit(hrefWithData, {
|
|
102
|
+
method: normalizedMethod,
|
|
103
|
+
data: isGet ? void 0 : data,
|
|
104
|
+
preserveScroll,
|
|
105
|
+
preserveState,
|
|
106
|
+
replace,
|
|
107
|
+
viewTransition
|
|
108
|
+
});
|
|
109
|
+
}, [
|
|
110
|
+
data,
|
|
111
|
+
hrefWithData,
|
|
112
|
+
isGet,
|
|
113
|
+
normalizedMethod,
|
|
114
|
+
onClickProp,
|
|
115
|
+
onNavigate,
|
|
116
|
+
preserveScroll,
|
|
117
|
+
preserveState,
|
|
118
|
+
replace,
|
|
119
|
+
router,
|
|
120
|
+
shouldHandleClick,
|
|
121
|
+
viewTransition
|
|
122
|
+
]);
|
|
123
|
+
const onMouseEnter = useCallback((event) => {
|
|
124
|
+
onMouseEnterProp?.(event);
|
|
125
|
+
if (event.defaultPrevented) return;
|
|
126
|
+
if (hoverTimerRef.current) clearTimeout(hoverTimerRef.current);
|
|
127
|
+
hoverTimerRef.current = setTimeout(doPrefetch, hoverDelay);
|
|
128
|
+
}, [
|
|
129
|
+
doPrefetch,
|
|
130
|
+
hoverDelay,
|
|
131
|
+
onMouseEnterProp
|
|
132
|
+
]);
|
|
133
|
+
const onMouseLeave = useCallback((event) => {
|
|
134
|
+
onMouseLeaveProp?.(event);
|
|
135
|
+
if (hoverTimerRef.current) {
|
|
136
|
+
clearTimeout(hoverTimerRef.current);
|
|
137
|
+
hoverTimerRef.current = null;
|
|
138
|
+
}
|
|
139
|
+
}, [onMouseLeaveProp]);
|
|
140
|
+
const onFocus = useCallback((event) => {
|
|
141
|
+
onFocusProp?.(event);
|
|
142
|
+
if (!event.defaultPrevented) doPrefetch();
|
|
143
|
+
}, [doPrefetch, onFocusProp]);
|
|
144
|
+
const onMouseDown = useCallback((event) => {
|
|
145
|
+
onMouseDownProp?.(event);
|
|
146
|
+
if (!event.defaultPrevented) doPrefetch();
|
|
147
|
+
}, [doPrefetch, onMouseDownProp]);
|
|
148
|
+
const onTouchStart = useCallback((event) => {
|
|
149
|
+
onTouchStartProp?.(event);
|
|
150
|
+
if (!event.defaultPrevented) doPrefetch();
|
|
151
|
+
}, [doPrefetch, onTouchStartProp]);
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
if (strategies.length === 0) return;
|
|
154
|
+
if (strategies.includes("mount")) doPrefetch();
|
|
155
|
+
let observer = null;
|
|
156
|
+
if (strategies.includes("visible") && elementRef.current) {
|
|
157
|
+
observer = new IntersectionObserver((entries) => {
|
|
158
|
+
if (entries.some((e) => e.isIntersecting)) {
|
|
159
|
+
doPrefetch();
|
|
160
|
+
observer?.disconnect();
|
|
161
|
+
observer = null;
|
|
162
|
+
}
|
|
163
|
+
}, { rootMargin: "0px" });
|
|
164
|
+
observer.observe(elementRef.current);
|
|
165
|
+
}
|
|
166
|
+
return () => {
|
|
167
|
+
if (hoverTimerRef.current) {
|
|
168
|
+
clearTimeout(hoverTimerRef.current);
|
|
169
|
+
hoverTimerRef.current = null;
|
|
170
|
+
}
|
|
171
|
+
if (observer) observer.disconnect();
|
|
172
|
+
};
|
|
173
|
+
}, [doPrefetch, strategies]);
|
|
174
|
+
const eventHandlers = useMemo(() => {
|
|
175
|
+
const handlers = {
|
|
176
|
+
onClick,
|
|
177
|
+
onFocus: onFocusProp,
|
|
178
|
+
onMouseDown: onMouseDownProp,
|
|
179
|
+
onMouseEnter: onMouseEnterProp,
|
|
180
|
+
onMouseLeave: onMouseLeaveProp,
|
|
181
|
+
onTouchStart: onTouchStartProp
|
|
182
|
+
};
|
|
183
|
+
if (strategies.includes("hover")) {
|
|
184
|
+
handlers.onMouseEnter = onMouseEnter;
|
|
185
|
+
handlers.onMouseLeave = onMouseLeave;
|
|
186
|
+
handlers.onFocus = onFocus;
|
|
187
|
+
handlers.onTouchStart = onTouchStart;
|
|
188
|
+
}
|
|
189
|
+
if (strategies.includes("click")) {
|
|
190
|
+
handlers.onMouseDown = onMouseDown;
|
|
191
|
+
handlers.onTouchStart = onTouchStart;
|
|
192
|
+
}
|
|
193
|
+
return handlers;
|
|
194
|
+
}, [
|
|
195
|
+
onClick,
|
|
196
|
+
onFocus,
|
|
197
|
+
onFocusProp,
|
|
198
|
+
onMouseDown,
|
|
199
|
+
onMouseDownProp,
|
|
200
|
+
onMouseEnter,
|
|
201
|
+
onMouseEnterProp,
|
|
202
|
+
onMouseLeave,
|
|
203
|
+
onMouseLeaveProp,
|
|
204
|
+
onTouchStart,
|
|
205
|
+
onTouchStartProp,
|
|
206
|
+
strategies
|
|
207
|
+
]);
|
|
208
|
+
const resolvedStyle = useMemo(() => ({
|
|
209
|
+
touchAction: "manipulation",
|
|
210
|
+
...style
|
|
211
|
+
}), [style]);
|
|
212
|
+
if (isGet) return /* @__PURE__ */ jsx("a", {
|
|
213
|
+
ref: elementRef,
|
|
214
|
+
href: hrefWithData,
|
|
215
|
+
style: resolvedStyle,
|
|
216
|
+
...rest,
|
|
217
|
+
...eventHandlers,
|
|
218
|
+
children
|
|
219
|
+
});
|
|
220
|
+
return /* @__PURE__ */ jsx("button", {
|
|
221
|
+
ref: elementRef,
|
|
222
|
+
type: "button",
|
|
223
|
+
style: resolvedStyle,
|
|
224
|
+
...rest,
|
|
225
|
+
...eventHandlers,
|
|
226
|
+
children
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
//#endregion
|
|
230
|
+
//#region src/runtime/use-router.ts
|
|
231
|
+
function useRouter() {
|
|
232
|
+
return useContext(RouterContext) ?? ssrProxy;
|
|
233
|
+
}
|
|
234
|
+
//#endregion
|
|
235
|
+
//#region src/runtime/use-shared.ts
|
|
236
|
+
function useShared() {
|
|
237
|
+
const shared = useContext(SharedContext);
|
|
238
|
+
if (!shared) throw new Error("useShared(): must be used inside a Void page.");
|
|
239
|
+
return shared;
|
|
240
|
+
}
|
|
241
|
+
//#endregion
|
|
242
|
+
//#region src/runtime/use-form.ts
|
|
243
|
+
function getValidationErrors$1(error) {
|
|
244
|
+
const body = error.body;
|
|
245
|
+
if (body && typeof body === "object" && "errors" in body && body.errors && typeof body.errors === "object" && !Array.isArray(body.errors)) return body.errors;
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
function useForm(url, defaults, options) {
|
|
249
|
+
let resolvedUrl = url;
|
|
250
|
+
let actionQuery = "";
|
|
251
|
+
const qIdx = resolvedUrl.indexOf("?");
|
|
252
|
+
if (qIdx !== -1) {
|
|
253
|
+
actionQuery = resolvedUrl.slice(qIdx);
|
|
254
|
+
resolvedUrl = resolvedUrl.slice(0, qIdx);
|
|
255
|
+
}
|
|
256
|
+
if (options?.params) for (const [key, value] of Object.entries(options.params)) resolvedUrl = resolvedUrl.replace(`:${key}`, encodeURIComponent(value));
|
|
257
|
+
resolvedUrl = resolvedUrl + actionQuery;
|
|
258
|
+
const router = useContext(RouterContext);
|
|
259
|
+
const [data, setDataState] = useState(() => ({ ...defaults }));
|
|
260
|
+
const [defaultsState, setDefaultsState] = useState(() => ({ ...defaults }));
|
|
261
|
+
const [errors, setErrors] = useState({});
|
|
262
|
+
const [error, setError] = useState(null);
|
|
263
|
+
const [wasSuccessful, setWasSuccessful] = useState(false);
|
|
264
|
+
const [recentlySuccessful, setRecentlySuccessful] = useState(false);
|
|
265
|
+
const defaultsRef = useRef(defaultsState);
|
|
266
|
+
const dataRef = useRef(data);
|
|
267
|
+
const successTimeoutRef = useRef(null);
|
|
268
|
+
const hasChanges = useMemo(() => {
|
|
269
|
+
return Object.keys(defaultsState).some((key) => !isEqualFormValue(data[key], defaultsState[key]));
|
|
270
|
+
}, [data, defaultsState]);
|
|
271
|
+
const setData = useCallback((field, value) => {
|
|
272
|
+
dataRef.current = {
|
|
273
|
+
...dataRef.current,
|
|
274
|
+
[field]: value
|
|
275
|
+
};
|
|
276
|
+
setDataState(dataRef.current);
|
|
277
|
+
}, []);
|
|
278
|
+
const reset = useCallback((...fields) => {
|
|
279
|
+
if (fields.length === 0) dataRef.current = { ...defaultsRef.current };
|
|
280
|
+
else {
|
|
281
|
+
const next = { ...dataRef.current };
|
|
282
|
+
for (const field of fields) next[field] = defaultsRef.current[field];
|
|
283
|
+
dataRef.current = next;
|
|
284
|
+
}
|
|
285
|
+
setDataState(dataRef.current);
|
|
286
|
+
}, []);
|
|
287
|
+
const clearErrors = useCallback((...fields) => {
|
|
288
|
+
if (fields.length === 0) setErrors({});
|
|
289
|
+
else setErrors((prev) => {
|
|
290
|
+
const next = { ...prev };
|
|
291
|
+
for (const field of fields) delete next[field];
|
|
292
|
+
return next;
|
|
293
|
+
});
|
|
294
|
+
}, []);
|
|
295
|
+
const clearError = useCallback(() => {
|
|
296
|
+
setError(null);
|
|
297
|
+
}, []);
|
|
298
|
+
const [, dispatchAction, pending] = useActionState(async (previousSuccessVersion, payload) => {
|
|
299
|
+
if (!router) throw new Error("useForm(): requires the Void Router.");
|
|
300
|
+
const method = payload instanceof FormData ? "POST" : payload.method;
|
|
301
|
+
setWasSuccessful(false);
|
|
302
|
+
setRecentlySuccessful(false);
|
|
303
|
+
if (successTimeoutRef.current) {
|
|
304
|
+
clearTimeout(successTimeoutRef.current);
|
|
305
|
+
successTimeoutRef.current = null;
|
|
306
|
+
}
|
|
307
|
+
setErrors({});
|
|
308
|
+
setError(null);
|
|
309
|
+
let result;
|
|
310
|
+
try {
|
|
311
|
+
result = await submitAction(router, resolvedUrl, {
|
|
312
|
+
method,
|
|
313
|
+
data: { ...dataRef.current },
|
|
314
|
+
_resolveOnShell: true
|
|
315
|
+
});
|
|
316
|
+
} catch (submitError) {
|
|
317
|
+
if (isAbortError(submitError)) return previousSuccessVersion;
|
|
318
|
+
throw submitError;
|
|
319
|
+
}
|
|
320
|
+
if (!result.ok) {
|
|
321
|
+
const validationErrors = getValidationErrors$1(result.error);
|
|
322
|
+
if (validationErrors) setErrors(validationErrors);
|
|
323
|
+
else setError(result.error);
|
|
324
|
+
return previousSuccessVersion;
|
|
325
|
+
}
|
|
326
|
+
const nextDefaults = { ...dataRef.current };
|
|
327
|
+
defaultsRef.current = nextDefaults;
|
|
328
|
+
setDefaultsState(nextDefaults);
|
|
329
|
+
setWasSuccessful(true);
|
|
330
|
+
setRecentlySuccessful(true);
|
|
331
|
+
successTimeoutRef.current = setTimeout(() => {
|
|
332
|
+
setRecentlySuccessful(false);
|
|
333
|
+
successTimeoutRef.current = null;
|
|
334
|
+
}, 2e3);
|
|
335
|
+
return previousSuccessVersion + 1;
|
|
336
|
+
}, 0);
|
|
337
|
+
useEffect(() => {
|
|
338
|
+
return () => {
|
|
339
|
+
if (successTimeoutRef.current) {
|
|
340
|
+
clearTimeout(successTimeoutRef.current);
|
|
341
|
+
successTimeoutRef.current = null;
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
}, []);
|
|
345
|
+
const dispatchMethodAction = useCallback((method) => (_formData) => {
|
|
346
|
+
dispatchAction({ method });
|
|
347
|
+
}, [dispatchAction]);
|
|
348
|
+
return {
|
|
349
|
+
data,
|
|
350
|
+
setData,
|
|
351
|
+
errors,
|
|
352
|
+
error,
|
|
353
|
+
pending,
|
|
354
|
+
hasChanges,
|
|
355
|
+
wasSuccessful,
|
|
356
|
+
recentlySuccessful,
|
|
357
|
+
post: dispatchAction,
|
|
358
|
+
put: useMemo(() => dispatchMethodAction("PUT"), [dispatchMethodAction]),
|
|
359
|
+
patch: useMemo(() => dispatchMethodAction("PATCH"), [dispatchMethodAction]),
|
|
360
|
+
delete: useMemo(() => dispatchMethodAction("DELETE"), [dispatchMethodAction]),
|
|
361
|
+
reset,
|
|
362
|
+
clearErrors,
|
|
363
|
+
clearError
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
//#endregion
|
|
367
|
+
//#region src/runtime/use-navigation.ts
|
|
368
|
+
function useNavigation() {
|
|
369
|
+
return useContext(NavigationContext);
|
|
370
|
+
}
|
|
371
|
+
//#endregion
|
|
372
|
+
//#region src/runtime/use-island-form.ts
|
|
373
|
+
function getValidationErrors(body) {
|
|
374
|
+
if (body && typeof body === "object" && "errors" in body && body.errors && typeof body.errors === "object" && !Array.isArray(body.errors)) return body.errors;
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
async function readActionErrorBody(response) {
|
|
378
|
+
const contentType = response.headers.get("content-type") || "";
|
|
379
|
+
try {
|
|
380
|
+
if (contentType.includes("application/json")) return await response.json();
|
|
381
|
+
return await response.text() || null;
|
|
382
|
+
} catch {
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Form hook for island pages. Uses fetch + page reload instead of the
|
|
388
|
+
* Void Router (which is not available in island mode).
|
|
389
|
+
*/
|
|
390
|
+
function useIslandForm(defaults) {
|
|
391
|
+
const [data, setDataState] = useState(() => ({ ...defaults }));
|
|
392
|
+
const [defaultsState, setDefaultsState] = useState(() => ({ ...defaults }));
|
|
393
|
+
const [errors, setErrors] = useState({});
|
|
394
|
+
const [error, setError] = useState(null);
|
|
395
|
+
const [pending, setPending] = useState(false);
|
|
396
|
+
const [wasSuccessful, setWasSuccessful] = useState(false);
|
|
397
|
+
const [recentlySuccessful, setRecentlySuccessful] = useState(false);
|
|
398
|
+
const defaultsRef = useRef(defaultsState);
|
|
399
|
+
const successTimeoutRef = useRef(null);
|
|
400
|
+
const hasChanges = useMemo(() => {
|
|
401
|
+
return Object.keys(defaultsState).some((key) => !isEqualFormValue(data[key], defaultsState[key]));
|
|
402
|
+
}, [data, defaultsState]);
|
|
403
|
+
const setData = useCallback((field, value) => {
|
|
404
|
+
setDataState((prev) => ({
|
|
405
|
+
...prev,
|
|
406
|
+
[field]: value
|
|
407
|
+
}));
|
|
408
|
+
}, []);
|
|
409
|
+
const reset = useCallback((...fields) => {
|
|
410
|
+
if (fields.length === 0) setDataState({ ...defaultsRef.current });
|
|
411
|
+
else setDataState((prev) => {
|
|
412
|
+
const next = { ...prev };
|
|
413
|
+
for (const field of fields) next[field] = defaultsRef.current[field];
|
|
414
|
+
return next;
|
|
415
|
+
});
|
|
416
|
+
}, []);
|
|
417
|
+
const clearErrors = useCallback((...fields) => {
|
|
418
|
+
if (fields.length === 0) setErrors({});
|
|
419
|
+
else setErrors((prev) => {
|
|
420
|
+
const next = { ...prev };
|
|
421
|
+
for (const f of fields) delete next[f];
|
|
422
|
+
return next;
|
|
423
|
+
});
|
|
424
|
+
}, []);
|
|
425
|
+
const clearError = useCallback(() => {
|
|
426
|
+
setError(null);
|
|
427
|
+
}, []);
|
|
428
|
+
const submit = useCallback(async (url, method) => {
|
|
429
|
+
setPending(true);
|
|
430
|
+
setWasSuccessful(false);
|
|
431
|
+
setRecentlySuccessful(false);
|
|
432
|
+
if (successTimeoutRef.current) clearTimeout(successTimeoutRef.current);
|
|
433
|
+
setErrors({});
|
|
434
|
+
setError(null);
|
|
435
|
+
try {
|
|
436
|
+
const response = await fetch(url, {
|
|
437
|
+
method,
|
|
438
|
+
headers: { "Content-Type": "application/json" },
|
|
439
|
+
body: JSON.stringify(data)
|
|
440
|
+
});
|
|
441
|
+
if (response.redirected) {
|
|
442
|
+
window.location.href = response.url;
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
if (response.ok) {
|
|
446
|
+
setWasSuccessful(true);
|
|
447
|
+
setRecentlySuccessful(true);
|
|
448
|
+
const nextDefaults = { ...data };
|
|
449
|
+
defaultsRef.current = nextDefaults;
|
|
450
|
+
setDefaultsState(nextDefaults);
|
|
451
|
+
successTimeoutRef.current = setTimeout(() => setRecentlySuccessful(false), 2e3);
|
|
452
|
+
window.location.reload();
|
|
453
|
+
} else if (!response.ok) {
|
|
454
|
+
const body = await readActionErrorBody(response);
|
|
455
|
+
const actionError = new VoidActionError({
|
|
456
|
+
body,
|
|
457
|
+
status: response.status,
|
|
458
|
+
statusText: response.statusText,
|
|
459
|
+
url
|
|
460
|
+
});
|
|
461
|
+
const validationErrors = response.status === 422 ? getValidationErrors(body) : null;
|
|
462
|
+
if (validationErrors) {
|
|
463
|
+
setErrors(validationErrors);
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
if (categorizeActionError(response.status) === "boundary") throw actionError;
|
|
467
|
+
setError(actionError);
|
|
468
|
+
}
|
|
469
|
+
} finally {
|
|
470
|
+
setPending(false);
|
|
471
|
+
}
|
|
472
|
+
}, [data]);
|
|
473
|
+
return {
|
|
474
|
+
data,
|
|
475
|
+
setData,
|
|
476
|
+
errors,
|
|
477
|
+
error,
|
|
478
|
+
pending,
|
|
479
|
+
hasChanges,
|
|
480
|
+
wasSuccessful,
|
|
481
|
+
recentlySuccessful,
|
|
482
|
+
reset,
|
|
483
|
+
clearErrors,
|
|
484
|
+
clearError,
|
|
485
|
+
post: (url) => submit(url, "POST"),
|
|
486
|
+
put: (url) => submit(url, "PUT"),
|
|
487
|
+
patch: (url) => submit(url, "PATCH"),
|
|
488
|
+
delete: (url) => submit(url, "DELETE")
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
//#endregion
|
|
492
|
+
export { useRouter as a, useShared as i, useNavigation as n, Link as o, useForm as r, useIslandForm as t };
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@void/react",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"files": [
|
|
5
|
+
"dist"
|
|
6
|
+
],
|
|
7
|
+
"type": "module",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.mts",
|
|
11
|
+
"import": "./dist/index.mjs"
|
|
12
|
+
},
|
|
13
|
+
"./plugin": {
|
|
14
|
+
"types": "./dist/plugin.d.mts",
|
|
15
|
+
"import": "./dist/plugin.mjs"
|
|
16
|
+
},
|
|
17
|
+
"./runtime": {
|
|
18
|
+
"types": "./dist/runtime/index.d.mts",
|
|
19
|
+
"import": "./dist/runtime/index.mjs"
|
|
20
|
+
},
|
|
21
|
+
"./prefetch": {
|
|
22
|
+
"types": "./dist/runtime/prefetch.d.mts",
|
|
23
|
+
"import": "./dist/runtime/prefetch.mjs"
|
|
24
|
+
},
|
|
25
|
+
"./pages-client": {
|
|
26
|
+
"types": "./dist/runtime/pages-client.d.mts",
|
|
27
|
+
"import": "./dist/runtime/pages-client.mjs"
|
|
28
|
+
},
|
|
29
|
+
"./pages-server": {
|
|
30
|
+
"types": "./dist/runtime/pages-server.d.mts",
|
|
31
|
+
"import": "./dist/runtime/pages-server.mjs"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsdown",
|
|
39
|
+
"dev": "tsdown --watch",
|
|
40
|
+
"typecheck": "tsgo --noEmit"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@vitejs/plugin-react": "^6.0.1"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "catalog:",
|
|
47
|
+
"@types/react": "^19.2.14",
|
|
48
|
+
"@types/react-dom": "^19.2.3",
|
|
49
|
+
"pathe": "catalog:",
|
|
50
|
+
"react": "^19.2.5",
|
|
51
|
+
"react-dom": "^19.2.5",
|
|
52
|
+
"tsdown": "catalog:",
|
|
53
|
+
"vite": "catalog:",
|
|
54
|
+
"void": "workspace:*"
|
|
55
|
+
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"react": "^19.0.0",
|
|
58
|
+
"react-dom": "^19.0.0",
|
|
59
|
+
"vite": "catalog:",
|
|
60
|
+
"void": "workspace:*"
|
|
61
|
+
}
|
|
62
|
+
}
|