@void/vue 1.2.0 → 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 CHANGED
@@ -1,4 +1,4 @@
1
- import { ActionClient, NavigationState, VisitOptions, VoidActionError, VoidRouter } from "void/pages-client";
1
+ import { ActionClient, FormResetOptions, NavigationState, VisitOptions, VoidActionError, VoidRouter } from "void/pages-client";
2
2
  import { Component } from "vue";
3
3
  import { CloudContextVariables, DeferredState as Deferred, InferProps } from "void";
4
4
  import { ActionFormOptions, ActionUrl, ResolveActionBody } from "void/routes";
@@ -33,7 +33,9 @@ export declare function useForm<U extends ActionUrl & string>(url: U, defaults:
33
33
  put(opts?: FormSubmitOptions): Promise<void>;
34
34
  patch(opts?: FormSubmitOptions): Promise<void>;
35
35
  delete(opts?: FormSubmitOptions): Promise<void>;
36
+ reset(options: FormResetOptions, ...fields: ReadonlyArray<keyof ResolveActionBody<U> & string>): void;
36
37
  reset(...fields: ReadonlyArray<keyof ResolveActionBody<U> & string>): void;
38
+ setError(field: keyof ResolveActionBody<U> & string, message: string): void;
37
39
  clearErrors(...fields: ReadonlyArray<keyof ResolveActionBody<U> & string>): void;
38
40
  clearError(): void;
39
41
  };
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { VoidActionError, categorizeActionError, createActionClient, formErrorMessage, formRetryMessage, formSubmissionBlocked, getValidationErrors, idleNavigationState, interpolateRouteParams, isAbortError, isEqualFormValue, isModifiedEvent, mergeDataIntoHref, scheduleFormRetry, ssrProxy, submitAction } from "void/pages-client";
2
- import { computed, defineComponent, h, inject, onMounted, onUnmounted, reactive, ref, watch } from "vue";
1
+ import { VoidActionError, categorizeActionError, cloneFormValue, createActionClient, formErrorMessage, formRetryMessage, formSubmissionBlocked, getValidationErrors, idleNavigationState, interpolateRouteParams, isAbortError, isEqualFormValue, isModifiedEvent, mergeDataIntoHref, parseFormResetArgs, scheduleFormRetry, ssrProxy, submitAction } from "void/pages-client";
2
+ import { computed, defineComponent, h, inject, onMounted, onUnmounted, reactive, ref, shallowRef, watch } from "vue";
3
3
  //#region src/runtime/form.ts
