@sbc-connect/nuxt-forms 0.1.11 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # @sbc-connect/nuxt-forms
2
2
 
3
+ ## 0.1.12
4
+
5
+ ### Patch Changes
6
+
7
+ - [#47](https://github.com/bcgov/connect-nuxt/pull/47) [`9edf5ab`](https://github.com/bcgov/connect-nuxt/commit/9edf5ab6c4c7598f661e8ea713e6a60b390105c1) Thanks [@deetz99](https://github.com/deetz99)! - Add zod and address validations. issue: bcgov/entity#29338
8
+
9
+ - Updated dependencies [[`9edf5ab`](https://github.com/bcgov/connect-nuxt/commit/9edf5ab6c4c7598f661e8ea713e6a60b390105c1)]:
10
+ - @sbc-connect/nuxt-base@0.1.11
11
+
3
12
  ## 0.1.11
4
13
 
5
14
  ### Patch Changes
@@ -17,22 +17,23 @@ const {
17
17
  class="flex"
18
18
  :class="[orientation === 'horizontal' ? 'flex-col gap-6 sm:flex-row sm:gap-4' : 'flex-col gap-6']"
19
19
  >
20
- <div :class="{ 'w-full sm:w-1/4': orientation === 'horizontal' }">
21
- <legend
22
- class="text-base text-neutral-highlighted font-bold"
23
- :class="{ 'text-red-600': !!error }"
24
- >
25
- <div class="flex flex-wrap gap-4">
26
- <span>{{ label }}</span>
27
- <span
28
- v-if="!!error && showErrorMsg"
29
- class="font-normal"
30
- >
31
- {{ error.message }}
32
- </span>
33
- </div>
34
- </legend>
35
- </div>
20
+ <legend
21
+ class="text-base text-neutral-highlighted font-bold pb-6"
22
+ :class="[
23
+ { 'text-red-600': !!error },
24
+ { 'w-full sm:w-1/4 pb-0': orientation === 'horizontal' },
25
+ ]"
26
+ >
27
+ <div class="flex flex-wrap gap-4">
28
+ <span>{{ label }}</span>
29
+ <span
30
+ v-if="!!error && showErrorMsg"
31
+ class="font-normal"
32
+ >
33
+ {{ error.message }}
34
+ </span>
35
+ </div>
36
+ </legend>
36
37
 
37
38
  <div class="flex-1">
38
39
  <slot />
@@ -1,19 +1,18 @@
1
1
  <script setup lang="ts">
2
- const props = defineProps<{
3
- id: string
2
+ defineProps<{
3
+ parentId: string
4
4
  schemaPrefix: string
5
5
  }>()
6
6
 
7
7
  const model = defineModel<string>({ default: '' })
8
-
9
- const inputId = props.id + '-city'
10
8
  </script>
11
9
 
12
10
  <template>
13
11
  <ConnectFormInput
14
12
  v-model="model"
13
+ :data-testid="`${parentId}-field-city`"
14
+ :input-id="`${parentId}-input-city`"
15
15
  :name="schemaPrefix + '.city'"
16
- :input-id="inputId"
17
16
  required
18
17
  :label="$t('connect.label.city')"
19
18
  />
@@ -1,8 +1,8 @@
1
1
  <script setup lang="ts">
2
- import { iscCountriesListSortedByName as countries } from '#forms/app/utils/isoCountriesList'
2
+ import { isoCountriesListSortedByName as countries } from '#forms/app/utils/isoCountriesList'
3
3
 
4
- const props = defineProps<{
5
- id: string
4
+ defineProps<{
5
+ parentId: string
6
6
  schemaPrefix: string
7
7
  disabled?: boolean
8
8
  }>()
@@ -13,8 +13,6 @@ defineEmits<{
13
13
 
14
14
  const model = defineModel<string>({ default: '' })
15
15
 
16
- const inputId = props.id + '-country'
17
-
18
16
  const displayedCountryName = computed(() => {
19
17
  if (model.value) {
20
18
  const found = countries.find(c => c.alpha_2 === model.value)?.name
@@ -32,14 +30,15 @@ const options = [
32
30
 
33
31
  <template>
34
32
  <UFormField
33
+ :data-testid="`${parentId}-field-country`"
35
34
  :name="schemaPrefix + '.country'"
36
35
  class="grow"
37
36
  >
38
37
  <template #default="{ error }">
39
38
  <USelect
40
- :id="inputId"
39
+ :id="`${parentId}-input-country`"
41
40
  v-model="model"
42
- :data-testid="inputId"
41
+ :data-testid="`${parentId}-input-country`"
43
42
  :items="options"
44
43
  value-key="alpha_2"
45
44
  label-key="name"
@@ -52,6 +51,9 @@ const options = [
52
51
  base: error
53
52
  ? 'shadow-input-error focus:shadow-input-error data-[state=open]:shadow-input-error'
54
53
  : '',
54
+ trailingIcon: error
55
+ ? 'text-error group-data-[state=open]:text-error group-focus:text-error'
56
+ : '',
55
57
  item: 'nth-2:border-b nth-2:border-gray-200',
56
58
  itemTrailingIcon: 'hidden',
57
59
  }"
@@ -66,7 +68,7 @@ const options = [
66
68
  ? 'top-1/2 -translate-y-1/2'
67
69
  : 'top-1 -translate-y-none text-xs',
68
70
  error
69
- ? 'text-red-600'
71
+ ? 'text-red-600 group-data-[state=open]:text-red-600 group-focus:text-red-600'
70
72
  : '',
71
73
  'absolute left-0 px-2.5 text-sm transition-all',
72
74
  'group-data-[state=open]:text-primary group-focus:text-primary',
@@ -1,19 +1,18 @@
1
1
  <script setup lang="ts">
2
- const props = defineProps<{
3
- id: string
2
+ defineProps<{
3
+ parentId: string
4
4
  schemaPrefix: string
5
5
  disabled?: boolean
6
6
  }>()
7
7
 
8
8
  const model = defineModel<string>({ default: '' })
9
-
10
- const inputId = props.id + '-locationDescription'
11
9
  </script>
12
10
 
13
11
  <template>
14
12
  <ConnectFormTextarea
15
13
  v-model="model"
16
- :input-id="inputId"
14
+ :data-testid="`${parentId}-field-locationDescription`"
15
+ :input-id="`${parentId}-input-locationDescription`"
17
16
  :name="schemaPrefix + '.locationDescription'"
18
17
  :disabled
19
18
  :label="$t('connect.label.deliveryInstructionsOpt')"
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
- const props = defineProps<{
3
- id: string
2
+ defineProps<{
3
+ parentId: string
4
4
  schemaPrefix: string
5
5
  disabled?: boolean
6
6
  country?: string
@@ -13,15 +13,14 @@ const model = defineModel({
13
13
  type: String,
14
14
  default: ''
15
15
  })
16
-
17
- const inputId = props.id + '-postalCode'
18
16
  </script>
19
17
 
20
18
  <template>
21
19
  <ConnectFormInput
22
20
  v-model="model"
23
21
  :name="schemaPrefix + '.postalCode'"
24
- :input-id="inputId"
22
+ :data-testid="`${parentId}-field-postalCode`"
23
+ :input-id="`${parentId}-input-postalCode`"
25
24
  :disabled
26
25
  required
27
26
  :label="country === 'US'
@@ -1,15 +1,13 @@
1
1
  <script setup lang="ts">
2
2
  const {
3
3
  maxlength = '1000',
4
- country,
5
- id
4
+ country
6
5
  } = defineProps<{
7
- id: string
6
+ parentId: string
8
7
  country?: string
9
8
  schemaPrefix: string
10
9
  disabled?: boolean
11
10
  maxlength?: string
12
- help?: string
13
11
  }>()
14
12
 
15
13
  const model = defineModel<string>({ default: '' })
@@ -32,32 +30,34 @@ const displayedRegionName = computed(() => {
32
30
  }
33
31
  return ''
34
32
  })
35
-
36
- const inputId = id + '-region'
37
33
  </script>
38
34
 
39
35
  <template>
40
36
  <UFormField
41
37
  :name="schemaPrefix + '.region'"
42
38
  class="grow flex-1"
39
+ :data-testid="`${parentId}-field-region`"
43
40
  >
44
41
  <template #default="{ error }">
45
42
  <USelect
46
43
  v-if="country === 'US' || country === 'CA'"
47
- :id="inputId"
44
+ :id="`${parentId}-input-region`"
48
45
  v-model="model"
49
- :data-testid="inputId"
46
+ :data-testid="`${parentId}-input-region`"
50
47
  :items="regions"
51
48
  :aria-label="country === 'CA' ? $t('connect.label.province') : $t('connect.label.state')"
52
49
  value-key="code"
53
50
  label-key="name"
54
- :aria-required="true"
51
+ :aria-required="country === 'US' || country === 'CA'"
55
52
  :disabled
56
53
  class="w-full grow ring-0"
57
54
  :ui="{
58
55
  base: error
59
56
  ? 'shadow-input-error focus:shadow-input-error data-[state=open]:shadow-input-error'
60
57
  : '',
58
+ trailingIcon: error
59
+ ? 'text-error group-data-[state=open]:text-error group-focus:text-error'
60
+ : '',
61
61
  }"
62
62
  >
63
63
  <template #default="{ modelValue }">
@@ -70,10 +70,10 @@ const inputId = id + '-region'
70
70
  ? 'top-1/2 -translate-y-1/2'
71
71
  : 'top-1 -translate-y-none text-xs',
72
72
  error
73
- ? 'text-error'
74
- : '',
73
+ ? 'text-error group-data-[state=open]:text-error group-focus:text-error'
74
+ : 'group-data-[state=open]:text-primary group-focus:text-primary',
75
75
  'absolute left-0 px-2.5 text-sm transition-all',
76
- 'group-data-[state=open]:text-primary group-focus:text-primary',
76
+ '',
77
77
  ]"
78
78
  >
79
79
  {{ country === 'CA' ? $t('connect.label.province') : $t('connect.label.state') }}
@@ -91,15 +91,15 @@ const inputId = id + '-region'
91
91
  </USelect>
92
92
  <ConnectInput
93
93
  v-else
94
- :id="inputId"
94
+ :id="`${parentId}-input-region`"
95
95
  v-model="model"
96
- :invalid="!!error"
96
+ :data-testid="`${parentId}-input-region`"
97
97
  :disabled
98
98
  :label="$t('connect.label.regionOpt')"
99
99
  :maxlength
100
100
  />
101
101
  <div
102
- v-if="!help && !error"
102
+ v-if="!$slots.help && !error"
103
103
  class="h-4 mt-1"
104
104
  />
105
105
  </template>
@@ -1,12 +1,11 @@
1
1
  <script setup lang="ts">
2
2
  const {
3
- id,
4
3
  schemaPrefix,
5
4
  helpText = 'none',
6
5
  country,
7
6
  disableAddressComplete
8
7
  } = defineProps<{
9
- id: string
8
+ parentId: string
10
9
  country?: string
11
10
  schemaPrefix: string
12
11
  disableAddressComplete?: boolean
@@ -19,8 +18,6 @@ const emit = defineEmits<{
19
18
  addressComplete: [value: ConnectAddress]
20
19
  }>()
21
20
 
22
- const inputId = id + '-street'
23
-
24
21
  const { address: canadaPostAddress, enableAddressComplete } = useCanadaPost()
25
22
 
26
23
  const addressComplete = (id: string) => {
@@ -37,7 +34,8 @@ watch(canadaPostAddress, (newAddress) => {
37
34
  <template>
38
35
  <ConnectFormInput
39
36
  v-model="model"
40
- :input-id="inputId"
37
+ :data-testid="`${parentId}-field-street`"
38
+ :input-id="`${parentId}-input-street`"
41
39
  :name="schemaPrefix + '.street'"
42
40
  :help="helpText === 'none'
43
41
  ? undefined
@@ -47,7 +45,7 @@ watch(canadaPostAddress, (newAddress) => {
47
45
  "
48
46
  :label="$t('connect.label.street')"
49
47
  required
50
- @keypress.once="addressComplete(inputId)"
51
- @click="addressComplete(inputId)"
48
+ @keypress.once="addressComplete(`${parentId}-input-street`)"
49
+ @click="addressComplete(`${parentId}-input-street`)"
52
50
  />
53
51
  </template>
@@ -1,19 +1,18 @@
1
1
  <script setup lang="ts">
2
- const props = defineProps<{
3
- id: string
2
+ defineProps<{
3
+ parentId: string
4
4
  schemaPrefix: string
5
5
  }>()
6
6
 
7
7
  const model = defineModel<string>({ default: '' })
8
-
9
- const inputId = props.id + '-streetAdditional'
10
8
  </script>
11
9
 
12
10
  <template>
13
11
  <ConnectFormInput
14
12
  v-model="model"
15
13
  :name="schemaPrefix + '.streetAdditional'"
16
- :input-id="inputId"
14
+ :data-testid="`${parentId}-field-streetAdditional`"
15
+ :input-id="`${parentId}-input-streetAdditional`"
17
16
  :label="$t('connect.label.streetAdditional')"
18
17
  />
19
18
  </template>
@@ -1,22 +1,23 @@
1
1
  <script setup lang="ts">
2
+ /* eslint-disable @typescript-eslint/no-explicit-any */ // allow any for form ref type
3
+ import type { Form } from '@nuxt/ui'
4
+
2
5
  const {
3
6
  disabledFields,
4
7
  excludedFields = ['streetName', 'streetNumber', 'unitNumber'],
5
8
  schemaPrefix,
6
- streetHelpText = 'none'
9
+ streetHelpText = 'none',
10
+ formRef
7
11
  } = defineProps<{
8
12
  id: string
9
13
  schemaPrefix: string
14
+ formRef?: Form<any> | null
10
15
  disabledFields?: Array<keyof ConnectAddress>
11
16
  excludedFields?: Array<keyof ConnectAddress>
12
17
  disableAddressComplete?: boolean
13
18
  streetHelpText?: 'allow-po' | 'no-po' | 'none'
14
19
  }>()
15
20
 
16
- const emit = defineEmits<{
17
- shouldValidate: [keys: Array<keyof ConnectAddress>]
18
- }>()
19
-
20
21
  const state = defineModel<Partial<ConnectAddress>>({ required: true })
21
22
 
22
23
  async function populateAddressComplete(e: ConnectAddress) {
@@ -37,17 +38,20 @@ async function populateAddressComplete(e: ConnectAddress) {
37
38
  // wait for dom to populate inputs
38
39
  await nextTick()
39
40
 
40
- // emit valid keys to be validated by parent UForm
41
- emit('shouldValidate', validKeys.map(k => schemaPrefix + k) as Array<keyof ConnectAddress>)
41
+ // validate populated fields if formRef provided
42
+ if (formRef) {
43
+ const fields = validKeys.map(k => `${schemaPrefix}.${k}`)
44
+ await formRef.validate({ name: fields, silent: true })
45
+ }
42
46
  }
43
47
  </script>
44
48
 
45
49
  <template>
46
- <div class="space-y-2" :data-testid="id">
50
+ <div class="space-y-2" :data-testid="id + '-container'">
47
51
  <ConnectFormAddressCountry
48
52
  v-if="!excludedFields.includes('country')"
49
- :id
50
53
  v-model="state.country"
54
+ :parent-id="id"
51
55
  :schema-prefix="schemaPrefix"
52
56
  :disabled="disabledFields?.includes('country')"
53
57
  @change="state.region = ''"
@@ -55,8 +59,8 @@ async function populateAddressComplete(e: ConnectAddress) {
55
59
 
56
60
  <ConnectFormAddressStreet
57
61
  v-if="!excludedFields.includes('street')"
58
- :id
59
62
  v-model="state.street"
63
+ :parent-id="id"
60
64
  :schema-prefix="schemaPrefix"
61
65
  :country="state.country"
62
66
  :disable-address-complete="disableAddressComplete"
@@ -67,8 +71,8 @@ async function populateAddressComplete(e: ConnectAddress) {
67
71
 
68
72
  <ConnectFormAddressStreetAdditional
69
73
  v-if="!excludedFields.includes('streetAdditional')"
70
- :id
71
74
  v-model="state.streetAdditional"
75
+ :parent-id="id"
72
76
  :schema-prefix="schemaPrefix"
73
77
  :disabled="disabledFields?.includes('streetAdditional')"
74
78
  />
@@ -76,16 +80,16 @@ async function populateAddressComplete(e: ConnectAddress) {
76
80
  <div class="flex flex-col gap-2 sm:gap-4 sm:flex-row">
77
81
  <ConnectFormAddressCity
78
82
  v-if="!excludedFields.includes('city')"
79
- :id
80
83
  v-model="state.city"
84
+ :parent-id="id"
81
85
  :schema-prefix="schemaPrefix"
82
86
  :disabled="disabledFields?.includes('city')"
83
87
  />
84
88
 
85
89
  <ConnectFormAddressRegion
86
90
  v-if="!excludedFields.includes('region')"
87
- :id
88
91
  v-model="state.region"
92
+ :parent-id="id"
89
93
  :schema-prefix="schemaPrefix"
90
94
  :country="state.country"
91
95
  :disabled="disabledFields?.includes('region')"
@@ -93,8 +97,8 @@ async function populateAddressComplete(e: ConnectAddress) {
93
97
 
94
98
  <ConnectFormAddressPostalCode
95
99
  v-if="!excludedFields.includes('postalCode')"
96
- :id
97
100
  v-model="state.postalCode"
101
+ :parent-id="id"
98
102
  :schema-prefix="schemaPrefix"
99
103
  :country="state.country"
100
104
  :disabled="disabledFields?.includes('postalCode')"
@@ -103,8 +107,8 @@ async function populateAddressComplete(e: ConnectAddress) {
103
107
 
104
108
  <ConnectFormAddressDeliveryInstructions
105
109
  v-if="!excludedFields.includes('locationDescription')"
106
- :id
107
110
  v-model="state.locationDescription"
111
+ :parent-id="id"
108
112
  :schema-prefix="schemaPrefix"
109
113
  :disabled="disabledFields?.includes('locationDescription')"
110
114
  />
@@ -0,0 +1,42 @@
1
+ <script setup lang="ts">
2
+ import type { FormError } from '@nuxt/ui'
3
+
4
+ const {
5
+ orientation = 'vertical'
6
+ } = defineProps<{
7
+ label: string
8
+ error?: FormError
9
+ showErrorMsg?: boolean
10
+ orientation?: 'vertical' | 'horizontal'
11
+ }>()
12
+ // TODO: figure out why text-error doesnt work on the legend text
13
+ </script>
14
+
15
+ <template>
16
+ <div
17
+ class="flex"
18
+ :class="[orientation === 'horizontal' ? 'flex-col gap-6 sm:flex-row sm:gap-4' : 'flex-col gap-6']"
19
+ >
20
+ <span
21
+ aria-hidden="true"
22
+ class="text-base text-neutral-highlighted font-bold"
23
+ :class="[
24
+ { 'text-red-600': !!error },
25
+ { 'w-full sm:w-1/4': orientation === 'horizontal' },
26
+ ]"
27
+ >
28
+ <div class="flex flex-wrap gap-4">
29
+ <span>{{ label }}</span>
30
+ <span
31
+ v-if="!!error && showErrorMsg"
32
+ class="font-normal"
33
+ >
34
+ {{ error.message }}
35
+ </span>
36
+ </div>
37
+ </span>
38
+ <div class="flex-1">
39
+ <slot />
40
+ </div>
41
+ </div>
42
+ </template>
@@ -2,7 +2,6 @@
2
2
  defineProps<{
3
3
  name: string
4
4
  label: string
5
- help?: string
6
5
  inputId: string
7
6
  required?: boolean
8
7
  autofocus?: boolean
@@ -16,7 +15,6 @@ const model = defineModel<string>({ required: true })
16
15
  <template>
17
16
  <UFormField
18
17
  :name
19
- :help
20
18
  class="grow flex-1"
21
19
  >
22
20
  <template #default="{ error }">
@@ -24,14 +22,13 @@ const model = defineModel<string>({ required: true })
24
22
  :id="inputId"
25
23
  v-model="model"
26
24
  :mask
27
- :required
28
- :invalid="!!error"
25
+ :aria-required="required"
29
26
  :label
30
27
  :autofocus
31
28
  :disabled
32
29
  />
33
30
  <div
34
- v-if="!help && !error"
31
+ v-if="!$slots.help && !error"
35
32
  class="h-4 mt-1"
36
33
  />
37
34
  </template>
@@ -2,7 +2,6 @@
2
2
  defineProps<{
3
3
  name: string
4
4
  label: string
5
- help?: string
6
5
  inputId: string
7
6
  required?: boolean
8
7
  autofocus?: boolean
@@ -15,7 +14,6 @@ const model = defineModel<string>({ required: true })
15
14
  <template>
16
15
  <UFormField
17
16
  :name
18
- :help
19
17
  class="grow flex-1"
20
18
  >
21
19
  <template #default="{ error }">
@@ -24,13 +22,12 @@ const model = defineModel<string>({ required: true })
24
22
  v-model="model"
25
23
  :data-testid="inputId"
26
24
  :required
27
- :invalid="!!error"
28
25
  :label
29
26
  :autofocus
30
27
  :disabled
31
28
  />
32
29
  <div
33
- v-if="!help && !error"
30
+ v-if="!$slots.help && !error"
34
31
  class="h-4 mt-1"
35
32
  />
36
33
  </template>
@@ -0,0 +1 @@
1
+ export * from './zod-schemas'
@@ -2023,7 +2023,7 @@ export const countrySubdivisions = {
2023
2023
  ca: subdivisionsCa,
2024
2024
  us: subdivisionsUs
2025
2025
  }
2026
- export const iscCountriesListSortedByName = isoCountriesList.toSorted(
2026
+ export const isoCountriesListSortedByName = isoCountriesList.toSorted(
2027
2027
  (c1, c2) => {
2028
2028
  if (c1.name < c2.name) {
2029
2029
  return -1
@@ -0,0 +1,111 @@
1
+ import { z } from 'zod'
2
+
3
+ export function getRequiredAddressSchema() {
4
+ const t = useNuxtApp().$i18n.t
5
+
6
+ return z.object({
7
+ street: z
8
+ .string()
9
+ .min(1, t('connect.validation.fieldRequired'))
10
+ .max(50, t('connect.validation.maxChars', { count: 50 })),
11
+ streetAdditional: z
12
+ .string()
13
+ .max(50, t('connect.validation.maxChars', { count: 50 }))
14
+ .optional(),
15
+ city: z
16
+ .string()
17
+ .min(1, t('connect.validation.fieldRequired'))
18
+ .max(40, t('connect.validation.maxChars', { count: 40 })),
19
+ region: z
20
+ .string()
21
+ .optional(),
22
+ postalCode: z
23
+ .string()
24
+ .min(1, t('connect.validation.fieldRequired'))
25
+ .max(15, t('connect.validation.maxChars', { count: 15 })),
26
+ country: z
27
+ .string()
28
+ .min(1, t('connect.validation.fieldRequired')),
29
+ locationDescription: z
30
+ .string()
31
+ .max(80, t('connect.validation.maxChars', { count: 80 }))
32
+ .optional()
33
+ }).superRefine((data, ctx) => {
34
+ // validate region based on country
35
+ // required if country is US or CA
36
+ // optional and max 2 characters if not US or CA
37
+ const country = data.country
38
+ const region = data.region
39
+
40
+ if (region && region.length > 2) {
41
+ ctx.addIssue({
42
+ code: 'custom',
43
+ message: t('connect.validation.maxChars', { count: 2 }),
44
+ path: ['region']
45
+ })
46
+ }
47
+
48
+ if ((country === 'US' || country === 'CA') && region?.length === 0) {
49
+ ctx.addIssue({
50
+ code: 'custom',
51
+ message: t('connect.validation.fieldRequired'),
52
+ path: ['region']
53
+ })
54
+ }
55
+ })
56
+ }
57
+
58
+ export function getNonRequiredAddressSchema() {
59
+ const t = useNuxtApp().$i18n.t
60
+
61
+ return z.object({
62
+ street: z
63
+ .string()
64
+ .min(1, t('connect.validation.fieldRequired'))
65
+ .max(50, t('connect.validation.maxChars', { count: 50 }))
66
+ .optional()
67
+ .or(z.literal('')),
68
+ streetAdditional: z
69
+ .string()
70
+ .max(50, t('connect.validation.maxChars', { count: 50 }))
71
+ .optional(),
72
+ city: z
73
+ .string()
74
+ .min(1, t('connect.validation.fieldRequired'))
75
+ .max(40, t('connect.validation.maxChars', { count: 40 }))
76
+ .optional()
77
+ .or(z.literal('')),
78
+ region: z
79
+ .string()
80
+ .optional(),
81
+ postalCode: z
82
+ .string()
83
+ .min(1, t('connect.validation.fieldRequired'))
84
+ .max(15, t('connect.validation.maxChars', { count: 15 }))
85
+ .optional()
86
+ .or(z.literal('')),
87
+ country: z
88
+ .string()
89
+ .min(1, t('connect.validation.fieldRequired'))
90
+ .optional()
91
+ .or(z.literal('')),
92
+ locationDescription: z
93
+ .string()
94
+ .max(80, t('connect.validation.maxChars', { count: 80 }))
95
+ .optional()
96
+ }).superRefine((data, ctx) => {
97
+ // validate region based on country
98
+ // required if country is US or CA
99
+ // optional and max 2 characters if not US or CA
100
+ // const country = data.country
101
+ const region = data.region
102
+
103
+ if (region && region.length > 2) {
104
+ ctx.addIssue({
105
+ code: 'custom',
106
+ message: t('connect.validation.maxChars', { count: 2 }),
107
+ path: ['region']
108
+ })
109
+ }
110
+ })
111
+ }
@@ -0,0 +1 @@
1
+ export * from './addresses'
@@ -33,6 +33,12 @@ export default {
33
33
  text: {
34
34
  addressCanBePOBox: 'Street address, PO box, rural route, or general delivery address.',
35
35
  addressCannotBePOBox: 'Address cannot be a PO Box.'
36
+ },
37
+ validation: {
38
+ fieldRequired: 'This field is required',
39
+ maxChars: 'Maximum 0 characters | Maximum 1 character | Maximum {count} characters', // 0/1 most likely will never be used but required for pluralization
40
+ minChars: 'Minimum 0 characters | Minimum 1 character | Minimum {count} characters', // 0/1 most likely will never be used but required for pluralization
41
+ required: 'Required'
36
42
  }
37
43
  }
38
44
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sbc-connect/nuxt-forms",
3
3
  "type": "module",
4
- "version": "0.1.11",
4
+ "version": "0.1.12",
5
5
  "repository": "github:bcgov/connect-nuxt",
6
6
  "license": "BSD-3-Clause",
7
7
  "main": "./nuxt.config.ts",
@@ -17,7 +17,8 @@
17
17
  "@sbc-connect/vitest-config": "0.0.6"
18
18
  },
19
19
  "dependencies": {
20
- "@sbc-connect/nuxt-base": "0.1.10"
20
+ "zod": "^4.0.17",
21
+ "@sbc-connect/nuxt-base": "0.1.11"
21
22
  },
22
23
  "scripts": {
23
24
  "preinstall": "npx only-allow pnpm",
@@ -29,8 +30,10 @@
29
30
  "lint": "eslint .",
30
31
  "lint:fix": "eslint --fix .",
31
32
  "test": "pnpm run test:unit; pnpm run test:e2e",
32
- "test:unit": "vitest run",
33
+ "test:unit": "vitest run --passWithNoTests",
34
+ "test:unit:watch": "vitest",
33
35
  "test:e2e": "npx playwright test",
36
+ "test:e2e:ui": "npx playwright test --ui",
34
37
  "typecheck": "npx nuxt typecheck"
35
38
  }
36
39
  }