react-image-gallery
Advanced tools
Comparing version 0.9.1 to 1.0.0
@@ -11,2 +11,6 @@ 'use strict'; | ||
var _clsx = require('clsx'); | ||
var _clsx2 = _interopRequireDefault(_clsx); | ||
var _react = require('react'); | ||
@@ -26,2 +30,6 @@ | ||
var _lodash5 = require('lodash.isequal'); | ||
var _lodash6 = _interopRequireDefault(_lodash5); | ||
var _resizeObserverPolyfill = require('resize-observer-polyfill'); | ||
@@ -33,4 +41,6 @@ | ||
var _propTypes2 = _interopRequireDefault(_propTypes); | ||
var _SVG = require('./SVG'); | ||
var _SVG2 = _interopRequireDefault(_SVG); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -46,2 +56,14 @@ | ||
var imageSetType = (0, _propTypes.arrayOf)((0, _propTypes.shape)({ | ||
srcSet: _propTypes.string, | ||
media: _propTypes.string | ||
})); | ||
function isEnterOrSpaceKey(event) { | ||
var key = parseInt(event.keyCode || event.which || 0, 10); | ||
var ENTER_KEY_CODE = 66; | ||
var SPACEBAR_KEY_CODE = 62; | ||
return key === ENTER_KEY_CODE || key === SPACEBAR_KEY_CODE; | ||
} | ||
var ImageGallery = function (_React$Component) { | ||
@@ -55,333 +77,2 @@ _inherits(ImageGallery, _React$Component); | ||
_this.slideToIndex = function (index, event) { | ||
var _this$state = _this.state, | ||
currentIndex = _this$state.currentIndex, | ||
isTransitioning = _this$state.isTransitioning; | ||
if (!isTransitioning) { | ||
if (event) { | ||
if (_this._intervalId) { | ||
// user triggered event while ImageGallery is playing, reset interval | ||
_this.pause(false); | ||
_this.play(false); | ||
} | ||
} | ||
var slideCount = _this.props.items.length - 1; | ||
var nextIndex = index; | ||
if (index < 0) { | ||
nextIndex = slideCount; | ||
} else if (index > slideCount) { | ||
nextIndex = 0; | ||
} | ||
_this.setState({ | ||
previousIndex: currentIndex, | ||
currentIndex: nextIndex, | ||
isTransitioning: nextIndex !== currentIndex, | ||
offsetPercentage: 0, | ||
style: { | ||
transition: 'all ' + _this.props.slideDuration + 'ms ease-out' | ||
} | ||
}, _this._onSliding); | ||
} | ||
}; | ||
_this._onSliding = function () { | ||
var isTransitioning = _this.state.isTransitioning; | ||
_this._transitionTimer = window.setTimeout(function () { | ||
if (isTransitioning) { | ||
_this.setState({ isTransitioning: !isTransitioning }); | ||
if (_this.props.onSlide) { | ||
_this.props.onSlide(_this.state.currentIndex); | ||
} | ||
} | ||
}, _this.props.slideDuration + 50); | ||
}; | ||
_this._handleScreenChange = function () { | ||
/* | ||
handles screen change events that the browser triggers e.g. esc key | ||
*/ | ||
var fullScreenElement = document.fullscreenElement || document.msFullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement; | ||
if (_this.props.onScreenChange) { | ||
_this.props.onScreenChange(fullScreenElement); | ||
} | ||
_this.setState({ isFullscreen: !!fullScreenElement }); | ||
}; | ||
_this._toggleFullScreen = function () { | ||
if (_this.state.isFullscreen) { | ||
_this.exitFullScreen(); | ||
} else { | ||
_this.fullScreen(); | ||
} | ||
}; | ||
_this._togglePlay = function () { | ||
if (_this._intervalId) { | ||
_this.pause(); | ||
} else { | ||
_this.play(); | ||
} | ||
}; | ||
_this._initGalleryResizing = function (element) { | ||
/* | ||
When image-gallery-slide-wrapper unmounts and mounts when thumbnail bar position is changed | ||
ref is called twice, once with null and another with the element. | ||
Make sure element is available before calling observe. | ||
*/ | ||
if (element) { | ||
_this._imageGallerySlideWrapper = element; | ||
_this.resizeObserver = new _resizeObserverPolyfill2.default(_this._createResizeObserver); | ||
_this.resizeObserver.observe(element); | ||
} | ||
}; | ||
_this._createResizeObserver = (0, _lodash4.default)(function (entries) { | ||
if (!entries) return; | ||
entries.forEach(function () { | ||
_this._handleResize(); | ||
}); | ||
}, 300); | ||
_this._handleResize = function () { | ||
var currentIndex = _this.state.currentIndex; | ||
if (_this._imageGallery) { | ||
_this.setState({ | ||
galleryWidth: _this._imageGallery.offsetWidth | ||
}); | ||
} | ||
if (_this._imageGallerySlideWrapper) { | ||
_this.setState({ | ||
gallerySlideWrapperHeight: _this._imageGallerySlideWrapper.offsetHeight | ||
}); | ||
} | ||
if (_this._thumbnailsWrapper) { | ||
if (_this._isThumbnailVertical()) { | ||
_this.setState({ thumbnailsWrapperHeight: _this._thumbnailsWrapper.offsetHeight }); | ||
} else { | ||
_this.setState({ thumbnailsWrapperWidth: _this._thumbnailsWrapper.offsetWidth }); | ||
} | ||
} | ||
// Adjust thumbnail container when thumbnail width or height is adjusted | ||
_this._setThumbsTranslate(-_this._getThumbsTranslate(currentIndex)); | ||
}; | ||
_this._handleKeyDown = function (event) { | ||
if (_this.props.disableArrowKeys) { | ||
return; | ||
} | ||
var LEFT_ARROW = 37; | ||
var RIGHT_ARROW = 39; | ||
var ESC_KEY = 27; | ||
var key = parseInt(event.keyCode || event.which || 0); | ||
switch (key) { | ||
case LEFT_ARROW: | ||
if (_this._canSlideLeft() && !_this._intervalId) { | ||
_this._slideLeft(); | ||
} | ||
break; | ||
case RIGHT_ARROW: | ||
if (_this._canSlideRight() && !_this._intervalId) { | ||
_this._slideRight(); | ||
} | ||
break; | ||
case ESC_KEY: | ||
if (_this.state.isFullscreen && !_this.props.useBrowserFullscreen) { | ||
_this.exitFullScreen(); | ||
} | ||
} | ||
}; | ||
_this._handleImageError = function (event) { | ||
if (_this.props.defaultImage && event.target.src.indexOf(_this.props.defaultImage) === -1) { | ||
event.target.src = _this.props.defaultImage; | ||
} | ||
}; | ||
_this._handleOnSwiped = function (_ref) { | ||
var event = _ref.event, | ||
dir = _ref.dir, | ||
velocity = _ref.velocity; | ||
if (_this.props.disableSwipe) return; | ||
var _this$state2 = _this.state, | ||
scrollingUpDown = _this$state2.scrollingUpDown, | ||
scrollingLeftRight = _this$state2.scrollingLeftRight; | ||
var isRTL = _this.props.isRTL; | ||
if (_this.props.stopPropagation) event.stopPropagation(); | ||
if (scrollingUpDown) { | ||
// user stopped scrollingUpDown | ||
_this.setState({ scrollingUpDown: false }); | ||
} | ||
if (scrollingLeftRight) { | ||
// user stopped scrollingLeftRight | ||
_this.setState({ scrollingLeftRight: false }); | ||
} | ||
if (!scrollingUpDown) { | ||
// don't swipe if user is scrolling | ||
var side = (dir === _reactSwipeable.LEFT ? 1 : -1) * (isRTL ? -1 : 1); // if it is RTL the direction is reversed | ||
var isFlick = velocity > _this.props.flickThreshold; | ||
_this._handleOnSwipedTo(side, isFlick); | ||
} | ||
}; | ||
_this._handleSwiping = function (_ref2) { | ||
var event = _ref2.event, | ||
absX = _ref2.absX, | ||
dir = _ref2.dir; | ||
if (_this.props.disableSwipe) return; | ||
var _this$state3 = _this.state, | ||
galleryWidth = _this$state3.galleryWidth, | ||
isTransitioning = _this$state3.isTransitioning, | ||
scrollingUpDown = _this$state3.scrollingUpDown, | ||
scrollingLeftRight = _this$state3.scrollingLeftRight; | ||
var swipingTransitionDuration = _this.props.swipingTransitionDuration; | ||
_this._setScrollDirection(dir); | ||
if (_this.props.stopPropagation) event.stopPropagation(); | ||
if ((_this.props.preventDefaultTouchmoveEvent || scrollingLeftRight) && event.cancelable) event.preventDefault(); | ||
if (!isTransitioning && !scrollingUpDown) { | ||
var side = dir === _reactSwipeable.RIGHT ? 1 : -1; | ||
var offsetPercentage = absX / galleryWidth * 100; | ||
if (Math.abs(offsetPercentage) >= 100) { | ||
offsetPercentage = 100; | ||
} | ||
var swipingTransition = { | ||
transition: 'transform ' + swipingTransitionDuration + 'ms ease-out' | ||
}; | ||
_this.setState({ | ||
offsetPercentage: side * offsetPercentage, | ||
style: swipingTransition | ||
}); | ||
} else { | ||
// don't move the slide | ||
_this.setState({ offsetPercentage: 0 }); | ||
} | ||
}; | ||
_this._slideLeft = function () { | ||
_this.props.isRTL ? _this._slideNext() : _this._slidePrevious(); | ||
}; | ||
_this._slideRight = function () { | ||
_this.props.isRTL ? _this._slidePrevious() : _this._slideNext(); | ||
}; | ||
_this._slidePrevious = function (event) { | ||
_this.slideToIndex(_this.state.currentIndex - 1, event); | ||
}; | ||
_this._slideNext = function (event) { | ||
_this.slideToIndex(_this.state.currentIndex + 1, event); | ||
}; | ||
_this._renderItem = function (item) { | ||
var onImageError = _this.props.onImageError || _this._handleImageError; | ||
return _react2.default.createElement( | ||
'div', | ||
{ className: 'image-gallery-image' }, | ||
item.imageSet ? _react2.default.createElement( | ||
'picture', | ||
{ | ||
onLoad: _this.props.onImageLoad, | ||
onError: onImageError | ||
}, | ||
item.imageSet.map(function (source, index) { | ||
return _react2.default.createElement('source', { | ||
key: index, | ||
media: source.media, | ||
srcSet: source.srcSet, | ||
type: source.type | ||
}); | ||
}), | ||
_react2.default.createElement('img', { | ||
alt: item.originalAlt, | ||
src: item.original | ||
}) | ||
) : _react2.default.createElement('img', { | ||
src: item.original, | ||
alt: item.originalAlt, | ||
srcSet: item.srcSet, | ||
sizes: item.sizes, | ||
title: item.originalTitle, | ||
onLoad: _this.props.onImageLoad, | ||
onError: onImageError | ||
}), | ||
item.description && _react2.default.createElement( | ||
'span', | ||
{ className: 'image-gallery-description' }, | ||
item.description | ||
) | ||
); | ||
}; | ||
_this._renderThumbInner = function (item) { | ||
var onThumbnailError = _this.props.onThumbnailError || _this._handleImageError; | ||
return _react2.default.createElement( | ||
'div', | ||
{ className: 'image-gallery-thumbnail-inner' }, | ||
_react2.default.createElement('img', { | ||
src: item.thumbnail, | ||
alt: item.thumbnailAlt, | ||
title: item.thumbnailTitle, | ||
onError: onThumbnailError | ||
}), | ||
item.thumbnailLabel && _react2.default.createElement( | ||
'div', | ||
{ className: 'image-gallery-thumbnail-label' }, | ||
item.thumbnailLabel | ||
) | ||
); | ||
}; | ||
_this._onThumbnailClick = function (event, index) { | ||
_this.slideToIndex(index, event); | ||
if (_this.props.onThumbnailClick) { | ||
_this.props.onThumbnailClick(event, index); | ||
} | ||
}; | ||
_this._onThumbnailMouseOver = function (event, index) { | ||
if (_this._thumbnailMouseOverTimer) { | ||
window.clearTimeout(_this._thumbnailMouseOverTimer); | ||
_this._thumbnailMouseOverTimer = null; | ||
} | ||
_this._thumbnailMouseOverTimer = window.setTimeout(function () { | ||
_this.slideToIndex(index); | ||
_this.pause(); | ||
}, 300); | ||
}; | ||
_this._onThumbnailMouseLeave = function () { | ||
if (_this._thumbnailMouseOverTimer) { | ||
window.clearTimeout(_this._thumbnailMouseOverTimer); | ||
_this._thumbnailMouseOverTimer = null; | ||
if (_this.props.autoPlay) { | ||
_this.play(); | ||
} | ||
} | ||
}; | ||
_this.state = { | ||
@@ -397,9 +88,29 @@ currentIndex: props.startIndex, | ||
}; | ||
_this.loadedImages = {}; | ||
_this.imageGallery = _react2.default.createRef(); | ||
_this.thumbnailsWrapper = _react2.default.createRef(); | ||
_this.thumbnails = _react2.default.createRef(); | ||
_this.imageGallerySlideWrapper = _react2.default.createRef(); | ||
// bindings | ||
_this.handleKeyDown = _this.handleKeyDown.bind(_this); | ||
_this.handleMouseDown = _this.handleMouseDown.bind(_this); | ||
_this.handleOnSwiped = _this.handleOnSwiped.bind(_this); | ||
_this.handleScreenChange = _this.handleScreenChange.bind(_this); | ||
_this.handleSwiping = _this.handleSwiping.bind(_this); | ||
_this.onThumbnailMouseLeave = _this.onThumbnailMouseLeave.bind(_this); | ||
_this.pauseOrPlay = _this.pauseOrPlay.bind(_this); | ||
_this.renderThumbInner = _this.renderThumbInner.bind(_this); | ||
_this.renderItem = _this.renderItem.bind(_this); | ||
_this.slideLeft = _this.slideLeft.bind(_this); | ||
_this.slideRight = _this.slideRight.bind(_this); | ||
_this.toggleFullScreen = _this.toggleFullScreen.bind(_this); | ||
_this.togglePlay = _this.togglePlay.bind(_this); | ||
// Used to update the throttle if slideDuration changes | ||
_this._unthrottledSlideToIndex = _this.slideToIndex; | ||
_this.slideToIndex = (0, _lodash2.default)(_this._unthrottledSlideToIndex, props.slideDuration, { trailing: false }); | ||
_this.unthrottledSlideToIndex = _this.slideToIndex; | ||
_this.slideToIndex = (0, _lodash2.default)(_this.unthrottledSlideToIndex, props.slideDuration, { trailing: false }); | ||
if (props.lazyLoad) { | ||
_this._lazyLoaded = []; | ||
_this.lazyLoaded = []; | ||
} | ||
@@ -412,7 +123,11 @@ return _this; | ||
value: function componentDidMount() { | ||
if (this.props.autoPlay) { | ||
var autoPlay = this.props.autoPlay; | ||
if (autoPlay) { | ||
this.play(); | ||
} | ||
window.addEventListener('keydown', this._handleKeyDown); | ||
this._onScreenChangeEvent(); | ||
window.addEventListener('keydown', this.handleKeyDown); | ||
window.addEventListener('mousedown', this.handleMouseDown); | ||
this.initResizeObserver(this.imageGallerySlideWrapper); | ||
this.addScreenChangeEvent(); | ||
} | ||
@@ -422,21 +137,39 @@ }, { | ||
value: function componentDidUpdate(prevProps, prevState) { | ||
var itemsSizeChanged = prevProps.items.length !== this.props.items.length; | ||
var itemsChanged = JSON.stringify(prevProps.items) !== JSON.stringify(this.props.items); | ||
var startIndexUpdated = prevProps.startIndex !== this.props.startIndex; | ||
var _props = this.props, | ||
items = _props.items, | ||
lazyLoad = _props.lazyLoad, | ||
slideDuration = _props.slideDuration, | ||
startIndex = _props.startIndex, | ||
thumbnailPosition = _props.thumbnailPosition; | ||
var currentIndex = this.state.currentIndex; | ||
var itemsSizeChanged = prevProps.items.length !== items.length; | ||
var itemsChanged = !(0, _lodash6.default)(prevProps.items, items); | ||
var startIndexUpdated = prevProps.startIndex !== startIndex; | ||
var thumbnailsPositionChanged = prevProps.thumbnailPosition !== thumbnailPosition; | ||
if (thumbnailsPositionChanged) { | ||
// re-initialize resizeObserver because slides was unmounted and mounted again | ||
this.removeResizeObserver(); | ||
this.initResizeObserver(this.imageGallerySlideWrapper); | ||
} | ||
if (itemsSizeChanged) { | ||
this._handleResize(); | ||
this.handleResize(); | ||
} | ||
if (prevState.currentIndex !== this.state.currentIndex) { | ||
this._slideThumbnailBar(prevState.currentIndex); | ||
if (prevState.currentIndex !== currentIndex) { | ||
this.slideThumbnailBar(prevState.currentIndex); | ||
} | ||
// if slideDuration changes, update slideToIndex throttle | ||
if (prevProps.slideDuration !== this.props.slideDuration) { | ||
this.slideToIndex = (0, _lodash2.default)(this._unthrottledSlideToIndex, this.props.slideDuration, { trailing: false }); | ||
if (prevProps.slideDuration !== slideDuration) { | ||
this.slideToIndex = (0, _lodash2.default)(this.unthrottledSlideToIndex, slideDuration, { trailing: false }); | ||
} | ||
if (this.props.lazyLoad && (!prevProps.lazyLoad || itemsChanged)) { | ||
this._lazyLoaded = []; | ||
if (lazyLoad && (!prevProps.lazyLoad || itemsChanged)) { | ||
this.lazyLoaded = []; | ||
} | ||
if (startIndexUpdated || itemsChanged) { | ||
this.setState({ currentIndex: this.props.startIndex }); | ||
// TODO: this should be fix/removed if all it is doing | ||
// is resetting the gallery currentIndext state | ||
this.setState({ currentIndex: startIndex }); | ||
} | ||
@@ -447,159 +180,80 @@ } | ||
value: function componentWillUnmount() { | ||
window.removeEventListener('keydown', this._handleKeyDown); | ||
this._offScreenChangeEvent(); | ||
if (this._intervalId) { | ||
window.clearInterval(this._intervalId); | ||
this._intervalId = null; | ||
window.removeEventListener('keydown', this.handleKeyDown); | ||
window.removeEventListener('mousedown', this.handleMouseDown); | ||
this.removeScreenChangeEvent(); | ||
this.removeResizeObserver(); | ||
if (this.intervalId) { | ||
window.clearInterval(this.intervalId); | ||
this.intervalId = null; | ||
} | ||
if (this.resizeObserver && this._imageGallerySlideWrapper) { | ||
this.resizeObserver.unobserve(this._imageGallerySlideWrapper); | ||
if (this.transitionTimer) { | ||
window.clearTimeout(this.transitionTimer); | ||
} | ||
if (this._transitionTimer) { | ||
window.clearTimeout(this._transitionTimer); | ||
} | ||
if (this._createResizeObserver) { | ||
this._createResizeObserver(); | ||
} | ||
} | ||
}, { | ||
key: 'play', | ||
value: function play() { | ||
key: 'onSliding', | ||
value: function onSliding() { | ||
var _this2 = this; | ||
var callback = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; | ||
var _state = this.state, | ||
currentIndex = _state.currentIndex, | ||
isTransitioning = _state.isTransitioning; | ||
var _props2 = this.props, | ||
onSlide = _props2.onSlide, | ||
slideDuration = _props2.slideDuration; | ||
if (!this._intervalId) { | ||
var _props = this.props, | ||
slideInterval = _props.slideInterval, | ||
slideDuration = _props.slideDuration; | ||
this.setState({ isPlaying: true }); | ||
this._intervalId = window.setInterval(function () { | ||
if (!_this2.props.infinite && !_this2._canSlideRight()) { | ||
_this2.pause(); | ||
} else { | ||
_this2.slideToIndex(_this2.state.currentIndex + 1); | ||
this.transitionTimer = window.setTimeout(function () { | ||
if (isTransitioning) { | ||
_this2.setState({ isTransitioning: !isTransitioning }); | ||
if (onSlide) { | ||
onSlide(currentIndex); | ||
} | ||
}, Math.max(slideInterval, slideDuration)); | ||
if (this.props.onPlay && callback) { | ||
this.props.onPlay(this.state.currentIndex); | ||
} | ||
} | ||
}, slideDuration + 50); | ||
} | ||
}, { | ||
key: 'pause', | ||
value: function pause() { | ||
var callback = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; | ||
key: 'onThumbnailClick', | ||
value: function onThumbnailClick(event, index) { | ||
var onThumbnailClick = this.props.onThumbnailClick; | ||
if (this._intervalId) { | ||
window.clearInterval(this._intervalId); | ||
this._intervalId = null; | ||
this.setState({ isPlaying: false }); | ||
if (this.props.onPause && callback) { | ||
this.props.onPause(this.state.currentIndex); | ||
} | ||
this.slideToIndex(index, event); | ||
if (onThumbnailClick) { | ||
onThumbnailClick(event, index); | ||
} | ||
} | ||
}, { | ||
key: 'setModalFullscreen', | ||
value: function setModalFullscreen(state) { | ||
this.setState({ modalFullscreen: state }); | ||
// manually call because browser does not support screenchange events | ||
if (this.props.onScreenChange) { | ||
this.props.onScreenChange(state); | ||
key: 'onThumbnailMouseOver', | ||
value: function onThumbnailMouseOver(event, index) { | ||
var _this3 = this; | ||
if (this.thumbnailMouseOverTimer) { | ||
window.clearTimeout(this.thumbnailMouseOverTimer); | ||
this.thumbnailMouseOverTimer = null; | ||
} | ||
this.thumbnailMouseOverTimer = window.setTimeout(function () { | ||
_this3.slideToIndex(index); | ||
_this3.pause(); | ||
}, 300); | ||
} | ||
}, { | ||
key: 'fullScreen', | ||
value: function fullScreen() { | ||
var gallery = this._imageGallery; | ||
key: 'onThumbnailMouseLeave', | ||
value: function onThumbnailMouseLeave() { | ||
if (this.thumbnailMouseOverTimer) { | ||
var autoPlay = this.props.autoPlay; | ||
if (this.props.useBrowserFullscreen) { | ||
if (gallery.requestFullscreen) { | ||
gallery.requestFullscreen(); | ||
} else if (gallery.msRequestFullscreen) { | ||
gallery.msRequestFullscreen(); | ||
} else if (gallery.mozRequestFullScreen) { | ||
gallery.mozRequestFullScreen(); | ||
} else if (gallery.webkitRequestFullscreen) { | ||
gallery.webkitRequestFullscreen(); | ||
} else { | ||
// fallback to fullscreen modal for unsupported browsers | ||
this.setModalFullscreen(true); | ||
window.clearTimeout(this.thumbnailMouseOverTimer); | ||
this.thumbnailMouseOverTimer = null; | ||
if (autoPlay) { | ||
this.play(); | ||
} | ||
} else { | ||
this.setModalFullscreen(true); | ||
} | ||
this.setState({ isFullscreen: true }); | ||
} | ||
}, { | ||
key: 'exitFullScreen', | ||
value: function exitFullScreen() { | ||
if (this.state.isFullscreen) { | ||
if (this.props.useBrowserFullscreen) { | ||
if (document.exitFullscreen) { | ||
document.exitFullscreen(); | ||
} else if (document.webkitExitFullscreen) { | ||
document.webkitExitFullscreen(); | ||
} else if (document.mozCancelFullScreen) { | ||
document.mozCancelFullScreen(); | ||
} else if (document.msExitFullscreen) { | ||
document.msExitFullscreen(); | ||
} else { | ||
// fallback to fullscreen modal for unsupported browsers | ||
this.setModalFullscreen(false); | ||
} | ||
} else { | ||
this.setModalFullscreen(false); | ||
} | ||
key: 'setScrollDirection', | ||
value: function setScrollDirection(dir) { | ||
var _state2 = this.state, | ||
scrollingUpDown = _state2.scrollingUpDown, | ||
scrollingLeftRight = _state2.scrollingLeftRight; | ||
this.setState({ isFullscreen: false }); | ||
} | ||
} | ||
}, { | ||
key: 'getCurrentIndex', | ||
value: function getCurrentIndex() { | ||
return this.state.currentIndex; | ||
} | ||
}, { | ||
key: '_onScreenChangeEvent', | ||
value: function _onScreenChangeEvent() { | ||
var _this3 = this; | ||
screenChangeEvents.map(function (eventName) { | ||
document.addEventListener(eventName, _this3._handleScreenChange); | ||
}); | ||
} | ||
}, { | ||
key: '_offScreenChangeEvent', | ||
value: function _offScreenChangeEvent() { | ||
var _this4 = this; | ||
screenChangeEvents.map(function (eventName) { | ||
document.removeEventListener(eventName, _this4._handleScreenChange); | ||
}); | ||
} | ||
}, { | ||
key: '_isThumbnailVertical', | ||
value: function _isThumbnailVertical() { | ||
var thumbnailPosition = this.props.thumbnailPosition; | ||
return thumbnailPosition === 'left' || thumbnailPosition === 'right'; | ||
} | ||
}, { | ||
key: '_setScrollDirection', | ||
value: function _setScrollDirection(dir) { | ||
var _state = this.state, | ||
scrollingUpDown = _state.scrollingUpDown, | ||
scrollingLeftRight = _state.scrollingLeftRight; | ||
if (!scrollingUpDown && !scrollingLeftRight) { | ||
@@ -614,123 +268,59 @@ if (dir === _reactSwipeable.LEFT || dir === _reactSwipeable.RIGHT) { | ||
}, { | ||
key: '_handleOnSwipedTo', | ||
value: function _handleOnSwipedTo(side, isFlick) { | ||
var _state2 = this.state, | ||
currentIndex = _state2.currentIndex, | ||
isTransitioning = _state2.isTransitioning; | ||
var slideTo = currentIndex; | ||
if ((this._sufficientSwipeOffset() || isFlick) && !isTransitioning) { | ||
slideTo += side; | ||
} | ||
if (side < 0) { | ||
if (!this._canSlideLeft()) { | ||
slideTo = currentIndex; | ||
} | ||
} else { | ||
if (!this._canSlideRight()) { | ||
slideTo = currentIndex; | ||
} | ||
} | ||
this._unthrottledSlideToIndex(slideTo); | ||
key: 'setThumbsTranslate', | ||
value: function setThumbsTranslate(thumbsTranslate) { | ||
this.setState({ thumbsTranslate: thumbsTranslate }); | ||
} | ||
}, { | ||
key: '_sufficientSwipeOffset', | ||
value: function _sufficientSwipeOffset() { | ||
return Math.abs(this.state.offsetPercentage) > this.props.swipeThreshold; | ||
} | ||
}, { | ||
key: '_canNavigate', | ||
value: function _canNavigate() { | ||
return this.props.items.length >= 2; | ||
} | ||
}, { | ||
key: '_canSlideLeft', | ||
value: function _canSlideLeft() { | ||
return this.props.infinite || (this.props.isRTL ? this._canSlideNext() : this._canSlidePrevious()); | ||
} | ||
}, { | ||
key: '_canSlideRight', | ||
value: function _canSlideRight() { | ||
return this.props.infinite || (this.props.isRTL ? this._canSlidePrevious() : this._canSlideNext()); | ||
} | ||
}, { | ||
key: '_canSlidePrevious', | ||
value: function _canSlidePrevious() { | ||
return this.state.currentIndex > 0; | ||
} | ||
}, { | ||
key: '_canSlideNext', | ||
value: function _canSlideNext() { | ||
return this.state.currentIndex < this.props.items.length - 1; | ||
} | ||
}, { | ||
key: '_slideThumbnailBar', | ||
value: function _slideThumbnailBar(previousIndex) { | ||
var _state3 = this.state, | ||
thumbsTranslate = _state3.thumbsTranslate, | ||
currentIndex = _state3.currentIndex; | ||
key: 'setModalFullscreen', | ||
value: function setModalFullscreen(state) { | ||
var onScreenChange = this.props.onScreenChange; | ||
if (this.state.currentIndex === 0) { | ||
this._setThumbsTranslate(0); | ||
} else { | ||
var indexDifference = Math.abs(previousIndex - currentIndex); | ||
var scroll = this._getThumbsTranslate(indexDifference); | ||
if (scroll > 0) { | ||
if (previousIndex < currentIndex) { | ||
this._setThumbsTranslate(thumbsTranslate - scroll); | ||
} else if (previousIndex > currentIndex) { | ||
this._setThumbsTranslate(thumbsTranslate + scroll); | ||
} | ||
} | ||
this.setState({ modalFullscreen: state }); | ||
// manually call because browser does not support screenchange events | ||
if (onScreenChange) { | ||
onScreenChange(state); | ||
} | ||
} | ||
}, { | ||
key: '_setThumbsTranslate', | ||
value: function _setThumbsTranslate(thumbsTranslate) { | ||
this.setState({ thumbsTranslate: thumbsTranslate }); | ||
} | ||
}, { | ||
key: '_getThumbsTranslate', | ||
value: function _getThumbsTranslate(indexDifference) { | ||
if (this.props.disableThumbnailScroll) { | ||
return 0; | ||
} | ||
key: 'getThumbsTranslate', | ||
value: function getThumbsTranslate(indexDifference) { | ||
var _props3 = this.props, | ||
disableThumbnailScroll = _props3.disableThumbnailScroll, | ||
items = _props3.items; | ||
var _state3 = this.state, | ||
thumbnailsWrapperWidth = _state3.thumbnailsWrapperWidth, | ||
thumbnailsWrapperHeight = _state3.thumbnailsWrapperHeight; | ||
var _state4 = this.state, | ||
thumbnailsWrapperWidth = _state4.thumbnailsWrapperWidth, | ||
thumbnailsWrapperHeight = _state4.thumbnailsWrapperHeight; | ||
var totalScroll = void 0; | ||
var thumbElement = this.thumbnails && this.thumbnails.current; | ||
if (this._thumbnails) { | ||
if (disableThumbnailScroll) return 0; | ||
if (thumbElement) { | ||
// total scroll required to see the last thumbnail | ||
if (this._isThumbnailVertical()) { | ||
if (this._thumbnails.scrollHeight <= thumbnailsWrapperHeight) { | ||
if (this.isThumbnailVertical()) { | ||
if (thumbElement.scrollHeight <= thumbnailsWrapperHeight) { | ||
return 0; | ||
} | ||
totalScroll = this._thumbnails.scrollHeight - thumbnailsWrapperHeight; | ||
totalScroll = thumbElement.scrollHeight - thumbnailsWrapperHeight; | ||
} else { | ||
if (this._thumbnails.scrollWidth <= thumbnailsWrapperWidth || thumbnailsWrapperWidth <= 0) { | ||
if (thumbElement.scrollWidth <= thumbnailsWrapperWidth || thumbnailsWrapperWidth <= 0) { | ||
return 0; | ||
} | ||
totalScroll = this._thumbnails.scrollWidth - thumbnailsWrapperWidth; | ||
totalScroll = thumbElement.scrollWidth - thumbnailsWrapperWidth; | ||
} | ||
var totalThumbnails = this._thumbnails.children.length; | ||
// scroll-x required per index change | ||
var perIndexScroll = totalScroll / (totalThumbnails - 1); | ||
var perIndexScroll = totalScroll / (items.length - 1); | ||
return indexDifference * perIndexScroll; | ||
} | ||
return 0; | ||
} | ||
}, { | ||
key: '_getAlignmentClassName', | ||
value: function _getAlignmentClassName(index) { | ||
/* | ||
Necessary for lazing loading | ||
*/ | ||
key: 'getAlignmentClassName', | ||
value: function getAlignmentClassName(index) { | ||
// Necessary for lazing loading | ||
var currentIndex = this.state.currentIndex; | ||
var _props4 = this.props, | ||
infinite = _props4.infinite, | ||
items = _props4.items; | ||
@@ -752,9 +342,11 @@ var alignment = ''; | ||
break; | ||
default: | ||
break; | ||
} | ||
if (this.props.items.length >= 3 && this.props.infinite) { | ||
if (index === 0 && currentIndex === this.props.items.length - 1) { | ||
if (items.length >= 3 && infinite) { | ||
if (index === 0 && currentIndex === items.length - 1) { | ||
// set first slide as right slide if were sliding right from last slide | ||
alignment = ' ' + rightClassName; | ||
} else if (index === this.props.items.length - 1 && currentIndex === 0) { | ||
} else if (index === items.length - 1 && currentIndex === 0) { | ||
// set last slide as left slide if were sliding left from first slide | ||
@@ -768,29 +360,9 @@ alignment = ' ' + leftClassName; | ||
}, { | ||
key: '_isGoingFromFirstToLast', | ||
value: function _isGoingFromFirstToLast() { | ||
var _state5 = this.state, | ||
currentIndex = _state5.currentIndex, | ||
previousIndex = _state5.previousIndex; | ||
var totalSlides = this.props.items.length - 1; | ||
return previousIndex === 0 && currentIndex === totalSlides; | ||
} | ||
}, { | ||
key: '_isGoingFromLastToFirst', | ||
value: function _isGoingFromLastToFirst() { | ||
var _state6 = this.state, | ||
currentIndex = _state6.currentIndex, | ||
previousIndex = _state6.previousIndex; | ||
var totalSlides = this.props.items.length - 1; | ||
return previousIndex === totalSlides && currentIndex === 0; | ||
} | ||
}, { | ||
key: '_getTranslateXForTwoSlide', | ||
value: function _getTranslateXForTwoSlide(index) { | ||
key: 'getTranslateXForTwoSlide', | ||
value: function getTranslateXForTwoSlide(index) { | ||
// For taking care of infinite swipe when there are only two slides | ||
var _state7 = this.state, | ||
currentIndex = _state7.currentIndex, | ||
offsetPercentage = _state7.offsetPercentage, | ||
previousIndex = _state7.previousIndex; | ||
var _state4 = this.state, | ||
currentIndex = _state4.currentIndex, | ||
offsetPercentage = _state4.offsetPercentage, | ||
previousIndex = _state4.previousIndex; | ||
@@ -825,3 +397,4 @@ var baseTranslateX = -100 * currentIndex; | ||
translateX = -100; | ||
} else if (currentIndex === 1 && index === 0 && offsetPercentage === 0 && this.direction === 'right') { | ||
} | ||
if (currentIndex === 1 && index === 0 && offsetPercentage === 0 && this.direction === 'right') { | ||
translateX = 100; | ||
@@ -834,8 +407,8 @@ } | ||
}, { | ||
key: '_getThumbnailBarHeight', | ||
value: function _getThumbnailBarHeight() { | ||
if (this._isThumbnailVertical()) { | ||
return { | ||
height: this.state.gallerySlideWrapperHeight | ||
}; | ||
key: 'getThumbnailBarHeight', | ||
value: function getThumbnailBarHeight() { | ||
if (this.isThumbnailVertical()) { | ||
var gallerySlideWrapperHeight = this.state.gallerySlideWrapperHeight; | ||
return { height: gallerySlideWrapperHeight }; | ||
} | ||
@@ -845,70 +418,14 @@ return {}; | ||
}, { | ||
key: '_shouldPushSlideOnInfiniteMode', | ||
value: function _shouldPushSlideOnInfiniteMode(index) { | ||
/* | ||
Push(show) slide if slide is the current slide, and the next slide | ||
OR | ||
The slide is going more than 1 slide left, or right, but not going from | ||
first to last and not going from last to first | ||
There is an edge case where if you go to the first or last slide, when they're | ||
not left, or right of each other they will try to catch up in the background | ||
so unless were going from first to last or vice versa we don't want the first | ||
or last slide to show up during our transition | ||
*/ | ||
return !this._slideIsTransitioning(index) || this._ignoreIsTransitioning() && !this._isFirstOrLastSlide(index); | ||
} | ||
}, { | ||
key: '_slideIsTransitioning', | ||
value: function _slideIsTransitioning(index) { | ||
/* | ||
returns true if the gallery is transitioning and the index is not the | ||
previous or currentIndex | ||
*/ | ||
var _state8 = this.state, | ||
isTransitioning = _state8.isTransitioning, | ||
previousIndex = _state8.previousIndex, | ||
currentIndex = _state8.currentIndex; | ||
key: 'getSlideStyle', | ||
value: function getSlideStyle(index) { | ||
var _state5 = this.state, | ||
currentIndex = _state5.currentIndex, | ||
offsetPercentage = _state5.offsetPercentage, | ||
slideStyle = _state5.slideStyle; | ||
var _props5 = this.props, | ||
infinite = _props5.infinite, | ||
items = _props5.items, | ||
useTranslate3D = _props5.useTranslate3D, | ||
isRTL = _props5.isRTL; | ||
var indexIsNotPreviousOrNextSlide = !(index === previousIndex || index === currentIndex); | ||
return isTransitioning && indexIsNotPreviousOrNextSlide; | ||
} | ||
}, { | ||
key: '_isFirstOrLastSlide', | ||
value: function _isFirstOrLastSlide(index) { | ||
var totalSlides = this.props.items.length - 1; | ||
var isLastSlide = index === totalSlides; | ||
var isFirstSlide = index === 0; | ||
return isLastSlide || isFirstSlide; | ||
} | ||
}, { | ||
key: '_ignoreIsTransitioning', | ||
value: function _ignoreIsTransitioning() { | ||
/* | ||
Ignore isTransitioning because were not going to sibling slides | ||
e.g. center to left or center to right | ||
*/ | ||
var _state9 = this.state, | ||
previousIndex = _state9.previousIndex, | ||
currentIndex = _state9.currentIndex; | ||
var totalSlides = this.props.items.length - 1; | ||
// we want to show the in between slides transition | ||
var slidingMoreThanOneSlideLeftOrRight = Math.abs(previousIndex - currentIndex) > 1; | ||
var notGoingFromFirstToLast = !(previousIndex === 0 && currentIndex === totalSlides); | ||
var notGoingFromLastToFirst = !(previousIndex === totalSlides && currentIndex === 0); | ||
return slidingMoreThanOneSlideLeftOrRight && notGoingFromFirstToLast && notGoingFromLastToFirst; | ||
} | ||
}, { | ||
key: '_getSlideStyle', | ||
value: function _getSlideStyle(index) { | ||
var _state10 = this.state, | ||
currentIndex = _state10.currentIndex, | ||
offsetPercentage = _state10.offsetPercentage; | ||
var _props2 = this.props, | ||
infinite = _props2.infinite, | ||
items = _props2.items, | ||
useTranslate3D = _props2.useTranslate3D, | ||
isRTL = _props2.isRTL; | ||
var baseTranslateX = -100 * currentIndex; | ||
@@ -935,3 +452,3 @@ var totalSlides = items.length - 1; | ||
if (infinite && items.length === 2) { | ||
translateX = this._getTranslateXForTwoSlide(index); | ||
translateX = this.getTranslateXForTwoSlide(index); | ||
} | ||
@@ -945,3 +462,3 @@ | ||
return { | ||
return _extends({}, { | ||
WebkitTransform: translate, | ||
@@ -952,11 +469,18 @@ MozTransform: translate, | ||
transform: translate | ||
}; | ||
}, slideStyle); | ||
} | ||
}, { | ||
key: '_getThumbnailStyle', | ||
value: function _getThumbnailStyle() { | ||
key: 'getCurrentIndex', | ||
value: function getCurrentIndex() { | ||
var currentIndex = this.state.currentIndex; | ||
return currentIndex; | ||
} | ||
}, { | ||
key: 'getThumbnailStyle', | ||
value: function getThumbnailStyle() { | ||
var translate = void 0; | ||
var _props3 = this.props, | ||
useTranslate3D = _props3.useTranslate3D, | ||
isRTL = _props3.isRTL; | ||
var _props6 = this.props, | ||
useTranslate3D = _props6.useTranslate3D, | ||
isRTL = _props6.isRTL; | ||
var thumbsTranslate = this.state.thumbsTranslate; | ||
@@ -966,3 +490,3 @@ | ||
if (this._isThumbnailVertical()) { | ||
if (this.isThumbnailVertical()) { | ||
translate = 'translate(0, ' + thumbsTranslate + 'px)'; | ||
@@ -987,24 +511,23 @@ if (useTranslate3D) { | ||
}, { | ||
key: 'render', | ||
value: function render() { | ||
var _this5 = this; | ||
key: 'getSlideItems', | ||
value: function getSlideItems(items) { | ||
var _this4 = this; | ||
var _state11 = this.state, | ||
currentIndex = _state11.currentIndex, | ||
isFullscreen = _state11.isFullscreen, | ||
modalFullscreen = _state11.modalFullscreen, | ||
isPlaying = _state11.isPlaying; | ||
var _props4 = this.props, | ||
infinite = _props4.infinite, | ||
slideOnThumbnailOver = _props4.slideOnThumbnailOver, | ||
isRTL = _props4.isRTL, | ||
lazyLoad = _props4.lazyLoad; | ||
var currentIndex = this.state.currentIndex; | ||
var _props7 = this.props, | ||
infinite = _props7.infinite, | ||
slideOnThumbnailOver = _props7.slideOnThumbnailOver, | ||
onClick = _props7.onClick, | ||
lazyLoad = _props7.lazyLoad, | ||
onTouchMove = _props7.onTouchMove, | ||
onTouchEnd = _props7.onTouchEnd, | ||
onTouchStart = _props7.onTouchStart, | ||
onMouseOver = _props7.onMouseOver, | ||
onMouseLeave = _props7.onMouseLeave, | ||
renderItem = _props7.renderItem, | ||
renderThumbInner = _props7.renderThumbInner, | ||
showThumbnails = _props7.showThumbnails, | ||
showBullets = _props7.showBullets; | ||
var thumbnailStyle = this._getThumbnailStyle(); | ||
var thumbnailPosition = this.props.thumbnailPosition; | ||
var slideLeft = this._slideLeft; | ||
var slideRight = this._slideRight; | ||
var slides = []; | ||
@@ -1014,17 +537,15 @@ var thumbnails = []; | ||
this.props.items.forEach(function (item, index) { | ||
var alignment = _this5._getAlignmentClassName(index); | ||
items.forEach(function (item, index) { | ||
var alignment = _this4.getAlignmentClassName(index); | ||
var originalClass = item.originalClass ? ' ' + item.originalClass : ''; | ||
var thumbnailClass = item.thumbnailClass ? ' ' + item.thumbnailClass : ''; | ||
var handleRenderItem = item.renderItem || renderItem || _this4.renderItem; | ||
var handleRenderThumbInner = item.renderThumbInner || renderThumbInner || _this4.renderThumbInner; | ||
var renderItem = item.renderItem || _this5.props.renderItem || _this5._renderItem; | ||
var renderThumbInner = item.renderThumbInner || _this5.props.renderThumbInner || _this5._renderThumbInner; | ||
var showItem = !lazyLoad || alignment || _this5._lazyLoaded[index]; | ||
if (showItem && lazyLoad && !_this5._lazyLoaded[index]) { | ||
_this5._lazyLoaded[index] = true; | ||
var showItem = !lazyLoad || alignment || _this4.lazyLoaded[index]; | ||
if (showItem && lazyLoad && !_this4.lazyLoaded[index]) { | ||
_this4.lazyLoaded[index] = true; | ||
} | ||
var slideStyle = _this5._getSlideStyle(index); | ||
var slideStyle = _this4.getSlideStyle(index); | ||
@@ -1034,14 +555,17 @@ var slide = _react2.default.createElement( | ||
{ | ||
key: index, | ||
className: 'image-gallery-slide' + alignment + originalClass, | ||
style: _extends(slideStyle, _this5.state.style), | ||
onClick: _this5.props.onClick, | ||
onTouchMove: _this5.props.onTouchMove, | ||
onTouchEnd: _this5.props.onTouchEnd, | ||
onTouchStart: _this5.props.onTouchStart, | ||
onMouseOver: _this5.props.onMouseOver, | ||
onMouseLeave: _this5.props.onMouseLeave, | ||
role: _this5.props.onClick && 'button' | ||
key: 'slide-' + item.original, | ||
tabIndex: '-1', | ||
className: 'image-gallery-slide ' + alignment + ' ' + originalClass, | ||
style: slideStyle, | ||
onClick: onClick, | ||
onKeyUp: _this4.handleSlideKeyUp, | ||
onTouchMove: onTouchMove, | ||
onTouchEnd: onTouchEnd, | ||
onTouchStart: onTouchStart, | ||
onMouseOver: onMouseOver, | ||
onFocus: onMouseOver, | ||
onMouseLeave: onMouseLeave, | ||
role: 'button' | ||
}, | ||
showItem ? renderItem(item) : _react2.default.createElement('div', { style: { height: '100%' } }) | ||
showItem ? handleRenderItem(item) : _react2.default.createElement('div', { style: { height: '100%' } }) | ||
); | ||
@@ -1051,3 +575,3 @@ | ||
// don't add some slides while transitioning to avoid background transitions | ||
if (_this5._shouldPushSlideOnInfiniteMode(index)) { | ||
if (_this4.shouldPushSlideOnInfiniteMode(index)) { | ||
slides.push(slide); | ||
@@ -1059,24 +583,33 @@ } | ||
if (_this5.props.showThumbnails) { | ||
if (showThumbnails) { | ||
var igThumbnailClass = (0, _clsx2.default)('image-gallery-thumbnail', thumbnailClass, { active: currentIndex === index }); | ||
thumbnails.push(_react2.default.createElement( | ||
'a', | ||
'button', | ||
{ | ||
key: index, | ||
role: 'button', | ||
key: 'thumbnail-' + item.original, | ||
type: 'button', | ||
tabIndex: '0', | ||
'aria-pressed': currentIndex === index ? 'true' : 'false', | ||
'aria-label': 'Go to Slide ' + (index + 1), | ||
className: 'image-gallery-thumbnail' + (currentIndex === index ? ' active' : '') + thumbnailClass, | ||
onMouseLeave: slideOnThumbnailOver ? _this5._onThumbnailMouseLeave : undefined, | ||
className: igThumbnailClass, | ||
onMouseLeave: slideOnThumbnailOver ? _this4.onThumbnailMouseLeave : null, | ||
onMouseOver: function onMouseOver(event) { | ||
return slideOnThumbnailOver ? _this5._onThumbnailMouseOver(event, index) : undefined; | ||
return _this4.handleThumbnailMouseOver(event, index); | ||
}, | ||
onFocus: function onFocus(event) { | ||
return _this4.handleThumbnailMouseOver(event, index); | ||
}, | ||
onKeyUp: function onKeyUp(event) { | ||
return _this4.handleThumbnailKeyUp(event, index); | ||
}, | ||
onClick: function onClick(event) { | ||
return _this5._onThumbnailClick(event, index); | ||
return _this4.onThumbnailClick(event, index); | ||
} | ||
}, | ||
renderThumbInner(item) | ||
handleRenderThumbInner(item) | ||
)); | ||
} | ||
if (_this5.props.showBullets) { | ||
if (showBullets) { | ||
// generate bullet elements and store them in array | ||
var bulletOnClick = function bulletOnClick(event) { | ||
@@ -1086,8 +619,9 @@ if (item.bulletOnClick) { | ||
} | ||
return _this5.slideToIndex.call(_this5, index, event); | ||
return _this4.slideToIndex.call(_this4, index, event); | ||
}; | ||
var igBulletClass = (0, _clsx2.default)('image-gallery-bullet', item.bulletClass, { active: currentIndex === index }); | ||
bullets.push(_react2.default.createElement('button', { | ||
key: index, | ||
type: 'button', | ||
className: ['image-gallery-bullet', currentIndex === index ? 'active' : '', item.bulletClass || ''].join(' '), | ||
key: 'bullet-' + item.original, | ||
className: igBulletClass, | ||
onClick: bulletOnClick, | ||
@@ -1100,31 +634,745 @@ 'aria-pressed': currentIndex === index ? 'true' : 'false', | ||
var slideWrapper = _react2.default.createElement( | ||
return { | ||
slides: slides, | ||
thumbnails: thumbnails, | ||
bullets: bullets | ||
}; | ||
} | ||
}, { | ||
key: 'ignoreIsTransitioning', | ||
value: function ignoreIsTransitioning() { | ||
/* | ||
Ignore isTransitioning because were not going to sibling slides | ||
e.g. center to left or center to right | ||
*/ | ||
var items = this.props.items; | ||
var _state6 = this.state, | ||
previousIndex = _state6.previousIndex, | ||
currentIndex = _state6.currentIndex; | ||
var totalSlides = items.length - 1; | ||
// we want to show the in between slides transition | ||
var slidingMoreThanOneSlideLeftOrRight = Math.abs(previousIndex - currentIndex) > 1; | ||
var notGoingFromFirstToLast = !(previousIndex === 0 && currentIndex === totalSlides); | ||
var notGoingFromLastToFirst = !(previousIndex === totalSlides && currentIndex === 0); | ||
return slidingMoreThanOneSlideLeftOrRight && notGoingFromFirstToLast && notGoingFromLastToFirst; | ||
} | ||
}, { | ||
key: 'isFirstOrLastSlide', | ||
value: function isFirstOrLastSlide(index) { | ||
var items = this.props.items; | ||
var totalSlides = items.length - 1; | ||
var isLastSlide = index === totalSlides; | ||
var isFirstSlide = index === 0; | ||
return isLastSlide || isFirstSlide; | ||
} | ||
}, { | ||
key: 'slideIsTransitioning', | ||
value: function slideIsTransitioning(index) { | ||
/* | ||
returns true if the gallery is transitioning and the index is not the | ||
previous or currentIndex | ||
*/ | ||
var _state7 = this.state, | ||
isTransitioning = _state7.isTransitioning, | ||
previousIndex = _state7.previousIndex, | ||
currentIndex = _state7.currentIndex; | ||
var indexIsNotPreviousOrNextSlide = !(index === previousIndex || index === currentIndex); | ||
return isTransitioning && indexIsNotPreviousOrNextSlide; | ||
} | ||
}, { | ||
key: 'shouldPushSlideOnInfiniteMode', | ||
value: function shouldPushSlideOnInfiniteMode(index) { | ||
/* | ||
Push (show) slide if slide is the current slide and the next slide | ||
OR | ||
The slide is going more than one slide left or right, but not going from | ||
first to last and not going from last to first | ||
Edge case: | ||
If you go to the first or last slide, when they're | ||
not left, or right of each other they will try to catch up in the background | ||
so unless were going from first to last or vice versa we don't want the first | ||
or last slide to show up during the transition | ||
*/ | ||
return !this.slideIsTransitioning(index) || this.ignoreIsTransitioning() && !this.isFirstOrLastSlide(index); | ||
} | ||
}, { | ||
key: 'slideThumbnailBar', | ||
value: function slideThumbnailBar(previousIndex) { | ||
var _state8 = this.state, | ||
thumbsTranslate = _state8.thumbsTranslate, | ||
currentIndex = _state8.currentIndex; | ||
if (currentIndex === 0) { | ||
this.setThumbsTranslate(0); | ||
} else { | ||
var indexDifference = Math.abs(previousIndex - currentIndex); | ||
var scroll = this.getThumbsTranslate(indexDifference); | ||
if (scroll > 0) { | ||
if (previousIndex < currentIndex) { | ||
this.setThumbsTranslate(thumbsTranslate - scroll); | ||
} else if (previousIndex > currentIndex) { | ||
this.setThumbsTranslate(thumbsTranslate + scroll); | ||
} | ||
} | ||
} | ||
} | ||
}, { | ||
key: 'canSlide', | ||
value: function canSlide() { | ||
var items = this.props.items; | ||
return items.length >= 2; | ||
} | ||
}, { | ||
key: 'canSlideLeft', | ||
value: function canSlideLeft() { | ||
var _props8 = this.props, | ||
infinite = _props8.infinite, | ||
isRTL = _props8.isRTL; | ||
return infinite || (isRTL ? this.canSlideNext() : this.canSlidePrevious()); | ||
} | ||
}, { | ||
key: 'canSlideRight', | ||
value: function canSlideRight() { | ||
var _props9 = this.props, | ||
infinite = _props9.infinite, | ||
isRTL = _props9.isRTL; | ||
return infinite || (isRTL ? this.canSlidePrevious() : this.canSlideNext()); | ||
} | ||
}, { | ||
key: 'canSlidePrevious', | ||
value: function canSlidePrevious() { | ||
var currentIndex = this.state.currentIndex; | ||
return currentIndex > 0; | ||
} | ||
}, { | ||
key: 'canSlideNext', | ||
value: function canSlideNext() { | ||
var currentIndex = this.state.currentIndex; | ||
var items = this.props.items; | ||
return currentIndex < items.length - 1; | ||
} | ||
}, { | ||
key: 'handleSwiping', | ||
value: function handleSwiping(_ref) { | ||
var event = _ref.event, | ||
absX = _ref.absX, | ||
dir = _ref.dir; | ||
var _props10 = this.props, | ||
preventDefaultTouchmoveEvent = _props10.preventDefaultTouchmoveEvent, | ||
disableSwipe = _props10.disableSwipe, | ||
stopPropagation = _props10.stopPropagation; | ||
var _state9 = this.state, | ||
galleryWidth = _state9.galleryWidth, | ||
isTransitioning = _state9.isTransitioning, | ||
scrollingUpDown = _state9.scrollingUpDown, | ||
scrollingLeftRight = _state9.scrollingLeftRight; | ||
if (disableSwipe) return; | ||
var swipingTransitionDuration = this.props.swipingTransitionDuration; | ||
this.setScrollDirection(dir); | ||
if (stopPropagation) event.stopPropagation(); | ||
if ((preventDefaultTouchmoveEvent || scrollingLeftRight) && event.cancelable) { | ||
event.preventDefault(); | ||
} | ||
if (!isTransitioning && !scrollingUpDown) { | ||
var side = dir === _reactSwipeable.RIGHT ? 1 : -1; | ||
var offsetPercentage = absX / galleryWidth * 100; | ||
if (Math.abs(offsetPercentage) >= 100) { | ||
offsetPercentage = 100; | ||
} | ||
var swipingTransition = { | ||
transition: 'transform ' + swipingTransitionDuration + 'ms ease-out' | ||
}; | ||
this.setState({ | ||
offsetPercentage: side * offsetPercentage, | ||
slideStyle: swipingTransition | ||
}); | ||
} else { | ||
// don't move the slide | ||
this.setState({ offsetPercentage: 0 }); | ||
} | ||
} | ||
}, { | ||
key: 'sufficientSwipe', | ||
value: function sufficientSwipe() { | ||
var offsetPercentage = this.state.offsetPercentage; | ||
var swipeThreshold = this.props.swipeThreshold; | ||
return Math.abs(offsetPercentage) > swipeThreshold; | ||
} | ||
}, { | ||
key: 'handleOnSwiped', | ||
value: function handleOnSwiped(_ref2) { | ||
var event = _ref2.event, | ||
dir = _ref2.dir, | ||
velocity = _ref2.velocity; | ||
var _props11 = this.props, | ||
disableSwipe = _props11.disableSwipe, | ||
stopPropagation = _props11.stopPropagation, | ||
flickThreshold = _props11.flickThreshold; | ||
var _state10 = this.state, | ||
scrollingUpDown = _state10.scrollingUpDown, | ||
scrollingLeftRight = _state10.scrollingLeftRight; | ||
if (disableSwipe) return; | ||
var isRTL = this.props.isRTL; | ||
if (stopPropagation) event.stopPropagation(); | ||
if (scrollingUpDown) { | ||
// user stopped scrollingUpDown | ||
this.setState({ scrollingUpDown: false }); | ||
} | ||
if (scrollingLeftRight) { | ||
// user stopped scrollingLeftRight | ||
this.setState({ scrollingLeftRight: false }); | ||
} | ||
if (!scrollingUpDown) { | ||
// don't swipe if user is scrolling | ||
// if it is RTL the direction is reversed | ||
var swipeDirection = (dir === _reactSwipeable.LEFT ? 1 : -1) * (isRTL ? -1 : 1); | ||
var isFlick = velocity > flickThreshold; | ||
this.handleOnSwipedTo(swipeDirection, isFlick); | ||
} | ||
} | ||
}, { | ||
key: 'handleOnSwipedTo', | ||
value: function handleOnSwipedTo(swipeDirection, isFlick) { | ||
var _state11 = this.state, | ||
currentIndex = _state11.currentIndex, | ||
isTransitioning = _state11.isTransitioning; | ||
var slideTo = currentIndex; | ||
if ((this.sufficientSwipe() || isFlick) && !isTransitioning) { | ||
// slideto the next/prev slide | ||
slideTo += swipeDirection; | ||
} | ||
// If we can't swipe left or right, stay in the current index (noop) | ||
if (swipeDirection === -1 && !this.canSlideLeft() || swipeDirection === 1 && !this.canSlideRight()) { | ||
slideTo = currentIndex; | ||
} | ||
this.unthrottledSlideToIndex(slideTo); | ||
} | ||
}, { | ||
key: 'handleMouseDown', | ||
value: function handleMouseDown() { | ||
// keep track of mouse vs keyboard usage for a11y | ||
this.imageGallery.current.classList.add('image-gallery-using-mouse'); | ||
} | ||
}, { | ||
key: 'handleKeyDown', | ||
value: function handleKeyDown(event) { | ||
var _props12 = this.props, | ||
disableKeyDown = _props12.disableKeyDown, | ||
useBrowserFullscreen = _props12.useBrowserFullscreen; | ||
var isFullscreen = this.state.isFullscreen; | ||
// keep track of mouse vs keyboard usage for a11y | ||
this.imageGallery.current.classList.remove('image-gallery-using-mouse'); | ||
if (disableKeyDown) return; | ||
var LEFT_ARROW = 37; | ||
var RIGHT_ARROW = 39; | ||
var ESC_KEY = 27; | ||
var key = parseInt(event.keyCode || event.which || 0, 10); | ||
switch (key) { | ||
case LEFT_ARROW: | ||
if (this.canSlideLeft() && !this.intervalId) { | ||
this.slideLeft(); | ||
} | ||
break; | ||
case RIGHT_ARROW: | ||
if (this.canSlideRight() && !this.intervalId) { | ||
this.slideRight(); | ||
} | ||
break; | ||
case ESC_KEY: | ||
if (isFullscreen && !useBrowserFullscreen) { | ||
this.exitFullScreen(); | ||
} | ||
break; | ||
default: | ||
break; | ||
} | ||
} | ||
}, { | ||
key: 'handleImageError', | ||
value: function handleImageError(event) { | ||
var onErrorImageURL = this.props.onErrorImageURL; | ||
if (onErrorImageURL && event.target.src.indexOf(onErrorImageURL) === -1) { | ||
/* eslint-disable no-param-reassign */ | ||
event.target.src = onErrorImageURL; | ||
/* eslint-enable no-param-reassign */ | ||
} | ||
} | ||
}, { | ||
key: 'removeResizeObserver', | ||
value: function removeResizeObserver() { | ||
if (this.resizeObserver && this.imageGallerySlideWrapper && this.imageGallerySlideWrapper.current) { | ||
this.resizeObserver.unobserve(this.imageGallerySlideWrapper.current); | ||
} | ||
} | ||
}, { | ||
key: 'handleResize', | ||
value: function handleResize() { | ||
var currentIndex = this.state.currentIndex; | ||
if (this.imageGallery && this.imageGallery.current) { | ||
this.setState({ galleryWidth: this.imageGallery.current.offsetWidth }); | ||
} | ||
if (this.imageGallerySlideWrapper && this.imageGallerySlideWrapper.current) { | ||
this.setState({ | ||
gallerySlideWrapperHeight: this.imageGallerySlideWrapper.current.offsetHeight | ||
}); | ||
} | ||
if (this.thumbnailsWrapper && this.thumbnailsWrapper.current) { | ||
if (this.isThumbnailVertical()) { | ||
this.setState({ thumbnailsWrapperHeight: this.thumbnailsWrapper.current.offsetHeight }); | ||
} else { | ||
this.setState({ thumbnailsWrapperWidth: this.thumbnailsWrapper.current.offsetWidth }); | ||
} | ||
} | ||
// Adjust thumbnail container when thumbnail width or height is adjusted | ||
this.setThumbsTranslate(-this.getThumbsTranslate(currentIndex)); | ||
} | ||
}, { | ||
key: 'initResizeObserver', | ||
value: function initResizeObserver(element) { | ||
var _this5 = this; | ||
this.resizeObserver = new _resizeObserverPolyfill2.default((0, _lodash4.default)(function (entries) { | ||
if (!entries) return; | ||
entries.forEach(function () { | ||
_this5.handleResize(); | ||
}); | ||
}, 300)); | ||
this.resizeObserver.observe(element.current); | ||
} | ||
}, { | ||
key: 'toggleFullScreen', | ||
value: function toggleFullScreen() { | ||
var isFullscreen = this.state.isFullscreen; | ||
if (isFullscreen) { | ||
this.exitFullScreen(); | ||
} else { | ||
this.fullScreen(); | ||
} | ||
} | ||
}, { | ||
key: 'togglePlay', | ||
value: function togglePlay() { | ||
if (this.intervalId) { | ||
this.pause(); | ||
} else { | ||
this.play(); | ||
} | ||
} | ||
}, { | ||
key: 'handleScreenChange', | ||
value: function handleScreenChange() { | ||
/* | ||
handles screen change events that the browser triggers e.g. esc key | ||
*/ | ||
var onScreenChange = this.props.onScreenChange; | ||
var fullScreenElement = document.fullscreenElement || document.msFullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement; | ||
if (onScreenChange) onScreenChange(fullScreenElement); | ||
this.setState({ isFullscreen: !!fullScreenElement }); | ||
} | ||
}, { | ||
key: 'slideToIndex', | ||
value: function slideToIndex(index, event) { | ||
var _state12 = this.state, | ||
currentIndex = _state12.currentIndex, | ||
isTransitioning = _state12.isTransitioning; | ||
var _props13 = this.props, | ||
items = _props13.items, | ||
slideDuration = _props13.slideDuration; | ||
if (!isTransitioning) { | ||
if (event) { | ||
if (this.intervalId) { | ||
// user triggered event while ImageGallery is playing, reset interval | ||
this.pause(false); | ||
this.play(false); | ||
} | ||
} | ||
var slideCount = items.length - 1; | ||
var nextIndex = index; | ||
if (index < 0) { | ||
nextIndex = slideCount; | ||
} else if (index > slideCount) { | ||
nextIndex = 0; | ||
} | ||
this.setState({ | ||
previousIndex: currentIndex, | ||
currentIndex: nextIndex, | ||
isTransitioning: nextIndex !== currentIndex, | ||
offsetPercentage: 0, | ||
slideStyle: { transition: 'all ' + slideDuration + 'ms ease-out' } | ||
}, this.onSliding); | ||
} | ||
} | ||
}, { | ||
key: 'slideLeft', | ||
value: function slideLeft() { | ||
var isRTL = this.props.isRTL; | ||
if (isRTL) { | ||
this.slideNext(); | ||
} else { | ||
this.slidePrevious(); | ||
} | ||
} | ||
}, { | ||
key: 'slideRight', | ||
value: function slideRight() { | ||
var isRTL = this.props.isRTL; | ||
if (isRTL) { | ||
this.slidePrevious(); | ||
} else { | ||
this.slideNext(); | ||
} | ||
} | ||
}, { | ||
key: 'slidePrevious', | ||
value: function slidePrevious(event) { | ||
var currentIndex = this.state.currentIndex; | ||
this.slideToIndex(currentIndex - 1, event); | ||
} | ||
}, { | ||
key: 'slideNext', | ||
value: function slideNext(event) { | ||
var currentIndex = this.state.currentIndex; | ||
this.slideToIndex(currentIndex + 1, event); | ||
} | ||
}, { | ||
key: 'handleThumbnailMouseOver', | ||
value: function handleThumbnailMouseOver(event, index) { | ||
var slideOnThumbnailOver = this.props.slideOnThumbnailOver; | ||
if (slideOnThumbnailOver) this.onThumbnailMouseOver(event, index); | ||
} | ||
}, { | ||
key: 'handleThumbnailKeyUp', | ||
value: function handleThumbnailKeyUp(event, index) { | ||
// a11y support ^_^ | ||
if (isEnterOrSpaceKey(event)) this.onThumbnailClick(event, index); | ||
} | ||
}, { | ||
key: 'handleSlideKeyUp', | ||
value: function handleSlideKeyUp(event) { | ||
// a11y support ^_^ | ||
if (isEnterOrSpaceKey(event)) { | ||
var onClick = this.props.onClick; | ||
onClick(event); | ||
} | ||
} | ||
}, { | ||
key: 'isThumbnailVertical', | ||
value: function isThumbnailVertical() { | ||
var thumbnailPosition = this.props.thumbnailPosition; | ||
return thumbnailPosition === 'left' || thumbnailPosition === 'right'; | ||
} | ||
}, { | ||
key: 'addScreenChangeEvent', | ||
value: function addScreenChangeEvent() { | ||
var _this6 = this; | ||
screenChangeEvents.forEach(function (eventName) { | ||
document.addEventListener(eventName, _this6.handleScreenChange); | ||
}); | ||
} | ||
}, { | ||
key: 'removeScreenChangeEvent', | ||
value: function removeScreenChangeEvent() { | ||
var _this7 = this; | ||
screenChangeEvents.forEach(function (eventName) { | ||
document.removeEventListener(eventName, _this7.handleScreenChange); | ||
}); | ||
} | ||
}, { | ||
key: 'fullScreen', | ||
value: function fullScreen() { | ||
var useBrowserFullscreen = this.props.useBrowserFullscreen; | ||
var gallery = this.imageGallery.current; | ||
if (useBrowserFullscreen) { | ||
if (gallery.requestFullscreen) { | ||
gallery.requestFullscreen(); | ||
} else if (gallery.msRequestFullscreen) { | ||
gallery.msRequestFullscreen(); | ||
} else if (gallery.mozRequestFullScreen) { | ||
gallery.mozRequestFullScreen(); | ||
} else if (gallery.webkitRequestFullscreen) { | ||
gallery.webkitRequestFullscreen(); | ||
} else { | ||
// fallback to fullscreen modal for unsupported browsers | ||
this.setModalFullscreen(true); | ||
} | ||
} else { | ||
this.setModalFullscreen(true); | ||
} | ||
this.setState({ isFullscreen: true }); | ||
} | ||
}, { | ||
key: 'exitFullScreen', | ||
value: function exitFullScreen() { | ||
var isFullscreen = this.state.isFullscreen; | ||
var useBrowserFullscreen = this.props.useBrowserFullscreen; | ||
if (isFullscreen) { | ||
if (useBrowserFullscreen) { | ||
if (document.exitFullscreen) { | ||
document.exitFullscreen(); | ||
} else if (document.webkitExitFullscreen) { | ||
document.webkitExitFullscreen(); | ||
} else if (document.mozCancelFullScreen) { | ||
document.mozCancelFullScreen(); | ||
} else if (document.msExitFullscreen) { | ||
document.msExitFullscreen(); | ||
} else { | ||
// fallback to fullscreen modal for unsupported browsers | ||
this.setModalFullscreen(false); | ||
} | ||
} else { | ||
this.setModalFullscreen(false); | ||
} | ||
this.setState({ isFullscreen: false }); | ||
} | ||
} | ||
}, { | ||
key: 'pauseOrPlay', | ||
value: function pauseOrPlay() { | ||
var infinite = this.props.infinite; | ||
var currentIndex = this.state.currentIndex; | ||
if (!infinite && !this.canSlideRight()) { | ||
this.pause(); | ||
} else { | ||
this.slideToIndex(currentIndex + 1); | ||
} | ||
} | ||
}, { | ||
key: 'play', | ||
value: function play() { | ||
var shouldCallOnPlay = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; | ||
var _props14 = this.props, | ||
onPlay = _props14.onPlay, | ||
slideInterval = _props14.slideInterval, | ||
slideDuration = _props14.slideDuration; | ||
var currentIndex = this.state.currentIndex; | ||
if (!this.intervalId) { | ||
this.setState({ isPlaying: true }); | ||
this.intervalId = window.setInterval(this.pauseOrPlay, Math.max(slideInterval, slideDuration)); | ||
if (onPlay && shouldCallOnPlay) { | ||
onPlay(currentIndex); | ||
} | ||
} | ||
} | ||
}, { | ||
key: 'pause', | ||
value: function pause() { | ||
var shouldCallOnPause = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; | ||
var onPause = this.props.onPause; | ||
var currentIndex = this.state.currentIndex; | ||
if (this.intervalId) { | ||
window.clearInterval(this.intervalId); | ||
this.intervalId = null; | ||
this.setState({ isPlaying: false }); | ||
if (onPause && shouldCallOnPause) { | ||
onPause(currentIndex); | ||
} | ||
} | ||
} | ||
}, { | ||
key: 'isImageLoaded', | ||
value: function isImageLoaded(item) { | ||
/* | ||
Keep track of images loaded so that onImageLoad prop is not | ||
called multiple times when re-render the images | ||
*/ | ||
var imageExists = this.loadedImages[item.original]; | ||
if (imageExists) { | ||
return true; | ||
} | ||
// add image as loaded | ||
this.loadedImages[item.original] = true; | ||
return false; | ||
} | ||
}, { | ||
key: 'renderItem', | ||
value: function renderItem(item) { | ||
var _props15 = this.props, | ||
onImageError = _props15.onImageError, | ||
onImageLoad = _props15.onImageLoad; | ||
var handleImageError = onImageError || this.handleImageError; | ||
return _react2.default.createElement( | ||
'div', | ||
{ | ||
ref: this._initGalleryResizing, | ||
className: 'image-gallery-slide-wrapper ' + thumbnailPosition + ' ' + (isRTL ? 'image-gallery-rtl' : '') | ||
}, | ||
this.props.renderCustomControls && this.props.renderCustomControls(), | ||
this.props.showFullscreenButton && this.props.renderFullscreenButton(this._toggleFullScreen, isFullscreen), | ||
this.props.showPlayButton && this.props.renderPlayPauseButton(this._togglePlay, isPlaying), | ||
this._canNavigate() ? [this.props.showNav && _react2.default.createElement( | ||
'span', | ||
{ key: 'navigation' }, | ||
this.props.renderLeftNav(slideLeft, !this._canSlideLeft()), | ||
this.props.renderRightNav(slideRight, !this._canSlideRight()) | ||
), _react2.default.createElement( | ||
_reactSwipeable.Swipeable, | ||
null, | ||
item.imageSet ? _react2.default.createElement( | ||
'picture', | ||
{ | ||
className: 'image-gallery-swipe', | ||
key: 'swipeable', | ||
delta: 0, | ||
onSwiping: this._handleSwiping, | ||
onSwiped: this._handleOnSwiped | ||
onLoad: !this.isImageLoaded(item) ? onImageLoad : null, | ||
onError: handleImageError | ||
}, | ||
item.imageSet.map(function (source) { | ||
return _react2.default.createElement('source', { | ||
key: source.media, | ||
media: source.media, | ||
srcSet: source.srcSet, | ||
type: source.type | ||
}); | ||
}), | ||
_react2.default.createElement('img', { | ||
className: 'image-gallery-image', | ||
alt: item.originalAlt, | ||
src: item.original | ||
}) | ||
) : _react2.default.createElement('img', { | ||
className: 'image-gallery-image', | ||
src: item.original, | ||
alt: item.originalAlt, | ||
srcSet: item.srcSet, | ||
sizes: item.sizes, | ||
title: item.originalTitle, | ||
onLoad: !this.isImageLoaded(item) ? onImageLoad : null, | ||
onError: handleImageError | ||
}), | ||
item.description && _react2.default.createElement( | ||
'span', | ||
{ className: 'image-gallery-description' }, | ||
item.description | ||
) | ||
); | ||
} | ||
}, { | ||
key: 'renderThumbInner', | ||
value: function renderThumbInner(item) { | ||
var onThumbnailError = this.props.onThumbnailError; | ||
var handleThumbnailError = onThumbnailError || this.handleImageError; | ||
return _react2.default.createElement( | ||
'div', | ||
{ className: 'image-gallery-thumbnail-inner' }, | ||
_react2.default.createElement('img', { | ||
className: 'image-gallery-thumbnail-image', | ||
src: item.thumbnail, | ||
alt: item.thumbnailAlt, | ||
title: item.thumbnailTitle, | ||
onError: handleThumbnailError | ||
}), | ||
item.thumbnailLabel && _react2.default.createElement( | ||
'div', | ||
{ className: 'image-gallery-thumbnail-label' }, | ||
item.thumbnailLabel | ||
) | ||
); | ||
} | ||
}, { | ||
key: 'render', | ||
value: function render() { | ||
var _state13 = this.state, | ||
currentIndex = _state13.currentIndex, | ||
isFullscreen = _state13.isFullscreen, | ||
modalFullscreen = _state13.modalFullscreen, | ||
isPlaying = _state13.isPlaying; | ||
var _props16 = this.props, | ||
additionalClass = _props16.additionalClass, | ||
indexSeparator = _props16.indexSeparator, | ||
isRTL = _props16.isRTL, | ||
items = _props16.items, | ||
thumbnailPosition = _props16.thumbnailPosition, | ||
renderFullscreenButton = _props16.renderFullscreenButton, | ||
renderCustomControls = _props16.renderCustomControls, | ||
renderLeftNav = _props16.renderLeftNav, | ||
renderRightNav = _props16.renderRightNav, | ||
showBullets = _props16.showBullets, | ||
showFullscreenButton = _props16.showFullscreenButton, | ||
showIndex = _props16.showIndex, | ||
showThumbnails = _props16.showThumbnails, | ||
showNav = _props16.showNav, | ||
showPlayButton = _props16.showPlayButton, | ||
renderPlayPauseButton = _props16.renderPlayPauseButton; | ||
var thumbnailStyle = this.getThumbnailStyle(); | ||
var _getSlideItems = this.getSlideItems(items), | ||
slides = _getSlideItems.slides, | ||
thumbnails = _getSlideItems.thumbnails, | ||
bullets = _getSlideItems.bullets; | ||
var slideWrapperClass = (0, _clsx2.default)('image-gallery-slide-wrapper', thumbnailPosition, { 'image-gallery-rtl': isRTL }); | ||
var slideWrapper = _react2.default.createElement( | ||
'div', | ||
{ ref: this.imageGallerySlideWrapper, className: slideWrapperClass }, | ||
renderCustomControls && renderCustomControls(), | ||
this.canSlide() ? _react2.default.createElement( | ||
_react2.default.Fragment, | ||
null, | ||
showNav && _react2.default.createElement( | ||
'span', | ||
{ key: 'navigation' }, | ||
renderLeftNav(this.slideLeft, !this.canSlideLeft()), | ||
renderRightNav(this.slideRight, !this.canSlideRight()) | ||
), | ||
_react2.default.createElement( | ||
'div', | ||
{ className: 'image-gallery-slides' }, | ||
slides | ||
_reactSwipeable.Swipeable, | ||
{ | ||
className: 'image-gallery-swipe', | ||
key: 'swipeable', | ||
delta: 0, | ||
onSwiping: this.handleSwiping, | ||
onSwiped: this.handleOnSwiped | ||
}, | ||
_react2.default.createElement( | ||
'div', | ||
{ className: 'image-gallery-slides' }, | ||
slides | ||
) | ||
) | ||
)] : _react2.default.createElement( | ||
) : _react2.default.createElement( | ||
'div', | ||
@@ -1134,3 +1382,4 @@ { className: 'image-gallery-slides' }, | ||
), | ||
this.props.showBullets && _react2.default.createElement( | ||
showPlayButton && renderPlayPauseButton(this.togglePlay, isPlaying), | ||
showBullets && _react2.default.createElement( | ||
'div', | ||
@@ -1148,3 +1397,4 @@ { className: 'image-gallery-bullets' }, | ||
), | ||
this.props.showIndex && _react2.default.createElement( | ||
showFullscreenButton && renderFullscreenButton(this.toggleFullScreen, isFullscreen), | ||
showIndex && _react2.default.createElement( | ||
'div', | ||
@@ -1155,3 +1405,3 @@ { className: 'image-gallery-index' }, | ||
{ className: 'image-gallery-index-current' }, | ||
this.state.currentIndex + 1 | ||
currentIndex + 1 | ||
), | ||
@@ -1161,3 +1411,3 @@ _react2.default.createElement( | ||
{ className: 'image-gallery-index-separator' }, | ||
this.props.indexSeparator | ||
indexSeparator | ||
), | ||
@@ -1167,3 +1417,3 @@ _react2.default.createElement( | ||
{ className: 'image-gallery-index-total' }, | ||
this.props.items.length | ||
items.length | ||
) | ||
@@ -1173,13 +1423,10 @@ ) | ||
var classNames = ['image-gallery', this.props.additionalClass, modalFullscreen ? 'fullscreen-modal' : ''].filter(function (name) { | ||
return typeof name === 'string'; | ||
}).join(' '); | ||
var igClass = (0, _clsx2.default)('image-gallery', additionalClass, { 'fullscreen-modal': modalFullscreen }); | ||
var igContentClass = (0, _clsx2.default)('image-gallery-content', thumbnailPosition, { fullscreen: isFullscreen }); | ||
var thumbnailWrapperClass = (0, _clsx2.default)('image-gallery-thumbnails-wrapper', thumbnailPosition, { 'thumbnails-wrapper-rtl': !this.isThumbnailVertical() && isRTL }); | ||
return _react2.default.createElement( | ||
'div', | ||
{ | ||
ref: function ref(i) { | ||
return _this5._imageGallery = i; | ||
}, | ||
className: classNames, | ||
ref: this.imageGallery, | ||
className: igClass, | ||
'aria-live': 'polite' | ||
@@ -1189,11 +1436,9 @@ }, | ||
'div', | ||
{ | ||
className: 'image-gallery-content' + (isFullscreen ? ' fullscreen' : '') | ||
}, | ||
{ className: igContentClass }, | ||
(thumbnailPosition === 'bottom' || thumbnailPosition === 'right') && slideWrapper, | ||
this.props.showThumbnails && _react2.default.createElement( | ||
showThumbnails && _react2.default.createElement( | ||
'div', | ||
{ | ||
className: 'image-gallery-thumbnails-wrapper ' + thumbnailPosition + ' ' + (!this._isThumbnailVertical() && isRTL ? 'thumbnails-wrapper-rtl' : ''), | ||
style: this._getThumbnailBarHeight() | ||
className: thumbnailWrapperClass, | ||
style: this.getThumbnailBarHeight() | ||
}, | ||
@@ -1204,5 +1449,3 @@ _react2.default.createElement( | ||
className: 'image-gallery-thumbnails', | ||
ref: function ref(i) { | ||
return _this5._thumbnailsWrapper = i; | ||
} | ||
ref: this.thumbnailsWrapper | ||
}, | ||
@@ -1212,5 +1455,3 @@ _react2.default.createElement( | ||
{ | ||
ref: function ref(t) { | ||
return _this5._thumbnails = t; | ||
}, | ||
ref: this.thumbnails, | ||
className: 'image-gallery-thumbnails-container', | ||
@@ -1234,54 +1475,74 @@ style: thumbnailStyle, | ||
ImageGallery.propTypes = { | ||
flickThreshold: _propTypes2.default.number, | ||
items: _propTypes2.default.array.isRequired, | ||
showNav: _propTypes2.default.bool, | ||
autoPlay: _propTypes2.default.bool, | ||
lazyLoad: _propTypes2.default.bool, | ||
infinite: _propTypes2.default.bool, | ||
showIndex: _propTypes2.default.bool, | ||
showBullets: _propTypes2.default.bool, | ||
showThumbnails: _propTypes2.default.bool, | ||
showPlayButton: _propTypes2.default.bool, | ||
showFullscreenButton: _propTypes2.default.bool, | ||
disableThumbnailScroll: _propTypes2.default.bool, | ||
disableArrowKeys: _propTypes2.default.bool, | ||
disableSwipe: _propTypes2.default.bool, | ||
useBrowserFullscreen: _propTypes2.default.bool, | ||
preventDefaultTouchmoveEvent: _propTypes2.default.bool, | ||
defaultImage: _propTypes2.default.string, | ||
indexSeparator: _propTypes2.default.string, | ||
thumbnailPosition: _propTypes2.default.string, | ||
startIndex: _propTypes2.default.number, | ||
slideDuration: _propTypes2.default.number, | ||
slideInterval: _propTypes2.default.number, | ||
slideOnThumbnailOver: _propTypes2.default.bool, | ||
swipeThreshold: _propTypes2.default.number, | ||
swipingTransitionDuration: _propTypes2.default.number, | ||
onSlide: _propTypes2.default.func, | ||
onScreenChange: _propTypes2.default.func, | ||
onPause: _propTypes2.default.func, | ||
onPlay: _propTypes2.default.func, | ||
onClick: _propTypes2.default.func, | ||
onImageLoad: _propTypes2.default.func, | ||
onImageError: _propTypes2.default.func, | ||
onTouchMove: _propTypes2.default.func, | ||
onTouchEnd: _propTypes2.default.func, | ||
onTouchStart: _propTypes2.default.func, | ||
onMouseOver: _propTypes2.default.func, | ||
onMouseLeave: _propTypes2.default.func, | ||
onThumbnailError: _propTypes2.default.func, | ||
onThumbnailClick: _propTypes2.default.func, | ||
renderCustomControls: _propTypes2.default.func, | ||
renderLeftNav: _propTypes2.default.func, | ||
renderRightNav: _propTypes2.default.func, | ||
renderPlayPauseButton: _propTypes2.default.func, | ||
renderFullscreenButton: _propTypes2.default.func, | ||
renderItem: _propTypes2.default.func, | ||
stopPropagation: _propTypes2.default.bool, | ||
additionalClass: _propTypes2.default.string, | ||
useTranslate3D: _propTypes2.default.bool, | ||
isRTL: _propTypes2.default.bool | ||
flickThreshold: _propTypes.number, | ||
items: (0, _propTypes.arrayOf)((0, _propTypes.shape)({ | ||
bulletClass: _propTypes.string, | ||
bulletOnClick: _propTypes.func, | ||
description: _propTypes.string, | ||
original: _propTypes.string.isRequired, | ||
originalAlt: _propTypes.string, | ||
originalTitle: _propTypes.string, | ||
thumbnail: _propTypes.string, | ||
thumbnailAlt: _propTypes.string, | ||
thumbnailLabel: _propTypes.string, | ||
thumbnailTitle: _propTypes.string, | ||
originalClass: _propTypes.string, | ||
thumbnailClass: _propTypes.string, | ||
renderItem: _propTypes.func, | ||
renderThumbInner: _propTypes.func, | ||
imageSet: imageSetType, | ||
srcSet: _propTypes.string, | ||
sizes: _propTypes.string | ||
})).isRequired, | ||
showNav: _propTypes.bool, | ||
autoPlay: _propTypes.bool, | ||
lazyLoad: _propTypes.bool, | ||
infinite: _propTypes.bool, | ||
showIndex: _propTypes.bool, | ||
showBullets: _propTypes.bool, | ||
showThumbnails: _propTypes.bool, | ||
showPlayButton: _propTypes.bool, | ||
showFullscreenButton: _propTypes.bool, | ||
disableThumbnailScroll: _propTypes.bool, | ||
disableKeyDown: _propTypes.bool, | ||
disableSwipe: _propTypes.bool, | ||
useBrowserFullscreen: _propTypes.bool, | ||
preventDefaultTouchmoveEvent: _propTypes.bool, | ||
onErrorImageURL: _propTypes.string, | ||
indexSeparator: _propTypes.string, | ||
thumbnailPosition: _propTypes.string, | ||
startIndex: _propTypes.number, | ||
slideDuration: _propTypes.number, | ||
slideInterval: _propTypes.number, | ||
slideOnThumbnailOver: _propTypes.bool, | ||
swipeThreshold: _propTypes.number, | ||
swipingTransitionDuration: _propTypes.number, | ||
onSlide: _propTypes.func, | ||
onScreenChange: _propTypes.func, | ||
onPause: _propTypes.func, | ||
onPlay: _propTypes.func, | ||
onClick: _propTypes.func, | ||
onImageLoad: _propTypes.func, | ||
onImageError: _propTypes.func, | ||
onTouchMove: _propTypes.func, | ||
onTouchEnd: _propTypes.func, | ||
onTouchStart: _propTypes.func, | ||
onMouseOver: _propTypes.func, | ||
onMouseLeave: _propTypes.func, | ||
onThumbnailError: _propTypes.func, | ||
onThumbnailClick: _propTypes.func, | ||
renderCustomControls: _propTypes.func, | ||
renderLeftNav: _propTypes.func, | ||
renderRightNav: _propTypes.func, | ||
renderPlayPauseButton: _propTypes.func, | ||
renderFullscreenButton: _propTypes.func, | ||
renderItem: _propTypes.func, | ||
renderThumbInner: _propTypes.func, | ||
stopPropagation: _propTypes.bool, | ||
additionalClass: _propTypes.string, | ||
useTranslate3D: _propTypes.bool, | ||
isRTL: _propTypes.bool | ||
}; | ||
ImageGallery.defaultProps = { | ||
items: [], | ||
onErrorImageURL: '', | ||
additionalClass: '', | ||
showNav: true, | ||
@@ -1297,3 +1558,3 @@ autoPlay: false, | ||
disableThumbnailScroll: false, | ||
disableArrowKeys: false, | ||
disableKeyDown: false, | ||
disableSwipe: false, | ||
@@ -1311,39 +1572,73 @@ useTranslate3D: true, | ||
swipingTransitionDuration: 0, | ||
onSlide: null, | ||
onScreenChange: null, | ||
onPause: null, | ||
onPlay: null, | ||
onClick: null, | ||
onImageLoad: null, | ||
onImageError: null, | ||
onTouchMove: null, | ||
onTouchEnd: null, | ||
onTouchStart: null, | ||
onMouseOver: null, | ||
onMouseLeave: null, | ||
onThumbnailError: null, | ||
onThumbnailClick: null, | ||
renderCustomControls: null, | ||
renderThumbInner: null, | ||
renderItem: null, | ||
slideInterval: 3000, | ||
slideOnThumbnailOver: false, | ||
swipeThreshold: 30, | ||
renderLeftNav: function renderLeftNav(onClick, disabled) { | ||
return _react2.default.createElement('button', { | ||
type: 'button', | ||
className: 'image-gallery-left-nav', | ||
disabled: disabled, | ||
onClick: onClick, | ||
'aria-label': 'Previous Slide' | ||
}); | ||
return _react2.default.createElement( | ||
'button', | ||
{ | ||
type: 'button', | ||
className: 'image-gallery-icon image-gallery-left-nav', | ||
disabled: disabled, | ||
onClick: onClick, | ||
'aria-label': 'Previous Slide' | ||
}, | ||
_react2.default.createElement(_SVG2.default, { icon: 'left', viewBox: '6 0 12 24' }) | ||
); | ||
}, | ||
renderRightNav: function renderRightNav(onClick, disabled) { | ||
return _react2.default.createElement('button', { | ||
type: 'button', | ||
className: 'image-gallery-right-nav', | ||
disabled: disabled, | ||
onClick: onClick, | ||
'aria-label': 'Next Slide' | ||
}); | ||
return _react2.default.createElement( | ||
'button', | ||
{ | ||
type: 'button', | ||
className: 'image-gallery-icon image-gallery-right-nav', | ||
disabled: disabled, | ||
onClick: onClick, | ||
'aria-label': 'Next Slide' | ||
}, | ||
_react2.default.createElement(_SVG2.default, { icon: 'right', viewBox: '6 0 12 24' }) | ||
); | ||
}, | ||
renderPlayPauseButton: function renderPlayPauseButton(onClick, isPlaying) { | ||
return _react2.default.createElement('button', { | ||
type: 'button', | ||
className: 'image-gallery-play-button' + (isPlaying ? ' active' : ''), | ||
onClick: onClick, | ||
'aria-label': 'Play or Pause Slideshow' | ||
}); | ||
return _react2.default.createElement( | ||
'button', | ||
{ | ||
type: 'button', | ||
className: 'image-gallery-icon image-gallery-play-button', | ||
onClick: onClick, | ||
'aria-label': 'Play or Pause Slideshow' | ||
}, | ||
_react2.default.createElement(_SVG2.default, { strokeWidth: 2, icon: isPlaying ? 'pause' : 'play' }) | ||
); | ||
}, | ||
renderFullscreenButton: function renderFullscreenButton(onClick, isFullscreen) { | ||
return _react2.default.createElement('button', { | ||
type: 'button', | ||
className: 'image-gallery-fullscreen-button' + (isFullscreen ? ' active' : ''), | ||
onClick: onClick, | ||
'aria-label': 'Open Fullscreen' | ||
}); | ||
return _react2.default.createElement( | ||
'button', | ||
{ | ||
type: 'button', | ||
className: 'image-gallery-icon image-gallery-fullscreen-button', | ||
onClick: onClick, | ||
'aria-label': 'Open Fullscreen' | ||
}, | ||
_react2.default.createElement(_SVG2.default, { strokeWidth: 2, icon: isFullscreen ? 'minimize' : 'maximize' }) | ||
); | ||
} | ||
}; | ||
exports.default = ImageGallery; |
@@ -37,10 +37,2 @@ var babel = require('gulp-babel'); | ||
gulp.task('sass-no-icon', function () { | ||
gulp.src('./styles/scss/image-gallery-no-icon.scss') | ||
.pipe(sass()) | ||
.pipe(rename('image-gallery-no-icon.css')) | ||
.pipe(gulp.dest('./styles/css/')) | ||
.pipe(livereload()); | ||
}); | ||
gulp.task('scripts', function() { | ||
@@ -53,2 +45,3 @@ watchify(browserify({ | ||
.bundle() | ||
.on('error', err => { console.error('error is', err) }) | ||
.pipe(source('example.js')) | ||
@@ -89,7 +82,7 @@ .pipe(buffer()) | ||
gulp.watch(['styles/**/*.scss'], ['sass']); | ||
gulp.watch(['src/*.jsx', 'example/app.js'], ['scripts']); | ||
gulp.watch(['src/*.jsx', 'src/icons/*.jsx', 'example/app.js'], ['scripts']); | ||
}); | ||
gulp.task('dev', ['watch', 'scripts', 'sass', 'server']); | ||
gulp.task('build', ['source-js', 'sass', 'sass-no-icon']); | ||
gulp.task('build', ['source-js', 'sass']); | ||
gulp.task('demo', ['demo-src']); |
{ | ||
"name": "react-image-gallery", | ||
"version": "0.9.1", | ||
"version": "1.0.0", | ||
"description": "React carousel image gallery component with thumbnail and mobile support", | ||
@@ -47,3 +47,3 @@ "main": "./build/image-gallery.js", | ||
"browserify": "^13.1.1", | ||
"eslint": "^5.3.0", | ||
"eslint": "^5.4.0", | ||
"eslint-config-airbnb": "^17.1.1", | ||
@@ -72,3 +72,5 @@ "eslint-plugin-import": "^2.18.0", | ||
"dependencies": { | ||
"clsx": "^1.0.4", | ||
"lodash.debounce": "^4.0.8", | ||
"lodash.isequal": "^4.5.0", | ||
"lodash.throttle": "^4.1.1", | ||
@@ -75,0 +77,0 @@ "prop-types": "^15.5.8", |
@@ -38,6 +38,2 @@ React Carousel Image Gallery | ||
@import "~react-image-gallery/styles/css/image-gallery.css"; | ||
# Stylesheet with no icons | ||
node_modules/react-image-gallery/styles/scss/image-gallery-no-icon.scss | ||
node_modules/react-image-gallery/styles/css/image-gallery-no-icon.css | ||
``` | ||
@@ -50,26 +46,21 @@ | ||
class MyComponent extends React.Component { | ||
const images = [ | ||
{ | ||
original: 'https://picsum.photos/id/1018/1000/600/', | ||
thumbnail: 'https://picsum.photos/id/1018/250/150/', | ||
}, | ||
{ | ||
original: 'https://picsum.photos/id/1015/1000/600/', | ||
thumbnail: 'https://picsum.photos/id/1015/250/150/', | ||
}, | ||
{ | ||
original: 'https://picsum.photos/id/1019/1000/600/', | ||
thumbnail: 'https://picsum.photos/id/1019/250/150/', | ||
}, | ||
]; | ||
class MyGallery extends React.Component { | ||
render() { | ||
const images = [ | ||
{ | ||
original: 'https://picsum.photos/id/1018/1000/600/', | ||
thumbnail: 'https://picsum.photos/id/1018/250/150/', | ||
}, | ||
{ | ||
original: 'https://picsum.photos/id/1015/1000/600/', | ||
thumbnail: 'https://picsum.photos/id/1015/250/150/', | ||
}, | ||
{ | ||
original: 'https://picsum.photos/id/1019/1000/600/', | ||
thumbnail: 'https://picsum.photos/id/1019/250/150/', | ||
}, | ||
]; | ||
return ( | ||
<ImageGallery items={images} /> | ||
); | ||
return <ImageGallery items={images} />; | ||
} | ||
} | ||
@@ -120,6 +111,6 @@ ``` | ||
* disables the thumbnail container from adjusting | ||
* `disableArrowKeys`: Boolean, default `false` | ||
* disables keydown listener for left and right keyboard arrow keys | ||
* `disableKeyDown`: Boolean, default `false` | ||
* disables keydown listener for keyboard shortcuts (left arrow, right arrow, esc key) | ||
* `disableSwipe`: Boolean, default `false` | ||
* `defaultImage`: String, default `undefined` | ||
* `onErrorImageURL`: String, default `undefined` | ||
* an image src pointing to your default image if an image fails to load | ||
@@ -145,5 +136,5 @@ * handles both slide image, and thumbnail image | ||
* `onImageError`: Function, `callback(event)` | ||
* overrides defaultImage | ||
* overrides onErrorImage | ||
* `onThumbnailError`: Function, `callback(event)` | ||
* overrides defaultImage | ||
* overrides onErrorImage | ||
* `onThumbnailClick`: Function, `callback(event, index)` | ||
@@ -250,5 +241,6 @@ * `onImageLoad`: Function, `callback(event)` | ||
Each PR should be specific and isolated to the issue you're trying to fix. Please do not stack features/chores/refactors/enhancements in one PR. Describe your feature/implementation in the PR. If you're unsure its useful or if it is a major change, please open an issue first and get feedback. | ||
* Follow eslint provided | ||
* Comment your code | ||
* Describe your feature/implementation in the pullrequest | ||
* Write [clean](https://github.com/ryanmcdermott/clean-code-javascript) code | ||
@@ -267,5 +259,4 @@ | ||
# License | ||
MIT |
@@ -0,1 +1,2 @@ | ||
import clsx from 'clsx'; | ||
import React from 'react'; | ||
@@ -5,4 +6,13 @@ import { Swipeable, LEFT, RIGHT } from 'react-swipeable'; | ||
import debounce from 'lodash.debounce'; | ||
import isEqual from 'lodash.isequal'; | ||
import ResizeObserver from 'resize-observer-polyfill'; | ||
import PropTypes from 'prop-types'; | ||
import { | ||
arrayOf, | ||
bool, | ||
func, | ||
number, | ||
shape, | ||
string, | ||
} from 'prop-types'; | ||
import SVG from './SVG'; | ||
@@ -16,82 +26,89 @@ const screenChangeEvents = [ | ||
export default class ImageGallery extends React.Component { | ||
const imageSetType = arrayOf(shape({ | ||
srcSet: string, | ||
media: string, | ||
})); | ||
constructor(props) { | ||
super(props); | ||
this.state = { | ||
currentIndex: props.startIndex, | ||
thumbsTranslate: 0, | ||
offsetPercentage: 0, | ||
galleryWidth: 0, | ||
thumbnailsWrapperWidth: 0, | ||
thumbnailsWrapperHeight: 0, | ||
isFullscreen: false, | ||
isPlaying: false | ||
}; | ||
function isEnterOrSpaceKey(event) { | ||
const key = parseInt(event.keyCode || event.which || 0, 10); | ||
const ENTER_KEY_CODE = 66; | ||
const SPACEBAR_KEY_CODE = 62; | ||
return key === ENTER_KEY_CODE || key === SPACEBAR_KEY_CODE; | ||
} | ||
// Used to update the throttle if slideDuration changes | ||
this._unthrottledSlideToIndex = this.slideToIndex; | ||
this.slideToIndex = throttle(this._unthrottledSlideToIndex, | ||
props.slideDuration, | ||
{trailing: false}); | ||
if (props.lazyLoad) { | ||
this._lazyLoaded = []; | ||
} | ||
} | ||
export default class ImageGallery extends React.Component { | ||
static propTypes = { | ||
flickThreshold: PropTypes.number, | ||
items: PropTypes.array.isRequired, | ||
showNav: PropTypes.bool, | ||
autoPlay: PropTypes.bool, | ||
lazyLoad: PropTypes.bool, | ||
infinite: PropTypes.bool, | ||
showIndex: PropTypes.bool, | ||
showBullets: PropTypes.bool, | ||
showThumbnails: PropTypes.bool, | ||
showPlayButton: PropTypes.bool, | ||
showFullscreenButton: PropTypes.bool, | ||
disableThumbnailScroll: PropTypes.bool, | ||
disableArrowKeys: PropTypes.bool, | ||
disableSwipe: PropTypes.bool, | ||
useBrowserFullscreen: PropTypes.bool, | ||
preventDefaultTouchmoveEvent: PropTypes.bool, | ||
defaultImage: PropTypes.string, | ||
indexSeparator: PropTypes.string, | ||
thumbnailPosition: PropTypes.string, | ||
startIndex: PropTypes.number, | ||
slideDuration: PropTypes.number, | ||
slideInterval: PropTypes.number, | ||
slideOnThumbnailOver: PropTypes.bool, | ||
swipeThreshold: PropTypes.number, | ||
swipingTransitionDuration: PropTypes.number, | ||
onSlide: PropTypes.func, | ||
onScreenChange: PropTypes.func, | ||
onPause: PropTypes.func, | ||
onPlay: PropTypes.func, | ||
onClick: PropTypes.func, | ||
onImageLoad: PropTypes.func, | ||
onImageError: PropTypes.func, | ||
onTouchMove: PropTypes.func, | ||
onTouchEnd: PropTypes.func, | ||
onTouchStart: PropTypes.func, | ||
onMouseOver: PropTypes.func, | ||
onMouseLeave: PropTypes.func, | ||
onThumbnailError: PropTypes.func, | ||
onThumbnailClick: PropTypes.func, | ||
renderCustomControls: PropTypes.func, | ||
renderLeftNav: PropTypes.func, | ||
renderRightNav: PropTypes.func, | ||
renderPlayPauseButton: PropTypes.func, | ||
renderFullscreenButton: PropTypes.func, | ||
renderItem: PropTypes.func, | ||
stopPropagation: PropTypes.bool, | ||
additionalClass: PropTypes.string, | ||
useTranslate3D: PropTypes.bool, | ||
isRTL: PropTypes.bool | ||
flickThreshold: number, | ||
items: arrayOf(shape({ | ||
bulletClass: string, | ||
bulletOnClick: func, | ||
description: string, | ||
original: string.isRequired, | ||
originalAlt: string, | ||
originalTitle: string, | ||
thumbnail: string, | ||
thumbnailAlt: string, | ||
thumbnailLabel: string, | ||
thumbnailTitle: string, | ||
originalClass: string, | ||
thumbnailClass: string, | ||
renderItem: func, | ||
renderThumbInner: func, | ||
imageSet: imageSetType, | ||
srcSet: string, | ||
sizes: string, | ||
})).isRequired, | ||
showNav: bool, | ||
autoPlay: bool, | ||
lazyLoad: bool, | ||
infinite: bool, | ||
showIndex: bool, | ||
showBullets: bool, | ||
showThumbnails: bool, | ||
showPlayButton: bool, | ||
showFullscreenButton: bool, | ||
disableThumbnailScroll: bool, | ||
disableKeyDown: bool, | ||
disableSwipe: bool, | ||
useBrowserFullscreen: bool, | ||
preventDefaultTouchmoveEvent: bool, | ||
onErrorImageURL: string, | ||
indexSeparator: string, | ||
thumbnailPosition: string, | ||
startIndex: number, | ||
slideDuration: number, | ||
slideInterval: number, | ||
slideOnThumbnailOver: bool, | ||
swipeThreshold: number, | ||
swipingTransitionDuration: number, | ||
onSlide: func, | ||
onScreenChange: func, | ||
onPause: func, | ||
onPlay: func, | ||
onClick: func, | ||
onImageLoad: func, | ||
onImageError: func, | ||
onTouchMove: func, | ||
onTouchEnd: func, | ||
onTouchStart: func, | ||
onMouseOver: func, | ||
onMouseLeave: func, | ||
onThumbnailError: func, | ||
onThumbnailClick: func, | ||
renderCustomControls: func, | ||
renderLeftNav: func, | ||
renderRightNav: func, | ||
renderPlayPauseButton: func, | ||
renderFullscreenButton: func, | ||
renderItem: func, | ||
renderThumbInner: func, | ||
stopPropagation: bool, | ||
additionalClass: string, | ||
useTranslate3D: bool, | ||
isRTL: bool, | ||
}; | ||
static defaultProps = { | ||
items: [], | ||
onErrorImageURL: '', | ||
additionalClass: '', | ||
showNav: true, | ||
@@ -107,3 +124,3 @@ autoPlay: false, | ||
disableThumbnailScroll: false, | ||
disableArrowKeys: false, | ||
disableKeyDown: false, | ||
disableSwipe: false, | ||
@@ -121,80 +138,161 @@ useTranslate3D: true, | ||
swipingTransitionDuration: 0, | ||
onSlide: null, | ||
onScreenChange: null, | ||
onPause: null, | ||
onPlay: null, | ||
onClick: null, | ||
onImageLoad: null, | ||
onImageError: null, | ||
onTouchMove: null, | ||
onTouchEnd: null, | ||
onTouchStart: null, | ||
onMouseOver: null, | ||
onMouseLeave: null, | ||
onThumbnailError: null, | ||
onThumbnailClick: null, | ||
renderCustomControls: null, | ||
renderThumbInner: null, | ||
renderItem: null, | ||
slideInterval: 3000, | ||
slideOnThumbnailOver: false, | ||
swipeThreshold: 30, | ||
renderLeftNav: (onClick, disabled) => { | ||
return ( | ||
<button | ||
type='button' | ||
className='image-gallery-left-nav' | ||
disabled={disabled} | ||
onClick={onClick} | ||
aria-label='Previous Slide' | ||
/> | ||
); | ||
}, | ||
renderRightNav: (onClick, disabled) => { | ||
return ( | ||
<button | ||
type='button' | ||
className='image-gallery-right-nav' | ||
disabled={disabled} | ||
onClick={onClick} | ||
aria-label='Next Slide' | ||
/> | ||
); | ||
}, | ||
renderPlayPauseButton: (onClick, isPlaying) => { | ||
return ( | ||
<button | ||
type='button' | ||
className={ | ||
`image-gallery-play-button${isPlaying ? ' active' : ''}`} | ||
onClick={onClick} | ||
aria-label='Play or Pause Slideshow' | ||
/> | ||
); | ||
}, | ||
renderFullscreenButton: (onClick, isFullscreen) => { | ||
return ( | ||
<button | ||
type='button' | ||
className={ | ||
`image-gallery-fullscreen-button${isFullscreen ? ' active' : ''}`} | ||
onClick={onClick} | ||
aria-label='Open Fullscreen' | ||
/> | ||
); | ||
}, | ||
renderLeftNav: (onClick, disabled) => ( | ||
<button | ||
type="button" | ||
className="image-gallery-icon image-gallery-left-nav" | ||
disabled={disabled} | ||
onClick={onClick} | ||
aria-label="Previous Slide" | ||
> | ||
<SVG icon="left" viewBox="6 0 12 24" /> | ||
</button> | ||
), | ||
renderRightNav: (onClick, disabled) => ( | ||
<button | ||
type="button" | ||
className="image-gallery-icon image-gallery-right-nav" | ||
disabled={disabled} | ||
onClick={onClick} | ||
aria-label="Next Slide" | ||
> | ||
<SVG icon="right" viewBox="6 0 12 24" /> | ||
</button> | ||
), | ||
renderPlayPauseButton: (onClick, isPlaying) => ( | ||
<button | ||
type="button" | ||
className="image-gallery-icon image-gallery-play-button" | ||
onClick={onClick} | ||
aria-label="Play or Pause Slideshow" | ||
> | ||
<SVG strokeWidth={2} icon={isPlaying ? 'pause' : 'play'} /> | ||
</button> | ||
), | ||
renderFullscreenButton: (onClick, isFullscreen) => ( | ||
<button | ||
type="button" | ||
className="image-gallery-icon image-gallery-fullscreen-button" | ||
onClick={onClick} | ||
aria-label="Open Fullscreen" | ||
> | ||
<SVG strokeWidth={2} icon={isFullscreen ? 'minimize' : 'maximize'} /> | ||
</button> | ||
), | ||
}; | ||
constructor(props) { | ||
super(props); | ||
this.state = { | ||
currentIndex: props.startIndex, | ||
thumbsTranslate: 0, | ||
offsetPercentage: 0, | ||
galleryWidth: 0, | ||
thumbnailsWrapperWidth: 0, | ||
thumbnailsWrapperHeight: 0, | ||
isFullscreen: false, | ||
isPlaying: false, | ||
}; | ||
this.loadedImages = {}; | ||
this.imageGallery = React.createRef(); | ||
this.thumbnailsWrapper = React.createRef(); | ||
this.thumbnails = React.createRef(); | ||
this.imageGallerySlideWrapper = React.createRef(); | ||
// bindings | ||
this.handleKeyDown = this.handleKeyDown.bind(this); | ||
this.handleMouseDown = this.handleMouseDown.bind(this); | ||
this.handleOnSwiped = this.handleOnSwiped.bind(this); | ||
this.handleScreenChange = this.handleScreenChange.bind(this); | ||
this.handleSwiping = this.handleSwiping.bind(this); | ||
this.onThumbnailMouseLeave = this.onThumbnailMouseLeave.bind(this); | ||
this.pauseOrPlay = this.pauseOrPlay.bind(this); | ||
this.renderThumbInner = this.renderThumbInner.bind(this); | ||
this.renderItem = this.renderItem.bind(this); | ||
this.slideLeft = this.slideLeft.bind(this); | ||
this.slideRight = this.slideRight.bind(this); | ||
this.toggleFullScreen = this.toggleFullScreen.bind(this); | ||
this.togglePlay = this.togglePlay.bind(this); | ||
// Used to update the throttle if slideDuration changes | ||
this.unthrottledSlideToIndex = this.slideToIndex; | ||
this.slideToIndex = throttle( | ||
this.unthrottledSlideToIndex, props.slideDuration, { trailing: false }, | ||
); | ||
if (props.lazyLoad) { | ||
this.lazyLoaded = []; | ||
} | ||
} | ||
componentDidMount() { | ||
if (this.props.autoPlay) { | ||
const { autoPlay } = this.props; | ||
if (autoPlay) { | ||
this.play(); | ||
} | ||
window.addEventListener('keydown', this._handleKeyDown); | ||
this._onScreenChangeEvent(); | ||
window.addEventListener('keydown', this.handleKeyDown); | ||
window.addEventListener('mousedown', this.handleMouseDown); | ||
this.initResizeObserver(this.imageGallerySlideWrapper); | ||
this.addScreenChangeEvent(); | ||
} | ||
componentDidUpdate(prevProps, prevState) { | ||
const itemsSizeChanged = prevProps.items.length !== this.props.items.length; | ||
const itemsChanged = JSON.stringify(prevProps.items) !== JSON.stringify(this.props.items); | ||
const startIndexUpdated = prevProps.startIndex !== this.props.startIndex; | ||
const { | ||
items, | ||
lazyLoad, | ||
slideDuration, | ||
startIndex, | ||
thumbnailPosition, | ||
} = this.props; | ||
const { currentIndex } = this.state; | ||
const itemsSizeChanged = prevProps.items.length !== items.length; | ||
const itemsChanged = !isEqual(prevProps.items, items); | ||
const startIndexUpdated = prevProps.startIndex !== startIndex; | ||
const thumbnailsPositionChanged = prevProps.thumbnailPosition !== thumbnailPosition; | ||
if (thumbnailsPositionChanged) { | ||
// re-initialize resizeObserver because slides was unmounted and mounted again | ||
this.removeResizeObserver(); | ||
this.initResizeObserver(this.imageGallerySlideWrapper); | ||
} | ||
if (itemsSizeChanged) { | ||
this._handleResize(); | ||
this.handleResize(); | ||
} | ||
if (prevState.currentIndex !== this.state.currentIndex) { | ||
this._slideThumbnailBar(prevState.currentIndex); | ||
if (prevState.currentIndex !== currentIndex) { | ||
this.slideThumbnailBar(prevState.currentIndex); | ||
} | ||
// if slideDuration changes, update slideToIndex throttle | ||
if (prevProps.slideDuration !== this.props.slideDuration) { | ||
this.slideToIndex = throttle(this._unthrottledSlideToIndex, | ||
this.props.slideDuration, | ||
{trailing: false}); | ||
if (prevProps.slideDuration !== slideDuration) { | ||
this.slideToIndex = throttle( | ||
this.unthrottledSlideToIndex, slideDuration, { trailing: false }, | ||
); | ||
} | ||
if (this.props.lazyLoad && (!prevProps.lazyLoad || itemsChanged)) { | ||
this._lazyLoaded = []; | ||
if (lazyLoad && (!prevProps.lazyLoad || itemsChanged)) { | ||
this.lazyLoaded = []; | ||
} | ||
if (startIndexUpdated || itemsChanged) { | ||
this.setState({ currentIndex: this.props.startIndex }); | ||
// TODO: this should be fix/removed if all it is doing | ||
// is resetting the gallery currentIndext state | ||
this.setState({ currentIndex: startIndex }); | ||
} | ||
@@ -204,290 +302,59 @@ } | ||
componentWillUnmount() { | ||
window.removeEventListener('keydown', this._handleKeyDown); | ||
this._offScreenChangeEvent(); | ||
if (this._intervalId) { | ||
window.clearInterval(this._intervalId); | ||
this._intervalId = null; | ||
window.removeEventListener('keydown', this.handleKeyDown); | ||
window.removeEventListener('mousedown', this.handleMouseDown); | ||
this.removeScreenChangeEvent(); | ||
this.removeResizeObserver(); | ||
if (this.intervalId) { | ||
window.clearInterval(this.intervalId); | ||
this.intervalId = null; | ||
} | ||
if(this.resizeObserver && this._imageGallerySlideWrapper) { | ||
this.resizeObserver.unobserve(this._imageGallerySlideWrapper); | ||
if (this.transitionTimer) { | ||
window.clearTimeout(this.transitionTimer); | ||
} | ||
if (this._transitionTimer) { | ||
window.clearTimeout(this._transitionTimer); | ||
} | ||
if (this._createResizeObserver) { | ||
this._createResizeObserver(); | ||
} | ||
} | ||
play(callback = true) { | ||
if (!this._intervalId) { | ||
const {slideInterval, slideDuration} = this.props; | ||
this.setState({isPlaying: true}); | ||
this._intervalId = window.setInterval(() => { | ||
if (!this.props.infinite && !this._canSlideRight()) { | ||
this.pause(); | ||
} else { | ||
this.slideToIndex(this.state.currentIndex + 1); | ||
} | ||
}, Math.max(slideInterval, slideDuration)); | ||
if (this.props.onPlay && callback) { | ||
this.props.onPlay(this.state.currentIndex); | ||
} | ||
} | ||
} | ||
pause(callback = true) { | ||
if (this._intervalId) { | ||
window.clearInterval(this._intervalId); | ||
this._intervalId = null; | ||
this.setState({isPlaying: false}); | ||
if (this.props.onPause && callback) { | ||
this.props.onPause(this.state.currentIndex); | ||
} | ||
} | ||
} | ||
setModalFullscreen(state) { | ||
this.setState({modalFullscreen: state}); | ||
// manually call because browser does not support screenchange events | ||
if (this.props.onScreenChange) { | ||
this.props.onScreenChange(state); | ||
} | ||
} | ||
fullScreen() { | ||
const gallery = this._imageGallery; | ||
if (this.props.useBrowserFullscreen) { | ||
if (gallery.requestFullscreen) { | ||
gallery.requestFullscreen(); | ||
} else if (gallery.msRequestFullscreen) { | ||
gallery.msRequestFullscreen(); | ||
} else if (gallery.mozRequestFullScreen) { | ||
gallery.mozRequestFullScreen(); | ||
} else if (gallery.webkitRequestFullscreen) { | ||
gallery.webkitRequestFullscreen(); | ||
} else { | ||
// fallback to fullscreen modal for unsupported browsers | ||
this.setModalFullscreen(true); | ||
} | ||
} else { | ||
this.setModalFullscreen(true); | ||
} | ||
this.setState({isFullscreen: true}); | ||
} | ||
exitFullScreen() { | ||
if (this.state.isFullscreen) { | ||
if (this.props.useBrowserFullscreen) { | ||
if (document.exitFullscreen) { | ||
document.exitFullscreen(); | ||
} else if (document.webkitExitFullscreen) { | ||
document.webkitExitFullscreen(); | ||
} else if (document.mozCancelFullScreen) { | ||
document.mozCancelFullScreen(); | ||
} else if (document.msExitFullscreen) { | ||
document.msExitFullscreen(); | ||
} else { | ||
// fallback to fullscreen modal for unsupported browsers | ||
this.setModalFullscreen(false); | ||
} | ||
} else { | ||
this.setModalFullscreen(false); | ||
} | ||
this.setState({isFullscreen: false}); | ||
} | ||
} | ||
slideToIndex = (index, event) => { | ||
const {currentIndex, isTransitioning} = this.state; | ||
if (!isTransitioning) { | ||
if (event) { | ||
if (this._intervalId) { | ||
// user triggered event while ImageGallery is playing, reset interval | ||
this.pause(false); | ||
this.play(false); | ||
} | ||
} | ||
let slideCount = this.props.items.length - 1; | ||
let nextIndex = index; | ||
if (index < 0) { | ||
nextIndex = slideCount; | ||
} else if (index > slideCount) { | ||
nextIndex = 0; | ||
} | ||
this.setState({ | ||
previousIndex: currentIndex, | ||
currentIndex: nextIndex, | ||
isTransitioning: nextIndex !== currentIndex, | ||
offsetPercentage: 0, | ||
style: { | ||
transition: `all ${this.props.slideDuration}ms ease-out` | ||
} | ||
}, this._onSliding); | ||
} | ||
}; | ||
_onSliding = () => { | ||
const { isTransitioning } = this.state; | ||
this._transitionTimer = window.setTimeout(() => { | ||
onSliding() { | ||
const { currentIndex, isTransitioning } = this.state; | ||
const { onSlide, slideDuration } = this.props; | ||
this.transitionTimer = window.setTimeout(() => { | ||
if (isTransitioning) { | ||
this.setState({isTransitioning: !isTransitioning}); | ||
if (this.props.onSlide) { | ||
this.props.onSlide(this.state.currentIndex); | ||
this.setState({ isTransitioning: !isTransitioning }); | ||
if (onSlide) { | ||
onSlide(currentIndex); | ||
} | ||
} | ||
}, this.props.slideDuration + 50); | ||
}; | ||
getCurrentIndex() { | ||
return this.state.currentIndex; | ||
}, slideDuration + 50); | ||
} | ||
_handleScreenChange = () => { | ||
/* | ||
handles screen change events that the browser triggers e.g. esc key | ||
*/ | ||
const fullScreenElement = document.fullscreenElement || | ||
document.msFullscreenElement || | ||
document.mozFullScreenElement || | ||
document.webkitFullscreenElement; | ||
if (this.props.onScreenChange) { | ||
this.props.onScreenChange(fullScreenElement); | ||
onThumbnailClick(event, index) { | ||
const { onThumbnailClick } = this.props; | ||
this.slideToIndex(index, event); | ||
if (onThumbnailClick) { | ||
onThumbnailClick(event, index); | ||
} | ||
this.setState({isFullscreen: !!fullScreenElement}); | ||
}; | ||
_onScreenChangeEvent() { | ||
screenChangeEvents.map(eventName => { | ||
document.addEventListener(eventName, this._handleScreenChange); | ||
}); | ||
} | ||
_offScreenChangeEvent() { | ||
screenChangeEvents.map(eventName => { | ||
document.removeEventListener(eventName, this._handleScreenChange); | ||
}); | ||
} | ||
_toggleFullScreen = () => { | ||
if (this.state.isFullscreen) { | ||
this.exitFullScreen(); | ||
} else { | ||
this.fullScreen(); | ||
onThumbnailMouseOver(event, index) { | ||
if (this.thumbnailMouseOverTimer) { | ||
window.clearTimeout(this.thumbnailMouseOverTimer); | ||
this.thumbnailMouseOverTimer = null; | ||
} | ||
}; | ||
_togglePlay = () => { | ||
if (this._intervalId) { | ||
this.thumbnailMouseOverTimer = window.setTimeout(() => { | ||
this.slideToIndex(index); | ||
this.pause(); | ||
} else { | ||
this.play(); | ||
} | ||
}; | ||
}, 300); | ||
} | ||
_initGalleryResizing = (element) => { | ||
/* | ||
When image-gallery-slide-wrapper unmounts and mounts when thumbnail bar position is changed | ||
ref is called twice, once with null and another with the element. | ||
Make sure element is available before calling observe. | ||
*/ | ||
if (element) { | ||
this._imageGallerySlideWrapper = element; | ||
this.resizeObserver = new ResizeObserver(this._createResizeObserver); | ||
this.resizeObserver.observe(element); | ||
} | ||
}; | ||
_createResizeObserver = debounce((entries) => { | ||
if (!entries) return; | ||
entries.forEach(() => { | ||
this._handleResize(); | ||
}); | ||
}, 300); | ||
_handleResize = () => { | ||
const { currentIndex } = this.state; | ||
if (this._imageGallery) { | ||
this.setState({ | ||
galleryWidth: this._imageGallery.offsetWidth | ||
}); | ||
} | ||
if (this._imageGallerySlideWrapper) { | ||
this.setState({ | ||
gallerySlideWrapperHeight: this._imageGallerySlideWrapper.offsetHeight | ||
}); | ||
} | ||
if (this._thumbnailsWrapper) { | ||
if (this._isThumbnailVertical()) { | ||
this.setState({thumbnailsWrapperHeight: this._thumbnailsWrapper.offsetHeight}); | ||
} else { | ||
this.setState({thumbnailsWrapperWidth: this._thumbnailsWrapper.offsetWidth}); | ||
onThumbnailMouseLeave() { | ||
if (this.thumbnailMouseOverTimer) { | ||
const { autoPlay } = this.props; | ||
window.clearTimeout(this.thumbnailMouseOverTimer); | ||
this.thumbnailMouseOverTimer = null; | ||
if (autoPlay) { | ||
this.play(); | ||
} | ||
} | ||
// Adjust thumbnail container when thumbnail width or height is adjusted | ||
this._setThumbsTranslate(-this._getThumbsTranslate(currentIndex)); | ||
}; | ||
_isThumbnailVertical() { | ||
const { thumbnailPosition } = this.props; | ||
return thumbnailPosition === 'left' || thumbnailPosition === 'right'; | ||
} | ||
_handleKeyDown = (event) => { | ||
if (this.props.disableArrowKeys) { | ||
return; | ||
} | ||
const LEFT_ARROW = 37; | ||
const RIGHT_ARROW = 39; | ||
const ESC_KEY = 27; | ||
const key = parseInt(event.keyCode || event.which || 0); | ||
switch(key) { | ||
case LEFT_ARROW: | ||
if (this._canSlideLeft() && !this._intervalId) { | ||
this._slideLeft(); | ||
} | ||
break; | ||
case RIGHT_ARROW: | ||
if (this._canSlideRight() && !this._intervalId) { | ||
this._slideRight(); | ||
} | ||
break; | ||
case ESC_KEY: | ||
if (this.state.isFullscreen && !this.props.useBrowserFullscreen) { | ||
this.exitFullScreen(); | ||
} | ||
} | ||
}; | ||
_handleImageError = (event) => { | ||
if (this.props.defaultImage && | ||
event.target.src.indexOf(this.props.defaultImage) === -1) { | ||
event.target.src = this.props.defaultImage; | ||
} | ||
}; | ||
_setScrollDirection(dir) { | ||
setScrollDirection(dir) { | ||
const { scrollingUpDown, scrollingLeftRight } = this.state; | ||
@@ -502,162 +369,49 @@ | ||
} | ||
}; | ||
_handleOnSwiped = ({ event, dir, velocity }) => { | ||
if (this.props.disableSwipe) return; | ||
const { scrollingUpDown, scrollingLeftRight } = this.state; | ||
const { isRTL } = this.props; | ||
if (this.props.stopPropagation) event.stopPropagation(); | ||
if (scrollingUpDown) { | ||
// user stopped scrollingUpDown | ||
this.setState({ scrollingUpDown: false }); | ||
} | ||
if (scrollingLeftRight) { | ||
// user stopped scrollingLeftRight | ||
this.setState({ scrollingLeftRight: false }); | ||
} | ||
if (!scrollingUpDown) { // don't swipe if user is scrolling | ||
const side = (dir === LEFT ? 1 : -1) * (isRTL ? -1 : 1); // if it is RTL the direction is reversed | ||
const isFlick = velocity > this.props.flickThreshold; | ||
this._handleOnSwipedTo(side, isFlick); | ||
} | ||
}; | ||
_handleOnSwipedTo(side, isFlick) { | ||
const { currentIndex, isTransitioning } = this.state; | ||
let slideTo = currentIndex; | ||
if ((this._sufficientSwipeOffset() || isFlick) && !isTransitioning) { | ||
slideTo += side; | ||
} | ||
if (side < 0) { | ||
if (!this._canSlideLeft()) { | ||
slideTo = currentIndex; | ||
} | ||
} else { | ||
if (!this._canSlideRight()) { | ||
slideTo = currentIndex; | ||
} | ||
} | ||
this._unthrottledSlideToIndex(slideTo); | ||
} | ||
_sufficientSwipeOffset() { | ||
return Math.abs(this.state.offsetPercentage) > this.props.swipeThreshold; | ||
setThumbsTranslate(thumbsTranslate) { | ||
this.setState({ thumbsTranslate }); | ||
} | ||
_handleSwiping = ({ event, absX, dir }) => { | ||
if (this.props.disableSwipe) return; | ||
const { galleryWidth, isTransitioning, scrollingUpDown, scrollingLeftRight } = this.state; | ||
const { swipingTransitionDuration } = this.props; | ||
this._setScrollDirection(dir); | ||
if (this.props.stopPropagation) event.stopPropagation(); | ||
if ( | ||
(this.props.preventDefaultTouchmoveEvent || scrollingLeftRight) | ||
&& event.cancelable | ||
) event.preventDefault(); | ||
if (!isTransitioning && !scrollingUpDown) { | ||
const side = dir === RIGHT ? 1 : -1; | ||
let offsetPercentage = (absX / galleryWidth * 100); | ||
if (Math.abs(offsetPercentage) >= 100) { | ||
offsetPercentage = 100; | ||
} | ||
const swipingTransition = { | ||
transition: `transform ${swipingTransitionDuration}ms ease-out` | ||
}; | ||
this.setState({ | ||
offsetPercentage: side * offsetPercentage, | ||
style: swipingTransition, | ||
}); | ||
} else { | ||
// don't move the slide | ||
this.setState({ offsetPercentage: 0 }); | ||
setModalFullscreen(state) { | ||
const { onScreenChange } = this.props; | ||
this.setState({ modalFullscreen: state }); | ||
// manually call because browser does not support screenchange events | ||
if (onScreenChange) { | ||
onScreenChange(state); | ||
} | ||
}; | ||
_canNavigate() { | ||
return this.props.items.length >= 2; | ||
} | ||
_canSlideLeft() { | ||
return this.props.infinite || | ||
(this.props.isRTL ? this._canSlideNext() : this._canSlidePrevious()); | ||
} | ||
getThumbsTranslate(indexDifference) { | ||
const { disableThumbnailScroll, items } = this.props; | ||
const { thumbnailsWrapperWidth, thumbnailsWrapperHeight } = this.state; | ||
let totalScroll; | ||
const thumbElement = this.thumbnails && this.thumbnails.current; | ||
_canSlideRight() { | ||
return this.props.infinite || | ||
(this.props.isRTL ? this._canSlidePrevious() : this._canSlideNext()); | ||
} | ||
if (disableThumbnailScroll) return 0; | ||
_canSlidePrevious() { | ||
return this.state.currentIndex > 0; | ||
} | ||
_canSlideNext() { | ||
return this.state.currentIndex < this.props.items.length - 1; | ||
} | ||
_slideThumbnailBar(previousIndex) { | ||
const { thumbsTranslate, currentIndex } = this.state; | ||
if (this.state.currentIndex === 0) { | ||
this._setThumbsTranslate(0); | ||
} else { | ||
let indexDifference = Math.abs(previousIndex - currentIndex); | ||
let scroll = this._getThumbsTranslate(indexDifference); | ||
if (scroll > 0) { | ||
if (previousIndex < currentIndex) { | ||
this._setThumbsTranslate(thumbsTranslate - scroll); | ||
} else if (previousIndex > currentIndex) { | ||
this._setThumbsTranslate(thumbsTranslate + scroll); | ||
} | ||
} | ||
} | ||
} | ||
_setThumbsTranslate(thumbsTranslate) { | ||
this.setState({thumbsTranslate}); | ||
} | ||
_getThumbsTranslate(indexDifference) { | ||
if (this.props.disableThumbnailScroll) { | ||
return 0; | ||
} | ||
const {thumbnailsWrapperWidth, thumbnailsWrapperHeight} = this.state; | ||
let totalScroll; | ||
if (this._thumbnails) { | ||
if (thumbElement) { | ||
// total scroll required to see the last thumbnail | ||
if (this._isThumbnailVertical()) { | ||
if (this._thumbnails.scrollHeight <= thumbnailsWrapperHeight) { | ||
if (this.isThumbnailVertical()) { | ||
if (thumbElement.scrollHeight <= thumbnailsWrapperHeight) { | ||
return 0; | ||
} | ||
totalScroll = this._thumbnails.scrollHeight - thumbnailsWrapperHeight; | ||
totalScroll = thumbElement.scrollHeight - thumbnailsWrapperHeight; | ||
} else { | ||
if (this._thumbnails.scrollWidth <= thumbnailsWrapperWidth || thumbnailsWrapperWidth <= 0) { | ||
if (thumbElement.scrollWidth <= thumbnailsWrapperWidth || thumbnailsWrapperWidth <= 0) { | ||
return 0; | ||
} | ||
totalScroll = this._thumbnails.scrollWidth - thumbnailsWrapperWidth; | ||
totalScroll = thumbElement.scrollWidth - thumbnailsWrapperWidth; | ||
} | ||
let totalThumbnails = this._thumbnails.children.length; | ||
// scroll-x required per index change | ||
let perIndexScroll = totalScroll / (totalThumbnails - 1); | ||
const perIndexScroll = totalScroll / (items.length - 1); | ||
return indexDifference * perIndexScroll; | ||
} | ||
return 0; | ||
} | ||
_getAlignmentClassName(index) { | ||
/* | ||
Necessary for lazing loading | ||
*/ | ||
let {currentIndex} = this.state; | ||
getAlignmentClassName(index) { | ||
// Necessary for lazing loading | ||
const { currentIndex } = this.state; | ||
const { infinite, items } = this.props; | ||
let alignment = ''; | ||
@@ -678,9 +432,11 @@ const leftClassName = 'left'; | ||
break; | ||
default: | ||
break; | ||
} | ||
if (this.props.items.length >= 3 && this.props.infinite) { | ||
if (index === 0 && currentIndex === this.props.items.length - 1) { | ||
if (items.length >= 3 && infinite) { | ||
if (index === 0 && currentIndex === items.length - 1) { | ||
// set first slide as right slide if were sliding right from last slide | ||
alignment = ` ${rightClassName}`; | ||
} else if (index === this.props.items.length - 1 && currentIndex === 0) { | ||
} else if (index === items.length - 1 && currentIndex === 0) { | ||
// set last slide as left slide if were sliding left from first slide | ||
@@ -694,17 +450,5 @@ alignment = ` ${leftClassName}`; | ||
_isGoingFromFirstToLast() { | ||
const {currentIndex, previousIndex} = this.state; | ||
const totalSlides = this.props.items.length - 1; | ||
return previousIndex === 0 && currentIndex === totalSlides; | ||
} | ||
_isGoingFromLastToFirst() { | ||
const {currentIndex, previousIndex} = this.state; | ||
const totalSlides = this.props.items.length - 1; | ||
return previousIndex === totalSlides && currentIndex === 0; | ||
} | ||
_getTranslateXForTwoSlide(index) { | ||
getTranslateXForTwoSlide(index) { | ||
// For taking care of infinite swipe when there are only two slides | ||
const {currentIndex, offsetPercentage, previousIndex} = this.state; | ||
const { currentIndex, offsetPercentage, previousIndex } = this.state; | ||
const baseTranslateX = -100 * currentIndex; | ||
@@ -729,7 +473,5 @@ let translateX = baseTranslateX + (index * 100) + offsetPercentage; | ||
// when swiped move the slide to the correct side | ||
if (previousIndex === 0 && index === 0 && | ||
offsetPercentage === 0 && this.direction === 'left') { | ||
if (previousIndex === 0 && index === 0 && offsetPercentage === 0 && this.direction === 'left') { | ||
translateX = 100; | ||
} else if (previousIndex === 1 && index === 1 && | ||
offsetPercentage === 0 && this.direction === 'right') { | ||
} else if (previousIndex === 1 && index === 1 && offsetPercentage === 0 && this.direction === 'right') { | ||
translateX = -100; | ||
@@ -739,7 +481,6 @@ } | ||
// keep the slide on the correct slide even when not a swipe | ||
if (currentIndex === 0 && index === 1 && | ||
offsetPercentage === 0 && this.direction === 'left') { | ||
if (currentIndex === 0 && index === 1 && offsetPercentage === 0 && this.direction === 'left') { | ||
translateX = -100; | ||
} else if (currentIndex === 1 && index === 0 && | ||
offsetPercentage === 0 && this.direction === 'right') { | ||
} | ||
if (currentIndex === 1 && index === 0 && offsetPercentage === 0 && this.direction === 'right') { | ||
translateX = 100; | ||
@@ -752,7 +493,6 @@ } | ||
_getThumbnailBarHeight() { | ||
if (this._isThumbnailVertical()) { | ||
return { | ||
height: this.state.gallerySlideWrapperHeight | ||
}; | ||
getThumbnailBarHeight() { | ||
if (this.isThumbnailVertical()) { | ||
const { gallerySlideWrapperHeight } = this.state; | ||
return { height: gallerySlideWrapperHeight }; | ||
} | ||
@@ -762,55 +502,10 @@ return {}; | ||
_shouldPushSlideOnInfiniteMode(index) { | ||
/* | ||
Push(show) slide if slide is the current slide, and the next slide | ||
OR | ||
The slide is going more than 1 slide left, or right, but not going from | ||
first to last and not going from last to first | ||
There is an edge case where if you go to the first or last slide, when they're | ||
not left, or right of each other they will try to catch up in the background | ||
so unless were going from first to last or vice versa we don't want the first | ||
or last slide to show up during our transition | ||
*/ | ||
return !this._slideIsTransitioning(index) || | ||
(this._ignoreIsTransitioning() && !this._isFirstOrLastSlide(index)); | ||
} | ||
_slideIsTransitioning(index) { | ||
/* | ||
returns true if the gallery is transitioning and the index is not the | ||
previous or currentIndex | ||
*/ | ||
const { isTransitioning, previousIndex, currentIndex } = this.state; | ||
const indexIsNotPreviousOrNextSlide = !(index === previousIndex || index === currentIndex); | ||
return isTransitioning && indexIsNotPreviousOrNextSlide; | ||
} | ||
_isFirstOrLastSlide(index) { | ||
const totalSlides = this.props.items.length - 1; | ||
const isLastSlide = index === totalSlides; | ||
const isFirstSlide = index === 0; | ||
return isLastSlide || isFirstSlide; | ||
} | ||
_ignoreIsTransitioning() { | ||
/* | ||
Ignore isTransitioning because were not going to sibling slides | ||
e.g. center to left or center to right | ||
*/ | ||
const { previousIndex, currentIndex } = this.state; | ||
const totalSlides = this.props.items.length - 1; | ||
// we want to show the in between slides transition | ||
const slidingMoreThanOneSlideLeftOrRight = Math.abs(previousIndex - currentIndex) > 1; | ||
const notGoingFromFirstToLast = !(previousIndex === 0 && currentIndex === totalSlides); | ||
const notGoingFromLastToFirst = !(previousIndex === totalSlides && currentIndex === 0); | ||
return slidingMoreThanOneSlideLeftOrRight && | ||
notGoingFromFirstToLast && | ||
notGoingFromLastToFirst; | ||
} | ||
_getSlideStyle(index) { | ||
const { currentIndex, offsetPercentage } = this.state; | ||
const { infinite, items, useTranslate3D, isRTL } = this.props; | ||
getSlideStyle(index) { | ||
const { currentIndex, offsetPercentage, slideStyle } = this.state; | ||
const { | ||
infinite, | ||
items, | ||
useTranslate3D, | ||
isRTL, | ||
} = this.props; | ||
const baseTranslateX = -100 * currentIndex; | ||
@@ -837,3 +532,3 @@ const totalSlides = items.length - 1; | ||
if (infinite && items.length === 2) { | ||
translateX = this._getTranslateXForTwoSlide(index); | ||
translateX = this.getTranslateXForTwoSlide(index); | ||
} | ||
@@ -847,3 +542,3 @@ | ||
return { | ||
return Object.assign({}, { | ||
WebkitTransform: translate, | ||
@@ -854,6 +549,11 @@ MozTransform: translate, | ||
transform: translate, | ||
}; | ||
}, slideStyle); | ||
} | ||
_getThumbnailStyle() { | ||
getCurrentIndex() { | ||
const { currentIndex } = this.state; | ||
return currentIndex; | ||
} | ||
getThumbnailStyle() { | ||
let translate; | ||
@@ -864,3 +564,3 @@ const { useTranslate3D, isRTL } = this.props; | ||
if (this._isThumbnailVertical()) { | ||
if (this.isThumbnailVertical()) { | ||
translate = `translate(0, ${thumbsTranslate}px)`; | ||
@@ -881,37 +581,647 @@ if (useTranslate3D) { | ||
OTransform: translate, | ||
transform: translate | ||
transform: translate, | ||
}; | ||
} | ||
_slideLeft = () => { | ||
this.props.isRTL ? this._slideNext() : this._slidePrevious(); | ||
}; | ||
getSlideItems(items) { | ||
const { currentIndex } = this.state; | ||
const { | ||
infinite, | ||
slideOnThumbnailOver, | ||
onClick, | ||
lazyLoad, | ||
onTouchMove, | ||
onTouchEnd, | ||
onTouchStart, | ||
onMouseOver, | ||
onMouseLeave, | ||
renderItem, | ||
renderThumbInner, | ||
showThumbnails, | ||
showBullets, | ||
} = this.props; | ||
_slideRight = () => { | ||
this.props.isRTL ? this._slidePrevious() : this._slideNext(); | ||
}; | ||
const slides = []; | ||
const thumbnails = []; | ||
const bullets = []; | ||
_slidePrevious = (event) => { | ||
this.slideToIndex(this.state.currentIndex - 1, event); | ||
}; | ||
items.forEach((item, index) => { | ||
const alignment = this.getAlignmentClassName(index); | ||
const originalClass = item.originalClass ? ` ${item.originalClass}` : ''; | ||
const thumbnailClass = item.thumbnailClass ? ` ${item.thumbnailClass}` : ''; | ||
const handleRenderItem = item.renderItem || renderItem || this.renderItem; | ||
const handleRenderThumbInner = item.renderThumbInner | ||
|| renderThumbInner || this.renderThumbInner; | ||
_slideNext = (event) => { | ||
this.slideToIndex(this.state.currentIndex + 1, event); | ||
}; | ||
const showItem = !lazyLoad || alignment || this.lazyLoaded[index]; | ||
if (showItem && lazyLoad && !this.lazyLoaded[index]) { | ||
this.lazyLoaded[index] = true; | ||
} | ||
_renderItem = (item) => { | ||
const onImageError = this.props.onImageError || this._handleImageError; | ||
const slideStyle = this.getSlideStyle(index); | ||
const slide = ( | ||
<div | ||
key={`slide-${item.original}`} | ||
tabIndex="-1" | ||
className={`image-gallery-slide ${alignment} ${originalClass}`} | ||
style={slideStyle} | ||
onClick={onClick} | ||
onKeyUp={this.handleSlideKeyUp} | ||
onTouchMove={onTouchMove} | ||
onTouchEnd={onTouchEnd} | ||
onTouchStart={onTouchStart} | ||
onMouseOver={onMouseOver} | ||
onFocus={onMouseOver} | ||
onMouseLeave={onMouseLeave} | ||
role="button" | ||
> | ||
{showItem ? handleRenderItem(item) : <div style={{ height: '100%' }} />} | ||
</div> | ||
); | ||
if (infinite) { | ||
// don't add some slides while transitioning to avoid background transitions | ||
if (this.shouldPushSlideOnInfiniteMode(index)) { | ||
slides.push(slide); | ||
} | ||
} else { | ||
slides.push(slide); | ||
} | ||
if (showThumbnails) { | ||
const igThumbnailClass = clsx( | ||
'image-gallery-thumbnail', | ||
thumbnailClass, | ||
{ active: currentIndex === index }, | ||
); | ||
thumbnails.push( | ||
<button | ||
key={`thumbnail-${item.original}`} | ||
type="button" | ||
tabIndex="0" | ||
aria-pressed={currentIndex === index ? 'true' : 'false'} | ||
aria-label={`Go to Slide ${index + 1}`} | ||
className={igThumbnailClass} | ||
onMouseLeave={slideOnThumbnailOver ? this.onThumbnailMouseLeave : null} | ||
onMouseOver={event => this.handleThumbnailMouseOver(event, index)} | ||
onFocus={event => this.handleThumbnailMouseOver(event, index)} | ||
onKeyUp={event => this.handleThumbnailKeyUp(event, index)} | ||
onClick={event => this.onThumbnailClick(event, index)} | ||
> | ||
{handleRenderThumbInner(item)} | ||
</button>, | ||
); | ||
} | ||
if (showBullets) { | ||
// generate bullet elements and store them in array | ||
const bulletOnClick = (event) => { | ||
if (item.bulletOnClick) { | ||
item.bulletOnClick({ item, itemIndex: index, currentIndex }); | ||
} | ||
return this.slideToIndex.call(this, index, event); | ||
}; | ||
const igBulletClass = clsx( | ||
'image-gallery-bullet', | ||
item.bulletClass, | ||
{ active: currentIndex === index }, | ||
); | ||
bullets.push( | ||
<button | ||
type="button" | ||
key={`bullet-${item.original}`} | ||
className={igBulletClass} | ||
onClick={bulletOnClick} | ||
aria-pressed={currentIndex === index ? 'true' : 'false'} | ||
aria-label={`Go to Slide ${index + 1}`} | ||
/>, | ||
); | ||
} | ||
}); | ||
return { | ||
slides, | ||
thumbnails, | ||
bullets, | ||
}; | ||
} | ||
ignoreIsTransitioning() { | ||
/* | ||
Ignore isTransitioning because were not going to sibling slides | ||
e.g. center to left or center to right | ||
*/ | ||
const { items } = this.props; | ||
const { previousIndex, currentIndex } = this.state; | ||
const totalSlides = items.length - 1; | ||
// we want to show the in between slides transition | ||
const slidingMoreThanOneSlideLeftOrRight = Math.abs(previousIndex - currentIndex) > 1; | ||
const notGoingFromFirstToLast = !(previousIndex === 0 && currentIndex === totalSlides); | ||
const notGoingFromLastToFirst = !(previousIndex === totalSlides && currentIndex === 0); | ||
return slidingMoreThanOneSlideLeftOrRight | ||
&& notGoingFromFirstToLast | ||
&& notGoingFromLastToFirst; | ||
} | ||
isFirstOrLastSlide(index) { | ||
const { items } = this.props; | ||
const totalSlides = items.length - 1; | ||
const isLastSlide = index === totalSlides; | ||
const isFirstSlide = index === 0; | ||
return isLastSlide || isFirstSlide; | ||
} | ||
slideIsTransitioning(index) { | ||
/* | ||
returns true if the gallery is transitioning and the index is not the | ||
previous or currentIndex | ||
*/ | ||
const { isTransitioning, previousIndex, currentIndex } = this.state; | ||
const indexIsNotPreviousOrNextSlide = !(index === previousIndex || index === currentIndex); | ||
return isTransitioning && indexIsNotPreviousOrNextSlide; | ||
} | ||
shouldPushSlideOnInfiniteMode(index) { | ||
/* | ||
Push (show) slide if slide is the current slide and the next slide | ||
OR | ||
The slide is going more than one slide left or right, but not going from | ||
first to last and not going from last to first | ||
Edge case: | ||
If you go to the first or last slide, when they're | ||
not left, or right of each other they will try to catch up in the background | ||
so unless were going from first to last or vice versa we don't want the first | ||
or last slide to show up during the transition | ||
*/ | ||
return !this.slideIsTransitioning(index) | ||
|| (this.ignoreIsTransitioning() && !this.isFirstOrLastSlide(index)); | ||
} | ||
slideThumbnailBar(previousIndex) { | ||
const { thumbsTranslate, currentIndex } = this.state; | ||
if (currentIndex === 0) { | ||
this.setThumbsTranslate(0); | ||
} else { | ||
const indexDifference = Math.abs(previousIndex - currentIndex); | ||
const scroll = this.getThumbsTranslate(indexDifference); | ||
if (scroll > 0) { | ||
if (previousIndex < currentIndex) { | ||
this.setThumbsTranslate(thumbsTranslate - scroll); | ||
} else if (previousIndex > currentIndex) { | ||
this.setThumbsTranslate(thumbsTranslate + scroll); | ||
} | ||
} | ||
} | ||
} | ||
canSlide() { | ||
const { items } = this.props; | ||
return items.length >= 2; | ||
} | ||
canSlideLeft() { | ||
const { infinite, isRTL } = this.props; | ||
return infinite || (isRTL ? this.canSlideNext() : this.canSlidePrevious()); | ||
} | ||
canSlideRight() { | ||
const { infinite, isRTL } = this.props; | ||
return infinite || (isRTL ? this.canSlidePrevious() : this.canSlideNext()); | ||
} | ||
canSlidePrevious() { | ||
const { currentIndex } = this.state; | ||
return currentIndex > 0; | ||
} | ||
canSlideNext() { | ||
const { currentIndex } = this.state; | ||
const { items } = this.props; | ||
return currentIndex < items.length - 1; | ||
} | ||
handleSwiping({ event, absX, dir }) { | ||
const { preventDefaultTouchmoveEvent, disableSwipe, stopPropagation } = this.props; | ||
const { | ||
galleryWidth, | ||
isTransitioning, | ||
scrollingUpDown, | ||
scrollingLeftRight, | ||
} = this.state; | ||
if (disableSwipe) return; | ||
const { swipingTransitionDuration } = this.props; | ||
this.setScrollDirection(dir); | ||
if (stopPropagation) event.stopPropagation(); | ||
if ((preventDefaultTouchmoveEvent || scrollingLeftRight) && event.cancelable) { | ||
event.preventDefault(); | ||
} | ||
if (!isTransitioning && !scrollingUpDown) { | ||
const side = dir === RIGHT ? 1 : -1; | ||
let offsetPercentage = (absX / galleryWidth * 100); | ||
if (Math.abs(offsetPercentage) >= 100) { | ||
offsetPercentage = 100; | ||
} | ||
const swipingTransition = { | ||
transition: `transform ${swipingTransitionDuration}ms ease-out`, | ||
}; | ||
this.setState({ | ||
offsetPercentage: side * offsetPercentage, | ||
slideStyle: swipingTransition, | ||
}); | ||
} else { | ||
// don't move the slide | ||
this.setState({ offsetPercentage: 0 }); | ||
} | ||
} | ||
sufficientSwipe() { | ||
const { offsetPercentage } = this.state; | ||
const { swipeThreshold } = this.props; | ||
return Math.abs(offsetPercentage) > swipeThreshold; | ||
} | ||
handleOnSwiped({ event, dir, velocity }) { | ||
const { disableSwipe, stopPropagation, flickThreshold } = this.props; | ||
const { scrollingUpDown, scrollingLeftRight } = this.state; | ||
if (disableSwipe) return; | ||
const { isRTL } = this.props; | ||
if (stopPropagation) event.stopPropagation(); | ||
if (scrollingUpDown) { | ||
// user stopped scrollingUpDown | ||
this.setState({ scrollingUpDown: false }); | ||
} | ||
if (scrollingLeftRight) { | ||
// user stopped scrollingLeftRight | ||
this.setState({ scrollingLeftRight: false }); | ||
} | ||
if (!scrollingUpDown) { // don't swipe if user is scrolling | ||
// if it is RTL the direction is reversed | ||
const swipeDirection = (dir === LEFT ? 1 : -1) * (isRTL ? -1 : 1); | ||
const isFlick = velocity > flickThreshold; | ||
this.handleOnSwipedTo(swipeDirection, isFlick); | ||
} | ||
} | ||
handleOnSwipedTo(swipeDirection, isFlick) { | ||
const { currentIndex, isTransitioning } = this.state; | ||
let slideTo = currentIndex; | ||
if ((this.sufficientSwipe() || isFlick) && !isTransitioning) { | ||
// slideto the next/prev slide | ||
slideTo += swipeDirection; | ||
} | ||
// If we can't swipe left or right, stay in the current index (noop) | ||
if ((swipeDirection === -1 && !this.canSlideLeft()) | ||
|| (swipeDirection === 1 && !this.canSlideRight())) { | ||
slideTo = currentIndex; | ||
} | ||
this.unthrottledSlideToIndex(slideTo); | ||
} | ||
handleMouseDown() { | ||
// keep track of mouse vs keyboard usage for a11y | ||
this.imageGallery.current.classList.add('image-gallery-using-mouse'); | ||
} | ||
handleKeyDown(event) { | ||
const { disableKeyDown, useBrowserFullscreen } = this.props; | ||
const { isFullscreen } = this.state; | ||
// keep track of mouse vs keyboard usage for a11y | ||
this.imageGallery.current.classList.remove('image-gallery-using-mouse'); | ||
if (disableKeyDown) return; | ||
const LEFT_ARROW = 37; | ||
const RIGHT_ARROW = 39; | ||
const ESC_KEY = 27; | ||
const key = parseInt(event.keyCode || event.which || 0, 10); | ||
switch (key) { | ||
case LEFT_ARROW: | ||
if (this.canSlideLeft() && !this.intervalId) { | ||
this.slideLeft(); | ||
} | ||
break; | ||
case RIGHT_ARROW: | ||
if (this.canSlideRight() && !this.intervalId) { | ||
this.slideRight(); | ||
} | ||
break; | ||
case ESC_KEY: | ||
if (isFullscreen && !useBrowserFullscreen) { | ||
this.exitFullScreen(); | ||
} | ||
break; | ||
default: | ||
break; | ||
} | ||
} | ||
handleImageError(event) { | ||
const { onErrorImageURL } = this.props; | ||
if (onErrorImageURL && event.target.src.indexOf(onErrorImageURL) === -1) { | ||
/* eslint-disable no-param-reassign */ | ||
event.target.src = onErrorImageURL; | ||
/* eslint-enable no-param-reassign */ | ||
} | ||
} | ||
removeResizeObserver() { | ||
if (this.resizeObserver | ||
&& this.imageGallerySlideWrapper && this.imageGallerySlideWrapper.current) { | ||
this.resizeObserver.unobserve(this.imageGallerySlideWrapper.current); | ||
} | ||
} | ||
handleResize() { | ||
const { currentIndex } = this.state; | ||
if (this.imageGallery && this.imageGallery.current) { | ||
this.setState({ galleryWidth: this.imageGallery.current.offsetWidth }); | ||
} | ||
if (this.imageGallerySlideWrapper && this.imageGallerySlideWrapper.current) { | ||
this.setState({ | ||
gallerySlideWrapperHeight: this.imageGallerySlideWrapper.current.offsetHeight, | ||
}); | ||
} | ||
if (this.thumbnailsWrapper && this.thumbnailsWrapper.current) { | ||
if (this.isThumbnailVertical()) { | ||
this.setState({ thumbnailsWrapperHeight: this.thumbnailsWrapper.current.offsetHeight }); | ||
} else { | ||
this.setState({ thumbnailsWrapperWidth: this.thumbnailsWrapper.current.offsetWidth }); | ||
} | ||
} | ||
// Adjust thumbnail container when thumbnail width or height is adjusted | ||
this.setThumbsTranslate(-this.getThumbsTranslate(currentIndex)); | ||
} | ||
initResizeObserver(element) { | ||
this.resizeObserver = new ResizeObserver(debounce((entries) => { | ||
if (!entries) return; | ||
entries.forEach(() => { | ||
this.handleResize(); | ||
}); | ||
}, 300)); | ||
this.resizeObserver.observe(element.current); | ||
} | ||
toggleFullScreen() { | ||
const { isFullscreen } = this.state; | ||
if (isFullscreen) { | ||
this.exitFullScreen(); | ||
} else { | ||
this.fullScreen(); | ||
} | ||
} | ||
togglePlay() { | ||
if (this.intervalId) { | ||
this.pause(); | ||
} else { | ||
this.play(); | ||
} | ||
} | ||
handleScreenChange() { | ||
/* | ||
handles screen change events that the browser triggers e.g. esc key | ||
*/ | ||
const { onScreenChange } = this.props; | ||
const fullScreenElement = document.fullscreenElement | ||
|| document.msFullscreenElement | ||
|| document.mozFullScreenElement | ||
|| document.webkitFullscreenElement; | ||
if (onScreenChange) onScreenChange(fullScreenElement); | ||
this.setState({ isFullscreen: !!fullScreenElement }); | ||
} | ||
slideToIndex(index, event) { | ||
const { currentIndex, isTransitioning } = this.state; | ||
const { items, slideDuration } = this.props; | ||
if (!isTransitioning) { | ||
if (event) { | ||
if (this.intervalId) { | ||
// user triggered event while ImageGallery is playing, reset interval | ||
this.pause(false); | ||
this.play(false); | ||
} | ||
} | ||
const slideCount = items.length - 1; | ||
let nextIndex = index; | ||
if (index < 0) { | ||
nextIndex = slideCount; | ||
} else if (index > slideCount) { | ||
nextIndex = 0; | ||
} | ||
this.setState({ | ||
previousIndex: currentIndex, | ||
currentIndex: nextIndex, | ||
isTransitioning: nextIndex !== currentIndex, | ||
offsetPercentage: 0, | ||
slideStyle: { transition: `all ${slideDuration}ms ease-out` }, | ||
}, this.onSliding); | ||
} | ||
} | ||
slideLeft() { | ||
const { isRTL } = this.props; | ||
if (isRTL) { | ||
this.slideNext(); | ||
} else { | ||
this.slidePrevious(); | ||
} | ||
} | ||
slideRight() { | ||
const { isRTL } = this.props; | ||
if (isRTL) { | ||
this.slidePrevious(); | ||
} else { | ||
this.slideNext(); | ||
} | ||
} | ||
slidePrevious(event) { | ||
const { currentIndex } = this.state; | ||
this.slideToIndex(currentIndex - 1, event); | ||
} | ||
slideNext(event) { | ||
const { currentIndex } = this.state; | ||
this.slideToIndex(currentIndex + 1, event); | ||
} | ||
handleThumbnailMouseOver(event, index) { | ||
const { slideOnThumbnailOver } = this.props; | ||
if (slideOnThumbnailOver) this.onThumbnailMouseOver(event, index); | ||
} | ||
handleThumbnailKeyUp(event, index) { | ||
// a11y support ^_^ | ||
if (isEnterOrSpaceKey(event)) this.onThumbnailClick(event, index); | ||
} | ||
handleSlideKeyUp(event) { | ||
// a11y support ^_^ | ||
if (isEnterOrSpaceKey(event)) { | ||
const { onClick } = this.props; | ||
onClick(event); | ||
} | ||
} | ||
isThumbnailVertical() { | ||
const { thumbnailPosition } = this.props; | ||
return thumbnailPosition === 'left' || thumbnailPosition === 'right'; | ||
} | ||
addScreenChangeEvent() { | ||
screenChangeEvents.forEach((eventName) => { | ||
document.addEventListener(eventName, this.handleScreenChange); | ||
}); | ||
} | ||
removeScreenChangeEvent() { | ||
screenChangeEvents.forEach((eventName) => { | ||
document.removeEventListener(eventName, this.handleScreenChange); | ||
}); | ||
} | ||
fullScreen() { | ||
const { useBrowserFullscreen } = this.props; | ||
const gallery = this.imageGallery.current; | ||
if (useBrowserFullscreen) { | ||
if (gallery.requestFullscreen) { | ||
gallery.requestFullscreen(); | ||
} else if (gallery.msRequestFullscreen) { | ||
gallery.msRequestFullscreen(); | ||
} else if (gallery.mozRequestFullScreen) { | ||
gallery.mozRequestFullScreen(); | ||
} else if (gallery.webkitRequestFullscreen) { | ||
gallery.webkitRequestFullscreen(); | ||
} else { | ||
// fallback to fullscreen modal for unsupported browsers | ||
this.setModalFullscreen(true); | ||
} | ||
} else { | ||
this.setModalFullscreen(true); | ||
} | ||
this.setState({ isFullscreen: true }); | ||
} | ||
exitFullScreen() { | ||
const { isFullscreen } = this.state; | ||
const { useBrowserFullscreen } = this.props; | ||
if (isFullscreen) { | ||
if (useBrowserFullscreen) { | ||
if (document.exitFullscreen) { | ||
document.exitFullscreen(); | ||
} else if (document.webkitExitFullscreen) { | ||
document.webkitExitFullscreen(); | ||
} else if (document.mozCancelFullScreen) { | ||
document.mozCancelFullScreen(); | ||
} else if (document.msExitFullscreen) { | ||
document.msExitFullscreen(); | ||
} else { | ||
// fallback to fullscreen modal for unsupported browsers | ||
this.setModalFullscreen(false); | ||
} | ||
} else { | ||
this.setModalFullscreen(false); | ||
} | ||
this.setState({ isFullscreen: false }); | ||
} | ||
} | ||
pauseOrPlay() { | ||
const { infinite } = this.props; | ||
const { currentIndex } = this.state; | ||
if (!infinite && !this.canSlideRight()) { | ||
this.pause(); | ||
} else { | ||
this.slideToIndex(currentIndex + 1); | ||
} | ||
} | ||
play(shouldCallOnPlay = true) { | ||
const { | ||
onPlay, | ||
slideInterval, | ||
slideDuration, | ||
} = this.props; | ||
const { currentIndex } = this.state; | ||
if (!this.intervalId) { | ||
this.setState({ isPlaying: true }); | ||
this.intervalId = window.setInterval( | ||
this.pauseOrPlay, | ||
Math.max(slideInterval, slideDuration), | ||
); | ||
if (onPlay && shouldCallOnPlay) { | ||
onPlay(currentIndex); | ||
} | ||
} | ||
} | ||
pause(shouldCallOnPause = true) { | ||
const { onPause } = this.props; | ||
const { currentIndex } = this.state; | ||
if (this.intervalId) { | ||
window.clearInterval(this.intervalId); | ||
this.intervalId = null; | ||
this.setState({ isPlaying: false }); | ||
if (onPause && shouldCallOnPause) { | ||
onPause(currentIndex); | ||
} | ||
} | ||
} | ||
isImageLoaded(item) { | ||
/* | ||
Keep track of images loaded so that onImageLoad prop is not | ||
called multiple times when re-render the images | ||
*/ | ||
const imageExists = this.loadedImages[item.original]; | ||
if (imageExists) { | ||
return true; | ||
} | ||
// add image as loaded | ||
this.loadedImages[item.original] = true; | ||
return false; | ||
} | ||
renderItem(item) { | ||
const { onImageError, onImageLoad } = this.props; | ||
const handleImageError = onImageError || this.handleImageError; | ||
return ( | ||
<div className='image-gallery-image'> | ||
<div> | ||
{ | ||
item.imageSet ? | ||
item.imageSet ? ( | ||
<picture | ||
onLoad={this.props.onImageLoad} | ||
onError={onImageError} | ||
onLoad={!this.isImageLoaded(item) ? onImageLoad : null} | ||
onError={handleImageError} | ||
> | ||
{ | ||
item.imageSet.map((source, index) => ( | ||
item.imageSet.map(source => ( | ||
<source | ||
key={index} | ||
key={source.media} | ||
media={source.media} | ||
@@ -924,2 +1234,3 @@ srcSet={source.srcSet} | ||
<img | ||
className="image-gallery-image" | ||
alt={item.originalAlt} | ||
@@ -929,4 +1240,5 @@ src={item.original} | ||
</picture> | ||
: | ||
) : ( | ||
<img | ||
className="image-gallery-image" | ||
src={item.original} | ||
@@ -937,65 +1249,43 @@ alt={item.originalAlt} | ||
title={item.originalTitle} | ||
onLoad={this.props.onImageLoad} | ||
onError={onImageError} | ||
onLoad={!this.isImageLoaded(item) ? onImageLoad : null} | ||
onError={handleImageError} | ||
/> | ||
) | ||
} | ||
{ | ||
item.description && | ||
<span className='image-gallery-description'> | ||
item.description && ( | ||
<span className="image-gallery-description"> | ||
{item.description} | ||
</span> | ||
) | ||
} | ||
</div> | ||
); | ||
}; | ||
} | ||
_renderThumbInner = (item) => { | ||
let onThumbnailError = this.props.onThumbnailError || this._handleImageError; | ||
renderThumbInner(item) { | ||
const { onThumbnailError } = this.props; | ||
const handleThumbnailError = onThumbnailError || this.handleImageError; | ||
return ( | ||
<div className='image-gallery-thumbnail-inner'> | ||
<div className="image-gallery-thumbnail-inner"> | ||
<img | ||
className="image-gallery-thumbnail-image" | ||
src={item.thumbnail} | ||
alt={item.thumbnailAlt} | ||
title={item.thumbnailTitle} | ||
onError={onThumbnailError} | ||
onError={handleThumbnailError} | ||
/> | ||
{item.thumbnailLabel && | ||
<div className='image-gallery-thumbnail-label'> | ||
{item.thumbnailLabel} | ||
</div> | ||
{ | ||
item.thumbnailLabel && ( | ||
<div className="image-gallery-thumbnail-label"> | ||
{item.thumbnailLabel} | ||
</div> | ||
) | ||
} | ||
</div> | ||
); | ||
}; | ||
} | ||
_onThumbnailClick = (event, index) => { | ||
this.slideToIndex(index, event); | ||
if (this.props.onThumbnailClick) { | ||
this.props.onThumbnailClick(event, index); | ||
} | ||
}; | ||
_onThumbnailMouseOver = (event, index) => { | ||
if (this._thumbnailMouseOverTimer) { | ||
window.clearTimeout(this._thumbnailMouseOverTimer); | ||
this._thumbnailMouseOverTimer = null; | ||
} | ||
this._thumbnailMouseOverTimer = window.setTimeout(() => { | ||
this.slideToIndex(index); | ||
this.pause(); | ||
}, 300); | ||
}; | ||
_onThumbnailMouseLeave = () => { | ||
if (this._thumbnailMouseOverTimer) { | ||
window.clearTimeout(this._thumbnailMouseOverTimer); | ||
this._thumbnailMouseOverTimer = null; | ||
if (this.props.autoPlay) { | ||
this.play(); | ||
} | ||
} | ||
}; | ||
render() { | ||
@@ -1010,159 +1300,68 @@ const { | ||
const { | ||
infinite, | ||
slideOnThumbnailOver, | ||
additionalClass, | ||
indexSeparator, // deprecate soon, and allow custom render | ||
isRTL, | ||
lazyLoad, | ||
items, | ||
thumbnailPosition, | ||
renderFullscreenButton, | ||
renderCustomControls, | ||
renderLeftNav, | ||
renderRightNav, | ||
showBullets, | ||
showFullscreenButton, | ||
showIndex, | ||
showThumbnails, | ||
showNav, | ||
showPlayButton, | ||
renderPlayPauseButton, | ||
} = this.props; | ||
const thumbnailStyle = this._getThumbnailStyle(); | ||
const thumbnailPosition = this.props.thumbnailPosition; | ||
const thumbnailStyle = this.getThumbnailStyle(); | ||
const { slides, thumbnails, bullets } = this.getSlideItems(items); | ||
const slideWrapperClass = clsx( | ||
'image-gallery-slide-wrapper', | ||
thumbnailPosition, | ||
{ 'image-gallery-rtl': isRTL }, | ||
); | ||
const slideLeft = this._slideLeft; | ||
const slideRight = this._slideRight; | ||
let slides = []; | ||
let thumbnails = []; | ||
let bullets = []; | ||
this.props.items.forEach((item, index) => { | ||
const alignment = this._getAlignmentClassName(index); | ||
const originalClass = item.originalClass ? ` ${item.originalClass}` : ''; | ||
const thumbnailClass = item.thumbnailClass ? ` ${item.thumbnailClass}` : ''; | ||
const renderItem = item.renderItem || | ||
this.props.renderItem || this._renderItem; | ||
const renderThumbInner = item.renderThumbInner || | ||
this.props.renderThumbInner || this._renderThumbInner; | ||
const showItem = !lazyLoad || alignment || this._lazyLoaded[index]; | ||
if (showItem && lazyLoad && !this._lazyLoaded[index]) { | ||
this._lazyLoaded[index] = true; | ||
} | ||
let slideStyle = this._getSlideStyle(index); | ||
const slide = ( | ||
<div | ||
key={index} | ||
className={'image-gallery-slide' + alignment + originalClass} | ||
style={Object.assign(slideStyle, this.state.style)} | ||
onClick={this.props.onClick} | ||
onTouchMove={this.props.onTouchMove} | ||
onTouchEnd={this.props.onTouchEnd} | ||
onTouchStart={this.props.onTouchStart} | ||
onMouseOver={this.props.onMouseOver} | ||
onMouseLeave={this.props.onMouseLeave} | ||
role={this.props.onClick && 'button'} | ||
> | ||
{showItem ? renderItem(item) : <div style={{ height: '100%' }}></div>} | ||
</div> | ||
); | ||
if (infinite) { | ||
// don't add some slides while transitioning to avoid background transitions | ||
if (this._shouldPushSlideOnInfiniteMode(index)) { | ||
slides.push(slide); | ||
} | ||
} else { | ||
slides.push(slide); | ||
} | ||
if (this.props.showThumbnails) { | ||
thumbnails.push( | ||
<a | ||
key={index} | ||
role='button' | ||
aria-pressed={currentIndex === index ? 'true' : 'false'} | ||
aria-label={`Go to Slide ${index + 1}`} | ||
className={ | ||
'image-gallery-thumbnail' + | ||
(currentIndex === index ? ' active' : '') + | ||
thumbnailClass | ||
} | ||
onMouseLeave={slideOnThumbnailOver ? this._onThumbnailMouseLeave : undefined} | ||
onMouseOver={event => slideOnThumbnailOver ? this._onThumbnailMouseOver(event, index) : undefined} | ||
onClick={event => this._onThumbnailClick(event, index)} | ||
> | ||
{renderThumbInner(item)} | ||
</a> | ||
); | ||
} | ||
if (this.props.showBullets) { | ||
const bulletOnClick = event => { | ||
if(item.bulletOnClick){ | ||
item.bulletOnClick({item, itemIndex: index, currentIndex}); | ||
} | ||
return this.slideToIndex.call(this, index, event); | ||
}; | ||
bullets.push( | ||
<button | ||
key={index} | ||
type='button' | ||
className={[ | ||
'image-gallery-bullet', | ||
currentIndex === index ? 'active' : '', | ||
item.bulletClass || '' | ||
].join(' ')} | ||
onClick={bulletOnClick} | ||
aria-pressed={currentIndex === index ? 'true' : 'false'} | ||
aria-label={`Go to Slide ${index + 1}`} | ||
> | ||
</button> | ||
); | ||
} | ||
}); | ||
const slideWrapper = ( | ||
<div | ||
ref={this._initGalleryResizing} | ||
className={`image-gallery-slide-wrapper ${thumbnailPosition} ${isRTL ? 'image-gallery-rtl' : ''}`} | ||
> | ||
{this.props.renderCustomControls && this.props.renderCustomControls()} | ||
<div ref={this.imageGallerySlideWrapper} className={slideWrapperClass}> | ||
{renderCustomControls && renderCustomControls()} | ||
{ | ||
this.props.showFullscreenButton && | ||
this.props.renderFullscreenButton(this._toggleFullScreen, isFullscreen) | ||
} | ||
{ | ||
this.props.showPlayButton && | ||
this.props.renderPlayPauseButton(this._togglePlay, isPlaying) | ||
} | ||
{ | ||
this._canNavigate() ? | ||
[ | ||
this.props.showNav && | ||
<span key='navigation'> | ||
{this.props.renderLeftNav(slideLeft, !this._canSlideLeft())} | ||
{this.props.renderRightNav(slideRight, !this._canSlideRight())} | ||
</span>, | ||
<Swipeable | ||
className='image-gallery-swipe' | ||
key='swipeable' | ||
delta={0} | ||
onSwiping={this._handleSwiping} | ||
onSwiped={this._handleOnSwiped} | ||
> | ||
<div className='image-gallery-slides'> | ||
{slides} | ||
</div> | ||
this.canSlide() ? ( | ||
<React.Fragment> | ||
{ | ||
showNav && ( | ||
<span key="navigation"> | ||
{renderLeftNav(this.slideLeft, !this.canSlideLeft())} | ||
{renderRightNav(this.slideRight, !this.canSlideRight())} | ||
</span> | ||
) | ||
} | ||
<Swipeable | ||
className="image-gallery-swipe" | ||
key="swipeable" | ||
delta={0} | ||
onSwiping={this.handleSwiping} | ||
onSwiped={this.handleOnSwiped} | ||
> | ||
<div className="image-gallery-slides"> | ||
{slides} | ||
</div> | ||
</Swipeable> | ||
] | ||
: | ||
<div className='image-gallery-slides'> | ||
</React.Fragment> | ||
) : ( | ||
<div className="image-gallery-slides"> | ||
{slides} | ||
</div> | ||
) | ||
} | ||
{showPlayButton && renderPlayPauseButton(this.togglePlay, isPlaying)} | ||
{ | ||
this.props.showBullets && | ||
<div className='image-gallery-bullets'> | ||
showBullets && ( | ||
<div className="image-gallery-bullets"> | ||
<div | ||
className='image-gallery-bullets-container' | ||
role='navigation' | ||
aria-label='Bullet Navigation' | ||
className="image-gallery-bullets-container" | ||
role="navigation" | ||
aria-label="Bullet Navigation" | ||
> | ||
@@ -1172,16 +1371,19 @@ {bullets} | ||
</div> | ||
) | ||
} | ||
{showFullscreenButton && renderFullscreenButton(this.toggleFullScreen, isFullscreen)} | ||
{ | ||
this.props.showIndex && | ||
<div className='image-gallery-index'> | ||
<span className='image-gallery-index-current'> | ||
{this.state.currentIndex + 1} | ||
showIndex && ( | ||
<div className="image-gallery-index"> | ||
<span className="image-gallery-index-current"> | ||
{currentIndex + 1} | ||
</span> | ||
<span className='image-gallery-index-separator'> | ||
{this.props.indexSeparator} | ||
<span className="image-gallery-index-separator"> | ||
{indexSeparator} | ||
</span> | ||
<span className='image-gallery-index-total'> | ||
{this.props.items.length} | ||
<span className="image-gallery-index-total"> | ||
{items.length} | ||
</span> | ||
</div> | ||
) | ||
} | ||
@@ -1191,38 +1393,32 @@ </div> | ||
const classNames = [ | ||
'image-gallery', | ||
this.props.additionalClass, | ||
modalFullscreen ? 'fullscreen-modal' : '', | ||
].filter(name => typeof name === 'string').join(' '); | ||
const igClass = clsx('image-gallery', additionalClass, { 'fullscreen-modal': modalFullscreen }); | ||
const igContentClass = clsx('image-gallery-content', thumbnailPosition, { fullscreen: isFullscreen }); | ||
const thumbnailWrapperClass = clsx( | ||
'image-gallery-thumbnails-wrapper', | ||
thumbnailPosition, | ||
{ 'thumbnails-wrapper-rtl': !this.isThumbnailVertical() && isRTL }, | ||
); | ||
return ( | ||
<div | ||
ref={i => this._imageGallery = i} | ||
className={classNames} | ||
aria-live='polite' | ||
ref={this.imageGallery} | ||
className={igClass} | ||
aria-live="polite" | ||
> | ||
<div | ||
className={`image-gallery-content${isFullscreen ? ' fullscreen' : ''}`} | ||
> | ||
<div className={igContentClass}> | ||
{(thumbnailPosition === 'bottom' || thumbnailPosition === 'right') && slideWrapper} | ||
{ | ||
(thumbnailPosition === 'bottom' || thumbnailPosition === 'right') && | ||
slideWrapper | ||
} | ||
{ | ||
this.props.showThumbnails && | ||
showThumbnails && ( | ||
<div | ||
className={`image-gallery-thumbnails-wrapper ${thumbnailPosition} ${!this._isThumbnailVertical() && isRTL ? 'thumbnails-wrapper-rtl' : ''}`} | ||
style={this._getThumbnailBarHeight()} | ||
className={thumbnailWrapperClass} | ||
style={this.getThumbnailBarHeight()} | ||
> | ||
<div | ||
className='image-gallery-thumbnails' | ||
ref={i => this._thumbnailsWrapper = i} | ||
className="image-gallery-thumbnails" | ||
ref={this.thumbnailsWrapper} | ||
> | ||
<div | ||
ref={t => this._thumbnails = t} | ||
className='image-gallery-thumbnails-container' | ||
ref={this.thumbnails} | ||
className="image-gallery-thumbnails-container" | ||
style={thumbnailStyle} | ||
aria-label='Thumbnail Navigation' | ||
aria-label="Thumbnail Navigation" | ||
> | ||
@@ -1233,8 +1429,5 @@ {thumbnails} | ||
</div> | ||
) | ||
} | ||
{ | ||
(thumbnailPosition === 'top' || thumbnailPosition === 'left') && | ||
slideWrapper | ||
} | ||
{(thumbnailPosition === 'top' || thumbnailPosition === 'left') && slideWrapper} | ||
</div> | ||
@@ -1245,3 +1438,2 @@ | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
3169
0
130625
8
10
257
1
+ Addedclsx@^1.0.4
+ Addedlodash.isequal@^4.5.0
+ Addedclsx@1.2.1(transitive)
+ Addedlodash.isequal@4.5.0(transitive)