@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
@@ -3,3 +3,11 @@ export interface IndexedObject<T = string> {
3
3
  }
4
4
 
5
5
  export type ValidateOnType = 'lazy' | ('input' | 'blur' | 'submit') | 'input lazy' | 'blur lazy' | 'submit lazy' | 'lazy input' | 'lazy blur' | 'lazy submit' | undefined
6
+
7
+ export interface IDataListItem {
8
+ id: number
9
+ [key: string]: string | number | undefined
10
+ accordionTitle?: string
11
+ }
12
+
13
+ export type Breakpoints = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'smAndDown' | 'smAndUp' | 'mdAndDown' | 'mdAndUp' | 'lgAndDown' | 'lgAndUp' | 'xlAndDown'
@@ -2,7 +2,6 @@
2
2
  import { computed } from 'vue'
3
3
  import { useDisplay } from 'vuetify'
4
4
  import type { ListItem } from './types'
5
- import { vLetterSpacing } from '@/directives/letterSpacing'
6
5
 
7
6
  const props = defineProps<{
8
7
  listTitle: string | null
@@ -57,7 +56,6 @@
57
56
  class="vd-collapse-list"
58
57
  >
59
58
  <h4
60
- v-letter-spacing
61
59
  class="text-subtitle-1 font-weight-bold mb-3"
62
60
  >
63
61
  {{ listTitle }}
@@ -9,7 +9,6 @@
9
9
  import { config } from './config'
10
10
  import { locales } from './locales'
11
11
  import CookiesSelection from '../CookiesSelection/CookiesSelection.vue'
12
- import { vLetterSpacing } from '@/directives/letterSpacing'
13
12
 
14
13
  const props = defineProps<CustomizableOptions & {
15
14
  items?: CookiesItems
@@ -167,7 +166,6 @@
167
166
  >
168
167
  <VSheet
169
168
  ref="vsheetRef"
170
- v-letter-spacing
171
169
  v-bind="options.banner"
172
170
  :aria-label="locales.label"
173
171
  class="vd-cookie-banner"
@@ -179,7 +177,7 @@
179
177
  >
180
178
  <div class="d-flex align-start flex-nowrap pa-0 mb-6">
181
179
  <h2
182
- v-letter-spacing
180
+
183
181
  class="text-h5 font-weight-bold"
184
182
  >
185
183
  {{ locales.title }}
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import { ref } from 'vue'
2
+ import { ref, onUnmounted } from 'vue'
3
3
  import { mdiContentCopy } from '@mdi/js'
4
4
 
5
5
  import useCustomizableOptions, { type CustomizableOptions } from '@/composables/useCustomizableOptions'
@@ -28,6 +28,13 @@
28
28
 
29
29
  const tooltip = ref(false)
30
30
  const copyIcon = mdiContentCopy
31
+ let tooltipTimeoutId: ReturnType<typeof setTimeout> | undefined
32
+
33
+ onUnmounted(() => {
34
+ if (tooltipTimeoutId !== undefined) {
35
+ clearTimeout(tooltipTimeoutId)
36
+ }
37
+ })
31
38
 
32
39
  function copy(): void {
33
40
  let contentToCopy
@@ -66,7 +73,7 @@
66
73
  return
67
74
  }
68
75
 
69
- setTimeout(() => {
76
+ tooltipTimeoutId = setTimeout(() => {
70
77
  tooltip.value = false
71
78
  }, props.tooltipDuration)
72
79
  }
@@ -66,6 +66,7 @@ describe('CopyBtn', () => {
66
66
  propsData: {
67
67
  label: 'test',
68
68
  textToCopy: 'test',
69
+ tooltipDuration: 100,
69
70
  },
70
71
  global: {
71
72
  plugins: [vuetify],
@@ -76,7 +77,8 @@ describe('CopyBtn', () => {
76
77
 
77
78
  expect(wrapper.vm.tooltip).toBeTruthy()
78
79
 
79
- vi.runAllTimers()
80
+ vi.advanceTimersByTime(101) // tooltipDuration + 1ms pour être sûr
81
+
80
82
  expect(wrapper.vm.tooltip).toBeFalsy()
81
83
  })
82
84
 
@@ -82,9 +82,16 @@ import AccessibilityIcon from '@/common/imgs/accessibility-svgrepo-com.svg';
82
82
  </div>
83
83
  </div>
84
84
 
85
+ <div className="accessibility-guide">
86
+ Accessibilité
87
+ =============
88
+ <Story of={AccessStories.Legende} />
85
89
  <br />
90
+
91
+ --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
86
92
  <Story of={AccessStories.AccessibilitePanel} />
87
93
  <br />
94
+ </div>
88
95
 
89
96
  <style>
90
97
  {
@@ -277,7 +277,10 @@ export const Legende: StoryObj = {
277
277
  <p style="color: grey; font-size: 14px">Correctifs associés (<a
278
278
  href="https://github.com/assurance-maladie-digital/design-system-v3/issues/787" target="_blank"
279
279
  style="color:#0C41BD;"
280
- >issue #787</a>)</p>
280
+ >issue #787</a>, <a
281
+ href="https://github.com/assurance-maladie-digital/design-system-v3/issues/931" target="_blank"
282
+ style="color:#0C41BD;"
283
+ >issue #931</a>)</p>
281
284
  </div>
282
285
  </div>
283
286
  `,
@@ -55,6 +55,10 @@ const meta: Meta<typeof SySelect> = {
55
55
  control: 'text',
56
56
  description: 'Permet de définir une largeur personnalisée pour le champ de sélection (en px)',
57
57
  },
58
+ helpText: {
59
+ control: 'text',
60
+ description: 'Texte d\'aide à la saisie',
61
+ },
58
62
  },
59
63
  } as Meta<typeof SySelect>
60
64
 
@@ -131,6 +135,80 @@ export const Default: Story = {
131
135
  },
132
136
  }
133
137
 
138
+ export const HelpText: Story = {
139
+ parameters: {
140
+ sourceCode: [
141
+ {
142
+ name: 'Template',
143
+ code: `
144
+ <template>
145
+ <SySelect
146
+ v-model="value"
147
+ :items="items"
148
+ help-text="Texte d'aide à la saisie"
149
+ />
150
+ </template>
151
+ `,
152
+ },
153
+ {
154
+ name: 'Script',
155
+ code: `
156
+ <script setup lang="ts">
157
+ import SySelect from '@cnamts/SySelect'
158
+
159
+ const items = [
160
+ { text: 'Adrien', value: 'Adrien' },
161
+ { text: 'Axel', value: 'Axel' },
162
+ { text: 'Baptiste', value: 'Baptiste' },
163
+ { text: 'Clement', value: 'Clement' },
164
+ { text: 'Corentin', value: 'Corentin' },
165
+ { text: 'Damien', value: 'Damien' },
166
+ { text: 'David', value: 'David' },
167
+ { text: 'Eloi', value: 'Eloi' },
168
+ { text: 'Louis', value: 'Louis' },
169
+ { text: 'Valentin', value: 'Valentin' },
170
+ ],
171
+ </script>
172
+ `,
173
+ },
174
+ ],
175
+ },
176
+ args: {
177
+ 'items': [
178
+ { text: 'Adrien', value: 'Adrien' },
179
+ { text: 'Axel', value: 'Axel' },
180
+ { text: 'Baptiste', value: 'Baptiste' },
181
+ { text: 'Clement', value: 'Clement' },
182
+ { text: 'Corentin', value: 'Corentin' },
183
+ { text: 'Damien', value: 'Damien' },
184
+ { text: 'David', value: 'David' },
185
+ { text: 'Eloi', value: 'Eloi' },
186
+ { text: 'Louis', value: 'Louis' },
187
+ { text: 'Valentin', value: 'Valentin' },
188
+ ],
189
+ 'helpText': 'Texte d\'aide à la saisie',
190
+ 'hideMessages': false,
191
+ 'required': true,
192
+ 'onUpdate:modelValue': fn(),
193
+ },
194
+ render: (args) => {
195
+ return {
196
+ components: { SySelect, VBtn, VMenu, VList, VListItem, VListItemTitle },
197
+ setup() {
198
+ return { args }
199
+ },
200
+ template: `
201
+ <div class="pa-4">
202
+ <SySelect
203
+ v-bind="args"
204
+ />
205
+ </div>
206
+ <br/><br/><br/><br/>
207
+ `,
208
+ }
209
+ },
210
+ }
211
+
134
212
  export const Required: Story = {
135
213
  parameters: {
136
214
  sourceCode: [
@@ -138,6 +216,7 @@ export const Required: Story = {
138
216
  name: 'Template',
139
217
  code: `
140
218
  <template>
219
+ <p class="mb-2 text-caption text-grey-darken-2">Ce champ est obligatoire</p>
141
220
  <SySelect
142
221
  v-model="value"
143
222
  :items="items"
@@ -177,6 +256,7 @@ export const Required: Story = {
177
256
  },
178
257
  template: `
179
258
  <div class="pa-4">
259
+ <p class="mb-2 text-caption text-grey-darken-2">Ce champ est obligatoire</p>
180
260
  <SySelect
181
261
  v-bind="args"
182
262
  :required="args.required"
@@ -108,6 +108,10 @@
108
108
  type: Boolean,
109
109
  default: false,
110
110
  },
111
+ helpText: {
112
+ type: String,
113
+ default: '',
114
+ },
111
115
  })
112
116
 
113
117
  const emit = defineEmits(['update:modelValue'])
@@ -120,22 +124,24 @@
120
124
  const labelWidth = ref(0)
121
125
  const labelRef = ref<HTMLElement | null>(null)
122
126
 
123
- const toggleMenu = () => {
127
+ const toggleMenu = (skipInitialFocus = false) => {
124
128
  if (props.readonly) return
125
129
  isOpen.value = !isOpen.value
126
130
  if (isOpen.value) {
127
131
  updateListPosition()
128
- // Initialiser la sélection à l'ouverture
129
- nextTick(() => {
130
- // Si un élément est déjà sélectionné, l'activer
131
- const selectedIndex = formattedItems.value.findIndex(item => isItemSelected(item))
132
- if (selectedIndex >= 0) {
133
- setActiveDescendant(selectedIndex)
134
- }
135
- else {
136
- setActiveDescendant(0)
137
- }
138
- })
132
+ // Initialiser la sélection à l'ouverture seulement si pas ouvert via clavier
133
+ if (!skipInitialFocus) {
134
+ nextTick(() => {
135
+ // Si un élément est déjà sélectionné, l'activer
136
+ const selectedIndex = formattedItems.value.findIndex(item => isItemSelected(item))
137
+ if (selectedIndex >= 0) {
138
+ setActiveDescendant(selectedIndex)
139
+ }
140
+ else {
141
+ setActiveDescendant(0)
142
+ }
143
+ })
144
+ }
139
145
  }
140
146
  }
141
147
 
@@ -152,6 +158,8 @@
152
158
  isOpen.value = false
153
159
  }
154
160
  const inputId = ref(`sy-select-${Math.random().toString(36).substring(7)}`)
161
+ // Generate unique menu ID for each component instance to avoid conflicts and validation issues
162
+ const uniqueMenuId = ref(props.menuId === 'sy-select-menu' ? `sy-select-menu-${Math.random().toString(36).substring(7)}` : props.menuId)
155
163
  const listStyles = ref<Record<string, string>>({})
156
164
  const updateListPosition = () => {
157
165
  if (input.value?.$el) {
@@ -357,6 +365,23 @@
357
365
 
358
366
  const input = ref<InstanceType<typeof VTextField> | null>(null)
359
367
 
368
+ // Détecte s'il y a des messages d'erreur, de succès ou d'avertissement
369
+ const hasMessages = computed(() => {
370
+ if (props.disableErrorHandling) return false
371
+ return props.errorMessages.length > 0 || hasError.value
372
+ })
373
+
374
+ // Détermine si le helpText doit être affiché à la position du message ou en dessous
375
+ const showHelpTextAsMessage = computed(() => {
376
+ // Afficher à la position du message si pas de messages d'erreur
377
+ return props.helpText && !hasMessages.value
378
+ })
379
+
380
+ const showHelpTextBelow = computed(() => {
381
+ // Afficher en dessous si il y a des messages d'erreur ET hideMessages n'est pas activé
382
+ return props.helpText && hasMessages.value && !props.hideMessages
383
+ })
384
+
360
385
  const calculatedWidth = computed(() => {
361
386
  const baseWidth = props.width ? Number(props.width) : 0
362
387
  const selectedText = typeof selectedItemText.value === 'string' ? selectedItemText.value : ''
@@ -378,6 +403,11 @@
378
403
  handleUpKey,
379
404
  handleCharacterKey,
380
405
  handleEscapeKey,
406
+ handleHomeKey,
407
+ handleEndKey,
408
+ handlePageUpKey,
409
+ handlePageDownKey,
410
+ handleTabKey,
381
411
  restoreFocus,
382
412
  } = useSySelectKeyboard({
383
413
  isOpen,
@@ -505,6 +535,59 @@
505
535
  }
506
536
  })
507
537
 
538
+ let mutationObserver: MutationObserver | null = null
539
+
540
+ // Function to set up proper ARIA attributes
541
+ const setupAriaAttributes = () => {
542
+ if (input.value && input.value.$el) {
543
+ // Find the input element
544
+ const inputElement = input.value.$el?.querySelector?.('input')
545
+ if (inputElement) {
546
+ // Remove problematic attributes that shouldn't be on input
547
+ inputElement.removeAttribute('aria-describedby')
548
+ inputElement.removeAttribute('size')
549
+ inputElement.removeAttribute('tabindex')
550
+ inputElement.removeAttribute('aria-hidden')
551
+
552
+ // Set proper combobox attributes on input element (following CNSA standard)
553
+ inputElement.setAttribute('role', 'combobox')
554
+ inputElement.setAttribute('aria-expanded', isOpen.value ? 'true' : 'false')
555
+ // Only set aria-controls when menu is open and element exists
556
+ if (isOpen.value) {
557
+ inputElement.setAttribute('aria-controls', uniqueMenuId.value)
558
+ }
559
+ else {
560
+ inputElement.removeAttribute('aria-controls')
561
+ }
562
+ // Note: aria-autocomplete is omitted for select-only combobox (invalid to set to 'none')
563
+ inputElement.setAttribute('aria-haspopup', 'listbox')
564
+ if (isOpen.value && activeDescendantId.value) {
565
+ inputElement.setAttribute('aria-activedescendant', activeDescendantId.value)
566
+ }
567
+ if (isRequired.value) {
568
+ inputElement.setAttribute('aria-required', 'true')
569
+ }
570
+ if (hasError.value) {
571
+ inputElement.setAttribute('aria-invalid', 'true')
572
+ }
573
+ }
574
+
575
+ // Clean up parent element - remove any conflicting attributes
576
+ const parentElement = input.value.$el
577
+ if (parentElement) {
578
+ // Remove any role or ARIA attributes from parent that should be on input
579
+ parentElement.removeAttribute('role')
580
+ parentElement.removeAttribute('aria-expanded')
581
+ parentElement.removeAttribute('aria-controls')
582
+ parentElement.removeAttribute('aria-haspopup')
583
+ parentElement.removeAttribute('aria-activedescendant')
584
+ parentElement.removeAttribute('aria-required')
585
+ parentElement.removeAttribute('aria-invalid')
586
+ parentElement.removeAttribute('aria-hidden')
587
+ }
588
+ }
589
+ }
590
+
508
591
  onMounted(() => {
509
592
  if (labelRef.value) {
510
593
  labelWidth.value = labelRef.value.offsetWidth + 64
@@ -513,25 +596,158 @@
513
596
  window.addEventListener('resize', updateListPosition)
514
597
 
515
598
  // Use nextTick to ensure the DOM is fully rendered
599
+ nextTick(() => {
600
+ // Initial setup
601
+ setupAriaAttributes()
602
+
603
+ // Set up MutationObserver to monitor attribute changes
604
+ if (input.value && input.value.$el) {
605
+ mutationObserver = new MutationObserver((mutations) => {
606
+ let needsCleanup = false
607
+ mutations.forEach((mutation) => {
608
+ if (mutation.type === 'attributes') {
609
+ const target = mutation.target as HTMLElement
610
+ const attributeName = mutation.attributeName
611
+
612
+ // Check if problematic attributes were added to input
613
+ if (target.tagName === 'INPUT' && (
614
+ attributeName === 'role'
615
+ || attributeName === 'aria-hidden'
616
+ || attributeName === 'aria-expanded'
617
+ || attributeName === 'aria-controls'
618
+ || attributeName === 'aria-haspopup'
619
+ )) {
620
+ needsCleanup = true
621
+ }
622
+
623
+ // Check if aria-hidden was added to parent
624
+ if (target === input.value?.$el && attributeName === 'aria-hidden') {
625
+ needsCleanup = true
626
+ }
627
+ }
628
+ })
629
+
630
+ if (needsCleanup) {
631
+ // Use setTimeout to avoid infinite loops
632
+ setTimeout(setupAriaAttributes, 0)
633
+ }
634
+ })
635
+
636
+ // Observe both the parent element and its children
637
+ mutationObserver.observe(input.value.$el, {
638
+ attributes: true,
639
+ subtree: true,
640
+ attributeFilter: ['role', 'aria-hidden', 'aria-expanded', 'aria-controls', 'aria-haspopup'],
641
+ })
642
+ }
643
+ })
644
+ })
645
+
646
+ // Watchers to update ARIA attributes dynamically on input element
647
+ watch(isOpen, (newValue) => {
516
648
  nextTick(() => {
517
649
  if (input.value && input.value.$el) {
518
- // Find the input element
519
- const inputElement = input.value.$el.querySelector('input')
650
+ const inputElement = input.value.$el?.querySelector?.('input')
520
651
  if (inputElement) {
521
- // Remove the aria-describedby attribute
522
- inputElement.removeAttribute('aria-describedby')
523
- // fix le critere RGAA 10.1 : Dans le site web, des feuilles de styles sont-elles utilisées pour contrôler la présentation de l'information?
524
- inputElement.removeAttribute('size')
525
- // Remove any tabindex from the input element to prevent it from being tabbable
526
- inputElement.removeAttribute('tabindex')
652
+ inputElement.setAttribute('aria-expanded', newValue ? 'true' : 'false')
653
+ if (newValue && activeDescendantId.value) {
654
+ inputElement.setAttribute('aria-activedescendant', activeDescendantId.value)
655
+ }
656
+ else {
657
+ inputElement.removeAttribute('aria-activedescendant')
658
+ }
527
659
  }
528
660
  }
529
661
  })
530
662
  })
531
663
 
664
+ watch(activeDescendantId, (newValue) => {
665
+ nextTick(() => {
666
+ if (input.value && input.value.$el && isOpen.value) {
667
+ const inputElement = input.value.$el?.querySelector?.('input')
668
+ if (inputElement) {
669
+ if (newValue) {
670
+ inputElement.setAttribute('aria-activedescendant', newValue)
671
+ }
672
+ else {
673
+ inputElement.removeAttribute('aria-activedescendant')
674
+ }
675
+ }
676
+ }
677
+ })
678
+ })
679
+
680
+ watch(hasError, (newValue) => {
681
+ nextTick(() => {
682
+ if (input.value && input.value.$el) {
683
+ const inputElement = input.value.$el?.querySelector?.('input')
684
+ if (inputElement) {
685
+ if (newValue) {
686
+ inputElement.setAttribute('aria-invalid', 'true')
687
+ }
688
+ else {
689
+ inputElement.removeAttribute('aria-invalid')
690
+ }
691
+ }
692
+ }
693
+ })
694
+ })
695
+
696
+ // Watch for selection changes to enforce correct accessibility attributes
697
+ // This prevents Vuetify from overriding our combobox attributes
698
+ watch(selectedItem, () => {
699
+ nextTick(() => {
700
+ if (input.value && input.value.$el) {
701
+ const inputElement = input.value.$el?.querySelector?.('input')
702
+ if (inputElement) {
703
+ // Ensure combobox role is maintained on input
704
+ inputElement.setAttribute('role', 'combobox')
705
+ // Ensure aria-hidden is never set to true
706
+ inputElement.removeAttribute('aria-hidden')
707
+ // Maintain other combobox attributes
708
+ inputElement.setAttribute('aria-expanded', isOpen.value ? 'true' : 'false')
709
+ inputElement.setAttribute('aria-haspopup', 'listbox')
710
+ // Note: aria-autocomplete is omitted for select-only combobox
711
+ // Only set aria-controls when menu is open and element exists
712
+ if (isOpen.value) {
713
+ inputElement.setAttribute('aria-controls', uniqueMenuId.value)
714
+ }
715
+ else {
716
+ inputElement.removeAttribute('aria-controls')
717
+ }
718
+
719
+ // Only add aria-required if the component is actually required
720
+ if (isRequired.value) {
721
+ inputElement.setAttribute('aria-required', 'true')
722
+ }
723
+ else {
724
+ inputElement.removeAttribute('aria-required')
725
+ }
726
+ }
727
+
728
+ // Clean up parent element
729
+ const parentElement = input.value.$el
730
+ if (parentElement) {
731
+ parentElement.removeAttribute('role')
732
+ parentElement.removeAttribute('aria-hidden')
733
+ parentElement.removeAttribute('aria-expanded')
734
+ parentElement.removeAttribute('aria-haspopup')
735
+ parentElement.removeAttribute('aria-controls')
736
+ parentElement.removeAttribute('aria-required')
737
+ }
738
+ }
739
+ })
740
+ }, { deep: true })
741
+
532
742
  onUnmounted(() => {
533
743
  window.removeEventListener('scroll', updateListPosition, true)
534
744
  window.removeEventListener('resize', updateListPosition)
745
+
746
+ // Clean up MutationObserver
747
+ if (mutationObserver) {
748
+ mutationObserver.disconnect()
749
+ mutationObserver = null
750
+ }
535
751
  })
536
752
 
537
753
  defineExpose({
@@ -549,27 +765,19 @@
549
765
  v-rgaa-svg-fix="true"
550
766
  :title="$attrs['aria-label'] || labelWithAsterisk"
551
767
  color="primary"
552
- role="combobox"
553
768
  :disabled="disabled"
554
769
  :label="labelWithAsterisk"
555
770
  :aria-label="$attrs['aria-label'] || labelWithAsterisk"
556
- :aria-expanded="isOpen ? 'true' : 'false'"
557
- :aria-controls="menuId"
558
- aria-autocomplete="list"
559
- aria-haspopup="listbox"
560
- aria-readonly="true"
561
- :aria-owns="menuId"
562
- :aria-activedescendant="isOpen ? activeDescendantId : undefined"
563
771
  :error-messages="props.disableErrorHandling ? [] : errorMessages"
564
772
  :variant="outlined ? 'outlined' : 'underlined'"
565
773
  :rules="isRequired && !props.disableErrorHandling ? ['Le champ est requis.'] : []"
566
- :aria-required="isRequired ? 'true' : undefined"
567
- :aria-invalid="hasError ? 'true' : undefined"
568
774
  :bg-color="props.bgColor"
569
775
  :density="props.density"
570
776
  :active="hasChips || isOpen"
571
777
  readonly
572
- :hide-details="props.hideMessages"
778
+ :hide-details="props.hideMessages && !showHelpTextAsMessage"
779
+ :hint="showHelpTextAsMessage ? props.helpText : ''"
780
+ :persistent-hint="!!showHelpTextAsMessage"
573
781
  class="sy-select"
574
782
  :width="calculatedWidth"
575
783
  :style="hasError ? { minWidth: `${labelWidth + 18}px`} : {minWidth: `${labelWidth}px`}"
@@ -580,6 +788,11 @@
580
788
  @keydown.down.prevent="handleDownKey"
581
789
  @keydown.up.prevent="handleUpKey"
582
790
  @keydown.esc.prevent="handleEscapeKey"
791
+ @keydown.home.prevent="handleHomeKey"
792
+ @keydown.end.prevent="handleEndKey"
793
+ @keydown.page-up.prevent="handlePageUpKey"
794
+ @keydown.page-down.prevent="handlePageDownKey"
795
+ @keydown.tab="handleTabKey"
583
796
  @keydown="(e) => {
584
797
  // Handle printable characters for keyboard navigation
585
798
  if (!e.ctrlKey && !e.altKey && !e.metaKey) {
@@ -607,6 +820,7 @@
607
820
  <SyIcon
608
821
  v-if="hasError"
609
822
  class="mr-6"
823
+ color="error"
610
824
  :icon="mdiInformation"
611
825
  :decorative="false"
612
826
  label="Information"
@@ -641,7 +855,7 @@
641
855
  >{{ label }}</span>
642
856
  <VList
643
857
  v-if="isOpen"
644
- :id="menuId"
858
+ :id="uniqueMenuId"
645
859
  class="v-list"
646
860
  role="listbox"
647
861
  :aria-multiselectable="props.multiple ? 'true' : undefined"
@@ -654,10 +868,14 @@
654
868
  tabindex="0"
655
869
  :title="props.multiple ? 'Sélection multiple' : 'Sélection'"
656
870
  @keydown.esc.prevent="closeList"
657
- @keydown.tab.prevent="closeList"
871
+ @keydown.tab="handleTabKey"
658
872
  @keydown.enter.prevent="handleEnterKey"
659
873
  @keydown.down.prevent="handleDownKey"
660
874
  @keydown.up.prevent="handleUpKey"
875
+ @keydown.home.prevent="handleHomeKey"
876
+ @keydown.end.prevent="handleEndKey"
877
+ @keydown.page-up.prevent="handlePageUpKey"
878
+ @keydown.page-down.prevent="handlePageDownKey"
661
879
  @click.stop
662
880
  >
663
881
  <VListItem
@@ -667,7 +885,7 @@
667
885
  :ref="'options-' + index"
668
886
  role="option"
669
887
  class="v-list-item"
670
- :aria-selected="(isItemSelected(item) || `option-${index}` === activeDescendantId) ? 'true' : 'false'"
888
+ :aria-selected="isItemSelected(item) ? 'true' : 'false'"
671
889
  tabindex="-1"
672
890
  :class="{ active: isItemSelected(item) || `option-${index}` === activeDescendantId }"
673
891
  @click.stop="(event) => selectItem(item, event)"
@@ -692,6 +910,14 @@
692
910
  </VListItemTitle>
693
911
  </VListItem>
694
912
  </VList>
913
+
914
+ <div
915
+ v-if="showHelpTextBelow"
916
+ class="help-text-below px-4 mt-1"
917
+ :class="{ 'text-disabled': props.disabled }"
918
+ >
919
+ {{ props.helpText }}
920
+ </div>
695
921
  </template>
696
922
 
697
923
  <style scoped lang="scss">
@@ -734,6 +960,26 @@
734
960
  background-color: rgb(0 0 0 / 8%);
735
961
  }
736
962
 
963
+ .help-text {
964
+ color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity));
965
+ font-size: 14px;
966
+ line-height: 1.2;
967
+ }
968
+
969
+ .help-text.text-disabled {
970
+ color: rgba(var(--v-theme-on-surface), var(--v-disabled-opacity));
971
+ }
972
+
973
+ .help-text-below {
974
+ color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity));
975
+ font-size: 14px;
976
+ line-height: 1.2;
977
+ }
978
+
979
+ .help-text-below.text-disabled {
980
+ color: rgba(var(--v-theme-on-surface), var(--v-disabled-opacity));
981
+ }
982
+
737
983
  /* Ensure focus styles match selection styles for keyboard navigation */
738
984
  .v-list-item:focus-visible,
739
985
  .v-list-item.keyboard-focused {