UNPKG

8.95 kBJavaScriptView Raw
1import classesToSelector from '../../shared/classes-to-selector.js';
2import $ from '../../shared/dom.js';
3export 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); // Container
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 } // Wrapper
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); // Slide
236
237 initSlides(); // Navigation
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 } // Pagination
257
258
259 if (hasClickablePagination()) {
260 swiper.pagination.$el.on('keydown', classesToSelector(swiper.params.pagination.bulletClass), onEnterOrSpaceKey);
261 } // Tab focus
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 } // Pagination
287
288
289 if (hasClickablePagination()) {
290 swiper.pagination.$el.off('keydown', classesToSelector(swiper.params.pagination.bulletClass), onEnterOrSpaceKey);
291 } // Tab focus
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