UNPKG

@cloudscape_innovation/react-images-viewer

Version:
1,222 lines (1,184 loc) 41.7 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('prop-types'), require('react'), require('aphrodite'), require('react-scrolllock'), require('aphrodite/no-important'), require('react-transition-group'), require('react-dom')) : typeof define === 'function' && define.amd ? define(['prop-types', 'react', 'aphrodite', 'react-scrolllock', 'aphrodite/no-important', 'react-transition-group', 'react-dom'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.ImgsViewer = factory(global.PropTypes, global.React, global.aphrodite, global.ScrollLock, global.aphrodite, global.ReactTransitionGroup, global.ReactDOM)); })(this, (function (PropTypes, React, aphrodite, ScrollLock, noImportant, reactTransitionGroup, reactDom) { 'use strict'; function _assertThisInitialized(e) { if (void 0 === e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); return e; } function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); } function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || false, o.configurable = true, "value" in o && (o.writable = true), Object.defineProperty(e, _toPropertyKey(o.key), o); } } function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), Object.defineProperty(e, "prototype", { writable: false }), e; } function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } function _getPrototypeOf(t) { return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) { return t.__proto__ || Object.getPrototypeOf(t); }, _getPrototypeOf(t); } function _inherits(t, e) { if ("function" != typeof e && null !== e) throw new TypeError("Super expression must either be null or a function"); t.prototype = Object.create(e && e.prototype, { constructor: { value: t, writable: true, configurable: true } }), Object.defineProperty(t, "prototype", { writable: false }), e && _setPrototypeOf(t, e); } function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function () { return !!t; })(); } function _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var n = Object.getOwnPropertySymbols(e); for (r = 0; r < n.length; r++) o = n[r], -1 === t.indexOf(o) && {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; } function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; } function _possibleConstructorReturn(t, e) { if (e && ("object" == typeof e || "function" == typeof e)) return e; if (void 0 !== e) throw new TypeError("Derived constructors may only return object or undefined"); return _assertThisInitialized(t); } function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return (String )(t); } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } // =================== // Theme // =================== var theme = {}; // container theme.container = { background: 'rgba(0, 0, 0, .8)', gutter: { horizontal: 10, vertical: 10 }, zIndex: 2001 }; // header theme.header = { height: 40 }; theme.close = { fill: 'white' }; // footer theme.footer = { color: '#fff', count: { color: 'rgba(255, 255, 255, .75)', fontSize: '.85em' }, height: 40, gutter: { horizontal: 0, vertical: 5 } }; // thumbnails theme.thumbnail = { activeBorderColor: '#fff', size: 50, gutter: 2 }; // arrow theme.arrow = { background: 'none', fill: '#fff', height: 120 }; function deepMerge(source) { var target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var extended = Object.assign({}, target); Object.keys(source).forEach(function (key) { if (_typeof(source[key]) !== "object" || !source[key]) { extended[key] = source[key]; } else { if (!target[key]) { extended[key] = source[key]; } else { extended[key] = deepMerge(target[key], source[key]); } } }); return extended; } // export function deepMerge(source, target = {}) { // // initialize with source styles // const styles = { ...source } // // massage in target styles // Object.keys(target).forEach(key => { // if (source[key]) { // styles[key] = (rsCss, props) => { // return target[key](source[key](rsCss, props), props) // } // } else { // styles[key] = target[key] // } // }) // return styles // } var canUseDom = !!(typeof window !== "undefined" && window.document && window.document.createElement); /** * Bind multiple conponent methods: * @param {this} context * @param {Array} functions * * constructor() { * ... * bindFunctions.call(this, ['handleClick', 'handleOther']) * } */ function bindFunctions(functions) { var _this = this; functions.forEach(function (f) { return _this[f] = _this[f].bind(_this); }); } var arrowLeft = (function (fill) { return "<svg fill=\"".concat(fill, "\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\" width=\"100%\" height=\"100%\" viewBox=\"0 0 512 512\" xml:space=\"preserve\">\n\t\t<path d=\"M213.7,256L213.7,256L213.7,256L380.9,81.9c4.2-4.3,4.1-11.4-0.2-15.8l-29.9-30.6c-4.3-4.4-11.3-4.5-15.5-0.2L131.1,247.9 c-2.2,2.2-3.2,5.2-3,8.1c-0.1,3,0.9,5.9,3,8.1l204.2,212.7c4.2,4.3,11.2,4.2,15.5-0.2l29.9-30.6c4.3-4.4,4.4-11.5,0.2-15.8 L213.7,256z\"/>\n\t</svg>"); }); var arrowRight = (function (fill) { return "<svg fill=\"".concat(fill, "\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\" width=\"100%\" height=\"100%\" viewBox=\"0 0 512 512\" xml:space=\"preserve\">\n\t\t<path d=\"M298.3,256L298.3,256L298.3,256L131.1,81.9c-4.2-4.3-4.1-11.4,0.2-15.8l29.9-30.6c4.3-4.4,11.3-4.5,15.5-0.2l204.2,212.7 c2.2,2.2,3.2,5.2,3,8.1c0.1,3-0.9,5.9-3,8.1L176.7,476.8c-4.2,4.3-11.2,4.2-15.5-0.2L131.3,446c-4.3-4.4-4.4-11.5-0.2-15.8 L298.3,256z\"/>\n\t</svg>"); }); var close = (function (fill) { return "<svg fill=\"".concat(fill, "\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\" width=\"100%\" height=\"100%\" viewBox=\"0 0 512 512\" style=\"enable-background:new 0 0 512 512;\" xml:space=\"preserve\">\n\t\t<path d=\"M443.6,387.1L312.4,255.4l131.5-130c5.4-5.4,5.4-14.2,0-19.6l-37.4-37.6c-2.6-2.6-6.1-4-9.8-4c-3.7,0-7.2,1.5-9.8,4 L256,197.8L124.9,68.3c-2.6-2.6-6.1-4-9.8-4c-3.7,0-7.2,1.5-9.8,4L68,105.9c-5.4,5.4-5.4,14.2,0,19.6l131.5,130L68.4,387.1 c-2.6,2.6-4.1,6.1-4.1,9.8c0,3.7,1.4,7.2,4.1,9.8l37.4,37.6c2.7,2.7,6.2,4.1,9.8,4.1c3.5,0,7.1-1.3,9.8-4.1L256,313.1l130.7,131.1 c2.7,2.7,6.2,4.1,9.8,4.1c3.5,0,7.1-1.3,9.8-4.1l37.4-37.6c2.6-2.6,4.1-6.1,4.1-9.8C447.7,393.2,446.2,389.7,443.6,387.1z\"/>\n\t</svg>"); }); var _excluded$3 = ["fill", "type"]; var icons = { arrowLeft: arrowLeft, arrowRight: arrowRight, close: close }; var Icon = function Icon(_ref) { var fill = _ref.fill, type = _ref.type, props = _objectWithoutProperties(_ref, _excluded$3); var icon = icons[type]; return /*#__PURE__*/React.createElement("span", _extends({ dangerouslySetInnerHTML: { __html: icon(fill) } }, props)); }; Icon.propTypes = { fill: PropTypes.string, type: PropTypes.oneOf(Object.keys(icons)) }; Icon.defaultProps = { fill: "#fff" }; var _excluded$2 = ["direction", "icon", "onClick", "size", "theme"]; function Arrow(_ref) { var direction = _ref.direction, icon = _ref.icon, onClick = _ref.onClick, size = _ref.size, theme$1 = _ref.theme, props = _objectWithoutProperties(_ref, _excluded$2); var classes = noImportant.StyleSheet.create(deepMerge(defaultStyles$5, theme$1)); return /*#__PURE__*/React.createElement("button", _extends({ type: "button" // default: submit , className: noImportant.css(classes.arrow, classes["arrow__direction__" + direction], size && classes["arrow__size__" + size]), onClick: onClick, onTouchEnd: onClick }, props), /*#__PURE__*/React.createElement(Icon, { fill: !!theme$1.arrow && theme$1.arrow.fill || theme.arrow.fill, type: icon })); } Arrow.propTypes = { theme: PropTypes.object, direction: PropTypes.oneOf(["left", "right"]), icon: PropTypes.string, onClick: PropTypes.func.isRequired, size: PropTypes.oneOf(["medium", "small"]).isRequired }; Arrow.defaultProps = { size: "medium" }; var defaultStyles$5 = { arrow: { background: "none", border: "none", borderRadius: 4, cursor: "pointer", outline: "none", padding: 10, // increase hit area position: "absolute", top: "50%", // disable user select WebkitTouchCallout: "none", userSelect: "none" }, // sizes arrow__size__medium: { height: theme.arrow.height, marginTop: theme.arrow.height / -2, width: 40, "@media (min-width: 768px)": { width: 70 } }, arrow__size__small: { height: theme.thumbnail.size, marginTop: theme.thumbnail.size / -2, width: 30, "@media (min-width: 500px)": { width: 40 } }, // direciton arrow__direction__right: { right: theme.container.gutter.horizontal }, arrow__direction__left: { left: theme.container.gutter.horizontal } }; function Container(props) { var classes = noImportant.StyleSheet.create(deepMerge(defaultStyles$4, props.theme)); return /*#__PURE__*/React.createElement("div", _extends({ id: "viewerBackdrop", className: noImportant.css(classes.container) }, props)); } Container.propTypes = { theme: PropTypes.object }; var defaultStyles$4 = { container: { alignItems: 'center', backdropColor: theme.container.background, boxSizing: 'border-box', display: 'flex', height: '100%', justifyContent: 'center', left: 0, paddingTop: theme.container.gutter.vertical, paddingRight: theme.container.gutter.horizontal, paddingBottom: theme.container.gutter.vertical, paddingLeft: theme.container.gutter.horizontal, position: 'fixed', top: 0, width: '100%', zIndex: theme.container.zIndex } }; var _excluded$1 = ["caption", "countCurr", "countSeparator", "countTotal", "showCount", "theme"]; function Footer(_ref) { var caption = _ref.caption, countCurr = _ref.countCurr, countSeparator = _ref.countSeparator, countTotal = _ref.countTotal, showCount = _ref.showCount, theme = _ref.theme, props = _objectWithoutProperties(_ref, _excluded$1); if (!caption && !showCount) return null; var classes = noImportant.StyleSheet.create(deepMerge(defaultStyles$3, theme)); var imgCount = showCount ? /*#__PURE__*/React.createElement("div", { className: noImportant.css(classes.footerCount) }, countCurr, countSeparator, countTotal) : /*#__PURE__*/React.createElement("span", null); return /*#__PURE__*/React.createElement("div", _extends({ className: noImportant.css(classes.footer) }, props), caption ? /*#__PURE__*/React.createElement("figcaption", { className: noImportant.css(classes.footerCaption) }, caption) : /*#__PURE__*/React.createElement("span", null), imgCount); } Footer.propTypes = { theme: PropTypes.object, caption: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), countCurr: PropTypes.number, countSeparator: PropTypes.string, countTotal: PropTypes.number, showCount: PropTypes.bool }; var defaultStyles$3 = { footer: { boxSizing: 'border-box', color: theme.footer.color, cursor: 'auto', display: 'flex', justifyContent: 'space-between', left: 0, lineHeight: 1.3, paddingTop: theme.footer.gutter.vertical, paddingRight: theme.footer.gutter.horizontal, paddingBottom: theme.footer.gutter.vertical, paddingLeft: theme.footer.gutter.horizontal }, footerCount: { color: theme.footer.count.color, fontSize: theme.footer.count.fontSize, paddingLeft: '1em' // add a small gutter for the caption }, footerCaption: { flex: '1 1 0' } }; var _excluded = ["customControls", "onClose", "showCloseBtn", "closeBtnTitle", "theme"]; function Header(_ref) { var customControls = _ref.customControls, onClose = _ref.onClose, showCloseBtn = _ref.showCloseBtn, closeBtnTitle = _ref.closeBtnTitle, theme$1 = _ref.theme, props = _objectWithoutProperties(_ref, _excluded); var classes = noImportant.StyleSheet.create(deepMerge(defaultStyles$2, theme$1)); return /*#__PURE__*/React.createElement("div", _extends({ className: noImportant.css(classes.header) }, props), customControls ? customControls : /*#__PURE__*/React.createElement("span", null), !!showCloseBtn && /*#__PURE__*/React.createElement("button", { title: closeBtnTitle, className: noImportant.css(classes.close), onClick: onClose }, /*#__PURE__*/React.createElement(Icon, { fill: !!theme$1.close && theme$1.close.fill || theme.close.fill, type: "close" }))); } Header.propTypes = { theme: PropTypes.object, customControls: PropTypes.array, onClose: PropTypes.func.isRequired, showCloseBtn: PropTypes.bool, closeBtnTitle: PropTypes.string }; var defaultStyles$2 = { header: { display: 'flex', justifyContent: 'space-between', height: theme.header.height }, close: { background: 'none', border: 'none', cursor: 'pointer', outline: 'none', position: 'relative', top: 0, verticalAlign: 'bottom', zIndex: 1, // increase hit area height: 40, marginRight: -10, padding: 10, width: 40 } }; function Thumbnail(_ref) { var index = _ref.index, src = _ref.src, thumbnail = _ref.thumbnail, active = _ref.active, _onClick = _ref.onClick, theme = _ref.theme; var url = thumbnail || src; var classes = noImportant.StyleSheet.create(deepMerge(defaultStyles$1, theme)); return /*#__PURE__*/React.createElement("div", { className: noImportant.css(classes.thumbnail, active && classes.thumbnail__active), onClick: function onClick(e) { e.preventDefault(); e.stopPropagation(); _onClick(index); }, style: { backgroundImage: "url(\"".concat(url, "\")") } }); } Thumbnail.propTypes = { theme: PropTypes.object, active: PropTypes.bool, index: PropTypes.number, onClick: PropTypes.func.isRequired, src: PropTypes.string, thumbnail: PropTypes.string }; var defaultStyles$1 = { thumbnail: { backgroundPosition: 'center', backgroundSize: 'cover', borderRadius: 2, boxShadow: 'inset 0 0 0 1px hsla(0, 0%, 100%, .2)', cursor: 'pointer', display: 'inline-block', height: theme.thumbnail.size, margin: theme.thumbnail.gutter, overflow: 'hidden', width: theme.thumbnail.size }, thumbnail__active: { boxShadow: "inset 0 0 0 2px ".concat(theme.thumbnail.activeBorderColor) } }; var classes = noImportant.StyleSheet.create({ paginatedThumbnails: { bottom: theme.container.gutter.vertical, height: theme.thumbnail.size, padding: '0 50px', position: 'absolute', textAlign: 'center', whiteSpace: 'nowrap', left: '50%', transform: 'translateX(-50%)' } }); var arrowStyles = { height: theme.thumbnail.size + theme.thumbnail.gutter * 2, width: 40 }; var PaginatedThumbnails = /*#__PURE__*/function (_Component) { function PaginatedThumbnails(props) { var _this; _classCallCheck(this, PaginatedThumbnails); _this = _callSuper(this, PaginatedThumbnails, [props]); _this.state = { hasCustomPage: false }; _this.gotoPrev = _this.gotoPrev.bind(_this); _this.gotoNext = _this.gotoNext.bind(_this); return _this; } _inherits(PaginatedThumbnails, _Component); return _createClass(PaginatedThumbnails, [{ key: "UNSAFE_componentWillReceiveProps", value: function UNSAFE_componentWillReceiveProps(nextProps) { if (nextProps.currImg !== this.props.currImg) { this.setState({ hasCustomPage: false }); } } // ==================== // Methods // ==================== }, { key: "getFirst", value: function getFirst() { var _this$props = this.props, currImg = _this$props.currImg, offset = _this$props.offset; if (this.state.hasCustomPage) { return this.clampFirst(this.state.first); } return this.clampFirst(currImg - offset); } }, { key: "setFirst", value: function setFirst(event, newFirst) { var first = this.state.first; if (event) { event.preventDefault(); event.stopPropagation(); } if (first === newFirst) return; this.setState({ hasCustomPage: true, first: newFirst }); } }, { key: "gotoPrev", value: function gotoPrev(event) { this.setFirst(event, this.getFirst() - this.props.offset); } }, { key: "gotoNext", value: function gotoNext(event) { this.setFirst(event, this.getFirst() + this.props.offset); } }, { key: "clampFirst", value: function clampFirst(value) { var _this$props2 = this.props, imgs = _this$props2.imgs, offset = _this$props2.offset; var totalCount = 2 * offset + 1; // show $offset extra thumbnails on each side if (value < 0) { return 0; } else if (value + totalCount > imgs.length) { // Too far return imgs.length - totalCount; } else { return value; } } // ==================== // Renderers // ==================== }, { key: "renderArrowPrev", value: function renderArrowPrev(theme) { var leftTitle = this.props.leftTitle; if (this.getFirst() <= 0) return null; return /*#__PURE__*/React.createElement(Arrow, { theme: theme, direction: "left", size: "small", icon: "arrowLeft", onClick: this.gotoPrev, style: arrowStyles, title: leftTitle, type: "button" }); } }, { key: "renderArrowNext", value: function renderArrowNext(theme) { var _this$props3 = this.props, offset = _this$props3.offset, imgs = _this$props3.imgs, rightTitle = _this$props3.rightTitle; var totalCount = 2 * offset + 1; if (this.getFirst() + totalCount >= imgs.length) return null; return /*#__PURE__*/React.createElement(Arrow, { theme: theme, direction: "right", size: "small", icon: "arrowRight", onClick: this.gotoNext, style: arrowStyles, title: rightTitle, type: "button" }); } }, { key: "render", value: function render() { var _this$props4 = this.props, imgs = _this$props4.imgs, currImg = _this$props4.currImg, onClickThumbnail = _this$props4.onClickThumbnail, offset = _this$props4.offset, theme = _this$props4.theme; var totalCount = 2 * offset + 1; // show $offset extra thumbnails on each side var thumbnails = []; var baseOffset = 0; if (imgs.length <= totalCount) { thumbnails = imgs; } else { // Try to center current image in list baseOffset = this.getFirst(); thumbnails = imgs.slice(baseOffset, baseOffset + totalCount); } return /*#__PURE__*/React.createElement("div", { className: noImportant.css(classes.paginatedThumbnails) }, this.renderArrowPrev(theme), thumbnails.map(function (img, idx) { return /*#__PURE__*/React.createElement(Thumbnail, _extends({ theme: theme, key: baseOffset + idx }, img, { index: baseOffset + idx, onClick: onClickThumbnail, active: baseOffset + idx === currImg })); }), this.renderArrowNext(theme)); } }]); }(React.Component); PaginatedThumbnails.propTypes = { theme: PropTypes.object, leftTitle: PropTypes.string, rightTitle: PropTypes.string, currImg: PropTypes.number, imgs: PropTypes.array, offset: PropTypes.number, onClickThumbnail: PropTypes.func.isRequired }; var Portal = /*#__PURE__*/function (_Component) { function Portal() { var _this; _classCallCheck(this, Portal); _this = _callSuper(this, Portal); _this.portalElement = null; return _this; } _inherits(Portal, _Component); return _createClass(Portal, [{ key: "componentDidMount", value: function componentDidMount() { var div = document.createElement('div'); document.body.appendChild(div); this.portalElement = div; this.componentDidUpdate(); } }, { key: "componentDidUpdate", value: function componentDidUpdate() { // Animate fade on mount/unmount var duration = 200; var styles = "\n\t\t\t\t.fade-enter { opacity: 0.01; }\n\t\t\t\t.fade-enter.fade-enter-active { opacity: 1; transition: opacity ".concat(duration, "ms; }\n\t\t\t\t.fade-leave { opacity: 1; }\n\t\t\t\t.fade-leave.fade-leave-active { opacity: .01; transition: opacity ").concat(duration, "ms; }\n\t\t"); reactDom.render(/*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("style", null, styles), /*#__PURE__*/React.createElement(reactTransitionGroup.TransitionGroup, this.props, /*#__PURE__*/React.createElement(reactTransitionGroup.CSSTransition, { timeout: { enter: duration, exit: duration }, className: "fade" }, /*#__PURE__*/React.createElement("div", null, this.props.children)))), this.portalElement); } }, { key: "componentWillUnmount", value: function componentWillUnmount() { reactDom.unmountComponentAtNode(this.portalElement); document.body.removeChild(this.portalElement); } }, { key: "render", value: function render() { return null; } }]); }(React.Component); Portal.propTypes = { children: PropTypes.arrayOf(PropTypes.any) }; var Spinner = function Spinner(props) { var classes = noImportant.StyleSheet.create(styles(props)); return /*#__PURE__*/React.createElement("div", { className: noImportant.css(classes.bouncingLoader) }, /*#__PURE__*/React.createElement("div", { className: noImportant.css(classes.child) }), /*#__PURE__*/React.createElement("div", { className: noImportant.css(classes.child, classes.child2) }), /*#__PURE__*/React.createElement("div", { className: noImportant.css(classes.child, classes.child3) })); }; Spinner.propTypes = { color: PropTypes.string, size: PropTypes.number }; var bouncingKeyframes = function bouncingKeyframes(size) { return { '0%': { opacity: 1, transform: 'translateY(0)' }, '100%': { opacity: .1, transform: "translateY(-".concat(size, "px)") } }; }; var styles = function styles(_ref) { var color = _ref.color, size = _ref.size; return { bouncingLoader: { display: 'flex', justifyContent: 'center' }, child: { width: size, height: size, margin: "".concat(3 * size, "px ").concat(.2 * size, "px"), background: color, borderRadius: '50%', animationName: bouncingKeyframes(size), animationDuration: '.6s', animationDirection: 'alternate', animationIterationCount: 'infinite' }, child2: { animationDelay: '0.2s' }, child3: { animationDelay: '0.4s' } }; }; function normalizeSourceSet(data) { var sourceSet = data.srcSet || data.srcset; if (Array.isArray(sourceSet)) { return sourceSet.join(); } return sourceSet; } var ThemeContext = /*#__PURE__*/React.createContext({ theme: theme, toggleTheme: function toggleTheme(newTheme) {} }); var ImgsViewer = /*#__PURE__*/function (_Component) { function ImgsViewer(props) { var _this; _classCallCheck(this, ImgsViewer); _this = _callSuper(this, ImgsViewer, [props]); _this.theme = deepMerge(theme, _this.props.theme); _this.classes = aphrodite.StyleSheet.create(deepMerge(defaultStyles, _this.props.theme)); _this.toggleTheme = function (theme) { _this.setState(function () { return { theme: theme }; }); }; _this.state = { imgLoaded: false, theme: _this.theme, toggleTheme: _this.toggleTheme }; bindFunctions.call(_this, ["gotoNext", "gotoPrev", "closeBackdrop", "handleKeyboardInput", "handleImgLoaded"]); return _this; } _inherits(ImgsViewer, _Component); return _createClass(ImgsViewer, [{ key: "componentDidMount", value: function componentDidMount() { if (this.props.isOpen) { if (this.props.enableKeyboardInput) { window.addEventListener("keydown", this.handleKeyboardInput); } if (typeof this.props.currImg === "number") { this.preloadImg(this.props.currImg, this.handleImgLoaded); } } } }, { key: "componentDidUpdate", value: function componentDidUpdate(prevProps) { if (!canUseDom) return; // always to preload imgs with both directions // then when user changs direction, img also show quickly if (this.props.preloadNextImg) { var nextIdx = this.props.currImg + 1; var prevIdx = this.props.currImg - 1; this.preloadImg(prevIdx); this.preloadImg(nextIdx); } // preload currImg if (prevProps.currImg !== this.props.currImg || !prevProps.isOpen && this.props.isOpen) { var img = this.preloadImgData(this.props.imgs[this.props.currImg], this.handleImgLoaded); if (img) this.setState({ imgLoaded: img.complete }); } // add/remove event listeners if (!prevProps.isOpen && this.props.isOpen && this.props.enableKeyboardInput) { window.addEventListener("keydown", this.handleKeyboardInput); } if (!this.props.isOpen && this.props.enableKeyboardInput) { window.removeEventListener("keydown", this.handleKeyboardInput); } } }, { key: "componentWillUnmount", value: function componentWillUnmount() { if (this.props.enableKeyboardInput) { window.removeEventListener("keydown", this.handleKeyboardInput); } } // ==================== // Methods // ==================== }, { key: "preloadImg", value: function preloadImg(idx, onload) { return this.preloadImgData(this.props.imgs[idx], onload); } }, { key: "preloadImgData", value: function preloadImgData(data, onload) { if (!data) return; var img = new Image(); var sourceSet = normalizeSourceSet(data); // Todo: add error handling for missing imgs img.onerror = onload; img.onload = onload; img.src = data.src; if (sourceSet) img.srcset = sourceSet; return img; } }, { key: "gotoNext", value: function gotoNext(event) { var _this$props = this.props, currImg = _this$props.currImg, imgs = _this$props.imgs; var imgLoaded = this.state.imgLoaded; if (!imgLoaded || currImg === imgs.length - 1) return; if (event) { event.preventDefault(); event.stopPropagation(); } this.props.onClickNext(); } }, { key: "gotoPrev", value: function gotoPrev(event) { var currImg = this.props.currImg; var imgLoaded = this.state.imgLoaded; if (!imgLoaded || currImg === 0) return; if (event) { event.preventDefault(); event.stopPropagation(); } this.props.onClickPrev(); } }, { key: "closeBackdrop", value: function closeBackdrop(event) { if (event.target.id === "viewerBackdrop" || event.target.tagName === "FIGURE") { this.props.onClose(); } } }, { key: "handleKeyboardInput", value: function handleKeyboardInput(event) { var keyCode = event.keyCode; if (keyCode === 37 || keyCode === 33 || keyCode === 38) { // left, pageup, up this.gotoPrev(event); return true; } else if (keyCode === 39 || keyCode === 34 || keyCode === 40) { // right, pagedown, down this.gotoNext(event); return true; } else if (keyCode === 27 || keyCode === 32) { // esc, space this.props.onClose(); return true; } return false; } }, { key: "handleImgLoaded", value: function handleImgLoaded() { this.setState({ imgLoaded: true }); } // ==================== // Renderers // ==================== }, { key: "renderArrowPrev", value: function renderArrowPrev(theme) { if (this.props.currImg === 0) return null; return /*#__PURE__*/React.createElement(Arrow, { theme: theme, direction: "left", icon: "arrowLeft", onClick: this.gotoPrev, title: this.props.leftArrowTitle, type: "button" }); } }, { key: "renderArrowNext", value: function renderArrowNext(theme) { if (this.props.currImg === this.props.imgs.length - 1) return null; return /*#__PURE__*/React.createElement(Arrow, { theme: theme, direction: "right", icon: "arrowRight", onClick: this.gotoNext, title: this.props.rightArrowTitle, type: "button" }); } }, { key: "renderDialog", value: function renderDialog(newState) { var _this2 = this; var _this$props2 = this.props, backdropCloseable = _this$props2.backdropCloseable, isOpen = _this$props2.isOpen, showThumbnails = _this$props2.showThumbnails, width = _this$props2.width; var imgLoaded = this.state.imgLoaded; if (!isOpen) return /*#__PURE__*/React.createElement("span", { key: "closed" }); var offsetThumbnails = showThumbnails ? this.theme.thumbnail.size + this.theme.container.gutter.vertical : 0; return /*#__PURE__*/React.createElement(ThemeContext.Consumer, null, function (_ref) { var theme = _ref.theme; _ref.toggleTheme; theme = newState.theme; return /*#__PURE__*/React.createElement(Container, { theme: theme, key: "open", onClick: backdropCloseable && _this2.closeBackdrop, onTouchEnd: backdropCloseable && _this2.closeBackdrop }, /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", { className: aphrodite.css(_this2.classes.content), style: { marginBottom: offsetThumbnails, maxWidth: width } }, imgLoaded && _this2.renderHeader(theme), " ", _this2.renderImgs(theme), _this2.renderSpinner(), " ", imgLoaded && _this2.renderFooter(theme)), imgLoaded && _this2.renderThumbnails(theme), imgLoaded && _this2.renderArrowPrev(theme), imgLoaded && _this2.renderArrowNext(theme), _this2.props.preventScroll && /*#__PURE__*/React.createElement(ScrollLock, null))); }); } }, { key: "renderImgs", value: function renderImgs(theme) { var _this$props3 = this.props, currImg = _this$props3.currImg, imgs = _this$props3.imgs, onClickImg = _this$props3.onClickImg, showThumbnails = _this$props3.showThumbnails; var imgLoaded = this.state.imgLoaded; if (!imgs || !imgs.length) return null; var img = imgs[currImg]; var sourceSet = normalizeSourceSet(img); var sizes = sourceSet ? "100vw" : null; var thumbnailsSize = showThumbnails ? theme.thumbnail.size : 0; var heightOffset = "".concat(theme.header.height + theme.footer.height + thumbnailsSize + theme.container.gutter.vertical, "px"); return /*#__PURE__*/React.createElement("figure", { className: aphrodite.css(this.classes.figure) }, /*#__PURE__*/React.createElement("img", { className: aphrodite.css(this.classes.img, imgLoaded && this.classes.imgLoaded), onClick: onClickImg, sizes: sizes, alt: img.alt, src: img.src, srcSet: sourceSet, style: { cursor: onClickImg ? "pointer" : "auto", maxHeight: "calc(100vh - ".concat(heightOffset) } })); } }, { key: "renderThumbnails", value: function renderThumbnails(theme) { var _this$props4 = this.props, imgs = _this$props4.imgs, currImg = _this$props4.currImg, leftArrowTitle = _this$props4.leftArrowTitle, rightArrowTitle = _this$props4.rightArrowTitle, onClickThumbnail = _this$props4.onClickThumbnail, showThumbnails = _this$props4.showThumbnails, thumbnailOffset = _this$props4.thumbnailOffset; if (!showThumbnails) return null; return /*#__PURE__*/React.createElement(PaginatedThumbnails, { theme: theme, leftTitle: leftArrowTitle, rightTitle: rightArrowTitle, currImg: currImg, imgs: imgs, offset: thumbnailOffset, onClickThumbnail: onClickThumbnail }); } }, { key: "renderHeader", value: function renderHeader(theme) { var _this$props5 = this.props, closeBtnTitle = _this$props5.closeBtnTitle, customControls = _this$props5.customControls, onClose = _this$props5.onClose, showCloseBtn = _this$props5.showCloseBtn; return /*#__PURE__*/React.createElement(Header, { theme: theme, customControls: customControls, onClose: onClose, showCloseBtn: showCloseBtn, closeBtnTitle: closeBtnTitle }); } }, { key: "renderFooter", value: function renderFooter(theme) { var _this$props6 = this.props, currImg = _this$props6.currImg, imgs = _this$props6.imgs, imgCountSeparator = _this$props6.imgCountSeparator, showImgCount = _this$props6.showImgCount; if (!imgs || !imgs.length) return null; return /*#__PURE__*/React.createElement(Footer, { theme: theme, caption: imgs[currImg].caption, countCurr: currImg + 1, countSeparator: imgCountSeparator, countTotal: imgs.length, showCount: showImgCount }); } }, { key: "renderSpinner", value: function renderSpinner() { var _this$props7 = this.props, spinner = _this$props7.spinner, spinnerDisabled = _this$props7.spinnerDisabled, spinnerColor = _this$props7.spinnerColor, spinnerSize = _this$props7.spinnerSize; var imgLoaded = this.state.imgLoaded; var Spinner = spinner; if (spinnerDisabled) return null; return /*#__PURE__*/React.createElement("div", { className: aphrodite.css(this.classes.spinner, !imgLoaded && this.classes.spinnerActive) }, /*#__PURE__*/React.createElement(Spinner, { color: spinnerColor, size: spinnerSize })); } }, { key: "render", value: function render() { return /*#__PURE__*/React.createElement(ThemeContext.Provider, { value: this.state }, /*#__PURE__*/React.createElement(Portal, null, " ", this.renderDialog(this.state), " ")); } }]); }(React.Component); ImgsViewer.propTypes = { backdropCloseable: PropTypes.bool, closeBtnTitle: PropTypes.string, currImg: PropTypes.number, customControls: PropTypes.arrayOf(PropTypes.node), enableKeyboardInput: PropTypes.bool, imgCountSeparator: PropTypes.string, imgs: PropTypes.arrayOf(PropTypes.shape({ src: PropTypes.string.isRequired, srcSet: PropTypes.oneOfType([PropTypes.array, PropTypes.string]), caption: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), thumbnail: PropTypes.string })).isRequired, isOpen: PropTypes.bool, leftArrowTitle: PropTypes.string, onClickImg: PropTypes.func, onClickNext: PropTypes.func, onClickPrev: PropTypes.func, onClickThumbnail: PropTypes.func, onClose: PropTypes.func.isRequired, preloadNextImg: PropTypes.bool, preventScroll: PropTypes.bool, rightArrowTitle: PropTypes.string, showCloseBtn: PropTypes.bool, showImgCount: PropTypes.bool, showThumbnails: PropTypes.bool, spinnerDisabled: PropTypes.bool, spinner: PropTypes.func, spinnerColor: PropTypes.string, spinnerSize: PropTypes.number, theme: PropTypes.object, thumbnailOffset: PropTypes.number, width: PropTypes.number }; ImgsViewer.defaultProps = { currImg: 0, enableKeyboardInput: true, imgCountSeparator: " / ", onClickShowNextImg: true, preloadNextImg: true, preventScroll: true, showCloseBtn: true, showImgCount: true, spinnerDisabled: false, spinner: Spinner, spinnerColor: "#fff", spinnerSize: 50, theme: {}, thumbnailOffset: 2, width: 1024 }; var defaultStyles = { content: { position: "relative" }, figure: { margin: 0 // remove browser default }, img: { display: "block", // removes browser default gutter height: "auto", margin: "0 auto", // main center on very short screens or very narrow img maxWidth: "100%", // disable user select WebkitTouchCallout: "none", userSelect: "none", // opacity animation on image load opacity: 0, transition: "opacity .3s" }, imgLoaded: { opacity: 1 }, spinner: { position: "absolute", top: "50%", left: "50%", transform: "translate(-50%, -50%)", // opacity animation to make spinner appear with delay opacity: 0, transition: "opacity .3s", pointerEvents: "none" }, spinnerActive: { opacity: 1 } }; return ImgsViewer; }));