Skip to content

Feat: added tabindex logic for better accessibility support #2422

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-slick",
"version": "0.30.3",
"version": "0.30.4",
"description": " React port of slick carousel",
"main": "./lib",
"files": [
Expand Down
39 changes: 38 additions & 1 deletion src/slider.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,38 @@ export default class Slider extends React.Component {
breakpoint: null
};
this._responsiveMediaHandlers = [];
this.childRefs = new Set();
this.observer = null;
}

innerSliderRefHandler = ref => (this.innerSlider = ref);

setupIntersectObserver() {
if (this.observer) {
this.observer.disconnect();
}
this.observer = new IntersectionObserver(
entries => {
entries.forEach(entry => {
entry.target.tabIndex = entry.isIntersecting ? 0 : -1;
entry.target.setAttribute(
"aria-hidden",
entry.isIntersecting ? "false" : "true"
);
});
},
{
root: null,
threshold: 0.1
}
);
this.childRefs.forEach(element => {
if (element && element instanceof Element) {
this.observer.observe(element);
}
});
}

media(query, handler) {
// javascript handler for css media query
const mql = window.matchMedia(query);
Expand All @@ -31,6 +59,9 @@ export default class Slider extends React.Component {

// handles responsive breakpoints
componentDidMount() {
window.addEventListener("resize", this.setupIntersectionObserver);
window.addEventListener("scroll", this.setupIntersectionObserver);
setTimeout(() => this.setupIntersectObserver(), 0);
// performance monitoring
//if (process.env.NODE_ENV !== 'production') {
//const { whyDidYouUpdate } = require('why-did-you-update')
Expand Down Expand Up @@ -76,6 +107,8 @@ export default class Slider extends React.Component {
this._responsiveMediaHandlers.forEach(function(obj) {
obj.mql.removeListener(obj.listener);
});
window.removeEventListener("resize", this.setupIntersectionObserver);
window.removeEventListener("scroll", this.setupIntersectionObserver);
}

slickPrev = () => this.innerSlider.slickPrev();
Expand Down Expand Up @@ -179,7 +212,11 @@ export default class Slider extends React.Component {
row.push(
React.cloneElement(children[k], {
key: 100 * i + 10 * j + k,
tabIndex: -1,
ref: el => {
if (el) {
this.childRefs.add(el);
}
},
style: {
width: `${100 / settings.slidesPerRow}%`,
display: "inline-block"
Expand Down
76 changes: 68 additions & 8 deletions src/track.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ const renderSlides = spec => {
let childrenCount = React.Children.count(spec.children);
let startIndex = lazyStartIndex(spec);
let endIndex = lazyEndIndex(spec);
const childRefs = spec.childRefs;

React.Children.forEach(spec.children, (elem, index) => {
let child;
Expand Down Expand Up @@ -122,10 +123,17 @@ const renderSlides = spec => {
React.cloneElement(child, {
key: "original" + getKey(child, index),
"data-index": index,
ref: el => {
if (el) {
childRefs.add(el);
}
},
className: classnames(slideClasses, slideClass),
tabIndex: "-1",
"aria-hidden": !slideClasses["slick-active"],
style: { outline: "none", ...(child.props.style || {}), ...childStyle },
style: {
outline: "none",
...(child.props.style || {}),
...childStyle
},
onClick: e => {
child.props && child.props.onClick && child.props.onClick(e);
if (spec.focusOnSelect) {
Expand Down Expand Up @@ -153,9 +161,12 @@ const renderSlides = spec => {
React.cloneElement(child, {
key: "precloned" + getKey(child, key),
"data-index": key,
tabIndex: "-1",
ref: el => {
if (el) {
childRefs.add(el);
}
},
className: classnames(slideClasses, slideClass),
"aria-hidden": !slideClasses["slick-active"],
style: { ...(child.props.style || {}), ...childStyle },
onClick: e => {
child.props && child.props.onClick && child.props.onClick(e);
Expand All @@ -176,9 +187,12 @@ const renderSlides = spec => {
React.cloneElement(child, {
key: "postcloned" + getKey(child, key),
"data-index": key,
tabIndex: "-1",
ref: el => {
if (el) {
childRefs.add(el);
}
},
className: classnames(slideClasses, slideClass),
"aria-hidden": !slideClasses["slick-active"],
style: { ...(child.props.style || {}), ...childStyle },
onClick: e => {
child.props && child.props.onClick && child.props.onClick(e);
Expand All @@ -201,12 +215,58 @@ const renderSlides = spec => {
export class Track extends React.PureComponent {
node = null;

constructor(props) {
super(props);
this.childRefs = new Set();
this.observer = null;
}

handleRef = ref => {
this.node = ref;
};

componentDidMount() {
window.addEventListener("resize", this.setupIntersectionObserver);
window.addEventListener("scroll", this.setupIntersectionObserver);
setTimeout(() => this.setupIntersectObserver(), 0);
}

componentWillUnmount() {
window.removeEventListener("resize", this.setupIntersectionObserver);
window.removeEventListener("scroll", this.setupIntersectionObserver);
}

setupIntersectObserver() {
if (this.observer) {
this.observer.disconnect();
}
this.observer = new IntersectionObserver(
entries => {
entries.forEach(entry => {
entry.target.tabIndex = entry.isIntersecting ? 0 : -1;
entry.target.setAttribute(
"aria-hidden",
entry.isIntersecting ? "false" : "true"
);
});
},
{
root: null,
threshold: 0.1
}
);
this.childRefs.forEach(element => {
if (element && element instanceof Element) {
this.observer.observe(element);
}
});
}

render() {
const slides = renderSlides(this.props);
const slides = renderSlides({
childRefs: this.childRefs,
...this.props
});
const { onMouseEnter, onMouseOver, onMouseLeave } = this.props;
const mouseEvents = { onMouseEnter, onMouseOver, onMouseLeave };
return (
Expand Down