diff --git a/apps/docs/.vitepress/config.mts b/apps/docs/.vitepress/config.mts index ba7b598..992094c 100644 --- a/apps/docs/.vitepress/config.mts +++ b/apps/docs/.vitepress/config.mts @@ -1,5 +1,6 @@ import { defineConfig } from 'vitepress' import { groupIconMdPlugin, groupIconVitePlugin } from 'vitepress-plugin-group-icons' +import { tabsMarkdownPlugin } from 'vitepress-plugin-tabs' import path from 'path' // https://vitepress.dev/reference/site-config @@ -11,6 +12,7 @@ export default defineConfig({ markdown: { config(md) { md.use(groupIconMdPlugin) + md.use(tabsMarkdownPlugin) } }, themeConfig: { @@ -41,7 +43,8 @@ export default defineConfig({ { text: 'useFieldEmits', link: '/guide/composables/useFieldEmits' }, { text: 'useFieldProps', link: '/guide/composables/useFieldProps' }, { text: 'useFieldValidate', link: '/guide/composables/useFieldValidate' }, - { text: 'useFormModel', link: '/guide/composables/useFormModel' } + { text: 'useFormModel', link: '/guide/composables/useFormModel' }, + { text: 'useValidation', link: '/guide/composables/useValidation' } ] }, { diff --git a/apps/docs/.vitepress/theme/index.js b/apps/docs/.vitepress/theme/index.js index ea4628c..b63094d 100644 --- a/apps/docs/.vitepress/theme/index.js +++ b/apps/docs/.vitepress/theme/index.js @@ -1,6 +1,8 @@ import DefaultTheme from 'vitepress/theme' import VueFormGenerator from '@/index' +import { enhanceAppWithTabs } from 'vitepress-plugin-tabs/client' + import './index.css' import '@/scss/themes/basic.scss' import 'virtual:group-icons.css' @@ -8,6 +10,7 @@ import 'virtual:group-icons.css' export default { extends: DefaultTheme, enhanceApp({ app }) { + enhanceAppWithTabs(app) app.use(VueFormGenerator, { messages: { productCodeValidator: 'Your product code is invalid' diff --git a/apps/docs/guide/composables/useFieldEmits.md b/apps/docs/guide/composables/useFieldEmits.md index 1eadcf9..a3f80d5 100644 --- a/apps/docs/guide/composables/useFieldEmits.md +++ b/apps/docs/guide/composables/useFieldEmits.md @@ -13,6 +13,15 @@ const emits = defineEmits(useFieldEmits()) ``` +## TypeScript alternative +```vue + +``` + ## Emits ### `onInput` diff --git a/apps/docs/guide/composables/useFieldProps.md b/apps/docs/guide/composables/useFieldProps.md index c0ce97a..1907d14 100644 --- a/apps/docs/guide/composables/useFieldProps.md +++ b/apps/docs/guide/composables/useFieldProps.md @@ -16,6 +16,20 @@ const { field, model } = toRefs(props) ``` +## TypeScript alternative +```vue + +``` + ## Props ### `id` diff --git a/apps/docs/guide/composables/useFieldValidate.md b/apps/docs/guide/composables/useFieldValidate.md index 8219462..8aeee7c 100644 --- a/apps/docs/guide/composables/useFieldValidate.md +++ b/apps/docs/guide/composables/useFieldValidate.md @@ -1,8 +1,11 @@ --- outline: [2,3] --- -# useFieldValidate -> Used to validate a field against validators defined in a fields schema +# useFieldValidate +> Used to validate a field against validators defined in a field's schema +::: warning +This composable is deprecated, please use [`useValidation`](/guide/composables/useValidation) instead +::: ## Usage ```vue diff --git a/apps/docs/guide/composables/useValidation.md b/apps/docs/guide/composables/useValidation.md new file mode 100644 index 0000000..cd7f97a --- /dev/null +++ b/apps/docs/guide/composables/useValidation.md @@ -0,0 +1,119 @@ +--- +outline: [2,3] +--- +# useValidation +> Used to validate a field against validators defined in a field's schema + +## Usage +::: code-group +```Vue [Vue] + +``` +```Vue [Vue TS] + +``` +::: + +## Arguments + +### `model` +Model object, as returned by the props + +### `field` +Field schema object, as returned by the props + +### `currentModelValue` +`Ref` of the current value from the field. Returned by [`useFormModel`](/guide/composables/useFormModel). + +### `formOptions` +Form options object, as returned by the props. + +### `emits` +Emit function as returned by `defineEmits()` + +### `isDisabled` +Whether the field is disabled, can be obtained from [`useFieldAttributes()`](/guide/composables/useFieldAttributes) + +### `isRequired` +Whether the field is required, can be obtained from [`useFieldAttributes()`](/guide/composables/useFieldAttributes) + +### `isReadonly` +Whether the field is readonly, can be obtained from [`useFieldAttributes()`](/guide/composables/useFieldAttributes) + +## Returns + +### `errors` +An array of errors for the current field. Will be auto-updated on every validation cycle. Must be cleared +manually when the value of a field has changed. + +### `validate` +A validation function, meant to be called when a validation has to take place. Used when a field is always validated +at the same moment and isn't affected by validation triggers, such as `'onChanged'` or `'onBlur'`. + +### `onChanged` +A wrapped validation function, only validates when `'onChanged'` validation is enabled. Should always be called if the value is changed, unless the field is unaffected by +validation triggers. + +### `onBlur` +A wrapped validation function, only validates when `'onBlur'` validation is enabled (which is the default behavior). Should always be called if the value is changed, unless the field is unaffected by +validation triggers. diff --git a/apps/docs/guide/customization/custom-components.md b/apps/docs/guide/customization/custom-components.md index 77f05f3..68f0b99 100644 --- a/apps/docs/guide/customization/custom-components.md +++ b/apps/docs/guide/customization/custom-components.md @@ -15,35 +15,21 @@ To create a field component you make use of different composables ([?](https://v get the behaviour you want the component to have. Different composables handle different functionality inside the field component. +:::tabs +== JavaScript Every component must at least use these composables to work properly: - [`useFieldEmits`](/guide/composables/useFieldEmits) - returns all events emitted by a field component; - [`useFieldProps`](/guide/composables/useFieldProps) - returns all props used by a field component; - [`useFormModel`](/guide/composables/useFormModel) - used to get the current model value for this field component. Optional: -- [`useFieldValidate`](/guide/composables/useFieldValidate) - used for validation of the field; +- [`useValidation`](/guide/composables/useValidation) - used for validation of the field; - [`useFieldAttributes`](/guide/composables/useFieldAttributes) - holds different dynamic field attributes like `required` and `readonly`. ### Basic example ::: code-group -```vue [template] - -``` + ```vue [script setup] ``` ::: + ### Advanced example For a more advanced example, you can take a look at the [`FieldSelect`](/guide/fields/FieldSelect) ([source](https://github.com/kevinkosterr/vue3-form-generator/blob/69cb6aeb8e8c82926ec3598e7d73be2d1146a3f2/src/fields/core/FieldSelect.vue)) component. ## Compatibility with validation ::: info If you want your component to be compatible with validation, you'll need to expose the `errors` value that is returned -by [`useFieldValidate`](/guide/composables/useFieldValidate) +by [`useValidation`](/guide/composables/useValidation) ::: ## Registering your component diff --git a/apps/docs/guide/form-generator/props.md b/apps/docs/guide/form-generator/props.md index 14e8c28..f67b8e9 100644 --- a/apps/docs/guide/form-generator/props.md +++ b/apps/docs/guide/form-generator/props.md @@ -12,9 +12,10 @@ outline: [ 2,3 ] | `idPrefix` | `string` | Prefix for all the generated ids in the form | ## `formOptions` -| Property | Type | Description | -|---------------|----------|------------------------------------------------------| -| `idPrefix` | `string` | Prefix for all the generated ids in the form | +| Property | Type | Default | Description | +|------------|--------------------------|--------|-----------------------------------------------------------------------------------------------------| +| `idPrefix` | `string` | | Prefix for all the generated ids in the form | +| `validate` | `'onChanged'` \| `'onBlur'` | `onBlur` | Method of validation, can be overwritten by individual fields. Can be either `onChanged` or `onBlur` | ## `model` Type: `Object` diff --git a/apps/docs/parts/customization/custom-components-template-example.md b/apps/docs/parts/customization/custom-components-template-example.md new file mode 100644 index 0000000..16f4308 --- /dev/null +++ b/apps/docs/parts/customization/custom-components-template-example.md @@ -0,0 +1,17 @@ +```vue [template] + +``` \ No newline at end of file diff --git a/apps/docs/parts/shared-field-properties.md b/apps/docs/parts/shared-field-properties.md index c857ba8..dcc9da8 100644 --- a/apps/docs/parts/shared-field-properties.md +++ b/apps/docs/parts/shared-field-properties.md @@ -1,17 +1,18 @@ -| Property | Default | Type | Description | -|-------------|------------|---------------------------------------------|-------------------------------------------------------------------------------------------------| -| name | - | `string` | Name of the field | -| model | - | `string` | Key of model in the form schema model | -| label | - | `string` | Label for the field | -| type | - | `string` | Type of field, generally `input` if the field is an input. | -| inputType | - | `string` | Type of input, only required when `type === 'input'` | +| Property | Default | Type | Description | +|-------------|-----------|---------------------------------------------|-------------------------------------------------------------------------------------------------| +| name | - | `string` | Name of the field | +| model | - | `string` | Key of model in the form schema model | +| label | - | `string` | Label for the field | +| type | - | `string` | Type of field, generally `input` if the field is an input. | +| inputType | - | `string` | Type of input, only required when `type === 'input'` | | id | _computed_ | `string` | `id` of the field | -| visible | `true` | `Boolean \| Function` | Whether the field is visible, method will be passed the `model`, `field` and field component* | -| required | `false` | `Boolean \| Function` | Whether the field is required, method will be passed the `model`, `field` and field component* | -| readonly | `false` | `Boolean \| Function` | Whether the field is read only, method will be passed the `model`, `field` and field component* | -| disabled | `false` | `Boolean \| Function` | Whether the field is disabled, method will be passed the `model`, `field` and field component* | -| hint | - | `string \| Function` | Hint to display underneath the field, can be passed a method* | +| visible | `true` | `Boolean \| Function` | Whether the field is visible, method will be passed the `model`, `field` and field component* | +| required | `false` | `Boolean \| Function` | Whether the field is required, method will be passed the `model`, `field` and field component* | +| readonly | `false` | `Boolean \| Function` | Whether the field is read only, method will be passed the `model`, `field` and field component* | +| disabled | `false` | `Boolean \| Function` | Whether the field is disabled, method will be passed the `model`, `field` and field component* | +| hint | - | `string \| Function` | Hint to display underneath the field, can be passed a method* | | validator | _computed_ | `Array \| Function \| undefined` | A (list of) validator(s) to be validating the field against. | -| onValidated | - | `Function \| undefined` | Method to be called after validation has been completed. | +| validate | `onBlur` | `'onChanged'` \| `'onBlur'` | Method of validation for the field. | +| onValidated | - | `Function \| undefined` | Method to be called after validation has been completed. | _*_ see [determineDynamicAttribute()](/guide/mixins/abstractField#determinedynamicattribute) for more information. \ No newline at end of file diff --git a/package.json b/package.json index dbc5e92..8294b37 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "vite-plugin-dts": "^4.3.0", "vitepress": "^1.6.3", "vitepress-plugin-group-icons": "^1.5.5", + "vitepress-plugin-tabs": "^0.7.1", "vitest": "^2.1.1", "vue": "^3.5.6", "vue-eslint-parser": "^9.4.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 65c15ee..50515c5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -83,6 +83,9 @@ importers: vitepress-plugin-group-icons: specifier: ^1.5.5 version: 1.5.5(markdown-it@14.1.0)(vite@5.4.19(sass@1.82.0)(terser@5.37.0)) + vitepress-plugin-tabs: + specifier: ^0.7.1 + version: 0.7.1(vitepress@1.6.3(@algolia/client-search@5.25.0)(postcss@8.4.49)(sass@1.82.0)(search-insights@2.17.3)(terser@5.37.0)(typescript@5.7.2))(vue@3.5.13(typescript@5.7.2)) vitest: specifier: '>=2.1.9' version: 3.1.3(jsdom@25.0.1)(sass@1.82.0)(terser@5.37.0) @@ -3040,6 +3043,12 @@ packages: markdown-it: '>=14' vite: '>=3' + vitepress-plugin-tabs@0.7.1: + resolution: {integrity: sha512-jxJvsicxnMSIYX9b8mAFLD2nwyKUcMO10dEt4nDSbinZhM8cGvAmMFOHPdf6TBX6gYZRl+/++/iYHtoM14fERQ==} + peerDependencies: + vitepress: ^1.0.0 + vue: ^3.5.0 + vitepress@1.6.3: resolution: {integrity: sha512-fCkfdOk8yRZT8GD9BFqusW3+GggWYZ/rYncOfmgcDtP3ualNHCAg+Robxp2/6xfH1WwPHtGpPwv7mbA3qomtBw==} hasBin: true @@ -6188,6 +6197,11 @@ snapshots: transitivePeerDependencies: - supports-color + vitepress-plugin-tabs@0.7.1(vitepress@1.6.3(@algolia/client-search@5.25.0)(postcss@8.4.49)(sass@1.82.0)(search-insights@2.17.3)(terser@5.37.0)(typescript@5.7.2))(vue@3.5.13(typescript@5.7.2)): + dependencies: + vitepress: 1.6.3(@algolia/client-search@5.25.0)(postcss@8.4.49)(sass@1.82.0)(search-insights@2.17.3)(terser@5.37.0)(typescript@5.7.2) + vue: 3.5.13(typescript@5.7.2) + vitepress@1.6.3(@algolia/client-search@5.25.0)(postcss@8.4.49)(sass@1.82.0)(search-insights@2.17.3)(terser@5.37.0)(typescript@5.7.2): dependencies: '@docsearch/css': 3.8.2 diff --git a/src/FormGenerator.vue b/src/FormGenerator.vue index 19e0934..141579f 100644 --- a/src/FormGenerator.vue +++ b/src/FormGenerator.vue @@ -51,7 +51,9 @@ const props = withDefaults(defineProps(), { enctype: 'application/x-www-form-urlencoded', id: '', idPrefix: '', // Kept for compatibility reasons. - options: () => ({}) + options: () => ({ + validate: 'onBlur' // Always validate onBlur by default. + }) }) type FormGroupInstance = ComponentPublicInstance> diff --git a/src/composables/index.ts b/src/composables/index.ts index ef00f9b..61a9cac 100644 --- a/src/composables/index.ts +++ b/src/composables/index.ts @@ -3,11 +3,15 @@ import { useFieldValidate } from '@/composables/useFieldValidate' import { useFieldAttributes } from '@/composables/useFieldAttributes' import { useFieldProps } from '@/composables/useFieldProps' import { useFieldEmits } from '@/composables/useFieldEmits' +import { useValidationWrapper } from '@/composables/useValidationWrapper' +import { useValidation } from '@/composables/useValidation' export { useFormModel, useFieldValidate, useFieldAttributes, useFieldProps, - useFieldEmits + useFieldEmits, + useValidationWrapper, + useValidation } \ No newline at end of file diff --git a/src/composables/useFieldEmits.ts b/src/composables/useFieldEmits.ts index 760b8c1..fb36aa9 100644 --- a/src/composables/useFieldEmits.ts +++ b/src/composables/useFieldEmits.ts @@ -1,3 +1,5 @@ -export function useFieldEmits (): string[] { +import type { FieldEmits } from '@/resources/types/field/fields' + +export function useFieldEmits (): (keyof FieldEmits)[] { return [ 'onInput', 'validated' ] } \ No newline at end of file diff --git a/src/composables/useFieldValidate.ts b/src/composables/useFieldValidate.ts index a169fc8..c49c7d9 100644 --- a/src/composables/useFieldValidate.ts +++ b/src/composables/useFieldValidate.ts @@ -1,32 +1,14 @@ import { ref, Ref, computed, ComputedRef } from 'vue' import { getMessage } from '@/validators/messages' -import { isFunction, isString, toUniqueArray } from '@/helpers' +import { isFunction, toUniqueArray, getValidator } from '@/helpers' import { TValidatorFunction } from '@/resources/types/functions' -import { ValidatorMap } from '@/resources/types/generic' import type { FormModel } from '@/resources/types/fieldAttributes' import type { Field } from '@/resources/types/field/fields' import validators from '@/validators' /** - * Get the corresponding validator function for a given string, or function. If a function is passed, the function is - * assumed to be the validator to use and thus return. If no argument is passed, we'll just return a function that - * will always return true, thus assuming the value is always valid. - * @param validator + * @deprecated will be removed in 3.0.0, use `useValidation` instead. */ -function getValidator (validator: string | TValidatorFunction | undefined): TValidatorFunction { - if (validator === undefined) return (): boolean => true - - if (isFunction(validator)) return validator - - if (isString(validator)) { - if ((validators as ValidatorMap)[validator] === undefined) { - throw new Error('Invalid validator: ' + validator) - } - return (validators as ValidatorMap)[validator] - } - return (): boolean => true -} - export function useFieldValidate ( model: FormModel, field: Field, diff --git a/src/composables/useValidation.ts b/src/composables/useValidation.ts new file mode 100644 index 0000000..5ed69eb --- /dev/null +++ b/src/composables/useValidation.ts @@ -0,0 +1,124 @@ +import type { Field, FieldEmits } from '@/resources/types/field/fields' +import type { FormModel } from '@/resources/types/fieldAttributes' +import type { FormOptions, ValidationTrigger } from '@/resources/types/generic' +import { type Ref, type ComputedRef, type EmitFn, computed, ref } from 'vue' + +import { isFunction, toUniqueArray, getValidator } from '@/helpers' +import { getMessage } from '@/validators/messages' +import { useValidationWrapper } from '@/composables/useValidationWrapper' +import { TValidatorFunction } from '@/resources/types/functions' + +import validators from '@/validators' + +/** + * Composable for validation of the value(s) from a field. + * Houses all the necessary logic for performing a validation and handles the emitted events. + * @param model - model object from the form. + * @param field - field schema object. + * @param currentModelValue - current model value Ref. + * @param formOptions + * @param emits + * @param isDisabled + * @param isRequired + * @param isReadOnly + */ +export function useValidation( + model: FormModel, + field: Field, + currentModelValue: Ref, + formOptions: FormOptions, + emits: EmitFn, + isDisabled: boolean, + isRequired: boolean, + isReadOnly: boolean +) { + + const errors: Ref = ref([]) + + /** + * The method of validation determined by this field. + * Can be either 'onChanged', 'onBlur' or undefined. + */ + const validationMethod: ComputedRef = computed(() => { + return field.validate + }) + + /** + * Compute all validators that should be present by default. + */ + const defaultValidators: ComputedRef = computed(() => { + const fieldValidators: TValidatorFunction[] = [] + if (!isDisabled && !isReadOnly) { + if (isRequired && !fieldValidators.includes(validators.required)) { + fieldValidators.push(validators.required) + } + + if ('min' in field && field.min) { + fieldValidators.push(validators.min) + } + + if ('max' in field && field.max) { + fieldValidators.push(validators.max) + } + } + return fieldValidators + }) + + /** + * Emit the 'validated' event. + * @param isValid - whether the field value is valid. + * @param errors - errors found during validation. + * @param field - field object. + */ + const emitValidated = (isValid: boolean, errors: string[], field: Field): void => { + emits('validated', isValid, errors, field) + } + + /** + * Validate the field against given validators, plus the validators that are put there by default. + */ + const validate = async (): Promise => { + if (!('validator' in field)) { + emitValidated(true, [], field) + return + } + + const results: string[] = [] + const fieldValidators: TValidatorFunction[] = [ ...defaultValidators.value ] + + if (Array.isArray(field.validator)) { + field.validator.forEach((validator: any) => fieldValidators.push(getValidator(validator))) + } else { + fieldValidators.push(getValidator(field.validator)) + } + + fieldValidators.forEach((validator: TValidatorFunction): void => { + const isValid: boolean = validator(currentModelValue.value, field, model) + if (!isValid) results.push(getMessage(validator.name)) + }) + + const uniqueResults = toUniqueArray(results) + + if ('onValidated' in field && field.onValidated) { + if (isFunction(field.onValidated)) { + field.onValidated.call(null, model, uniqueResults, field) + } else { + throw new Error('onValidated property must be of type `function` on field: ' + field.name) + } + } + + errors.value = uniqueResults + emitValidated(uniqueResults.length === 0, uniqueResults, field) + } + + const onChanged = useValidationWrapper(validate, 'onChanged', validationMethod.value, formOptions.validate) + const onBlur = useValidationWrapper(validate, 'onBlur', validationMethod.value, formOptions.validate) + + return { + errors, + validate, + onChanged, + onBlur + } + +} \ No newline at end of file diff --git a/src/composables/useValidationWrapper.ts b/src/composables/useValidationWrapper.ts new file mode 100644 index 0000000..a2be833 --- /dev/null +++ b/src/composables/useValidationWrapper.ts @@ -0,0 +1,30 @@ +import { type ValidationTrigger } from '@/resources/types/generic' + + +/** + * Use a provided validator function when the trigger of that validator matches + * the validation method used for the current form or form field. + * @param {Function} fn - validator function to execute upon meeting conditions + * @param {ValidationTrigger} trigger - trigger for the validation, 'onBlur' or 'onChanged' + * @param {string | undefined} fieldValidationMethod - validation method as set by the field schema, can be undefined. + * @param {string | undefined} formValidationMethod - validation method as set by the form options, can be undefined. + */ +export function useValidationWrapper( + fn: (...args: any[]) => void, + trigger: ValidationTrigger, + fieldValidationMethod: string | undefined, + formValidationMethod: string | undefined +) { + return (...args: any[]) => { + if (fieldValidationMethod !== undefined) { + return fieldValidationMethod === trigger ? fn(...args) : undefined + } + + if (formValidationMethod !== undefined) { + return formValidationMethod === trigger ? fn(...args) : undefined + } + + return trigger === 'onBlur' ? fn(...args) : undefined + } + +} \ No newline at end of file diff --git a/src/fields/core/FieldColor.vue b/src/fields/core/FieldColor.vue index 8a7570a..28d41ca 100644 --- a/src/fields/core/FieldColor.vue +++ b/src/fields/core/FieldColor.vue @@ -7,6 +7,9 @@ type="text" :value="currentModelValue" placeholder="#ffffff" + :required="isRequired" + :readonly="isReadonly" + :disabled="isDisabled" @input="onFieldValueChanged" @blur="onBlur" > @@ -17,6 +20,8 @@ :name="props.field.name" :value="currentModelValue" :required="isRequired" + :readonly="isReadonly" + :disabled="isDisabled" @input="onFieldValueChanged" @blur="onBlur" > @@ -29,14 +34,13 @@ import { toRefs, onBeforeMount } from 'vue' import { useFormModel, useFieldAttributes, - useFieldValidate, - useFieldEmits + useValidation } from '@/composables' import { vMaska } from 'maska/vue' -import type { ColorField, FieldPropRefs, FieldProps } from '@/resources/types/field/fields' +import type { ColorField, FieldEmits, FieldPropRefs, FieldProps } from '@/resources/types/field/fields' import type { MaskOptions } from 'maska' -const emits = defineEmits(useFieldEmits()) +const emits = defineEmits() const props = defineProps>() const maskOptions: Readonly = { @@ -51,31 +55,25 @@ const maskOptions: Readonly = { const { field, model }: FieldPropRefs = toRefs(props) const { currentModelValue } = useFormModel(model.value, field.value) -const { isRequired, isVisible, hint } = useFieldAttributes(model.value, field.value) -const { errors, validate } = useFieldValidate( +const { isRequired, isVisible, isDisabled, isReadonly, hint } = useFieldAttributes(model.value, field.value) +const { errors, onChanged, onBlur } = useValidation( model.value, field.value, - false, + currentModelValue, + props.formOptions, + emits, + isDisabled.value, isRequired.value, - false + isReadonly.value ) -const onBlur = () => { - validate(currentModelValue.value).then((validationErrors) => { - emits('validated', - validationErrors.length === 0, - validationErrors, - field.value - ) - }) -} - const onFieldValueChanged = (event: Event) => { const target = event.target as HTMLInputElement errors.value = [] // Ensure a change doesn't emit twice; we need this because both inputs might trigger this function at once. if (target.value !== currentModelValue.value) { emits('onInput', target.value) + onChanged() } } @@ -88,7 +86,7 @@ onBeforeMount(() => { } else if (field.value.validator !== undefined) { fieldValidators.push(field.value.validator) } - // Keep in mind that the native color picker only supports 6 digit hex codes, + // Keep in mind that the native color picker only supports 6-digit hex codes, // so even though a value might technically be valid, it won't display the right color on the color picker input. fieldValidators.push(validators.hexColorValue) field.value.validator = fieldValidators diff --git a/src/fields/core/FieldMask.vue b/src/fields/core/FieldMask.vue index c645bbb..6b616a3 100644 --- a/src/fields/core/FieldMask.vue +++ b/src/fields/core/FieldMask.vue @@ -13,13 +13,13 @@