@seafile/react-image-lightbox
Version:
A lightbox component for React.js
1,495 lines (1,424 loc) • 79.6 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var React = require('react');
var React__default = _interopDefault(React);
var PropTypes = _interopDefault(require('prop-types'));
var Modal = _interopDefault(require('react-modal'));
function _iterableToArrayLimit(r, l) {
var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
if (null != t) {
var e,
n,
i,
u,
a = [],
f = !0,
o = !1;
try {
if (i = (t = t.call(r)).next, 0 === l) {
if (Object(t) !== t) return;
f = !1;
} else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
} catch (r) {
o = !0, n = r;
} finally {
try {
if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
} finally {
if (o) throw n;
}
}
return a;
}
}
function ownKeys(e, r) {
var t = Object.keys(e);
if (Object.getOwnPropertySymbols) {
var o = Object.getOwnPropertySymbols(e);
r && (o = o.filter(function (r) {
return Object.getOwnPropertyDescriptor(e, r).enumerable;
})), t.push.apply(t, o);
}
return t;
}
function _objectSpread2(e) {
for (var r = 1; r < arguments.length; r++) {
var t = null != arguments[r] ? arguments[r] : {};
r % 2 ? ownKeys(Object(t), !0).forEach(function (r) {
_defineProperty(e, r, t[r]);
}) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {
Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
});
}
return e;
}
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
Object.defineProperty(Constructor, "prototype", {
writable: false
});
return Constructor;
}
function _defineProperty(obj, key, value) {
key = _toPropertyKey(key);
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function _extends() {
_extends = Object.assign ? Object.assign.bind() : function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function");
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
writable: true,
configurable: true
}
});
Object.defineProperty(subClass, "prototype", {
writable: false
});
if (superClass) _setPrototypeOf(subClass, superClass);
}
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}
function _setPrototypeOf(o, p) {
_setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
return _setPrototypeOf(o, p);
}
function _isNativeReflectConstruct() {
if (typeof Reflect === "undefined" || !Reflect.construct) return false;
if (Reflect.construct.sham) return false;
if (typeof Proxy === "function") return true;
try {
Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));
return true;
} catch (e) {
return false;
}
}
function _assertThisInitialized(self) {
if (self === void 0) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return self;
}
function _possibleConstructorReturn(self, call) {
if (call && (typeof call === "object" || typeof call === "function")) {
return call;
} else if (call !== void 0) {
throw new TypeError("Derived constructors may only return object or undefined");
}
return _assertThisInitialized(self);
}
function _createSuper(Derived) {
var hasNativeReflectConstruct = _isNativeReflectConstruct();
return function _createSuperInternal() {
var Super = _getPrototypeOf(Derived),
result;
if (hasNativeReflectConstruct) {
var NewTarget = _getPrototypeOf(this).constructor;
result = Reflect.construct(Super, arguments, NewTarget);
} else {
result = Super.apply(this, arguments);
}
return _possibleConstructorReturn(this, result);
};
}
function _slicedToArray(arr, i) {
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
}
function _toConsumableArray(arr) {
return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
}
function _arrayWithoutHoles(arr) {
if (Array.isArray(arr)) return _arrayLikeToArray(arr);
}
function _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}
function _iterableToArray(iter) {
if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(o);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _nonIterableSpread() {
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _toPrimitive(input, hint) {
if (typeof input !== "object" || input === null) return input;
var prim = input[Symbol.toPrimitive];
if (prim !== undefined) {
var res = prim.call(input, hint || "default");
if (typeof res !== "object") return res;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return (hint === "string" ? String : Number)(input);
}
function _toPropertyKey(arg) {
var key = _toPrimitive(arg, "string");
return typeof key === "symbol" ? key : String(key);
}
/**
* Placeholder for future translate functionality
*/
function translate(str) {
var replaceStrings = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
if (!str) {
return '';
}
var translated = str;
if (replaceStrings) {
Object.keys(replaceStrings).forEach(function (placeholder) {
translated = translated.replace(placeholder, replaceStrings[placeholder]);
});
}
return translated;
}
function getWindowWidth() {
return typeof global.window !== 'undefined' ? global.window.innerWidth : 0;
}
function getWindowHeight() {
return typeof global.window !== 'undefined' ? global.window.innerHeight : 0;
}
var isMobile = typeof window !== 'undefined' && (window.innerWidth < 768 || navigator.userAgent.toLowerCase().match(/(ipod|ipad|iphone|android|coolpad|mmp|smartphone|midp|wap|xoom|symbian|j2me|blackberry|wince)/i) != null);
// Get the highest window context that isn't cross-origin
// (When in an iframe)
function getHighestSafeWindowContext() {
var self = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : global.window.self;
var referrer = self.document.referrer;
// If we reached the top level, return self
if (self === global.window.top || !referrer) {
return self;
}
var getOrigin = function getOrigin(href) {
return href.match(/(.*\/\/.*?)(\/|$)/)[1];
};
// If parent is the same origin, we can move up one context
// Reference: https://stackoverflow.com/a/21965342/1601953
if (getOrigin(self.location.href) === getOrigin(referrer)) {
return getHighestSafeWindowContext(self.parent);
}
// If a different origin, we consider the current level
// as the top reachable one
return self;
}
// Min image zoom level
var MIN_ZOOM_LEVEL = 0;
// Max image zoom level
var MAX_ZOOM_LEVEL = 5;
// Size ratio between previous and next zoom levels
var ZOOM_RATIO = 1.2;
// How much to increase/decrease the zoom level when the zoom buttons are clicked
var ZOOM_BUTTON_INCREMENT_SIZE = 1;
// Used to judge the amount of vertical scroll needed to initiate a zoom action
var WHEEL_MOVE_Y_THRESHOLD = 1;
var KEYS = {
ESC: 27,
LEFT_ARROW: 37,
UP_ARROW: 38,
RIGHT_ARROW: 39,
DOWN_ARROW: 40
};
// Actions
var ACTION_NONE = 0;
var ACTION_MOVE = 1;
var ACTION_SWIPE = 2;
var ACTION_PINCH = 3;
// Events source
var SOURCE_ANY = 0;
var SOURCE_MOUSE = 1;
var SOURCE_TOUCH = 2;
var SOURCE_POINTER = 3;
// Minimal swipe distance
var MIN_SWIPE_DISTANCE = 200;
var ReactImageLightbox = /*#__PURE__*/function (_Component) {
_inherits(ReactImageLightbox, _Component);
var _super = _createSuper(ReactImageLightbox);
function ReactImageLightbox(props) {
var _this;
_classCallCheck(this, ReactImageLightbox);
_this = _super.call(this, props);
_defineProperty(_assertThisInitialized(_this), "handleWheel", function (event) {
event.preventDefault();
});
_this.state = {
//-----------------------------
// Animation
//-----------------------------
// Lightbox is closing
// When Lightbox is mounted, if animation is enabled it will open with the reverse of the closing animation
isClosing: !props.animationDisabled,
// Component parts should animate (e.g., when images are moving, or image is being zoomed)
shouldAnimate: false,
//-----------------------------
// Zoom settings
//-----------------------------
// Zoom level of image
zoomLevel: MIN_ZOOM_LEVEL,
//-----------------------------
// Image position settings
//-----------------------------
// Horizontal offset from center
offsetX: 0,
// Vertical offset from center
offsetY: 0,
// image load error for srcType
loadErrorStatus: {},
// rotate image degree
rotateDeg: 0
};
// Refs
_this.outerEl = /*#__PURE__*/React__default.createRef();
_this.zoomInBtn = /*#__PURE__*/React__default.createRef();
_this.zoomOutBtn = /*#__PURE__*/React__default.createRef();
_this.caption = /*#__PURE__*/React__default.createRef();
_this.closeIfClickInner = _this.closeIfClickInner.bind(_assertThisInitialized(_this));
_this.handleImageDoubleClick = _this.handleImageDoubleClick.bind(_assertThisInitialized(_this));
_this.handleImageClick = _this.handleImageClick.bind(_assertThisInitialized(_this));
_this.handleImageMouseWheel = _this.handleImageMouseWheel.bind(_assertThisInitialized(_this));
_this.handleKeyInput = _this.handleKeyInput.bind(_assertThisInitialized(_this));
_this.handleMouseUp = _this.handleMouseUp.bind(_assertThisInitialized(_this));
_this.handleMouseDown = _this.handleMouseDown.bind(_assertThisInitialized(_this));
_this.handleMouseMove = _this.handleMouseMove.bind(_assertThisInitialized(_this));
_this.handleOuterMousewheel = _this.handleOuterMousewheel.bind(_assertThisInitialized(_this));
_this.handleTouchStart = _this.handleTouchStart.bind(_assertThisInitialized(_this));
_this.handleTouchMove = _this.handleTouchMove.bind(_assertThisInitialized(_this));
_this.handleTouchEnd = _this.handleTouchEnd.bind(_assertThisInitialized(_this));
_this.handlePointerEvent = _this.handlePointerEvent.bind(_assertThisInitialized(_this));
_this.handleCaptionMousewheel = _this.handleCaptionMousewheel.bind(_assertThisInitialized(_this));
_this.handleWindowResize = _this.handleWindowResize.bind(_assertThisInitialized(_this));
_this.handleZoomInButtonClick = _this.handleZoomInButtonClick.bind(_assertThisInitialized(_this));
_this.handleZoomOutButtonClick = _this.handleZoomOutButtonClick.bind(_assertThisInitialized(_this));
_this.requestClose = _this.requestClose.bind(_assertThisInitialized(_this));
_this.requestMoveNext = _this.requestMoveNext.bind(_assertThisInitialized(_this));
_this.requestMovePrev = _this.requestMovePrev.bind(_assertThisInitialized(_this));
_this.requestMoveUp = _this.requestMoveUp.bind(_assertThisInitialized(_this));
_this.requestMoveDown = _this.requestMoveDown.bind(_assertThisInitialized(_this));
_this.rotateImage = _this.rotateImage.bind(_assertThisInitialized(_this));
_this.isMobile = isMobile;
return _this;
}
// eslint-disable-next-line camelcase
_createClass(ReactImageLightbox, [{
key: "UNSAFE_componentWillMount",
value: function UNSAFE_componentWillMount() {
// Timeouts - always clear it before umount
this.timeouts = [];
// Current action
this.currentAction = ACTION_NONE;
// Events source
this.eventsSource = SOURCE_ANY;
// Empty pointers list
this.pointerList = [];
// Prevent inner close
this.preventInnerClose = false;
this.preventInnerCloseTimeout = null;
// Used to disable animation when changing props.mainSrc|nextSrc|prevSrc
this.keyPressed = false;
// Used to store load state / dimensions of images
this.imageCache = {};
// Time the last keydown event was called (used in keyboard action rate limiting)
this.lastKeyDownTime = 0;
// Used for debouncing window resize event
this.resizeTimeout = null;
// Used to determine when actions are triggered by the scroll wheel
this.wheelActionTimeout = null;
this.resetScrollTimeout = null;
this.scrollX = 0;
this.scrollY = 0;
// Used in panning zoomed images
this.moveStartX = 0;
this.moveStartY = 0;
this.moveStartOffsetX = 0;
this.moveStartOffsetY = 0;
// Used to swipe
this.swipeStartX = 0;
this.swipeStartY = 0;
this.swipeEndX = 0;
this.swipeEndY = 0;
// Used to pinch
this.pinchTouchList = null;
this.pinchDistance = 0;
// Used to differentiate between images with identical src
this.keyCounter = 0;
// Used to detect a move when all src's remain unchanged (four or more of the same image in a row)
this.moveRequested = false;
if (!this.props.animationDisabled) {
// Make opening animation play
this.setState({
isClosing: false
});
}
}
}, {
key: "componentDidMount",
value: function componentDidMount() {
var _this2 = this;
// Prevents cross-origin errors when using a cross-origin iframe
this.windowContext = getHighestSafeWindowContext();
this.listeners = {
resize: this.handleWindowResize,
mouseup: this.handleMouseUp,
touchend: this.handleTouchEnd,
touchcancel: this.handleTouchEnd,
pointerdown: this.handlePointerEvent,
pointermove: this.handlePointerEvent,
pointerup: this.handlePointerEvent,
pointercancel: this.handlePointerEvent
};
Object.keys(this.listeners).forEach(function (type) {
_this2.windowContext.addEventListener(type, _this2.listeners[type]);
});
document.addEventListener('wheel', this.handleWheel, {
passive: false
});
this.loadAllImages();
}
// eslint-disable-next-line camelcase
}, {
key: "UNSAFE_componentWillReceiveProps",
value: function UNSAFE_componentWillReceiveProps(nextProps) {
var _this3 = this;
// Iterate through the source types for prevProps and nextProps to
// determine if any of the sources changed
var sourcesChanged = false;
var prevSrcDict = {};
var nextSrcDict = {};
this.getSrcTypes().forEach(function (srcType) {
if (_this3.props[srcType.name] !== nextProps[srcType.name]) {
sourcesChanged = true;
prevSrcDict[_this3.props[srcType.name]] = true;
nextSrcDict[nextProps[srcType.name]] = true;
}
});
if (sourcesChanged || this.moveRequested) {
// Reset the loaded state for images not rendered next
Object.keys(prevSrcDict).forEach(function (prevSrc) {
if (!(prevSrc in nextSrcDict) && prevSrc in _this3.imageCache) {
_this3.imageCache[prevSrc].loaded = false;
}
});
this.moveRequested = false;
// Load any new images
this.loadAllImages(nextProps);
}
}
}, {
key: "shouldComponentUpdate",
value: function shouldComponentUpdate() {
// Wait for move...
return !this.moveRequested;
}
}, {
key: "componentWillUnmount",
value: function componentWillUnmount() {
var _this4 = this;
this.didUnmount = true;
Object.keys(this.listeners).forEach(function (type) {
_this4.windowContext.removeEventListener(type, _this4.listeners[type]);
});
document.removeEventListener('wheel', this.handleWheel, {
passive: false
});
this.timeouts.forEach(function (tid) {
return clearTimeout(tid);
});
}
}, {
key: "setTimeout",
value: function (_setTimeout) {
function setTimeout(_x, _x2) {
return _setTimeout.apply(this, arguments);
}
setTimeout.toString = function () {
return _setTimeout.toString();
};
return setTimeout;
}(function (func, time) {
var _this5 = this;
var id = setTimeout(function () {
_this5.timeouts = _this5.timeouts.filter(function (tid) {
return tid !== id;
});
func();
}, time);
this.timeouts.push(id);
return id;
})
}, {
key: "setPreventInnerClose",
value: function setPreventInnerClose() {
var _this6 = this;
if (this.preventInnerCloseTimeout) {
this.clearTimeout(this.preventInnerCloseTimeout);
}
this.preventInnerClose = true;
this.preventInnerCloseTimeout = this.setTimeout(function () {
_this6.preventInnerClose = false;
_this6.preventInnerCloseTimeout = null;
}, 100);
}
// Get info for the best suited image to display with the given srcType
}, {
key: "getBestImageForType",
value: function getBestImageForType(srcType) {
var imageSrc = this.props[srcType];
var fitSizes = {};
if (this.isImageLoaded(imageSrc)) {
// Use full-size image if available
fitSizes = this.getFitSizes(this.imageCache[imageSrc].width, this.imageCache[imageSrc].height);
} else if (this.isImageLoaded(this.props["".concat(srcType, "Thumbnail")])) {
// Fall back to using thumbnail if the image has not been loaded
imageSrc = this.props["".concat(srcType, "Thumbnail")];
fitSizes = this.getFitSizes(this.imageCache[imageSrc].width, this.imageCache[imageSrc].height, true);
} else {
return null;
}
return {
src: imageSrc,
height: this.imageCache[imageSrc].height,
width: this.imageCache[imageSrc].width,
targetHeight: fitSizes.height,
targetWidth: fitSizes.width
};
}
// Get sizing for when an image is larger than the window
}, {
key: "getFitSizes",
value: function getFitSizes(width, height, stretch) {
var boxSize = this.getLightboxRect();
// Padding (px) between the edge of the window and the lightbox
var imagePadding = this.isMobile ? 0 : 70;
var maxHeight = boxSize.height - imagePadding * 2;
var maxWidth = boxSize.width - imagePadding * 2;
if (!stretch) {
maxHeight = Math.min(maxHeight, height);
maxWidth = Math.min(maxWidth, width);
}
var maxRatio = maxWidth / maxHeight;
var srcRatio = width / height;
if (maxRatio > srcRatio) {
// height is the constraining dimension of the photo
return {
width: width * maxHeight / height,
height: maxHeight
};
}
return {
width: maxWidth,
height: height * maxWidth / width
};
}
}, {
key: "getMaxOffsets",
value: function getMaxOffsets() {
var zoomLevel = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.state.zoomLevel;
var currentImageInfo = this.getBestImageForType('mainSrc');
if (currentImageInfo === null) {
return {
maxX: 0,
minX: 0,
maxY: 0,
minY: 0
};
}
var boxSize = this.getLightboxRect();
var zoomMultiplier = this.getZoomMultiplier(zoomLevel);
var maxX = 0;
if (zoomMultiplier * currentImageInfo.width - boxSize.width < 0) {
// if there is still blank space in the X dimension, don't limit except to the opposite edge
maxX = (boxSize.width - zoomMultiplier * currentImageInfo.width) / 2;
} else {
maxX = (zoomMultiplier * currentImageInfo.width - boxSize.width) / 2;
}
var maxY = 0;
if (zoomMultiplier * currentImageInfo.height - boxSize.height < 0) {
// if there is still blank space in the Y dimension, don't limit except to the opposite edge
maxY = (boxSize.height - zoomMultiplier * currentImageInfo.height) / 2;
} else {
maxY = (zoomMultiplier * currentImageInfo.height - boxSize.height) / 2;
}
return {
maxX: maxX,
maxY: maxY,
minX: -1 * maxX,
minY: -1 * maxY
};
}
// Get image src types
}, {
key: "getSrcTypes",
value: function getSrcTypes() {
return [{
name: 'mainSrc',
keyEnding: "i".concat(this.keyCounter)
}, {
name: 'mainSrcThumbnail',
keyEnding: "t".concat(this.keyCounter)
}, {
name: 'nextSrc',
keyEnding: "i".concat(this.keyCounter + 1)
}, {
name: 'nextSrcThumbnail',
keyEnding: "t".concat(this.keyCounter + 1)
}, {
name: 'prevSrc',
keyEnding: "i".concat(this.keyCounter - 1)
}, {
name: 'prevSrcThumbnail',
keyEnding: "t".concat(this.keyCounter - 1)
}];
}
/**
* Get sizing when the image is scaled
*/
}, {
key: "getZoomMultiplier",
value: function getZoomMultiplier() {
var zoomLevel = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.state.zoomLevel;
return Math.pow(ZOOM_RATIO, zoomLevel);
}
/**
* Get the size of the lightbox in pixels
*/
}, {
key: "getLightboxRect",
value: function getLightboxRect() {
if (this.outerEl.current) {
return this.outerEl.current.getBoundingClientRect();
}
return {
width: getWindowWidth(),
height: getWindowHeight(),
top: 0,
right: 0,
bottom: 0,
left: 0
};
}
}, {
key: "clearTimeout",
value: function (_clearTimeout) {
function clearTimeout(_x3) {
return _clearTimeout.apply(this, arguments);
}
clearTimeout.toString = function () {
return _clearTimeout.toString();
};
return clearTimeout;
}(function (id) {
this.timeouts = this.timeouts.filter(function (tid) {
return tid !== id;
});
clearTimeout(id);
}
// Change zoom level
)
}, {
key: "changeZoom",
value: function changeZoom(zoomLevel, clientX, clientY) {
// Ignore if zoom disabled
if (!this.props.enableZoom) {
return;
}
// Constrain zoom level to the set bounds
var nextZoomLevel = Math.max(MIN_ZOOM_LEVEL, Math.min(MAX_ZOOM_LEVEL, zoomLevel));
// Ignore requests that don't change the zoom level
if (nextZoomLevel === this.state.zoomLevel) {
return;
}
if (nextZoomLevel === MIN_ZOOM_LEVEL) {
// Snap back to center if zoomed all the way out
this.setState({
zoomLevel: nextZoomLevel,
offsetX: 0,
offsetY: 0
});
return;
}
var imageBaseSize = this.getBestImageForType('mainSrc');
if (imageBaseSize === null) {
return;
}
var currentZoomMultiplier = this.getZoomMultiplier();
var nextZoomMultiplier = this.getZoomMultiplier(nextZoomLevel);
// Default to the center of the image to zoom when no mouse position specified
var boxRect = this.getLightboxRect();
var pointerX = typeof clientX !== 'undefined' ? clientX - boxRect.left : boxRect.width / 2;
var pointerY = typeof clientY !== 'undefined' ? clientY - boxRect.top : boxRect.height / 2;
var currentImageOffsetX = (boxRect.width - imageBaseSize.width * currentZoomMultiplier) / 2;
var currentImageOffsetY = (boxRect.height - imageBaseSize.height * currentZoomMultiplier) / 2;
var currentImageRealOffsetX = currentImageOffsetX - this.state.offsetX;
var currentImageRealOffsetY = currentImageOffsetY - this.state.offsetY;
var currentPointerXRelativeToImage = (pointerX - currentImageRealOffsetX) / currentZoomMultiplier;
var currentPointerYRelativeToImage = (pointerY - currentImageRealOffsetY) / currentZoomMultiplier;
var nextImageRealOffsetX = pointerX - currentPointerXRelativeToImage * nextZoomMultiplier;
var nextImageRealOffsetY = pointerY - currentPointerYRelativeToImage * nextZoomMultiplier;
var nextImageOffsetX = (boxRect.width - imageBaseSize.width * nextZoomMultiplier) / 2;
var nextImageOffsetY = (boxRect.height - imageBaseSize.height * nextZoomMultiplier) / 2;
var nextOffsetX = nextImageOffsetX - nextImageRealOffsetX;
var nextOffsetY = nextImageOffsetY - nextImageRealOffsetY;
// When zooming out, limit the offset so things don't get left askew
if (this.currentAction !== ACTION_PINCH) {
var maxOffsets = this.getMaxOffsets();
if (this.state.zoomLevel > nextZoomLevel) {
nextOffsetX = Math.max(maxOffsets.minX, Math.min(maxOffsets.maxX, nextOffsetX));
nextOffsetY = Math.max(maxOffsets.minY, Math.min(maxOffsets.maxY, nextOffsetY));
}
}
this.setState({
zoomLevel: nextZoomLevel,
offsetX: nextOffsetX,
offsetY: nextOffsetY
});
}
}, {
key: "closeIfClickInner",
value: function closeIfClickInner(event) {
if (!this.preventInnerClose && event.target.className.search(/\bril-inner\b/) > -1) {
this.requestClose(event);
}
}
/**
* Handle user keyboard actions
*/
}, {
key: "handleKeyInput",
value: function handleKeyInput(event) {
event.stopPropagation();
// Ignore key input during animations
if (this.isAnimating()) {
return;
}
// Allow slightly faster navigation through the images when user presses keys repeatedly
if (event.type === 'keyup') {
this.lastKeyDownTime -= this.props.keyRepeatKeyupBonus;
return;
}
var keyCode = event.which || event.keyCode;
// Ignore key presses that happen too close to each other (when rapid fire key pressing or holding down the key)
// But allow it if it's a lightbox closing action
var currentTime = new Date();
if (currentTime.getTime() - this.lastKeyDownTime < this.props.keyRepeatLimit && keyCode !== KEYS.ESC) {
return;
}
this.lastKeyDownTime = currentTime.getTime();
switch (keyCode) {
// ESC key closes the lightbox
case KEYS.ESC:
event.preventDefault();
this.requestClose(event);
break;
// Left arrow key moves to previous image
case KEYS.LEFT_ARROW:
if (!this.props.prevSrc) {
return;
}
event.preventDefault();
this.keyPressed = true;
this.requestMovePrev(event);
break;
// Right arrow key moves to next image
case KEYS.RIGHT_ARROW:
if (!this.props.nextSrc) {
return;
}
event.preventDefault();
this.keyPressed = true;
this.requestMoveNext(event);
break;
// Up arrow key moves to upper row
case KEYS.UP_ARROW:
{
if (this.props.onClickMoveUp) {
event.preventDefault();
this.keyPressed = true;
this.requestMoveUp(event);
}
break;
}
// Down arrow key moves to down row
case KEYS.DOWN_ARROW:
{
if (this.props.onClickMoveUp) {
event.preventDefault();
this.keyPressed = true;
this.requestMoveDown(event);
}
break;
}
}
}
/**
* Handle a mouse wheel event over the lightbox container
*/
}, {
key: "handleOuterMousewheel",
value: function handleOuterMousewheel(event) {
var _this7 = this;
// Prevent scrolling of the background
event.stopPropagation();
this.clearTimeout(this.resetScrollTimeout);
this.resetScrollTimeout = this.setTimeout(function () {
_this7.scrollX = 0;
_this7.scrollY = 0;
}, 300);
}
}, {
key: "handleImageMouseWheel",
value: function handleImageMouseWheel(event) {
// when gesture move up/down/left/right, event.deltaY is integer, move image
if (parseInt(event.deltaY) === parseFloat(event.deltaY)) {
if (Math.abs(event.deltaY) > Math.abs(event.deltaX)) {
var newOffsetY = this.state.offsetY + event.deltaY;
newOffsetY = newOffsetY < 0 ? 0 : newOffsetY;
this.setState({
offsetY: newOffsetY
});
} else {
var newOffsetX = this.state.offsetX + event.deltaX;
newOffsetX = newOffsetX < 0 ? 0 : newOffsetX;
this.setState({
offsetX: newOffsetX
});
}
return;
}
// when gesture zoom in or zoom out, event.deltaY is decimal, zoom image
var yThreshold = WHEEL_MOVE_Y_THRESHOLD;
if (Math.abs(event.deltaY) >= Math.abs(event.deltaX)) {
event.stopPropagation();
// If the vertical scroll amount was large enough, perform a zoom
if (Math.abs(event.deltaY) < yThreshold) {
return;
}
this.scrollX = 0;
this.scrollY += event.deltaY;
this.changeZoom(this.state.zoomLevel - event.deltaY, event.clientX, event.clientY);
}
}
/**
* Handle a double click on the current image
*/
}, {
key: "handleImageDoubleClick",
value: function handleImageDoubleClick(event) {
if (this.state.zoomLevel > MIN_ZOOM_LEVEL) {
// A double click when zoomed in zooms all the way out
this.changeZoom(MIN_ZOOM_LEVEL, event.clientX, event.clientY);
} else {
// A double click when zoomed all the way out zooms in
this.changeZoom(this.state.zoomLevel + ZOOM_BUTTON_INCREMENT_SIZE, event.clientX, event.clientY);
}
}
}, {
key: "handleImageClick",
value: function handleImageClick(event) {
event.stopPropagation();
event.nativeEvent.stopImmediatePropagation();
}
}, {
key: "shouldHandleEvent",
value: function shouldHandleEvent(source) {
if (this.eventsSource === source) {
return true;
}
if (this.eventsSource === SOURCE_ANY) {
this.eventsSource = source;
return true;
}
switch (source) {
case SOURCE_MOUSE:
return false;
case SOURCE_TOUCH:
this.eventsSource = SOURCE_TOUCH;
this.filterPointersBySource();
return true;
case SOURCE_POINTER:
if (this.eventsSource === SOURCE_MOUSE) {
this.eventsSource = SOURCE_POINTER;
this.filterPointersBySource();
return true;
}
return false;
default:
return false;
}
}
}, {
key: "addPointer",
value: function addPointer(pointer) {
this.pointerList.push(pointer);
}
}, {
key: "removePointer",
value: function removePointer(pointer) {
this.pointerList = this.pointerList.filter(function (_ref) {
var id = _ref.id;
return id !== pointer.id;
});
}
}, {
key: "filterPointersBySource",
value: function filterPointersBySource() {
var _this8 = this;
this.pointerList = this.pointerList.filter(function (_ref2) {
var source = _ref2.source;
return source === _this8.eventsSource;
});
}
}, {
key: "handleMouseDown",
value: function handleMouseDown(event) {
if (this.shouldHandleEvent(SOURCE_MOUSE) && ReactImageLightbox.isTargetMatchImage(event.target)) {
this.addPointer(ReactImageLightbox.parseMouseEvent(event));
this.multiPointerStart(event);
}
}
}, {
key: "handleMouseMove",
value: function handleMouseMove(event) {
if (this.shouldHandleEvent(SOURCE_MOUSE)) {
this.multiPointerMove(event, [ReactImageLightbox.parseMouseEvent(event)]);
}
}
}, {
key: "handleMouseUp",
value: function handleMouseUp(event) {
if (this.shouldHandleEvent(SOURCE_MOUSE)) {
this.removePointer(ReactImageLightbox.parseMouseEvent(event));
this.multiPointerEnd(event);
}
}
}, {
key: "handlePointerEvent",
value: function handlePointerEvent(event) {
if (this.shouldHandleEvent(SOURCE_POINTER)) {
switch (event.type) {
case 'pointerdown':
if (ReactImageLightbox.isTargetMatchImage(event.target)) {
this.addPointer(ReactImageLightbox.parsePointerEvent(event));
this.multiPointerStart(event);
}
break;
case 'pointermove':
this.multiPointerMove(event, [ReactImageLightbox.parsePointerEvent(event)]);
break;
case 'pointerup':
case 'pointercancel':
this.removePointer(ReactImageLightbox.parsePointerEvent(event));
this.multiPointerEnd(event);
break;
}
}
}
}, {
key: "handleTouchStart",
value: function handleTouchStart(event) {
var _this9 = this;
if (this.shouldHandleEvent(SOURCE_TOUCH) && ReactImageLightbox.isTargetMatchImage(event.target)) {
[].forEach.call(event.changedTouches, function (eventTouch) {
return _this9.addPointer(ReactImageLightbox.parseTouchPointer(eventTouch));
});
this.multiPointerStart(event);
}
}
}, {
key: "handleTouchMove",
value: function handleTouchMove(event) {
if (this.shouldHandleEvent(SOURCE_TOUCH)) {
this.multiPointerMove(event, [].map.call(event.changedTouches, function (eventTouch) {
return ReactImageLightbox.parseTouchPointer(eventTouch);
}));
}
}
}, {
key: "handleTouchEnd",
value: function handleTouchEnd(event) {
var _this10 = this;
if (this.shouldHandleEvent(SOURCE_TOUCH)) {
[].map.call(event.changedTouches, function (touch) {
return _this10.removePointer(ReactImageLightbox.parseTouchPointer(touch));
});
this.multiPointerEnd(event);
}
}
}, {
key: "decideMoveOrSwipe",
value: function decideMoveOrSwipe(pointer) {
if (this.state.zoomLevel <= MIN_ZOOM_LEVEL) {
this.handleSwipeStart(pointer);
} else {
this.handleMoveStart(pointer);
}
}
}, {
key: "multiPointerStart",
value: function multiPointerStart(event) {
this.handleEnd(null);
switch (this.pointerList.length) {
case 1:
{
event.preventDefault();
this.decideMoveOrSwipe(this.pointerList[0]);
break;
}
case 2:
{
event.preventDefault();
this.handlePinchStart(this.pointerList);
break;
}
}
}
}, {
key: "multiPointerMove",
value: function multiPointerMove(event, pointerList) {
switch (this.currentAction) {
case ACTION_MOVE:
{
event.preventDefault();
this.handleMove(pointerList[0]);
break;
}
case ACTION_SWIPE:
{
event.preventDefault();
this.handleSwipe(pointerList[0]);
break;
}
case ACTION_PINCH:
{
event.preventDefault();
this.handlePinch(pointerList);
break;
}
}
}
}, {
key: "multiPointerEnd",
value: function multiPointerEnd(event) {
if (this.currentAction !== ACTION_NONE) {
this.setPreventInnerClose();
this.handleEnd(event);
}
switch (this.pointerList.length) {
case 0:
{
this.eventsSource = SOURCE_ANY;
break;
}
case 1:
{
event.preventDefault();
this.decideMoveOrSwipe(this.pointerList[0]);
break;
}
case 2:
{
event.preventDefault();
this.handlePinchStart(this.pointerList);
break;
}
}
}
}, {
key: "handleEnd",
value: function handleEnd(event) {
switch (this.currentAction) {
case ACTION_MOVE:
this.handleMoveEnd(event);
break;
case ACTION_SWIPE:
this.handleSwipeEnd(event);
break;
case ACTION_PINCH:
this.handlePinchEnd(event);
break;
}
}
// Handle move start over the lightbox container
// This happens:
// - On a mouseDown event
// - On a touchstart event
}, {
key: "handleMoveStart",
value: function handleMoveStart(_ref3) {
var clientX = _ref3.x,
clientY = _ref3.y;
if (!this.props.enableZoom) {
return;
}
this.currentAction = ACTION_MOVE;
this.moveStartX = clientX;
this.moveStartY = clientY;
this.moveStartOffsetX = this.state.offsetX;
this.moveStartOffsetY = this.state.offsetY;
}
// Handle dragging over the lightbox container
// This happens:
// - After a mouseDown and before a mouseUp event
// - After a touchstart and before a touchend event
}, {
key: "handleMove",
value: function handleMove(_ref4) {
var clientX = _ref4.x,
clientY = _ref4.y;
var newOffsetX = this.moveStartX - clientX + this.moveStartOffsetX;
var newOffsetY = this.moveStartY - clientY + this.moveStartOffsetY;
if (this.state.offsetX !== newOffsetX || this.state.offsetY !== newOffsetY) {
this.setState({
offsetX: newOffsetX,
offsetY: newOffsetY
});
}
}
}, {
key: "handleMoveEnd",
value: function handleMoveEnd() {
var _this11 = this;
this.currentAction = ACTION_NONE;
this.moveStartX = 0;
this.moveStartY = 0;
this.moveStartOffsetX = 0;
this.moveStartOffsetY = 0;
// Snap image back into frame if outside max offset range
var maxOffsets = this.getMaxOffsets();
var nextOffsetX = Math.max(maxOffsets.minX, Math.min(maxOffsets.maxX, this.state.offsetX));
var nextOffsetY = Math.max(maxOffsets.minY, Math.min(maxOffsets.maxY, this.state.offsetY));
if (nextOffsetX !== this.state.offsetX || nextOffsetY !== this.state.offsetY) {
this.setState({
offsetX: nextOffsetX,
offsetY: nextOffsetY,
shouldAnimate: true
});
this.setTimeout(function () {
_this11.setState({
shouldAnimate: false
});
}, this.props.animationDuration);
}
}
}, {
key: "handleSwipeStart",
value: function handleSwipeStart(_ref5) {
var clientX = _ref5.x,
clientY = _ref5.y;
this.currentAction = ACTION_SWIPE;
this.swipeStartX = clientX;
this.swipeStartY = clientY;
this.swipeEndX = clientX;
this.swipeEndY = clientY;
}
}, {
key: "handleSwipe",
value: function handleSwipe(_ref6) {
var clientX = _ref6.x,
clientY = _ref6.y;
this.swipeEndX = clientX;
this.swipeEndY = clientY;
}
}, {
key: "handleSwipeEnd",
value: function handleSwipeEnd(event) {
var xDiff = this.swipeEndX - this.swipeStartX;
var xDiffAbs = Math.abs(xDiff);
var yDiffAbs = Math.abs(this.swipeEndY - this.swipeStartY);
this.currentAction = ACTION_NONE;
this.swipeStartX = 0;
this.swipeStartY = 0;
this.swipeEndX = 0;
this.swipeEndY = 0;
if (!event || this.isAnimating() || xDiffAbs < yDiffAbs * 1.5) {
return;
}
if (xDiffAbs < MIN_SWIPE_DISTANCE) {
var boxRect = this.getLightboxRect();
if (xDiffAbs < boxRect.width / 4) {
return;
}
}
if (xDiff > 0 && this.props.prevSrc) {
event.preventDefault();
this.requestMovePrev();
} else if (xDiff < 0 && this.props.nextSrc) {
event.preventDefault();
this.requestMoveNext();
}
}
}, {
key: "calculatePinchDistance",
value: function calculatePinchDistance() {
var _ref7 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.pinchTouchList,
_ref8 = _slicedToArray(_ref7, 2),
a = _ref8[0],
b = _ref8[1];
return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2));
}
}, {
key: "calculatePinchCenter",
value: function calculatePinchCenter() {
var _ref9 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.pinchTouchList,
_ref10 = _slicedToArray(_ref9, 2),
a = _ref10[0],
b = _ref10[1];
return {
x: a.x - (a.x - b.x) / 2,
y: a.y - (a.y - b.y) / 2
};
}
}, {
key: "handlePinchStart",
value: function handlePinchStart(pointerList) {
if (!this.props.enableZoom) {
return;
}
this.currentAction = ACTION_PINCH;
this.pinchTouchList = pointerList.map(function (_ref11) {
var id = _ref11.id,
x = _ref11.x,
y = _ref11.y;
return {
id: id,
x: x,
y: y
};
});
this.pinchDistance = this.calculatePinchDistance();
}
}, {
key: "handlePinch",
value: function handlePinch(pointerList) {
this.pinchTouchList = this.pinchTouchList.map(function (oldPointer) {
for (var i = 0; i < pointerList.length; i += 1) {
if (pointerList[i].id === oldPointer.id) {
return pointerList[i];
}
}
return oldPointer;
});
var newDistance = this.calculatePinchDistance();
var zoomLevel = this.state.zoomLevel + newDistance - this.pinchDistance;
this.pinchDistance = newDistance;
var _this$calculatePinchC = this.calculatePinchCenter(this.pinchTouchList),
clientX = _this$calculatePinchC.x,
clientY = _this$calculatePinchC.y;
this.changeZoom(zoomLevel, clientX, clientY);
}
}, {
key: "handlePinchEnd",
value: function handlePinchEnd() {
this.currentAction = ACTION_NONE;
this.pinchTouchList = null;
this.pinchDistance = 0;
}
// Handle the window resize event
}, {
key: "handleWindowResize",
value: function handleWindowResize() {
this.clearTimeout(this.resizeTimeout);
this.resizeTimeout = this.setTimeout(this.forceUpdate.bind(this), 100);
}
}, {
key: "handleZoomInButtonClick",
value: function handleZoomInButtonClick() {
var nextZoomLevel = this.state.zoomLevel + ZOOM_BUTTON_INCREMENT_SIZE;
this.changeZoom(nextZoomLevel);
if (nextZoomLevel === MAX_ZOOM_LEVEL) {
this.zoomOutBtn.current.focus();
}
}
}, {
key: "handleZoomOutButtonClick",
value: function handleZoomOutButtonClick() {
var nextZoomLevel = this.state.zoomLevel - ZOOM_BUTTON_INCREMENT_SIZE;
this.changeZoom(nextZoomLevel);
if (nextZoomLevel === MIN_ZOOM_LEVEL) {
this.zoomInBtn.current.focus();
}
}
}, {
key: "handleCaptionMousewheel",
value: function handleCaptionMousewheel(event) {
event.stopPropagation();
if (!this.caption.current) {
return;
}
var _this$caption$current = this.caption.current.getBoundingClientRect(),
height = _this$caption$current.height;
var _this$caption$current2 = this.caption.current,
scrollHeight = _this$caption$current2.scrollHeight,
scrollTop = _this$caption$current2.scrollTop;
if (event.deltaY > 0 && height + scrollTop >= scrollHeight || event.deltaY < 0 && scrollTop <= 0) {
event.preventDefault();
}
}
// Detach key and mouse input events
}, {
key: "isAnimating",
value: function isAnimating() {
return this.state.shouldAnimate || this.state.isClosing;
}
// Check if image is loaded
}, {
key: "isImageLoaded",
value: function isImageLoaded(imageSrc) {
return imageSrc && imageSrc in this.imageCache && this.imageCache[imageSrc].loaded;
}
// Load image from src and call callback with image width and height on load
}, {
key: "loadImage",
value: function loadImage(srcType, imageSrc, done) {
var _this12 = this;
// Return the image info if it is already cached
if (this.isImageLoaded(imageSrc)) {
this.setTimeout(function () {
done();
}, 1);
return;
}
var inMemoryImage = new global.Image();
if (this.props.imageCrossOrigin) {
inMemoryImage.crossOrigin = this.props.imageCrossOrigin;
}
inMemoryImage.onerror = function (errorEvent) {
_this12.props.onImageLoadError(imageSrc, srcType, errorEvent);
// failed to load so set the state loadErrorStatus
_this12.setState(function (prevState) {
return {
loadErrorStatus: _objectSpread2(_objectSpread2({}, prevState.loadErrorStatus), {}, _defineProperty({}, srcType, true))
};
});
done(errorEvent);
};
inMemoryImage.onload = function () {
_this12.props.onImageLoad(imageSrc, srcType, inMemoryImage);
_this12.imageCache[imageSrc] = {
loaded: true,
width: inMemoryImage.width,
height: inMemoryImage.height
};
done();
};
inMemoryImage.src = imageSrc;
}
// Load all images and their thumbnails
}, {
key: "loadAllImages",
value: function loadAllImages() {
var _this13 = this;
var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.props;
var generateLoadDoneCallback = function generateLoadDoneCallback(srcType, imageSrc) {
return function (err) {
// Give up showing image on error
if (err) {
return;
}
// Don't rerender if the src is not the same as when the load started
// or if the component has unmounted
if (_this13.props[srcType] !== imageSrc || _this13.didUnmount) {
return;
}
// Force rerender with the new image
_this13.forceUpdate();
};
};
// Load the images
this.getSrcTypes().forEach(function (srcType) {
var type = srcType.name;
// there is no error when we try to load it initially
if (props[type] && _this13.state.loadErrorStatus[type]) {
_this13.setState(function (prevState) {
return {
loadErrorStatus: _objectSpread2(_objectSpread2({}, prevState.loadErrorStatus), {}, _defineProperty({}, type, false))
};
});
}
// Load unloaded images
if (props[type] && !_this13.isImageLoaded(props[type])) {
_this13.loadImage(type, props[type], generateLoadDoneCallback(type, props[type]));
}
});
}
// Request that the lightbox be closed
}, {
key: "requestClose",
value: function requestClose(event) {
var _this14 = this;
// Call the parent close request
var closeLightbox = function closeLightbox() {
_this14.saveRotateImage();
_this14.props.onCloseRequest(event);
};
if (this.props.animationDisabled || event.type === 'keydown' && !this.props.animationOnKeyInput) {
// No animation
closeLightbox();
return;
}
// With animation
// Start closing animation
this.setState({
isClosing: true
});
// Perform the actual closing at the