@@ -7,15 +7,15 @@ SPDX-License-Identifier: AGPL-3.0-only
77<div v-if =" Object.keys(form).filter(item => !form[item].hidden).length > 0" class =" _gaps_m" >
88 <template v-for =" v , k in form " >
99 <template v-if =" typeof v .hidden == ' function' ? v .hidden (values ) : v .hidden " ></template >
10- <MkInput v-else-if =" v.type === 'number'" v-model =" values[k]" type =" number" :step =" v.step || 1" :manualSave =" v.manualSave" >
10+ <MkInput v-else-if =" v.type === 'number'" v-model =" values[k]" type =" number" :step =" v.step || 1" :manualSave =" v.manualSave" @savingStateChange = " (changed, invalid) => onSavingStateChange(k, changed, invalid) " >
1111 <template #label ><span v-text =" v.label || k" ></span ><span v-if =" v.required === false" > ({{ i18n.ts.optional }})</span ></template >
1212 <template v-if =" v .description " #caption >{{ v.description }}</template >
1313 </MkInput >
14- <MkInput v-else-if =" v.type === 'string' && !v.multiline" v-model =" values[k]" type =" text" :mfmAutocomplete =" v.treatAsMfm" :manualSave =" v.manualSave" >
14+ <MkInput v-else-if =" v.type === 'string' && !v.multiline" v-model =" values[k]" type =" text" :mfmAutocomplete =" v.treatAsMfm" :manualSave =" v.manualSave" @savingStateChange = " (changed, invalid) => onSavingStateChange(k, changed, invalid) " >
1515 <template #label ><span v-text =" v.label || k" ></span ><span v-if =" v.required === false" > ({{ i18n.ts.optional }})</span ></template >
1616 <template v-if =" v .description " #caption >{{ v.description }}</template >
1717 </MkInput >
18- <MkTextarea v-else-if =" v.type === 'string' && v.multiline" v-model =" values[k]" :mfmAutocomplete =" v.treatAsMfm" :mfmPreview =" v.treatAsMfm" :manualSave =" v.manualSave" >
18+ <MkTextarea v-else-if =" v.type === 'string' && v.multiline" v-model =" values[k]" :mfmAutocomplete =" v.treatAsMfm" :mfmPreview =" v.treatAsMfm" :manualSave =" v.manualSave" @savingStateChange = " (changed, invalid) => onSavingStateChange(k, changed, invalid) " >
1919 <template #label ><span v-text =" v.label || k" ></span ><span v-if =" v.required === false" > ({{ i18n.ts.optional }})</span ></template >
2020 <template v-if =" v .description " #caption >{{ v.description }}</template >
2121 </MkTextarea >
@@ -49,6 +49,7 @@ SPDX-License-Identifier: AGPL-3.0-only
4949</template >
5050
5151<script lang="ts" setup>
52+ import { computed , ref , watch } from ' vue' ;
5253import XFile from ' @/components/MkForm.file.vue' ;
5354import MkInput from ' @/components/MkInput.vue' ;
5455import MkTextarea from ' @/components/MkTextarea.vue' ;
@@ -65,9 +66,43 @@ const props = defineProps<{
6566 form: Form ;
6667}>();
6768
69+ const emit = defineEmits <{
70+ (ev : ' canSaveStateChange' , canSave : boolean ): void ;
71+ }>();
72+
6873// TODO: ジェネリックにしたい
6974const values = defineModel <Record <string , any >>({ required: true });
7075
76+ // 保存可能状態の管理
77+ const inputSavingStates = ref <Record <string , { changed: boolean ; invalid: boolean }>>({});
78+
79+ function onSavingStateChange(key : string , changed : boolean , invalid : boolean ) {
80+ inputSavingStates .value [key ] = { changed , invalid };
81+ }
82+
83+ const canSave = computed (() => {
84+ for (const key in inputSavingStates .value ) {
85+ const state = inputSavingStates .value [key ];
86+ if (
87+ (' manualSave' in props .form [key ] && props .form [key ].manualSave && state .changed ) ||
88+ state .invalid
89+ ) {
90+ return false ;
91+ }
92+ if (' required' in props .form [key ] && props .form [key ].required ) {
93+ const val = values .value [key ];
94+ if (val === null || val === undefined || val === ' ' ) {
95+ return false ;
96+ }
97+ }
98+ }
99+ return true ;
100+ });
101+
102+ watch (canSave , (newCanSave ) => {
103+ emit (' canSaveStateChange' , newCanSave );
104+ }, { immediate: true });
105+
71106function getMkSelectDef(def : EnumFormItem ): MkSelectItem [] {
72107 return def .enum .map ((v ) => {
73108 if (typeof v === ' string' ) {
0 commit comments