fix(@vben-core/form-ui): correct field slot props (#8216)
This commit is contained in:
@@ -104,6 +104,82 @@ describe('useVbenForm integration', () => {
|
|||||||
expect(validateValue).toHaveBeenCalledTimes(initialValidationCount + 1);
|
expect(validateValue).toHaveBeenCalledTimes(initialValidationCount + 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('keeps only the active model protocol and boolean disabled in field slots', async () => {
|
||||||
|
interface ModelProtocolValues {
|
||||||
|
defaultField: string;
|
||||||
|
valueField: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let defaultSlotProps: Record<string, any> | undefined;
|
||||||
|
let valueSlotProps: Record<string, any> | undefined;
|
||||||
|
const [Form, formApi] = useVbenForm<ModelProtocolValues>({
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
component: TestInput,
|
||||||
|
defaultValue: 'default-initial',
|
||||||
|
fieldName: 'defaultField',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: TestInput,
|
||||||
|
componentProps: {
|
||||||
|
eventMode: 'value-and-change',
|
||||||
|
modelValue: 'stale-model-value',
|
||||||
|
},
|
||||||
|
defaultValue: 'value-initial',
|
||||||
|
fieldName: 'valueField',
|
||||||
|
modelPropName: 'value',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const wrapper = mount(Form, {
|
||||||
|
slots: {
|
||||||
|
defaultField(slotProps: Record<string, any>) {
|
||||||
|
defaultSlotProps = slotProps;
|
||||||
|
return h(TestInput, {
|
||||||
|
class: 'default-protocol-input',
|
||||||
|
modelValue: slotProps.modelValue,
|
||||||
|
'onUpdate:modelValue': slotProps['onUpdate:modelValue'],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
valueField(slotProps: Record<string, any>) {
|
||||||
|
valueSlotProps = slotProps;
|
||||||
|
return h(TestInput, {
|
||||||
|
class: 'value-protocol-input',
|
||||||
|
eventMode: slotProps.eventMode,
|
||||||
|
value: slotProps.value,
|
||||||
|
'onUpdate:value': slotProps['onUpdate:value'],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
wrappers.push(wrapper);
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(defaultSlotProps).toBeDefined();
|
||||||
|
expect(valueSlotProps).toBeDefined();
|
||||||
|
if (!defaultSlotProps || !valueSlotProps) return;
|
||||||
|
|
||||||
|
expect(defaultSlotProps.disabled).toBe(false);
|
||||||
|
expect(defaultSlotProps.modelValue).toBe('default-initial');
|
||||||
|
expect(defaultSlotProps).toHaveProperty('onUpdate:modelValue');
|
||||||
|
expect(defaultSlotProps).not.toHaveProperty('value');
|
||||||
|
|
||||||
|
expect(valueSlotProps.disabled).toBe(false);
|
||||||
|
expect(valueSlotProps.value).toBe('value-initial');
|
||||||
|
expect(valueSlotProps).toHaveProperty('onUpdate:value');
|
||||||
|
expect(valueSlotProps).not.toHaveProperty('modelValue');
|
||||||
|
expect(valueSlotProps).not.toHaveProperty('onUpdate:modelValue');
|
||||||
|
|
||||||
|
await wrapper.get('.default-protocol-input').setValue('default-updated');
|
||||||
|
await wrapper.get('.value-protocol-input').setValue('value-updated');
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(await formApi.getValues()).toEqual({
|
||||||
|
defaultField: 'default-updated',
|
||||||
|
valueField: 'value-updated',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('keeps values reactive when exposed through the default slot', async () => {
|
it('keeps values reactive when exposed through the default slot', async () => {
|
||||||
const [Form, formApi] = useVbenForm({
|
const [Form, formApi] = useVbenForm({
|
||||||
schema: [
|
schema: [
|
||||||
|
|||||||
@@ -147,6 +147,24 @@ describe('form public types', () => {
|
|||||||
expectTypeOf<
|
expectTypeOf<
|
||||||
DefaultSlotProps['values']
|
DefaultSlotProps['values']
|
||||||
>().toEqualTypeOf<AccountFormValues>();
|
>().toEqualTypeOf<AccountFormValues>();
|
||||||
|
|
||||||
|
const [WideForm] = useVbenForm<Record<string, unknown>>({ schema: [] });
|
||||||
|
type WideFormSlots = InstanceType<typeof WideForm>['$slots'];
|
||||||
|
type WideFieldSlot = NonNullable<WideFormSlots['dynamic-field']>;
|
||||||
|
type WideFieldSlotProps = Parameters<WideFieldSlot>[0];
|
||||||
|
|
||||||
|
expectTypeOf<WideFieldSlotProps>().not.toBeAny();
|
||||||
|
expectTypeOf<WideFieldSlotProps['modelValue']>().toEqualTypeOf<unknown>();
|
||||||
|
expectTypeOf<
|
||||||
|
WideFieldSlotProps['field']['state']['value']
|
||||||
|
>().toEqualTypeOf<unknown>();
|
||||||
|
expectTypeOf<
|
||||||
|
WideFieldSlotProps['componentField']['modelValue']
|
||||||
|
>().toEqualTypeOf<unknown>();
|
||||||
|
expectTypeOf<WideFieldSlotProps['name']>().toBeString();
|
||||||
|
expectTypeOf<WideFieldSlotProps['values']>().toEqualTypeOf<
|
||||||
|
Record<string, unknown>
|
||||||
|
>();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('keeps form and submit values distinct with a codec', () => {
|
it('keeps form and submit values distinct with a codec', () => {
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ watch(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const shouldDisabled = computed(() => {
|
const shouldDisabled = computed(() => {
|
||||||
return isDisabled.value || disabled || computedProps.value?.disabled;
|
return Boolean(isDisabled.value || disabled || computedProps.value?.disabled);
|
||||||
});
|
});
|
||||||
|
|
||||||
const customContentRender = computed(() => {
|
const customContentRender = computed(() => {
|
||||||
@@ -307,14 +307,20 @@ function createFieldSlotProps(slotProps: RuntimeFieldSlotProps) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function fieldBindEvent(componentField: Record<string, any>) {
|
function resolveModelPropName() {
|
||||||
|
return (
|
||||||
|
modelPropName ||
|
||||||
|
(isString(component) ? componentBindEventMap.value?.[component] : null)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fieldBindEvent(
|
||||||
|
componentField: Record<string, any>,
|
||||||
|
bindEventField: null | string | undefined,
|
||||||
|
) {
|
||||||
const modelValue = componentField.modelValue;
|
const modelValue = componentField.modelValue;
|
||||||
const handler = componentField['onUpdate:modelValue'];
|
const handler = componentField['onUpdate:modelValue'];
|
||||||
|
|
||||||
const bindEventField =
|
|
||||||
modelPropName ||
|
|
||||||
(isString(component) ? componentBindEventMap.value?.[component] : null);
|
|
||||||
|
|
||||||
let value = modelValue;
|
let value = modelValue;
|
||||||
// antd design 的一些组件会传递一个 event 对象
|
// antd design 的一些组件会传递一个 event 对象
|
||||||
if (modelValue && isObject(modelValue) && bindEventField) {
|
if (modelValue && isObject(modelValue) && bindEventField) {
|
||||||
@@ -348,7 +354,11 @@ function fieldBindEvent(componentField: Record<string, any>) {
|
|||||||
|
|
||||||
function createComponentProps(slotProps: RuntimeFieldSlotProps) {
|
function createComponentProps(slotProps: RuntimeFieldSlotProps) {
|
||||||
const normalizedSlotProps = createFieldSlotProps(slotProps);
|
const normalizedSlotProps = createFieldSlotProps(slotProps);
|
||||||
const bindEvents = fieldBindEvent(normalizedSlotProps.componentField);
|
const bindEventField = resolveModelPropName();
|
||||||
|
const bindEvents = fieldBindEvent(
|
||||||
|
normalizedSlotProps.componentField,
|
||||||
|
bindEventField,
|
||||||
|
);
|
||||||
|
|
||||||
const binds = {
|
const binds = {
|
||||||
...normalizedSlotProps.componentField,
|
...normalizedSlotProps.componentField,
|
||||||
@@ -361,6 +371,10 @@ function createComponentProps(slotProps: RuntimeFieldSlotProps) {
|
|||||||
? { onInput: computedProps.value.onInput }
|
? { onInput: computedProps.value.onInput }
|
||||||
: {}),
|
: {}),
|
||||||
};
|
};
|
||||||
|
if (bindEventField && bindEventField !== 'modelValue') {
|
||||||
|
Reflect.deleteProperty(binds, 'modelValue');
|
||||||
|
Reflect.deleteProperty(binds, 'onUpdate:modelValue');
|
||||||
|
}
|
||||||
|
|
||||||
return binds;
|
return binds;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -235,16 +235,19 @@ export interface VbenFormDefaultSlotProps<
|
|||||||
|
|
||||||
export interface VbenFormFieldSlotProps<
|
export interface VbenFormFieldSlotProps<
|
||||||
TValues extends FormValues = FormValues,
|
TValues extends FormValues = FormValues,
|
||||||
TFieldName extends KnownFormFieldName<TValues> = KnownFormFieldName<TValues>,
|
TFieldName extends FormFieldName<TValues> = FormFieldName<TValues>,
|
||||||
T extends BaseFormComponentType = BaseFormComponentType,
|
T extends BaseFormComponentType = BaseFormComponentType,
|
||||||
P extends Record<string, any> = Record<never, never>,
|
P extends Record<string, any> = Record<never, never>,
|
||||||
TSubmitValues extends FormValues = TValues,
|
TSubmitValues extends FormValues = TValues,
|
||||||
> extends VbenFormActionSlotProps<TValues, T, P, TSubmitValues> {
|
> extends VbenFormActionSlotProps<TValues, T, P, TSubmitValues> {
|
||||||
componentField: FormComponentField<TValues[TFieldName], TFieldName>;
|
componentField: FormComponentField<
|
||||||
|
FormFieldValue<TValues, TFieldName>,
|
||||||
|
TFieldName
|
||||||
|
>;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
field: FormRuntimeField<TValues[TFieldName]>;
|
field: FormRuntimeField<FormFieldValue<TValues, TFieldName>>;
|
||||||
isInValid: boolean;
|
isInValid: boolean;
|
||||||
modelValue: TValues[TFieldName];
|
modelValue: FormFieldValue<TValues, TFieldName>;
|
||||||
name: TFieldName;
|
name: TFieldName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,7 +258,19 @@ type VbenFormFieldSlots<
|
|||||||
TSubmitValues extends FormValues,
|
TSubmitValues extends FormValues,
|
||||||
> =
|
> =
|
||||||
string extends Extract<keyof TValues, string>
|
string extends Extract<keyof TValues, string>
|
||||||
? Record<string, ((props: any) => any) | undefined>
|
? Record<
|
||||||
|
string,
|
||||||
|
| ((
|
||||||
|
props: VbenFormFieldSlotProps<
|
||||||
|
TValues,
|
||||||
|
FormFieldName<TValues>,
|
||||||
|
T,
|
||||||
|
P,
|
||||||
|
TSubmitValues
|
||||||
|
>,
|
||||||
|
) => any)
|
||||||
|
| undefined
|
||||||
|
>
|
||||||
: {
|
: {
|
||||||
[TFieldName in KnownFormFieldName<TValues>]?: (
|
[TFieldName in KnownFormFieldName<TValues>]?: (
|
||||||
props: VbenFormFieldSlotProps<
|
props: VbenFormFieldSlotProps<
|
||||||
|
|||||||
Reference in New Issue
Block a user