4
4
  const Form = defineComponent({
5
5
  name: "VoidForm",
@@ -241,28 +241,36 @@ function useShared() {
241
241
  //#endregion
242
242
  //#region src/runtime/use-form.ts
243
243
  function useForm(url, defaults, options) {
244
- const formDefaults = defaults;
244
+ const initialDefaults = cloneFormValue(defaults);
245
245
  const router = inject("__void_router", null);
246
- const data = reactive({ ...formDefaults });
246
+ const data = reactive(cloneFormValue(initialDefaults));
247
247
  const errors = reactive({});
248
248
  const error = ref(null);
249
249
  const pending = ref(false);
250
250
  const wasSuccessful = ref(false);
251
251
  const recentlySuccessful = ref(false);
252
- let defaults_ = { ...formDefaults };
252
+ const defaults_ = shallowRef(initialDefaults);
253
253
  let successTimeout = null;
254
- const hasChanges = ref(false);
255
- watch(() => ({ ...data }), (val) => {
256
- hasChanges.value = Object.keys(defaults_).some((key) => !isEqualFormValue(val[key], defaults_[key]));
257
- }, { deep: true });
258
- function reset(...fields) {
259
- if (fields.length === 0) Object.assign(data, { ...defaults_ });
260
- else for (const field of fields) data[field] = defaults_[field];
254
+ let dataSnapshot = cloneFormValue(initialDefaults);
255
+ watch(() => cloneFormValue(data), (val) => {
256
+ for (const key of /* @__PURE__ */ new Set([...Object.keys(dataSnapshot), ...Object.keys(val)])) if (!isEqualFormValue(val[key], dataSnapshot[key])) delete errors[key];
257
+ dataSnapshot = val;
258
+ }, { flush: "sync" });
259
+ const hasChanges = computed(() => Object.keys(defaults_.value).some((key) => !isEqualFormValue(data[key], defaults_.value[key])));
260
+ function reset(...args) {
261
+ const { initial, fields } = parseFormResetArgs(args);
262
+ const source = initial ? initialDefaults : defaults_.value;
263
+ if (fields.length === 0) Object.assign(data, cloneFormValue(source));
264
+ else for (const field of fields) data[field] = cloneFormValue(source[field]);
265
+ dataSnapshot = cloneFormValue(data);
261
266
  }
262
267
  function clearErrors(...fields) {
263
268
  if (fields.length === 0) for (const key of Object.keys(errors)) delete errors[key];
264
269
  else for (const field of fields) delete errors[field];
265
270
  }
271
+ function setError(field, message) {
272
+ errors[field] = message;
273
+ }
266
274
  function clearError() {
267
275
  error.value = null;
268
276
  }
@@ -288,7 +296,10 @@ function useForm(url, defaults, options) {
288
296
  }
289
297
  wasSuccessful.value = true;
290
298
  recentlySuccessful.value = true;
291
- defaults_ = { ...data };
299
+ if (options?.resetOnSuccess) {
300
+ Object.assign(data, cloneFormValue(initialDefaults));
301
+ defaults_.value = cloneFormValue(initialDefaults);
302
+ } else defaults_.value = cloneFormValue(data);
292
303
  successTimeout = setTimeout(() => {
293
304
  recentlySuccessful.value = false;
294
305
  }, 2e3);
@@ -308,6 +319,7 @@ function useForm(url, defaults, options) {
308
319
  wasSuccessful,
309
320
  recentlySuccessful,
310
321
  reset,
322
+ setError,
311
323
  clearErrors,
312
324
  clearError,
313
325
  post: (opts) => submit("POST", opts),
package/dist/plugin.mjs CHANGED
@@ -89,7 +89,10 @@ function islandsPlugin(pagesDir, options) {
89
89
  },
90
90
  async buildStart() {
91
91
  if (existsSync(pagesDir)) {
92
- pageScan = await scanPages(root, { pagesDir });
92
+ pageScan = await scanPages(root, {
93
+ pagesDir,
94
+ environment: isDev ? "dev" : "prod"
95
+ });
93
96
  const filesToScan = [
94
97
  ...pageScan.pages.map((page) => ({
95
98
  filePath: page.componentPath,
@@ -312,6 +315,35 @@ initIslands(islands, hydrateIsland);
312
315
  //#region src/plugin-pages.ts
313
316
  const RENDERER_ID = "virtual:void-pages-renderer";
314
317
  const CLIENT_ENTRY_ID = "virtual:void-pages-client";
318
+ /** Runtime helper injected into generated SSR and client page bundles. */
319
+ const PATCH_PROPS_RUNTIME = `
320
+ function hasRuntimePropsDeclaration(comp) {
321
+ const declared = comp.props;
322
+ if (declared == null) return false;
323
+ if (Array.isArray(declared)) return declared.length > 0;
324
+ if (typeof declared === "object") return Object.keys(declared).length > 0;
325
+ return false;
326
+ }
327
+
328
+ function patchProps(comp, props) {
329
+ if (comp.__voidPatched) return;
330
+ const keys = Object.keys(props);
331
+ if (hasRuntimePropsDeclaration(comp)) {
332
+ const declared = comp.props;
333
+ // defineModel() can declare runtime props even after typed loader props are stripped.
334
+ const missing = keys.filter((key) =>
335
+ Array.isArray(declared) ? !declared.includes(key) : !Object.hasOwn(declared, key)
336
+ );
337
+ if (missing.length === 0) return;
338
+ comp.props = Array.isArray(declared)
339
+ ? [...declared, ...missing]
340
+ : { ...Object.fromEntries(missing.map((key) => [key, null])), ...declared };
341
+ } else {
342
+ comp.props = keys;
343
+ }
344
+ comp.__voidPatched = true;
345
+ }
346
+ `;
315
347
  function isVuePageFile(id, pagesDir) {
316
348
  if (!id.startsWith(pagesDir + "/")) return false;
317
349
  const rel = id.slice(pagesDir.length + 1);
@@ -364,7 +396,10 @@ function pagesPlugin(pagesDir, options) {
364
396
  const normalizedPagesDir = normalizePath(pagesDir);
365
397
  let pendingRescan = null;
366
398
  const rescanPages = async () => {
367
- pageScan = existsSync(pagesDir) ? await scanPages(root, { pagesDir }) : null;
399
+ pageScan = existsSync(pagesDir) ? await scanPages(root, {
400
+ pagesDir,
401
+ environment: isDev ? "dev" : "prod"
402
+ }) : null;
368
403
  };
369
404
  const runRescan = () => {
370
405
  pendingRescan ??= rescanPages().finally(() => {
@@ -384,7 +419,7 @@ function pagesPlugin(pagesDir, options) {
384
419
  transform: {
385
420
  filter: { id: /\.vue$/ },
386
421
  handler(code, id) {
387
- if (!isVuePageFile(id, pagesDir)) return;
422
+ if (!isVuePageFile(normalizePath(id), normalizedPagesDir)) return;
388
423
  const transformed = stripDefinePropsTypeArgument(code);
389
424
  if (!transformed) return;
390
425
  return {
@@ -396,7 +431,7 @@ function pagesPlugin(pagesDir, options) {
396
431
  handleHotUpdate: {
397
432
  order: "pre",
398
433
  handler(ctx) {
399
- if (!isVuePageFile(ctx.file, pagesDir)) return;
434
+ if (!isVuePageFile(normalizePath(ctx.file), normalizedPagesDir)) return;
400
435
  const read = ctx.read;
401
436
  ctx.read = async () => {
402
437
  const code = await read();
@@ -454,11 +489,9 @@ function pagesPlugin(pagesDir, options) {
454
489
  viteConfig = config;
455
490
  },
456
491
  config(_, env) {
492
+ isDev = env.command === "serve";
457
493
  if (!existsSync(pagesDir)) return;
458
- if (env.command === "serve") {
459
- isDev = true;
460
- return { optimizeDeps: { include: ["vue"] } };
461
- }
494
+ if (isDev) return { optimizeDeps: { include: ["vue"] } };
462
495
  if (!hasNonIslandPages(pagesDir)) return;
463
496
  return { environments: { client: { build: {
464
497
  manifest: true,
@@ -477,11 +510,7 @@ import { serializePageData } from "void/pages-server";
477
510
 
478
511
  ${generateComponentManifest(scan, pagesDir)}
479
512
 
480
- function patchProps(comp, props) {
481
- if (comp.__voidPatched) return;
482
- comp.props = Object.keys(props);
483
- comp.__voidPatched = true;
484
- }
513
+ ${PATCH_PROPS_RUNTIME}
485
514
 
486
515
  export async function renderPage(pageObj, assetTags) {
487
516
  const PageComponent = (await components[pageObj.component]()).default;
@@ -545,11 +574,7 @@ const currentPageKey = ref(0);
545
574
  const routeUrl = ref(window.location.pathname + window.location.search + window.location.hash);
546
575
  const navigation = reactive(idleNavigationState());
547
576
 
548
- function patchProps(comp, props) {
549
- if (comp.__voidPatched) return;
550
- comp.props = Object.keys(props);
551
- comp.__voidPatched = true;
552
- }
577
+ ${PATCH_PROPS_RUNTIME}
553
578
 
554
579
  function replaceRecord(target, source) {
555
580
  for (const key of Object.keys(target)) delete target[key];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@void/vue",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Vue pages, server rendering, forms, and navigation for Void.",
5
5
  "homepage": "https://void.cloud",
6
6
  "bugs": {
@@ -41,12 +41,12 @@
41
41
  "pathslash": "^0.1.0",
42
42
  "vite": "npm:@voidzero-dev/vite-plus-core@1.3.0",
43
43
  "vite-plus": "1.3.0",
44
- "void": "1.2.0",
44
+ "void": "1.3.0",
45
45
  "vue": "^3.5.44"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "vite": "^8.0.0",
49
- "void": "1.2.0",
49
+ "void": "1.3.0",
50
50
  "vue": "^3.5.0"
51
51
  },
52
52
  "scripts": {