diff --git a/src/modules/a11y/a11y.js b/src/modules/a11y/a11y.js index 8fe823697..cfed3f1e3 100644 --- a/src/modules/a11y/a11y.js +++ b/src/modules/a11y/a11y.js @@ -1,54 +1,72 @@ import $ from '../../shared/dom.js'; -import { bindModuleMethods, classesToSelector } from '../../shared/utils.js'; +import { classesToSelector } from '../../shared/utils.js'; -const A11y = { - getRandomNumber(size = 16) { +export default function A11y({ swiper, extendParams, on }) { + extendParams({ + 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}}', + slideLabelMessage: '{{index}} / {{slidesLength}}', + containerMessage: null, + containerRoleDescriptionMessage: null, + itemRoleDescriptionMessage: null, + slideRole: 'group', + }, + }); + + const liveRegion = $( + ``, + ); + + function notify(message) { + const notification = liveRegion; + if (notification.length === 0) return; + notification.html(''); + notification.html(message); + } + + function getRandomNumber(size = 16) { const randomChar = () => Math.round(16 * Math.random()).toString(16); return 'x'.repeat(size).replace(/x/g, randomChar); - }, - makeElFocusable($el) { + } + function makeElFocusable($el) { $el.attr('tabIndex', '0'); - return $el; - }, - makeElNotFocusable($el) { + } + function makeElNotFocusable($el) { $el.attr('tabIndex', '-1'); - return $el; - }, - addElRole($el, role) { + } + function addElRole($el, role) { $el.attr('role', role); - return $el; - }, - addElRoleDescription($el, description) { + } + function addElRoleDescription($el, description) { $el.attr('aria-roledescription', description); - return $el; - }, - addElControls($el, controls) { + } + function addElControls($el, controls) { $el.attr('aria-controls', controls); - return $el; - }, - addElLabel($el, label) { + } + function addElLabel($el, label) { $el.attr('aria-label', label); - return $el; - }, - addElId($el, id) { + } + function addElId($el, id) { $el.attr('id', id); - return $el; - }, - addElLive($el, live) { + } + function addElLive($el, live) { $el.attr('aria-live', live); - return $el; - }, - disableEl($el) { + } + function disableEl($el) { $el.attr('aria-disabled', true); - return $el; - }, - enableEl($el) { + } + function enableEl($el) { $el.attr('aria-disabled', false); - return $el; - }, - onEnterOrSpaceKey(e) { + } + + function onEnterOrSpaceKey(e) { if (e.keyCode !== 13 && e.keyCode !== 32) return; - const swiper = this; const params = swiper.params.a11y; const $targetEl = $(e.target); if (swiper.navigation && swiper.navigation.$nextEl && $targetEl.is(swiper.navigation.$nextEl)) { @@ -56,9 +74,9 @@ const A11y = { swiper.slideNext(); } if (swiper.isEnd) { - swiper.a11y.notify(params.lastSlideMessage); + notify(params.lastSlideMessage); } else { - swiper.a11y.notify(params.nextSlideMessage); + notify(params.nextSlideMessage); } } if (swiper.navigation && swiper.navigation.$prevEl && $targetEl.is(swiper.navigation.$prevEl)) { @@ -66,9 +84,9 @@ const A11y = { swiper.slidePrev(); } if (swiper.isBeginning) { - swiper.a11y.notify(params.firstSlideMessage); + notify(params.firstSlideMessage); } else { - swiper.a11y.notify(params.prevSlideMessage); + notify(params.prevSlideMessage); } } @@ -78,88 +96,94 @@ const A11y = { ) { $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; + } + function updateNavigation() { 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); + disableEl($prevEl); + makeElNotFocusable($prevEl); } else { - swiper.a11y.enableEl($prevEl); - swiper.a11y.makeElFocusable($prevEl); + enableEl($prevEl); + makeElFocusable($prevEl); } } if ($nextEl && $nextEl.length > 0) { if (swiper.isEnd) { - swiper.a11y.disableEl($nextEl); - swiper.a11y.makeElNotFocusable($nextEl); + disableEl($nextEl); + makeElNotFocusable($nextEl); } else { - swiper.a11y.enableEl($nextEl); - swiper.a11y.makeElFocusable($nextEl); + enableEl($nextEl); + makeElFocusable($nextEl); } } - }, - updatePagination() { - const swiper = this; - const params = swiper.params.a11y; - if ( + } + + function hasPagination() { + return ( swiper.pagination && swiper.params.pagination.clickable && swiper.pagination.bullets && swiper.pagination.bullets.length - ) { + ); + } + + function updatePagination() { + const params = swiper.params.a11y; + if (hasPagination()) { swiper.pagination.bullets.each((bulletEl) => { const $bulletEl = $(bulletEl); - swiper.a11y.makeElFocusable($bulletEl); + makeElFocusable($bulletEl); if (!swiper.params.pagination.renderBullet) { - swiper.a11y.addElRole($bulletEl, 'button'); - swiper.a11y.addElLabel( + addElRole($bulletEl, 'button'); + addElLabel( $bulletEl, params.paginationBulletMessage.replace(/\{\{index\}\}/, $bulletEl.index() + 1), ); } }); } - }, - init() { - const swiper = this; + } + + const initNavEl = ($el, wrapperId, message) => { + makeElFocusable($el); + if ($el[0].tagName !== 'BUTTON') { + addElRole($el, 'button'); + $el.on('keydown', onEnterOrSpaceKey); + } + addElLabel($el, message); + addElControls($el, wrapperId); + }; + + function init() { const params = swiper.params.a11y; - swiper.$el.append(swiper.a11y.liveRegion); + swiper.$el.append(liveRegion); // Container const $containerEl = swiper.$el; if (params.containerRoleDescriptionMessage) { - swiper.a11y.addElRoleDescription($containerEl, params.containerRoleDescriptionMessage); + addElRoleDescription($containerEl, params.containerRoleDescriptionMessage); } if (params.containerMessage) { - swiper.a11y.addElLabel($containerEl, params.containerMessage); + addElLabel($containerEl, params.containerMessage); } // Wrapper const $wrapperEl = swiper.$wrapperEl; - const wrapperId = $wrapperEl.attr('id') || `swiper-wrapper-${swiper.a11y.getRandomNumber(16)}`; + const wrapperId = $wrapperEl.attr('id') || `swiper-wrapper-${getRandomNumber(16)}`; const live = swiper.params.autoplay && swiper.params.autoplay.enabled ? 'off' : 'polite'; - swiper.a11y.addElId($wrapperEl, wrapperId); - swiper.a11y.addElLive($wrapperEl, live); + addElId($wrapperEl, wrapperId); + addElLive($wrapperEl, live); // Slide if (params.itemRoleDescriptionMessage) { - swiper.a11y.addElRoleDescription($(swiper.slides), params.itemRoleDescriptionMessage); + addElRoleDescription($(swiper.slides), params.itemRoleDescriptionMessage); } - swiper.a11y.addElRole($(swiper.slides), params.slideRole); + addElRole($(swiper.slides), params.slideRole); const slidesLength = swiper.params.loop ? swiper.slides.filter((el) => !el.classList.contains(swiper.params.slideDuplicateClass)) @@ -173,7 +197,7 @@ const A11y = { const ariaLabelMessage = params.slideLabelMessage .replace(/\{\{index\}\}/, slideIndex + 1) .replace(/\{\{slidesLength\}\}/, slidesLength); - swiper.a11y.addElLabel($slideEl, ariaLabelMessage); + addElLabel($slideEl, ariaLabelMessage); }); // Navigation @@ -187,42 +211,23 @@ const A11y = { } if ($nextEl && $nextEl.length) { - swiper.a11y.makeElFocusable($nextEl); - if ($nextEl[0].tagName !== 'BUTTON') { - swiper.a11y.addElRole($nextEl, 'button'); - $nextEl.on('keydown', swiper.a11y.onEnterOrSpaceKey); - } - swiper.a11y.addElLabel($nextEl, params.nextSlideMessage); - swiper.a11y.addElControls($nextEl, wrapperId); + initNavEl($nextEl, params.nextSlideMessage); } if ($prevEl && $prevEl.length) { - swiper.a11y.makeElFocusable($prevEl); - if ($prevEl[0].tagName !== 'BUTTON') { - swiper.a11y.addElRole($prevEl, 'button'); - $prevEl.on('keydown', swiper.a11y.onEnterOrSpaceKey); - } - swiper.a11y.addElLabel($prevEl, params.prevSlideMessage); - swiper.a11y.addElControls($prevEl, wrapperId); + initNavEl($prevEl, params.prevSlideMessage); } // Pagination - if ( - swiper.pagination && - swiper.params.pagination.clickable && - swiper.pagination.bullets && - swiper.pagination.bullets.length - ) { + if (hasPagination()) { swiper.pagination.$el.on( 'keydown', classesToSelector(swiper.params.pagination.bulletClass), - swiper.a11y.onEnterOrSpaceKey, + onEnterOrSpaceKey, ); } - }, - destroy() { - const swiper = this; - if (swiper.a11y.liveRegion && swiper.a11y.liveRegion.length > 0) - swiper.a11y.liveRegion.remove(); + } + function destroy() { + if (liveRegion && liveRegion.length > 0) liveRegion.remove(); let $nextEl; let $prevEl; @@ -233,77 +238,41 @@ const A11y = { $prevEl = swiper.navigation.$prevEl; } if ($nextEl) { - $nextEl.off('keydown', swiper.a11y.onEnterOrSpaceKey); + $nextEl.off('keydown', onEnterOrSpaceKey); } if ($prevEl) { - $prevEl.off('keydown', swiper.a11y.onEnterOrSpaceKey); + $prevEl.off('keydown', onEnterOrSpaceKey); } // Pagination - if ( - swiper.pagination && - swiper.params.pagination.clickable && - swiper.pagination.bullets && - swiper.pagination.bullets.length - ) { + if (hasPagination()) { swiper.pagination.$el.off( 'keydown', classesToSelector(swiper.params.pagination.bulletClass), - swiper.a11y.onEnterOrSpaceKey, + onEnterOrSpaceKey, ); } - }, -}; -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}}', - slideLabelMessage: '{{index}} / {{slidesLength}}', - containerMessage: null, - containerRoleDescriptionMessage: null, - itemRoleDescriptionMessage: null, - slideRole: 'group', - }, - }, - create() { - const swiper = this; - bindModuleMethods(swiper, { - a11y: { - ...A11y, - liveRegion: $( - ``, - ), - }, - }); - }, - on: { - afterInit(swiper) { - if (!swiper.params.a11y.enabled) return; - swiper.a11y.init(); - swiper.a11y.updateNavigation(); - }, - toEdge(swiper) { - if (!swiper.params.a11y.enabled) return; - swiper.a11y.updateNavigation(); - }, - fromEdge(swiper) { - if (!swiper.params.a11y.enabled) return; - swiper.a11y.updateNavigation(); - }, - paginationUpdate(swiper) { - if (!swiper.params.a11y.enabled) return; - swiper.a11y.updatePagination(); - }, - destroy(swiper) { - if (!swiper.params.a11y.enabled) return; - swiper.a11y.destroy(); - }, - }, -}; + } + + on('afterInit', () => { + if (!swiper.params.a11y.enabled) return; + init(); + updateNavigation(); + }); + on('toEdge', () => { + if (!swiper.params.a11y.enabled) return; + updateNavigation(); + }); + on('fromEdge', () => { + if (!swiper.params.a11y.enabled) return; + updateNavigation(); + }); + on('paginationUpdate', () => { + if (!swiper.params.a11y.enabled) return; + updatePagination(); + }); + on('destroy', () => { + if (!swiper.params.a11y.enabled) return; + destroy(); + }); +}