From 36894378aa3636eeb4fb54aa747319e21c6eb5cd Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Sat, 10 Jul 2021 18:59:47 +0200 Subject: [PATCH] feat: add standalone prop for fields (#3379) --- docs/content/api/field.md | 31 ++++++++++----------- docs/content/api/use-field.md | 1 + packages/vee-validate/src/Field.ts | 6 +++++ packages/vee-validate/src/useField.ts | 23 +++++++++++++--- packages/vee-validate/tests/Form.spec.ts | 34 ++++++++++++++++++++++++ 5 files changed, 76 insertions(+), 19 deletions(-) diff --git a/docs/content/api/field.md b/docs/content/api/field.md index 830aa6759..777cb8fd4 100644 --- a/docs/content/api/field.md +++ b/docs/content/api/field.md @@ -123,21 +123,22 @@ Note that you no longer should use `v-model` on your input as `v-bind="field"` w ### Props -| Prop | Type | Required/Default | Description | -| :-------------------- | :----------------------------- | :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| as | `string` | `"span"` | The element to render as a root node, defaults to `input` | -| name | `string` | Required | The field's name, must be inside `
` | -| rules | `object \| string \| Function` | `null` | The field's validation rules | -| validateOnMount | `boolean` | `false` | If true, field will be validated when the component is mounted | -| validateOnInput | `boolean` | `false` | If true, field will be validated when `input` event is dispatched/emitted | -| validateOnChange | `boolean` | `true` | If true, field will be validated when `change` event is dispatched/emitted | -| validateOnBlur | `boolean` | `true` | If true, field will be validated when `blur` event is dispatched/emitted | -| validateOnModelUpdate | `boolean` | `true` | If true, field will be validated when `update:modelValue` event is emitted | -| bails | `boolean` | `true` | Stops validating as soon as a rule fails the validation | -| label | `string` | `undefined` | A different string to override the field `name` prop in error messages, useful for display better or formatted names. The generated message won't be updated if this prop changes, you will need to re-validate the input. | -| value | `any` | `undefined` | The field's initial value, optional as long as the field type is not `checkbox` or `radio`. | -| type | `string` | `undefined` | The field type, must be provided if you want your field to behave as a `checkbox` or a `radio` input. | -| unchecked-value | `any` | `undefined` | Only useful when the `type="checkbox"` and the field is a single checkbox field (not bound to an array value). Controls the input's value when it's unchecked. | +| Prop | Type | Required/Default | Description | +| :-------------------- | :----------------------------- | :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| as | `string` | `"span"` | The element to render as a root node, defaults to `input` | +| name | `string` | Required | The field's name, must be inside `` | +| rules | `object \| string \| Function` | `null` | The field's validation rules | +| validateOnMount | `boolean` | `false` | If true, field will be validated when the component is mounted | +| validateOnInput | `boolean` | `false` | If true, field will be validated when `input` event is dispatched/emitted | +| validateOnChange | `boolean` | `true` | If true, field will be validated when `change` event is dispatched/emitted | +| validateOnBlur | `boolean` | `true` | If true, field will be validated when `blur` event is dispatched/emitted | +| validateOnModelUpdate | `boolean` | `true` | If true, field will be validated when `update:modelValue` event is emitted | +| bails | `boolean` | `true` | Stops validating as soon as a rule fails the validation | +| label | `string` | `undefined` | A different string to override the field `name` prop in error messages, useful for display better or formatted names. The generated message won't be updated if this prop changes, you will need to re-validate the input. | +| value | `any` | `undefined` | The field's initial value, optional as long as the field type is not `checkbox` or `radio`. | +| type | `string` | `undefined` | The field type, must be provided if you want your field to behave as a `checkbox` or a `radio` input. | +| unchecked-value | `any` | `undefined` | Only useful when the `type="checkbox"` and the field is a single checkbox field (not bound to an array value). Controls the input's value when it's unchecked. | +| standalone | `boolean` | `false` | Excludes the field from participating in any `Form` or `useForm` contexts, useful for creating inputs that do contribute to the `values` object. In other words, the form won't pick up or validate fields marked as standalone | ### Slots diff --git a/docs/content/api/use-field.md b/docs/content/api/use-field.md index db5c94bb0..25ac00c19 100644 --- a/docs/content/api/use-field.md +++ b/docs/content/api/use-field.md @@ -90,6 +90,7 @@ interface FieldOptions { type?: string; // The input type, can be any string. Toggles specific toggle mode for `checkbox` checkedValue?: string; // Used the input type is `checkbox` or `radio` otherwise ignored uncheckedValue?: string; // Used the input type is `checkbox` otherwise ignored + standalone?: boolean; // Excludes the field from participating in any `Form` or `useForm` contexts, useful for creating inputs that do contribute to the `values`, In other words, the form won't pick up or validate fields marked as standalone } interface ValidationResult { diff --git a/packages/vee-validate/src/Field.ts b/packages/vee-validate/src/Field.ts index 0332ae43b..add84a002 100644 --- a/packages/vee-validate/src/Field.ts +++ b/packages/vee-validate/src/Field.ts @@ -53,6 +53,7 @@ export const Field = defineComponent({ type: Boolean, default: () => getConfig().bails, }, + label: { type: String, default: undefined, @@ -73,6 +74,10 @@ export const Field = defineComponent({ type: null as unknown as PropType<((e: any) => unknown) | undefined>, default: undefined, }, + standalone: { + type: Boolean, + default: false, + }, }, setup(props, ctx) { const rules = toRef(props, 'rules'); @@ -98,6 +103,7 @@ export const Field = defineComponent({ } = useField(name, rules, { validateOnMount: props.validateOnMount, bails: props.bails, + standalone: props.standalone, type: ctx.attrs.type as string, initialValue: resolveInitialValue(props, ctx), // Only for checkboxes and radio buttons diff --git a/packages/vee-validate/src/useField.ts b/packages/vee-validate/src/useField.ts index 986a61b78..87d423f75 100644 --- a/packages/vee-validate/src/useField.ts +++ b/packages/vee-validate/src/useField.ts @@ -50,6 +50,7 @@ interface FieldOptions { checkedValue?: MaybeRef; uncheckedValue?: MaybeRef; label?: MaybeRef; + standalone?: boolean; } type RuleExpression = @@ -71,10 +72,20 @@ export function useField( opts?: Partial> ): FieldComposable { const fid = ID_COUNTER >= Number.MAX_SAFE_INTEGER ? 0 : ++ID_COUNTER; - const { initialValue, validateOnMount, bails, type, checkedValue, label, validateOnValueUpdate, uncheckedValue } = - normalizeOptions(unref(name), opts); + const { + initialValue, + validateOnMount, + bails, + type, + checkedValue, + label, + validateOnValueUpdate, + uncheckedValue, + standalone, + } = normalizeOptions(unref(name), opts); + + const form = !standalone ? injectWithSelf(FormContextSymbol) : undefined; - const form = injectWithSelf(FormContextSymbol); const { meta, errors, @@ -92,6 +103,7 @@ export function useField( form, type, checkedValue, + standalone, }); const normalizedRules = computed(() => { @@ -293,6 +305,7 @@ function normalizeOptions(name: string, opts: Partial({ form, type, checkedValue, + standalone, }: { name: MaybeRef; checkedValue?: MaybeRef; initValue?: MaybeRef; form?: FormContext; type?: string; + standalone?: boolean; }) { const { errors, errorMessage, setErrors } = useFieldErrors(name, form); - const formInitialValues = injectWithSelf(FormInitialValuesSymbol, undefined); + const formInitialValues = standalone ? undefined : injectWithSelf(FormInitialValuesSymbol, undefined); // clones the ref value to a mutable version const initialValueRef = ref(unref(initValue)) as Ref; diff --git a/packages/vee-validate/tests/Form.spec.ts b/packages/vee-validate/tests/Form.spec.ts index 539f522c6..4c616497c 100644 --- a/packages/vee-validate/tests/Form.spec.ts +++ b/packages/vee-validate/tests/Form.spec.ts @@ -2143,4 +2143,38 @@ describe('', () => { // field was re-checked expect(span.textContent).toBe(''); }); + + test('standalone fields are excluded from form state', async () => { + const wrapper = mountWithHoc({ + setup() { + return {}; + }, + template: ` + + + + {{ errorMessage }} + + {{ errors.fname }} + {{ meta.valid }} + + `, + }); + + await flushPromises(); + const formError = wrapper.$el.querySelector('#formError'); + const fieldError = wrapper.$el.querySelector('#fieldError'); + const meta = wrapper.$el.querySelector('#meta'); + + expect(formError.textContent).toBe(''); + expect(fieldError.textContent).toBe(''); + expect(meta.textContent).toBe('true'); + + setValue(wrapper.$el.querySelector('input'), ''); + await flushPromises(); + + expect(formError.textContent).toBe(''); + expect(fieldError.textContent).toBe(REQUIRED_MESSAGE); + expect(meta.textContent).toBe('true'); + }); });