UNPKG

swiper

Version:

Most modern mobile touch slider and framework with hardware accelerated transitions

403 lines (400 loc) 15.1 kB
import { c as classesToSelector } from '../shared/classes-to-selector.mjs'; import { a as createElement, s as makeElementsArray, b as elementIndex, w as setInnerHTML } from '../shared/utils.mjs'; const isVirtualEnabled = (swiper) => !!swiper.virtual && !!swiper.params.virtual?.enabled; const 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, containerRole: null, itemRoleDescriptionMessage: null, slideRole: 'group', id: null, scrollOnFocus: true, wrapperLiveRegion: true, }, }); swiper.a11y = { clicked: false, }; let liveRegion = null; let preventFocusHandler = false; let focusTargetSlideEl; let visibilityChangedTimestamp = new Date().getTime(); function getParams() { return swiper.params.a11y; } function notify(message) { const notification = liveRegion; if (!notification || !message) return; setInnerHTML(notification, message); } function getRandomNumber(size = 16) { const randomChar = () => Math.round(16 * Math.random()).toString(16); return 'x'.repeat(size).replace(/x/g, randomChar); } function makeElFocusable(el) { const els = makeElementsArray(el); els.forEach((subEl) => { subEl.setAttribute('tabIndex', '0'); }); } function makeElNotFocusable(el) { const els = makeElementsArray(el); els.forEach((subEl) => { subEl.setAttribute('tabIndex', '-1'); }); } function addElRole(el, role) { const els = makeElementsArray(el); els.forEach((subEl) => { subEl.setAttribute('role', role); }); } function addElRoleDescription(el, description) { const els = makeElementsArray(el); els.forEach((subEl) => { subEl.setAttribute('aria-roledescription', description); }); } function addElLabel(el, label) { const els = makeElementsArray(el); els.forEach((subEl) => { subEl.setAttribute('aria-label', label); }); } function addElId(el, id) { const els = makeElementsArray(el); els.forEach((subEl) => { subEl.setAttribute('id', id); }); } function addElLive(el, live) { const els = makeElementsArray(el); els.forEach((subEl) => { subEl.setAttribute('aria-live', live); }); } function disableEl(el) { const els = makeElementsArray(el); els.forEach((subEl) => { subEl.setAttribute('aria-disabled', 'true'); }); } function enableEl(el) { const els = makeElementsArray(el); els.forEach((subEl) => { subEl.removeAttribute('aria-disabled'); }); } function onEnterOrSpaceKey(e) { if (e.keyCode !== 13 && e.keyCode !== 32) return; const params = getParams(); const paginationParams = swiper.params.pagination; const targetEl = e.target; if (swiper.pagination && swiper.pagination.el && (targetEl === swiper.pagination.el || swiper.pagination.el.contains(targetEl))) { if (!targetEl.matches(classesToSelector(paginationParams?.bulletClass))) return; } if (swiper.navigation && swiper.navigation.prevEl && swiper.navigation.nextEl) { const prevEls = makeElementsArray(swiper.navigation.prevEl); const nextEls = makeElementsArray(swiper.navigation.nextEl); if (nextEls.includes(targetEl)) { if (!(swiper.isEnd && !swiper.params.loop)) { swiper.slideNext(); } if (swiper.isEnd) { notify(params.lastSlideMessage); } else { notify(params.nextSlideMessage); } } if (prevEls.includes(targetEl)) { if (!(swiper.isBeginning && !swiper.params.loop)) { swiper.slidePrev(); } if (swiper.isBeginning) { notify(params.firstSlideMessage); } else { notify(params.prevSlideMessage); } } } if (swiper.pagination && targetEl.matches(classesToSelector(paginationParams?.bulletClass))) { targetEl.click(); } } function updateNavigation() { if (swiper.params.loop || swiper.params.rewind || !swiper.navigation) return; const { nextEl, prevEl } = swiper.navigation; if (prevEl) { if (swiper.isBeginning) { disableEl(prevEl); makeElNotFocusable(prevEl); } else { enableEl(prevEl); makeElFocusable(prevEl); } } if (nextEl) { if (swiper.isEnd) { disableEl(nextEl); makeElNotFocusable(nextEl); } else { enableEl(nextEl); makeElFocusable(nextEl); } } } function hasPagination() { return !!(swiper.pagination && swiper.pagination.bullets && swiper.pagination.bullets.length); } function hasClickablePagination() { const paginationParams = swiper.params.pagination; return hasPagination() && !!paginationParams?.clickable; } function updatePagination() { const params = getParams(); if (!hasPagination()) return; const paginationParams = swiper.params.pagination; swiper.pagination.bullets.forEach((bulletEl) => { if (paginationParams.clickable) { makeElFocusable(bulletEl); if (!paginationParams.renderBullet) { addElRole(bulletEl, 'button'); addElLabel(bulletEl, params.paginationBulletMessage.replace(/\{\{index\}\}/, String((elementIndex(bulletEl) ?? 0) + 1))); } } if (bulletEl.matches(classesToSelector(paginationParams.bulletActiveClass))) { bulletEl.setAttribute('aria-current', 'true'); } else { bulletEl.removeAttribute('aria-current'); } }); } const initNavEl = (el, _wrapperId, message) => { makeElFocusable(el); if (el.tagName !== 'BUTTON') { addElRole(el, 'button'); el.addEventListener('keydown', onEnterOrSpaceKey); } addElLabel(el, message); }; const handlePointerDown = (e) => { if (focusTargetSlideEl && focusTargetSlideEl !== e.target && !focusTargetSlideEl.contains(e.target)) { preventFocusHandler = true; } swiper.a11y.clicked = true; }; const handlePointerUp = () => { preventFocusHandler = false; requestAnimationFrame(() => { requestAnimationFrame(() => { if (!swiper.destroyed) { swiper.a11y.clicked = false; } }); }); }; const onVisibilityChange = (_e) => { visibilityChangedTimestamp = new Date().getTime(); }; const handleFocus = (e) => { const params = getParams(); if (swiper.a11y.clicked || !params.scrollOnFocus) return; if (new Date().getTime() - visibilityChangedTimestamp < 100) return; const target = e.target; const slideEl = target.closest(`.${swiper.params.slideClass}, swiper-slide`); if (!slideEl || !swiper.slides.includes(slideEl)) return; focusTargetSlideEl = slideEl; const isVirtual = isVirtualEnabled(swiper); const isActive = (isVirtual ? parseInt(slideEl.getAttribute('data-swiper-slide-index') || '0', 10) : swiper.slides.indexOf(slideEl)) === swiper.activeIndex; const isVisible = swiper.params.watchSlidesProgress && swiper.visibleSlides && swiper.visibleSlides.includes(slideEl); if (isActive || isVisible) return; const sourceCapabilities = e.sourceCapabilities; if (sourceCapabilities && sourceCapabilities.firesTouchEvents) return; if (swiper.isHorizontal()) { swiper.el.scrollLeft = 0; } else { swiper.el.scrollTop = 0; } requestAnimationFrame(() => { if (preventFocusHandler) return; if (swiper.params.loop) { swiper.slideToLoop(swiper.getSlideIndexWhenGrid(parseInt(slideEl.getAttribute('data-swiper-slide-index') || '0', 10)), 0); } else if (isVirtual) { swiper.slideTo(swiper.getSlideIndexWhenGrid(parseInt(slideEl.getAttribute('data-swiper-slide-index') || '0', 10)), 0); } else { swiper.slideTo(swiper.getSlideIndexWhenGrid(swiper.slides.indexOf(slideEl)), 0); } preventFocusHandler = false; }); }; const initSlides = () => { const params = getParams(); if (params.itemRoleDescriptionMessage) { addElRoleDescription(swiper.slides, params.itemRoleDescriptionMessage); } if (params.slideRole) { addElRole(swiper.slides, params.slideRole); } const slidesLength = swiper.slides.length; const slideLabelMessage = params.slideLabelMessage; if (slideLabelMessage) { swiper.slides.forEach((slideEl, index) => { const slideIndex = swiper.params.loop ? parseInt(slideEl.getAttribute('data-swiper-slide-index') || '0', 10) : index; const ariaLabelMessage = slideLabelMessage .replace(/\{\{index\}\}/, String(slideIndex + 1)) .replace(/\{\{slidesLength\}\}/, String(slidesLength)); addElLabel(slideEl, ariaLabelMessage); }); } }; const init = () => { const params = getParams(); if (liveRegion) swiper.el.append(liveRegion); // Container const containerEl = swiper.el; if (params.containerRoleDescriptionMessage) { addElRoleDescription(containerEl, params.containerRoleDescriptionMessage); } if (params.containerMessage) { addElLabel(containerEl, params.containerMessage); } if (params.containerRole) { addElRole(containerEl, params.containerRole); } // Wrapper const wrapperEl = swiper.wrapperEl; const wrapperId = String(params.id || wrapperEl.getAttribute('id') || `swiper-wrapper-${getRandomNumber(16)}`); addElId(wrapperEl, wrapperId); if (params.wrapperLiveRegion) { const autoplayParams = swiper.params.autoplay; const live = swiper.params.autoplay && autoplayParams?.enabled ? 'off' : 'polite'; addElLive(wrapperEl, live); } // Slide initSlides(); // Navigation const nav = swiper.navigation ? swiper.navigation : { nextEl: undefined, prevEl: undefined }; const nextEls = makeElementsArray(nav.nextEl); const prevEls = makeElementsArray(nav.prevEl); if (nextEls) { nextEls.forEach((el) => initNavEl(el, wrapperId, params.nextSlideMessage)); } if (prevEls) { prevEls.forEach((el) => initNavEl(el, wrapperId, params.prevSlideMessage)); } // Pagination if (hasClickablePagination()) { const paginationEl = makeElementsArray(swiper.pagination.el); paginationEl.forEach((el) => { el.addEventListener('keydown', onEnterOrSpaceKey); }); } // Tab focus document.addEventListener('visibilitychange', onVisibilityChange); swiper.el.addEventListener('focus', handleFocus, true); swiper.el.addEventListener('pointerdown', handlePointerDown, true); swiper.el.addEventListener('pointerup', handlePointerUp, true); }; function destroy() { if (liveRegion) liveRegion.remove(); const nav = swiper.navigation ? swiper.navigation : { nextEl: undefined, prevEl: undefined }; const nextEls = makeElementsArray(nav.nextEl); const prevEls = makeElementsArray(nav.prevEl); if (nextEls) { nextEls.forEach((el) => el.removeEventListener('keydown', onEnterOrSpaceKey)); } if (prevEls) { prevEls.forEach((el) => el.removeEventListener('keydown', onEnterOrSpaceKey)); } // Pagination if (hasClickablePagination()) { const paginationEl = makeElementsArray(swiper.pagination.el); paginationEl.forEach((el) => { el.removeEventListener('keydown', onEnterOrSpaceKey); }); } document.removeEventListener('visibilitychange', onVisibilityChange); // Tab focus if (swiper.el && typeof swiper.el !== 'string') { swiper.el.removeEventListener('focus', handleFocus, true); swiper.el.removeEventListener('pointerdown', handlePointerDown, true); swiper.el.removeEventListener('pointerup', handlePointerUp, true); } } on('beforeInit', () => { liveRegion = createElement('span', getParams().notificationClass); liveRegion.setAttribute('aria-live', 'assertive'); liveRegion.setAttribute('aria-atomic', 'true'); }); on('afterInit', () => { if (!getParams().enabled) return; init(); }); on('slidesLengthChange snapGridLengthChange slidesGridLengthChange', () => { if (!getParams().enabled) return; initSlides(); }); on('fromEdge toEdge afterInit lock unlock', () => { if (!getParams().enabled) return; updateNavigation(); }); on('paginationUpdate', () => { if (!getParams().enabled) return; updatePagination(); }); on('destroy', () => { if (!getParams().enabled) return; destroy(); }); }; export { A11y as default };