@sfxcode/nuxt-ui-formkit 1.2.2 → 1.2.3

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 CHANGED
@@ -61,6 +61,9 @@ FormKit Nuxt UI bridges the gap between [FormKit](https://formkit.com/)'s powerf
61
61
  - `FUDataDebug` - Development tool for form debugging
62
62
  - `FUAutoForm` - Schema-free forms: inputs inferred from your data's value shapes, or a Valibot/Zod schema, with an override map for fine-tuning
63
63
 
64
+ 🛡️ **Standard Schema Validation** - Validate against Zod/Valibot/ArkType instead of hand-written validation strings
65
+ - `FUDataEdit`'s `standardSchema` prop, or `useFormKitForm`'s `standardSchema` option - errors land on the exact field (including inside a repeater row), respecting each field's own validation-visibility timing
66
+
64
67
  ⚙️ **Config Helper** - One-line `formkit.config.ts` setup
65
68
  - `createNuxtUiFormkitConfig` - Assembles all `nuxtUIXxx` inputs/outputs and this module's plugins into `{ inputs, plugins }` you spread into your own config
66
69
 
@@ -339,6 +342,9 @@ The playground includes comprehensive examples for all components:
339
342
  ### Form Composable
340
343
  - [useFormKitForm](./playground/app/pages/form/form-composable.vue)
341
344
 
345
+ ### Standard Schema Validation
346
+ - [Standard Schema Validation](./playground/app/pages/form/standard-schema.vue)
347
+
342
348
  ## Development
343
349
 
344
350
  <details>
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=4.0.0"
6
6
  },
7
- "version": "1.2.2",
7
+ "version": "1.2.3",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
@@ -1,4 +1,5 @@
1
1
  import type { FormKitSchemaDefinition } from '@formkit/core';
2
+ import type { StandardSchemaV1 } from '@standard-schema/spec';
2
3
  import type { PropType } from 'vue';
3
4
  declare var __VLS_15: {}, __VLS_18: {}, __VLS_26: {};
4
5
  type __VLS_Slots = {} & {
@@ -21,6 +22,10 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
21
22
  type: PropType<FormKitSchemaDefinition>;
22
23
  default: null;
23
24
  };
25
+ standardSchema: {
26
+ type: PropType<StandardSchemaV1>;
27
+ default: null;
28
+ };
24
29
  formClass: {
25
30
  type: StringConstructor;
26
31
  default: string;
@@ -91,6 +96,10 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
91
96
  type: PropType<FormKitSchemaDefinition>;
92
97
  default: null;
93
98
  };
