@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.
Files changed (106) hide show
  1. package/dist/components/Accordion/Accordion.d.ts +39 -0
  2. package/dist/components/Accordion/config.d.ts +9 -0
  3. package/dist/components/ChipList/ChipList.d.ts +1 -1
  4. package/dist/components/CopyBtn/CopyBtn.d.ts +2 -0
  5. package/dist/components/Customs/SyInputSelect/SyInputSelect.d.ts +12 -0
  6. package/dist/components/Customs/SySelect/SySelect.d.ts +43 -16
  7. package/dist/components/Customs/SyTextField/SyTextField.d.ts +17 -17
  8. package/dist/components/DatePicker/DatePicker.d.ts +34 -34
  9. package/dist/components/DatePicker/DateTextInput.d.ts +16 -16
  10. package/dist/components/DiacriticPicker/DiacriticPicker.d.ts +27 -0
  11. package/dist/components/DiacriticPicker/config.d.ts +14 -0
  12. package/dist/components/DiacriticPicker/locales.d.ts +6 -0
  13. package/dist/components/DownloadBtn/DownloadBtn.d.ts +1 -1
  14. package/dist/components/FooterBar/FooterBar.d.ts +1 -1
  15. package/dist/components/NirField/NirField.d.ts +34 -32
  16. package/dist/components/NotificationBar/NotificationBar.d.ts +1 -1
  17. package/dist/components/PasswordField/PasswordField.d.ts +1 -1
  18. package/dist/components/PeriodField/PeriodField.d.ts +64 -64
  19. package/dist/components/PhoneField/PhoneField.d.ts +1 -0
  20. package/dist/components/PhoneField/tests/types.d.ts +18 -0
  21. package/dist/components/SyTextArea/SyTextArea.d.ts +900 -0
  22. package/dist/components/SyTextArea/locales.d.ts +3 -0
  23. package/dist/components/SyTextArea/trimStartOnUpdate.d.ts +1 -0
  24. package/dist/components/SyTextArea/useTextActions.d.ts +13 -0
  25. package/dist/components/SyTextArea/wrapText.d.ts +1 -0
  26. package/dist/components/TableToolbar/TableToolbar.d.ts +10 -4
  27. package/dist/components/TableToolbar/config.d.ts +3 -2
  28. package/dist/components/index.d.ts +3 -0
  29. package/dist/composables/date/useHolidayDay.d.ts +36 -0
  30. package/dist/design-system-v3.js +4202 -3529
  31. package/dist/design-system-v3.umd.cjs +4 -1
  32. package/dist/designTokens/tokens/pa/paLightTheme.d.ts +1 -32
  33. package/dist/style.css +1 -1
  34. package/dist/utils/rules/index.d.ts +1 -0
  35. package/dist/utils/rules/isHolidayDay/index.d.ts +11 -0
  36. package/dist/utils/rules/isHolidayDay/locales.d.ts +2 -0
  37. package/package.json +3 -2
  38. package/src/assets/settings.scss +12 -0
  39. package/src/components/Accordion/Accordion.mdx +69 -0
  40. package/src/components/Accordion/Accordion.stories.ts +262 -0
  41. package/src/components/Accordion/Accordion.vue +319 -0
  42. package/src/components/Accordion/config.ts +9 -0
  43. package/src/components/Accordion/tests/__snapshots__/accordion.spec.ts.snap +155 -0
  44. package/src/components/Accordion/tests/accordion.spec.ts +492 -0
  45. package/src/components/CopyBtn/CopyBtn.stories.ts +189 -0
  46. package/src/components/CopyBtn/CopyBtn.vue +29 -1
  47. package/src/components/CopyBtn/tests/CopyBtn.spec.ts +102 -0
  48. package/src/components/Customs/SyInputSelect/SyInputSelect.stories.ts +155 -1
  49. package/src/components/Customs/SyInputSelect/SyInputSelect.vue +97 -14
  50. package/src/components/Customs/SyInputSelect/tests/SyInputSelect.spec.ts +386 -106
  51. package/src/components/Customs/SySelect/SySelect.stories.ts +121 -2
  52. package/src/components/Customs/SySelect/SySelect.vue +33 -8
  53. package/src/components/Customs/SySelect/tests/SySelect.spec.ts +290 -1
  54. package/src/components/Customs/SyTextField/SyTextField.vue +5 -3
  55. package/src/components/DatePicker/DatePicker.vue +16 -3
  56. package/src/components/DatePicker/DateTextInput.vue +16 -5
  57. package/src/components/DatePicker/examples/DatePickerHolidayRule.vue +130 -0
  58. package/src/components/DiacriticPicker/DiacriticPicker.mdx +104 -0
  59. package/src/components/DiacriticPicker/DiacriticPicker.stories.ts +447 -0
  60. package/src/components/DiacriticPicker/DiacriticPicker.vue +262 -0
  61. package/src/components/DiacriticPicker/config.ts +15 -0
  62. package/src/components/DiacriticPicker/locales.ts +6 -0
  63. package/src/components/DiacriticPicker/tests/DiatriticPicker.spec.ts +132 -0
  64. package/src/components/DialogBox/DialogBox.vue +1 -3
  65. package/src/components/NirField/NirField.stories.ts +172 -0
  66. package/src/components/NirField/NirField.vue +15 -7
  67. package/src/components/NotificationBar/Accessibilite.stories.ts +1 -1
  68. package/src/components/NotificationBar/NotificationBar.stories.ts +14 -0
  69. package/src/components/NotificationBar/NotificationBar.vue +26 -3
  70. package/src/components/NotificationBar/{options.ts → config.ts} +0 -1
  71. package/src/components/PaginatedTable/PaginatedTable.vue +0 -11
  72. package/src/components/PasswordField/PasswordField.stories.ts +4 -3
  73. package/src/components/PasswordField/PasswordField.vue +26 -18
  74. package/src/components/PasswordField/tests/PasswordField.spec.ts +1 -10
  75. package/src/components/PhoneField/PhoneField.stories.ts +143 -0
  76. package/src/components/PhoneField/PhoneField.vue +88 -30
  77. package/src/components/PhoneField/tests/PhoneField.additional.spec.ts +266 -0
  78. package/src/components/PhoneField/tests/PhoneField.spec.ts +248 -28
  79. package/src/components/PhoneField/tests/types.d.ts +19 -0
  80. package/src/components/SyTextArea/SyTextArea.mdx +17 -0
  81. package/src/components/SyTextArea/SyTextArea.stories.ts +322 -0
  82. package/src/components/SyTextArea/SyTextArea.vue +113 -0
  83. package/src/components/SyTextArea/locales.ts +3 -0
  84. package/src/components/SyTextArea/tests/SyTextArea.spec.ts +194 -0
  85. package/src/components/SyTextArea/trimStartOnUpdate.ts +12 -0
  86. package/src/components/SyTextArea/useTextActions.ts +52 -0
  87. package/src/components/SyTextArea/wrapText.ts +42 -0
  88. package/src/components/TableToolbar/TableToolbar.mdx +86 -1
  89. package/src/components/TableToolbar/TableToolbar.stories.ts +422 -74
  90. package/src/components/TableToolbar/TableToolbar.vue +25 -8
  91. package/src/components/TableToolbar/config.ts +3 -2
  92. package/src/components/TableToolbar/tests/__snapshots__/TableToolbar.spec.ts.snap +35 -12
  93. package/src/components/index.ts +3 -0
  94. package/src/composables/date/useHolidayDay.ts +98 -0
  95. package/src/composables/rules/useFieldValidation.ts +16 -3
  96. package/src/composables/validation/useValidation.ts +2 -1
  97. package/src/designTokens/tokens/pa/paLightTheme.ts +10 -41
  98. package/src/stories/Accessibilite/Introduction.mdx +5 -2
  99. package/src/stories/DesignTokens/colors.stories.ts +100 -41
  100. package/src/utils/rules/index.ts +1 -0
  101. package/src/utils/rules/isHolidayDay/IsHolidayDay.mdx +52 -0
  102. package/src/utils/rules/isHolidayDay/IsHolidayDay.stories.ts +129 -0
  103. package/src/utils/rules/isHolidayDay/index.ts +36 -0
  104. package/src/utils/rules/isHolidayDay/locales.ts +5 -0
  105. package/src/utils/rules/isHolidayDay/tests/isHolidayDay.spec.ts +35 -0
  106. /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
- // Si la gestion des erreurs est désactivée, on ne considère jamais le champ comme requis
163
- if (props.disableErrorHandling) return false
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
- // Si la gestion des erreurs est désactivée, on ne met jamais hasError à true
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('closes the menu when v-click-outside directive is called', async () => {
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
- // Si le champ est vide et non requis, on ne fait pas de validation
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 } = validation
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
- const date = input ? parseDate(input, props.format) : null
351
- selectedDates.value = date === null ? null : date
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
- } = useValidation({
68
- showSuccessMessages: props.showSuccessMessages,
69
- fieldIdentifier: props.label || props.placeholder,
70
- disableErrorHandling: props.disableErrorHandling,
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>