@cnamts/synapse 1.0.20 → 1.0.21

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 (135) hide show
  1. package/dist/{DateFilter-XURUmpMl.js → DateFilter-uN8OURoP.js} +1 -1
  2. package/dist/{NumberFilter-BZc0O8wV.js → NumberFilter-sm1dQNQi.js} +1 -1
  3. package/dist/{PeriodFilter-ZNdXcl3p.js → PeriodFilter-Cklsxnh9.js} +1 -1
  4. package/dist/{SelectFilter-DshYU5OK.js → SelectFilter-CWefj27Z.js} +1 -1
  5. package/dist/{TextFilter-D_c5dRPl.js → TextFilter-Ddyj885L.js} +1 -1
  6. package/dist/components/Customs/SyCheckBoxGroup/SyCheckBoxGroup.d.ts +160 -0
  7. package/dist/components/Customs/SyCheckBoxGroup/locales.d.ts +3 -0
  8. package/dist/components/Customs/SyCheckBoxGroup/types.d.ts +10 -0
  9. package/dist/components/Customs/SyCheckbox/SyCheckbox.d.ts +1545 -2
  10. package/dist/components/Customs/SyRadioGroup/SyRadioGroup.d.ts +1495 -2
  11. package/dist/components/DeclarationAccessibilityPage/DeclarationAccessibilityPage.d.ts +60 -0
  12. package/dist/components/ErrorPage/ErrorPage.d.ts +1 -12
  13. package/dist/components/ErrorPage/locales.d.ts +18 -3
  14. package/dist/components/FileUpload/FileUpload.d.ts +2 -0
  15. package/dist/components/MaintenancePage/locales.d.ts +18 -2
  16. package/dist/components/NotFoundPage/locales.d.ts +20 -4
  17. package/dist/components/StatusPage/StatusPage.d.ts +39 -0
  18. package/dist/components/UploadWorkflow/UploadWorkflow.d.ts +13 -3
  19. package/dist/components/index.d.ts +3 -0
  20. package/dist/design-system-v3.js +126 -123
  21. package/dist/design-system-v3.umd.cjs +163 -163
  22. package/dist/{main-CuI6xaPq.js → main-CWniLr0s.js} +15191 -14668
  23. package/dist/style.css +1 -1
  24. package/dist/utils/theme/index.d.ts +6 -0
  25. package/package.json +7 -4
  26. package/src/components/ContextualMenu/ContextualMenu.stories.ts +0 -3
  27. package/src/components/ContextualMenu/accessibilite/Accessibility.mdx +67 -11
  28. package/src/components/CookieBanner/CookieBanner.stories.ts +11 -20
  29. package/src/components/CookieBanner/CookieBanner.vue +20 -5
  30. package/src/components/CookieBanner/accessibilite/Accessibility.mdx +67 -11
  31. package/src/components/CookieBanner/tests/CookieBanner.spec.ts +48 -4
  32. package/src/components/Customs/SyCheckBoxGroup/SyCheckBoxGroup.mdx +32 -0
  33. package/src/components/Customs/SyCheckBoxGroup/SyCheckBoxGroup.stories.ts +856 -0
  34. package/src/components/Customs/SyCheckBoxGroup/SyCheckBoxGroup.vue +334 -0
  35. package/src/components/Customs/SyCheckBoxGroup/accessibilite/Accessibility.mdx +243 -0
  36. package/src/components/Customs/SyCheckBoxGroup/locales.ts +3 -0
  37. package/src/components/Customs/SyCheckBoxGroup/tests/SyCheckBoxGroup.a11y.spec.ts +30 -0
  38. package/src/components/Customs/SyCheckBoxGroup/tests/SyCheckBoxGroup.spec.ts +152 -0
  39. package/src/components/Customs/SyCheckBoxGroup/types.ts +10 -0
  40. package/src/components/Customs/SyCheckbox/SyCheckbox.vue +16 -27
  41. package/src/components/Customs/SyCheckbox/accessibilite/Accessibility.mdx +1 -1
  42. package/src/components/Customs/SyForm/SyForm.a11y.spec.ts +1 -1
  43. package/src/components/Customs/SyRadioGroup/SyRadioGroup.vue +16 -43
  44. package/src/components/DatePicker/CalendarMode/DatePicker.vue +35 -11
  45. package/src/components/DatePicker/ComplexDatePicker/ComplexDatePicker.stories.ts +43 -2
  46. package/src/components/DatePicker/DateTextInput/DateTextInput.vue +48 -21
  47. package/src/components/DatePicker/DateTextInput/NoCalendar.stories.ts +98 -0
  48. package/src/components/DeclarationAccessibilityPage/DeclarationAccessibilityPage.mdx +83 -0
  49. package/src/components/DeclarationAccessibilityPage/DeclarationAccessibilityPage.stories.ts +502 -0
  50. package/src/components/DeclarationAccessibilityPage/DeclarationAccessibilityPage.vue +428 -0
  51. package/src/components/DeclarationAccessibilityPage/accessibilite/Accessibility.mdx +75 -0
  52. package/src/components/DeclarationAccessibilityPage/tests/DeclarationAccessibilityPage.a11y.spec.ts +53 -0
  53. package/src/components/DeclarationAccessibilityPage/tests/DeclarationAccessibilityPage.spec.ts +59 -0
  54. package/src/components/DiacriticPicker/DiacriticPicker.vue +20 -1
  55. package/src/components/ErrorPage/ErrorPage.mdx +6 -16
  56. package/src/components/ErrorPage/ErrorPage.stories.ts +16 -87
  57. package/src/components/ErrorPage/ErrorPage.vue +38 -125
  58. package/src/components/ErrorPage/accessibilite/Accessibility.mdx +68 -6
  59. package/src/components/ErrorPage/assets/error-ap.svg +1774 -0
  60. package/src/components/ErrorPage/locales.ts +21 -3
  61. package/src/components/ErrorPage/tests/ErrorPage.a11y.spec.ts +5 -13
  62. package/src/components/ErrorPage/tests/ErrorPage.spec.ts +2 -41
  63. package/src/components/ErrorPage/tests/__snapshots__/ErrorPage.spec.ts.snap +8 -266
  64. package/src/components/FileUpload/FileUpload.vue +5 -0
  65. package/src/components/FooterBar/FooterBar.stories.ts +18 -14
  66. package/src/components/FooterBar/defaultSocialMediaLinks.ts +6 -4
  67. package/src/components/MaintenancePage/MaintenancePage.mdx +1 -1
  68. package/src/components/MaintenancePage/MaintenancePage.vue +15 -7
  69. package/src/components/MaintenancePage/accessibilite/Accessibility.mdx +61 -6
  70. package/src/components/MaintenancePage/assets/maintenance-ap.svg +1718 -0
  71. package/src/components/MaintenancePage/locales.ts +24 -3
  72. package/src/components/MaintenancePage/tests/MaintenancePage.a11y.spec.ts +75 -3
  73. package/src/components/MaintenancePage/tests/MaintenancePage.spec.ts +42 -2
  74. package/src/components/MaintenancePage/tests/__snapshots__/MaintenancePage.spec.ts.snap +3 -2
  75. package/src/components/NotFoundPage/NotFoundPage.mdx +1 -1
  76. package/src/components/NotFoundPage/NotFoundPage.stories.ts +3 -3
  77. package/src/components/NotFoundPage/NotFoundPage.vue +16 -11
  78. package/src/components/NotFoundPage/accessibilite/Accessibility.mdx +78 -6
  79. package/src/components/NotFoundPage/assets/not-found-ap.svg +2061 -0
  80. package/src/components/NotFoundPage/locales.ts +24 -4
  81. package/src/components/NotFoundPage/tests/NotFoundPage.a11y.spec.ts +168 -4
  82. package/src/components/NotFoundPage/tests/NotFoundPage.spec.ts +100 -12
  83. package/src/components/NotFoundPage/tests/__snapshots__/NotFoundPage.spec.ts.snap +2 -2
  84. package/src/components/NotificationBar/NotificationBar.mdx +2 -2
  85. package/src/components/NotificationBar/accessibilite/Accessibility.mdx +68 -8
  86. package/src/components/PageContainer/tests/PageContainer.a11y.spec.ts +14 -7
  87. package/src/components/PhoneField/PhoneField.stories.ts +46 -38
  88. package/src/components/SocialMediaLinks/DefaultSocialMediaLinks.ts +6 -4
  89. package/src/components/SocialMediaLinks/SocialMediaLinks.mdx +7 -5
  90. package/src/components/SocialMediaLinks/SocialMediaLinks.stories.ts +17 -13
  91. package/src/components/SocialMediaLinks/SocialMediaLinks.vue +9 -1
  92. package/src/components/SocialMediaLinks/accessibilite/Accessibility.mdx +63 -11
  93. package/src/components/SocialMediaLinks/tests/DefaultSocialMediaLinks.spec.ts +5 -5
  94. package/src/components/SocialMediaLinks/tests/SocialMediaLinks.a11y.spec.ts +59 -0
  95. package/src/components/SocialMediaLinks/tests/SocialMediaLinks.spec.ts +9 -7
  96. package/src/components/StatusPage/StatusPage.mdx +22 -0
  97. package/src/components/StatusPage/StatusPage.stories.ts +193 -0
  98. package/src/components/StatusPage/StatusPage.vue +145 -0
  99. package/src/components/StatusPage/accessibilite/Accessibility.mdx +81 -0
  100. package/src/components/StatusPage/tests/StatusPage.a11y.spec.ts +29 -0
  101. package/src/components/StatusPage/tests/StatusPage.spec.ts +50 -0
  102. package/src/components/StatusPage/tests/__snapshots__/StatusPage.spec.ts.snap +270 -0
  103. package/src/components/TableToolbar/TableToolbar.stories.ts +6 -6
  104. package/src/components/TableToolbar/TableToolbar.vue +1 -1
  105. package/src/components/TableToolbar/tests/__snapshots__/TableToolbar.spec.ts.snap +0 -5
  106. package/src/components/UploadWorkflow/UploadWorkflow.mdx +11 -1
  107. package/src/components/UploadWorkflow/UploadWorkflow.stories.ts +107 -3
  108. package/src/components/UploadWorkflow/UploadWorkflow.vue +35 -24
  109. package/src/components/UploadWorkflow/tests/UploadWorkflow.spec.ts +48 -0
  110. package/src/components/UploadWorkflow/tests/__snapshots__/UploadWorkflow.spec.ts.snap +9 -5
  111. package/src/components/UploadWorkflow/useFileList.ts +7 -0
  112. package/src/components/index.ts +3 -0
  113. package/src/composables/rules/tests/useFieldValidation.spec.ts +39 -3
  114. package/src/composables/rules/useFieldValidation.ts +24 -9
  115. package/src/stories/Accessibilite/KitDePreAudit/Preaudit.mdx +7 -0
  116. package/src/utils/theme/index.ts +19 -0
  117. package/src/utils/theme/tests/useThemeLocales.spec.ts +245 -0
  118. package/dist/components/MaintenancePage/index.d.ts +0 -2
  119. package/src/components/Customs/SyPagination/tests/SyPagination.a11y.spec.ts +0 -27
  120. package/src/components/Customs/SyTabs/tests/SyTabs.a11y.spec.ts +0 -51
  121. package/src/components/DataListItem/tests/DataListItem.a11y.spec.ts +0 -31
  122. package/src/components/DatePicker/CalendarMode/tests/DatePicker.a11y.spec.ts +0 -27
  123. package/src/components/DatePicker/ComplexDatePicker/tests/ComplexDatePicker.a11y.spec.ts +0 -26
  124. package/src/components/DatePicker/DateTextInput/tests/DateTextInput.a11y.spec.ts +0 -27
  125. package/src/components/DownloadBtn/tests/DownloadBtn.a11y.spec.ts +0 -26
  126. package/src/components/ExternalLinks/tests/ExternalLinks.a11y.spec.ts +0 -39
  127. package/src/components/HeaderNavigationBar/tests/HeaderNavigationBar.a11y.spec.ts +0 -45
  128. package/src/components/HeaderToolbar/tests/HeaderToolbar.a11y.spec.ts +0 -25
  129. package/src/components/LunarCalendar/tests/LunarCalendar.a11y.spec.ts +0 -31
  130. package/src/components/MaintenancePage/index.ts +0 -3
  131. package/src/components/PageContainer/Accessibilite/AccessibilityGuide.mdx +0 -0
  132. package/src/components/PaginatedTable/tests/PaginatedTable.a11y.spec.ts +0 -43
  133. package/src/components/PhoneField/tests/PhoneField.a11y.spec.ts +0 -34
  134. /package/src/components/NotFoundPage/assets/{not-found.svg → not-found-cnam.svg} +0 -0
  135. /package/src/components/PageContainer/{Accessibilite → accessibilite}/Accessibility.mdx +0 -0
