codevdesign 2.0.3 → 2.0.4

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.
@@ -1,340 +1,340 @@
1
- <template>
2
- <div>
3
- <v-form
4
- ref="form"
5
- v-model="formValide"
6
- @submit.prevent
7
- >
8
- <v-combobox
9
- v-model="codeBudgetaire"
10
- :items="itemsCombobox"
11
- item-value="code"
12
- item-title="code"
13
- :return-object="false"
14
- v-bind="$attrs"
15
- persistent-hint
16
- variant="outlined"
17
- hide-details="auto"
18
- :error="!estValideComplet"
19
- :rules="reglesVuetify"
20
- :hint="afficherHint ? placeholder : ''"
21
- @blur="sauvegarder"
22
- @keydown="caractereAutorises"
23
- @update:model-value="gererChangement"
24
- @paste="gererPaste"
25
- >
26
- <template #item="{ props, item }">
27
- <v-list-item
28
- v-bind="props"
29
- :title="item.nom || item.code"
30
- :subtitle="item.nom ? item.code : undefined"
31
- />
32
- </template>
33
- </v-combobox>
34
- </v-form>
35
- </div>
36
- </template>
37
-
38
- <script setup lang="ts">
39
- import { ref, computed, onMounted, nextTick, watch } from 'vue'
40
- import type { VForm } from 'vuetify/components'
41
-
42
- const emit = defineEmits<{
43
- 'update:modelValue': [string | null]
44
- 'update:codeBudgetairesProp': [CodeBudgetaireItem[]]
45
- 'update:valide': [boolean]
46
- }>()
47
- type CodeBudgetaireItem = string | [string, string]
48
-
49
- const props = withDefaults(
50
- defineProps<{
51
- codeBudgetairesProp: CodeBudgetaireItem[]
52
- modelValue: string | null
53
- afficherHint?: boolean
54
- regleMessageErreur: string
55
- format?: string
56
- activerExtension?: boolean
57
- reglesSupp?: ((v: string) => true | string)[]
58
- }>(),
59
- {
60
- afficherHint: false,
61
- format: '999-9-99999-999',
62
- activerExtension: false,
63
- reglesSupp: () => [],
64
- },
65
- )
66
-
67
- const formValide = ref(false)
68
- const form = ref<VForm | null>(null)
69
- const codeBudgetaire = ref(props.modelValue ?? '')
70
- const derniereValeurSauvegardee = ref<string | null>(null)
71
- const format = props.format
72
- const activerExtension = props.activerExtension
73
-
74
- onMounted(() => {
75
- derniereValeurSauvegardee.value = codeBudgetaire.value
76
- })
77
-
78
- const itemsCombobox = computed(() => {
79
- return props.codeBudgetairesProp.map(item => {
80
- if (typeof item === 'string') {
81
- return {
82
- code: item,
83
- nom: '', // pas de nom
84
- }
85
- }
86
-
87
- const [code, nomBrut] = item
88
- const nom = (nomBrut ?? '').toString().trim()
89
-
90
- return { code, nom }
91
- })
92
- })
93
-
94
- const placeholder = computed(() => {
95
- const base = format.replace(/9/g, '0')
96
- const extension = activerExtension ? '-XXX/XXX' : ''
97
- return base + extension
98
- })
99
-
100
- const estValide = computed(() => {
101
- const val = codeBudgetaire.value?.toUpperCase().trim() || ''
102
- const base = val.slice(0, 15)
103
- const extension = val.slice(15)
104
-
105
- if (!/^\d{3}-\d{1}-\d{5}-\d{3}$/.test(base)) return false
106
-
107
- if (!activerExtension) return val.length === 15
108
-
109
- if (val.length === 15) return true
110
- if (val.length !== 22) return false
111
- if (extension.length !== 7) return false
112
-
113
- if (extension[3] !== '/') return false
114
- if (!/^[A-Z0-9/]$/i.test(extension[0]!)) return false
115
-
116
- const alphanumAt = [1, 2, 4, 5, 6]
117
- return alphanumAt.every(i => /^[A-Z0-9]$/i.test(extension[i]!))
118
- })
119
-
120
- // Règles Vuetify combinées (interne + supplémentaires)
121
- const reglesVuetify = computed(() => [
122
- // règle interne
123
- () => (estValide.value ? true : props.regleMessageErreur),
124
-
125
- // règles supplémentaires venant du parent
126
- ...props.reglesSupp.map(rule => {
127
- return () => rule(codeBudgetaire.value) // on passe la valeur formatée
128
- }),
129
- ])
130
-
131
- // Validité globale du composant (interne + toutes les règles supp)
132
- const estValideComplet = computed(() => {
133
- if (!estValide.value) return false
134
- return props.reglesSupp.every(rule => rule(codeBudgetaire.value) === true)
135
- })
136
-
137
- const caractereAutorises = (e: KeyboardEvent) => {
138
- if (e.key === 'Enter') {
139
- sauvegarder()
140
- return
141
- }
142
-
143
- if (e.ctrlKey || e.metaKey) return
144
-
145
- const touchesSpecifiques = ['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab', 'Home', 'End']
146
- if (touchesSpecifiques.includes(e.key)) return
147
-
148
- const input = e.target as HTMLInputElement
149
- const position = input.selectionStart ?? 0
150
-
151
- // Gestion de la partie de base (15 premiers caractères)
152
- if (position < 15) {
153
- if (!/^\d$/.test(e.key)) {
154
- e.preventDefault()
155
- return
156
- }
157
-
158
- // Insérer chiffre et auto-ajout des tirets
159
- e.preventDefault()
160
-
161
- const value = codeBudgetaire.value.replace(/-/g, '')
162
- const clean = value.slice(0, 12) + e.key
163
-
164
- let formatted = ''
165
- if (clean.length > 0) formatted += clean.slice(0, 3)
166
- if (clean.length > 3) formatted += '-' + clean.slice(3, 4)
167
- if (clean.length > 4) formatted += '-' + clean.slice(4, 9)
168
- if (clean.length > 9) formatted += '-' + clean.slice(9, 12)
169
-
170
- codeBudgetaire.value = formatted.slice(0, 15)
171
-
172
- nextTick(() => {
173
- const newPos = codeBudgetaire.value.length
174
- input.selectionStart = input.selectionEnd = newPos
175
- })
176
-
177
- return
178
- }
179
-
180
- // --- Gestion de l'extension ---
181
- if (!activerExtension || position >= 22 || codeBudgetaire.value.length >= 22) {
182
- e.preventDefault()
183
- return
184
- }
185
-
186
- const extensionPos = position - 15
187
-
188
- // Règle 1 : extension[0] = alphanum ou /
189
- if (extensionPos === 0) {
190
- if (!/^[A-Z0-9/]$/i.test(e.key)) {
191
- e.preventDefault()
192
- return
193
- }
194
- return
195
- }
196
-
197
- // Règle 2 : extension[1,2,4,5,6] = alphanum
198
- if ([1, 2, 4, 5, 6].includes(extensionPos)) {
199
- if (!/^[A-Z0-9]$/i.test(e.key)) {
200
- e.preventDefault()
201
- return
202
- }
203
- return
204
- }
205
-
206
- // Règle 3 : slash automatique à position 3 (index 18)
207
- if (extensionPos === 3) {
208
- e.preventDefault()
209
- const before = codeBudgetaire.value.slice(0, position)
210
- const after = codeBudgetaire.value.slice(position)
211
- codeBudgetaire.value = before + '/' + after
212
- nextTick(() => {
213
- input.selectionStart = input.selectionEnd = position + 1
214
- })
215
- return
216
- }
217
-
218
- // Tout autre cas = bloqué
219
- e.preventDefault()
220
- }
221
-
222
- const formaterCodeBudgetaire = (valeur: string): string => {
223
- if (!valeur) return ''
224
-
225
- const upper = valeur.toUpperCase().replace(/[^A-Z0-9/]/g, '')
226
- const chiffres = upper.replace(/[^0-9]/g, '').slice(0, 12)
227
-
228
- let base = ''
229
- if (chiffres.length > 0) base += chiffres.slice(0, 3)
230
- if (chiffres.length > 3) base += '-' + chiffres.slice(3, 4)
231
- if (chiffres.length > 4) base += '-' + chiffres.slice(4, 9)
232
- if (chiffres.length > 9) base += '-' + chiffres.slice(9, 12)
233
-
234
- if (!activerExtension || base.length < 15) return base
235
-
236
- const reste = upper.slice(chiffres.length).replace(/[^A-Z0-9/]/gi, '')
237
-
238
- // Extraire les 7 premiers caractères restants pour l’extension
239
- let ext = reste.slice(0, 7).split('')
240
-
241
- // Ne garder que le premier slash s’il est à l’index 0 ou 3
242
- ext = ext.filter((c, i) => {
243
- if (c !== '/') return true
244
- return i === 0 || i === 3
245
- })
246
-
247
- // Enlever les slash non autorisés
248
- ext = ext.map((c, i) => {
249
- if (c === '/' && i !== 0 && i !== 3) return ''
250
- if (c !== '/' && !/^[A-Z0-9]$/i.test(c)) return ''
251
- return c
252
- })
253
-
254
- // Forcer le slash à la 4e position
255
- if (ext.length > 3) {
256
- ext[3] = '/'
257
- }
258
-
259
- // Réduire à 7 caractères
260
- ext = ext.slice(0, 7)
261
-
262
- return (base + ext.join('')).slice(0, 22)
263
- }
264
-
265
- const gererPaste = (e: ClipboardEvent) => {
266
- e.preventDefault()
267
- const clipboardData = e.clipboardData
268
- if (!clipboardData) return
269
- const pasted = clipboardData.getData('text') || ''
270
- codeBudgetaire.value = formaterCodeBudgetaire(pasted)
271
-
272
- setTimeout(() => {
273
- const input = e.target as HTMLInputElement
274
- input.selectionStart = input.selectionEnd = codeBudgetaire.value.length
275
- }, 0)
276
- }
277
-
278
- const sauvegarder = () => {
279
- codeBudgetaire.value = formaterCodeBudgetaire(codeBudgetaire.value)
280
-
281
- if (!estValideComplet.value) return
282
- if (codeBudgetaire.value === derniereValeurSauvegardee.value) return
283
-
284
- const codeNormalise = codeBudgetaire.value.trim().toUpperCase()
285
-
286
- const existe = props.codeBudgetairesProp.some(item => {
287
- const code = typeof item === 'string' ? item : item[0]
288
- return code.trim().toUpperCase() === codeNormalise
289
- })
290
-
291
- if (!existe) {
292
- const nouvelleListe = [...props.codeBudgetairesProp, codeBudgetaire.value]
293
- emit('update:codeBudgetairesProp', nouvelleListe)
294
- }
295
-
296
- derniereValeurSauvegardee.value = codeBudgetaire.value
297
- emit('update:modelValue', codeBudgetaire.value)
298
- }
299
-
300
- const extraireCode = (val: unknown): string => {
301
- if (val == null) return ''
302
- if (typeof val === 'string') return val
303
-
304
- // Cas où Vuetify enverrait { code, label }
305
- if (typeof val === 'object' && 'code' in (val as Record<string, unknown>)) {
306
- const obj = val as { code?: unknown }
307
- return typeof obj.code === 'string' ? obj.code : String(obj.code ?? '')
308
- }
309
-
310
- return String(val)
311
- }
312
-
313
- const gererChangement = (val: unknown) => {
314
- const code = extraireCode(val)
315
- codeBudgetaire.value = formaterCodeBudgetaire(code)
316
-
317
- const valeurFormatee = codeBudgetaire.value.trim().toUpperCase()
318
-
319
- const estDansListe = props.codeBudgetairesProp.some(item => {
320
- const codeItem = typeof item === 'string' ? item : item[0]
321
- return codeItem.trim().toUpperCase() === valeurFormatee
322
- })
323
-
324
- if (
325
- estDansListe &&
326
- valeurFormatee !== (derniereValeurSauvegardee.value ?? '').toUpperCase() &&
327
- estValideComplet.value
328
- ) {
329
- sauvegarder()
330
- }
331
- }
332
-
333
- watch(
334
- () => codeBudgetaire.value,
335
- () => {
336
- emit('update:valide', estValideComplet.value)
337
- },
338
- { immediate: true },
339
- )
340
- </script>
1
+ <template>
2
+ <div>
3
+ <v-form
4
+ ref="form"
5
+ v-model="formValide"
6
+ @submit.prevent
7
+ >
8
+ <v-combobox
9
+ v-model="codeBudgetaire"
10
+ :items="itemsCombobox"
11
+ item-value="code"
12
+ item-title="code"
13
+ :return-object="false"
14
+ persistent-hint
15
+ variant="outlined"
16
+ hide-details="auto"
17
+ :error="!estValideComplet"
18
+ :rules="reglesVuetify"
19
+ :hint="afficherHint ? placeholder : ''"
20
+ v-bind="$attrs"
21
+ @blur="sauvegarder"
22
+ @keydown="caractereAutorises"
23
+ @update:model-value="gererChangement"
24
+ @paste="gererPaste"
25
+ >
26
+ <template #item="{ props, item }">
27
+ <v-list-item
28
+ v-bind="props"
29
+ :title="item.nom || item.code"
30
+ :subtitle="item.nom ? item.code : undefined"
31
+ />
32
+ </template>
33
+ </v-combobox>
34
+ </v-form>
35
+ </div>
36
+ </template>
37
+
38
+ <script setup lang="ts">
39
+ import { ref, computed, onMounted, nextTick, watch } from 'vue'
40
+ import type { VForm } from 'vuetify/components'
41
+
42
+ const emit = defineEmits<{
43
+ 'update:modelValue': [string | null]
44
+ 'update:codeBudgetairesProp': [CodeBudgetaireItem[]]
45
+ 'update:valide': [boolean]
46
+ }>()
47
+ type CodeBudgetaireItem = string | [string, string]
48
+
49
+ const props = withDefaults(
50
+ defineProps<{
51
+ codeBudgetairesProp: CodeBudgetaireItem[]
52
+ modelValue: string | null
53
+ afficherHint?: boolean
54
+ regleMessageErreur: string
55
+ format?: string
56
+ activerExtension?: boolean
57
+ reglesSupp?: ((v: string) => true | string)[]
58
+ }>(),
59
+ {
60
+ afficherHint: false,
61
+ format: '999-9-99999-999',
62
+ activerExtension: false,
63
+ reglesSupp: () => [],
64
+ },
65
+ )
66
+
67
+ const formValide = ref(false)
68
+ const form = ref<VForm | null>(null)
69
+ const codeBudgetaire = ref(props.modelValue ?? '')
70
+ const derniereValeurSauvegardee = ref<string | null>(null)
71
+ const format = props.format
72
+ const activerExtension = props.activerExtension
73
+
74
+ onMounted(() => {
75
+ derniereValeurSauvegardee.value = codeBudgetaire.value
76
+ })
77
+
78
+ const itemsCombobox = computed(() => {
79
+ return props.codeBudgetairesProp.map(item => {
80
+ if (typeof item === 'string') {
81
+ return {
82
+ code: item,
83
+ nom: '', // pas de nom
84
+ }
85
+ }
86
+
87
+ const [code, nomBrut] = item
88
+ const nom = (nomBrut ?? '').toString().trim()
89
+
90
+ return { code, nom }
91
+ })
92
+ })
93
+
94
+ const placeholder = computed(() => {
95
+ const base = format.replace(/9/g, '0')
96
+ const extension = activerExtension ? '-XXX/XXX' : ''
97
+ return base + extension
98
+ })
99
+
100
+ const estValide = computed(() => {
101
+ const val = codeBudgetaire.value?.toUpperCase().trim() || ''
102
+ const base = val.slice(0, 15)
103
+ const extension = val.slice(15)
104
+
105
+ if (!/^\d{3}-\d{1}-\d{5}-\d{3}$/.test(base)) return false
106
+
107
+ if (!activerExtension) return val.length === 15
108
+
109
+ if (val.length === 15) return true
110
+ if (val.length !== 22) return false
111
+ if (extension.length !== 7) return false
112
+
113
+ if (extension[3] !== '/') return false
114
+ if (!/^[A-Z0-9/]$/i.test(extension[0]!)) return false
115
+
116
+ const alphanumAt = [1, 2, 4, 5, 6]
117
+ return alphanumAt.every(i => /^[A-Z0-9]$/i.test(extension[i]!))
118
+ })
119
+
120
+ // Règles Vuetify combinées (interne + supplémentaires)
121
+ const reglesVuetify = computed(() => [
122
+ // règle interne
123
+ () => (estValide.value ? true : props.regleMessageErreur),
124
+
125
+ // règles supplémentaires venant du parent
126
+ ...props.reglesSupp.map(rule => {
127
+ return () => rule(codeBudgetaire.value) // on passe la valeur formatée
128
+ }),
129
+ ])
130
+
131
+ // Validité globale du composant (interne + toutes les règles supp)
132
+ const estValideComplet = computed(() => {
133
+ if (!estValide.value) return false
134
+ return props.reglesSupp.every(rule => rule(codeBudgetaire.value) === true)
135
+ })
136
+
137
+ const caractereAutorises = (e: KeyboardEvent) => {
138
+ if (e.key === 'Enter') {
139
+ sauvegarder()
140
+ return
141
+ }
142
+
143
+ if (e.ctrlKey || e.metaKey) return
144
+
145
+ const touchesSpecifiques = ['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab', 'Home', 'End']
146
+ if (touchesSpecifiques.includes(e.key)) return
147
+
148
+ const input = e.target as HTMLInputElement
149
+ const position = input.selectionStart ?? 0
150
+
151
+ // Gestion de la partie de base (15 premiers caractères)
152
+ if (position < 15) {
153
+ if (!/^\d$/.test(e.key)) {
154
+ e.preventDefault()
155
+ return
156
+ }
157
+
158
+ // Insérer chiffre et auto-ajout des tirets
159
+ e.preventDefault()
160
+
161
+ const value = codeBudgetaire.value.replace(/-/g, '')
162
+ const clean = value.slice(0, 12) + e.key
163
+
164
+ let formatted = ''
165
+ if (clean.length > 0) formatted += clean.slice(0, 3)
166
+ if (clean.length > 3) formatted += '-' + clean.slice(3, 4)
167
+ if (clean.length > 4) formatted += '-' + clean.slice(4, 9)
168
+ if (clean.length > 9) formatted += '-' + clean.slice(9, 12)
169
+
170
+ codeBudgetaire.value = formatted.slice(0, 15)
171
+
172
+ nextTick(() => {
173
+ const newPos = codeBudgetaire.value.length
174
+ input.selectionStart = input.selectionEnd = newPos
175
+ })
176
+
177
+ return
178
+ }
179
+
180
+ // --- Gestion de l'extension ---
181
+ if (!activerExtension || position >= 22 || codeBudgetaire.value.length >= 22) {
182
+ e.preventDefault()
183
+ return
184
+ }
185
+
186
+ const extensionPos = position - 15
187
+
188
+ // Règle 1 : extension[0] = alphanum ou /
189
+ if (extensionPos === 0) {
190
+ if (!/^[A-Z0-9/]$/i.test(e.key)) {
191
+ e.preventDefault()
192
+ return
193
+ }
194
+ return
195
+ }
196
+
197
+ // Règle 2 : extension[1,2,4,5,6] = alphanum
198
+ if ([1, 2, 4, 5, 6].includes(extensionPos)) {
199
+ if (!/^[A-Z0-9]$/i.test(e.key)) {
200
+ e.preventDefault()
201
+ return
202
+ }
203
+ return
204
+ }
205
+
206
+ // Règle 3 : slash automatique à position 3 (index 18)
207
+ if (extensionPos === 3) {
208
+ e.preventDefault()
209
+ const before = codeBudgetaire.value.slice(0, position)
210
+ const after = codeBudgetaire.value.slice(position)
211
+ codeBudgetaire.value = before + '/' + after
212
+ nextTick(() => {
213
+ input.selectionStart = input.selectionEnd = position + 1
214
+ })
215
+ return
216
+ }
217
+
218
+ // Tout autre cas = bloqué
219
+ e.preventDefault()
220
+ }
221
+
222
+ const formaterCodeBudgetaire = (valeur: string): string => {
223
+ if (!valeur) return ''
224
+
225
+ const upper = valeur.toUpperCase().replace(/[^A-Z0-9/]/g, '')
226
+ const chiffres = upper.replace(/[^0-9]/g, '').slice(0, 12)
227
+
228
+ let base = ''
229
+ if (chiffres.length > 0) base += chiffres.slice(0, 3)
230
+ if (chiffres.length > 3) base += '-' + chiffres.slice(3, 4)
231
+ if (chiffres.length > 4) base += '-' + chiffres.slice(4, 9)
232
+ if (chiffres.length > 9) base += '-' + chiffres.slice(9, 12)
233
+
234
+ if (!activerExtension || base.length < 15) return base
235
+
236
+ const reste = upper.slice(chiffres.length).replace(/[^A-Z0-9/]/gi, '')
237
+
238
+ // Extraire les 7 premiers caractères restants pour l’extension
239
+ let ext = reste.slice(0, 7).split('')
240
+
241
+ // Ne garder que le premier slash s’il est à l’index 0 ou 3
242
+ ext = ext.filter((c, i) => {
243
+ if (c !== '/') return true
244
+ return i === 0 || i === 3
245
+ })
246
+
247
+ // Enlever les slash non autorisés
248
+ ext = ext.map((c, i) => {
249
+ if (c === '/' && i !== 0 && i !== 3) return ''
250
+ if (c !== '/' && !/^[A-Z0-9]$/i.test(c)) return ''
251
+ return c
252
+ })
253
+
254
+ // Forcer le slash à la 4e position
255
+ if (ext.length > 3) {
256
+ ext[3] = '/'
257
+ }
258
+
259
+ // Réduire à 7 caractères
260
+ ext = ext.slice(0, 7)
261
+
262
+ return (base + ext.join('')).slice(0, 22)
263
+ }
264
+
265
+ const gererPaste = (e: ClipboardEvent) => {
266
+ e.preventDefault()
267
+ const clipboardData = e.clipboardData
268
+ if (!clipboardData) return
269
+ const pasted = clipboardData.getData('text') || ''
270
+ codeBudgetaire.value = formaterCodeBudgetaire(pasted)
271
+
272
+ setTimeout(() => {
273
+ const input = e.target as HTMLInputElement
274
+ input.selectionStart = input.selectionEnd = codeBudgetaire.value.length
275
+ }, 0)
276
+ }
277
+
278
+ const sauvegarder = () => {
279
+ codeBudgetaire.value = formaterCodeBudgetaire(codeBudgetaire.value)
280
+
281
+ if (!estValideComplet.value) return
282
+ if (codeBudgetaire.value === derniereValeurSauvegardee.value) return
283
+
284
+ const codeNormalise = codeBudgetaire.value.trim().toUpperCase()
285
+
286
+ const existe = props.codeBudgetairesProp.some(item => {
287
+ const code = typeof item === 'string' ? item : item[0]
288
+ return code.trim().toUpperCase() === codeNormalise
289
+ })
290
+
291
+ if (!existe) {
292
+ const nouvelleListe = [...props.codeBudgetairesProp, codeBudgetaire.value]
293
+ emit('update:codeBudgetairesProp', nouvelleListe)
294
+ }
295
+
296
+ derniereValeurSauvegardee.value = codeBudgetaire.value
297
+ emit('update:modelValue', codeBudgetaire.value)
298
+ }
299
+
300
+ const extraireCode = (val: unknown): string => {
301
+ if (val == null) return ''
302
+ if (typeof val === 'string') return val
303
+
304
+ // Cas où Vuetify enverrait { code, label }
305
+ if (typeof val === 'object' && 'code' in (val as Record<string, unknown>)) {
306
+ const obj = val as { code?: unknown }
307
+ return typeof obj.code === 'string' ? obj.code : String(obj.code ?? '')
308
+ }
309
+
310
+ return String(val)
311
+ }
312
+
313
+ const gererChangement = (val: unknown) => {
314
+ const code = extraireCode(val)
315
+ codeBudgetaire.value = formaterCodeBudgetaire(code)
316
+
317
+ const valeurFormatee = codeBudgetaire.value.trim().toUpperCase()
318
+
319
+ const estDansListe = props.codeBudgetairesProp.some(item => {
320
+ const codeItem = typeof item === 'string' ? item : item[0]
321
+ return codeItem.trim().toUpperCase() === valeurFormatee
322
+ })
323
+
324
+ if (
325
+ estDansListe &&
326
+ valeurFormatee !== (derniereValeurSauvegardee.value ?? '').toUpperCase() &&
327
+ estValideComplet.value
328
+ ) {
329
+ sauvegarder()
330
+ }
331
+ }
332
+
333
+ watch(
334
+ () => codeBudgetaire.value,
335
+ () => {
336
+ emit('update:valide', estValideComplet.value)
337
+ },
338
+ { immediate: true },
339
+ )
340
+ </script>