11<template >
2- <Field
3- v-slot =" props "
4- v-model =" modelValue "
5- :name =" field .name "
6- type="text"
7- :validate-on-blur =" fieldValidateOn === ' blur' "
8- :validate-on-change =" fieldValidateOn === ' change' "
9- :validate-on-input =" fieldValidateOn === ' input' "
10- :validate-on-model-update =" fieldValidateOn != null "
11- >
12- <div :class =" {
13- ...buttonFieldContainerClass,
14- 'v-input--error': props.errorMessage ? props.errorMessage?.length > 0 : false,
15- }" >
16- <v-label >
17- <FieldLabel
18- :label =" field .label "
19- :required =" fieldRequired "
20- />
21- </v-label >
22-
23- <v-item-group
24- :id =" field?.id"
25- v-model =" modelValue"
26- class =" mt-2"
27- :class =" itemGroupClass"
28- :data-cy =" `vsf-field-group-${field.name}`"
29- :style =" itemGroupStyle"
2+ <div :class =" buttonFieldContainerClass" >
3+ <v-label >
4+ <FieldLabel
5+ :label =" field .label "
6+ :required =" fieldRequired "
7+ />
8+ </v-label >
9+
10+ <v-item-group
11+ :id =" field?.id"
12+ v-model =" modelValue"
13+ class =" mt-2"
14+ :class =" itemGroupClass"
15+ :data-cy =" `vsf-field-group-${field.name}`"
16+ :style =" itemGroupStyle"
17+ >
18+ <v-item
19+ v-for =" option, key in field?.options"
20+ :key =" option.value"
3021 >
31- <v-item
32- v-for =" option, key in field?.options"
33- :key =" option.value"
34- >
35- <template #default >
36- <v-btn
37- v-bind =" boundSettings"
38- :id =" getId(option, key)"
39- :active =" isActive(option.value)"
40- :appendIcon =" getIcon(option, 'appendIcon')"
41- class =" text-none"
42- :class =" {
43- [`${option?.class}`]: true,
44- ...buttonClass,
45- [`${field.selectedClass}`]: isActive(option.value) && field.selectedClass != null,
46- }"
47- :color =" option?.color || field?.color || settings?.color"
48- :data-cy =" `vsf-field-${field.name}`"
49- :density =" fieldDensity"
50- :height =" getHeight(option)"
51- :icon =" getIcon(option, 'icon')"
52- :maxHeight =" getMaxHeight(option)"
53- :maxWidth =" getMaxWidth(option)"
54- :minHeight =" getMinHeight(option)"
55- :minWidth =" getMinWidth(option)"
56- :prependIcon =" getIcon(option, 'prependIcon')"
57- :value =" option.value"
58- :variant =" getVariant(option.value)"
59- :width =" getWidth(option)"
60- @click.prevent =" onActions(props.validate, 'click', option.value); props.handleInput(modelValue)"
61- @keydown.space.prevent =" onActions(props.validate, 'click', option.value); props.handleInput(modelValue)"
62- @mousedown =" onFocus(option.value)"
63- @mouseleave =" onFocus(null)"
64- @mouseup =" onFocus(null)"
22+ <template #default >
23+ <v-btn
24+ v-bind =" boundSettings"
25+ :id =" getId(option, key)"
26+ :active =" isActive(option.value)"
27+ :appendIcon =" getIcon(option, 'appendIcon')"
28+ class =" text-none"
29+ :class =" { ...buttonClass, ...buttonClassAdditional(option) }"
30+ :color =" option?.color || field?.color || settings?.color"
31+ :data-cy =" `vsf-field-${field.name}`"
32+ :density =" fieldDensity"
33+ :height =" getHeight(option)"
34+ :icon =" getIcon(option, 'icon')"
35+ :maxHeight =" getMaxHeight(option)"
36+ :maxWidth =" getMaxWidth(option)"
37+ :minHeight =" getMinHeight(option)"
38+ :minWidth =" getMinWidth(option)"
39+ :prependIcon =" getIcon(option, 'prependIcon')"
40+ :value =" option.value"
41+ :variant =" getVariant(option.value)"
42+ :width =" getWidth(option)"
43+ @click.prevent =" onActions('click', option.value)"
44+ @keydown.space.prevent =" onActions('click', option.value)"
45+ @mousedown =" onFocus(option.value)"
46+ @mouseleave =" onFocus(null)"
47+ @mouseup =" onFocus(null)"
48+ >
49+ <template
50+ v-if =" getIcon (option , ' icon' ) == null "
51+ #default
6552 >
66- <template
67- v-if =" getIcon (option , ' icon' ) == null "
68- #default
69- >
70- <span
71- class =" vsf-button-field__btn-label"
72- :class =" getLabelClass(option)"
73- v-html =" option.label"
74- ></span >
75- </template >
76- </v-btn >
77- </template >
78- </v-item >
79- </v-item-group >
80-
81- <div
82- v-if =" hasDetails"
83- class =" v-input__details"
53+ <span
54+ class =" vsf-button-field__btn-label"
55+ :class =" getLabelClass(option)"
56+ v-html =" option.label"
57+ ></span >
58+ </template >
59+ </v-btn >
60+ </template >
61+ </v-item >
62+ </v-item-group >
63+
64+ <div
65+ v-if =" hasDetails"
66+ class =" v-input__details"
67+ >
68+ <VMessages
69+ :active =" activeMessages (errorMessage )"
70+ :color =" errorMessage ? ' error' : undefined "
71+ data-cy="vsf-field-messages"
72+ :messages =" fieldMessages (errorMessage )"
8473 >
85- <VMessages
86- :active =" activeMessages (props .errorMessage )"
87- :color =" props .errorMessage ? ' error' : undefined "
88- data-cy="vsf-field-messages"
89- :messages =" fieldMessages (props .errorMessage )"
90- >
91- </VMessages >
92- </div >
74+ </VMessages >
9375 </div >
76+ </div >
9477
95- <input
96- data-cy = " vsf-button-field-input "
97- :name = " field.name "
98- type = " hidden "
99- :value = " modelValue "
100- />
101- </ Field >
78+ <input
79+ v-model = " value "
80+ data-cy = " vsf-button- field-input "
81+ :name = " field.name "
82+ type = " hidden "
83+ @change = " handleChange "
84+ / >
10285</template >
10386
10487
10588<script lang="ts" setup>
106- import { Field } from ' vee-validate' ;
89+ import { useField } from ' vee-validate' ;
10790import { VMessages } from ' vuetify/components' ;
10891import type { Option , VSFButtonFieldProps } from ' ./index' ;
10992import type { FieldLabelProps } from ' ../../shared/FieldLabel.vue' ;
@@ -125,42 +108,62 @@ const fieldRequired = computed<FieldLabelProps['required']>(() => {
125108const fieldValidateOn = computed (() => field ?.validateOn ?? settings .value ?.validateOn );
126109const originalValue = modelValue .value ;
127110
111+
112+ const { errorMessage, handleChange, setValue, validate, value } = useField (
113+ field .name ,
114+ undefined ,
115+ {
116+ initialValue: field ?.multiple ? [] : null ,
117+ validateOnBlur: fieldValidateOn .value === ' blur' ,
118+ validateOnChange: fieldValidateOn .value === ' change' ,
119+ validateOnInput: fieldValidateOn .value === ' input' ,
120+ validateOnModelUpdate: fieldValidateOn .value != null ,
121+ },
122+ );
123+
124+
128125onUnmounted (() => {
129126 if (! settings .value ?.keepValuesOnUnmount ) {
130127 modelValue .value = originalValue ;
128+ setValue (originalValue );
131129 }
132130});
133131
134132
135- if (modelValue ?.value == null ) {
136- modelValue .value = field ?.multiple ? [] : null ;
137- }
138-
139133const currentValue = ref (modelValue .value );
140134
141135// ------------------------- Validate On Actions //
142- async function onActions(validate : FieldValidateResult , action : ValidateAction , value ? : string | number ): Promise <void > {
143- if (currentValue .value === value && (fieldValidateOn .value === ' change' || fieldValidateOn .value === ' input' )) {
136+ async function onActions(action : ValidateAction , val ? : string | number ): Promise <void > {
137+ if (currentValue .value === val && (fieldValidateOn .value === ' change' || fieldValidateOn .value === ' input' )) {
144138 return ;
145139 }
146140
147- if (! field ?.disabled && value ) {
141+ if (! field ?.disabled && value .value ) {
142+ let updatedValue: string | string [];
143+
148144 if (field ?.multiple ) {
149- const localModelValue = modelValue .value == null ? [] : modelValue .value as string [];
145+ const localModelValue = Array .isArray (value .value ) ? value .value .slice () : [];
146+ const valStr = String (val );
150147
151- if (localModelValue != null && localModelValue .includes (String (value ))) {
152- const index = localModelValue .indexOf (String (value ));
153- localModelValue .splice (index , 1 );
148+ if (localModelValue .includes (valStr )) {
149+ localModelValue .splice (localModelValue .indexOf (valStr ), 1 );
154150 }
155151 else {
156- localModelValue .push (String ( value ) );
152+ localModelValue .push (valStr );
157153 }
158154
159- modelValue . value = localModelValue ;
155+ updatedValue = localModelValue ;
160156 }
161157 else {
162- modelValue . value = value ;
158+ updatedValue = val as string ;
163159 }
160+
161+ setValue (updatedValue );
162+ modelValue .value = updatedValue ;
163+ }
164+ else {
165+ setValue (val );
166+ modelValue .value = val ;
164167 }
165168
166169 await useOnActions ({
@@ -170,7 +173,7 @@ async function onActions(validate: FieldValidateResult, action: ValidateAction,
170173 settingsValidateOn: settings .value ?.validateOn ,
171174 validate ,
172175 }).then (() => {
173- currentValue .value = modelValue .value ;
176+ currentValue .value = value .value ;
174177 })
175178 .catch ((error : Error ) => {
176179 console .error (error );
@@ -310,11 +313,11 @@ function getHeight(option: Option): string | number | undefined {
310313}
311314
312315const isActive = (val : string | number ): boolean | undefined => {
313- if (! modelValue .value ) {
316+ if (! value .value ) {
314317 return undefined ;
315318 }
316319
317- return modelValue .value === val || (modelValue .value as string []).includes (val as string );
320+ return value .value === val || (value .value as string []).includes (val as string );
318321};
319322
320323// ------------------------- Variants //
@@ -330,8 +333,8 @@ function getVariant(val: string | number): VSFButtonFieldProps['field']['variant
330333
331334
332335// -------------------------------------------------- Active Messages //
333- function activeMessages(errorMessage : string | string []): boolean {
334- if (errorMessage ? errorMessage .length > 0 : false ) {
336+ function activeMessages(errorMsg : string | string []): boolean {
337+ if (errorMsg ? errorMsg .length > 0 : false ) {
335338 return true ;
336339 }
337340
@@ -346,9 +349,9 @@ function activeMessages(errorMessage: string | string[]): boolean {
346349 return false ;
347350}
348351
349- function fieldMessages(errorMessage ? : string | string []): string | string [] {
350- if (errorMessage ? errorMessage .length > 0 : false ) {
351- return errorMessage as string [];
352+ function fieldMessages(errorMsg ? : string | string []): string | string [] {
353+ if (errorMsg ? errorMsg .length > 0 : false ) {
354+ return errorMsg as string [];
352355 }
353356
354357 if (field .hint && (field .persistentHint || isFocused .value )) {
@@ -403,6 +406,7 @@ const buttonFieldContainerClass = computed(() => {
403406 return {
404407 ' d-flex' : field ?.align ,
405408 ' flex-column' : field ?.align ,
409+ ' v-input--error' : errorMessage ? errorMessage ?.length > 0 : false ,
406410 ' vsf-button-field__container' : true ,
407411 [` align-${field ?.align } ` ]: field ?.align ,
408412 };
@@ -420,6 +424,13 @@ const buttonClass = computed(() => {
420424 return {};
421425});
422426
427+ const buttonClassAdditional = (option ) => {
428+ return {
429+ [` ${option ?.class } ` ]: true ,
430+ [` ${field .selectedClass } ` ]: isActive (option .value ) && field .selectedClass != null ,
431+ };
432+ };
433+
423434const getLabelClass = (option : Option ): object => {
424435 const isActiveOption = isActive (option .value );
425436 const optionVariant = getVariant (option .value );
0 commit comments