codevdesign 1.0.7 → 1.0.8

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,257 +1,257 @@
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="codeBudgetairesProp"
11
- v-bind="$attrs"
12
- :label="label"
13
- persistent-hint
14
- variant="outlined"
15
- hide-details="auto"
16
- :error="!estValide"
17
- :rules="[v => (estValide ? true : regleMessageErreur)]"
18
- :hint="afficherHint ? placeholder : ''"
19
- :max-width="maxWidth || '100%'"
20
- :min-width="minWidth || '100%'"
21
- @blur="sauvegarder"
22
- @keydown.enter="sauvegarder"
23
- @keydown="caractereAutorises"
24
- @update:modelValue="gererChangement"
25
- @paste="gererPaste"
26
- />
27
- </v-form>
28
- </div>
29
- </template>
30
-
31
- <script setup lang="ts">
32
- import { ref, computed, onMounted, nextTick } from 'vue'
33
- import type { VForm } from 'vuetify/components'
34
-
35
- const emit = defineEmits(['update:modelValue', 'update:codeBudgetairesProp'])
36
-
37
- const props = withDefaults(
38
- defineProps<{
39
- codeBudgetairesProp: string[]
40
- modelValue: string | null
41
- label: string
42
- afficherHint?: boolean
43
- regleMessageErreur: string
44
- maxWidth?: string | number
45
- minWidth?: string | number
46
- format?: string
47
- activerExtension?: boolean
48
- }>(),
49
- {
50
- afficherHint: false,
51
- format: '999-9-99999-999',
52
- activerExtension: false,
53
- },
54
- )
55
-
56
- const formValide = ref(false)
57
- const form = ref<VForm | null>(null)
58
- const codeBudgetaire = ref(props.modelValue ?? '')
59
- const derniereValeurSauvegardee = ref<string | null>(null)
60
- const format = props.format
61
- const activerExtension = props.activerExtension
62
-
63
- const estValide = computed(() => {
64
- const val = codeBudgetaire.value?.toUpperCase().trim() || ''
65
- const base = val.slice(0, 15)
66
- const extension = val.slice(15)
67
-
68
- if (!/^\d{3}-\d{1}-\d{5}-\d{3}$/.test(base)) return false
69
-
70
- if (!activerExtension) return val.length === 15
71
-
72
- if (val.length === 15) return true
73
- if (val.length !== 22) return false
74
- if (extension.length !== 7) return false
75
-
76
- if (extension[3] !== '/') return false
77
- if (!/^[A-Z0-9/]$/i.test(extension[0]!)) return false
78
-
79
- const alphanumAt = [1, 2, 4, 5, 6]
80
- return alphanumAt.every(i => /^[A-Z0-9]$/i.test(extension[i]!))
81
- })
82
-
83
- const caractereAutorises = (e: KeyboardEvent) => {
84
- if (e.ctrlKey || e.metaKey) return
85
-
86
- const touchesSpecifiques = ['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab', 'Home', 'End']
87
- if (touchesSpecifiques.includes(e.key)) return
88
-
89
- const input = e.target as HTMLInputElement
90
- let position = input.selectionStart ?? 0
91
-
92
- // Gestion de la partie de base (15 premiers caractères)
93
- if (position < 15) {
94
- if (!/^\d$/.test(e.key)) {
95
- e.preventDefault()
96
- return
97
- }
98
-
99
- // Insérer chiffre et auto-ajout des tirets
100
- e.preventDefault()
101
-
102
- const value = codeBudgetaire.value.replace(/-/g, '')
103
- const clean = value.slice(0, 12) + e.key
104
-
105
- let formatted = ''
106
- if (clean.length > 0) formatted += clean.slice(0, 3)
107
- if (clean.length > 3) formatted += '-' + clean.slice(3, 4)
108
- if (clean.length > 4) formatted += '-' + clean.slice(4, 9)
109
- if (clean.length > 9) formatted += '-' + clean.slice(9, 12)
110
-
111
- codeBudgetaire.value = formatted.slice(0, 15)
112
-
113
- nextTick(() => {
114
- const newPos = codeBudgetaire.value.length
115
- input.selectionStart = input.selectionEnd = newPos
116
- })
117
-
118
- return
119
- }
120
-
121
- // --- Gestion de l'extension ---
122
- if (!activerExtension || position >= 22 || codeBudgetaire.value.length >= 22) {
123
- e.preventDefault()
124
- return
125
- }
126
-
127
- const extensionPos = position - 15
128
-
129
- // Règle 1 : extension[0] = alphanum ou /
130
- if (extensionPos === 0) {
131
- if (!/^[A-Z0-9/]$/i.test(e.key)) {
132
- e.preventDefault()
133
- return
134
- }
135
- return
136
- }
137
-
138
- // Règle 2 : extension[1,2,4,5,6] = alphanum
139
- if ([1, 2, 4, 5, 6].includes(extensionPos)) {
140
- if (!/^[A-Z0-9]$/i.test(e.key)) {
141
- e.preventDefault()
142
- return
143
- }
144
- return
145
- }
146
-
147
- // Règle 3 : slash automatique à position 3 (index 18)
148
- if (extensionPos === 3) {
149
- e.preventDefault()
150
- const before = codeBudgetaire.value.slice(0, position)
151
- const after = codeBudgetaire.value.slice(position)
152
- codeBudgetaire.value = before + '/' + after
153
- nextTick(() => {
154
- input.selectionStart = input.selectionEnd = position + 1
155
- })
156
- return
157
- }
158
-
159
- // Tout autre cas = bloqué
160
- e.preventDefault()
161
- }
162
-
163
- const formaterCodeBudgetaire = (valeur: string): string => {
164
- if (!valeur) return ''
165
-
166
- const upper = valeur.toUpperCase().replace(/[^A-Z0-9/]/g, '')
167
- const chiffres = upper.replace(/[^0-9]/g, '').slice(0, 12)
168
-
169
- let base = ''
170
- if (chiffres.length > 0) base += chiffres.slice(0, 3)
171
- if (chiffres.length > 3) base += '-' + chiffres.slice(3, 4)
172
- if (chiffres.length > 4) base += '-' + chiffres.slice(4, 9)
173
- if (chiffres.length > 9) base += '-' + chiffres.slice(9, 12)
174
-
175
- if (!activerExtension || base.length < 15) return base
176
-
177
- const reste = upper.slice(chiffres.length).replace(/[^A-Z0-9/]/gi, '')
178
-
179
- // Extraire les 7 premiers caractères restants pour l’extension
180
- let ext = reste.slice(0, 7).split('')
181
-
182
- // Ne garder que le premier slash s’il est à l’index 0 ou 3
183
- ext = ext.filter((c, i) => {
184
- if (c !== '/') return true
185
- return i === 0 || i === 3
186
- })
187
-
188
- // Enlever les slash non autorisés
189
- ext = ext.map((c, i) => {
190
- if (c === '/' && i !== 0 && i !== 3) return ''
191
- if (c !== '/' && !/^[A-Z0-9]$/i.test(c)) return ''
192
- return c
193
- })
194
-
195
- // Forcer le slash à la 4e position
196
- if (ext.length > 3) {
197
- ext[3] = '/'
198
- }
199
-
200
- // Réduire à 7 caractères
201
- ext = ext.slice(0, 7)
202
-
203
- return (base + ext.join('')).slice(0, 22)
204
- }
205
-
206
- const gererPaste = (e: ClipboardEvent) => {
207
- e.preventDefault()
208
- const clipboardData = e.clipboardData
209
- if (!clipboardData) return
210
- let pasted = clipboardData.getData('text') || ''
211
- codeBudgetaire.value = formaterCodeBudgetaire(pasted)
212
-
213
- setTimeout(() => {
214
- const input = e.target as HTMLInputElement
215
- input.selectionStart = input.selectionEnd = codeBudgetaire.value.length
216
- }, 0)
217
- }
218
-
219
- const sauvegarder = () => {
220
- codeBudgetaire.value = formaterCodeBudgetaire(codeBudgetaire.value)
221
- if (!estValide.value) return
222
- if (codeBudgetaire.value === derniereValeurSauvegardee.value) return
223
-
224
- const existe = props.codeBudgetairesProp.some(
225
- item => item.trim().toUpperCase() === codeBudgetaire.value.trim().toUpperCase(),
226
- )
227
-
228
- if (!existe) {
229
- const nouvelleListe = [...props.codeBudgetairesProp, codeBudgetaire.value]
230
- emit('update:codeBudgetairesProp', nouvelleListe)
231
- }
232
-
233
- derniereValeurSauvegardee.value = codeBudgetaire.value
234
- emit('update:modelValue', codeBudgetaire.value)
235
- }
236
-
237
- const gererChangement = (val: string) => {
238
- codeBudgetaire.value = formaterCodeBudgetaire(val)
239
-
240
- const valeurFormatee = codeBudgetaire.value
241
- const estDansListe = props.codeBudgetairesProp.includes(valeurFormatee)
242
-
243
- if (estDansListe && valeurFormatee !== derniereValeurSauvegardee.value && estValide.value) {
244
- sauvegarder()
245
- }
246
- }
247
-
248
- onMounted(() => {
249
- derniereValeurSauvegardee.value = codeBudgetaire.value
250
- })
251
-
252
- const placeholder = computed(() => {
253
- const base = format.replace(/9/g, '0')
254
- const extension = activerExtension ? '-XXX/XXX' : ''
255
- return base + extension
256
- })
257
- </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="codeBudgetairesProp"
11
+ v-bind="$attrs"
12
+ :label="label"
13
+ persistent-hint
14
+ variant="outlined"
15
+ hide-details="auto"
16
+ :error="!estValide"
17
+ :rules="[v => (estValide ? true : regleMessageErreur)]"
18
+ :hint="afficherHint ? placeholder : ''"
19
+ :max-width="maxWidth || '100%'"
20
+ :min-width="minWidth || '100%'"
21
+ @blur="sauvegarder"
22
+ @keydown.enter="sauvegarder"
23
+ @keydown="caractereAutorises"
24
+ @update:modelValue="gererChangement"
25
+ @paste="gererPaste"
26
+ />
27
+ </v-form>
28
+ </div>
29
+ </template>
30
+
31
+ <script setup lang="ts">
32
+ import { ref, computed, onMounted, nextTick } from 'vue'
33
+ import type { VForm } from 'vuetify/components'
34
+
35
+ const emit = defineEmits(['update:modelValue', 'update:codeBudgetairesProp'])
36
+
37
+ const props = withDefaults(
38
+ defineProps<{
39
+ codeBudgetairesProp: string[]
40
+ modelValue: string | null
41
+ label: string
42
+ afficherHint?: boolean
43
+ regleMessageErreur: string
44
+ maxWidth?: string | number
45
+ minWidth?: string | number
46
+ format?: string
47
+ activerExtension?: boolean
48
+ }>(),
49
+ {
50
+ afficherHint: false,
51
+ format: '999-9-99999-999',
52
+ activerExtension: false,
53
+ },
54
+ )
55
+
56
+ const formValide = ref(false)
57
+ const form = ref<VForm | null>(null)
58
+ const codeBudgetaire = ref(props.modelValue ?? '')
59
+ const derniereValeurSauvegardee = ref<string | null>(null)
60
+ const format = props.format
61
+ const activerExtension = props.activerExtension
62
+
63
+ const estValide = computed(() => {
64
+ const val = codeBudgetaire.value?.toUpperCase().trim() || ''
65
+ const base = val.slice(0, 15)
66
+ const extension = val.slice(15)
67
+
68
+ if (!/^\d{3}-\d{1}-\d{5}-\d{3}$/.test(base)) return false
69
+
70
+ if (!activerExtension) return val.length === 15
71
+
72
+ if (val.length === 15) return true
73
+ if (val.length !== 22) return false
74
+ if (extension.length !== 7) return false
75
+
76
+ if (extension[3] !== '/') return false
77
+ if (!/^[A-Z0-9/]$/i.test(extension[0]!)) return false
78
+
79
+ const alphanumAt = [1, 2, 4, 5, 6]
80
+ return alphanumAt.every(i => /^[A-Z0-9]$/i.test(extension[i]!))
81
+ })
82
+
83
+ const caractereAutorises = (e: KeyboardEvent) => {
84
+ if (e.ctrlKey || e.metaKey) return
85
+
86
+ const touchesSpecifiques = ['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab', 'Home', 'End']
87
+ if (touchesSpecifiques.includes(e.key)) return
88
+
89
+ const input = e.target as HTMLInputElement
90
+ let position = input.selectionStart ?? 0
91
+
92
+ // Gestion de la partie de base (15 premiers caractères)
93
+ if (position < 15) {
94
+ if (!/^\d$/.test(e.key)) {
95
+ e.preventDefault()
96
+ return
97
+ }
98
+
99
+ // Insérer chiffre et auto-ajout des tirets
100
+ e.preventDefault()
101
+
102
+ const value = codeBudgetaire.value.replace(/-/g, '')
103
+ const clean = value.slice(0, 12) + e.key
104
+
105
+ let formatted = ''
106
+ if (clean.length > 0) formatted += clean.slice(0, 3)
107
+ if (clean.length > 3) formatted += '-' + clean.slice(3, 4)
108
+ if (clean.length > 4) formatted += '-' + clean.slice(4, 9)
109
+ if (clean.length > 9) formatted += '-' + clean.slice(9, 12)
110
+
111
+ codeBudgetaire.value = formatted.slice(0, 15)
112
+
113
+ nextTick(() => {
114
+ const newPos = codeBudgetaire.value.length
115
+ input.selectionStart = input.selectionEnd = newPos
116
+ })
117
+
118
+ return
119
+ }
120
+
121
+ // --- Gestion de l'extension ---
122
+ if (!activerExtension || position >= 22 || codeBudgetaire.value.length >= 22) {
123
+ e.preventDefault()
124
+ return
125
+ }
126
+
127
+ const extensionPos = position - 15
128
+
129
+ // Règle 1 : extension[0] = alphanum ou /
130
+ if (extensionPos === 0) {
131
+ if (!/^[A-Z0-9/]$/i.test(e.key)) {
132
+ e.preventDefault()
133
+ return
134
+ }
135
+ return
136
+ }
137
+
138
+ // Règle 2 : extension[1,2,4,5,6] = alphanum
139
+ if ([1, 2, 4, 5, 6].includes(extensionPos)) {
140
+ if (!/^[A-Z0-9]$/i.test(e.key)) {
141
+ e.preventDefault()
142
+ return
143
+ }
144
+ return
145
+ }
146
+
147
+ // Règle 3 : slash automatique à position 3 (index 18)
148
+ if (extensionPos === 3) {
149
+ e.preventDefault()
150
+ const before = codeBudgetaire.value.slice(0, position)
151
+ const after = codeBudgetaire.value.slice(position)
152
+ codeBudgetaire.value = before + '/' + after
153
+ nextTick(() => {
154
+ input.selectionStart = input.selectionEnd = position + 1
155
+ })
156
+ return
157
+ }
158
+
159
+ // Tout autre cas = bloqué
160
+ e.preventDefault()
161
+ }
162
+
163
+ const formaterCodeBudgetaire = (valeur: string): string => {
164
+ if (!valeur) return ''
165
+
166
+ const upper = valeur.toUpperCase().replace(/[^A-Z0-9/]/g, '')
167
+ const chiffres = upper.replace(/[^0-9]/g, '').slice(0, 12)
168
+
169
+ let base = ''
170
+ if (chiffres.length > 0) base += chiffres.slice(0, 3)
171
+ if (chiffres.length > 3) base += '-' + chiffres.slice(3, 4)
172
+ if (chiffres.length > 4) base += '-' + chiffres.slice(4, 9)
173
+ if (chiffres.length > 9) base += '-' + chiffres.slice(9, 12)
174
+
175
+ if (!activerExtension || base.length < 15) return base
176
+
177
+ const reste = upper.slice(chiffres.length).replace(/[^A-Z0-9/]/gi, '')
178
+
179
+ // Extraire les 7 premiers caractères restants pour l’extension
180
+ let ext = reste.slice(0, 7).split('')
181
+
182
+ // Ne garder que le premier slash s’il est à l’index 0 ou 3
183
+ ext = ext.filter((c, i) => {
184
+ if (c !== '/') return true
185
+ return i === 0 || i === 3
186
+ })
187
+
188
+ // Enlever les slash non autorisés
189
+ ext = ext.map((c, i) => {
190
+ if (c === '/' && i !== 0 && i !== 3) return ''
191
+ if (c !== '/' && !/^[A-Z0-9]$/i.test(c)) return ''
192
+ return c
193
+ })
194
+
195
+ // Forcer le slash à la 4e position
196
+ if (ext.length > 3) {
197
+ ext[3] = '/'
198
+ }
199
+
200
+ // Réduire à 7 caractères
201
+ ext = ext.slice(0, 7)
202
+
203
+ return (base + ext.join('')).slice(0, 22)
204
+ }
205
+
206
+ const gererPaste = (e: ClipboardEvent) => {
207
+ e.preventDefault()
208
+ const clipboardData = e.clipboardData
209
+ if (!clipboardData) return
210
+ let pasted = clipboardData.getData('text') || ''
211
+ codeBudgetaire.value = formaterCodeBudgetaire(pasted)
212
+
213
+ setTimeout(() => {
214
+ const input = e.target as HTMLInputElement
215
+ input.selectionStart = input.selectionEnd = codeBudgetaire.value.length
216
+ }, 0)
217
+ }
218
+
219
+ const sauvegarder = () => {
220
+ codeBudgetaire.value = formaterCodeBudgetaire(codeBudgetaire.value)
221
+ if (!estValide.value) return
222
+ if (codeBudgetaire.value === derniereValeurSauvegardee.value) return
223
+
224
+ const existe = props.codeBudgetairesProp.some(
225
+ item => item.trim().toUpperCase() === codeBudgetaire.value.trim().toUpperCase(),
226
+ )
227
+
228
+ if (!existe) {
229
+ const nouvelleListe = [...props.codeBudgetairesProp, codeBudgetaire.value]
230
+ emit('update:codeBudgetairesProp', nouvelleListe)
231
+ }
232
+
233
+ derniereValeurSauvegardee.value = codeBudgetaire.value
234
+ emit('update:modelValue', codeBudgetaire.value)
235
+ }
236
+
237
+ const gererChangement = (val: string) => {
238
+ codeBudgetaire.value = formaterCodeBudgetaire(val)
239
+
240
+ const valeurFormatee = codeBudgetaire.value
241
+ const estDansListe = props.codeBudgetairesProp.includes(valeurFormatee)
242
+
243
+ if (estDansListe && valeurFormatee !== derniereValeurSauvegardee.value && estValide.value) {
244
+ sauvegarder()
245
+ }
246
+ }
247
+
248
+ onMounted(() => {
249
+ derniereValeurSauvegardee.value = codeBudgetaire.value
250
+ })
251
+
252
+ const placeholder = computed(() => {
253
+ const base = format.replace(/9/g, '0')
254
+ const extension = activerExtension ? '-XXX/XXX' : ''
255
+ return base + extension
256
+ })
257
+ </script>
@@ -1,75 +1,75 @@
1
- <template>
2
- <csqcDialogue
3
- ref="modale"
4
- :titre="props.titre"
5
- :operation-en-cours="operationEnCours"
6
- activator="supprimer"
7
- v-bind="$attrs"
8
- :largeur="props.largeur"
9
- @ok="confirmer"
10
- @annuler="annuler"
11
- >
12
- <v-form
13
- ref="form"
14
- @submit.prevent
15
- >
16
- <v-row>
17
- <v-col
18
- cols="12"
19
- class="pa-0 ma-0"
20
- >
21
- <span v-html="texte"></span>
22
- </v-col>
23
- </v-row>
24
- </v-form>
25
- </csqcDialogue>
26
- </template>
27
-
28
- <script lang="ts" setup>
29
- import { ref } from 'vue'
30
- import csqcDialogue from './csqcDialogue.vue'
31
-
32
- const modale = ref<InstanceType<typeof csqcDialogue> | null>(null)
33
- const utilisateurATermine = ref(false)
34
- const reponse = ref(false)
35
- const texte = ref('')
36
- const modeAlerte = ref(false)
37
-
38
- const props = defineProps({
39
- operationEnCours: { type: Boolean, default: false },
40
- titre: { type: String, default: '' },
41
- largeur: { type: String, default: '525px' },
42
- })
43
-
44
- const ouvrir = async (message: string, modeAlerteParam: boolean = false) => {
45
- texte.value = message
46
- modeAlerte.value = modeAlerteParam
47
- utilisateurATermine.value = false
48
- modale.value?.ouvrir()
49
-
50
- while (!utilisateurATermine.value) {
51
- await new Promise(resolve => setTimeout(resolve, 100)) // Attendre 100ms
52
- }
53
-
54
- return reponse.value
55
- }
56
-
57
- const confirmer = (): void => {
58
- reponse.value = true
59
- utilisateurATermine.value = true
60
-
61
- if (modeAlerte.value) fermer()
62
- }
63
-
64
- const annuler = (): void => {
65
- reponse.value = false
66
- utilisateurATermine.value = true
67
- fermer()
68
- }
69
-
70
- const fermer = (): void => {
71
- modale.value?.fermer()
72
- }
73
-
74
- defineExpose({ ouvrir, fermer })
75
- </script>
1
+ <template>
2
+ <csqcDialogue
3
+ ref="modale"
4
+ :titre="props.titre"
5
+ :operation-en-cours="operationEnCours"
6
+ activator="supprimer"
7
+ v-bind="$attrs"
8
+ :largeur="props.largeur"
9
+ @ok="confirmer"
10
+ @annuler="annuler"
11
+ >
12
+ <v-form
13
+ ref="form"
14
+ @submit.prevent
15
+ >
16
+ <v-row>
17
+ <v-col
18
+ cols="12"
19
+ class="pa-0 ma-0"
20
+ >
21
+ <span v-html="texte"></span>
22
+ </v-col>
23
+ </v-row>
24
+ </v-form>
25
+ </csqcDialogue>
26
+ </template>
27
+
28
+ <script lang="ts" setup>
29
+ import { ref } from 'vue'
30
+ import csqcDialogue from './csqcDialogue.vue'
31
+
32
+ const modale = ref<InstanceType<typeof csqcDialogue> | null>(null)
33
+ const utilisateurATermine = ref(false)
34
+ const reponse = ref(false)
35
+ const texte = ref('')
36
+ const modeAlerte = ref(false)
37
+
38
+ const props = defineProps({
39
+ operationEnCours: { type: Boolean, default: false },
40
+ titre: { type: String, default: '' },
41
+ largeur: { type: String, default: '525px' },
42
+ })
43
+
44
+ const ouvrir = async (message: string, modeAlerteParam: boolean = false) => {
45
+ texte.value = message
46
+ modeAlerte.value = modeAlerteParam
47
+ utilisateurATermine.value = false
48
+ modale.value?.ouvrir()
49
+
50
+ while (!utilisateurATermine.value) {
51
+ await new Promise(resolve => setTimeout(resolve, 100)) // Attendre 100ms
52
+ }
53
+
54
+ return reponse.value
55
+ }
56
+
57
+ const confirmer = (): void => {
58
+ reponse.value = true
59
+ utilisateurATermine.value = true
60
+
61
+ if (modeAlerte.value) fermer()
62
+ }
63
+
64
+ const annuler = (): void => {
65
+ reponse.value = false
66
+ utilisateurATermine.value = true
67
+ fermer()
68
+ }
69
+
70
+ const fermer = (): void => {
71
+ modale.value?.fermer()
72
+ }
73
+
74
+ defineExpose({ ouvrir, fermer })
75
+ </script>