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