@@ -4,7 +4,7 @@
4
4
  } from '@/composables/useCustomizableOptions'
5
5
  import { useWidthable, type Widthable } from '@/composables/widthable'
6
6
  import { isRequired } from '@/utils/rules/isRequired'
7
- import { computed, reactive, ref, toRef, watch } from 'vue'
7
+ import { computed, ref, toRef } from 'vue'
8
8
  import DialogBox from '../DialogBox/DialogBox.vue'
9
9
  import FileList from '../FileList/FileList.vue'
10
10
  import FilePreview from '../FilePreview/FilePreview.vue'
@@ -22,37 +22,35 @@
22
22
  uploadList: UploadItem[]
23
23
  sectionTitle?: string
24
24
  showFilePreview?: boolean
25
+ infoText?: string
26
+ headingLevel?: number
25
27
  locales?: typeof defaultLocales
26
28
  }
27
29
  >(),
28
30
  {
29
31
  sectionTitle: undefined,
30
32
  showFilePreview: false,
33
+ infoText: '',
34
+ headingLevel: 4,
31
35
  locales: () => defaultLocales,
32
36
  },
33
37
  )
34
38
 
35
39
  const emits = defineEmits<{
36
40
  (e: 'error', value: string[]): void
41
+ (e: 'preview', value: FileItem): void
37
42
  (e: 'update:modelValue', value: SelectedFile[]): void
38
43
  }>()
