import $ from '../../utils/dom'; import Utils from '../../utils/utils'; const a11y = { makeElFocusable($el) { $el.attr('tabIndex', '0'); return $el; }, makeElNotFocusable($el) { $el.attr('tabIndex', '-1'); return $el; }, addElRole($el, role) { $el.attr('role', role); return $el; }, addElLabel($el, label) { $el.attr('aria-label', label); return $el; }, disableEl($el) { $el.attr('aria-disabled', true); return $el; }, enableEl($el) { $el.attr('aria-disabled', false); return $el; }, onEnterKey(e) { const swiper = this; const params = swiper.params.a11y; if (e.keyCode !== 13) return; const $targetEl = $(e.target); if (swiper.navigation && swiper.navigation.$nextEl && $targetEl.is(swiper.navigation.$nextEl)) { if (!(swiper.isEnd && !swiper.params.loop)) { swiper.slideNext(); } if (swiper.isEnd) { swiper.a11y.notify(params.lastSlideMessage); } else { swiper.a11y.notify(params.nextSlideMessage); } } if (swiper.navigation && swiper.navigation.$prevEl && $targetEl.is(swiper.navigation.$prevEl)) { if (!(swiper.isBeginning && !swiper.params.loop)) { swiper.slidePrev(); } if (swiper.isBeginning) { swiper.a11y.notify(params.firstSlideMessage); } else { swiper.a11y.notify(params.prevSlideMessage); } } if (swiper.pagination && $targetEl.is(`.${swiper.params.pagination.bulletClass}`)) { $targetEl[0].click(); } }, notify(message) { const swiper = this; const notification = swiper.a11y.liveRegion; if (notification.length === 0) return; notification.html(''); notification.html(message); }, updateNavigation() { const swiper = this; if (swiper.params.loop || !swiper.navigation) return; const { $nextEl, $prevEl } = swiper.navigation; if ($prevEl && $prevEl.length > 0) { if (swiper.isBeginning) { swiper.a11y.disableEl($prevEl); swiper.a11y.makeElNotFocusable($prevEl); } else { swiper.a11y.enableEl($prevEl); swiper.a11y.makeElFocusable($prevEl); } } if ($nextEl && $nextEl.length > 0) { if (swiper.isEnd) { swiper.a11y.disableEl($nextEl); swiper.a11y.makeElNotFocusable($nextEl); } else { swiper.a11y.enableEl($nextEl); swiper.a11y.makeElFocusable($nextEl); } } }, updatePagination() { const swiper = this; const params = swiper.params.a11y; if (swiper.pagination && swiper.params.pagination.clickable && swiper.pagination.bullets && swiper.pagination.bullets.length) { swiper.pagination.bullets.each((bulletIndex, bulletEl) => { const $bulletEl = $(bulletEl); swiper.a11y.makeElFocusable($bulletEl); swiper.a11y.addElRole($bulletEl, 'button'); swiper.a11y.addElLabel($bulletEl, params.paginationBulletMessage.replace(/\{\{index\}\}/, $bulletEl.index() + 1)); }); } }, init() { const swiper = this; swiper.$el.append(swiper.a11y.liveRegion); // Navigation const params = swiper.params.a11y; let $nextEl; let $prevEl; if (swiper.navigation && swiper.navigation.$nextEl) { $nextEl = swiper.navigation.$nextEl; } if (swiper.navigation && swiper.navigation.$prevEl) { $prevEl = swiper.navigation.$prevEl; } if ($nextEl) { swiper.a11y.makeElFocusable($nextEl); swiper.a11y.addElRole($nextEl, 'button'); swiper.a11y.addElLabel($nextEl, params.nextSlideMessage); $nextEl.on('keydown', swiper.a11y.onEnterKey); } if ($prevEl) { swiper.a11y.makeElFocusable($prevEl); swiper.a11y.addElRole($prevEl, 'button'); swiper.a11y.addElLabel($prevEl, params.prevSlideMessage); $prevEl.on('keydown', swiper.a11y.onEnterKey); } // Pagination if (swiper.pagination && swiper.params.pagination.clickable && swiper.pagination.bullets && swiper.pagination.bullets.length) { swiper.pagination.$el.on('keydown', `.${swiper.params.pagination.bulletClass}`, swiper.a11y.onEnterKey); } }, destroy() { const swiper = this; if (swiper.a11y.liveRegion && swiper.a11y.liveRegion.length > 0) swiper.a11y.liveRegion.remove(); let $nextEl; let $prevEl; if (swiper.navigation && swiper.navigation.$nextEl) { $nextEl = swiper.navigation.$nextEl; } if (swiper.navigation && swiper.navigation.$prevEl) { $prevEl = swiper.navigation.$prevEl; } if ($nextEl) { $nextEl.off('keydown', swiper.a11y.onEnterKey); } if ($prevEl) { $prevEl.off('keydown', swiper.a11y.onEnterKey); } // Pagination if (swiper.pagination && swiper.params.pagination.clickable && swiper.pagination.bullets && swiper.pagination.bullets.length) { swiper.pagination.$el.off('keydown', `.${swiper.params.pagination.bulletClass}`, swiper.a11y.onEnterKey); } }, }; export default { name: 'a11y', params: { a11y: { enabled: true, notificationClass: 'swiper-notification', prevSlideMessage: 'Previous slide', nextSlideMessage: 'Next slide', firstSlideMessage: 'This is the first slide', lastSlideMessage: 'This is the last slide', paginationBulletMessage: 'Go to slide {{index}}', }, }, create() { const swiper = this; Utils.extend(swiper, { a11y: { liveRegion: $(``), }, }); Object.keys(a11y).forEach((methodName) => { swiper.a11y[methodName] = a11y[methodName].bind(swiper); }); }, on: { init() { const swiper = this; if (!swiper.params.a11y.enabled) return; swiper.a11y.init(); swiper.a11y.updateNavigation(); }, toEdge() { const swiper = this; if (!swiper.params.a11y.enabled) return; swiper.a11y.updateNavigation(); }, fromEdge() { const swiper = this; if (!swiper.params.a11y.enabled) return; swiper.a11y.updateNavigation(); }, paginationUpdate() { const swiper = this; if (!swiper.params.a11y.enabled) return; swiper.a11y.updatePagination(); }, destroy() { const swiper = this; if (!swiper.params.a11y.enabled) return; swiper.a11y.destroy(); }, }, };