Skip to content

Commit e723152

Browse files
Add componentField to Field's slot (#4270)
1 parent 16580b4 commit e723152

File tree

4 files changed

+67
-5
lines changed

4 files changed

+67
-5
lines changed

docs/src/pages/api/field.mdx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,3 +329,7 @@ An array containing a few listeners for the `input` event, it involves updating
329329
</CodeTitle>
330330

331331
An array containing a few listeners for the `change` event, it involves updating the field value and triggering validation.
332+
333+
#### `componentField`
334+
335+
Same as `field`, but provides value as `modelValue` to use with components

packages/vee-validate/src/Field.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,30 @@ interface ValidationTriggersProps {
1515
validateOnModelUpdate: boolean;
1616
}
1717

18-
interface FieldBindingObject<TValue = any> {
18+
interface SharedBindingObject<TValue = any> {
1919
name: string;
2020
onBlur: (e: Event) => void;
2121
onInput: (e: Event) => void;
2222
onChange: (e: Event) => void;
2323
'onUpdate:modelValue'?: ((e: TValue) => unknown) | undefined;
24+
}
25+
26+
interface FieldBindingObject<TValue = any> extends SharedBindingObject<TValue> {
2427
value?: TValue;
2528
checked?: boolean;
2629
}
2730

31+
interface ComponentFieldBindingObject<TValue = any> extends SharedBindingObject<TValue> {
32+
modelValue?: TValue;
33+
}
34+
2835
interface FieldSlotProps<TValue = unknown>
2936
extends Pick<
3037
FieldContext,
3138
'validate' | 'resetField' | 'handleChange' | 'handleReset' | 'handleBlur' | 'setTouched' | 'setErrors'
3239
> {
3340
field: FieldBindingObject<TValue>;
41+
componentField: ComponentFieldBindingObject<TValue>;
3442
value: TValue;
3543
meta: FieldMeta<TValue>;
3644
errors: string[];
@@ -147,7 +155,7 @@ const FieldImpl = /** #__PURE__ */ defineComponent({
147155
ctx.emit('update:modelValue', value.value);
148156
};
149157

150-
const fieldProps = computed(() => {
158+
const sharedProps = computed(() => {
151159
const { validateOnInput, validateOnChange, validateOnBlur, validateOnModelUpdate } =
152160
resolveValidationTriggers(props);
153161

@@ -185,6 +193,14 @@ const FieldImpl = /** #__PURE__ */ defineComponent({
185193

186194
attrs['onUpdate:modelValue'] = e => onChangeHandler(e, validateOnModelUpdate);
187195

196+
return attrs;
197+
});
198+
199+
const fieldProps = computed(() => {
200+
const attrs = {
201+
...sharedProps.value,
202+
};
203+
188204
if (hasCheckedAttr(ctx.attrs.type) && checked) {
189205
attrs.checked = checked.value;
190206
}
@@ -197,9 +213,17 @@ const FieldImpl = /** #__PURE__ */ defineComponent({
197213
return attrs;
198214
});
199215

216+
const componentProps = computed(() => {
217+
return {
218+
...sharedProps.value,
219+
modelValue: value.value,
220+
};
221+
});
222+
200223
function slotProps(): FieldSlotProps {
201224
return {
202225
field: fieldProps.value,
226+
componentField: componentProps.value,
203227
value: value.value,
204228
meta,
205229
errors: errors.value,

packages/vee-validate/tests/Field.spec.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { defineRule, configure } from '@/vee-validate';
1+
import { defineRule, configure, useForm } from '@/vee-validate';
22
import { mountWithHoc, setValue, dispatchEvent, setChecked, flushPromises, dispatchFileEvent } from './helpers';
33
import * as yup from 'yup';
44
import { computed, reactive, ref, Ref } from 'vue';
5+
import ModelComp from './helpers/ModelComp';
56

67
vi.useFakeTimers();
78

@@ -1208,4 +1209,36 @@ describe('<Field />', () => {
12081209
expect(lastCallOf(radioType)).toHaveProperty('checked');
12091210
expect(lastCallOf(radioType)).not.toHaveProperty('value');
12101211
});
1212+
1213+
describe('componentField', () => {
1214+
test('provides bindable object to components', async () => {
1215+
mountWithHoc({
1216+
components: {
1217+
ModelComp,
1218+
},
1219+
template: `
1220+
<Field name="name" v-slot="{ componentField, errorMessage, value }" rules="required">
1221+
<ModelComp v-bind="componentField" />
1222+
<span id="errors">{{ errorMessage }}</span>
1223+
<span id="values">{{ value }}</span>
1224+
</Field>
1225+
`,
1226+
});
1227+
1228+
await flushPromises();
1229+
const errorEl = document.getElementById('errors');
1230+
const valuesEl = document.getElementById('values');
1231+
const inputEl = document.querySelector('input') as HTMLInputElement;
1232+
expect(inputEl.getAttribute('name')).toBe('name');
1233+
setValue(inputEl, '');
1234+
dispatchEvent(inputEl, 'blur');
1235+
await flushPromises();
1236+
expect(errorEl?.textContent).toBe('This field is required');
1237+
setValue(inputEl, '123');
1238+
dispatchEvent(inputEl, 'blur');
1239+
await flushPromises();
1240+
expect(errorEl?.textContent).toBe('');
1241+
expect(valuesEl?.textContent).toBe('123');
1242+
});
1243+
});
12111244
});
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export default {
2-
props: ['modelValue', 'test'],
2+
props: ['modelValue', 'name', 'test'],
33
emits: ['blur', 'update:modelValue'],
4-
template: `<input type="text" :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" @blur="$emit('blur')">
4+
inheritAttrs: false,
5+
template: `<input type="text" :name="name" :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" @blur="$emit('blur')">
56
<div v-if="test">{{ test }}</div>`,
67
};

0 commit comments

Comments
 (0)