diff --git a/package.json b/package.json index 4b9c7d040..b170dfbae 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-slick", - "version": "0.30.3", + "version": "0.30.4", "description": " React port of slick carousel", "main": "./lib", "files": [ diff --git a/src/slider.js b/src/slider.js index 7fdbe4a13..75c3d7011 100644 --- a/src/slider.js +++ b/src/slider.js @@ -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); @@ -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') @@ -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(); @@ -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" diff --git a/src/track.js b/src/track.js index b64e722e7..35671cc86 100644 --- a/src/track.js +++ b/src/track.js @@ -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; @@ -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) { @@ -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); @@ -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); @@ -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 (