39
44
 
40
45
  const selectedFiles = defineModel<SelectedFile[]>({
41
46
  type: Array,
42
- default: reactive([]),
47
+ default: () => [],
43
48
  })
44
- watch(
45
- selectedFiles,
46
- (value) => {
47
- emits('update:modelValue', value)
48
- },
49
- { deep: true },
50
- )
51
49
 
52
50
  const { widthStyles } = useWidthable(props)
53
51
  const options = useCustomizableOptions(config, props)
54
52
 
55
- const fileUpload = ref<typeof FileUpload>()
53
+ const fileUpload = ref<InstanceType<typeof FileUpload>>()
56
54
  const { selectItems, selectedItem } = useFileUploadJourney(
57
55
  toRef(props, 'uploadList'),
58
56
  )
@@ -65,19 +63,21 @@
65
63
  = useFileList(selectedFiles, toRef(props, 'uploadList'))
66
64
 
67
65
  // handle FileList
68
- let inlineSelectedItemId: string | undefined = undefined
66
+ const inlineSelectedItemId = ref<string | undefined>(undefined)
69
67
  function uploadInline(item: FileItem) {
70
- inlineSelectedItemId = item.id
68
+ inlineSelectedItemId.value = item.id
71
69
 
72
- fileUpload.value!.fileInput!.click()
70
+ fileUpload.value?.openFileDialog()
73
71
  }
