Browse Source

fix(@vben-core/form-ui): support native Zod string format defaults (#8372)

* fix(form-ui): support native zod string format defaults

* fix(form-ui): normalize zod formats before default extraction

* docs(form-ui): document zod default normalization
pull/8383/head
俞可 · YU KE 3 weeks ago
committed by GitHub
parent
commit
13ebc73b0d
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 5
      .changeset/bright-zebras-default.md
  2. 57
      packages/@core/ui-kit/form-ui/__tests__/form-integration.test.ts
  3. 58
      packages/@core/ui-kit/form-ui/src/use-form-context.ts

5
.changeset/bright-zebras-default.md

@ -0,0 +1,5 @@
---
'@vben-core/form-ui': patch
---
fix(@vben-core/form-ui): initialize native Zod string formats with empty values

57
packages/@core/ui-kit/form-ui/__tests__/form-integration.test.ts

@ -78,6 +78,63 @@ afterEach(() => {
});
describe('useVbenForm integration', () => {
it('provides empty defaults for native zod string formats', async () => {
const [Form, formApi] = useVbenForm({
schema: [
{
component: TestInput,
fieldName: 'email',
rules: z.email(),
},
{
component: TestInput,
fieldName: 'address',
rules: z.ipv4(),
},
],
showDefaultActions: false,
});
const wrapper = mount(Form);
wrappers.push(wrapper);
await flushPromises();
expect(await formApi.getValues()).toEqual({
address: '',
email: '',
});
});
it('filters native zod formats from nested default extraction', async () => {
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
const [Form, formApi] = useVbenForm({
schema: [
{
component: TestInput,
fieldName: 'profile',
rules: z.object({
email: z.email(),
address: z.object({
ip: z.ipv4(),
backup: z.email().default('backup@example.com'),
}),
}),
},
],
showDefaultActions: false,
});
const wrapper = mount(Form);
wrappers.push(wrapper);
await flushPromises();
expect(warn).not.toHaveBeenCalled();
expect(await formApi.getValues()).toEqual({
profile: {
address: { backup: 'backup@example.com', ip: '' },
email: '',
},
});
});
it('uses model updates as the primary channel and preserves empty strings', async () => {
const validateValue = vi.fn();
const [Form, formApi] = useVbenForm({

58
packages/@core/ui-kit/form-ui/src/use-form-context.ts

@ -9,7 +9,15 @@ import { computed, toRaw, unref, useSlots } from 'vue';
import { createContext } from '@vben-core/shadcn-ui';
import { isString, mergeWithArrayOverride, set } from '@vben-core/shared/utils';
import { object, ZodIntersection, ZodNumber, ZodObject, ZodString } from 'zod';
import {
object,
ZodIntersection,
ZodNumber,
ZodObject,
ZodString,
string as zodString,
ZodStringFormat,
} from 'zod';
import { getDefaultsForSchema } from 'zod-defaults';
import { useFormRuntime } from './form-runtime';
@ -26,6 +34,46 @@ export const [injectFormProps, provideFormProps] =
export const [injectComponentRefMap, provideComponentRefMap] =
createContext<Map<string, unknown>>('ComponentRefMap');
/**
* Rebuild the schema used by zod-defaults, replacing native format nodes that
* zod-defaults does not recognise while retaining supported wrappers.
*/
function normalizeSchemaForDefaults(rule: ZodType): ZodType {
const rawRule = toRaw(rule) as any;
if (rawRule instanceof ZodStringFormat) {
return zodString();
}
if (rawRule instanceof ZodObject) {
const shape = Object.fromEntries(
Object.entries(rawRule.shape).map(([key, value]) => [
key,
normalizeSchemaForDefaults(value as ZodType),
]),
);
return object(shape);
}
if (rawRule instanceof ZodIntersection) {
const { left, right } = (rawRule as any).def;
return normalizeSchemaForDefaults(left as ZodType).and(
normalizeSchemaForDefaults(right as ZodType),
);
}
if (rawRule.constructor.name === 'ZodDefault') {
const inner = normalizeSchemaForDefaults(rawRule.unwrap());
return inner.default(rawRule.def.defaultValue);
}
if (rawRule.constructor.name === 'ZodPipe') {
return normalizeSchemaForDefaults(rawRule.in).pipe(rawRule.out);
}
return rawRule;
}
export function useFormInitial(
props: ComputedRef<VbenFormProps> | VbenFormProps,
) {
@ -56,7 +104,7 @@ export function useFormInitial(
// 检查规则是否适合提取默认值
const rawRules = toRaw(item.rules);
const customDefaultValue = getCustomDefaultValue(rawRules);
zodObject[item.fieldName] = rawRules;
zodObject[item.fieldName] = normalizeSchemaForDefaults(rawRules);
if (customDefaultValue !== undefined) {
initialValues[item.fieldName] = customDefaultValue;
}
@ -74,8 +122,8 @@ export function useFormInitial(
// 自定义默认值提取逻辑
function getCustomDefaultValue(rule: any): any {
rule = toRaw(rule);
if (rule instanceof ZodString) {
return ''; // 默认为空字符串
if (rule instanceof ZodString || rule instanceof ZodStringFormat) {
return ''; // 字符串及其格式校验默认为空字符串
} else if (rule instanceof ZodNumber) {
return null; // 默认为 null(避免显示 0)
} else if (rule instanceof ZodObject) {
@ -86,7 +134,7 @@ export function useFormInitial(
}
return defaultValues;
} else if (rule instanceof ZodIntersection) {
return getDefaultsForSchema(rule);
return getDefaultsForSchema(normalizeSchemaForDefaults(rule) as any);
} else {
return undefined; // 其他类型不提供默认值
}

Loading…
Cancel
Save