origam 2.18.4 → 2.18.5
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/CHANGELOG.md +77 -0
- package/dist/src/assets/locales/en.json +9 -0
- package/dist/src/assets/locales/fr.json +9 -0
- package/dist/src/components/DataTable/OrigamDataTableGroupHeaderRow.vue +4 -0
- package/dist/src/components/DatePicker/OrigamDatePickerControls.vue +5 -0
- package/dist/src/components/InfiniteScroll/OrigamInfiniteScroll.vue +17 -0
- package/dist/src/components/Slide/OrigamSlideGroup.vue +5 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -89,6 +89,83 @@ les ruptures, elles, ne le sont pas — voir "Work priorities and versioning"
|
|
|
89
89
|
du `CLAUDE.md`. `origam` n'a aucun consommateur : les deux ruptures
|
|
90
90
|
ci-dessous partent en **mineure**, sans shim ni période de grâce.
|
|
91
91
|
|
|
92
|
+
### Fixed — `button-name` et `scrollable-region-focusable` (6 des 18 violations laissées de côté par le lot précédent)
|
|
93
|
+
|
|
94
|
+
Recompté sur `a11y-violations.baseline.json` avant de commencer (pas repris
|
|
95
|
+
d'un chiffre cité ailleurs) : **18 violations / 17 clés**, dont deux familles
|
|
96
|
+
explicitement laissées de côté par le lot ci-dessus.
|
|
97
|
+
|
|
98
|
+
**`button-name` (3, cause commune)** — `OrigamDataTableGroupHeaderRow`,
|
|
99
|
+
`OrigamDatePicker`, `OrigamDatePickerControls` rendaient chacun un
|
|
100
|
+
`<origam-btn icon="…">` sans `aria-label`/`aria-labelledby`/`title` : un
|
|
101
|
+
bouton icône réel, annoncé "button" et rien d'autre par un lecteur d'écran
|
|
102
|
+
(`OrigamBtn` avertit déjà de ce cas précis en dev depuis #653, mais ne
|
|
103
|
+
fabrique jamais de libellé — cf. son commentaire `isUnnamedIconOnly`, à
|
|
104
|
+
raison : un libellé deviné tairait axe sans rien dire à l'utilisateur).
|
|
105
|
+
Corrigé à la source de chaque bouton (pas dans `OrigamBtn`, qui n'a pas
|
|
106
|
+
l'information sémantique) :
|
|
107
|
+
- `OrigamDataTableGroupHeaderRow` — le bouton d'expand/collapse du groupe
|
|
108
|
+
reçoit `aria-expanded` + `aria-label` dynamique (`origam.data_table_group_header_row.{expand_group,collapse_group}`),
|
|
109
|
+
même motif que le correctif `data_table_row.{expand_row,collapse_row}`
|
|
110
|
+
déjà en place sur `OrigamDataTableRow`.
|
|
111
|
+
- `OrigamDatePickerControls` — les 3 boutons icône (bascule mois/année,
|
|
112
|
+
précédent, suivant) reçoivent chacun un `aria-label` via `useLocale()`
|
|
113
|
+
(`origam.date_picker_controls.{toggle_year,prev,next}`).
|
|
114
|
+
- `OrigamDatePicker::default` en héritait sans bouton icône propre — sa
|
|
115
|
+
Variant par défaut rend `OrigamDatePickerControls`, donc le correctif
|
|
116
|
+
ci-dessus l'assainit aussi ; son AUTRE violation (`color-contrast`,
|
|
117
|
+
hors périmètre) reste dans la baseline.
|
|
118
|
+
|
|
119
|
+
Nouvelles clés ajoutées à `en.json` ET `fr.json` dans le même commit,
|
|
120
|
+
`snake_case` minuscule, aucune chaîne en dur dans les templates.
|
|
121
|
+
|
|
122
|
+
**`scrollable-region-focusable` (3, cause commune)** — `OrigamInfiniteScroll`,
|
|
123
|
+
`OrigamInfiniteScrollIntersect` (dont la story monte elle-même un
|
|
124
|
+
`OrigamInfiniteScroll`) et `OrigamSlideGroup` rendent chacun un conteneur
|
|
125
|
+
`overflow: auto` sans jamais être atteignables au clavier quand leur contenu
|
|
126
|
+
n'a aucun descendant focusable — axe-core l'exige (technique WAI WCAG
|
|
127
|
+
SCR29).
|
|
128
|
+
|
|
129
|
+
⚠️ `tabindex="0"` inconditionnel a été essayé et écarté : `OrigamSlideGroup`
|
|
130
|
+
a un seul consommateur réel (`OrigamChipGroup`), qui slotte toujours des
|
|
131
|
+
puces focusables — y poser `tabindex` sans condition aurait inséré un arrêt
|
|
132
|
+
de tabulation redondant AVANT les items, dégradant le parcours clavier réel
|
|
133
|
+
pour corriger un cas de story synthétique. Le correctif retenu conditionne
|
|
134
|
+
donc le `tabindex` sur l'ABSENCE de descendant focusable (`focusableChildren()`,
|
|
135
|
+
déjà utilisé ailleurs dans le DS) :
|
|
136
|
+
- `OrigamInfiniteScroll` — `hasFocusableDescendant` (recalculé par
|
|
137
|
+
`MutationObserver` sur la racine, le contenu s'accumulant en continu via
|
|
138
|
+
`@load`) pilote un `:tabindex` conditionnel sur la racine.
|
|
139
|
+
- `OrigamSlideGroup` — `hasFocusableContent` (recalculé dans le même
|
|
140
|
+
`requestAnimationFrame` qui mesure déjà `isOverflowing`) pilote un
|
|
141
|
+
`:tabindex` conditionnel sur `.origam-slide-group__container`, l'élément
|
|
142
|
+
RÉELLEMENT scrollable — distinct de la racine, qui porte déjà son propre
|
|
143
|
+
`tabindex` conditionnel pour un mécanisme sans rapport (proxy de focus
|
|
144
|
+
vers le premier item, cf. `handleFocus`) qu'axe ne peut pas lire ici
|
|
145
|
+
puisque `__container` n'est pas un ancêtre de la racine.
|
|
146
|
+
- `OrigamInfiniteScrollIntersect::default` en héritait sans conteneur
|
|
147
|
+
scrollable propre — sa Variant par défaut monte un `OrigamInfiniteScroll`,
|
|
148
|
+
donc le correctif ci-dessus l'assainit aussi.
|
|
149
|
+
|
|
150
|
+
**Bilan mesuré** : baseline **18 clés/17 violations → 12 clés/12 violations**
|
|
151
|
+
(6 clés retirées : `OrigamDataTableGroupHeaderRow`, `OrigamDatePickerControls`,
|
|
152
|
+
`OrigamInfiniteScroll`, `OrigamInfiniteScrollIntersect`, `OrigamSlideGroup`,
|
|
153
|
+
et `button-name` retiré de l'entrée `OrigamDatePicker` qui garde son
|
|
154
|
+
`color-contrast`). Suite `pnpm -F @origam/tests test:a11y` complète (226
|
|
155
|
+
tests, catalogue 218/218) : **226 passed**, aucune régression sur les 36
|
|
156
|
+
composants à baseline vide. Contrôle positif exécuté sur
|
|
157
|
+
`OrigamDataTableGroupHeaderRow` : retirer `aria-expanded`/`aria-label` fait
|
|
158
|
+
échouer la porte avec `1 violation(s) a11y NOUVELLE(S) non baselinée(s) —
|
|
159
|
+
button-name` (`REAL_EXIT=1`) ; le correctif remis, la suite ciblée repasse
|
|
160
|
+
au vert. `pnpm -F origam guards` (28/28), `guards:self` (15/15),
|
|
161
|
+
`pnpm -F @origam/tests test:e2e:audit` (3/3) et `pnpm run lint`
|
|
162
|
+
(`--max-warnings 0`) verts sur le dernier commit.
|
|
163
|
+
|
|
164
|
+
**Laissé de côté, délibérément** (12 clés / 12 violations restent dans
|
|
165
|
+
`a11y-violations.baseline.json`) : `color-contrast` (8 instances, périmètre
|
|
166
|
+
d'autres agents), `aria-required-parent`/`aria-required-children`/`listitem`/
|
|
167
|
+
`aria-prohibited-attr` (un défaut chacun, cause non partagée avec ce lot).
|
|
168
|
+
|
|
92
169
|
### ⚠️ BREAKING — `click:prepend` / `click:append` retirés d'`IBtnEmits` (#443, #577)
|
|
93
170
|
|
|
94
171
|
`<OrigamBtn>` n'émet plus `click:prepend` ni `click:append` — ni au niveau du
|
|
@@ -51,6 +51,11 @@
|
|
|
51
51
|
"placeholder": "Enter date"
|
|
52
52
|
}
|
|
53
53
|
},
|
|
54
|
+
"date_picker_controls": {
|
|
55
|
+
"prev": "Previous",
|
|
56
|
+
"next": "Next",
|
|
57
|
+
"toggle_year": "Toggle year selection"
|
|
58
|
+
},
|
|
54
59
|
"no_data_text": "No data available",
|
|
55
60
|
"bracket": {
|
|
56
61
|
"aria_label": "Tournament bracket",
|
|
@@ -199,6 +204,10 @@
|
|
|
199
204
|
"expand_row": "Expand row",
|
|
200
205
|
"collapse_row": "Collapse row"
|
|
201
206
|
},
|
|
207
|
+
"data_table_group_header_row": {
|
|
208
|
+
"expand_group": "Expand group",
|
|
209
|
+
"collapse_group": "Collapse group"
|
|
210
|
+
},
|
|
202
211
|
"rating": {
|
|
203
212
|
"aria_label": {
|
|
204
213
|
"item": "Rating {0} of {1}"
|
|
@@ -51,6 +51,11 @@
|
|
|
51
51
|
"placeholder": "Entrer une date"
|
|
52
52
|
}
|
|
53
53
|
},
|
|
54
|
+
"date_picker_controls": {
|
|
55
|
+
"prev": "Précédent",
|
|
56
|
+
"next": "Suivant",
|
|
57
|
+
"toggle_year": "Basculer la sélection de l'année"
|
|
58
|
+
},
|
|
54
59
|
"no_data_text": "Aucune donnée disponible",
|
|
55
60
|
"bracket": {
|
|
56
61
|
"aria_label": "Tableau du tournoi",
|
|
@@ -199,6 +204,10 @@
|
|
|
199
204
|
"expand_row": "Développer la ligne",
|
|
200
205
|
"collapse_row": "Réduire la ligne"
|
|
201
206
|
},
|
|
207
|
+
"data_table_group_header_row": {
|
|
208
|
+
"expand_group": "Développer le groupe",
|
|
209
|
+
"collapse_group": "Réduire le groupe"
|
|
210
|
+
},
|
|
202
211
|
"rating": {
|
|
203
212
|
"aria_label": {
|
|
204
213
|
"item": "Note de {0} sur {1}"
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
>
|
|
16
16
|
<origam-data-table-column-cell class="origam-data-table-group-header-row__column">
|
|
17
17
|
<origam-btn
|
|
18
|
+
:aria-expanded="isGroupOpen(item)"
|
|
19
|
+
:aria-label="isGroupOpen(item) ? t('origam.data_table_group_header_row.collapse_group') : t('origam.data_table_group_header_row.expand_group')"
|
|
18
20
|
:icon="groupIcon"
|
|
19
21
|
size="small"
|
|
20
22
|
variant="text"
|
|
@@ -54,6 +56,7 @@ import OrigamCheckboxBtn from "../Checkbox/OrigamCheckboxBtn.vue";
|
|
|
54
56
|
import OrigamDataTableColumnCell from "./OrigamDataTableColumnCell.vue";
|
|
55
57
|
import { useGroupBy } from "../../composables/DataTable/group.composable";
|
|
56
58
|
import { useHeaders } from "../../composables/DataTable/headers.composable";
|
|
59
|
+
import { useLocale } from "../../composables/Commons/locale.composable";
|
|
57
60
|
import { useProps } from "../../composables/Commons/props.composable";
|
|
58
61
|
import { useSelection } from "../../composables/DataTable/select.composable";
|
|
59
62
|
import { useStyle } from "../../composables/Commons/style.composable";
|
|
@@ -74,6 +77,7 @@ const props = defineProps({
|
|
|
74
77
|
defineEmits([]);
|
|
75
78
|
defineSlots();
|
|
76
79
|
const { filterProps } = useProps(props);
|
|
80
|
+
const { t } = useLocale();
|
|
77
81
|
const { isGroupOpen, toggleGroup, extractRows } = useGroupBy();
|
|
78
82
|
const { isSelected, isSomeSelected, select } = useSelection();
|
|
79
83
|
const { columns } = useHeaders();
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
<origam-btn
|
|
19
19
|
key="mode-btn"
|
|
20
|
+
:aria-label="t('origam.date_picker_controls.toggle_year')"
|
|
20
21
|
:disabled="disableYear"
|
|
21
22
|
:icon="modeIcon"
|
|
22
23
|
class="origam-date-picker-controls__mode-btn"
|
|
@@ -35,6 +36,7 @@
|
|
|
35
36
|
<origam-btn-group>
|
|
36
37
|
<template #default>
|
|
37
38
|
<origam-btn
|
|
39
|
+
:aria-label="t('origam.date_picker_controls.prev')"
|
|
38
40
|
:disabled="disablePrev"
|
|
39
41
|
:icon="prevIcon"
|
|
40
42
|
:rounded="0"
|
|
@@ -42,6 +44,7 @@
|
|
|
42
44
|
/>
|
|
43
45
|
|
|
44
46
|
<origam-btn
|
|
47
|
+
:aria-label="t('origam.date_picker_controls.next')"
|
|
45
48
|
:disabled="disableNext"
|
|
46
49
|
:icon="nextIcon"
|
|
47
50
|
:rounded="0"
|
|
@@ -58,6 +61,7 @@ import OrigamBtn from "../Btn/OrigamBtn.vue";
|
|
|
58
61
|
import OrigamBtnGroup from "../Btn/OrigamBtnGroup.vue";
|
|
59
62
|
import OrigamSpacer from "../Grids/OrigamSpacer.vue";
|
|
60
63
|
import { useUnsupportedProp } from "../../composables/Commons/unsupportedProp.composable";
|
|
64
|
+
import { useLocale } from "../../composables/Commons/locale.composable";
|
|
61
65
|
import { useProps } from "../../composables/Commons/props.composable";
|
|
62
66
|
import { useStyle } from "../../composables/Commons/style.composable";
|
|
63
67
|
import { DATE_MODE } from "../../enums/DatePicker/date-picker.enum";
|
|
@@ -94,6 +98,7 @@ useUnsupportedProp(
|
|
|
94
98
|
const emits = defineEmits(["click:year", "click:month", "click:prev", "click:next"]);
|
|
95
99
|
defineSlots();
|
|
96
100
|
const { filterProps } = useProps(props);
|
|
101
|
+
const { t } = useLocale();
|
|
97
102
|
const disableMonth = computed(() => {
|
|
98
103
|
return props.disabled || props.disabledMonth;
|
|
99
104
|
});
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
ref="rootEl"
|
|
6
6
|
:class="infiniteScrollClasses"
|
|
7
7
|
:style="infiniteScrollStyles"
|
|
8
|
+
:tabindex="hasFocusableDescendant ? undefined : 0"
|
|
8
9
|
>
|
|
9
10
|
<div class="origam-infinite-scroll__side" role="status" aria-live="polite" :style="typographyStyles">
|
|
10
11
|
<template v-if="hasStartIntersect">
|
|
@@ -129,6 +130,7 @@ import { IN_BROWSER } from "../../consts/Commons/commons.const";
|
|
|
129
130
|
import { DIRECTION } from "../../enums/Commons/direction.enum";
|
|
130
131
|
import { INFINITE_SCROLL_MODE, INFINITE_SCROLL_SIDE, INFINITE_SCROLL_STATUS } from "../../enums/InfiniteScroll/infinite-scroll.enum";
|
|
131
132
|
import { PROGRESS_TYPE } from "../../enums/Progress/progress.enum";
|
|
133
|
+
import { focusableChildren } from "../../utils/Commons/commons.util";
|
|
132
134
|
const props = defineProps({
|
|
133
135
|
side: { type: null, required: false, default: INFINITE_SCROLL_SIDE.END },
|
|
134
136
|
mode: { type: null, required: false, default: INFINITE_SCROLL_MODE.INTERSECT },
|
|
@@ -159,6 +161,21 @@ const { typographyStyles } = useTypography(props, "infinite-scroll__loader");
|
|
|
159
161
|
const { colorClasses, colorStyles } = useBothColor(toRef(props, "bgColor"), toRef(props, "color"));
|
|
160
162
|
const rootEl = ref();
|
|
161
163
|
const isIntersecting = shallowRef(false);
|
|
164
|
+
const hasFocusableDescendant = shallowRef(false);
|
|
165
|
+
let focusableObserver;
|
|
166
|
+
const refreshHasFocusableDescendant = () => {
|
|
167
|
+
if (!rootEl.value) return;
|
|
168
|
+
hasFocusableDescendant.value = focusableChildren(rootEl.value).length > 0;
|
|
169
|
+
};
|
|
170
|
+
onMounted(() => {
|
|
171
|
+
if (!IN_BROWSER || !rootEl.value) return;
|
|
172
|
+
refreshHasFocusableDescendant();
|
|
173
|
+
focusableObserver = new MutationObserver(refreshHasFocusableDescendant);
|
|
174
|
+
focusableObserver.observe(rootEl.value, { childList: true, subtree: true });
|
|
175
|
+
});
|
|
176
|
+
onBeforeUnmount(() => {
|
|
177
|
+
focusableObserver?.disconnect();
|
|
178
|
+
});
|
|
162
179
|
const propertyDirection = computed(() => {
|
|
163
180
|
return props.direction === DIRECTION.VERTICAL ? "scrollTop" : "scrollLeft";
|
|
164
181
|
});
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
key="container"
|
|
26
26
|
ref="containerRef"
|
|
27
27
|
class="origam-slide-group__container"
|
|
28
|
+
:tabindex="isOverflowing && !hasFocusableContent ? 0 : undefined"
|
|
28
29
|
@scroll="handleScroll"
|
|
29
30
|
>
|
|
30
31
|
<div
|
|
@@ -138,6 +139,7 @@ const { marginClasses, marginStyles } = useMargin(props);
|
|
|
138
139
|
const { displayClasses, mobile } = useDisplay(props);
|
|
139
140
|
const group = useGroup(props, ORIGAM_SLIDE_GROUP_KEY);
|
|
140
141
|
const isOverflowing = shallowRef(false);
|
|
142
|
+
const hasFocusableContent = shallowRef(false);
|
|
141
143
|
const scrollOffset = shallowRef(0);
|
|
142
144
|
const containerSize = shallowRef(0);
|
|
143
145
|
const contentSize = shallowRef(0);
|
|
@@ -172,6 +174,9 @@ if (IN_BROWSER) {
|
|
|
172
174
|
contentSize.value = contentRect.value[sizeProperty];
|
|
173
175
|
isOverflowing.value = containerSize.value + 1 < contentSize.value;
|
|
174
176
|
}
|
|
177
|
+
if (contentRef.value) {
|
|
178
|
+
hasFocusableContent.value = focusableChildren(contentRef.value).length > 0;
|
|
179
|
+
}
|
|
175
180
|
if (firstSelectedIndex.value >= 0 && contentRef.value) {
|
|
176
181
|
const selectedElement = contentRef.value.children[lastSelectedIndex.value];
|
|
177
182
|
scrollToChildren(selectedElement, props.centerActive);
|