74
72
  function previewFile(file: FileItem & { file?: File }) {
73
+ emits('preview', file)
75
74
  showPreviewDialog.value = true
76
75
  fileToPreview.value = file.file
77
76
  }
78
77
 
79
78
  // handle FileUpload
80
79
  const uploadedFiles = ref<File[]>([])
80
+ const uploadedFile = computed(() => uploadedFiles.value[0])
81
81
 
82
82
  const showFileUpload = computed(
83
83
  () => (
@@ -88,10 +88,10 @@
88
88
 
89
89
  function fileSelected(files: File[]) {
90
90
  const fileId
91
- = inlineSelectedItemId ?? (props.uploadList.length === 1
91
+ = inlineSelectedItemId.value ?? (props.uploadList.length === 1
92
92
  ? props.uploadList[0]!.id
93
93
  : undefined)
94
- inlineSelectedItemId = undefined
94
+ inlineSelectedItemId.value = undefined
95
95
 
96
96
  if (props.showFilePreview || fileId === undefined) {
97
97
  showSelectDialog.value = true
@@ -105,22 +105,22 @@
105
105
 
106
106
  function uploadError(errors: string[]) {
107
107
  emits('error', errors)
108
- if (!inlineSelectedItemId) {
108
+ if (!inlineSelectedItemId.value) {
109
109
  return
110
110
  }
111
- setItemOnError(inlineSelectedItemId)
112
- inlineSelectedItemId = undefined
111
+ setItemOnError(inlineSelectedItemId.value)
112
+ inlineSelectedItemId.value = undefined
113
113
  }
114
114
 
115
115
  // handle DialogBox
116
116
  const showSelectDialog = ref(false)
117
117
 
118
118
  function dialogConfirm() {
119
- if (!selectedItem.value) {
119
+ if (!selectedItem.value || !uploadedFile.value) {
120
120
  return
121
121
  }
122
122
 
123
- addOrReplaceFile(uploadedFiles.value[0]!, selectedItem.value)
123
+ addOrReplaceFile(uploadedFile.value, selectedItem.value)
124
124
 
125
125
  showSelectDialog.value = false
126
126
  selectedItem.value = undefined
@@ -137,9 +137,13 @@
137
137
  class="sy-upload-workflow white"
138
138
  >
139
139
  <slot name="title">
140
- <h4 class="text-h6 mb-2">
140
+ <div
141
+ :aria-level="props.headingLevel"
142
+ role="heading"
143
+ class="text-h6 mb-2"
144
+ >
141
145
  {{ title }}
142
- </h4>
146
+ </div>
143
147
  </slot>
144
148
 
145
149
  <FileList
@@ -159,7 +163,14 @@
159
163
  v-model="uploadedFiles"
160
164
  @error="uploadError"
161
165
  @update:model-value="fileSelected"
162
- />
166
+ >
167
+ <template
168
+ v-if="props.infoText"
169
+ #info-text
170
+ >
171
+ {{ props.infoText }}
172
+ </template>
173
+ </FileUpload>
163
174
  </Transition>
164
175
 
165
176
  <DialogBox
@@ -195,7 +206,7 @@
195
206
  <FilePreview
196
207
  v-if="showFilePreview"
197
208
  :options="options.filePreview"
198
- :file="uploadedFiles[0]"
209
+ :file="uploadedFile"
199
210
  />
200
211
  </DialogBox>
201
212
 
@@ -1,5 +1,6 @@
1
1
  import { describe, it, expect, vi } from 'vitest'
2
2
  import { mount } from '@vue/test-utils'
3
+ import { nextTick } from 'vue'
3
4
 
4
5
  import UploadWorkflow from '../UploadWorkflow.vue'
5
6
  import { locales as fileListLocales } from '@/components/FileList/UploadItem/locales'
@@ -34,6 +35,24 @@ describe('UploadWorkflow', () => {
34
35
  expect(wrapper.find('.sy-file-upload').isVisible()).toBeTruthy()
35
36
  })
36
37
 
38
+ it('renders default title with role heading and configured aria-level', () => {
39
+ const wrapper = mount(UploadWorkflow, {
40
+ props: {
41
+ uploadList: [
42
+ {
43
+ id: 'ID',
44
+ title: 'Carte d\'identité',
45
+ },
46
+ ],
47
+ headingLevel: 2,
48
+ },
49
+ })
50
+
51
+ const title = wrapper.find('[role="heading"]')
52
+ expect(title.exists()).toBe(true)
53
+ expect(title.attributes('aria-level')).toBe('2')
54
+ })
55
+
37
56
  it('shows the file in the list when set with the list button', async () => {
38
57
  const wrapper = mount(UploadWorkflow, {
39
58
  props: {
@@ -237,6 +256,35 @@ describe('UploadWorkflow', () => {
237
256
 
238
257
  await wrapper.find('.file-item__action-preview').trigger('click')
239
258
 
259
+ expect(wrapper.emitted('preview')).toBeTruthy()
260
+ expect(wrapper.emitted('preview')?.[0]?.[0]).toMatchObject({
261
+ id: 'CERFA1',
262
+ title: 'CERFA 1',
263
+ showPreviewBtn: true,
264
+ })
265
+
240
266
  expect(wrapper.find('.sy-file-preview img').exists()).toBeTruthy()
241
267
  })
268
+
269
+ it('render custom infoText in FileUpload info-text slot', async () => {
270
+ const wrapper = mount(UploadWorkflow, {
271
+ props: {
272
+ modelValue: [],
273
+ uploadList: [
274
+ {
275
+ id: 'ID',
276
+ title: 'Carte d\'identité',
277
+ },
278
+ ],
279
+ infoText: 'Texte personnalisé',
280
+ },
281
+ })
282
+
283
+ await nextTick()
284
+
285
+ const fileUpload = wrapper.find('.sy-file-upload')
286
+ expect(fileUpload.exists()).toBe(true)
287
+
288
+ expect(fileUpload.text()).toContain('Texte personnalisé')
289
+ })
242
290
  })
@@ -8,12 +8,16 @@ exports[`UploadWorkflow > render the upload list 1`] = `
8
8
  "
9
9
  style="width: 100%;"
10
10
  >
11
- <h4 class="
12
- mb-2
13
- text-h6
14
- ">
11
+ <div
12
+ aria-level="4"
13
+ class="
14
+ mb-2
15
+ text-h6
16
+ "
17
+ role="heading"
18
+ >
15
19
  Documents à nous transmettre
16
- </h4>
20
+ </div>
17
21
  <ul
18
22
  class="upload-list"
19
23
  style="width: 100%;"
@@ -8,6 +8,10 @@ export default function useFileList(
8
8
  ) {
9
9
  const errorSelectedFiles = ref<string[]>([])
10
10
 
11
+ function notifySelectedFilesChanged() {
12
+ selectedFiles.value = selectedFiles.value.slice()
13
+ }
14
+
11
15
  function removeFromErrorList(fileId: string) {
12
16
  const errorIndex = errorSelectedFiles.value.findIndex(item => item === fileId)
13
17
  if (errorIndex !== -1) {
@@ -22,12 +26,14 @@ export default function useFileList(
22
26
  function resetFile(fileItem: FileListItem | SelectedFile) {
23
27
  const itemIndex = selectedFiles.value.findIndex(item => item.id === fileItem.id)
24
28
  selectedFiles.value.splice(itemIndex, 1)
29
+ notifySelectedFilesChanged()
25
30
  }
26
31
 
27
32
  function replaceFile(file: File, item: SelectedFile, state: FileState = 'success') {
28
33
  item.file = file
29
34
  item.fileName = file.name
30
35
  item.state = state
36
+ notifySelectedFilesChanged()
31
37
  }
32
38
 
33
39
  function addOrReplaceFile(file: File, fileId: string, state: FileState = 'success') {
@@ -52,6 +58,7 @@ export default function useFileList(
52
58
  fileName: file.name,
53
59
  file,
54
60
  })
61
+ notifySelectedFilesChanged()
55
62
  removeFromErrorList(fileId)
56
63
  }
57
64
  }
@@ -53,6 +53,7 @@ export { default as SySelect } from './Customs/Selects/SySelect/SySelect.vue'
53
53
  export { default as SyAutocomplete } from './Customs/Selects/SyAutocomplete/SyAutocomplete.vue'
54
54
  // export { default as SyBtnSelect } from './Customs/Selects/SyBtnSelect/SyBtnSelect.vue'
55
55
  export { default as SyCheckbox } from './Customs/SyCheckbox/SyCheckbox.vue'
56
+ export { default as SyCheckBoxGroup } from './Customs/SyCheckBoxGroup/SyCheckBoxGroup.vue'
56
57
  export { default as SyForm } from './Customs/SyForm/SyForm.vue'
57
58
  export { default as SyRadioGroup } from './Customs/SyRadioGroup/SyRadioGroup.vue'
58
59
  export { default as SyTextField } from './Customs/SyTextField/SyTextField.vue'
@@ -119,6 +120,8 @@ export { default as SyAlert } from './SyAlert/SyAlert.vue'
119
120
  export { default as ErrorPage } from './ErrorPage/ErrorPage.vue'
120
121
  export { default as MaintenancePage } from './MaintenancePage/MaintenancePage.vue'
121
122
  export { default as NotFoundPage } from './NotFoundPage/NotFoundPage.vue'
123
+ export { default as StatusPage } from './StatusPage/StatusPage.vue'
124
+ export { default as DeclarationAccessibilityPage } from './DeclarationAccessibilityPage/DeclarationAccessibilityPage.vue'
122
125
 
123
126
  // ===========================
124
127
  // Amelipro
@@ -284,6 +284,18 @@ describe('useFieldValidation', () => {
284
284
  }])[0]!
285
285
  expect(ruleWithoutDate(new Date())).toEqual({ error: 'Configuration de la règle invalide' })
286
286
  expect(rule('')).toEqual({}) // Empty string should be ignored
287
+
288
+ const ruleWithEmptyDate = generateRules([{
289
+ type: 'notBeforeDate',
290
+ options: { date: '', message: 'Date cannot be before reference date.' },
291
+ }])[0]!
292
+ expect(ruleWithEmptyDate(new Date())).toEqual({})
293
+
294
+ const ruleWithUndefinedDate = generateRules([{
295
+ type: 'notBeforeDate',
296
+ options: { date: undefined as unknown as string, message: 'Date cannot be before reference date.' },
297
+ }])[0]!
298
+ expect(ruleWithUndefinedDate(new Date())).toEqual({})
287
299
  })
288
300
 
289
301
  it('should throw when date reference is not a string in notBeforeDate rule', () => {
@@ -296,7 +308,7 @@ describe('useFieldValidation', () => {
296
308
  },
297
309
  }])[0]!
298
310
  invalidRule(new Date())
299
- }).toThrow('Date reference must be a string in DD/MM/YYYY format')
311
+ }).toThrow('La date de référence doit être une chaîne au format DD/MM/YYYY')
300
312
  })
301
313
 
302
314
  it('should validate notAfterDate rule', () => {
@@ -324,6 +336,18 @@ describe('useFieldValidation', () => {
324
336
  }])[0]!
325
337
  expect(ruleWithoutDate(new Date())).toEqual({ error: 'Configuration de la règle invalide' })
326
338
  expect(rule('')).toEqual({}) // Empty string should be ignored
339
+
340
+ const ruleWithEmptyDate = generateRules([{
341
+ type: 'notAfterDate',
342
+ options: { date: '', message: 'Date cannot be after reference date.' },
343
+ }])[0]!
344
+ expect(ruleWithEmptyDate(new Date())).toEqual({})
345
+
346
+ const ruleWithUndefinedDate = generateRules([{
347
+ type: 'notAfterDate',
348
+ options: { date: undefined as unknown as string, message: 'Date cannot be after reference date.' },
349
+ }])[0]!
350
+ expect(ruleWithUndefinedDate(new Date())).toEqual({})
327
351
  })
328
352
 
329
353
  it('should throw when date reference is not a string in notAfterDate rule', () => {
@@ -336,7 +360,7 @@ describe('useFieldValidation', () => {
336
360
  },
337
361
  }])[0]!
338
362
  invalidRule(new Date())
339
- }).toThrow('Date reference must be a string in DD/MM/YYYY format')
363
+ }).toThrow('La date de référence doit être une chaîne au format DD/MM/YYYY')
340
364
  })
341
365
 
342
366
  it('should validate dateExact rule', () => {
@@ -364,6 +388,18 @@ describe('useFieldValidation', () => {
364
388
  }])[0]!
365
389
  expect(ruleWithoutDate(new Date())).toEqual({ error: 'Configuration de la règle invalide' })
366
390
  expect(rule('')).toEqual({}) // Empty string should be ignored
391
+
392
+ const ruleWithEmptyDate = generateRules([{
393
+ type: 'dateExact',
394
+ options: { date: '', message: 'Date must be exactly the reference date.' },
395
+ }])[0]!
396
+ expect(ruleWithEmptyDate(new Date())).toEqual({})
397
+
398
+ const ruleWithUndefinedDate = generateRules([{
399
+ type: 'dateExact',
400
+ options: { date: undefined as unknown as string, message: 'Date must be exactly the reference date.' },
401
+ }])[0]!
402
+ expect(ruleWithUndefinedDate(new Date())).toEqual({})
367
403
  })
368
404
 
369
405
  it('should throw when date reference is not a string in dateExact rule', () => {
@@ -376,7 +412,7 @@ describe('useFieldValidation', () => {
376
412
  },
377
413
  }])[0]!
378
414
  invalidRule(new Date())
379
- }).toThrow('Date reference must be a string in DD/MM/YYYY format')
415
+ }).toThrow('La date de référence doit être une chaîne au format DD/MM/YYYY')
380
416
  })
381
417
 
382
418
  it('should validate custom rule', () => {
@@ -233,14 +233,19 @@ export function useFieldValidation() {
233
233
  }
234
234
 
235
235
  case 'notBeforeDate': {
236
- if (typeof options.date === 'undefined') {
236
+ const hasDateOption = Object.prototype.hasOwnProperty.call(options, 'date')
237
+ if (!hasDateOption) {
237
238
  return { error: 'Configuration de la règle invalide' }
238
239
  }
239
240
  // Si la valeur est null ou vide, ne pas valider (champ vide autorisé)
240
241
  if (value === null || value === undefined || value === '') {
241
242
  return {}
242
243
  }
243
- if (options.date === null || (typeof options.date === 'string' && options.date.trim() === '')) {
244
+ if (
245
+ options.date === undefined
246
+ || options.date === null
247
+ || (typeof options.date === 'string' && options.date.trim() === '')
248
+ ) {
244
249
  return {}
245
250
  }
246
251
  const dateValue = parseDate(value)
@@ -250,7 +255,7 @@ export function useFieldValidation() {
250
255
 
251
256
  // Check if options.date is a string and in DD/MM/YYYY format
252
257
  if (typeof options.date !== 'string') {
253
- throw new Error('Date reference must be a string in DD/MM/YYYY format')
258
+ throw new Error('La date de référence doit être une chaîne au format DD/MM/YYYY')
254
259
  }
255
260
 
256
261
  const referenceDate = parseDate(options.date)
@@ -269,14 +274,19 @@ export function useFieldValidation() {
269
274
  }
270
275
 
271
276
  case 'notAfterDate': {
272
- if (typeof options.date === 'undefined') {
277
+ const hasDateOption = Object.prototype.hasOwnProperty.call(options, 'date')
278
+ if (!hasDateOption) {
273
279
  return { error: 'Configuration de la règle invalide' }
274
280
  }
275
281
  // Si la valeur est null ou vide, ne pas valider (champ vide autorisé)
276
282
  if (value === null || value === undefined || value === '') {
277
283
  return {}
278
284
  }
279
- if (options.date === null || (typeof options.date === 'string' && options.date.trim() === '')) {
285
+ if (
286
+ options.date === undefined
287
+ || options.date === null
288
+ || (typeof options.date === 'string' && options.date.trim() === '')
289
+ ) {
280
290
  return {}
281
291
  }
282
292
  const dateValue = parseDate(value)
@@ -286,7 +296,7 @@ export function useFieldValidation() {
286
296
 
287
297
  // Check if options.date is a string and in DD/MM/YYYY format
288
298
  if (typeof options.date !== 'string') {
289
- throw new Error('Date reference must be a string in DD/MM/YYYY format')
299
+ throw new Error('La date de référence doit être une chaîne au format DD/MM/YYYY')
290
300
  }
291
301
 
292
302
  const referenceDate = parseDate(options.date)
@@ -305,14 +315,19 @@ export function useFieldValidation() {
305
315
  }
306
316
 
307
317
  case 'dateExact': {
308
- if (typeof options.date === 'undefined') {
318
+ const hasDateOption = Object.prototype.hasOwnProperty.call(options, 'date')
319
+ if (!hasDateOption) {
309
320
  return { error: 'Configuration de la règle invalide' }
310
321
  }
311
322
  // Si la valeur est null ou vide, ne pas valider (champ vide autorisé)
312
323
  if (value === null || value === undefined || value === '') {
313
324
  return {}
314
325
  }
315
- if (options.date === null || (typeof options.date === 'string' && options.date.trim() === '')) {
326
+ if (
327
+ options.date === undefined
328
+ || options.date === null
329
+ || (typeof options.date === 'string' && options.date.trim() === '')
330
+ ) {
316
331
  return {}
317
332
  }
318
333
  const dateValue = parseDate(value)
@@ -321,7 +336,7 @@ export function useFieldValidation() {
321
336
  }
322
337
 
323
338
  if (typeof options.date !== 'string') {
324
- throw new Error('Date reference must be a string in DD/MM/YYYY format')
339
+ throw new Error('La date de référence doit être une chaîne au format DD/MM/YYYY')
325
340
  }
326
341
 
327
342
  const referenceDate = parseDate(options.date)
@@ -351,6 +351,9 @@ import '../../styles/shared.css';
351
351
 
352
352
  <h4>Outil éventuel</h4>
353
353
  <p>Utilisation du lecteur d'écran pour vérifier la navigation et l'accessibilité des formulaires, tel que l'indication du caractère obligatoire du champ, la liaison du champ aux aides à la saisie ainsi qu'aux messages d'erreurs, le comportement à la soumission du formulaire, etc.</p>
354
+
355
+ <h4>Ressources</h4>
356
+ <p><a href="https://bati-itao.github.io/learning/esdc-self-paced-web-accessibility-course/module6/forms-concepts-fr.html">Concepts des formulaires</a></p>
354
357
  </div>
355
358
 
356
359
  <div className="verification-card">
@@ -375,6 +378,8 @@ import '../../styles/shared.css';
375
378
  <li>Les en-têtes de colonnes des tableaux utilisent des balises `th` avec l'attribut `scope="col"`.</li>
376
379
  </ul>
377
380
  </div>
381
+ <h4>Ressources</h4>
382
+ <p><a href="https://bati-itao.github.io/learning/esdc-self-paced-web-accessibility-course/module4/tables-concepts-fr.html">Concepts des tableaux</a></p>
378
383
  </div>
379
384
 
380
385
  <div className="verification-card">
@@ -432,6 +437,8 @@ import '../../styles/shared.css';
432
437
  </ol>
433
438
  <h4>Outil éventuel</h4>
434
439
  <p>Aucun</p>
440
+ <h4>Ressources</h4>
441
+ <p><a href="https://bati-itao.github.io/learning/esdc-self-paced-web-accessibility-course/module9/introduction-fr.html">Concepts des contenus multimédia</a></p>
435
442
  </div>
436
443
 
437
444
  <div className="info-section">
@@ -0,0 +1,19 @@
1
+ import { computed } from 'vue'
2
+ import { useTheme } from 'vuetify'
3
+ import type { ComputedRef } from 'vue'
4
+
5
+ export function useThemeLocales<
6
+ T extends Record<string, unknown> & { default: unknown },
7
+ >(locales: T): {
8
+ themeLocales: ComputedRef<T[keyof T]>
9
+ } {
10
+ const vuetifyTheme = useTheme()
11
+
12
+ const themeLocales = computed((): T[keyof T] => {
13
+ return (locales[vuetifyTheme.name.value as keyof T] ?? locales.default) as T[keyof T]
14
+ })
15
+
16
+ return {
17
+ themeLocales,
18
+ }
19
+ }