Change-Id: I066c0e7f8ce87ec00b1141a1b44430444a819b42 (cherry picked from commit 05907a1a42da82737090d55046974d401f8af057)
283 lines
No EOL
7.6 KiB
JavaScript
283 lines
No EOL
7.6 KiB
JavaScript
import classesToSelector from '../../shared/classes-to-selector.js';
|
|
import $ from '../../shared/dom.js';
|
|
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'
|
|
}
|
|
});
|
|
let liveRegion = null;
|
|
|
|
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);
|
|
}
|
|
|
|
function makeElFocusable($el) {
|
|
$el.attr('tabIndex', '0');
|
|
}
|
|
|
|
function makeElNotFocusable($el) {
|
|
$el.attr('tabIndex', '-1');
|
|
}
|
|
|
|
function addElRole($el, role) {
|
|
$el.attr('role', role);
|
|
}
|
|
|
|
function addElRoleDescription($el, description) {
|
|
$el.attr('aria-roledescription', description);
|
|
}
|
|
|
|
function addElControls($el, controls) {
|
|
$el.attr('aria-controls', controls);
|
|
}
|
|
|
|
function addElLabel($el, label) {
|
|
$el.attr('aria-label', label);
|
|
}
|
|
|
|
function addElId($el, id) {
|
|
$el.attr('id', id);
|
|
}
|
|
|
|
function addElLive($el, live) {
|
|
$el.attr('aria-live', live);
|
|
}
|
|
|
|
function disableEl($el) {
|
|
$el.attr('aria-disabled', true);
|
|
}
|
|
|
|
function enableEl($el) {
|
|
$el.attr('aria-disabled', false);
|
|
}
|
|
|
|
function onEnterOrSpaceKey(e) {
|
|
if (e.keyCode !== 13 && e.keyCode !== 32) return;
|
|
const params = swiper.params.a11y;
|
|
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) {
|
|
notify(params.lastSlideMessage);
|
|
} else {
|
|
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) {
|
|
notify(params.firstSlideMessage);
|
|
} else {
|
|
notify(params.prevSlideMessage);
|
|
}
|
|
}
|
|
|
|
if (swiper.pagination && $targetEl.is(classesToSelector(swiper.params.pagination.bulletClass))) {
|
|
$targetEl[0].click();
|
|
}
|
|
}
|
|
|
|
function updateNavigation() {
|
|
if (swiper.params.loop || !swiper.navigation) return;
|
|
const {
|
|
$nextEl,
|
|
$prevEl
|
|
} = swiper.navigation;
|
|
|
|
if ($prevEl && $prevEl.length > 0) {
|
|
if (swiper.isBeginning) {
|
|
disableEl($prevEl);
|
|
makeElNotFocusable($prevEl);
|
|
} else {
|
|
enableEl($prevEl);
|
|
makeElFocusable($prevEl);
|
|
}
|
|
}
|
|
|
|
if ($nextEl && $nextEl.length > 0) {
|
|
if (swiper.isEnd) {
|
|
disableEl($nextEl);
|
|
makeElNotFocusable($nextEl);
|
|
} else {
|
|
enableEl($nextEl);
|
|
makeElFocusable($nextEl);
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
makeElFocusable($bulletEl);
|
|
|
|
if (!swiper.params.pagination.renderBullet) {
|
|
addElRole($bulletEl, 'button');
|
|
addElLabel($bulletEl, params.paginationBulletMessage.replace(/\{\{index\}\}/, $bulletEl.index() + 1));
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
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(liveRegion); // Container
|
|
|
|
const $containerEl = swiper.$el;
|
|
|
|
if (params.containerRoleDescriptionMessage) {
|
|
addElRoleDescription($containerEl, params.containerRoleDescriptionMessage);
|
|
}
|
|
|
|
if (params.containerMessage) {
|
|
addElLabel($containerEl, params.containerMessage);
|
|
} // Wrapper
|
|
|
|
|
|
const $wrapperEl = swiper.$wrapperEl;
|
|
const wrapperId = $wrapperEl.attr('id') || `swiper-wrapper-${getRandomNumber(16)}`;
|
|
const live = swiper.params.autoplay && swiper.params.autoplay.enabled ? 'off' : 'polite';
|
|
addElId($wrapperEl, wrapperId);
|
|
addElLive($wrapperEl, live); // Slide
|
|
|
|
if (params.itemRoleDescriptionMessage) {
|
|
addElRoleDescription($(swiper.slides), params.itemRoleDescriptionMessage);
|
|
}
|
|
|
|
addElRole($(swiper.slides), params.slideRole);
|
|
const slidesLength = swiper.params.loop ? swiper.slides.filter(el => !el.classList.contains(swiper.params.slideDuplicateClass)).length : swiper.slides.length;
|
|
swiper.slides.each((slideEl, index) => {
|
|
const $slideEl = $(slideEl);
|
|
const slideIndex = swiper.params.loop ? parseInt($slideEl.attr('data-swiper-slide-index'), 10) : index;
|
|
const ariaLabelMessage = params.slideLabelMessage.replace(/\{\{index\}\}/, slideIndex + 1).replace(/\{\{slidesLength\}\}/, slidesLength);
|
|
addElLabel($slideEl, ariaLabelMessage);
|
|
}); // Navigation
|
|
|
|
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.length) {
|
|
initNavEl($nextEl, wrapperId, params.nextSlideMessage);
|
|
}
|
|
|
|
if ($prevEl && $prevEl.length) {
|
|
initNavEl($prevEl, wrapperId, params.prevSlideMessage);
|
|
} // Pagination
|
|
|
|
|
|
if (hasPagination()) {
|
|
swiper.pagination.$el.on('keydown', classesToSelector(swiper.params.pagination.bulletClass), onEnterOrSpaceKey);
|
|
}
|
|
}
|
|
|
|
function destroy() {
|
|
if (liveRegion && liveRegion.length > 0) 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', onEnterOrSpaceKey);
|
|
}
|
|
|
|
if ($prevEl) {
|
|
$prevEl.off('keydown', onEnterOrSpaceKey);
|
|
} // Pagination
|
|
|
|
|
|
if (hasPagination()) {
|
|
swiper.pagination.$el.off('keydown', classesToSelector(swiper.params.pagination.bulletClass), onEnterOrSpaceKey);
|
|
}
|
|
}
|
|
|
|
on('beforeInit', () => {
|
|
liveRegion = $(`<span class="${swiper.params.a11y.notificationClass}" aria-live="assertive" aria-atomic="true"></span>`);
|
|
});
|
|
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();
|
|
});
|
|
} |