@cnamts/synapse 1.0.4 → 1.0.6

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 (206) hide show
  1. package/dist/DateFilter-BlOpwEVq.js +98 -0
  2. package/dist/NumberFilter-BPUXE4wY.js +121 -0
  3. package/dist/PeriodFilter-B2yx329_.js +112 -0
  4. package/dist/SelectFilter-CedKn1oV.js +136 -0
  5. package/dist/TextFilter-DkhJjRtR.js +114 -0
  6. package/dist/components/Amelipro/AmeliproAccordion/AmeliproAccordion.d.ts +103 -0
  7. package/dist/components/Amelipro/AmeliproAccordion/AmeliproAccordionTemplate/AmeliproAccordionTemplate.d.ts +105 -0
  8. package/dist/components/Amelipro/AmeliproAutoCompleteField/AmeliproAutoCompleteField.d.ts +3 -3
  9. package/dist/components/Amelipro/AmeliproCaptcha/AmeliproCaptcha.d.ts +132 -0
  10. package/dist/components/Amelipro/AmeliproCaptcha/types.d.ts +5 -0
  11. package/dist/components/Amelipro/AmeliproCard/AmeliproCard.d.ts +3 -3
  12. package/dist/components/Amelipro/AmeliproCustomSelector/AmeliproCustomSelector.d.ts +126 -0
  13. package/dist/components/Amelipro/AmeliproCustomSelector/types.d.ts +6 -0
  14. package/dist/components/Amelipro/AmeliproIllustratedDataTile/AmeliproIllustratedDataTile.d.ts +1 -1
  15. package/dist/components/Amelipro/AmeliproMultipleFoldingCard/AmeliproMultipleFoldingCard.d.ts +1 -1
  16. package/dist/components/Amelipro/AmeliproSelect/AmeliproSelect.d.ts +3 -3
  17. package/dist/components/Amelipro/AmeliproTable/AmeliproTable.d.ts +190 -0
  18. package/dist/components/Amelipro/AmeliproTable/types.d.ts +34 -0
  19. package/dist/components/Amelipro/AmeliproTabs/AmeliproTabs.d.ts +3 -3
  20. package/dist/components/Amelipro/AmeliproTextField/AmeliproTextField.d.ts +1 -1
  21. package/dist/components/Amelipro/AmeliproTileBtn/AmeliproTileBtn.d.ts +1 -1
  22. package/dist/components/Amelipro/types.d.ts +6 -0
  23. package/dist/components/CookieBanner/CookieBanner.d.ts +1 -1
  24. package/dist/components/Customs/Selects/SySelect/SySelect.d.ts +11 -2
  25. package/dist/components/Customs/Selects/SySelect/composables/useSySelectKeyboard.d.ts +6 -1
  26. package/dist/components/Customs/SyTextField/SyTextField.d.ts +3 -1
  27. package/dist/components/DataList/DataList.d.ts +9 -0
  28. package/dist/components/DataListGroup/DataListGroup.d.ts +10 -1
  29. package/dist/components/DataListItem/DataListItem.d.ts +1 -1
  30. package/dist/components/DataListItem/config.d.ts +1 -1
  31. package/dist/components/DatePicker/CalendarMode/DatePicker.d.ts +18 -8
  32. package/dist/components/DatePicker/ComplexDatePicker/ComplexDatePicker.d.ts +16 -6
  33. package/dist/components/DatePicker/DateTextInput/DateTextInput.d.ts +6 -1
  34. package/dist/components/DatePicker/composables/useDateInputEditing.d.ts +17 -8
  35. package/dist/components/DatePicker/composables/useKeyboardEvents.d.ts +41 -0
  36. package/dist/components/DatePicker/composables/useManualDateValidation.d.ts +4 -9
  37. package/dist/components/DatePicker/utils/dateFormattingUtils.d.ts +72 -0
  38. package/dist/components/DatePicker/utils/validationUtils.d.ts +38 -0
  39. package/dist/components/HeaderBar/HeaderBurgerMenu/HeaderBurgerMenu.d.ts +9 -3
  40. package/dist/components/HeaderBar/HeaderBurgerMenu/HeaderMenuItem/HeaderMenuItem.d.ts +6 -1
  41. package/dist/components/HeaderBar/HeaderBurgerMenu/HeaderMenuSection/HeaderMenuSection.d.ts +11 -1
  42. package/dist/components/HeaderBar/HeaderBurgerMenu/HeaderSubMenu/HeaderSubMenu.d.ts +11 -1
  43. package/dist/components/HeaderBar/HeaderBurgerMenu/locals.d.ts +2 -0
  44. package/dist/components/HeaderBar/HeaderBurgerMenu/useMenuPosition.d.ts +4 -0
  45. package/dist/components/NirField/NirField.d.ts +14 -4
  46. package/dist/components/PeriodField/PeriodField.d.ts +24 -4
  47. package/dist/components/Tables/common/SyTablePagination.d.ts +10 -0
  48. package/dist/components/index.d.ts +4 -0
  49. package/dist/composables/index.d.ts +1 -0
  50. package/dist/composables/usePagination.d.ts +16 -0
  51. package/dist/design-system-v3.js +165 -160
  52. package/dist/design-system-v3.umd.cjs +120 -138
  53. package/dist/directives/lockFocus.d.ts +17 -0
  54. package/dist/{main-BzyNNvHX.js → main-BXPFSAB4.js} +14664 -13282
  55. package/dist/style.css +1 -0
  56. package/package.json +5 -2
  57. package/src/assets/amelipro/apTheme.scss +149 -0
  58. package/src/assets/amelipro/apTokens.scss +0 -148
  59. package/src/assets/overrides/_btns.scss +15 -0
  60. package/src/assets/overrides/_container.scss +36 -0
  61. package/src/assets/overrides/_forms.scss +7 -0
  62. package/src/assets/{_spacers.scss → overrides/_spacers.scss} +0 -7
  63. package/src/assets/overrides/_tables.scss +18 -0
  64. package/src/assets/overrides/_tooltips.scss +10 -0
  65. package/src/assets/overrides/_typography.scss +196 -0
  66. package/src/assets/settings.scss +11 -51
  67. package/src/assets/themes.scss +10 -0
  68. package/src/assets/tokens.scss +9 -156
  69. package/src/components/Accordion/composables/__tests__/useAccordionGroupCommunication.spec.ts +80 -40
  70. package/src/components/Amelipro/AmeliproAccordion/AmeliproAccordion.mdx +15 -0
  71. package/src/components/Amelipro/AmeliproAccordion/AmeliproAccordion.stories.ts +83 -0
  72. package/src/components/Amelipro/AmeliproAccordion/AmeliproAccordion.vue +86 -0
  73. package/src/components/Amelipro/AmeliproAccordion/AmeliproAccordionTemplate/AmeliproAccordionTemplate.vue +242 -0
  74. package/src/components/Amelipro/AmeliproAccordion/AmeliproAccordionTemplate/__tests__/AmeliproAccordionTemplate.spec.ts +20 -0
  75. package/src/components/Amelipro/AmeliproAccordion/AmeliproAccordionTemplate/__tests__/__snapshots__/AmeliproAccordionTemplate.spec.ts.snap +124 -0
  76. package/src/components/Amelipro/AmeliproAccordion/__tests__/AmeliproAccordion.spec.ts +20 -0
  77. package/src/components/Amelipro/AmeliproAccordion/__tests__/__snapshots__/AmeliproAccordion.spec.ts.snap +124 -0
  78. package/src/components/Amelipro/AmeliproCaptcha/AmeliproCaptcha.mdx +15 -0
  79. package/src/components/Amelipro/AmeliproCaptcha/AmeliproCaptcha.stories.ts +87 -0
  80. package/src/components/Amelipro/AmeliproCaptcha/AmeliproCaptcha.vue +233 -0
  81. package/src/components/Amelipro/AmeliproCaptcha/__tests__/AmeliproCaptcha.spec.ts +24 -0
  82. package/src/components/Amelipro/AmeliproCaptcha/__tests__/__snapshots__/AmeliproCaptcha.spec.ts.snap +384 -0
  83. package/src/components/Amelipro/AmeliproCaptcha/types.d.ts +5 -0
  84. package/src/components/Amelipro/AmeliproCustomSelector/AmeliproCustomSelector.mdx +15 -0
  85. package/src/components/Amelipro/AmeliproCustomSelector/AmeliproCustomSelector.stories.ts +143 -0
  86. package/src/components/Amelipro/AmeliproCustomSelector/AmeliproCustomSelector.vue +351 -0
  87. package/src/components/Amelipro/AmeliproCustomSelector/__tests__/AmeliproCustomSelector.spec.ts +50 -0
  88. package/src/components/Amelipro/AmeliproCustomSelector/__tests__/__snapshots__/AmeliproCustomSelector.spec.ts.snap +186 -0
  89. package/src/components/Amelipro/AmeliproCustomSelector/types.d.ts +6 -0
  90. package/src/components/Amelipro/AmeliproHeader/AmeliproHeaderBar/AmeliproHeaderBrandSection/tests/__snapshots__/AmeliproHeaderBrandSection.spec.ts.snap +1 -1
  91. package/src/components/Amelipro/AmeliproHeader/AmeliproHeaderBar/tests/__snapshots__/AmeliproHeaderBar.spec.ts.snap +1 -1
  92. package/src/components/Amelipro/AmeliproHeader/tests/__snapshots__/AmeliproHeader.spec.ts.snap +1 -708
  93. package/src/components/Amelipro/AmeliproMenu/tests/__snapshots__/AmeliproMenu.spec.ts.snap +1 -1
  94. package/src/components/Amelipro/AmeliproPageLayout/tests/__snapshots__/AmeliproPageLayout.spec.ts.snap +1 -708
  95. package/src/components/Amelipro/AmeliproTable/AmeliproTable.mdx +22 -0
  96. package/src/components/Amelipro/AmeliproTable/AmeliproTable.stories.ts +550 -0
  97. package/src/components/Amelipro/AmeliproTable/AmeliproTable.vue +421 -0
  98. package/src/components/Amelipro/AmeliproTable/__tests__/AmeliproTable.spec.ts +72 -0
  99. package/src/components/Amelipro/AmeliproTable/__tests__/__snapshots__/AmeliproTable.spec.ts.snap +427 -0
  100. package/src/components/Amelipro/AmeliproTable/types.d.ts +34 -0
  101. package/src/components/Amelipro/ServiceMenu/ServiceMenu.vue +12 -1
  102. package/src/components/Amelipro/ServiceMenu/tests/__snapshots__/ServiceMenu.spec.ts.snap +0 -820
  103. package/src/components/Amelipro/types.ts +8 -0
  104. package/src/components/CollapsibleList/CollapsibleList.vue +0 -2
  105. package/src/components/CookieBanner/CookieBanner.vue +1 -3
  106. package/src/components/CopyBtn/CopyBtn.vue +9 -2
  107. package/src/components/CopyBtn/tests/CopyBtn.spec.ts +3 -1
  108. package/src/components/Customs/Selects/SySelect/Accessibilite.mdx +7 -0
  109. package/src/components/Customs/Selects/SySelect/Accessibilite.stories.ts +4 -1
  110. package/src/components/Customs/Selects/SySelect/SySelect.stories.ts +80 -0
  111. package/src/components/Customs/Selects/SySelect/SySelect.vue +280 -34
  112. package/src/components/Customs/Selects/SySelect/composables/tests/useSySelectKeyboard.spec.ts +14 -5
  113. package/src/components/Customs/Selects/SySelect/composables/useSySelectKeyboard.ts +129 -12
  114. package/src/components/Customs/SyIcon/SyIcon.spec.ts +3 -0
  115. package/src/components/Customs/SyTextField/Accessibilite.stories.ts +3 -1
  116. package/src/components/Customs/SyTextField/SyTextField.stories.ts +79 -6
  117. package/src/components/Customs/SyTextField/SyTextField.vue +218 -24
  118. package/src/components/DataList/Accessibilite.stories.ts +4 -0
  119. package/src/components/DataList/DataList.vue +19 -12
  120. package/src/components/DataListGroup/Accessibilite.stories.ts +4 -0
  121. package/src/components/DataListGroup/DataListGroup.vue +32 -15
  122. package/src/components/DataListItem/DataListItem.vue +14 -11
  123. package/src/components/DataListItem/config.ts +1 -1
  124. package/src/components/DataListItem/tests/DataListItem.spec.ts +2 -2
  125. package/src/components/DatePicker/CalendarMode/DatePicker.vue +12 -7
  126. package/src/components/DatePicker/ComplexDatePicker/ComplexDatePicker.stories.ts +174 -0
  127. package/src/components/DatePicker/ComplexDatePicker/ComplexDatePicker.vue +27 -5
  128. package/src/components/DatePicker/DatePickerValidationExample/DatePickerValidation.stories.ts +286 -0
  129. package/src/components/DatePicker/DateTextInput/DateRange.stories.ts +1 -1
  130. package/src/components/DatePicker/DateTextInput/DateTextInput.vue +29 -31
  131. package/src/components/DatePicker/composables/tests/useManualDateValidation.spec.ts +11 -3
  132. package/src/components/DatePicker/composables/useDateInputEditing.ts +73 -209
  133. package/src/components/DatePicker/composables/useKeyboardEvents.ts +149 -0
  134. package/src/components/DatePicker/composables/useManualDateValidation.ts +27 -68
  135. package/src/components/DatePicker/utils/dateFormattingUtils.ts +228 -0
  136. package/src/components/DatePicker/utils/validationUtils.ts +90 -0
  137. package/src/components/DialogBox/Accessibilite.stories.ts +4 -0
  138. package/src/components/DialogBox/DialogBox.stories.ts +10 -10
  139. package/src/components/DialogBox/DialogBox.vue +83 -21
  140. package/src/components/DialogBox/tests/__snapshots__/DialogBox.spec.ts.snap +12 -6
  141. package/src/components/FileUpload/tests/FileUpload.spec.ts +3 -0
  142. package/src/components/FooterBar/FooterBar.vue +1 -0
  143. package/src/components/HeaderBar/Accessibilite.stories.ts +4 -0
  144. package/src/components/HeaderBar/HeaderBar.mdx +47 -22
  145. package/src/components/HeaderBar/HeaderBar.stories.ts +54 -13
  146. package/src/components/HeaderBar/HeaderBar.vue +2 -1
  147. package/src/components/HeaderBar/HeaderBurgerMenu/Accessibilite.stories.ts +4 -0
  148. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderBurgerMenu.stories.ts +160 -82
  149. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderBurgerMenu.vue +41 -56
  150. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderMenuItem/HeaderMenuItem.vue +10 -3
  151. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderMenuSection/HeaderMenuSection.vue +41 -18
  152. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderMenuSection/tests/HeaderMenuSection.spec.ts +7 -2
  153. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderSubMenu/HeaderSubMenu.stories.ts +36 -9
  154. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderSubMenu/HeaderSubMenu.vue +41 -9
  155. package/src/components/HeaderBar/HeaderBurgerMenu/locals.ts +2 -0
  156. package/src/components/HeaderBar/HeaderBurgerMenu/tests/HeaderBurgerMenu.spec.ts +1 -1
  157. package/src/components/HeaderBar/HeaderBurgerMenu/tests/__snapshots__/HeaderBurgerMenu.spec.ts.snap +8 -3
  158. package/src/components/HeaderBar/HeaderBurgerMenu/useMenuPosition.ts +50 -0
  159. package/src/components/HeaderBar/HeaderLogo/HeaderLogo.vue +5 -5
  160. package/src/components/HeaderBar/HeaderMenuBtn/HeaderMenuBtn.vue +7 -4
  161. package/src/components/HeaderBar/locales.ts +1 -1
  162. package/src/components/HeaderBar/tests/__snapshots__/HeaderBar.spec.ts.snap +2 -1
  163. package/src/components/HeaderLoading/HeaderLoading.vue +0 -1
  164. package/src/components/HeaderNavigationBar/tests/HeaderNavigationBar.spec.ts +2 -0
  165. package/src/components/LangBtn/LangBtn.vue +0 -3
  166. package/src/components/LogoBrandSection/Accessibilite.stories.ts +4 -1
  167. package/src/components/LogoBrandSection/LogoBrandSection.stories.ts +2 -2
  168. package/src/components/LogoBrandSection/LogoBrandSection.vue +13 -8
  169. package/src/components/LogoBrandSection/locales.ts +1 -1
  170. package/src/components/LogoBrandSection/tests/LogoBrandSection.spec.ts +1 -1
  171. package/src/components/LogoBrandSection/tests/__snapshots__/LogoBrandSection.spec.ts.snap +6 -4
  172. package/src/components/NirField/NirField.vue +5 -5
  173. package/src/components/NirField/tests/NirField.spec.ts +78 -12
  174. package/src/components/Tables/SyServerTable/tests/SyServerTable.spec.ts +104 -6
  175. package/src/components/Tables/common/TableHeader.vue +10 -7
  176. package/src/components/Tables/common/tableAccessibilityUtils.ts +13 -2
  177. package/src/components/Tables/common/useTableAria.ts +17 -1
  178. package/src/components/UserMenuBtn/tests/UserMenuBtn.spec.ts +2 -1
  179. package/src/components/index.ts +4 -0
  180. package/src/composables/date/tests/useDatePickerAccessibility.spec.ts +34 -5
  181. package/src/composables/index.ts +3 -0
  182. package/src/composables/useFilterable/useFilterable.ts +13 -1
  183. package/src/composables/usePagination.ts +103 -0
  184. package/src/directives/lockFocus.ts +48 -0
  185. package/src/main.ts +1 -2
  186. package/src/stories/Accessibilite/Aculturation/SensibilisationAccessibilite.mdx +1 -8
  187. package/src/stories/Accessibilite/{Aculturation/AuditDesignSystem.mdx → AuditDesignSystem.mdx} +1 -1
  188. package/src/stories/Accessibilite/KitDePreAudit/Outils/Tanaguru/FauxPositifs.mdx +102 -0
  189. package/src/stories/Accessibilite/KitDePreAudit/Outils/Tanaguru/FauxPositifs.stories.ts +219 -0
  190. package/src/stories/Accessibilite/KitDePreAudit/Outils/{Tanaguru.mdx → Tanaguru/Utilisation.mdx} +1 -1
  191. package/src/stories/DesignTokens/ColorIntegrationExample.vue +43 -0
  192. package/src/stories/DesignTokens/Colors.mdx +2 -0
  193. package/src/stories/DesignTokens/colors.stories.ts +9 -0
  194. package/src/vuetifyConfig.ts +3 -3
  195. package/dist/DateFilter-yrwJv_2R.js +0 -95
  196. package/dist/NumberFilter-BQXtywZI.js +0 -117
  197. package/dist/PeriodFilter-BYXVSzr5.js +0 -108
  198. package/dist/SelectFilter-CJV_mlN3.js +0 -133
  199. package/dist/TextFilter-DN0ejYIs.js +0 -110
  200. package/dist/design-system-v3.css +0 -1
  201. package/dist/directives/letterSpacing.d.ts +0 -27
  202. package/src/assets/_fonts.scss +0 -6
  203. package/src/assets/_typography.scss +0 -157
  204. package/src/directives/letterSpacing.ts +0 -233
  205. /package/src/assets/{_elevations.scss → overrides/_elevations.scss} +0 -0
  206. /package/src/assets/{_radius.scss → overrides/_radius.scss} +0 -0