99
+ standardSchema: {
100
+ type: PropType<StandardSchemaV1>;
101
+ default: null;
102
+ };
94
103
  formClass: {
95
104
  type: StringConstructor;
96
105
  default: string;
@@ -152,6 +161,7 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
152
161
  schema: FormKitSchemaDefinition;
153
162
  data: Record<string, any>;
154
163
  id: string;
164
+ standardSchema: StandardSchemaV1<unknown, unknown>;
155
165
  formClass: string;
156
166
  actionsClass: string;
157
167
  submitSeverity: string;
@@ -1,7 +1,8 @@
1
1
  <script setup>
2
2
  import { reset } from "@formkit/core";
3
- import { FormKit, FormKitMessages, FormKitSchema } from "@formkit/vue";
4
- import { ref, watch } from "vue";
3
+ import { FormKit, FormKitMessages, FormKitSchema, useFormKitContextById } from "@formkit/vue";
4
+ import { onScopeDispose, ref, watch } from "vue";
5
+ import { attachStandardSchema } from "../composables/useFormKitStandardSchema";
5
6
  import FUDataDebug from "./FUDataDebug.vue";
6
7
  const props = defineProps({
7
8
  id: {
@@ -16,6 +17,10 @@ const props = defineProps({
16
17
  type: Object,
17
18
  default: null
18
19
  },
20
+ standardSchema: {
21
+ type: Object,
22
+ default: null
23
+ },
19
24
  formClass: {
20
25
  type: String,
21
26
  default: ""
@@ -70,6 +75,12 @@ const props = defineProps({
70
75
  }
71
76
  });
72
77
  const emit = defineEmits(["dataSaved", "onReset"]);
78
+ let detachStandardSchema;
79
+ useFormKitContextById(props.id, (context) => {
80
+ if (props.standardSchema)
81
+ detachStandardSchema = attachStandardSchema(context.node, props.standardSchema);
82
+ });
83
+ onScopeDispose(() => detachStandardSchema?.());
73
84
  const formSchema = ref(props.schema);
74
85
  const formData = defineModel({ type: [Object, String, Array] });
75
86
  watch(() => props.schema, (newSchema) => {
@@ -1,4 +1,5 @@
1
1
  import type { FormKitSchemaDefinition } from '@formkit/core';
2
+ import type { StandardSchemaV1 } from '@standard-schema/spec';
2
3
  import type { PropType } from 'vue';
3
4
  declare var __VLS_15: {}, __VLS_18: {}, __VLS_26: {};
4
5
  type __VLS_Slots = {} & {
@@ -21,6 +22,10 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
21
22
  type: PropType<FormKitSchemaDefinition>;
22
23
  default: null;
23
24
  };
25
+ standardSchema: {
26
+ type: PropType<StandardSchemaV1>;
27
+ default: null;
28
+ };
24
29
  formClass: {
25
30
  type: StringConstructor;
26
31
  default: string;
@@ -91,6 +96,10 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
91
96
  type: PropType<FormKitSchemaDefinition>;
92
97
  default: null;
93
98
  };
99
+ standardSchema: {
100
+ type: PropType<StandardSchemaV1>;
101
+ default: null;
102
+ };
94
103
  formClass: {
95
104
  type: StringConstructor;
96
105
  default: string;
@@ -152,6 +161,7 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
152
161
  schema: FormKitSchemaDefinition;
153
162
  data: Record<string, any>;
154
163
  id: string;
164
+ standardSchema: StandardSchemaV1<unknown, unknown>;
155
165
  formClass: string;
156
166
  actionsClass: string;
157
167
  submitSeverity: string;
@@ -1,5 +1,9 @@
1
1
  import type { ErrorMessages } from '@formkit/core';
2
- export declare function useFormKitForm(id: string): {
2
+ import type { StandardSchemaV1 } from '@standard-schema/spec';
3
+ export interface UseFormKitFormOptions {
4
+ standardSchema?: StandardSchemaV1;
5
+ }
6
+ export declare function useFormKitForm(id: string, options?: UseFormKitFormOptions): {
3
7
  submit: (handler?: () => unknown | Promise<unknown>) => void;
4
8
  reset: (resetTo?: unknown) => void;
5
9
  setErrors: (localErrors: ErrorMessages, childErrors?: ErrorMessages) => void;
@@ -1,8 +1,14 @@
1
1
  import { clearErrors, reset, setErrors, submitForm } from "@formkit/core";
2
2
  import { useFormKitContextById } from "@formkit/vue";
3
- import { computed, ref } from "vue";
4
- export function useFormKitForm(id) {
5
- const context = useFormKitContextById(id);
3
+ import { computed, onScopeDispose, ref } from "vue";
4
+ import { attachStandardSchema } from "./useFormKitStandardSchema.js";
5
+ export function useFormKitForm(id, options) {
6
+ let detachStandardSchema;
7
+ const context = useFormKitContextById(id, (formContext) => {
8
+ if (options?.standardSchema)
9
+ detachStandardSchema = attachStandardSchema(formContext.node, options.standardSchema);
10
+ });
11
+ onScopeDispose(() => detachStandardSchema?.());
6
12
  const isLoading = ref(false);
7
13
  const isValid = computed(() => context.value?.state.valid ?? false);
8
14
  const isSubmitted = computed(() => context.value?.state.submitted ?? false);
@@ -0,0 +1,20 @@
1
+ import type { FormKitNode } from '@formkit/core';
2
+ import type { StandardSchemaV1 } from '@standard-schema/spec';
3
+ /**
4
+ * Runs `schema` against `node`'s live value and applies the resulting issues
5
+ * as `type: 'validation'` messages - to `node` itself for root-level issues,
6
+ * and to the addressed descendant (`resolveAddress(node, address)`) for
7
+ * every `childErrors` entry - so they respect each target's own
8
+ * `validationVisibility` timing exactly like FormKit's native rule-derived
9
+ * errors, instead of appearing instantly like `setErrors` does. See phase 1
10
+ * of the standard-schema-validation-bridge plan for why.
11
+ *
12
+ * Re-validates on the node's own `commit` event, which already fires once
13
+ * per settled change regardless of which descendant triggered it (a child's
14
+ * `calm()` cascades synchronously up through every ancestor's own `commit`),
15
+ * so this doesn't need a second debounce layer.
16
+ *
17
+ * Returns a detach function that stops re-validating and clears every
18
+ * schema-derived message this call ever applied.
19
+ */
20
+ export declare function attachStandardSchema(node: FormKitNode, schema: StandardSchemaV1): () => void;
@@ -0,0 +1,68 @@
1
+ import { createMessage, isNode } from "@formkit/core";
2
+ import { issuesToFormKitErrors } from "../utils/standardSchemaErrors.js";
3
+ const STANDARD_SCHEMA_SOURCE = "standard-schema";
4
+ function isStandardSchemaMessage(message) {
5
+ return message.meta.source === STANDARD_SCHEMA_SOURCE;
6
+ }
7
+ function clearStandardSchemaMessages(node) {
8
+ node.store.filter((message) => !isStandardSchemaMessage(message), "validation");
9
+ }
10
+ function setStandardSchemaMessages(node, values) {
11
+ clearStandardSchemaMessages(node);
12
+ values.forEach((value, index) => {
13
+ node.store.set(createMessage({
14
+ key: `standard_schema_${index}`,
15
+ type: "validation",
16
+ blocking: true,
17
+ value,
18
+ meta: { source: STANDARD_SCHEMA_SOURCE }
19
+ }));
20
+ });
21
+ }
22
+ function resolveAddress(root, address) {
23
+ let pointer = root;
24
+ for (const segment of address.split(root.config.delimiter)) {
25
+ if (!pointer)
26
+ return void 0;
27
+ const matches = pointer.children.filter(isNode).filter((child) => String(child.name) === segment);
28
+ pointer = matches.find((child) => child.type !== "input") ?? matches[0];
29
+ }
30
+ return pointer;
31
+ }
32
+ export function attachStandardSchema(node, schema) {
33
+ let previousAddresses = /* @__PURE__ */ new Set();
34
+ let runToken = 0;
35
+ async function validate() {
36
+ const token = ++runToken;
37
+ const result = await schema["~standard"].validate(node.value);
38
+ if (token !== runToken)
39
+ return;
40
+ const { localErrors, childErrors } = issuesToFormKitErrors(result.issues ?? []);
41
+ setStandardSchemaMessages(node, localErrors);
42
+ const currentAddresses = new Set(Object.keys(childErrors));
43
+ for (const address of previousAddresses) {
44
+ if (currentAddresses.has(address))
45
+ continue;
46
+ const target = resolveAddress(node, address);
47
+ if (target)
48
+ clearStandardSchemaMessages(target);
49
+ }
50
+ for (const [address, messages] of Object.entries(childErrors)) {
51
+ const target = resolveAddress(node, address);
52
+ if (target)
53
+ setStandardSchemaMessages(target, messages);
54
+ }
55
+ previousAddresses = currentAddresses;
56
+ }
57
+ const receipt = node.on("commit", () => void validate());
58
+ void validate();
59
+ return function detachStandardSchema() {
60
+ node.off(receipt);
61
+ clearStandardSchemaMessages(node);
62
+ for (const address of previousAddresses) {
63
+ const target = resolveAddress(node, address);
64
+ if (target)
65
+ clearStandardSchemaMessages(target);
66
+ }
67
+ };
68
+ }
@@ -0,0 +1,14 @@
1
+ import type { StandardSchemaV1 } from '@standard-schema/spec';
2
+ export interface FormKitStandardSchemaErrors {
3
+ localErrors: string[];
4
+ childErrors: Record<string, string[]>;
5
+ }
6
+ /**
7
+ * Normalizes a Standard Schema `Issue[]` array into FormKit's
8
+ * `setErrors`/`clearErrors` address shape. Mirrors `@nuxt/ui`'s own
9
+ * `validateStandardSchema()` path-flattening exactly (`item.key` for object
10
+ * path segments, the bare value otherwise, joined with `.`) so a Zod/Valibot
11
+ * issue at `items[2].email` resolves to the same FormKit node address
12
+ * (`"items.2.email"`) Nuxt UI's own forms would use for the same schema.
13
+ */
14
+ export declare function issuesToFormKitErrors(issues: readonly StandardSchemaV1.Issue[]): FormKitStandardSchemaErrors;
@@ -0,0 +1,15 @@
1
+ export function issuesToFormKitErrors(issues) {
2
+ const localErrors = [];
3
+ const childErrors = {};
4
+ for (const issue of issues) {
5
+ const address = issue.path?.map((item) => typeof item === "object" ? item.key : item).join(".");
6
+ if (!address) {
7
+ localErrors.push(issue.message);
8
+ continue;
9
+ }
10
+ if (!childErrors[address])
11
+ childErrors[address] = [];
12
+ childErrors[address].push(issue.message);
13
+ }
14
+ return { localErrors, childErrors };
15
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sfxcode/nuxt-ui-formkit",
3
- "version": "1.2.2",
3
+ "version": "1.2.3",
4
4
  "description": "FormKit integration for Nuxt UI - Seamlessly connect FormKit form handling with Nuxt UI components",
5
5
  "author": {
6
6
  "name": "Tom",
@@ -92,6 +92,7 @@
92
92
  "@formkit/nuxt": "^2.1.0",
93
93
  "@nuxt/kit": "4.4.8",
94
94
  "@nuxt/ui": "^4.9.0",
95
+ "@standard-schema/spec": "^1.1.0",
95
96
  "@vue/compiler-dom": "^3.5.39"
96
97
  },
97
98
  "devDependencies": {