@cnamts/synapse 1.0.22 → 1.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{DateFilter-B5n-ZkLi.js → DateFilter-Dc-gSGwk.js} +1 -1
- package/dist/{NumberFilter-CtiZ9uj8.js → NumberFilter-vP38Wp6j.js} +1 -1
- package/dist/{PeriodFilter-DzqiMb-b.js → PeriodFilter-Ba1uYUnT.js} +1 -1
- package/dist/{SelectFilter-BOYlF7rX.js → SelectFilter-BioGT6Nn.js} +1 -1
- package/dist/{TextFilter-BOFRNfcX.js → TextFilter-B84dpnoq.js} +1 -1
- package/dist/components/Accordion/Accordion.d.ts +13 -2
- package/dist/components/Accordion/composables/useAccordionState.d.ts +2 -1
- package/dist/components/Amelipro/AmeliproAutoCompleteField/AmeliproAutoCompleteField.d.ts +7 -7
- package/dist/components/Amelipro/AmeliproCheckbox/AmeliproCheckbox.d.ts +1 -1
- package/dist/components/Amelipro/AmeliproCustomSelector/AmeliproCustomSelector.d.ts +1 -1
- package/dist/components/Amelipro/AmeliproPostalAddressField/AmeliproPostalAddressField.d.ts +1 -1
- package/dist/components/Amelipro/AmeliproSelect/AmeliproSelect.d.ts +7 -7
- package/dist/components/Amelipro/AmeliproTabs/AmeliproTabs.d.ts +16 -16
- package/dist/components/Amelipro/AmeliproTextArea/AmeliproTextArea.d.ts +1 -1
- package/dist/components/Amelipro/AmeliproTextField/AmeliproTextField.d.ts +1 -1
- package/dist/components/Customs/Selects/SyAutocomplete/SyAutocomplete.d.ts +22 -1
- package/dist/components/Customs/Selects/SyAutocomplete/locales.d.ts +5 -0
- package/dist/components/Customs/Selects/SyInputSelect/SyInputSelect.d.ts +1 -1
- package/dist/components/Customs/Selects/SySelect/SySelect.d.ts +1 -1
- package/dist/components/Customs/Selects/SySelect/locales.d.ts +1 -0
- package/dist/components/Customs/SyCheckBoxGroup/SyCheckBoxGroup.d.ts +1 -1
- package/dist/components/Customs/SyCheckbox/SyCheckbox.d.ts +1 -1
- package/dist/components/Customs/SyRadioGroup/SyRadioGroup.d.ts +1 -1
- package/dist/components/Customs/SyTextField/SyTextField.d.ts +5 -2
- package/dist/components/DatePicker/CalendarMode/DatePicker.d.ts +13 -9
- package/dist/components/DatePicker/ComplexDatePicker/ComplexDatePicker.d.ts +7 -5
- package/dist/components/DatePicker/DateTextInput/DateTextInput.d.ts +2 -1
- package/dist/components/ErrorPage/ErrorPage.d.ts +3 -1
- package/dist/components/FileList/UploadItem/UploadItem.d.ts +6 -0
- package/dist/components/FileList/UploadItem/locales.d.ts +1 -4
- package/dist/components/FileUpload/FileUploadContent.d.ts +2 -0
- package/dist/components/FileUpload/validateFiles.d.ts +2 -1
- package/dist/components/HeaderBar/HeaderBar.d.ts +2 -1
- package/dist/components/HeaderBar/HeaderLogo/HeaderLogo.d.ts +2 -1
- package/dist/components/HeaderNavigationBar/HeaderNavigationBar.d.ts +2 -1
- package/dist/components/MonthPicker/MonthPicker.d.ts +1939 -0
- package/dist/components/MonthPicker/MonthPickerText/MonthPickerInput.d.ts +1899 -0
- package/dist/components/MonthPicker/MonthPickerText/useTextField.d.ts +21 -0
- package/dist/components/MonthPicker/MonthPickerVisual/MonthPickerVisual.d.ts +21 -0
- package/dist/components/MonthPicker/MonthPickerVisual/MonthPickerVisualProps.d.ts +12 -0
- package/dist/components/MonthPicker/MonthPickerVisual/MonthSelector.d.ts +11 -0
- package/dist/components/MonthPicker/MonthPickerVisual/VisualPickerFooter.d.ts +6 -0
- package/dist/components/MonthPicker/MonthPickerVisual/VisualPickerHeader.d.ts +14 -0
- package/dist/components/MonthPicker/MonthPickerVisual/YearSelector.d.ts +14 -0
- package/dist/components/MonthPicker/MonthPickerVisual/useMonthGrid.d.ts +9 -0
- package/dist/components/MonthPicker/MonthPickerVisual/useYearGrid.d.ts +8 -0
- package/dist/components/MonthPicker/MonthPickerVisual/utils.d.ts +8 -0
- package/dist/components/MonthPicker/locales.d.ts +12 -0
- package/dist/components/MonthPicker/useMonthPickerValidation.d.ts +25 -0
- package/dist/components/NirField/NirField.d.ts +3 -1
- package/dist/components/NotificationBar/Notification/Notification.d.ts +3 -0
- package/dist/components/PasswordField/PasswordField.d.ts +1 -1
- package/dist/components/PeriodField/PeriodField.d.ts +29 -21
- package/dist/components/PhoneField/PhoneField.d.ts +2 -1
- package/dist/components/SyBtnMenu/SyBtnMenu.d.ts +1 -1
- package/dist/components/SyHeading/SyHeading.a11y.test.d.ts +1 -0
- package/dist/components/SyHeading/SyHeading.d.ts +4 -2
- package/dist/components/SyHeading/SyHeading.test.d.ts +1 -0
- package/dist/components/SyTextArea/SyTextArea.d.ts +1 -1
- package/dist/components/Tables/SyServerTable/SyServerTable.d.ts +4 -4
- package/dist/components/Tables/SyTable/SyTable.d.ts +4 -4
- package/dist/components/Tables/common/SyTablePagination.d.ts +6 -6
- package/dist/components/index.d.ts +1 -0
- package/dist/design-system-v3.js +102 -99
- package/dist/design-system-v3.umd.cjs +126 -126
- package/dist/designTokens/tokens/cnam/cnamContextual.d.ts +5 -0
- package/dist/{main-CEl4J8_T.js → main-aLKwdMi1.js} +11167 -10522
- package/dist/main.d.ts +1 -0
- package/dist/style.css +1 -1
- package/package.json +10 -4
- package/src/assets/apTokens.scss +2 -2
- package/src/assets/overrides/_btns.scss +8 -0
- package/src/assets/overrides/_forms.scss +9 -0
- package/src/assets/overrides/_icons.scss +38 -9
- package/src/assets/overrides/_tables.scss +19 -0
- package/src/components/Accordion/Accordion.mdx +23 -9
- package/src/components/Accordion/Accordion.stories.ts +153 -3
- package/src/components/Accordion/Accordion.vue +7 -6
- package/src/components/Accordion/composables/__tests__/useAccordionState.spec.ts +40 -12
- package/src/components/Accordion/composables/useAccordionState.ts +3 -4
- package/src/components/Accordion/tests/accordion.spec.ts +131 -19
- package/src/components/Amelipro/AmeliproPagination/AmeliproPagination.mdx +3 -1
- package/src/components/Amelipro/AmeliproPagination/AmeliproPagination.stories.ts +8 -0
- package/src/components/BackBtn/accessibilite/Accessibility.mdx +62 -10
- package/src/components/BackToTopBtn/BackToTopBtn.stories.ts +9 -3
- package/src/components/BackToTopBtn/accessibilite/Accessibility.mdx +86 -6
- package/src/components/Captcha/tests/Captcha.spec.ts +0 -29
- package/src/components/Captcha/tests/__snapshots__/Captcha.spec.ts.snap +2 -110
- package/src/components/Customs/Selects/SelectBtnField/accessibilite/Accessibility.mdx +133 -10
- package/src/components/Customs/Selects/SyAutocomplete/SyAutocomplete.stories.ts +379 -93
- package/src/components/Customs/Selects/SyAutocomplete/SyAutocomplete.vue +144 -83
- package/src/components/Customs/Selects/SyAutocomplete/accessibilite/Accessibilite.stories.ts +40 -1
- package/src/components/Customs/Selects/SyAutocomplete/accessibilite/Accessibility.mdx +7 -1
- package/src/components/Customs/Selects/SyAutocomplete/locales.ts +5 -0
- package/src/components/Customs/Selects/SyAutocomplete/tests/SyAutocomplete.a11y.spec.ts +96 -0
- package/src/components/Customs/Selects/SyAutocomplete/tests/SyAutocomplete.spec.ts +234 -9
- package/src/components/Customs/Selects/SyAutocomplete/utils/ariaManager.ts +13 -3
- package/src/components/Customs/Selects/SyAutocomplete/utils/useSelectionLogic.ts +9 -10
- package/src/components/Customs/Selects/SySelect/SySelect.vue +46 -3
- package/src/components/Customs/Selects/SySelect/locales.ts +1 -0
- package/src/components/Customs/SyIcon/SyIcon.vue +1 -1
- package/src/components/Customs/SyIcon/tests/SyIcon.a11y.spec.ts +20 -0
- package/src/components/Customs/SyIconButton/SyIconButton.mdx +46 -0
- package/src/components/Customs/SyIconButton/SyIconButton.stories.ts +184 -0
- package/src/components/Customs/SyIconButton/SyIconButton.vue +38 -0
- package/src/components/Customs/SyIconButton/accessibilite/Accessibility.mdx +64 -0
- package/src/components/Customs/SyIconButton/tests/SyIconButton.a11y.spec.ts +87 -0
- package/src/components/Customs/SyIconButton/tests/SyIconButton.spec.ts +152 -0
- package/src/components/Customs/SyIconButton/tests/__snapshots__/SyIconButton.spec.ts.snap +61 -0
- package/src/components/Customs/SyPagination/SyPagination.vue +5 -5
- package/src/components/Customs/SyTextField/SyTextField.vue +20 -2
- package/src/components/Customs/SyTextField/accessibilite/Accessibility.mdx +67 -9
- package/src/components/Customs/SyTextField/tests/SyTextField.a11y.spec.ts +15 -0
- package/src/components/Customs/SyTextField/tests/SyTextField.spec.ts +36 -0
- package/src/components/DataList/accessibilite/Accessibility.mdx +79 -11
- package/src/components/DataListGroup/accessibilite/Accessibility.mdx +80 -11
- package/src/components/DownloadBtn/tests/DownloadBtn.a11y.spec.ts +25 -0
- package/src/components/ErrorPage/ErrorPage.stories.ts +113 -19
- package/src/components/ErrorPage/ErrorPage.vue +17 -2
- package/src/components/ErrorPage/tests/ErrorPage.a11y.spec.ts +17 -0
- package/src/components/ErrorPage/tests/ErrorPage.spec.ts +21 -1
- package/src/components/ErrorPage/tests/__snapshots__/ErrorPage.spec.ts.snap +0 -1
- package/src/components/ExternalLinks/tests/ExternalLinks.a11y.spec.ts +23 -0
- package/src/components/FileList/FileList.stories.ts +51 -1
- package/src/components/FileList/UploadItem/UploadItem.vue +13 -6
- package/src/components/FileList/UploadItem/locales.ts +3 -12
- package/src/components/FileList/accessibilite/Accessibility.mdx +3 -0
- package/src/components/FileUpload/FileUpload.vue +2 -1
- package/src/components/FileUpload/FileUploadContent.vue +2 -1
- package/src/components/FileUpload/tests/FileUpload.spec.ts +47 -0
- package/src/components/FileUpload/validateFiles.ts +5 -2
- package/src/components/FranceConnectBtn/accessibilite/Accessibility.mdx +62 -9
- package/src/components/HeaderBar/HeaderBar.vue +2 -1
- package/src/components/HeaderBar/HeaderLogo/HeaderLogo.vue +2 -1
- package/src/components/HeaderNavigationBar/HeaderNavigationBar.vue +2 -1
- package/src/components/LunarCalendar/accessibilite/Accessibility.mdx +74 -8
- package/src/components/LunarCalendar/tests/LunarCalendar.a11y.spec.ts +163 -0
- package/src/components/MaintenancePage/MaintenancePage.vue +1 -1
- package/src/components/MaintenancePage/tests/MaintenancePage.spec.ts +4 -5
- package/src/components/MaintenancePage/tests/__snapshots__/MaintenancePage.spec.ts.snap +0 -1
- package/src/components/MonthPicker/MonthPicker.mdx +35 -0
- package/src/components/MonthPicker/MonthPicker.stories.ts +527 -0
- package/src/components/MonthPicker/MonthPicker.vue +79 -0
- package/src/components/MonthPicker/MonthPickerText/MonthPickerInput.vue +89 -0
- package/src/components/MonthPicker/MonthPickerText/useTextField.ts +27 -0
- package/src/components/MonthPicker/MonthPickerVisual/MonthPickerVisual.vue +154 -0
- package/src/components/MonthPicker/MonthPickerVisual/MonthPickerVisualProps.ts +13 -0
- package/src/components/MonthPicker/MonthPickerVisual/MonthSelector.vue +137 -0
- package/src/components/MonthPicker/MonthPickerVisual/VisualPickerFooter.vue +60 -0
- package/src/components/MonthPicker/MonthPickerVisual/VisualPickerHeader.vue +149 -0
- package/src/components/MonthPicker/MonthPickerVisual/YearSelector.vue +143 -0
- package/src/components/MonthPicker/MonthPickerVisual/useMonthGrid.ts +45 -0
- package/src/components/MonthPicker/MonthPickerVisual/useYearGrid.ts +45 -0
- package/src/components/MonthPicker/MonthPickerVisual/utils.ts +17 -0
- package/src/components/MonthPicker/accessibilite/Accessibility.mdx +59 -0
- package/src/components/MonthPicker/locales.ts +12 -0
- package/src/components/MonthPicker/tests/MonthPicker.a11y.spec.ts +71 -0
- package/src/components/MonthPicker/tests/MonthPicker.spec.ts +1248 -0
- package/src/components/MonthPicker/tests/__snapshots__/MonthPicker.spec.ts.snap +2545 -0
- package/src/components/MonthPicker/useMonthPickerValidation.ts +30 -0
- package/src/components/NirField/NirField.mdx +1 -2
- package/src/components/NirField/NirField.stories.ts +66 -6
- package/src/components/NotFoundPage/tests/NotFoundPage.spec.ts +2 -3
- package/src/components/NotFoundPage/tests/__snapshots__/NotFoundPage.spec.ts.snap +22 -14
- package/src/components/NotificationBar/Notification/Notification.vue +3 -1
- package/src/components/NotificationBar/NotificationBar.stories.ts +154 -0
- package/src/components/NotificationBar/tests/NotificationBar.a11y.spec.ts +26 -0
- package/src/components/NotificationBar/tests/NotificationBar.spec.ts +60 -0
- package/src/components/RangeField/accessibilite/Accessibility.mdx +79 -11
- package/src/components/SkipLink/tests/SkipLink.a11y.spec.ts +23 -0
- package/src/components/StatusPage/StatusPage.stories.ts +118 -0
- package/src/components/StatusPage/StatusPage.vue +5 -3
- package/src/components/StatusPage/tests/StatusPage.a11y.spec.ts +22 -0
- package/src/components/StatusPage/tests/StatusPage.spec.ts +22 -0
- package/src/components/StatusPage/tests/__snapshots__/StatusPage.spec.ts.snap +22 -14
- package/src/components/SubHeader/tests/SubHeader.a11y.spec.ts +20 -0
- package/src/components/SyAlert/SyAlert.vue +1 -0
- package/src/components/SyAlert/accessibilite/Accessibility.mdx +79 -9
- package/src/components/SyAlert/tests/SyAlert.a11y.spec.ts +23 -0
- package/src/components/SyHeading/SyHeading.a11y.test.ts +149 -0
- package/src/components/SyHeading/SyHeading.test.ts +115 -0
- package/src/components/SyHeading/SyHeading.vue +5 -3
- package/src/components/SyTextArea/accessibilite/Accessibility.mdx +80 -8
- package/src/components/SyTextArea/tests/SyTextArea.a11y.spec.ts +151 -0
- package/src/components/ToolbarContainer/tests/ToolbarContainer.a11y.spec.ts +126 -0
- package/src/components/UploadWorkflow/tests/__snapshots__/UploadWorkflow.spec.ts.snap +2 -2
- package/src/components/index.ts +1 -0
- package/src/composables/useFormFieldErrorHandling.ts +11 -2
- package/src/designTokens/tokens/cnam/cnamContextual.ts +6 -1
- package/src/main.ts +2 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
2
2
|
import { mount, flushPromises } from '@vue/test-utils'
|
|
3
|
-
import { VMenu
|
|
3
|
+
import { VMenu } from 'vuetify/components'
|
|
4
4
|
|
|
5
5
|
import SyAutocomplete from '../SyAutocomplete.vue'
|
|
6
6
|
import SyTextField from '@/components/Customs/SyTextField/SyTextField.vue'
|
|
@@ -128,13 +128,13 @@ describe('SyAutocomplete', () => {
|
|
|
128
128
|
|
|
129
129
|
// Some environments can emit a follow-up input event with the previous DOM value.
|
|
130
130
|
// Ensure it doesn't re-populate the query after selection.
|
|
131
|
-
textField.vm.$emit('update:modelValue', '
|
|
131
|
+
textField.vm.$emit('update:modelValue', 'Opt')
|
|
132
132
|
await flushPromises()
|
|
133
133
|
await wrapper.vm.$nextTick()
|
|
134
134
|
|
|
135
|
-
//
|
|
135
|
+
// Search and input should be cleared; selected items render as inline labels, not in input value
|
|
136
136
|
expect(wrapper.vm.search).toBe('')
|
|
137
|
-
expect(getInputEl()!.value).toBe('
|
|
137
|
+
expect(getInputEl()!.value).toBe('')
|
|
138
138
|
})
|
|
139
139
|
|
|
140
140
|
it('displays chips in multiple mode', async () => {
|
|
@@ -150,12 +150,13 @@ describe('SyAutocomplete', () => {
|
|
|
150
150
|
textKey: 'text',
|
|
151
151
|
valueKey: 'value',
|
|
152
152
|
},
|
|
153
|
+
attachTo: document.body,
|
|
153
154
|
})
|
|
154
155
|
|
|
155
156
|
await wrapper.vm.$nextTick()
|
|
156
|
-
const chips = wrapper.
|
|
157
|
+
const chips = wrapper.findAll('.v-chip')
|
|
157
158
|
expect(chips.length).toBe(1)
|
|
158
|
-
expect(chips[0]!.text()).
|
|
159
|
+
expect(chips[0]!.text()).toContain('Option 1')
|
|
159
160
|
})
|
|
160
161
|
|
|
161
162
|
it('removes chip when close button is clicked', async () => {
|
|
@@ -171,14 +172,15 @@ describe('SyAutocomplete', () => {
|
|
|
171
172
|
textKey: 'text',
|
|
172
173
|
valueKey: 'value',
|
|
173
174
|
},
|
|
175
|
+
attachTo: document.body,
|
|
174
176
|
})
|
|
175
177
|
|
|
176
178
|
await wrapper.vm.$nextTick()
|
|
177
|
-
const
|
|
178
|
-
await
|
|
179
|
+
const closeBtn = wrapper.find('.v-chip__close')
|
|
180
|
+
await closeBtn.trigger('click')
|
|
179
181
|
await wrapper.vm.$nextTick()
|
|
180
182
|
|
|
181
|
-
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual([
|
|
183
|
+
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual([null])
|
|
182
184
|
})
|
|
183
185
|
|
|
184
186
|
it('shows clear button when clearable and has selection', async () => {
|
|
@@ -298,6 +300,181 @@ describe('SyAutocomplete', () => {
|
|
|
298
300
|
expect(isMenuOverlayActive()).toBe(false)
|
|
299
301
|
})
|
|
300
302
|
|
|
303
|
+
describe('selectionText', () => {
|
|
304
|
+
const selectionText = (selected: unknown[]) => `${selected.length} élément${selected.length > 1 ? 's' : ''} sélectionné${selected.length > 1 ? 's' : ''}`
|
|
305
|
+
|
|
306
|
+
it('displays custom text in prepend-inner when items are selected', async () => {
|
|
307
|
+
wrapper.unmount()
|
|
308
|
+
wrapper = mount(SyAutocomplete, {
|
|
309
|
+
props: {
|
|
310
|
+
modelValue: ['1', '2'],
|
|
311
|
+
items,
|
|
312
|
+
multiple: true,
|
|
313
|
+
selectionText,
|
|
314
|
+
label: 'Test selectionText',
|
|
315
|
+
textKey: 'text',
|
|
316
|
+
valueKey: 'value',
|
|
317
|
+
menuId,
|
|
318
|
+
},
|
|
319
|
+
attachTo: document.body,
|
|
320
|
+
})
|
|
321
|
+
await wrapper.vm.$nextTick()
|
|
322
|
+
|
|
323
|
+
const selectionTextEl = wrapper.find('.sy-autocomplete__selection-text')
|
|
324
|
+
expect(selectionTextEl.exists()).toBe(true)
|
|
325
|
+
expect(selectionTextEl.text()).toBe('2 éléments sélectionnés')
|
|
326
|
+
expect(getInputEl()?.value).toBe('')
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
it('does not display selection text element when no items are selected', async () => {
|
|
330
|
+
wrapper.unmount()
|
|
331
|
+
wrapper = mount(SyAutocomplete, {
|
|
332
|
+
props: {
|
|
333
|
+
modelValue: [],
|
|
334
|
+
items,
|
|
335
|
+
multiple: true,
|
|
336
|
+
selectionText,
|
|
337
|
+
label: 'Test selectionText vide',
|
|
338
|
+
textKey: 'text',
|
|
339
|
+
valueKey: 'value',
|
|
340
|
+
menuId,
|
|
341
|
+
},
|
|
342
|
+
attachTo: document.body,
|
|
343
|
+
})
|
|
344
|
+
await wrapper.vm.$nextTick()
|
|
345
|
+
|
|
346
|
+
expect(wrapper.find('.sy-autocomplete__selection-text').exists()).toBe(false)
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
it('keeps custom text visible when menu is open', async () => {
|
|
350
|
+
wrapper.unmount()
|
|
351
|
+
wrapper = mount(SyAutocomplete, {
|
|
352
|
+
props: {
|
|
353
|
+
modelValue: ['1', '2'],
|
|
354
|
+
items,
|
|
355
|
+
multiple: true,
|
|
356
|
+
selectionText,
|
|
357
|
+
label: 'Test selectionText ouvert',
|
|
358
|
+
textKey: 'text',
|
|
359
|
+
valueKey: 'value',
|
|
360
|
+
menuId,
|
|
361
|
+
},
|
|
362
|
+
attachTo: document.body,
|
|
363
|
+
})
|
|
364
|
+
await wrapper.vm.$nextTick()
|
|
365
|
+
|
|
366
|
+
const input = wrapper.find('input')
|
|
367
|
+
await input.trigger('click')
|
|
368
|
+
await flushPromises()
|
|
369
|
+
await wrapper.vm.$nextTick()
|
|
370
|
+
|
|
371
|
+
// Custom text still visible in prepend-inner, input empty for searching
|
|
372
|
+
expect(wrapper.find('.sy-autocomplete__selection-text').text()).toBe('2 éléments sélectionnés')
|
|
373
|
+
expect(getInputEl()?.value).toBe('')
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
it('updates custom text when selection changes', async () => {
|
|
377
|
+
wrapper.unmount()
|
|
378
|
+
wrapper = mount(SyAutocomplete, {
|
|
379
|
+
props: {
|
|
380
|
+
modelValue: ['1'],
|
|
381
|
+
items,
|
|
382
|
+
multiple: true,
|
|
383
|
+
selectionText,
|
|
384
|
+
label: 'Test selectionText update',
|
|
385
|
+
textKey: 'text',
|
|
386
|
+
valueKey: 'value',
|
|
387
|
+
menuId,
|
|
388
|
+
},
|
|
389
|
+
attachTo: document.body,
|
|
390
|
+
})
|
|
391
|
+
await wrapper.vm.$nextTick()
|
|
392
|
+
expect(wrapper.find('.sy-autocomplete__selection-text').text()).toBe('1 élément sélectionné')
|
|
393
|
+
|
|
394
|
+
await wrapper.setProps({ modelValue: ['1', '2', '3'] })
|
|
395
|
+
await wrapper.vm.$nextTick()
|
|
396
|
+
expect(wrapper.find('.sy-autocomplete__selection-text').text()).toBe('3 éléments sélectionnés')
|
|
397
|
+
})
|
|
398
|
+
})
|
|
399
|
+
|
|
400
|
+
describe('loading', () => {
|
|
401
|
+
it('shows progress bar when loading is true', async () => {
|
|
402
|
+
wrapper.unmount()
|
|
403
|
+
wrapper = mount(SyAutocomplete, {
|
|
404
|
+
props: {
|
|
405
|
+
modelValue: null,
|
|
406
|
+
items,
|
|
407
|
+
label: 'Test Loading',
|
|
408
|
+
textKey: 'text',
|
|
409
|
+
valueKey: 'value',
|
|
410
|
+
loading: true,
|
|
411
|
+
menuId,
|
|
412
|
+
},
|
|
413
|
+
attachTo: document.body,
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
await wrapper.vm.$nextTick()
|
|
417
|
+
const progressBar = wrapper.find('.v-progress-linear')
|
|
418
|
+
expect(progressBar.exists()).toBe(true)
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
it('does not show progress bar when loading is false', async () => {
|
|
422
|
+
await wrapper.vm.$nextTick()
|
|
423
|
+
const progressBar = wrapper.find('.v-progress-linear')
|
|
424
|
+
expect(progressBar.exists()).toBe(false)
|
|
425
|
+
})
|
|
426
|
+
|
|
427
|
+
it('hides no-data message while loading', async () => {
|
|
428
|
+
wrapper.unmount()
|
|
429
|
+
wrapper = mount(SyAutocomplete, {
|
|
430
|
+
props: {
|
|
431
|
+
modelValue: null,
|
|
432
|
+
items: [],
|
|
433
|
+
label: 'Test Loading No Data',
|
|
434
|
+
textKey: 'text',
|
|
435
|
+
valueKey: 'value',
|
|
436
|
+
loading: true,
|
|
437
|
+
menuId,
|
|
438
|
+
},
|
|
439
|
+
attachTo: document.body,
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
const input = wrapper.find('input')
|
|
443
|
+
await input.trigger('click')
|
|
444
|
+
await flushPromises()
|
|
445
|
+
await wrapper.vm.$nextTick()
|
|
446
|
+
|
|
447
|
+
// No-data item should not appear while loading
|
|
448
|
+
const noDataItem = document.body.querySelector(`#${menuId} .v-list-item`)
|
|
449
|
+
expect(noDataItem).toBeNull()
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
it('shows no-data message once loading is done and items is empty', async () => {
|
|
453
|
+
wrapper.unmount()
|
|
454
|
+
wrapper = mount(SyAutocomplete, {
|
|
455
|
+
props: {
|
|
456
|
+
modelValue: null,
|
|
457
|
+
items: [],
|
|
458
|
+
label: 'Test Loading Done',
|
|
459
|
+
textKey: 'text',
|
|
460
|
+
valueKey: 'value',
|
|
461
|
+
loading: false,
|
|
462
|
+
menuId,
|
|
463
|
+
},
|
|
464
|
+
attachTo: document.body,
|
|
465
|
+
})
|
|
466
|
+
|
|
467
|
+
const input = wrapper.find('input')
|
|
468
|
+
await input.trigger('click')
|
|
469
|
+
await flushPromises()
|
|
470
|
+
await wrapper.vm.$nextTick()
|
|
471
|
+
|
|
472
|
+
const listItems = document.body.querySelectorAll(`#${menuId} .v-list-item`)
|
|
473
|
+
const texts = Array.from(listItems).map(el => el.textContent?.trim())
|
|
474
|
+
expect(texts).toContain('Aucune option')
|
|
475
|
+
})
|
|
476
|
+
})
|
|
477
|
+
|
|
301
478
|
it('selects and deselects items in multiple mode (mouse + keyboard)', async () => {
|
|
302
479
|
wrapper.unmount()
|
|
303
480
|
wrapper = mount(SyAutocomplete, {
|
|
@@ -342,4 +519,52 @@ describe('SyAutocomplete', () => {
|
|
|
342
519
|
option0 = getOption(0)
|
|
343
520
|
expect(option0?.getAttribute('aria-selected')).toBe('false')
|
|
344
521
|
})
|
|
522
|
+
|
|
523
|
+
describe('hideDetails', () => {
|
|
524
|
+
it('hides the details zone when hideDetails is true', async () => {
|
|
525
|
+
wrapper.unmount()
|
|
526
|
+
wrapper = mount(SyAutocomplete, {
|
|
527
|
+
props: {
|
|
528
|
+
modelValue: null,
|
|
529
|
+
items,
|
|
530
|
+
label: 'Test hideDetails',
|
|
531
|
+
textKey: 'text',
|
|
532
|
+
valueKey: 'value',
|
|
533
|
+
hideDetails: true,
|
|
534
|
+
menuId,
|
|
535
|
+
},
|
|
536
|
+
attachTo: document.body,
|
|
537
|
+
})
|
|
538
|
+
|
|
539
|
+
await wrapper.vm.$nextTick()
|
|
540
|
+
expect(wrapper.find('.v-input__details').exists()).toBe(false)
|
|
541
|
+
})
|
|
542
|
+
|
|
543
|
+
it('shows the details zone when hideDetails is false', async () => {
|
|
544
|
+
await wrapper.vm.$nextTick()
|
|
545
|
+
expect(wrapper.find('.v-input__details').exists()).toBe(true)
|
|
546
|
+
})
|
|
547
|
+
|
|
548
|
+
it('does not show error messages when hideDetails is true even with validation errors', async () => {
|
|
549
|
+
wrapper.unmount()
|
|
550
|
+
wrapper = mount(SyAutocomplete, {
|
|
551
|
+
props: {
|
|
552
|
+
modelValue: null,
|
|
553
|
+
items,
|
|
554
|
+
label: 'Test hideDetails + erreur',
|
|
555
|
+
textKey: 'text',
|
|
556
|
+
valueKey: 'value',
|
|
557
|
+
hideDetails: true,
|
|
558
|
+
hasError: true,
|
|
559
|
+
errorMessages: ['Erreur de validation'],
|
|
560
|
+
menuId,
|
|
561
|
+
},
|
|
562
|
+
attachTo: document.body,
|
|
563
|
+
})
|
|
564
|
+
|
|
565
|
+
await wrapper.vm.$nextTick()
|
|
566
|
+
expect(wrapper.find('.v-input__details').exists()).toBe(false)
|
|
567
|
+
expect(wrapper.find('.v-messages').exists()).toBe(false)
|
|
568
|
+
})
|
|
569
|
+
})
|
|
345
570
|
})
|
|
@@ -145,9 +145,19 @@ export const ariaManager = {
|
|
|
145
145
|
popupRendered,
|
|
146
146
|
},
|
|
147
147
|
)
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
148
|
+
const labelledById = inputEl.getAttribute('aria-labelledby')
|
|
149
|
+
const labelElement = labelledById ? document.getElementById(labelledById) : null
|
|
150
|
+
const labelElementHasText = Boolean(labelElement?.textContent?.trim())
|
|
151
|
+
if (labelElementHasText) {
|
|
152
|
+
// aria-labelledby references a visible label — aria-label is redundant and causes validator errors
|
|
153
|
+
inputEl.removeAttribute('aria-label')
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
// No visible label element (e.g. chips mode with label="") — use aria-label as fallback
|
|
157
|
+
const labelToApply = inputLabel || inputEl.getAttribute('aria-label') || inputEl.getAttribute('placeholder') || ''
|
|
158
|
+
if (labelToApply) {
|
|
159
|
+
inputEl.setAttribute('aria-label', labelToApply)
|
|
160
|
+
}
|
|
151
161
|
}
|
|
152
162
|
inputEl.setAttribute('aria-haspopup', 'listbox')
|
|
153
163
|
if (!popupRendered) {
|
|
@@ -19,15 +19,9 @@ export function useSelectionLogic(
|
|
|
19
19
|
emit: EmitFunction,
|
|
20
20
|
) {
|
|
21
21
|
const resetValue = () => {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
else {
|
|
27
|
-
selected.value = null
|
|
28
|
-
search.value = ''
|
|
29
|
-
emit('update:modelValue', null)
|
|
30
|
-
}
|
|
22
|
+
selected.value = null
|
|
23
|
+
search.value = ''
|
|
24
|
+
emit('update:modelValue', null)
|
|
31
25
|
}
|
|
32
26
|
|
|
33
27
|
const updateMultipleSelection = (valueKeyValue: string | number, valueToStore: ItemType | string | number) => {
|
|
@@ -44,7 +38,12 @@ export function useSelectionLogic(
|
|
|
44
38
|
arr.push(valueToStore)
|
|
45
39
|
}
|
|
46
40
|
selected.value = arr as SelectArray
|
|
47
|
-
|
|
41
|
+
if (arr.length === 0) {
|
|
42
|
+
emit('update:modelValue', null)
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
emit('update:modelValue', arr as SelectArray)
|
|
46
|
+
}
|
|
48
47
|
}
|
|
49
48
|
|
|
50
49
|
const updateSingleSelection = (valueToStore: SelectValue, item: ItemType) => {
|
|
@@ -414,6 +414,11 @@
|
|
|
414
414
|
return ''
|
|
415
415
|
}
|
|
416
416
|
|
|
417
|
+
// If inline labels are shown, return empty string to hide input text
|
|
418
|
+
if (hasMultipleSelections.value) {
|
|
419
|
+
return ''
|
|
420
|
+
}
|
|
421
|
+
|
|
417
422
|
// For multiple mode, show default option text when nothing is selected
|
|
418
423
|
if (props.multiple) {
|
|
419
424
|
if (!selectedItem.value || (Array.isArray(selectedItem.value) && selectedItem.value.length === 0)) {
|
|
@@ -457,6 +462,10 @@
|
|
|
457
462
|
return props.chips && props.multiple && Array.isArray(selectedItem.value) && selectedItem.value.length > 0
|
|
458
463
|
})
|
|
459
464
|
|
|
465
|
+
const hasMultipleSelections = computed(() => {
|
|
466
|
+
return !props.chips && props.multiple && Array.isArray(selectedItem.value) && (selectedItem.value as unknown[]).length > 0
|
|
467
|
+
})
|
|
468
|
+
|
|
460
469
|
const hasSelectionToClear = computed(() => {
|
|
461
470
|
return props.multiple
|
|
462
471
|
? (((selectedItem.value as unknown[] | null | undefined)?.length) ?? 0) > 0
|
|
@@ -904,7 +913,10 @@
|
|
|
904
913
|
<template #activator="{ props: activatorProps }">
|
|
905
914
|
<div
|
|
906
915
|
class="sy-select"
|
|
907
|
-
:class="{
|
|
916
|
+
:class="{
|
|
917
|
+
'sy-select--clearable': props.clearable,
|
|
918
|
+
'sy-select--with-chips': hasChips,
|
|
919
|
+
}"
|
|
908
920
|
>
|
|
909
921
|
<VTextField
|
|
910
922
|
:id="inputId"
|
|
@@ -920,7 +932,7 @@
|
|
|
920
932
|
:rules="isRequired && !props.disableErrorHandling ? ['Le champ est requis.'] : []"
|
|
921
933
|
:bg-color="props.bgColor"
|
|
922
934
|
:density="props.density"
|
|
923
|
-
:active="hasChips || isOpen"
|
|
935
|
+
:active="hasChips || hasMultipleSelections || isOpen"
|
|
924
936
|
readonly
|
|
925
937
|
:hide-details="props.hideMessages && !showHelpTextAsMessage"
|
|
926
938
|
:hint="showHelpTextAsMessage ? props.helpText : ''"
|
|
@@ -955,12 +967,21 @@
|
|
|
955
967
|
size="small"
|
|
956
968
|
class="ma-1"
|
|
957
969
|
closable
|
|
958
|
-
:close-label="
|
|
970
|
+
:close-label="locales.removeChip(getChipText(item))"
|
|
959
971
|
@click:close="removeChip(item)"
|
|
960
972
|
>
|
|
961
973
|
{{ getChipText(item) }}
|
|
962
974
|
</VChip>
|
|
963
975
|
</div>
|
|
976
|
+
<template v-else-if="hasMultipleSelections">
|
|
977
|
+
<span
|
|
978
|
+
v-for="item in (selectedItem as unknown[])"
|
|
979
|
+
:key="props.returnObject && item ? String((item as ItemType)[props.valueKey]) : String(item)"
|
|
980
|
+
class="sy-select__label"
|
|
981
|
+
>
|
|
982
|
+
{{ getChipText(item) }}
|
|
983
|
+
</span>
|
|
984
|
+
</template>
|
|
964
985
|
<!-- Prepend -->
|
|
965
986
|
<template
|
|
966
987
|
v-if="$slots.prepend || props.prependIcon || props.prependTooltip"
|
|
@@ -1227,6 +1248,11 @@
|
|
|
1227
1248
|
opacity: var(--v-medium-emphasis-opacity) !important;
|
|
1228
1249
|
}
|
|
1229
1250
|
|
|
1251
|
+
/* Style spécifique pour les chips */
|
|
1252
|
+
:deep(.v-chip .v-chip__close .v-icon__svg) {
|
|
1253
|
+
fill: inherit !important;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1230
1256
|
.sy-select__clear-button {
|
|
1231
1257
|
position: absolute;
|
|
1232
1258
|
background: transparent;
|
|
@@ -1249,6 +1275,18 @@
|
|
|
1249
1275
|
margin: 2px;
|
|
1250
1276
|
}
|
|
1251
1277
|
|
|
1278
|
+
.sy-select__label {
|
|
1279
|
+
align-self: center;
|
|
1280
|
+
white-space: nowrap;
|
|
1281
|
+
flex-shrink: 0;
|
|
1282
|
+
font-size: inherit;
|
|
1283
|
+
|
|
1284
|
+
&:not(:last-of-type)::after {
|
|
1285
|
+
content: ',';
|
|
1286
|
+
margin-right: 4px;
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1252
1290
|
.sy-select :deep(.v-field__input) {
|
|
1253
1291
|
opacity: 1;
|
|
1254
1292
|
color: tokens.$grey-darken-20 !important;
|
|
@@ -1274,4 +1312,9 @@
|
|
|
1274
1312
|
position: absolute;
|
|
1275
1313
|
white-space: nowrap;
|
|
1276
1314
|
}
|
|
1315
|
+
|
|
1316
|
+
.sy-select--with-chips :deep(.v-field__input input) {
|
|
1317
|
+
position: absolute;
|
|
1318
|
+
z-index: -1;
|
|
1319
|
+
}
|
|
1277
1320
|
</style>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { describe, it } from 'vitest'
|
|
4
|
+
import { mount } from '@vue/test-utils'
|
|
5
|
+
import { axe } from 'vitest-axe'
|
|
6
|
+
import { assertNoA11yViolations } from '@tests/unit/accessibility/axeUtils'
|
|
7
|
+
import SyIcon from '../SyIcon.vue'
|
|
8
|
+
|
|
9
|
+
describe('SyIcon – accessibility (axe)', () => {
|
|
10
|
+
it('has no obvious axe violations', async () => {
|
|
11
|
+
const wrapper = mount(SyIcon, {
|
|
12
|
+
props: {
|
|
13
|
+
icon: 'home',
|
|
14
|
+
},
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const results = await axe(wrapper.element as HTMLElement)
|
|
18
|
+
assertNoA11yViolations(results, 'SyIcon – default state')
|
|
19
|
+
})
|
|
20
|
+
})
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Meta, Primary, Controls, Canvas } from '@storybook/blocks';
|
|
2
|
+
import * as SyIconButtonStories from './SyIconButton.stories';
|
|
3
|
+
import '@/stories/styles/shared.css';
|
|
4
|
+
|
|
5
|
+
<Meta of={SyIconButtonStories} />
|
|
6
|
+
|
|
7
|
+
<div className="header">
|
|
8
|
+
<h1>SyIconButton</h1>
|
|
9
|
+
<p>Le composant `SyIconButton` est un bouton icône qui combine `v-btn` de Vuetify et `SyIcon` en garantissant l'accessibilité via une prop `label` obligatoire portée par `aria-label`.</p>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<Canvas of={SyIconButtonStories.Default} />
|
|
13
|
+
|
|
14
|
+
# API
|
|
15
|
+
|
|
16
|
+
<Controls of={SyIconButtonStories.Default} />
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Accessibilité
|
|
20
|
+
|
|
21
|
+
Le composant `SyIconButton` gère automatiquement les attributs ARIA :
|
|
22
|
+
|
|
23
|
+
- **Label obligatoire** : la prop `label` est portée par le bouton via `aria-label` — elle constitue le nom accessible du bouton
|
|
24
|
+
- **Icône décorative** : `SyIcon` interne reçoit `:decorative="true"` car le bouton porte déjà le nom accessible via `aria-label` — l'icône est donc ignorée par les lecteurs d'écran
|
|
25
|
+
|
|
26
|
+
> **Important :** Le `label` doit décrire l'**action** du bouton et non l'icône elle-même (ex. _"Fermer"_ plutôt que _"Croix"_). Ne jamais fournir un `label` vide.
|
|
27
|
+
|
|
28
|
+
Pour plus d'informations sur l'accessibilité, consultez la [page dédiée à l'accessibilité](?path=/docs/customs-syiconbutton--accessibility).
|
|
29
|
+
|
|
30
|
+
## Exemples
|
|
31
|
+
|
|
32
|
+
### Bouton icône par défaut
|
|
33
|
+
|
|
34
|
+
<Canvas of={SyIconButtonStories.Default} />
|
|
35
|
+
|
|
36
|
+
### Bouton icône désactivé
|
|
37
|
+
|
|
38
|
+
<Canvas of={SyIconButtonStories.Disabled} />
|
|
39
|
+
|
|
40
|
+
### Bouton icône avec couleur
|
|
41
|
+
|
|
42
|
+
<Canvas of={SyIconButtonStories.WithColor} />
|
|
43
|
+
|
|
44
|
+
### Bouton icône avec taille personnalisée
|
|
45
|
+
|
|
46
|
+
<Canvas of={SyIconButtonStories.WithSize} />
|