@@ -191,7 +191,7 @@ describe('useSySelectKeyboard', () => {
191
191
  })
192
192
 
193
193
  describe('handleUpKey', () => {
194
- it('ouvre le menu et sélectionne le dernier élément si le menu est fermé', async () => {
194
+ it('ouvre le menu et sélectionne le premier élément si le menu est fermé (comportement RGAA)', async () => {
195
195
  isOpen.value = false
196
196
  keyboard.handleUpKey()
197
197
  expect(toggleMenu).toHaveBeenCalled()
@@ -200,7 +200,8 @@ describe('useSySelectKeyboard', () => {
200
200
  isOpen.value = true
201
201
  await nextTick()
202
202
 
203
- expect(keyboard.activeDescendantId.value).toBe(`option-${mockItems.length - 1}`)
203
+ // Comportement RGAA correct : flèche haut ouvre et va au premier élément
204
+ expect(keyboard.activeDescendantId.value).toBe('option-0')
204
205
  })
205
206
 
206
207
  it('sélectionne l\'élément précédent si le menu est ouvert', () => {
@@ -210,24 +211,32 @@ describe('useSySelectKeyboard', () => {
210
211
  expect(keyboard.activeDescendantId.value).toBe('option-1')
211
212
  })
212
213
 
213
- it('boucle au dernier élément si on est au premier élément', () => {
214
+ it('reste au premier élément si on est déjà au premier (pas de bouclage)', () => {
214
215
  isOpen.value = true
215
216
  keyboard.setActiveDescendant(0)
216
217
  keyboard.handleUpKey()
217
- expect(keyboard.activeDescendantId.value).toBe(`option-${mockItems.length - 1}`)
218
+ // Ne doit pas boucler, doit rester au premier élément
219
+ expect(keyboard.activeDescendantId.value).toBe('option-0')
218
220
  })
219
221
  })
220
222
 
221
223
  describe('handleCharacterKey', () => {
222
224
  it('trouve et sélectionne un élément commençant par le caractère donné', () => {
225
+ // Menu déjà ouvert, pas besoin d'attendre nextTick
226
+ isOpen.value = true
223
227
  keyboard.handleCharacterKey('b')
224
228
  expect(keyboard.activeDescendantId.value).toBe('option-1') // Banana
225
229
  })
226
230
 
227
- it('ouvre le menu si fermé et trouve un élément correspondant', () => {
231
+ it('ouvre le menu si fermé et trouve un élément correspondant', async () => {
228
232
  isOpen.value = false
229
233
  keyboard.handleCharacterKey('c')
230
234
  expect(toggleMenu).toHaveBeenCalled()
235
+
236
+ // Simuler l'ouverture du menu et attendre nextTick
237
+ isOpen.value = true
238
+ await nextTick()
239
+
231
240
  expect(keyboard.activeDescendantId.value).toBe('option-2') // Cherry
232
241
  })
233
242
 
@@ -7,7 +7,7 @@ export type ItemType = {
7
7
  export interface UseSySelectKeyboardOptions {
8
8
  isOpen: Ref<boolean>
9
9
  formattedItems: Ref<ItemType[]>
10
- toggleMenu: () => void
10
+ toggleMenu: (skipInitialFocus?: boolean) => void
11
11
  selectItem: (item: ItemType | null, event?: Event) => void
12
12
  getItemText: (item: unknown) => unknown
13
13
  updateListPosition: () => void
@@ -85,6 +85,22 @@ export function useSySelectKeyboard(options: UseSySelectKeyboardOptions) {
85
85
  })
86
86
  }
87
87
 
88
+ /**
89
+ * Fonction pour effacer seulement le focus visuel et ARIA sans réinitialiser lastFocusedIndex
90
+ * Utilisée quand on ferme le dropdown mais qu'on veut conserver la mémoire du dernier focus
91
+ */
92
+ const clearVisualFocus = () => {
93
+ activeDescendantId.value = ''
94
+
95
+ // Supprimer la classe de focus visuel de tous les éléments
96
+ nextTick(() => {
97
+ const allItems = document.querySelectorAll('.v-list-item')
98
+ allItems.forEach((item) => {
99
+ item.classList.remove('keyboard-focused')
100
+ })
101
+ })
102
+ }
103
+
88
104
  /**
89
105
  * Trouve l'index de l'élément actuellement sélectionné ou actif
90
106
  * Utilise lastFocusedIndex comme source de vérité principale
@@ -171,29 +187,37 @@ export function useSySelectKeyboard(options: UseSySelectKeyboardOptions) {
171
187
 
172
188
  const handleDownKey = () => {
173
189
  if (!isOpen.value) {
174
- toggleMenu()
190
+ // Passer skipInitialFocus=true pour éviter que toggleMenu override notre focus
191
+ toggleMenu(true)
175
192
  nextTick(() => {
176
- // Sélectionner le premier élément à l'ouverture
177
- setActiveDescendant(0)
193
+ // Restaurer le dernier item qui avait le focus, ou le premier item par défaut
194
+ const indexToFocus = lastFocusedIndex.value >= 0 && lastFocusedIndex.value < formattedItems.value.length
195
+ ? lastFocusedIndex.value
196
+ : 0
197
+ setActiveDescendant(indexToFocus)
178
198
  })
179
199
  }
180
200
  else {
181
- const currentIndex = findSelectedItemIndex()
182
- const nextIndex = currentIndex >= 0 ? Math.min(currentIndex + 1, formattedItems.value.length - 1) : 0
201
+ // Utiliser lastFocusedIndex comme point de départ (pas l'item sélectionné)
202
+ const currentIndex = lastFocusedIndex.value >= 0 ? lastFocusedIndex.value : 0
203
+ const nextIndex = Math.min(currentIndex + 1, formattedItems.value.length - 1)
183
204
  setActiveDescendant(nextIndex)
184
205
  }
185
206
  }
186
207
 
187
208
  const handleUpKey = () => {
188
209
  if (!isOpen.value) {
189
- toggleMenu()
210
+ // Passer skipInitialFocus=true pour éviter que toggleMenu override notre focus
211
+ toggleMenu(true)
190
212
  nextTick(() => {
191
- setActiveDescendant(formattedItems.value.length - 1)
213
+ // Aller au premier item quand on ouvre avec flèche haut (comportement RGAA correct)
214
+ setActiveDescendant(0)
192
215
  })
193
216
  }
194
217
  else {
195
218
  const currentIndex = findSelectedItemIndex()
196
- const prevIndex = currentIndex > 0 ? currentIndex - 1 : formattedItems.value.length - 1
219
+ // Ne pas boucler : rester au premier item si on est déjà au premier
220
+ const prevIndex = currentIndex >= 0 ? Math.max(currentIndex - 1, 0) : 0
197
221
  setActiveDescendant(prevIndex)
198
222
  }
199
223
  }
@@ -203,12 +227,100 @@ export function useSySelectKeyboard(options: UseSySelectKeyboardOptions) {
203
227
  if (key.length === 1 && key.match(/\S/)) {
204
228
  const index = findItemStartingWith(key)
205
229
  if (index >= 0) {
206
- if (!isOpen.value) toggleMenu()
207
- setActiveDescendant(index)
230
+ if (!isOpen.value) {
231
+ toggleMenu()
232
+ // Attendre que le menu soit ouvert avant de définir le focus
233
+ nextTick(() => {
234
+ setActiveDescendant(index)
235
+ })
236
+ }
237
+ else {
238
+ // Menu déjà ouvert, définir le focus immédiatement
239
+ setActiveDescendant(index)
240
+ }
208
241
  }
209
242
  }
210
243
  }
211
244
 
245
+ const handleHomeKey = () => {
246
+ if (!isOpen.value) {
247
+ toggleMenu()
248
+ nextTick(() => {
249
+ // Aller au premier item
250
+ setActiveDescendant(0)
251
+ })
252
+ }
253
+ else {
254
+ // Menu déjà ouvert, aller au premier item
255
+ setActiveDescendant(0)
256
+ }
257
+ }
258
+
259
+ const handleEndKey = () => {
260
+ if (!isOpen.value) {
261
+ toggleMenu()
262
+ nextTick(() => {
263
+ // Aller au dernier item
264
+ setActiveDescendant(formattedItems.value.length - 1)
265
+ })
266
+ }
267
+ else {
268
+ // Menu déjà ouvert, aller au dernier item
269
+ setActiveDescendant(formattedItems.value.length - 1)
270
+ }
271
+ }
272
+
273
+ const handlePageUpKey = () => {
274
+ if (!isOpen.value) {
275
+ toggleMenu()
276
+ nextTick(() => {
277
+ // Aller au premier item
278
+ setActiveDescendant(0)
279
+ })
280
+ }
281
+ else {
282
+ // Menu déjà ouvert, naviguer de 10 items vers le haut
283
+ const currentIndex = lastFocusedIndex.value >= 0 ? lastFocusedIndex.value : 0
284
+ const newIndex = Math.max(currentIndex - 10, 0)
285
+ setActiveDescendant(newIndex)
286
+ }
287
+ }
288
+
289
+ const handlePageDownKey = () => {
290
+ if (!isOpen.value) {
291
+ toggleMenu()
292
+ nextTick(() => {
293
+ // Aller au dernier item
294
+ setActiveDescendant(formattedItems.value.length - 1)
295
+ })
296
+ }
297
+ else {
298
+ // Menu déjà ouvert, naviguer de 10 items vers le bas
299
+ const currentIndex = lastFocusedIndex.value >= 0 ? lastFocusedIndex.value : 0
300
+ const newIndex = Math.min(currentIndex + 10, formattedItems.value.length - 1)
301
+ setActiveDescendant(newIndex)
302
+ }
303
+ }
304
+
305
+ const handleTabKey = () => {
306
+ if (isOpen.value && activeDescendantId.value) {
307
+ // Trouver l'item actuellement focusé
308
+ const currentIndex = parseInt(activeDescendantId.value.split('-')[1])
309
+ if (!isNaN(currentIndex) && currentIndex >= 0 && currentIndex < formattedItems.value.length) {
310
+ const currentItem = formattedItems.value[currentIndex]
311
+ // Sélectionner l'item qui a le focus
312
+ selectItem(currentItem)
313
+ }
314
+ }
315
+ // Fermer le menu (la navigation Tab normale continuera après)
316
+ if (isOpen.value) {
317
+ isOpen.value = false
318
+ clearActiveDescendant()
319
+ }
320
+ // Ne pas empêcher le comportement par défaut de Tab pour permettre
321
+ // la navigation vers l'élément suivant focusable
322
+ }
323
+
212
324
  // Watch activeDescendantId pour synchroniser lastFocusedIndex
213
325
  watch(activeDescendantId, (newId) => {
214
326
  if (newId) {
@@ -240,7 +352,7 @@ export function useSySelectKeyboard(options: UseSySelectKeyboardOptions) {
240
352
  }
241
353
  else {
242
354
  // Conserver lastFocusedIndex mais effacer le focus visuel et ARIA
243
- clearActiveDescendant()
355
+ clearVisualFocus()
244
356
  }
245
357
  })
246
358
 
@@ -267,6 +379,11 @@ export function useSySelectKeyboard(options: UseSySelectKeyboardOptions) {
267
379
  handleUpKey,
268
380
  handleCharacterKey,
269
381
  handleEscapeKey,
382
+ handleHomeKey,
383
+ handleEndKey,
384
+ handlePageUpKey,
385
+ handlePageDownKey,
386
+ handleTabKey,
270
387
  restoreFocus,
271
388
  }
272
389
  }
@@ -8,6 +8,7 @@ describe('SyIcon', () => {
8
8
  const wrapper = mount(SyIcon, {
9
9
  props: {
10
10
  icon: 'mdi-home',
11
+ decorative: true, // Marquer l'icône comme décorative pour éviter les avertissements d'accessibilité
11
12
  },
12
13
  global: {
13
14
  stubs: {
@@ -82,6 +83,7 @@ describe('SyIcon', () => {
82
83
  const wrapper = mount(SyIcon, {
83
84
  props: {
84
85
  icon: 'mdi-home',
86
+ decorative: true, // Marquer l'icône comme décorative pour éviter les avertissements d'accessibilité
85
87
  color: 'primary',
86
88
  },
87
89
  global: {
@@ -106,6 +108,7 @@ describe('SyIcon', () => {
106
108
  const wrapper = mount(SyIcon, {
107
109
  props: {
108
110
  icon: 'mdi-home',
111
+ decorative: true, // Marquer l'icône comme décorative pour éviter les avertissements d'accessibilité
109
112
  size: 'large',
110
113
  },
111
114
  global: {
@@ -222,7 +222,9 @@ export const Legende: StoryObj = {
222
222
  rapport</a></p>
223
223
  <p style="color: grey; font-size: 14px">Correctifs associés (<a
224
224
  href="https://github.com/assurance-maladie-digital/design-system/issues/4028" target="_blank"
225
- style="color:#0C41BD;">issue #4028</a>)</p>
225
+ style="color:#0C41BD;">issue #4028</a>, <a
226
+ href="https://github.com/assurance-maladie-digital/design-system-v3/pull/935" target="_blank"
227
+ style="color:#0C41BD;">issue #935</a>)</p>
226
228
  </div>
227
229
  `,
228
230
  }
@@ -181,6 +181,10 @@ const meta = {
181
181
  description: 'Texte d\'aide affiché sous le champ',
182
182
  control: 'text',
183
183
  },
184
+ 'helpText': {
185
+ description: 'Texte d\'aide affiché sous le champ',
186
+ control: 'text',
187
+ },
184
188
  'loading': {
185
189
  description: 'Affiche un indicateur de chargement',
186
190
  control: 'boolean',
@@ -377,6 +381,58 @@ export const Default: Story = {
377
381
  },
378
382
  }
379
383
 
384
+ export const HelpText: Story = {
385
+ parameters: {
386
+ sourceCode: [
387
+ {
388
+ name: 'Template',
389
+ code: `
390
+ <template>
391
+ <SyTextField
392
+ v-model="value"
393
+ help-text="Texte d'aide à la saisie"
394
+ />
395
+ </template>
396
+ `,
397
+ },
398
+ {
399
+ name: 'Script',
400
+ code: `
401
+ <script setup lang="ts">
402
+ import { SyTextField } from '@cnamts/synapse'
403
+ </script>
404
+ `,
405
+ },
406
+ ],
407
+ },
408
+ args: {
409
+ showDivider: false,
410
+ variantStyle: 'outlined',
411
+ color: 'primary',
412
+ isClearable: true,
413
+ label: 'Label',
414
+ modelValue: '',
415
+ helpText: 'Texte d\'aide à la saisie',
416
+ },
417
+ render: (args) => {
418
+ return {
419
+ components: { SyTextField, VIcon },
420
+ setup() {
421
+ const value = ref(args.modelValue)
422
+ watch(() => args.modelValue, (newValue) => {
423
+ value.value = newValue
424
+ })
425
+ return { args, value }
426
+ },
427
+ template: `
428
+ <div>
429
+ <SyTextField v-bind="args" v-model="value" />
430
+ </div>
431
+ `,
432
+ }
433
+ },
434
+ }
435
+
380
436
  export const Required: Story = {
381
437
  args: {
382
438
  ...Default.args,
@@ -393,7 +449,8 @@ export const Required: Story = {
393
449
  return { args, value }
394
450
  },
395
451
  template: `
396
- <div class="d-flex flex-wrap align-center">
452
+ <div>
453
+ <p class="mb-2 text-caption text-grey-darken-2">Ce champ est obligatoire</p>
397
454
  <SyTextField v-bind="args" v-model="value" />
398
455
  </div>
399
456
  `,
@@ -413,6 +470,7 @@ Pour afficher l'astérisque sur un champ requis, il faut activer la prop \`displ
413
470
  {
414
471
  name: 'Template',
415
472
  code: `<template>
473
+ <p class="mb-2 text-caption text-grey-darken-2">Ce champ est obligatoire</p>
416
474
  <SyTextField
417
475
  v-model="value"
418
476
  required
@@ -451,7 +509,7 @@ export const RequiredWithAsterisk: Story = {
451
509
  },
452
510
  template: `
453
511
  <div class="d-flex flex-wrap align-center">
454
- <SyTextField v-bind="args" v-model="value" />
512
+ <SyTextField v-bind="args" v-model="value" />
455
513
  </div>
456
514
  `,
457
515
  }
@@ -837,6 +895,7 @@ Cette story montre l'utilisation des règles de validation standard. Le champ :
837
895
  code: `<SyTextField
838
896
  v-model="value"
839
897
  label="Champ avec validation"
898
+ helpText="Le champ doit contenir à minima 3 caractères"
840
899
  :customRules="[
841
900
  {
842
901
  type: 'minLength',
@@ -862,6 +921,7 @@ Cette story montre l'utilisation des règles de validation standard. Le champ :
862
921
  v-model="value"
863
922
  v-bind="args"
864
923
  label="Champ avec validation"
924
+ helpText="Le champ doit contenir à minima 3 caractères"
865
925
  :customRules="[
866
926
  {
867
927
  type: 'minLength',
@@ -960,12 +1020,13 @@ Cette story montre un cas d'usage courant : la validation d'une adresse email. L
960
1020
  v-model="value"
961
1021
  autocomplete="email"
962
1022
  label="Email"
1023
+ helpText="Format attendu : nom@domaine.fr"
963
1024
  required
964
1025
  :customRules="[
965
1026
  {
966
1027
  type: 'email',
967
1028
  options: {
968
- message: 'L'email n'est pas valide',
1029
+ message: 'L'email n'est pas valide'
969
1030
  successMessage: 'L'email est valide'
970
1031
  }
971
1032
  }
@@ -986,6 +1047,7 @@ Cette story montre un cas d'usage courant : la validation d'une adresse email. L
986
1047
  v-model="value"
987
1048
  v-bind="args"
988
1049
  label="Email"
1050
+ helpText="Format attendu : nom@domaine.fr"
989
1051
  autocomplete="email"
990
1052
  required
991
1053
  :customRules="[
@@ -1023,6 +1085,7 @@ Cette story montre l'utilisation de la règle \`matchPattern\` pour valider un f
1023
1085
  code: `<SyTextField
1024
1086
  v-model="value"
1025
1087
  label="Code postal"
1088
+ helpText="Exemple : 31000"
1026
1089
  autocomplete="postal-code"
1027
1090
  required
1028
1091
  :customRules="[
@@ -1051,6 +1114,7 @@ Cette story montre l'utilisation de la règle \`matchPattern\` pour valider un f
1051
1114
  v-model="value"
1052
1115
  v-bind="args"
1053
1116
  label="Code postal"
1117
+ helpText="Exemple : 31000"
1054
1118
  autocomplete="postal-code"
1055
1119
  required
1056
1120
  :customRules="[
@@ -1307,9 +1371,11 @@ export const FormValidation: Story = {
1307
1371
  v-model="nomValue"
1308
1372
  label="Nom"
1309
1373
  placeholder="Votre nom"
1374
+ autocomplete="family-name"
1310
1375
  required
1311
1376
  show-success-messages
1312
1377
  class="mb-4"
1378
+ aria-describedby="nom-rule"
1313
1379
  />
1314
1380
 
1315
1381
  <SyTextField
@@ -1317,9 +1383,11 @@ export const FormValidation: Story = {
1317
1383
  v-model="prenomValue"
1318
1384
  label="Prénom"
1319
1385
  placeholder="Votre prénom"
1386
+ autocomplete="given-name"
1320
1387
  :custom-rules="prenomRules"
1321
1388
  show-success-messages
1322
1389
  class="mb-4"
1390
+ aria-describedby="prenom-rule"
1323
1391
  />
1324
1392
 
1325
1393
  <SyTextField
@@ -1330,15 +1398,16 @@ export const FormValidation: Story = {
1330
1398
  :custom-rules="ageRules"
1331
1399
  show-success-messages
1332
1400
  class="mb-4"
1401
+ aria-describedby="age-rule"
1333
1402
  />
1334
1403
  </div>
1335
1404
 
1336
1405
  <div class="text-caption mb-4">
1337
1406
  <strong>Règles de validation :</strong>
1338
1407
  <ul>
1339
- <li>Nom : Champ requis</li>
1340
- <li>Prénom : Minimum 3 caractères</li>
1341
- <li>Âge : Uniquement des chiffres</li>
1408
+ <li id="nom-rule">Nom : Champ requis</li>
1409
+ <li id="prenom-rule">Prénom : Minimum 3 caractères</li>
1410
+ <li id="age-rule">Âge : Uniquement des chiffres</li>
1342
1411
  </ul>
1343
1412
  </div>
1344
1413
 
@@ -1542,6 +1611,7 @@ export const WithoutSuccessMessages: Story = {
1542
1611
  <SyTextField
1543
1612
  v-model="value1"
1544
1613
  v-bind="args"
1614
+ autocomplete="email"
1545
1615
  showSuccessMessages
1546
1616
  />
1547
1617
  </div>
@@ -1551,6 +1621,7 @@ export const WithoutSuccessMessages: Story = {
1551
1621
  <SyTextField
1552
1622
  v-model="value2"
1553
1623
  v-bind="args"
1624
+ autocomplete="email"
1554
1625
  :showSuccessMessages="false"
1555
1626
  />
1556
1627
  </div>
@@ -1583,6 +1654,7 @@ export const WithoutSuccessMessages: Story = {
1583
1654
  <SyTextField
1584
1655
  v-model="email"
1585
1656
  label="Email"
1657
+ autocomplete="email"
1586
1658
  :custom-rules="[{
1587
1659
  type: 'matchPattern',
1588
1660
  options: {
@@ -1597,6 +1669,7 @@ export const WithoutSuccessMessages: Story = {
1597
1669
  <SyTextField
1598
1670
  v-model="email"
1599
1671
  label="Email"
1672
+ autocomplete="email"
1600
1673
  :custom-rules="[{
1601
1674
  type: 'matchPattern',
1602
1675
  options: {