From 76f807ec6f20cb6f35e481db04950ba9311e4b8e Mon Sep 17 00:00:00 2001 From: Sergei Maertens Date: Tue, 10 Oct 2023 14:19:45 +0200 Subject: [PATCH 1/4] :sparkles: [#2] Include DateTime schema in AnyComponentSchema --- src/formio/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/formio/index.ts b/src/formio/index.ts index 1624dbd..6daa733 100644 --- a/src/formio/index.ts +++ b/src/formio/index.ts @@ -1,6 +1,7 @@ import { ContentComponentSchema, DateComponentSchema, + DateTimeComponentSchema, EmailComponentSchema, NumberComponentSchema, TextFieldComponentSchema, @@ -34,6 +35,7 @@ export type AnyComponentSchema = | TextFieldComponentSchema | EmailComponentSchema | DateComponentSchema + | DateTimeComponentSchema | NumberComponentSchema // layout | ContentComponentSchema; From f90ec1ca01cbd64b5f25d8523793bf0959a9c20a Mon Sep 17 00:00:00 2001 From: Sergei Maertens Date: Tue, 10 Oct 2023 14:54:32 +0200 Subject: [PATCH 2/4] :sparkles: [#2] Implement types for time component Note that the current implementation puts minTime and maxTime in the root rather than nested in the validate object, which is something that will have to change. --- src/formio/components/index.ts | 1 + src/formio/components/time.ts | 31 +++++ src/formio/index.ts | 2 + src/formio/validation.ts | 11 +- test-d/formio/components/time.test-d.ts | 161 ++++++++++++++++++++++++ tsconfig.json | 1 + 6 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 src/formio/components/time.ts create mode 100644 test-d/formio/components/time.test-d.ts diff --git a/src/formio/components/index.ts b/src/formio/components/index.ts index 9acb96b..64ed4de 100644 --- a/src/formio/components/index.ts +++ b/src/formio/components/index.ts @@ -3,6 +3,7 @@ export * from './textfield'; export * from './email'; export * from './date'; export * from './datetime'; +export * from './time'; export * from './number'; // Layout components diff --git a/src/formio/components/time.ts b/src/formio/components/time.ts new file mode 100644 index 0000000..4f62120 --- /dev/null +++ b/src/formio/components/time.ts @@ -0,0 +1,31 @@ +import {InputComponentSchema, MultipleCapable} from '..'; + +type Validator = 'required' | 'minTime' | 'maxTime'; +type TranslatableKeys = 'label' | 'description' | 'tooltip'; + +export type TimeInputSchema = InputComponentSchema; + +/** + * @group Form.io components + * @category Base types + */ +export interface BaseTimeComponentSchema extends Omit { + type: 'time'; + // hardcoded in builder + inputType: 'text'; + format: 'HH:mm'; + validateOn: 'blur'; +} + +/** + * A time component schema. + * + * Note that the value/`defaultValue` type is just a plain string - a serialized + * ISO-8601 time. + * + * The smallest supported resolution is minutes, seconds are truncated to be 0 seconds. + * + * @group Form.io components + * @category Concrete types + */ +export type TimeComponentSchema = MultipleCapable; diff --git a/src/formio/index.ts b/src/formio/index.ts index 6daa733..5f3a058 100644 --- a/src/formio/index.ts +++ b/src/formio/index.ts @@ -5,6 +5,7 @@ import { EmailComponentSchema, NumberComponentSchema, TextFieldComponentSchema, + TimeComponentSchema, } from './components'; /** @@ -36,6 +37,7 @@ export type AnyComponentSchema = | EmailComponentSchema | DateComponentSchema | DateTimeComponentSchema + | TimeComponentSchema | NumberComponentSchema // layout | ContentComponentSchema; diff --git a/src/formio/validation.ts b/src/formio/validation.ts index 827594c..df63ced 100644 --- a/src/formio/validation.ts +++ b/src/formio/validation.ts @@ -4,6 +4,8 @@ import {ValidateOptions} from 'formiojs'; declare module 'formiojs' { interface ValidateOptions { plugins?: string[]; + minTime?: string | null; + maxTime?: string | null; } } @@ -21,7 +23,11 @@ export type BaseErrorKeys = | 'invalid_email' | 'pattern' | 'minDate' - | 'maxDate'; + | 'maxDate' + // custom, added by OF + | 'minTime' + | 'maxTime' + | 'invalid_time'; export type ComponentErrors = { [K in Keys]?: string; @@ -58,6 +64,9 @@ const VALIDATOR_TO_ERROR_KEY = { // 'email': 'invalid_email', // email component is exposed, but adds the validation implicitly minDate: 'minDate', maxDate: 'maxDate', + // custom, for time component + minTime: 'minTime' as 'minTime' | 'invalid_time', + maxTime: 'maxTime' as 'maxTime' | 'invalid_time', } as const satisfies ValidatorToErrorMap; // infer valid component error keys from the mapping of validation error code to the diff --git a/test-d/formio/components/time.test-d.ts b/test-d/formio/components/time.test-d.ts new file mode 100644 index 0000000..77cbc7a --- /dev/null +++ b/test-d/formio/components/time.test-d.ts @@ -0,0 +1,161 @@ +import {expectAssignable, expectNotAssignable} from 'tsd'; + +import {TimeComponentSchema} from '../../../lib'; + +// minimal time component schema +expectAssignable({ + id: 'ezftxdl', + type: 'time', + key: 'someTime', + label: 'Some time', + inputType: 'text', + format: 'HH:mm', + validateOn: 'blur', +}); + +// with translated error messages - the multiple messages is special here +expectAssignable({ + id: 'ezftxdl', + type: 'time', + key: 'someTime', + label: 'Some time', + inputType: 'text', + format: 'HH:mm', + validateOn: 'blur', + translatedErrors: { + nl: { + required: '', + minTime: 'Moet minimum XYZ zijn', + invalid_time: 'Ongeldige tijd opgegeven', + }, + en: { + required: '', + maxTime: 'Must be maximum XYZ', + }, + }, +}); + +// multiple false and appropriate default value type +expectAssignable({ + id: 'ezftxdl', + type: 'time', + key: 'someTime', + label: 'Some time', + inputType: 'text', + format: 'HH:mm', + validateOn: 'blur', + multiple: false, + defaultValue: '09:47', +}); + +// multiple true and appropriate default value type +expectAssignable({ + id: 'ezftxdl', + type: 'time', + key: 'someTime', + label: 'Some time', + inputType: 'text', + format: 'HH:mm', + validateOn: 'blur', + multiple: true, + defaultValue: ['12:15'], +}); + +// full, correct schema +expectAssignable({ + id: 'ezftxdl', + type: 'time', + inputType: 'text', + format: 'HH:mm', + validateOn: 'blur', + // basic tab + label: 'Some time', + key: 'someTime', + description: '', + tooltip: 'A tooltip', + showInSummary: true, + showInEmail: false, + showInPDF: true, + multiple: false, + hidden: false, + clearOnHide: true, + isSensitiveData: false, + defaultValue: '', + // Advanced tab + conditional: { + show: undefined, + when: '', + eq: '', + }, + // Validation tab + validate: { + required: false, + plugins: [], + minTime: '10:00', + maxTime: '20:00', + }, + translatedErrors: { + nl: { + required: '', + minTime: 'Moet minimum XYZ zijn', + invalid_time: 'Ongeldige tijd opgegeven', + }, + en: { + required: '', + maxTime: 'Must be maximum XYZ', + }, + }, + // registration tab + registration: { + attribute: '', + }, + // custom OF extensions + openForms: { + // translations tab in builder form + translations: { + nl: { + label: 'foo', + tooltip: 'bar', + }, + }, + }, +}); + +// invalid, multiple true and non-array default value +expectNotAssignable({ + id: 'ezftxdl', + type: 'time', + key: 'someTime', + label: 'Some time', + inputType: 'text', + format: 'HH:mm', + validateOn: 'blur', + multiple: true, + defaultValue: '', +} as const); + +// invalid, multiple false and array default value +expectNotAssignable({ + id: 'ezftxdl', + type: 'time', + key: 'someTime', + label: 'Some time', + inputType: 'text', + format: 'HH:mm', + validateOn: 'blur', + multiple: false, + defaultValue: [''], +} as const); + +// invalid, multiple true and wrong default value in array element +expectNotAssignable({ + id: 'ezftxdl', + type: 'time', + key: 'someTime', + label: 'Some time', + inputType: 'text', + format: 'HH:mm', + validateOn: 'blur', + multiple: true, + defaultValue: [new Date()], +} as const); diff --git a/tsconfig.json b/tsconfig.json index 56a7f93..7edab70 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,6 +15,7 @@ "strictBindCallApply": true, "strictNullChecks": true, "allowSyntheticDefaultImports": true, + "noErrorTruncation": true, "paths": { "@/*": ["./*"], } From 9dcad4af44d1996be998726579cc290d95a6a017 Mon Sep 17 00:00:00 2001 From: Sergei Maertens Date: Wed, 11 Oct 2023 16:42:30 +0200 Subject: [PATCH 3/4] :sparkles: [#2] Implement types for phone number component --- src/formio/components/index.ts | 1 + src/formio/components/phonenumber.ts | 23 +++ src/formio/index.ts | 2 + .../formio/components/phonenumber.test-d.ts | 164 ++++++++++++++++++ 4 files changed, 190 insertions(+) create mode 100644 src/formio/components/phonenumber.ts create mode 100644 test-d/formio/components/phonenumber.test-d.ts diff --git a/src/formio/components/index.ts b/src/formio/components/index.ts index 64ed4de..282bb05 100644 --- a/src/formio/components/index.ts +++ b/src/formio/components/index.ts @@ -4,6 +4,7 @@ export * from './email'; export * from './date'; export * from './datetime'; export * from './time'; +export * from './phonenumber'; export * from './number'; // Layout components diff --git a/src/formio/components/phonenumber.ts b/src/formio/components/phonenumber.ts new file mode 100644 index 0000000..ad691a3 --- /dev/null +++ b/src/formio/components/phonenumber.ts @@ -0,0 +1,23 @@ +import {InputComponentSchema, MultipleCapable} from '..'; + +type Validator = 'required' | 'pattern'; +type TranslatableKeys = 'label' | 'description' | 'tooltip'; + +export type PhoneNumberInputSchema = InputComponentSchema; + +/** + * @group Form.io components + * @category Base types + */ +export interface BasePhoneNumberComponentSchema extends Omit { + type: 'phoneNumber'; + inputMask: null; + // additional properties + autocomplete?: string; +} + +/** + * @group Form.io components + * @category Concrete types + */ +export type PhoneNumberComponentSchema = MultipleCapable; diff --git a/src/formio/index.ts b/src/formio/index.ts index 5f3a058..8fbf3bf 100644 --- a/src/formio/index.ts +++ b/src/formio/index.ts @@ -4,6 +4,7 @@ import { DateTimeComponentSchema, EmailComponentSchema, NumberComponentSchema, + PhoneNumberComponentSchema, TextFieldComponentSchema, TimeComponentSchema, } from './components'; @@ -38,6 +39,7 @@ export type AnyComponentSchema = | DateComponentSchema | DateTimeComponentSchema | TimeComponentSchema + | PhoneNumberComponentSchema | NumberComponentSchema // layout | ContentComponentSchema; diff --git a/test-d/formio/components/phonenumber.test-d.ts b/test-d/formio/components/phonenumber.test-d.ts new file mode 100644 index 0000000..2952f85 --- /dev/null +++ b/test-d/formio/components/phonenumber.test-d.ts @@ -0,0 +1,164 @@ +import {expectAssignable, expectNotAssignable} from 'tsd'; + +import {PhoneNumberComponentSchema} from '../../../lib/'; + +// minimal textfield component schema +expectAssignable({ + id: 'yejak', + type: 'phoneNumber', + key: 'someInput', + label: 'Some input', + inputMask: null, +}); + +// with additional, phonenumber-component specific properties +expectAssignable({ + id: 'yejak', + type: 'phoneNumber', + key: 'someInput', + label: 'Some input', + inputMask: null, + placeholder: 'tel', +}); + +// multiple false and appropriate default value type +expectAssignable({ + id: 'yejak', + type: 'phoneNumber', + key: 'someInput', + label: 'Some input', + inputMask: null, + multiple: false, + defaultValue: '', +}); +// multiple true and appropriate default value type +expectAssignable({ + id: 'yejak', + type: 'phoneNumber', + key: 'someInput', + label: 'Some input', + inputMask: null, + multiple: true, + defaultValue: [''], +}); + +// full, correct schema +expectAssignable({ + id: 'yejak', + type: 'phoneNumber', + inputMask: null, + // basic tab in builder form + label: 'Some input', + key: 'someInput', + description: 'A description', + tooltip: 'A tooltip', + showInSummary: true, + showInEmail: false, + showInPDF: true, + multiple: false, + hidden: false, + clearOnHide: true, + isSensitiveData: true, + defaultValue: '', + autocomplete: 'tel', + // advanced tab in builder form + conditional: { + show: undefined, + when: undefined, + eq: undefined, + }, + // validation tab in builder form + validate: { + required: false, + plugins: ['phonenumber-international'], + pattern: '', + }, + translatedErrors: { + nl: { + required: 'Je moet een waarde opgeven!!!', + pattern: 'Enkel getallen toegestaan.', + }, + }, + errors: { + // translatedErrors is converted into errors by the backend + required: 'Je moet een waarde opgeven!!!', + pattern: 'Enkel getallen toegestaan.', + }, + // registration tab in builder form + registration: { + attribute: '', + }, + // translations tab in builder form + openForms: { + translations: { + nl: { + label: 'foo', + description: 'bar', + }, + }, + }, +}); + +// different component type +expectNotAssignable({ + id: 'yejak', + type: 'textfield', + key: 'someInput', + label: 'Some input', + inputMask: null, +} as const); + +// using unsupported properties +expectNotAssignable({ + id: 'yejak', + type: 'phoneNumber', + key: 'someInput', + label: 'Some input', + inputMask: null, + showCharCount: true, +} as const); + +// incorrect, invalid validator key +expectNotAssignable({ + id: 'yejak', + type: 'phoneNumber', + key: 'someInput', + label: 'Some input', + inputMask: null, + validate: { + maxLength: 100, + }, +} as const); + +// invalid, multiple true and non-array default value +expectNotAssignable({ + id: 'yejak', + type: 'phoneNumber', + key: 'someInput', + label: 'Some input', + inputMask: null, + multiple: true, + defaultValue: '', +} as const); + +// invalid, multiple false and array default value +expectNotAssignable({ + id: 'yejak', + type: 'phoneNumber', + key: 'someInput', + label: 'Some input', + inputMask: null, + multiple: false, + defaultValue: [''], +} as const); + +// invalid, multiple true and wrong default value in array element +expectNotAssignable({ + id: 'yejak', + type: 'phoneNumber', + key: 'someInput', + label: 'Some input', + inputMask: null, + multiple: true, + defaultValue: [123], +} as const); From b12fef296f275d45477585cd32c6894d0e9fa537 Mon Sep 17 00:00:00 2001 From: Sergei Maertens Date: Thu, 12 Oct 2023 17:41:28 +0200 Subject: [PATCH 4/4] :sparkles: [#2] Implement types for postcode component --- src/formio/components/index.ts | 1 + src/formio/components/postcode.ts | 43 +++++ src/formio/index.ts | 2 + src/formio/validation.ts | 5 + test-d/formio/components/postcode.test-d.ts | 203 ++++++++++++++++++++ 5 files changed, 254 insertions(+) create mode 100644 src/formio/components/postcode.ts create mode 100644 test-d/formio/components/postcode.test-d.ts diff --git a/src/formio/components/index.ts b/src/formio/components/index.ts index 282bb05..605f02a 100644 --- a/src/formio/components/index.ts +++ b/src/formio/components/index.ts @@ -5,6 +5,7 @@ export * from './date'; export * from './datetime'; export * from './time'; export * from './phonenumber'; +export * from './postcode'; export * from './number'; // Layout components diff --git a/src/formio/components/postcode.ts b/src/formio/components/postcode.ts new file mode 100644 index 0000000..f8fc848 --- /dev/null +++ b/src/formio/components/postcode.ts @@ -0,0 +1,43 @@ +import {InputComponentSchema, MultipleCapable, PrefillConfig} from '..'; + +type Validator = 'required' | 'pattern' | 'customMessage'; +type TranslatableKeys = 'label' | 'description' | 'tooltip'; + +export type PostCodeInputSchema = InputComponentSchema; + +/** + * The textfield component properties that configure it for Dutch postal codes. + * + * @group Form.io components + * @category Base types + */ +export interface PostcodeProperties { + inputMask: '9999 AA'; + validate: { + // Dutch postcode has 4 numbers and 2 letters (case insensitive). Letter combinations SS, SD and SA + // are not used due to the Nazi-association. + // See https://stackoverflow.com/a/17898538/7146757 and https://nl.wikipedia.org/wiki/Postcodes_in_Nederland + pattern: '^[1-9][0-9]{3} ?(?!sa|sd|ss|SA|SD|SS)[a-zA-Z]{2}$'; + }; + validateOn: 'blur'; +} + +/** + * @group Form.io components + * @category Base types + * @deprecated Use textfield instead, with the additional hardcoded properties. + */ +export type BasePostcodeComponentSchema = Omit & + PrefillConfig & + PostcodeProperties & { + type: 'postcode'; + // additional properties + autocomplete?: string; + }; + +/** + * @group Form.io components + * @category Concrete types + * @deprecated Use textfield instead, with the additional hardcoded properties. + */ +export type PostcodeComponentSchema = MultipleCapable; diff --git a/src/formio/index.ts b/src/formio/index.ts index 8fbf3bf..bf0a86a 100644 --- a/src/formio/index.ts +++ b/src/formio/index.ts @@ -5,6 +5,7 @@ import { EmailComponentSchema, NumberComponentSchema, PhoneNumberComponentSchema, + PostcodeComponentSchema, TextFieldComponentSchema, TimeComponentSchema, } from './components'; @@ -40,6 +41,7 @@ export type AnyComponentSchema = | DateTimeComponentSchema | TimeComponentSchema | PhoneNumberComponentSchema + | PostcodeComponentSchema | NumberComponentSchema // layout | ContentComponentSchema; diff --git a/src/formio/validation.ts b/src/formio/validation.ts index df63ced..0bf5f90 100644 --- a/src/formio/validation.ts +++ b/src/formio/validation.ts @@ -3,6 +3,9 @@ import {ValidateOptions} from 'formiojs'; // extend formio's validate interface with our custom extension(s) declare module 'formiojs' { interface ValidateOptions { + // it's not a validator but formio uses it and we can provide translations support + // in the future + customMessage?: string; plugins?: string[]; minTime?: string | null; maxTime?: string | null; @@ -24,6 +27,7 @@ export type BaseErrorKeys = | 'pattern' | 'minDate' | 'maxDate' + | 'customMessage' // custom, added by OF | 'minTime' | 'maxTime' @@ -56,6 +60,7 @@ export type CuratedValidatorNames = keyof CuratedValidateOptions; type ValidatorToErrorMap = Required<{[K in CuratedValidatorNames]: BaseErrorKeys}>; const VALIDATOR_TO_ERROR_KEY = { + customMessage: 'customMessage', required: 'required', min: 'min', max: 'max', diff --git a/test-d/formio/components/postcode.test-d.ts b/test-d/formio/components/postcode.test-d.ts new file mode 100644 index 0000000..ea021dd --- /dev/null +++ b/test-d/formio/components/postcode.test-d.ts @@ -0,0 +1,203 @@ +import {expectAssignable, expectNotAssignable} from 'tsd'; + +import {PostcodeComponentSchema} from '../../../lib/'; + +// minimal postcode component schema +expectAssignable({ + id: 'yejak', + type: 'postcode', + key: 'someInput', + label: 'Some input', + inputMask: '9999 AA', + validate: { + pattern: '^[1-9][0-9]{3} ?(?!sa|sd|ss|SA|SD|SS)[a-zA-Z]{2}$', + }, + validateOn: 'blur', +}); + +// with additional, phonenumber-component specific properties +expectAssignable({ + id: 'yejak', + type: 'postcode', + key: 'someInput', + label: 'Some input', + inputMask: '9999 AA', + validate: { + pattern: '^[1-9][0-9]{3} ?(?!sa|sd|ss|SA|SD|SS)[a-zA-Z]{2}$', + }, + validateOn: 'blur', + autocomplete: 'postal-code', +}); + +// multiple false and appropriate default value type +expectAssignable({ + id: 'yejak', + type: 'postcode', + key: 'someInput', + label: 'Some input', + inputMask: '9999 AA', + validate: { + pattern: '^[1-9][0-9]{3} ?(?!sa|sd|ss|SA|SD|SS)[a-zA-Z]{2}$', + }, + validateOn: 'blur', + multiple: false, + defaultValue: '1015 CJ', +}); + +// multiple true and appropriate default value type +expectAssignable({ + id: 'yejak', + type: 'postcode', + key: 'someInput', + label: 'Some input', + inputMask: '9999 AA', + validate: { + pattern: '^[1-9][0-9]{3} ?(?!sa|sd|ss|SA|SD|SS)[a-zA-Z]{2}$', + }, + validateOn: 'blur', + multiple: true, + defaultValue: ['1015 CJ'], +}); + +// full, correct schema +expectAssignable({ + id: 'yejak', + type: 'postcode', + inputMask: '9999 AA', + validateOn: 'blur', + // basic tab in builder form + label: 'Some input', + key: 'someInput', + description: 'A description', + tooltip: 'A tooltip', + showInSummary: true, + showInEmail: false, + showInPDF: true, + multiple: false, + hidden: false, + clearOnHide: true, + isSensitiveData: true, + defaultValue: '', + disabled: false, + // advanced tab in builder form + conditional: { + show: undefined, + when: undefined, + eq: undefined, + }, + // validation tab in builder form + validate: { + required: false, + plugins: [], + // FIXED/constant, can't be edited + pattern: '^[1-9][0-9]{3} ?(?!sa|sd|ss|SA|SD|SS)[a-zA-Z]{2}$', + }, + translatedErrors: { + nl: { + required: 'Je moet een waarde opgeven!!!', + }, + }, + errors: { + // translatedErrors is converted into errors by the backend + required: 'Je moet een waarde opgeven!!!', + }, + // registration tab in builder form + registration: { + attribute: '', + }, + // translations tab in builder form + openForms: { + translations: { + nl: { + label: 'foo', + description: 'bar', + }, + }, + }, +}); + +// different component type +expectNotAssignable({ + id: 'yejak', + type: 'textfield', // TODO: in the future this may become a specialized textfield alias? + key: 'someInput', + label: 'Some input', + inputMask: '9999 AA', + validate: { + pattern: '^[1-9][0-9]{3} ?(?!sa|sd|ss|SA|SD|SS)[a-zA-Z]{2}$', + }, + validateOn: 'blur', +} as const); + +// using unsupported properties +expectNotAssignable({ + id: 'yejak', + type: 'postcode', + key: 'someInput', + label: 'Some input', + inputMask: '9999 AA', + validate: { + pattern: '^[1-9][0-9]{3} ?(?!sa|sd|ss|SA|SD|SS)[a-zA-Z]{2}$', + }, + validateOn: 'blur', + placeholder: 'no placeholder', +} as const); + +// incorrect, invalid validator key +expectNotAssignable({ + id: 'yejak', + type: 'postcode', + key: 'someInput', + label: 'Some input', + inputMask: '9999 AA', + validate: { + pattern: '^[1-9][0-9]{3} ?(?!sa|sd|ss|SA|SD|SS)[a-zA-Z]{2}$', + maxLength: 7, + }, + validateOn: 'blur', +} as const); + +// invalid, multiple true and non-array default value +expectNotAssignable({ + id: 'yejak', + type: 'postcode', + key: 'someInput', + label: 'Some input', + inputMask: '9999 AA', + validate: { + pattern: '^[1-9][0-9]{3} ?(?!sa|sd|ss|SA|SD|SS)[a-zA-Z]{2}$', + }, + validateOn: 'blur', + multiple: true, + defaultValue: '', +} as const); + +// invalid, multiple false and array default value +expectNotAssignable({ + id: 'yejak', + type: 'postcode', + key: 'someInput', + label: 'Some input', + inputMask: '9999 AA', + validate: { + pattern: '^[1-9][0-9]{3} ?(?!sa|sd|ss|SA|SD|SS)[a-zA-Z]{2}$', + }, + validateOn: 'blur', + multiple: false, + defaultValue: [''], +} as const); + +// invalid, multiple true and wrong default value in array element +expectNotAssignable({ + id: 'yejak', + type: 'postcode', + key: 'someInput', + label: 'Some input', + inputMask: '9999 AA', + validate: { + pattern: '^[1-9][0-9]{3} ?(?!sa|sd|ss|SA|SD|SS)[a-zA-Z]{2}$', + }, + validateOn: 'blur', + multiple: true, + defaultValue: [123], +} as const);