1 | import classesToSelector from '../../shared/classes-to-selector.js';
|
2 | import $ from '../../shared/dom.js';
|
3 | export default function A11y(_ref) {
|
4 | let {
|
5 | swiper,
|
6 | extendParams,
|
7 | on
|
8 | } = _ref;
|
9 | extendParams({
|
10 | a11y: {
|
11 | enabled: true,
|
12 | notificationClass: 'swiper-notification',
|
13 | prevSlideMessage: 'Previous slide',
|
14 | nextSlideMessage: 'Next slide',
|
15 | firstSlideMessage: 'This is the first slide',
|
16 | lastSlideMessage: 'This is the last slide',
|
17 | paginationBulletMessage: 'Go to slide {{index}}',
|
18 | slideLabelMessage: '{{index}} / {{slidesLength}}',
|
19 | containerMessage: null,
|
20 | containerRoleDescriptionMessage: null,
|
21 | itemRoleDescriptionMessage: null,
|
22 | slideRole: 'group',
|
23 | id: null
|
24 | }
|
25 | });
|
26 | let liveRegion = null;
|
27 |
|
28 | function notify(message) {
|
29 | const notification = liveRegion;
|
30 | if (notification.length === 0) return;
|
31 | notification.html('');
|
32 | notification.html(message);
|
33 | }
|
34 |
|
35 | function getRandomNumber(size) {
|
36 | if (size === void 0) {
|
37 | size = 16;
|
38 | }
|
39 |
|
40 | const randomChar = () => Math.round(16 * Math.random()).toString(16);
|
41 |
|
42 | return 'x'.repeat(size).replace(/x/g, randomChar);
|
43 | }
|
44 |
|
45 | function makeElFocusable($el) {
|
46 | $el.attr('tabIndex', '0');
|
47 | }
|
48 |
|
49 | function makeElNotFocusable($el) {
|
50 | $el.attr('tabIndex', '-1');
|
51 | }
|
52 |
|
53 | function addElRole($el, role) {
|
54 | $el.attr('role', role);
|
55 | }
|
56 |
|
57 | function addElRoleDescription($el, description) {
|
58 | $el.attr('aria-roledescription', description);
|
59 | }
|
60 |
|
61 | function addElControls($el, controls) {
|
62 | $el.attr('aria-controls', controls);
|
63 | }
|
64 |
|
65 | function addElLabel($el, label) {
|
66 | $el.attr('aria-label', label);
|
67 | }
|
68 |
|
69 | function addElId($el, id) {
|
70 | $el.attr('id', id);
|
71 | }
|
72 |
|
73 | function addElLive($el, live) {
|
74 | $el.attr('aria-live', live);
|
75 | }
|
76 |
|
77 | function disableEl($el) {
|
78 | $el.attr('aria-disabled', true);
|
79 | }
|
80 |
|
81 | function enableEl($el) {
|
82 | $el.attr('aria-disabled', false);
|
83 | }
|
84 |
|
85 | function onEnterOrSpaceKey(e) {
|
86 | if (e.keyCode !== 13 && e.keyCode !== 32) return;
|
87 | const params = swiper.params.a11y;
|
88 | const $targetEl = $(e.target);
|
89 |
|
90 | if (swiper.navigation && swiper.navigation.$nextEl && $targetEl.is(swiper.navigation.$nextEl)) {
|
91 | if (!(swiper.isEnd && !swiper.params.loop)) {
|
92 | swiper.slideNext();
|
93 | }
|
94 |
|
95 | if (swiper.isEnd) {
|
96 | notify(params.lastSlideMessage);
|
97 | } else {
|
98 | notify(params.nextSlideMessage);
|
99 | }
|
100 | }
|
101 |
|
102 | if (swiper.navigation && swiper.navigation.$prevEl && $targetEl.is(swiper.navigation.$prevEl)) {
|
103 | if (!(swiper.isBeginning && !swiper.params.loop)) {
|
104 | swiper.slidePrev();
|
105 | }
|
106 |
|
107 | if (swiper.isBeginning) {
|
108 | notify(params.firstSlideMessage);
|
109 | } else {
|
110 | notify(params.prevSlideMessage);
|
111 | }
|
112 | }
|
113 |
|
114 | if (swiper.pagination && $targetEl.is(classesToSelector(swiper.params.pagination.bulletClass))) {
|
115 | $targetEl[0].click();
|
116 | }
|
117 | }
|
118 |
|
119 | function updateNavigation() {
|
120 | if (swiper.params.loop || swiper.params.rewind || !swiper.navigation) return;
|
121 | const {
|
122 | $nextEl,
|
123 | $prevEl
|
124 | } = swiper.navigation;
|
125 |
|
126 | if ($prevEl && $prevEl.length > 0) {
|
127 | if (swiper.isBeginning) {
|
128 | disableEl($prevEl);
|
129 | makeElNotFocusable($prevEl);
|
130 | } else {
|
131 | enableEl($prevEl);
|
132 | makeElFocusable($prevEl);
|
133 | }
|
134 | }
|
135 |
|
136 | if ($nextEl && $nextEl.length > 0) {
|
137 | if (swiper.isEnd) {
|
138 | disableEl($nextEl);
|
139 | makeElNotFocusable($nextEl);
|
140 | } else {
|
141 | enableEl($nextEl);
|
142 | makeElFocusable($nextEl);
|
143 | }
|
144 | }
|
145 | }
|
146 |
|
147 | function hasPagination() {
|
148 | return swiper.pagination && swiper.pagination.bullets && swiper.pagination.bullets.length;
|
149 | }
|
150 |
|
151 | function hasClickablePagination() {
|
152 | return hasPagination() && swiper.params.pagination.clickable;
|
153 | }
|
154 |
|
155 | function updatePagination() {
|
156 | const params = swiper.params.a11y;
|
157 | if (!hasPagination()) return;
|
158 | swiper.pagination.bullets.each(bulletEl => {
|
159 | const $bulletEl = $(bulletEl);
|
160 |
|
161 | if (swiper.params.pagination.clickable) {
|
162 | makeElFocusable($bulletEl);
|
163 |
|
164 | if (!swiper.params.pagination.renderBullet) {
|
165 | addElRole($bulletEl, 'button');
|
166 | addElLabel($bulletEl, params.paginationBulletMessage.replace(/\{\{index\}\}/, $bulletEl.index() + 1));
|
167 | }
|
168 | }
|
169 |
|
170 | if ($bulletEl.is(`.${swiper.params.pagination.bulletActiveClass}`)) {
|
171 | $bulletEl.attr('aria-current', 'true');
|
172 | } else {
|
173 | $bulletEl.removeAttr('aria-current');
|
174 | }
|
175 | });
|
176 | }
|
177 |
|
178 | const initNavEl = ($el, wrapperId, message) => {
|
179 | makeElFocusable($el);
|
180 |
|
181 | if ($el[0].tagName !== 'BUTTON') {
|
182 | addElRole($el, 'button');
|
183 | $el.on('keydown', onEnterOrSpaceKey);
|
184 | }
|
185 |
|
186 | addElLabel($el, message);
|
187 | addElControls($el, wrapperId);
|
188 | };
|
189 |
|
190 | const handleFocus = e => {
|
191 | const slideEl = e.target.closest(`.${swiper.params.slideClass}`);
|
192 | if (!slideEl || !swiper.slides.includes(slideEl)) return;
|
193 | const isActive = swiper.slides.indexOf(slideEl) === swiper.activeIndex;
|
194 | const isVisible = swiper.params.watchSlidesProgress && swiper.visibleSlides && swiper.visibleSlides.includes(slideEl);
|
195 | if (isActive || isVisible) return;
|
196 | swiper.slideTo(swiper.slides.indexOf(slideEl), 0);
|
197 | };
|
198 |
|
199 | const initSlides = () => {
|
200 | const params = swiper.params.a11y;
|
201 |
|
202 | if (params.itemRoleDescriptionMessage) {
|
203 | addElRoleDescription($(swiper.slides), params.itemRoleDescriptionMessage);
|
204 | }
|
205 |
|
206 | addElRole($(swiper.slides), params.slideRole);
|
207 | const slidesLength = swiper.params.loop ? swiper.slides.filter(el => !el.classList.contains(swiper.params.slideDuplicateClass)).length : swiper.slides.length;
|
208 | swiper.slides.each((slideEl, index) => {
|
209 | const $slideEl = $(slideEl);
|
210 | const slideIndex = swiper.params.loop ? parseInt($slideEl.attr('data-swiper-slide-index'), 10) : index;
|
211 | const ariaLabelMessage = params.slideLabelMessage.replace(/\{\{index\}\}/, slideIndex + 1).replace(/\{\{slidesLength\}\}/, slidesLength);
|
212 | addElLabel($slideEl, ariaLabelMessage);
|
213 | });
|
214 | };
|
215 |
|
216 | const init = () => {
|
217 | const params = swiper.params.a11y;
|
218 | swiper.$el.append(liveRegion);
|
219 |
|
220 | const $containerEl = swiper.$el;
|
221 |
|
222 | if (params.containerRoleDescriptionMessage) {
|
223 | addElRoleDescription($containerEl, params.containerRoleDescriptionMessage);
|
224 | }
|
225 |
|
226 | if (params.containerMessage) {
|
227 | addElLabel($containerEl, params.containerMessage);
|
228 | }
|
229 |
|
230 |
|
231 | const $wrapperEl = swiper.$wrapperEl;
|
232 | const wrapperId = params.id || $wrapperEl.attr('id') || `swiper-wrapper-${getRandomNumber(16)}`;
|
233 | const live = swiper.params.autoplay && swiper.params.autoplay.enabled ? 'off' : 'polite';
|
234 | addElId($wrapperEl, wrapperId);
|
235 | addElLive($wrapperEl, live);
|
236 |
|
237 | initSlides();
|
238 |
|
239 | let $nextEl;
|
240 | let $prevEl;
|
241 |
|
242 | if (swiper.navigation && swiper.navigation.$nextEl) {
|
243 | $nextEl = swiper.navigation.$nextEl;
|
244 | }
|
245 |
|
246 | if (swiper.navigation && swiper.navigation.$prevEl) {
|
247 | $prevEl = swiper.navigation.$prevEl;
|
248 | }
|
249 |
|
250 | if ($nextEl && $nextEl.length) {
|
251 | initNavEl($nextEl, wrapperId, params.nextSlideMessage);
|
252 | }
|
253 |
|
254 | if ($prevEl && $prevEl.length) {
|
255 | initNavEl($prevEl, wrapperId, params.prevSlideMessage);
|
256 | }
|
257 |
|
258 |
|
259 | if (hasClickablePagination()) {
|
260 | swiper.pagination.$el.on('keydown', classesToSelector(swiper.params.pagination.bulletClass), onEnterOrSpaceKey);
|
261 | }
|
262 |
|
263 |
|
264 | swiper.$el.on('focus', handleFocus, true);
|
265 | };
|
266 |
|
267 | function destroy() {
|
268 | if (liveRegion && liveRegion.length > 0) liveRegion.remove();
|
269 | let $nextEl;
|
270 | let $prevEl;
|
271 |
|
272 | if (swiper.navigation && swiper.navigation.$nextEl) {
|
273 | $nextEl = swiper.navigation.$nextEl;
|
274 | }
|
275 |
|
276 | if (swiper.navigation && swiper.navigation.$prevEl) {
|
277 | $prevEl = swiper.navigation.$prevEl;
|
278 | }
|
279 |
|
280 | if ($nextEl) {
|
281 | $nextEl.off('keydown', onEnterOrSpaceKey);
|
282 | }
|
283 |
|
284 | if ($prevEl) {
|
285 | $prevEl.off('keydown', onEnterOrSpaceKey);
|
286 | }
|
287 |
|
288 |
|
289 | if (hasClickablePagination()) {
|
290 | swiper.pagination.$el.off('keydown', classesToSelector(swiper.params.pagination.bulletClass), onEnterOrSpaceKey);
|
291 | }
|
292 |
|
293 |
|
294 | swiper.$el.off('focus', handleFocus, true);
|
295 | }
|
296 |
|
297 | on('beforeInit', () => {
|
298 | liveRegion = $(`<span class="${swiper.params.a11y.notificationClass}" aria-live="assertive" aria-atomic="true"></span>`);
|
299 | });
|
300 | on('afterInit', () => {
|
301 | if (!swiper.params.a11y.enabled) return;
|
302 | init();
|
303 | });
|
304 | on('slidesLengthChange snapGridLengthChange slidesGridLengthChange', () => {
|
305 | if (!swiper.params.a11y.enabled) return;
|
306 | initSlides();
|
307 | });
|
308 | on('fromEdge toEdge afterInit lock unlock', () => {
|
309 | if (!swiper.params.a11y.enabled) return;
|
310 | updateNavigation();
|
311 | });
|
312 | on('paginationUpdate', () => {
|
313 | if (!swiper.params.a11y.enabled) return;
|
314 | updatePagination();
|
315 | });
|
316 | on('destroy', () => {
|
317 | if (!swiper.params.a11y.enabled) return;
|
318 | destroy();
|
319 | });
|
320 | } |
\ | No newline at end of file |