@cnamts/synapse 0.0.16-alpha → 1.0.0
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/dist/components/Accordion/Accordion.d.ts +39 -0
- package/dist/components/Accordion/config.d.ts +9 -0
- package/dist/components/ChipList/ChipList.d.ts +1 -1
- package/dist/components/CopyBtn/CopyBtn.d.ts +2 -0
- package/dist/components/Customs/SyInputSelect/SyInputSelect.d.ts +12 -0
- package/dist/components/Customs/SySelect/SySelect.d.ts +43 -16
- package/dist/components/Customs/SyTextField/SyTextField.d.ts +17 -17
- package/dist/components/DatePicker/DatePicker.d.ts +34 -34
- package/dist/components/DatePicker/DateTextInput.d.ts +16 -16
- package/dist/components/DiacriticPicker/DiacriticPicker.d.ts +27 -0
- package/dist/components/DiacriticPicker/config.d.ts +14 -0
- package/dist/components/DiacriticPicker/locales.d.ts +6 -0
- package/dist/components/DownloadBtn/DownloadBtn.d.ts +1 -1
- package/dist/components/FooterBar/FooterBar.d.ts +1 -1
- package/dist/components/NirField/NirField.d.ts +34 -32
- package/dist/components/NotificationBar/NotificationBar.d.ts +1 -1
- package/dist/components/PasswordField/PasswordField.d.ts +1 -1
- package/dist/components/PeriodField/PeriodField.d.ts +64 -64
- package/dist/components/PhoneField/PhoneField.d.ts +1 -0
- package/dist/components/PhoneField/tests/types.d.ts +18 -0
- package/dist/components/SyTextArea/SyTextArea.d.ts +900 -0
- package/dist/components/SyTextArea/locales.d.ts +3 -0
- package/dist/components/SyTextArea/trimStartOnUpdate.d.ts +1 -0
- package/dist/components/SyTextArea/useTextActions.d.ts +13 -0
- package/dist/components/SyTextArea/wrapText.d.ts +1 -0
- package/dist/components/TableToolbar/TableToolbar.d.ts +10 -4
- package/dist/components/TableToolbar/config.d.ts +3 -2
- package/dist/components/index.d.ts +3 -0
- package/dist/composables/date/useHolidayDay.d.ts +36 -0
- package/dist/design-system-v3.js +4202 -3529
- package/dist/design-system-v3.umd.cjs +4 -1
- package/dist/designTokens/tokens/pa/paLightTheme.d.ts +1 -32
- package/dist/style.css +1 -1
- package/dist/utils/rules/index.d.ts +1 -0
- package/dist/utils/rules/isHolidayDay/index.d.ts +11 -0
- package/dist/utils/rules/isHolidayDay/locales.d.ts +2 -0
- package/package.json +3 -2
- package/src/assets/settings.scss +12 -0
- package/src/components/Accordion/Accordion.mdx +69 -0
- package/src/components/Accordion/Accordion.stories.ts +262 -0
- package/src/components/Accordion/Accordion.vue +319 -0
- package/src/components/Accordion/config.ts +9 -0
- package/src/components/Accordion/tests/__snapshots__/accordion.spec.ts.snap +155 -0
- package/src/components/Accordion/tests/accordion.spec.ts +492 -0
- package/src/components/CopyBtn/CopyBtn.stories.ts +189 -0
- package/src/components/CopyBtn/CopyBtn.vue +29 -1
- package/src/components/CopyBtn/tests/CopyBtn.spec.ts +102 -0
- package/src/components/Customs/SyInputSelect/SyInputSelect.stories.ts +155 -1
- package/src/components/Customs/SyInputSelect/SyInputSelect.vue +97 -14
- package/src/components/Customs/SyInputSelect/tests/SyInputSelect.spec.ts +386 -106
- package/src/components/Customs/SySelect/SySelect.stories.ts +121 -2
- package/src/components/Customs/SySelect/SySelect.vue +33 -8
- package/src/components/Customs/SySelect/tests/SySelect.spec.ts +290 -1
- package/src/components/Customs/SyTextField/SyTextField.vue +5 -3
- package/src/components/DatePicker/DatePicker.vue +16 -3
- package/src/components/DatePicker/DateTextInput.vue +16 -5
- package/src/components/DatePicker/examples/DatePickerHolidayRule.vue +130 -0
- package/src/components/DiacriticPicker/DiacriticPicker.mdx +104 -0
- package/src/components/DiacriticPicker/DiacriticPicker.stories.ts +447 -0
- package/src/components/DiacriticPicker/DiacriticPicker.vue +262 -0
- package/src/components/DiacriticPicker/config.ts +15 -0
- package/src/components/DiacriticPicker/locales.ts +6 -0
- package/src/components/DiacriticPicker/tests/DiatriticPicker.spec.ts +132 -0
- package/src/components/DialogBox/DialogBox.vue +1 -3
- package/src/components/NirField/NirField.stories.ts +172 -0
- package/src/components/NirField/NirField.vue +15 -7
- package/src/components/NotificationBar/Accessibilite.stories.ts +1 -1
- package/src/components/NotificationBar/NotificationBar.stories.ts +14 -0
- package/src/components/NotificationBar/NotificationBar.vue +26 -3
- package/src/components/NotificationBar/{options.ts → config.ts} +0 -1
- package/src/components/PaginatedTable/PaginatedTable.vue +0 -11
- package/src/components/PasswordField/PasswordField.stories.ts +4 -3
- package/src/components/PasswordField/PasswordField.vue +26 -18
- package/src/components/PasswordField/tests/PasswordField.spec.ts +1 -10
- package/src/components/PhoneField/PhoneField.stories.ts +143 -0
- package/src/components/PhoneField/PhoneField.vue +88 -30
- package/src/components/PhoneField/tests/PhoneField.additional.spec.ts +266 -0
- package/src/components/PhoneField/tests/PhoneField.spec.ts +248 -28
- package/src/components/PhoneField/tests/types.d.ts +19 -0
- package/src/components/SyTextArea/SyTextArea.mdx +17 -0
- package/src/components/SyTextArea/SyTextArea.stories.ts +322 -0
- package/src/components/SyTextArea/SyTextArea.vue +113 -0
- package/src/components/SyTextArea/locales.ts +3 -0
- package/src/components/SyTextArea/tests/SyTextArea.spec.ts +194 -0
- package/src/components/SyTextArea/trimStartOnUpdate.ts +12 -0
- package/src/components/SyTextArea/useTextActions.ts +52 -0
- package/src/components/SyTextArea/wrapText.ts +42 -0
- package/src/components/TableToolbar/TableToolbar.mdx +86 -1
- package/src/components/TableToolbar/TableToolbar.stories.ts +422 -74
- package/src/components/TableToolbar/TableToolbar.vue +25 -8
- package/src/components/TableToolbar/config.ts +3 -2
- package/src/components/TableToolbar/tests/__snapshots__/TableToolbar.spec.ts.snap +35 -12
- package/src/components/index.ts +3 -0
- package/src/composables/date/useHolidayDay.ts +98 -0
- package/src/composables/rules/useFieldValidation.ts +16 -3
- package/src/composables/validation/useValidation.ts +2 -1
- package/src/designTokens/tokens/pa/paLightTheme.ts +10 -41
- package/src/stories/Accessibilite/Introduction.mdx +5 -2
- package/src/stories/DesignTokens/colors.stories.ts +100 -41
- package/src/utils/rules/index.ts +1 -0
- package/src/utils/rules/isHolidayDay/IsHolidayDay.mdx +52 -0
- package/src/utils/rules/isHolidayDay/IsHolidayDay.stories.ts +129 -0
- package/src/utils/rules/isHolidayDay/index.ts +36 -0
- package/src/utils/rules/isHolidayDay/locales.ts +5 -0
- package/src/utils/rules/isHolidayDay/tests/isHolidayDay.spec.ts +35 -0
- /package/dist/components/NotificationBar/{options.d.ts → config.d.ts} +0 -0
|
@@ -61,6 +61,10 @@
|
|
|
61
61
|
type: Boolean,
|
|
62
62
|
default: false,
|
|
63
63
|
},
|
|
64
|
+
density: {
|
|
65
|
+
type: String as PropType<'default' | 'comfortable' | 'compact' | undefined>,
|
|
66
|
+
default: 'default',
|
|
67
|
+
},
|
|
64
68
|
bgColor: {
|
|
65
69
|
type: String,
|
|
66
70
|
default: undefined,
|
|
@@ -73,6 +77,14 @@
|
|
|
73
77
|
type: Boolean,
|
|
74
78
|
default: false,
|
|
75
79
|
},
|
|
80
|
+
hideMessages: {
|
|
81
|
+
type: Boolean,
|
|
82
|
+
default: false,
|
|
83
|
+
},
|
|
84
|
+
width: {
|
|
85
|
+
type: String,
|
|
86
|
+
default: 'undefined',
|
|
87
|
+
},
|
|
76
88
|
})
|
|
77
89
|
|
|
78
90
|
const emit = defineEmits(['update:modelValue'])
|
|
@@ -100,9 +112,8 @@
|
|
|
100
112
|
const rect = input.value.$el.getBoundingClientRect()
|
|
101
113
|
listStyles.value = {
|
|
102
114
|
position: 'fixed',
|
|
103
|
-
top: `${rect.bottom}px`,
|
|
115
|
+
top: props.density === 'compact' ? `${rect.bottom + 22}px` : `${rect.bottom}px`,
|
|
104
116
|
left: `${rect.left}px`,
|
|
105
|
-
width: `${rect.width}px`,
|
|
106
117
|
zIndex: '999',
|
|
107
118
|
}
|
|
108
119
|
}
|
|
@@ -159,21 +170,27 @@
|
|
|
159
170
|
})
|
|
160
171
|
|
|
161
172
|
const isRequired = computed(() => {
|
|
162
|
-
|
|
163
|
-
if (props.
|
|
173
|
+
if (props.disableErrorHandling || props.hideMessages) return false
|
|
174
|
+
if (props.readonly) return
|
|
164
175
|
return (props.required || props.errorMessages.length > 0) && !selectedItem.value
|
|
165
176
|
})
|
|
166
177
|
|
|
167
178
|
const input = ref<InstanceType<typeof VTextField> | null>(null)
|
|
168
179
|
|
|
180
|
+
const calculatedWidth = computed(() => {
|
|
181
|
+
const baseWidth = props.width ? Number(props.width) : 0
|
|
182
|
+
const selectedText = typeof selectedItemText.value === 'string' ? selectedItemText.value : ''
|
|
183
|
+
const clearableAdjustment = props.clearable ? 4 : 0
|
|
184
|
+
return `${baseWidth + selectedText.length * (4 + clearableAdjustment)}px`
|
|
185
|
+
})
|
|
186
|
+
|
|
169
187
|
watch(() => props.modelValue, (newValue) => {
|
|
170
188
|
selectedItem.value = newValue
|
|
171
189
|
})
|
|
172
190
|
|
|
173
191
|
watch([isOpen, hasError], ([newIsOpen, newHasError]) => {
|
|
174
192
|
if (!newIsOpen) {
|
|
175
|
-
|
|
176
|
-
if (props.disableErrorHandling) {
|
|
193
|
+
if (props.disableErrorHandling || props.readonly) {
|
|
177
194
|
hasError.value = false
|
|
178
195
|
}
|
|
179
196
|
else {
|
|
@@ -186,7 +203,6 @@
|
|
|
186
203
|
})
|
|
187
204
|
|
|
188
205
|
watch(() => props.errorMessages, (newValue) => {
|
|
189
|
-
// Si la gestion des erreurs est désactivée, on ne met jamais hasError à true
|
|
190
206
|
if (!props.disableErrorHandling) {
|
|
191
207
|
hasError.value = newValue.length > 0
|
|
192
208
|
}
|
|
@@ -202,6 +218,13 @@
|
|
|
202
218
|
}
|
|
203
219
|
window.addEventListener('scroll', updateListPosition, true)
|
|
204
220
|
window.addEventListener('resize', updateListPosition)
|
|
221
|
+
|
|
222
|
+
if (props.hideMessages) {
|
|
223
|
+
const message = document.querySelector('.v-input__details')
|
|
224
|
+
if (message) {
|
|
225
|
+
message.classList.add('d-sr-only')
|
|
226
|
+
}
|
|
227
|
+
}
|
|
205
228
|
})
|
|
206
229
|
|
|
207
230
|
onUnmounted(() => {
|
|
@@ -230,11 +253,13 @@
|
|
|
230
253
|
:aria-label="labelWithAsterisk"
|
|
231
254
|
:error-messages="props.disableErrorHandling ? [] : errorMessages"
|
|
232
255
|
:variant="outlined ? 'outlined' : 'underlined'"
|
|
233
|
-
:rules="isRequired && !props.disableErrorHandling ? ['Le champ est requis.'] : []"
|
|
256
|
+
:rules="isRequired && !props.disableErrorHandling && !props.hideMessages ? ['Le champ est requis.'] : []"
|
|
234
257
|
:display-asterisk="displayAsterisk"
|
|
235
258
|
:bg-color="props.bgColor"
|
|
259
|
+
:density="props.density"
|
|
236
260
|
readonly
|
|
237
261
|
class="sy-select"
|
|
262
|
+
:width="calculatedWidth"
|
|
238
263
|
:style="hasError ? { minWidth: `${labelWidth + 18}px`} : {minWidth: `${labelWidth}px`}"
|
|
239
264
|
@click="toggleMenu"
|
|
240
265
|
@keydown.enter.prevent="toggleMenu"
|
|
@@ -192,14 +192,303 @@ describe('SySelect.vue', () => {
|
|
|
192
192
|
expect(wrapper.emitted()['update:modelValue'][0]).toEqual(['1'])
|
|
193
193
|
})
|
|
194
194
|
|
|
195
|
-
it('
|
|
195
|
+
it('ferme le menu avec la méthode closeList', async () => {
|
|
196
196
|
const wrapper = mount(SySelect, {
|
|
197
|
+
props: {
|
|
198
|
+
items: [{ text: 'Option 1', value: '1' }],
|
|
199
|
+
},
|
|
200
|
+
global: {
|
|
201
|
+
plugins: [vuetify],
|
|
202
|
+
},
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
await wrapper.find('.sy-select').trigger('click')
|
|
206
|
+
await wrapper.vm.$nextTick()
|
|
207
|
+
|
|
208
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- This is a generic type
|
|
209
|
+
const instance = wrapper.vm as any
|
|
210
|
+
expect(instance.isOpen).toBe(true)
|
|
211
|
+
|
|
212
|
+
instance.closeList()
|
|
213
|
+
await wrapper.vm.$nextTick()
|
|
214
|
+
|
|
215
|
+
expect(instance.isOpen).toBe(false)
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
describe('Affichage de l\'astérisque', () => {
|
|
219
|
+
it('affiche l\'astérisque quand displayAsterisk et required sont true', () => {
|
|
220
|
+
const wrapper = mount(SySelect, {
|
|
221
|
+
props: {
|
|
222
|
+
displayAsterisk: true,
|
|
223
|
+
required: true,
|
|
224
|
+
label: 'Test Label',
|
|
225
|
+
},
|
|
226
|
+
global: {
|
|
227
|
+
plugins: [vuetify],
|
|
228
|
+
},
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
const html = wrapper.html()
|
|
232
|
+
expect(html).toContain('Test Label *')
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
it('n\'affiche pas l\'astérisque quand displayAsterisk est false', () => {
|
|
236
|
+
const wrapper = mount(SySelect, {
|
|
237
|
+
props: {
|
|
238
|
+
displayAsterisk: false,
|
|
239
|
+
required: true,
|
|
240
|
+
label: 'Test Label',
|
|
241
|
+
},
|
|
242
|
+
global: {
|
|
243
|
+
plugins: [vuetify],
|
|
244
|
+
},
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
const html = wrapper.html()
|
|
248
|
+
expect(html).not.toContain('Test Label *')
|
|
249
|
+
expect(html).toContain('Test Label')
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
it('n\'affiche pas l\'astérisque quand required est false', () => {
|
|
253
|
+
const wrapper = mount(SySelect, {
|
|
254
|
+
props: {
|
|
255
|
+
displayAsterisk: true,
|
|
256
|
+
required: false,
|
|
257
|
+
label: 'Test Label',
|
|
258
|
+
},
|
|
259
|
+
global: {
|
|
260
|
+
plugins: [vuetify],
|
|
261
|
+
},
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
const html = wrapper.html()
|
|
265
|
+
expect(html).not.toContain('Test Label *')
|
|
266
|
+
expect(html).toContain('Test Label')
|
|
267
|
+
})
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
describe('Mode readonly', () => {
|
|
271
|
+
it('empêche l\'ouverture du menu en mode readonly', async () => {
|
|
272
|
+
const wrapper = mount(SySelect, {
|
|
273
|
+
props: {
|
|
274
|
+
readonly: true,
|
|
275
|
+
items: [{ text: 'Option 1', value: '1' }],
|
|
276
|
+
},
|
|
277
|
+
global: {
|
|
278
|
+
plugins: [vuetify],
|
|
279
|
+
},
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
await wrapper.find('.sy-select').trigger('click')
|
|
283
|
+
await wrapper.vm.$nextTick()
|
|
284
|
+
|
|
285
|
+
expect(wrapper.find('.v-list').exists()).toBe(false)
|
|
286
|
+
|
|
287
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- This is a generic type
|
|
288
|
+
const instance = wrapper.vm as any
|
|
289
|
+
expect(instance.isOpen).toBe(false)
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
it('affiche correctement le champ en mode readonly', () => {
|
|
293
|
+
const wrapper = mount(SySelect, {
|
|
294
|
+
props: {
|
|
295
|
+
readonly: true,
|
|
296
|
+
modelValue: { text: 'Option 1', value: '1' },
|
|
297
|
+
textKey: 'text',
|
|
298
|
+
returnObject: true,
|
|
299
|
+
},
|
|
300
|
+
global: {
|
|
301
|
+
plugins: [vuetify],
|
|
302
|
+
},
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
expect(wrapper.find('.v-input--readonly').exists()).toBe(true)
|
|
306
|
+
|
|
307
|
+
expect(wrapper.html()).toContain('Option 1')
|
|
308
|
+
})
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
describe('Option clearable', () => {
|
|
312
|
+
it('affiche l\'icône de suppression quand clearable est true et qu\'une valeur est sélectionnée', async () => {
|
|
313
|
+
const wrapper = mount(SySelect, {
|
|
314
|
+
props: {
|
|
315
|
+
clearable: true,
|
|
316
|
+
modelValue: { text: 'Option 1', value: '1' },
|
|
317
|
+
returnObject: true,
|
|
318
|
+
},
|
|
319
|
+
global: {
|
|
320
|
+
plugins: [vuetify],
|
|
321
|
+
},
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
expect(wrapper.find('.sy-select__clear-icon').exists()).toBe(true)
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
it('n\'affiche pas l\'icône de suppression quand clearable est false', () => {
|
|
328
|
+
const wrapper = mount(SySelect, {
|
|
329
|
+
props: {
|
|
330
|
+
clearable: false,
|
|
331
|
+
modelValue: { text: 'Option 1', value: '1' },
|
|
332
|
+
returnObject: true,
|
|
333
|
+
},
|
|
334
|
+
global: {
|
|
335
|
+
plugins: [vuetify],
|
|
336
|
+
},
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
expect(wrapper.find('.v-icon.mdi-close-circle').exists()).toBe(false)
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
it('efface la valeur sélectionnée avec la méthode selectItem', async () => {
|
|
343
|
+
const wrapper = mount(SySelect, {
|
|
344
|
+
props: {
|
|
345
|
+
clearable: true,
|
|
346
|
+
modelValue: { text: 'Option 1', value: '1' },
|
|
347
|
+
returnObject: true,
|
|
348
|
+
},
|
|
349
|
+
global: {
|
|
350
|
+
plugins: [vuetify],
|
|
351
|
+
},
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- This is a generic type
|
|
355
|
+
const instance = wrapper.vm as any
|
|
356
|
+
instance.selectItem(null)
|
|
357
|
+
await wrapper.vm.$nextTick()
|
|
358
|
+
|
|
359
|
+
expect(wrapper.emitted()['update:modelValue'][0]).toEqual([null])
|
|
360
|
+
})
|
|
361
|
+
})
|
|
362
|
+
|
|
363
|
+
describe('Validation', () => {
|
|
364
|
+
it('affiche une erreur pour un champ requis sans valeur', async () => {
|
|
365
|
+
const wrapper = mount(SySelect, {
|
|
366
|
+
props: {
|
|
367
|
+
required: true,
|
|
368
|
+
label: 'Test Label',
|
|
369
|
+
modelValue: undefined,
|
|
370
|
+
},
|
|
371
|
+
global: {
|
|
372
|
+
plugins: [vuetify],
|
|
373
|
+
},
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
await wrapper.find('.sy-select').trigger('click')
|
|
377
|
+
await wrapper.vm.$nextTick()
|
|
378
|
+
await wrapper.find('.sy-select').trigger('click')
|
|
379
|
+
await wrapper.vm.$nextTick()
|
|
380
|
+
|
|
381
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- This is a generic type
|
|
382
|
+
const instance = wrapper.vm as any
|
|
383
|
+
expect(instance.hasError).toBe(true)
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
it('n\'affiche pas d\'erreur pour un champ requis avec une valeur', async () => {
|
|
387
|
+
const wrapper = mount(SySelect, {
|
|
388
|
+
props: {
|
|
389
|
+
required: true,
|
|
390
|
+
label: 'Test Label',
|
|
391
|
+
modelValue: { text: 'Option 1', value: '1' },
|
|
392
|
+
returnObject: true,
|
|
393
|
+
},
|
|
394
|
+
global: {
|
|
395
|
+
plugins: [vuetify],
|
|
396
|
+
},
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
await wrapper.find('.sy-select').trigger('click')
|
|
400
|
+
await wrapper.vm.$nextTick()
|
|
401
|
+
await wrapper.find('.sy-select').trigger('click')
|
|
402
|
+
await wrapper.vm.$nextTick()
|
|
403
|
+
|
|
404
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- This is a generic type
|
|
405
|
+
const instance = wrapper.vm as any
|
|
406
|
+
expect(instance.hasError).toBe(false)
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
it('n\'affiche pas d\'erreur quand disableErrorHandling est true', async () => {
|
|
410
|
+
const wrapper = mount(SySelect, {
|
|
411
|
+
props: {
|
|
412
|
+
required: true,
|
|
413
|
+
label: 'Test Label',
|
|
414
|
+
modelValue: undefined,
|
|
415
|
+
disableErrorHandling: true,
|
|
416
|
+
},
|
|
417
|
+
global: {
|
|
418
|
+
plugins: [vuetify],
|
|
419
|
+
},
|
|
420
|
+
})
|
|
421
|
+
|
|
422
|
+
await wrapper.find('.sy-select').trigger('click')
|
|
423
|
+
await wrapper.vm.$nextTick()
|
|
424
|
+
await wrapper.find('.sy-select').trigger('click')
|
|
425
|
+
await wrapper.vm.$nextTick()
|
|
426
|
+
|
|
427
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- This is a generic type
|
|
428
|
+
const instance = wrapper.vm as any
|
|
429
|
+
expect(instance.hasError).toBe(false)
|
|
430
|
+
})
|
|
431
|
+
})
|
|
432
|
+
|
|
433
|
+
describe('Comportement du menu', () => {
|
|
434
|
+
it('ouvre et ferme le menu au clic', async () => {
|
|
435
|
+
const wrapper = mount(SySelect, {
|
|
436
|
+
props: {
|
|
437
|
+
items: [{ text: 'Option 1', value: '1' }],
|
|
438
|
+
},
|
|
439
|
+
global: {
|
|
440
|
+
plugins: [vuetify],
|
|
441
|
+
},
|
|
442
|
+
})
|
|
443
|
+
|
|
444
|
+
expect(wrapper.find('.v-list').exists()).toBe(false)
|
|
445
|
+
|
|
446
|
+
await wrapper.find('.sy-select').trigger('click')
|
|
447
|
+
await wrapper.vm.$nextTick()
|
|
448
|
+
|
|
449
|
+
expect(wrapper.find('.v-list').exists()).toBe(true)
|
|
450
|
+
|
|
451
|
+
await wrapper.find('.sy-select').trigger('click')
|
|
452
|
+
await wrapper.vm.$nextTick()
|
|
453
|
+
|
|
454
|
+
expect(wrapper.find('.v-list').exists()).toBe(false)
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
it('met à jour isOpen quand on ouvre le menu', async () => {
|
|
458
|
+
const wrapper = mount(SySelect, {
|
|
459
|
+
props: {
|
|
460
|
+
items: [{ text: 'Option 1', value: '1' }],
|
|
461
|
+
},
|
|
462
|
+
global: {
|
|
463
|
+
plugins: [vuetify],
|
|
464
|
+
},
|
|
465
|
+
})
|
|
466
|
+
|
|
467
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- This is a generic type
|
|
468
|
+
const instance = wrapper.vm as any
|
|
469
|
+
expect(instance.isOpen).toBe(false)
|
|
470
|
+
|
|
471
|
+
await wrapper.find('.sy-select').trigger('click')
|
|
472
|
+
await wrapper.vm.$nextTick()
|
|
473
|
+
|
|
474
|
+
expect(instance.isOpen).toBe(true)
|
|
475
|
+
})
|
|
476
|
+
})
|
|
477
|
+
|
|
478
|
+
it('ferme le menu après un clic sur le sélecteur', async () => {
|
|
479
|
+
const wrapper = mount(SySelect, {
|
|
480
|
+
props: {
|
|
481
|
+
items: [{ text: 'Option 1', value: '1' }],
|
|
482
|
+
},
|
|
197
483
|
global: {
|
|
198
484
|
plugins: [vuetify],
|
|
199
485
|
},
|
|
200
486
|
})
|
|
487
|
+
|
|
201
488
|
await wrapper.find('.sy-select').trigger('click')
|
|
489
|
+
await wrapper.vm.$nextTick()
|
|
202
490
|
expect(wrapper.find('.v-list').exists()).toBe(true)
|
|
491
|
+
|
|
203
492
|
await wrapper.find('.sy-select').trigger('mouseleave')
|
|
204
493
|
await wrapper.find('.sy-select').trigger('click')
|
|
205
494
|
await wrapper.vm.$nextTick()
|
|
@@ -212,7 +212,11 @@
|
|
|
212
212
|
)
|
|
213
213
|
|
|
214
214
|
const validateField = (value: string | number | null) => {
|
|
215
|
-
|
|
215
|
+
if (props.readonly) {
|
|
216
|
+
validation.clearValidation()
|
|
217
|
+
return true
|
|
218
|
+
}
|
|
219
|
+
|
|
216
220
|
if (!value && !props.required) {
|
|
217
221
|
validation.clearValidation()
|
|
218
222
|
return true
|
|
@@ -247,7 +251,6 @@
|
|
|
247
251
|
}
|
|
248
252
|
})
|
|
249
253
|
|
|
250
|
-
// Computed pour l'affichage des états
|
|
251
254
|
const hasError = computed(() => validation.hasError.value)
|
|
252
255
|
const hasWarning = computed(() => validation.hasWarning.value)
|
|
253
256
|
const hasSuccess = computed(() => validation.hasSuccess.value)
|
|
@@ -256,7 +259,6 @@
|
|
|
256
259
|
const warnings = computed(() => validation.warnings.value)
|
|
257
260
|
const successes = computed(() => validation.successes.value)
|
|
258
261
|
|
|
259
|
-
// Computed pour les icônes
|
|
260
262
|
const appendInnerIconColor = computed(() => {
|
|
261
263
|
if (props.appendInnerIcon === 'error') return 'error'
|
|
262
264
|
if (props.appendInnerIcon === 'success') return 'success'
|
|
@@ -86,7 +86,15 @@
|
|
|
86
86
|
warningRules: props.customWarningRules,
|
|
87
87
|
disableErrorHandling: props.disableErrorHandling,
|
|
88
88
|
})
|
|
89
|
-
const { errors, warnings, successes, validateField, clearValidation } =
|
|
89
|
+
const { errors, warnings, successes, validateField, clearValidation } = !props.readonly
|
|
90
|
+
? validation
|
|
91
|
+
: {
|
|
92
|
+
errors: ref<string[]>([]),
|
|
93
|
+
warnings: ref<string[]>([]),
|
|
94
|
+
successes: ref<string[]>([]),
|
|
95
|
+
validateField: () => {},
|
|
96
|
+
clearValidation: () => {},
|
|
97
|
+
}
|
|
90
98
|
|
|
91
99
|
const errorMessages = errors
|
|
92
100
|
const warningMessages = warnings
|
|
@@ -114,6 +122,9 @@
|
|
|
114
122
|
|
|
115
123
|
// Vérifier si le champ est requis et vide
|
|
116
124
|
if ((forceValidation || !isUpdatingFromInternal.value) && props.required && (!selectedDates.value || (Array.isArray(selectedDates.value) && selectedDates.value.length === 0))) {
|
|
125
|
+
if (props.readonly) {
|
|
126
|
+
return
|
|
127
|
+
}
|
|
117
128
|
if (shouldDisplayErrors) {
|
|
118
129
|
errors.value.push('La date est requise.')
|
|
119
130
|
}
|
|
@@ -347,8 +358,10 @@
|
|
|
347
358
|
return
|
|
348
359
|
}
|
|
349
360
|
|
|
350
|
-
|
|
351
|
-
|
|
361
|
+
if (!props.displayRange) {
|
|
362
|
+
const date = input ? parseDate(input, props.format) : null
|
|
363
|
+
selectedDates.value = date === null ? null : date
|
|
364
|
+
}
|
|
352
365
|
}
|
|
353
366
|
|
|
354
367
|
// Gestionnaire de clic en dehors
|
|
@@ -64,11 +64,20 @@
|
|
|
64
64
|
hasError,
|
|
65
65
|
clearValidation,
|
|
66
66
|
validateField,
|
|
67
|
-
} =
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
} = !props.readonly
|
|
68
|
+
? useValidation({
|
|
69
|
+
showSuccessMessages: props.showSuccessMessages,
|
|
70
|
+
fieldIdentifier: props.label || props.placeholder,
|
|
71
|
+
disableErrorHandling: props.disableErrorHandling,
|
|
72
|
+
})
|
|
73
|
+
: {
|
|
74
|
+
errors: ref<string[]>([]),
|
|
75
|
+
warnings: ref<string[]>([]),
|
|
76
|
+
successes: ref<string[]>([]),
|
|
77
|
+
hasError: ref(false),
|
|
78
|
+
clearValidation: () => {},
|
|
79
|
+
validateField: () => {},
|
|
80
|
+
}
|
|
72
81
|
|
|
73
82
|
const errorMessages = errors
|
|
74
83
|
const warningMessages = warnings
|
|
@@ -114,6 +123,7 @@
|
|
|
114
123
|
}
|
|
115
124
|
|
|
116
125
|
const validateDateFormat = (dateStr: string): { isValid: boolean, message: string } => {
|
|
126
|
+
if (props.readonly) return { isValid: true, message: '' }
|
|
117
127
|
if (!dateStr) {
|
|
118
128
|
return {
|
|
119
129
|
isValid: !props.required || !hasInteracted.value || props.disableErrorHandling,
|
|
@@ -145,6 +155,7 @@
|
|
|
145
155
|
clearValidation()
|
|
146
156
|
|
|
147
157
|
if (!value && props.required && hasInteracted.value) {
|
|
158
|
+
if (props.readonly) return true
|
|
148
159
|
if (!props.disableErrorHandling) {
|
|
149
160
|
errors.value.push('La date est requise')
|
|
150
161
|
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { ref, computed } from 'vue'
|
|
3
|
+
import { useFieldValidation } from '@/composables/rules/useFieldValidation'
|
|
4
|
+
import { useHolidayDay } from '@/composables/date/useHolidayDay'
|
|
5
|
+
|
|
6
|
+
const selectedDate = ref('')
|
|
7
|
+
const { generateRules } = useFieldValidation()
|
|
8
|
+
const { isHolidayDay, getJoursFeries } = useHolidayDay()
|
|
9
|
+
|
|
10
|
+
// Année courante pour afficher les jours fériés
|
|
11
|
+
const currentYear = new Date().getFullYear()
|
|
12
|
+
|
|
13
|
+
// Création de la règle qui vérifie qu'une date n'est pas un jour férié
|
|
14
|
+
const holidayRules = generateRules([
|
|
15
|
+
{
|
|
16
|
+
type: 'isHolidayDay',
|
|
17
|
+
options: {
|
|
18
|
+
fieldName: 'La date',
|
|
19
|
+
message: 'Vous ne pouvez pas sélectionner un jour férié.',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
])
|
|
23
|
+
|
|
24
|
+
// Vérification si la date sélectionnée est un jour férié (pour l'affichage)
|
|
25
|
+
const isDateHoliday = computed(() => {
|
|
26
|
+
if (!selectedDate.value) return false
|
|
27
|
+
return isHolidayDay(selectedDate.value)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
// Liste des jours fériés de l'année courante
|
|
31
|
+
const currentYearHolidays = computed(() => {
|
|
32
|
+
const holidays = getJoursFeries(currentYear)
|
|
33
|
+
return Array.from(holidays).sort()
|
|
34
|
+
})
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<template>
|
|
38
|
+
<div class="date-picker-holiday-example">
|
|
39
|
+
<h2>DatePicker avec règle de validation pour jours fériés</h2>
|
|
40
|
+
<p>
|
|
41
|
+
Cet exemple montre comment utiliser la règle <code>isHolidayDay</code> pour empêcher
|
|
42
|
+
la sélection de jours fériés dans un DatePicker.
|
|
43
|
+
</p>
|
|
44
|
+
|
|
45
|
+
<div class="date-picker-container">
|
|
46
|
+
<DatePicker
|
|
47
|
+
v-model="selectedDate"
|
|
48
|
+
label="Date (pas de jour férié)"
|
|
49
|
+
:rules="holidayRules"
|
|
50
|
+
error-messages
|
|
51
|
+
placeholder="Sélectionnez une date non fériée"
|
|
52
|
+
/>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<div class="info-container mt-4">
|
|
56
|
+
<h3>Informations</h3>
|
|
57
|
+
<p>Date sélectionnée : <strong>{{ selectedDate || 'Aucune' }}</strong></p>
|
|
58
|
+
|
|
59
|
+
<div
|
|
60
|
+
v-if="selectedDate"
|
|
61
|
+
class="mt-2"
|
|
62
|
+
>
|
|
63
|
+
<p
|
|
64
|
+
v-if="isDateHoliday"
|
|
65
|
+
class="error-text"
|
|
66
|
+
>
|
|
67
|
+
⚠️ Le {{ selectedDate }} est un jour férié.
|
|
68
|
+
</p>
|
|
69
|
+
<p
|
|
70
|
+
v-else
|
|
71
|
+
class="success-text"
|
|
72
|
+
>
|
|
73
|
+
✅ Le {{ selectedDate }} n'est pas un jour férié.
|
|
74
|
+
</p>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<div
|
|
78
|
+
v-if="currentYearHolidays.length"
|
|
79
|
+
class="holiday-list mt-4"
|
|
80
|
+
>
|
|
81
|
+
<h4>Jours fériés {{ currentYear }} :</h4>
|
|
82
|
+
<ul>
|
|
83
|
+
<li
|
|
84
|
+
v-for="(date, index) in currentYearHolidays"
|
|
85
|
+
:key="index"
|
|
86
|
+
>
|
|
87
|
+
{{ date }}
|
|
88
|
+
</li>
|
|
89
|
+
</ul>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
</template>
|
|
94
|
+
|
|
95
|
+
<style scoped>
|
|
96
|
+
.date-picker-holiday-example {
|
|
97
|
+
max-width: 800px;
|
|
98
|
+
margin: 0 auto;
|
|
99
|
+
padding: 20px;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.date-picker-container {
|
|
103
|
+
max-width: 400px;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.info-container {
|
|
107
|
+
background-color: #f5f5f5;
|
|
108
|
+
padding: 15px;
|
|
109
|
+
border-radius: 4px;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.error-text {
|
|
113
|
+
color: #d32f2f;
|
|
114
|
+
font-weight: bold;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.success-text {
|
|
118
|
+
color: #2e7d32;
|
|
119
|
+
font-weight: bold;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.holiday-list {
|
|
123
|
+
max-height: 200px;
|
|
124
|
+
overflow-y: auto;
|
|
125
|
+
background-color: #fff;
|
|
126
|
+
padding: 10px;
|
|
127
|
+
border-radius: 4px;
|
|
128
|
+
border: 1px solid #e0e0e0;
|
|
129
|
+
}
|
|
130
|
+
</style>
|