[
  {
    "path": ".babelrc.js",
    "content": "module.exports = {\n    plugins: [\n        ['@babel/plugin-transform-class-properties'],\n        ['@babel/plugin-transform-object-rest-spread'],\n        ['@babel/plugin-transform-runtime', { regenerator: false }],\n    ],\n    presets: ['@babel/env', '@babel/react', '@babel/preset-typescript'],\n};\n"
  },
  {
    "path": ".browserslistrc",
    "content": "> 0.5%, last 2 versions, Firefox ESR, not dead\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 4\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n"
  },
  {
    "path": ".eslintignore",
    "content": "# See https://help.github.com/ignore-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\nexample/node_modules/**\n\n# production\n/build\n/dist\n/coverage\n\n# misc\n.DS_Store\n.env\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nyarn.lock*\npackage.json*\nCHANGELOG.md*\nREADME.md*\n\nexample/.DS_Store\nexample/.env\nexample/npm-debug.log*\nexample/yarn-debug.log*\nexample/yarn-error.log*\nexample/yarn.lock*\n"
  },
  {
    "path": ".eslintrc.js",
    "content": "/* eslint-disable sort-keys */\n/**\n * Configure ESLint\n *\n * https://eslint.org/docs/user-guide/configuring\n */\nmodule.exports = {\n    env: {\n        browser: true,\n        es6: true,\n        jest: true,\n    },\n    extends: [\n        'plugin:react/recommended',\n        'plugin:import/warnings',\n        'plugin:@typescript-eslint/recommended',\n        'plugin:prettier/recommended',\n    ],\n    globals: {\n        document: true,\n        window: true,\n    },\n    parser: '@typescript-eslint/parser',\n    parserOptions: {\n        sourceType: 'module',\n    },\n    plugins: [\n        'prettier',\n        'jsx-a11y',\n        'react',\n        'react-hooks',\n        'import',\n        'sort-destructure-keys',\n        '@typescript-eslint',\n    ],\n    root: true,\n    rules: {\n        'prettier/prettier': ['error', { endOfLine: 'auto' }],\n        // Enforce React Hooks rules\n        // https://www.npmjs.com/package/eslint-plugin-react-hooks\n        'react-hooks/rules-of-hooks': 'error',\n        'react-hooks/exhaustive-deps': 'warn',\n\n        'sort-destructure-keys/sort-destructure-keys': [\n            'error',\n            { caseSensitive: false },\n        ],\n        'sort-keys': ['error', 'asc', { caseSensitive: false, natural: false }],\n        'sort-vars': [\n            'error',\n            {\n                ignoreCase: true,\n            },\n        ],\n        'react/jsx-sort-props': ['error', { ignoreCase: true }],\n        '@typescript-eslint/ban-ts-comment': 'off',\n        '@typescript-eslint/no-explicit-any': 'off',\n        '@typescript-eslint/explicit-module-boundary-types': 'off',\n        '@typescript-eslint/member-ordering': [\n            'error',\n            {\n                default: {\n                    order: 'alphabetically',\n                },\n                classes: {\n                    order: 'as-written',\n                },\n            },\n        ],\n    },\n    settings: {\n        'import/resolver': {\n            node: true,\n            'eslint-import-resolver-typescript': true,\n        },\n        react: {\n            version: 'detect',\n        },\n    },\n};\n"
  },
  {
    "path": ".gitignore",
    "content": "\n# See https://help.github.com/ignore-files/ for more about ignoring files.\n\n# dependencies\nnode_modules\n\n# builds\ncoverage\nbuild\ndist\n.rpt2_cache\n.next\n\n# misc\n.DS_Store\n.env\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n"
  },
  {
    "path": ".prettierignore",
    "content": "node_modules/**\n.next/**\ndist/**\ncoverage/**\nexample/node_modules/**\nexample/.next/**\nyarn.lock\nyarn-error.log\n.editorconfig\n.eslintignore\n.gitignore\n.prettierignore\n.browserslistrc\n"
  },
  {
    "path": ".prettierrc.js",
    "content": "/**\n * Configure Prettier\n *\n * https://prettier.io/docs/en/configuration.html#basic-configuration\n */\nmodule.exports = {\n    endOfLine: 'auto',\n    semi: true,\n    singleQuote: true,\n    tabWidth: 4,\n};\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: node_js\nnode_js:\n    - 'lts/*'\ncache:\n    yarn: true\n    directories:\n        - node_modules\ninstall: yarn install\njobs:\n    include:\n        - stage: lint\n          script: yarn lint\n        - stage: test\n          script: yarn test\n        - stage: build\n          script: yarn build\nbranches:\n    only: master\nnotifications:\n    email: false\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n    \"editor.formatOnSave\": true,\n    \"javascript.validate.enable\": false,\n    \"editor.tabSize\": 4,\n    \"editor.detectIndentation\": false,\n    \"eslint.enable\": true\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\n## Upcoming\n\n-   Add `swipe up` to close lightbox\n\n## [1.8.0] - 2023-10-06\n\n-   Add inline mode SSR support\n-   fix next.js + styled-components + SWC client/server classname mismatch error\n-   Upgrade several dependencies\n    -   @react-spring/web ^9.7\n    -   rollup 3\n    -   typescript 5\n\n### Potentially Breaking\n\n    This release removes the esm build as it doesn't work well with next.js + SWC compiler\n\n## [1.7.1] - 2022-09-12\n\n### Fixed\n\n-   Allow vertical scrolling with an InlineLightbox on mobile (even while finger is over the image)\n\n## [1.7.0] - 2022-07-28\n\n-   A few small bug fixes in situations where the `items` array may change size\n-   Upgrade several dependencies\n    -   \"@react-spring/web\": \"9.5.2\"\n    -   \"rollup\": \"^2.77.2\",\n\n### Added\n\n-   `Lightbox` can now be used as a slider component embedded in a page like slick-slider by using the `inline` prop\n\n## [1.6.0] - 2021-06-06\n\n-   Upgrade to `@react-spring/web@9.2.1` stable from `@tim-soft/react-spring-web@9.0.0-beta.36`\n\n## [1.5.0] - 2021-02-17\n\n-   Rewrite project with typescript 4\n-   Upgrade `react-use-gesture@7.0.15` to `react-use-gesture@9.0.4`\n    -   This upgrade should fix some miscellaneous bugs such as `unable to spread non iterable instance` and more consistent trackpad support\n\n### Added\n\n-   The `images` prop now accepts a list of objects whose properties can be _almost_ any valid React `<img />` prop including `srcset` and `loading` (lazy loading)\n\nIf you use typescript, the exact type can be imported/used like this\n\n```typescript\nimport Lightbox, { ImagesListType } from 'react-spring-lightbox';\n\nconst images: ImagesListType = [\n    {\n        alt: 'Windows 10 Dark Mode Setting',\n        'aria-details': 'Some details',\n        'aria-disabled': 'false',\n        loading: 'lazy',\n        src: 'https://timellenberger.com/static/blog-content/dark-mode/win10-dark-mode.jpg',\n        srcSet: '/wp-content/uploads/flamingo4x.jpg 4x, /wp-content/uploads/flamingo3x.jpg 3x, /wp-content/uploads/flamingo2x.jpg 2x, /wp-content/uploads/flamingo1x.jpg 1x',\n    },\n    {\n        alt: 'macOS Mojave Dark Mode Setting',\n        'aria-details': 'Some details',\n        'aria-disabled': 'false',\n        loading: 'lazy',\n        src: 'https://timellenberger.com/static/blog-content/dark-mode/macos-dark-mode.png',\n        srcSet: '/wp-content/uploads/flamingo4x.jpg 4x, /wp-content/uploads/flamingo3x.jpg 3x, /wp-content/uploads/flamingo2x.jpg 2x, /wp-content/uploads/flamingo1x.jpg 1x',\n    },\n];\n\nconst SimpleLightbox = () => <Lightbox images={images} {...otherProps} />;\n```\n\nThe exact type is:\n\n```typescript\nexport type ImagesListItem = Omit<\n    React.HTMLProps<HTMLImageElement>,\n    'draggable' | 'onClick' | 'onDragStart' | 'ref'\n> & { alt: string; loading?: 'auto' | 'eager' | 'lazy'; src: string };\n```\n\nWhich translates to any React `<img />` prop minus `draggable`, `onClick`, `onDragStart` and `ref` as they are used internally. `alt` and `src` are required and explicitly support `loading` as it is an experimental chrome feature not included in `React.HTMLProps<HTMLImageElement>`.\n\n## [1.4.11] - 2020-06-10\n\n### Fixed\n\n-   Use aliased version of react-spring dependencies, fixes \"Cannot read property 'ref' of null\" error\n\n## [1.4.10] - 2020-05-03\n\n### Added\n\n-   Optimize output bundles with Terser\n-   Apply `babel-plugin-styled-components` babel plugin to optimize styled-components styles\n\n## [1.4.9] - 2020-05-02\n\n### Fixed\n\n-   Fix partially off-screen image stage in ie11\n\n### Added\n\n-   Upgrade to `rollup@2.7.6`, `react-use-gesture@7.0.15` and `@babel/****@7.9.6`\n\n## [1.4.8] - 2020-04-08\n\n### Fixed\n\n-   Dropped `lodash.clamp` dependency\n-   Call onPrev/onNext callbacks on all paging events, even at the beginning or end of image array to allow for infinite paging\n\n## [1.4.7] - 2020-04-05\n\n### Added\n\n-   Lower distance and velocity gesture threshold for a paging between images\n-   Allow click to zoom while a paging animation completes\n-   Upgrade to `rollup@2.3.3` and `react-use-gesture@7.0.10`\n-   Add `sideEffects: false` to `package.json`\n\n## [1.4.6] - 2020-04-04\n\n### Fixed\n\n-   Handle edge case bugs with `singleClickToZoom` option\n-   Fix undefined errors in panning drag handler on initial drags\n\n## [1.4.5] - 2020-03-27\n\n### Added\n\n-   Add optional `singleClickToZoom` prop which allows single click/tap zooming on images\n\n## [1.4.4] - 2020-03-24\n\n### Fixed\n\n-   Add orientationchange event listener for ios devices\n\n## [1.4.3] - 2020-03-23\n\n### Fixed\n\n-   Drop lodash.merge\n-   Fix image heights not adjusting on window resize\n\n## [1.4.2] - 2020-03-15\n\n### Fixed\n\n-   Remove need for react-use-measure and @juggle/resize-observer\n\n## [1.4.1] - 2020-03-13\n\n### Fixed\n\n-   Fix image stage height on Safari\n-   Upgrade to react-use-gesture@7.0.5\n-   Upgrade to rollup@2.0.6\n\n## [1.4.0] - 2020-03-7\n\n### BREAKING CHANGE\n\n-   Replaced inline styles with styled-components. This library now has a peer dependency on `styled-components@5`\n\n### Fixed\n\n-   Gigantic initial image size in Firefox and MS Edge\n-   Click background to close functionality\n\n### Added\n\n-   Vendor prefixed styles\n-   A resize observer polyfill is now included to support MS Edge\n\n## [1.2.1] - 2020-03-5\n\n### Added\n\n-   Added `renderImageOverlay` prop, renders a React component within the image stage, useful for creating UI overlays on top of the current image\n\n## [1.2.0] - 2020-02-14\n\n-   Upgrade react-use-gesture v6 -> v7\n-   Upgrade all deps\n\n## [1.1.7] - 2019-09-23\n\n### Fixed\n\n-   Improved panning performance\n-   Tweaked mousewheel swiping threshold\n-   Upgrade to react-use-gesture v6\n\n## [1.1.4] - 2019-08-27\n\n### Added\n\n-   Implement mousewheel paging of images\n\n## [1.1.3] - 2019-08-19\n\n### Fixed\n\n-   Prevent vertical dragging from paging images\n-   Switch to @react-spring/web package\n\n## [1.1.2] - 2019-08-17\n\n### Fixed\n\n-   Properly dispose wheel event listener\n\n## [1.1.1] - 2019-08-14\n\n### Fixed\n\n-   Adjusted \"pan out of bounds\" threshold\n\n## [1.1.0] - 2019-08-14\n\n### Added\n\n-   Implement proper <kbd>Ctrl</kbd> + `Mousewheel` and `Trackpad Pinch` zooming\n\n## [1.0.1] - 2019-08-7\n\nAdd testing suite and travis-ci config\n\n## [1.0.0] - 2019-08-5\n\nUpgrade deps and release as stable\n\n## [0.0.3] - 2019-08-1\n\n### Changed\n\n-   Renamed onClickNext => onNext\n-   Renamed onClickPrev => onPrev\n\n## [0.0.2] - 2019-07-31\n\nInitial Release\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2019-present Tim Ellenberger\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# react-spring-lightbox\n\n[![npm](https://img.shields.io/npm/v/react-spring-lightbox.svg?color=brightgreen&style=popout-square)](https://www.npmjs.com/package/react-spring-lightbox)\n[![NPM](https://img.shields.io/npm/l/react-spring-lightbox.svg?color=brightgreen&style=popout-square)](https://github.com/tim-soft/react-spring-lightbox/blob/master/LICENSE)\n![npm bundle size](https://img.shields.io/bundlephobia/minzip/react-spring-lightbox.svg?style=popout-square)\n![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=popout-square)\n[![Travis (.org)](https://img.shields.io/travis/tim-soft/react-spring-lightbox?style=flat-square)](https://travis-ci.org/tim-soft/react-spring-lightbox)\n\nReact-spring-lightbox is a flexible image gallery lightbox with native-feeling touch gestures and buttery smooth animations.\n\n<p align=\"middle\">\n  <a href=\"https://71hts.csb.app/\">\n    <img src=\"https://thumbs.gfycat.com/CrispGeneralEquestrian-size_restricted.gif\" />\n  </a>\n  <br />\n  <a href=\"https://timellenberger.com/libraries/react-spring-lightbox\">Docs</a>\n  &nbsp;&nbsp;&nbsp;\n  <a href=\"https://codesandbox.io/s/react-spring-lightbox-mosaic-71hts?fontsize=14&module=%2Fsrc%2FImageGallery%2Findex.js\">Codesandbox</a>\n</p>\n\n## ✨ Features\n\n-   :point_up: &nbsp;&nbsp;&nbsp;`Mousewheel`, swipe or click+drag to page photos\n-   :keyboard: &nbsp;Keyboard controls <kbd>&leftarrow;</kbd> <kbd>&rightarrow;</kbd> <kbd>Esc</kbd>\n-   :mouse2: &nbsp;<kbd>Ctrl</kbd> + `Mousewheel` or `Trackpad Pinch` to zoom\n-   :mag_right: &nbsp;Double/Single-tap or double/single-click to zoom in/out\n-   :ok_hand: &nbsp;&nbsp;&nbsp;Pinch to zoom\n-   :point_left: &nbsp;Panning on zoomed-in images\n-   :checkered_flag: &nbsp;Highly performant spring based animations via [react-spring](https://github.com/react-spring/react-spring)\n-   No external CSS\n-   Implement your own UI\n-   Supports IE 11, Edge, Safari, Chrome, Firefox and Opera\n-   Full typescript support\n-   Supports any `<img />` attribute including `loading` (lazy loading), `srcset` and `aria-*`\n\n## Install\n\n```bash\nyarn add react-spring-lightbox\n```\n\n## Usage\n\nThe `images` prop now accepts a list of objects whose properties can be _almost_ any valid React `<img />` prop including `srcset`, `loading` (lazy loading) and `aria-*` attributes.\n\nIf you use typescript, the exact type can be imported from `import { ImagesListType } from 'react-spring-lightbox';`\n\n```typescript\nimport React, { useState } from 'react';\nimport Lightbox, { ImagesListType } from 'react-spring-lightbox';\n\nconst images: ImagesListType = [\n    {\n        src: 'https://timellenberger.com/static/blog-content/dark-mode/win10-dark-mode.jpg',\n        loading: 'lazy',\n        alt: 'Windows 10 Dark Mode Setting',\n    },\n    {\n        src: 'https://timellenberger.com/static/blog-content/dark-mode/macos-dark-mode.png',\n        loading: 'lazy',\n        alt: 'macOS Mojave Dark Mode Setting',\n    },\n    {\n        src: 'https://timellenberger.com/static/blog-content/dark-mode/android-9-dark-mode.jpg',\n        loading: 'lazy',\n        alt: 'Android 9.0 Dark Mode Setting',\n    },\n];\n\nconst CoolLightbox = () => {\n    const [currentImageIndex, setCurrentIndex] = useState(0);\n\n    const gotoPrevious = () =>\n        currentImageIndex > 0 && setCurrentIndex(currentImageIndex - 1);\n\n    const gotoNext = () =>\n        currentImageIndex + 1 < images.length &&\n        setCurrentIndex(currentImageIndex + 1);\n\n    return (\n        <Lightbox\n            isOpen={true}\n            onPrev={gotoPrevious}\n            onNext={gotoNext}\n            images={images}\n            currentIndex={currentImageIndex}\n            /* Add your own UI */\n            // renderHeader={() => (<CustomHeader />)}\n            // renderFooter={() => (<CustomFooter />)}\n            // renderPrevButton={() => (<CustomLeftArrowButton />)}\n            // renderNextButton={() => (<CustomRightArrowButton />)}\n            // renderImageOverlay={() => (<ImageOverlayComponent >)}\n\n            /* Add styling */\n            // className=\"cool-class\"\n            // style={{ background: \"grey\" }}\n\n            /* Handle closing */\n            // onClose={handleClose}\n\n            /* Use single or double click to zoom */\n            // singleClickToZoom\n\n            /* react-spring config for open/close animation */\n            // pageTransitionConfig={{\n            //   from: { transform: \"scale(0.75)\", opacity: 0 },\n            //   enter: { transform: \"scale(1)\", opacity: 1 },\n            //   leave: { transform: \"scale(0.75)\", opacity: 0 },\n            //   config: { mass: 1, tension: 320, friction: 32 }\n            // }}\n        />\n    );\n};\n\nexport default CoolLightbox;\n```\n\n## Props\n\n| Prop                 | Description                                                                                                        |\n| -------------------- | ------------------------------------------------------------------------------------------------------------------ |\n| isOpen               | Flag that dictates if the lightbox is open or closed                                                               |\n| onClose              | Function that closes the Lightbox                                                                                  |\n| onPrev               | Function that changes currentIndex to previous image in images                                                     |\n| onNext               | Function that changes currentIndex to next image in images                                                         |\n| currentIndex         | Index of image in images array that is currently shown                                                             |\n| renderHeader         | A React component that renders above the image pager                                                               |\n| renderFooter         | A React component that renders below the image pager                                                               |\n| renderPrevButton     | A React component that is used for previous button in image pager                                                  |\n| renderNextButton     | A React component that is used for next button in image pager                                                      |\n| renderImageOverlay   | A React component that renders within the image stage, useful for creating UI overlays on top of the current image |\n| singleClickToZoom    | Overrides the default behavior of double clicking causing an image zoom to a single click                          |\n| images               | Array of image objects to be shown in Lightbox                                                                     |\n| className            | Classes are applied to the root lightbox component                                                                 |\n| style                | Inline styles are applied to the root lightbox component                                                           |\n| pageTransitionConfig | React-Spring useTransition config for page open/close animation                                                    |\n\n## Local Development\n\nClone the repo\n\n```bash\ngit clone https://github.com/tim-soft/react-spring-lightbox.git react-spring-lightbox\ncd react-spring-lightbox\n```\n\nSetup symlinks\n\n```bash\nyarn link\ncd example\nyarn link react-spring-lightbox\n```\n\nRun the library in development mode\n\n```bash\nyarn start\n```\n\nRun the example app in development mode\n\n```bash\ncd example\nyarn dev\n```\n\nChanges to the library code should hot reload in the demo app\n\n## License\n\nMIT © [Tim Ellenberger](https://github.com/tim-soft)\n"
  },
  {
    "path": "example/README.md",
    "content": "# react-spring-lightbox demo app\n\nThis directory contains a Next.js app useful for testing and developing `react-spring-lightbox`\n\nInstall dependencies\n\n```bash\nyarn install\n```\n\nRun locally with hot module reloading\n\n```bash\nyarn dev\n```\n"
  },
  {
    "path": "example/components/GalleryLightbox/components/GridImage.jsx",
    "content": "import * as React from 'react';\nimport PropTypes from 'prop-types';\nimport styled from 'styled-components';\n\n/**\n * A single image element in a masonry style image grid\n */\nconst GridImage = ({ index, key, left, onClick, photo, top }) => {\n    const { alt, caption, height, src, width } = photo;\n    return (\n        <ImageContainer\n            index={index}\n            key={`${key}-${index}`}\n            onClick={(e) => onClick(e, { index })}\n            style={{ height, left, top, width }}\n        >\n            <OverlayContainer>\n                <Image alt={alt} caption={caption} src={src} />\n                <Caption>\n                    <h4>{caption}</h4>\n                </Caption>\n            </OverlayContainer>\n        </ImageContainer>\n    );\n};\n\nGridImage.propTypes = {\n    containerHeight: PropTypes.number.isRequired,\n    index: PropTypes.number.isRequired,\n    key: PropTypes.string.isRequired,\n    left: PropTypes.number.isRequired,\n    onClick: PropTypes.func.isRequired,\n    photo: PropTypes.shape({\n        alt: PropTypes.string.isRequired,\n        caption: PropTypes.string.isRequired,\n        height: PropTypes.number.isRequired,\n        src: PropTypes.string.isRequired,\n        width: PropTypes.number.isRequired,\n    }).isRequired,\n    top: PropTypes.number.isRequired,\n};\n\nexport default GridImage;\n\nconst Caption = styled.div`\n    position: absolute;\n    bottom: 0;\n    width: 100%;\n    background-color: ${({ theme }) => theme.accentColor};\n    color: ${({ theme }) => theme.pageContentLinkHoverColor};\n    h4 {\n        text-align: center;\n        margin: 1em 0;\n    }\n`;\n\nconst OverlayContainer = styled.div`\n    position: relative;\n    height: 100%;\n    overflow: hidden;\n`;\n\nconst ImageContainer = styled.div`\n    display: block;\n    position: absolute;\n    cursor: pointer;\n    border-width: 2px;\n    border-color: transparent;\n    border-style: solid;\n    :hover {\n        border-color: ${({ theme }) => theme.pageContentLinkHoverColor};\n    }\n`;\n\nconst Image = styled.img`\n    width: inherit;\n    height: inherit;\n    position: absolute;\n`;\n"
  },
  {
    "path": "example/components/GalleryLightbox/components/LightboxArrowButton.jsx",
    "content": "/* eslint-disable no-shadow */\nimport * as React from 'react';\nimport PropTypes from 'prop-types';\nimport styled from 'styled-components';\nimport { IoIosArrowBack, IoIosArrowForward } from 'react-icons/io';\nimport { animated, useTransition } from '@react-spring/web';\nimport ButtonControl from './LightboxButtonControl';\n\nconst ArrowButton = ({ className, disabled, onClick, position }) => {\n    const transitions = useTransition(!disabled, {\n        enter: { opacity: 1 },\n        from: { opacity: 0 },\n        leave: { opacity: 0 },\n    });\n\n    return transitions(\n        (props, item) =>\n            item && (\n                <StyledAnimatedDiv className={className} style={props}>\n                    <Button onClick={onClick} position={position} type=\"button\">\n                        {position === 'left' && <IoIosArrowBack />}\n                        {position === 'right' && <IoIosArrowForward />}\n                    </Button>\n                </StyledAnimatedDiv>\n            ),\n    );\n};\n\nArrowButton.propTypes = {\n    disabled: PropTypes.bool,\n    onClick: PropTypes.func.isRequired,\n    position: PropTypes.oneOf(['left', 'right']).isRequired,\n};\n\nArrowButton.defaultProps = {\n    disabled: false,\n};\n\nexport default ArrowButton;\n\nconst StyledAnimatedDiv = styled(animated.div)`\n    z-index: 999;\n`;\n\nconst Button = styled(ButtonControl)`\n    position: absolute;\n    top: 0;\n    bottom: 0;\n    left: ${({ position }) => (position === 'left' ? 0 : 'unset')};\n    right: ${({ position }) => (position === 'right' ? 0 : 'unset')};\n`;\n"
  },
  {
    "path": "example/components/GalleryLightbox/components/LightboxButtonControl.jsx",
    "content": "import styled from 'styled-components';\n\nexport default styled.button`\n    z-index: 10;\n    background: none;\n    border-style: none;\n    font-size: 50px;\n    cursor: pointer;\n    padding: 0;\n    margin: 0;\n    color: ${({ theme }) => theme.pageContentFontColor};\n    transition: color 0.2s linear;\n    :hover {\n        color: ${({ theme }) => theme.pageContentLinkHoverColor};\n    }\n    :focus {\n        outline: none;\n        color: ${({ theme }) => theme.pageContentLinkHoverColor};\n    }\n`;\n"
  },
  {
    "path": "example/components/GalleryLightbox/components/LightboxHeader.jsx",
    "content": "import * as React from 'react';\nimport PropTypes from 'prop-types';\nimport styled from 'styled-components';\nimport { IoIosClose } from 'react-icons/io';\nimport Color from 'color';\nimport ButtonControl from './LightboxButtonControl';\n\nconst LightboxHeader = ({ currentIndex, galleryTitle, images, onClose }) => (\n    <TopHeaderBar>\n        <LeftSideDescriptionContainer>\n            <GalleryHeading>{galleryTitle}</GalleryHeading>\n            <GallerySubheading>\n                {images[currentIndex].caption}\n            </GallerySubheading>\n        </LeftSideDescriptionContainer>\n\n        <RightSideContainer>\n            <PageIndicator>\n                {currentIndex + 1} / {images.length}\n            </PageIndicator>\n            <CloseButton onClick={onClose} type=\"button\">\n                <IoIosClose size={60} />\n            </CloseButton>\n        </RightSideContainer>\n    </TopHeaderBar>\n);\n\nLightboxHeader.propTypes = {\n    currentIndex: PropTypes.number.isRequired,\n    galleryTitle: PropTypes.string.isRequired,\n    images: PropTypes.arrayOf(\n        PropTypes.shape({\n            alt: PropTypes.string.isRequired,\n            caption: PropTypes.string.isRequired,\n            height: PropTypes.number,\n            src: PropTypes.string.isRequired,\n            width: PropTypes.number,\n        }),\n    ).isRequired,\n    onClose: PropTypes.func.isRequired,\n};\n\nexport default LightboxHeader;\n\nconst GalleryHeading = styled.h2`\n    margin: 0 0 5px 0;\n    font-weight: normal;\n`;\n\nconst GallerySubheading = styled.h4`\n    margin: 0;\n    font-weight: normal;\n    color: ${({ theme }) => theme.pageContentLinkHoverColor};\n`;\n\nconst PageIndicator = styled.span`\n    white-space: nowrap;\n    min-width: 60px;\n    text-align: center;\n`;\n\nconst RightSideContainer = styled.div`\n    width: 117px;\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n`;\n\nconst CloseButton = styled(ButtonControl)`\n    height: 100%;\n    display: flex;\n    border-left-style: solid;\n    border-left-width: 3px;\n    border-left-color: ${({ theme }) => theme.headerNavFontColor};\n    color: inherit;\n`;\n\nconst LeftSideDescriptionContainer = styled.div`\n    display: flex;\n    flex-direction: column;\n    justify-content: center;\n    border-left-width: 3px;\n    border-left-color: ${({ theme }) => theme.pageContentLinkHoverColor};\n    border-left-style: solid;\n    padding: 8px 0 8px 10px;\n`;\n\nconst TopHeaderBar = styled.header`\n    z-index: 10;\n    cursor: auto;\n    display: flex;\n    justify-content: space-between;\n    padding: 10px 2px 10px 20px;\n    color: ${({ theme }) => theme.headerNavFontColor};\n    background-color: ${({ theme }) =>\n        Color(theme.pageBackgroundColor).alpha(0.5).hsl().string()};\n    > * {\n        height: inherit;\n    }\n`;\n"
  },
  {
    "path": "example/components/GalleryLightbox/index.jsx",
    "content": "import { FiHeart, FiPrinter, FiShare } from 'react-icons/fi';\nimport React from 'react';\nimport PropTypes from 'prop-types';\nimport styled from 'styled-components';\nimport Color from 'color';\nimport Gallery from 'react-photo-gallery';\nimport Lightbox from 'react-spring-lightbox';\nimport GridImage from './components/GridImage';\nimport LightboxHeader from './components/LightboxHeader';\nimport LightboxArrowButton from './components/LightboxArrowButton';\n\nclass BlogImageGallery extends React.Component {\n    static propTypes = {\n        galleryTitle: PropTypes.string.isRequired,\n        imageMasonryDirection: PropTypes.oneOf(['column', 'row']),\n        images: PropTypes.arrayOf(\n            PropTypes.shape({\n                alt: PropTypes.string.isRequired,\n                caption: PropTypes.string.isRequired,\n                height: PropTypes.number,\n                src: PropTypes.string.isRequired,\n                width: PropTypes.number,\n            }),\n        ).isRequired,\n    };\n\n    static defaultProps = {\n        imageMasonryDirection: 'column',\n    };\n\n    constructor() {\n        super();\n\n        this.state = {\n            clientSide: false,\n            currentImageIndex: 0,\n            lightboxIsOpen: false,\n        };\n    }\n\n    componentDidMount() {\n        this.setState({ clientSide: true });\n    }\n\n    openLightbox = (e, { index }) => {\n        this.setState({\n            currentImageIndex: index,\n            lightboxIsOpen: true,\n        });\n    };\n\n    closeLightbox = () => {\n        this.setState({\n            lightboxIsOpen: false,\n        });\n    };\n\n    gotoPrevious = () => {\n        const { currentImageIndex } = this.state;\n\n        // If the current image isn't the first in the list, go to the previous\n        if (currentImageIndex > 0) {\n            this.setState({\n                currentImageIndex: currentImageIndex - 1,\n            });\n        }\n    };\n\n    gotoNext = () => {\n        const { images } = this.props;\n        const { currentImageIndex } = this.state;\n\n        // If the current image isn't the list in the list, go to the next\n        if (currentImageIndex + 1 < images.length) {\n            this.setState({\n                currentImageIndex: currentImageIndex + 1,\n            });\n        }\n    };\n\n    /**\n     * Sets breakpoints for column width based on containerWidth\n     *\n     * @int containerWidth The current width of the image grid\n     */\n    columnConfig = (containerWidth) => {\n        let columns = 1;\n        if (containerWidth >= 500) columns = 2;\n        if (containerWidth >= 900) columns = 3;\n        if (containerWidth >= 1500) columns = 4;\n\n        return columns;\n    };\n\n    render() {\n        const { clientSide, currentImageIndex, lightboxIsOpen } = this.state;\n        const { galleryTitle, imageMasonryDirection, images } = this.props;\n\n        // remove the height and width props for the lightbox images array\n        const listboxImages = [...images].map((image) => {\n            const newImage = { ...image };\n            delete newImage.height;\n            delete newImage.width;\n\n            return newImage;\n        });\n\n        return (\n            <GalleryContainer>\n                {clientSide && (\n                    <Gallery\n                        columns={this.columnConfig}\n                        direction={imageMasonryDirection}\n                        margin={6}\n                        onClick={this.openLightbox}\n                        photos={images}\n                        renderImage={GridImage}\n                    />\n                )}\n                <StyledLightbox\n                    currentIndex={currentImageIndex}\n                    galleryTitle={galleryTitle}\n                    images={listboxImages}\n                    isOpen={lightboxIsOpen}\n                    onClose={this.closeLightbox}\n                    onNext={this.gotoNext}\n                    onPrev={this.gotoPrevious}\n                    renderHeader={() => (\n                        <LightboxHeader\n                            currentIndex={currentImageIndex}\n                            galleryTitle={galleryTitle}\n                            images={images}\n                            onClose={this.closeLightbox}\n                        />\n                    )}\n                    renderImageOverlay={() => (\n                        <ImageOverlay>\n                            <p>Create your own UI</p>\n                            <FiPrinter size=\"3em\" />\n                            <FiShare size=\"3em\" />\n                            <FiHeart size=\"3em\" />\n                        </ImageOverlay>\n                    )}\n                    renderNextButton={({ canNext }) => (\n                        <LightboxArrowButton\n                            disabled={!canNext}\n                            onClick={this.gotoNext}\n                            position=\"right\"\n                        />\n                    )}\n                    renderPrevButton={({ canPrev }) => (\n                        <LightboxArrowButton\n                            disabled={!canPrev}\n                            onClick={this.gotoPrevious}\n                            position=\"left\"\n                        />\n                    )}\n                    singleClickToZoom\n                />\n            </GalleryContainer>\n        );\n    }\n}\n\nexport default BlogImageGallery;\n\nconst GalleryContainer = styled.div`\n    overflow-y: auto;\n    max-height: calc(100% - 4em);\n    padding: 2em;\n`;\n\nconst StyledLightbox = styled(Lightbox)`\n    background: ${({ theme }) =>\n        Color(theme.accentColor).alpha(0.95).hsl().string()};\n    * ::selection {\n        background: ${({ theme }) => theme.pageContentSelectionColor};\n    }\n    * ::-moz-selection {\n        background: ${({ theme }) =>\n            new Color(theme.pageContentSelectionColor).darken(0.57).hex()};\n    }\n`;\n\nconst ImageOverlay = styled.div`\n    position: absolute;\n    top: 0%;\n    right: 0%;\n    border: ${({ theme }) => theme.pageContentSelectionColor} 1px solid;\n    background: rgba(39, 39, 39, 0.5);\n    p {\n        color: ${({ theme }) => theme.pageContentSelectionColor};\n        text-align: center;\n        font-weight: bold;\n        font-size: 1.2em;\n        margin: 0.5em 0;\n    }\n    svg {\n        border: white 1px solid;\n        fill: ${({ theme }) => theme.pageContentSelectionColor};\n        margin: 10px;\n        padding: 5px;\n        :hover {\n            border: ${({ theme }) => theme.pageContentSelectionColor} 1px solid;\n            fill: ${({ theme }) =>\n                new Color(theme.pageContentSelectionColor).darken(0.57).hex()};\n            cursor: pointer;\n        }\n    }\n`;\n"
  },
  {
    "path": "example/components/InlineLightbox/index.jsx",
    "content": "import * as React from 'react';\nimport PropTypes from 'prop-types';\nimport styled from 'styled-components';\nimport Lightbox from 'react-spring-lightbox';\nimport LightboxArrowButton from '../GalleryLightbox/components/LightboxArrowButton';\n\nconst InlineLightbox = ({ images }) => {\n    const [currentImageIndex, setCurrentImageIndex] = React.useState(0);\n    const inlineCarouselElement = React.useRef();\n\n    React.useEffect(() => {\n        inlineCarouselElement?.current?.addEventListener('wheel', preventWheel);\n\n        setCurrentImageIndex(0);\n    }, [inlineCarouselElement, images]);\n\n    const preventWheel = (e) => {\n        e.preventDefault();\n        e.stopPropagation();\n        return false;\n    };\n\n    const canPrev = currentImageIndex > 0;\n    const canNext = currentImageIndex + 1 < images.length;\n\n    const gotoNext = () => {\n        canNext ? setCurrentImageIndex(currentImageIndex + 1) : () => null;\n    };\n\n    const gotoPrevious = () => {\n        canPrev ? setCurrentImageIndex(currentImageIndex - 1) : null;\n    };\n\n    return (\n        <Container ref={inlineCarouselElement}>\n            <Lightbox\n                currentIndex={currentImageIndex}\n                images={images}\n                inline\n                isOpen\n                onNext={gotoNext}\n                onPrev={gotoPrevious}\n                renderNextButton={({ canNext }) => (\n                    <StyledLightboxArrowButton\n                        disabled={!canNext}\n                        onClick={gotoNext}\n                        position=\"right\"\n                    />\n                )}\n                renderPrevButton={({ canPrev }) => (\n                    <StyledLightboxArrowButton\n                        disabled={!canPrev}\n                        onClick={gotoPrevious}\n                        position=\"left\"\n                    />\n                )}\n                singleClickToZoom\n            />\n        </Container>\n    );\n};\n\nexport default InlineLightbox;\n\nInlineLightbox.propTypes = {\n    images: PropTypes.arrayOf(\n        PropTypes.shape({\n            alt: PropTypes.string.isRequired,\n            caption: PropTypes.string.isRequired,\n            height: PropTypes.number,\n            src: PropTypes.string.isRequired,\n            width: PropTypes.number,\n        }),\n    ).isRequired,\n};\n\nconst Container = styled.div`\n    display: inline-flex;\n    flex-direction: column;\n    width: 100%;\n    height: 384px;\n    overflow: hidden;\n`;\n\nconst StyledLightboxArrowButton = styled(LightboxArrowButton)`\n    z-index: 10;\n    button {\n        font-size: 25px;\n    }\n`;\n"
  },
  {
    "path": "example/next-env.d.ts",
    "content": "/// <reference types=\"next\" />\n/// <reference types=\"next/image-types/global\" />\n\n// NOTE: This file should not be edited\n// see https://nextjs.org/docs/basic-features/typescript for more information.\n"
  },
  {
    "path": "example/next.config.js",
    "content": "/** @type {import('next').NextConfig} */\nconst nextConfig = {\n    compiler: {\n        styledComponents: true,\n    },\n    transpilePackages: ['styled-components', 'react-spring-lightbox'],\n};\n\nmodule.exports = nextConfig;\n"
  },
  {
    "path": "example/package.json",
    "content": "{\n    \"name\": \"react-spring-lightbox-example\",\n    \"homepage\": \"https://tim-soft.github.io/react-spring-lightbox\",\n    \"version\": \"0.0.0\",\n    \"license\": \"MIT\",\n    \"private\": true,\n    \"dependencies\": {\n        \"@react-spring/web\": \"link:../node_modules/@react-spring/web\",\n        \"color\": \"^4.2.3\",\n        \"lodash.clamp\": \"^4.0.3\",\n        \"lodash.merge\": \"^4.6.2\",\n        \"next\": \"13.5.4\",\n        \"prop-types\": \"^15.6.2\",\n        \"react\": \"link:../node_modules/react\",\n        \"react-dom\": \"link:../node_modules/react-dom\",\n        \"react-icons\": \"^4.2.0\",\n        \"react-is\": \"link:../node_modules/react-is\",\n        \"react-photo-gallery\": \"^8.0.0\",\n        \"react-spring-lightbox\": \"link:..\",\n        \"styled-components\": \"link:../node_modules/styled-components\",\n        \"styled-normalize\": \"^8.0.7\"\n    },\n    \"scripts\": {\n        \"dev\": \"next\",\n        \"start\": \"next start\",\n        \"build\": \"next build\"\n    },\n    \"browserslist\": [\n        \">0.2%\",\n        \"not dead\",\n        \"not ie <= 11\",\n        \"not op_mini all\"\n    ]\n}\n"
  },
  {
    "path": "example/pages/_app.jsx",
    "content": "import App from 'next/app';\nimport React from 'react';\nimport { createGlobalStyle, ThemeProvider } from 'styled-components';\nimport styledNormalize from 'styled-normalize';\n\nexport default class MyApp extends App {\n    render() {\n        const { Component, pageProps } = this.props;\n        return (\n            <>\n                {/* Adds some basic body styles */}\n                <DefaultStyles />\n\n                <ThemeProvider\n                    theme={{\n                        accentColor: '#1f1f1f',\n                        headerNavFontColor: '#e2e5ec',\n                        pageBackgroundColor: '#101010',\n                        pageContentFontColor: '#e2e5ec',\n                        pageContentLinkHoverColor: 'aquamarine',\n                        pageContentSelectionColor: 'aquamarine',\n                    }}\n                >\n                    <Component {...pageProps} />\n                </ThemeProvider>\n            </>\n        );\n    }\n}\n\n/**\n * Adds global styles and normalize.css to the entire app\n *\n * http://nicolasgallagher.com/about-normalize-css/\n * https://www.styled-components.com/docs/api#createglobalstyle\n */\nconst DefaultStyles = createGlobalStyle`\n ${styledNormalize}\n body {\n   margin: 0;\n   background: #1D1E1F;\n   font-family: 'Montserrat', sans-serif;\n   -ms-text-size-adjust: 100%;\n   -webkit-text-size-adjust: 100%;\n   -moz-osx-font-smoothing: grayscale;\n   -webkit-font-smoothing: antialiased;\n }\n`;\n"
  },
  {
    "path": "example/pages/_document.jsx",
    "content": "import * as React from 'react';\nimport Document from 'next/document';\nimport { ServerStyleSheet } from 'styled-components';\n\nexport default class MyDocument extends Document {\n    static async getInitialProps(ctx) {\n        const sheet = new ServerStyleSheet();\n        const originalRenderPage = ctx.renderPage;\n\n        try {\n            ctx.renderPage = () =>\n                originalRenderPage({\n                    enhanceApp: (App) => (props) =>\n                        sheet.collectStyles(<App {...props} />),\n                });\n\n            const initialProps = await Document.getInitialProps(ctx);\n            return {\n                ...initialProps,\n                styles: (\n                    <>\n                        {initialProps.styles}\n                        {sheet.getStyleElement()}\n                    </>\n                ),\n            };\n        } finally {\n            sheet.seal();\n        }\n    }\n}\n"
  },
  {
    "path": "example/pages/index.tsx",
    "content": "import * as React from 'react';\nimport styled from 'styled-components';\nimport GalleryLightbox from '../components/GalleryLightbox';\nimport InlineLightbox from '../components/InlineLightbox';\n\nconst images = [\n    {\n        alt: 'Windows 10 Dark Mode Setting',\n        caption: 'Windows 10 Dark Mode Setting',\n        height: 2035,\n        src: 'https://timellenberger.com/static/blog-content/dark-mode/win10-dark-mode.jpg',\n        width: 2848,\n    },\n    {\n        alt: 'macOS Mojave Dark Mode Setting',\n        caption: 'macOS Mojave Dark Mode Setting',\n        height: 1218,\n        src: 'https://timellenberger.com/static/blog-content/dark-mode/macos-dark-mode.png',\n        width: 1200,\n    },\n    {\n        alt: 'Android 9.0 Dark Mode Setting',\n        caption: 'Android 9.0 Dark Mode Setting',\n        height: 600,\n        src: 'https://timellenberger.com/static/blog-content/dark-mode/android-9-dark-mode.jpg',\n        width: 1280,\n    },\n    {\n        alt: 'Windows 10 Dark Mode Setting#',\n        caption: 'Windows 10 Dark Mode Setting#',\n        height: 2035,\n        src: 'https://timellenberger.com/static/blog-content/dark-mode/win10-dark-mode.jpg',\n        width: 2848,\n    },\n    {\n        alt: 'Windows 10 Dark Mode Setting',\n        caption: 'Windows 10 Dark Mode Setting',\n        height: 2035,\n        src: 'https://timellenberger.com/static/blog-content/dark-mode/win10-dark-mode.jpg',\n        width: 2848,\n    },\n    {\n        alt: 'macOS Mojave Dark Mode Setting',\n        caption: 'macOS Mojave Dark Mode Setting',\n        height: 1218,\n        src: 'https://timellenberger.com/static/blog-content/dark-mode/macos-dark-mode.png',\n        width: 1200,\n    },\n    {\n        alt: 'Android 9.0 Dark Mode Setting',\n        caption: 'Android 9.0 Dark Mode Setting',\n        height: 600,\n        src: 'https://timellenberger.com/static/blog-content/dark-mode/android-9-dark-mode.jpg',\n        width: 1280,\n    },\n    {\n        alt: 'Windows 10 Dark Mode Setting#',\n        caption: 'Windows 10 Dark Mode Setting#',\n        height: 2035,\n        src: 'https://timellenberger.com/static/blog-content/dark-mode/win10-dark-mode.jpg',\n        width: 2848,\n    },\n    {\n        alt: 'Android 9.0 Dark Mode Setting',\n        caption: 'Android 9.0 Dark Mode Setting',\n        height: 600,\n        src: 'https://timellenberger.com/static/blog-content/dark-mode/android-9-dark-mode.jpg',\n        width: 1280,\n    },\n    {\n        alt: 'Windows 10 Dark Mode Setting#',\n        caption: 'Windows 10 Dark Mode Setting#',\n        height: 2035,\n        src: 'https://timellenberger.com/static/blog-content/dark-mode/win10-dark-mode.jpg',\n        width: 2848,\n    },\n    {\n        alt: 'macOS Mojave Dark Mode Setting',\n        caption: 'macOS Mojave Dark Mode Setting',\n        height: 1218,\n        src: 'https://timellenberger.com/static/blog-content/dark-mode/macos-dark-mode.png',\n        width: 1200,\n    },\n    {\n        alt: 'Android 9.0 Dark Mode Setting',\n        caption: 'Android 9.0 Dark Mode Setting',\n        height: 600,\n        src: 'https://timellenberger.com/static/blog-content/dark-mode/android-9-dark-mode.jpg',\n        width: 1280,\n    },\n    {\n        alt: 'Windows 10 Dark Mode Setting#',\n        caption: 'Windows 10 Dark Mode Setting#',\n        height: 2035,\n        src: 'https://timellenberger.com/static/blog-content/dark-mode/win10-dark-mode.jpg',\n        width: 2848,\n    },\n    {\n        alt: 'Android 9.0 Dark Mode Setting',\n        caption: 'Android 9.0 Dark Mode Setting',\n        height: 600,\n        src: 'https://timellenberger.com/static/blog-content/dark-mode/android-9-dark-mode.jpg',\n        width: 1280,\n    },\n    {\n        alt: 'Windows 10 Dark Mode Setting#',\n        caption: 'Windows 10 Dark Mode Setting#',\n        height: 2035,\n        src: 'https://timellenberger.com/static/blog-content/dark-mode/win10-dark-mode.jpg',\n        width: 2848,\n    },\n];\n\nconst getRandomImages = (numImages: number) => {\n    const imageArray = [...new Array(numImages)];\n\n    const getRandomInt = (min, max) => {\n        min = Math.ceil(min);\n        max = Math.floor(max);\n        return Math.floor(Math.random() * (max - min) + min); //The maximum is exclusive and the minimum is inclusive\n    };\n\n    const altCaption = 'picsum photo';\n    const randomizedArray = imageArray.map((imageObj) => {\n        const imageURL = `https://picsum.photos/id/${getRandomInt(\n            1,\n            200,\n        )}/1920/1280`;\n        return {\n            ...imageObj,\n            alt: altCaption,\n            caption: altCaption,\n            src: imageURL,\n        };\n    });\n\n    return randomizedArray;\n};\n\nconst HomePage = () => {\n    const [inlineImages, setInlineImages] = React.useState(getRandomImages(15));\n\n    return (\n        <Container>\n            <GalleryLightboxExample>\n                <StyledH2>Gallery Lightbox</StyledH2>\n                <GalleryLightbox\n                    galleryTitle=\"Dark Mode: OS Level Control In Your CSS\"\n                    imageMasonryDirection=\"column\"\n                    images={images}\n                />\n            </GalleryLightboxExample>\n            <hr />\n            <InlineLightboxExampleContainer>\n                <StyledH2>Inline Lightbox</StyledH2>\n                <InlineLightboxExample>\n                    <OtherInlineContent>\n                        🎉🎉Inline content🎉🎉\n                    </OtherInlineContent>\n                    <InlineLightbox images={images} />\n                    <OtherInlineContent>\n                        🎉🎉Inline content🎉🎉\n                    </OtherInlineContent>\n                </InlineLightboxExample>\n                <Button\n                    onClick={() =>\n                        setInlineImages(getRandomImages(inlineImages.length))\n                    }\n                >\n                    Generate new images\n                </Button>\n                <Button\n                    onClick={() => {\n                        if (inlineImages.length === 1) {\n                            setInlineImages(getRandomImages(15));\n                        } else {\n                            setInlineImages(getRandomImages(1));\n                        }\n                    }}\n                >\n                    Switch Image Array\n                </Button>\n            </InlineLightboxExampleContainer>\n        </Container>\n    );\n};\n\nexport default HomePage;\n\nconst Container = styled.div`\n    height: 100%;\n    width: 100%;\n    user-select: none;\n    background: #272727;\n    color: #fff;\n    padding: 50px 0;\n`;\n\nconst Button = styled.button`\n    border-radius: 10px;\n    background: teal;\n    color: #fff;\n    padding: 16px 10px;\n    margin: 20px auto;\n    cursor: pointer;\n    :hover {\n        background: darkblue;\n    }\n    :active {\n        background: darkseagreen;\n    }\n`;\n\nconst GalleryLightboxExample = styled.div`\n    height: 100%;\n    width: 100%;\n`;\n\nconst InlineLightboxExampleContainer = styled.div`\n    height: 100%;\n    width: 100%;\n    display: flex;\n    flex-direction: column;\n`;\nconst InlineLightboxExample = styled.div`\n    width: 100%;\n    display: flex;\n    justify-content: center;\n`;\n\nconst OtherInlineContent = styled.div`\n    width: 400px;\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    padding: 10px;\n    background: blueviolet;\n`;\n\nconst StyledH2 = styled.h2`\n    text-align: center;\n`;\n"
  },
  {
    "path": "example/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"allowJs\": true,\n        \"esModuleInterop\": true,\n        \"incremental\": true,\n        \"isolatedModules\": true,\n        \"jsx\": \"preserve\",\n        \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n        \"module\": \"esnext\",\n        \"moduleResolution\": \"node\",\n        \"noEmit\": true,\n        \"resolveJsonModule\": true,\n        \"skipLibCheck\": true,\n        \"strict\": false\n    },\n    \"exclude\": [\"node_modules\"],\n    \"include\": [\"next-env.d.ts\", \"**/*.ts\", \"**/*.tsx\"]\n}\n"
  },
  {
    "path": "jest-setup.ts",
    "content": "import '@testing-library/jest-dom';\n"
  },
  {
    "path": "jest.config.js",
    "content": "/**\n * Configure Jest as the test runner for @testing-library\n *\n * @see https://jestjs.io/docs/en/configuration\n */\nmodule.exports = {\n    collectCoverage: true,\n    coveragePathIgnorePatterns: ['/node_modules/', '/__tests__/'],\n    preset: 'ts-jest',\n    setupFilesAfterEnv: ['babel-polyfill', '<rootDir>/jest-setup.ts'],\n    testEnvironment: 'jsdom',\n    testMatch: ['**/__tests__/**/*.(spec|test).[jt]s?(x)'],\n    transform: {\n        '^.+\\\\.(js|jsx)$': 'babel-jest',\n        '^.+\\\\.(ts|tsx)?$': 'ts-jest',\n    },\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n    \"name\": \"react-spring-lightbox\",\n    \"version\": \"1.8.0\",\n    \"description\": \"A flexible image gallery lightbox with native-feeling touch gestures and buttery smooth animations, built with react-spring.\",\n    \"author\": \"Tim Ellenberger <timellenberger@gmail.com>\",\n    \"license\": \"MIT\",\n    \"repository\": \"tim-soft/react-spring-lightbox\",\n    \"sideEffects\": false,\n    \"bugs\": {\n        \"url\": \"https://github.com/tim-soft/react-spring-lightbox/issues\"\n    },\n    \"homepage\": \"https://timellenberger.com\",\n    \"keywords\": [\n        \"react\",\n        \"spring\",\n        \"lightbox\",\n        \"modal\",\n        \"gallery\",\n        \"touch\",\n        \"gestures\",\n        \"images\"\n    ],\n    \"main\": \"dist/index.cjs.js\",\n    \"types\": \"dist/index.d.ts\",\n    \"engines\": {\n        \"node\": \">=16\",\n        \"npm\": \">=7\"\n    },\n    \"scripts\": {\n        \"fix\": \"yarn fix:eslint && yarn fix:prettier\",\n        \"fix:eslint\": \"eslint --fix \\\"**/*.*\\\"\",\n        \"fix:prettier\": \"prettier --write \\\"**/*.*\\\"\",\n        \"lint\": \"yarn lint:eslint && yarn lint:prettier && yarn lint:ts\",\n        \"lint:eslint\": \"eslint \\\"**/*.*\\\"\",\n        \"lint:prettier\": \"prettier --check \\\"**/*.*\\\"\",\n        \"lint:ts\": \"npx tsc --noEmit -p .\",\n        \"test\": \"jest\",\n        \"test:watch\": \"jest --watch\",\n        \"build\": \"rollup --config && yarn run build:types\",\n        \"build:types\": \"tsc --project tsconfig.buildtypes.json --emitDeclarationOnly\",\n        \"start\": \"rollup --config --watch\",\n        \"prepare\": \"yarn run build\"\n    },\n    \"husky\": {\n        \"hooks\": {\n            \"pre-commit\": \"lint-staged\"\n        }\n    },\n    \"lint-staged\": {\n        \"*.{json,md}\": [\n            \"prettier --write\",\n            \"git add --force\"\n        ],\n        \"*.{js, jsx}\": [\n            \"prettier --write\",\n            \"eslint --no-ignore --fix\",\n            \"git add --force\"\n        ]\n    },\n    \"peerDependencies\": {\n        \"react\": \">=16.8\",\n        \"react-dom\": \">=16.8\",\n        \"styled-components\": \">=5.X\"\n    },\n    \"devDependencies\": {\n        \"@babel/core\": \"^7.23.0\",\n        \"@babel/plugin-transform-class-properties\": \"^7.18.6\",\n        \"@babel/plugin-transform-object-rest-spread\": \"^7.20.7\",\n        \"@babel/plugin-transform-runtime\": \"^7.22.15\",\n        \"@babel/preset-env\": \"^7.22.20\",\n        \"@babel/preset-react\": \"^7.22.15\",\n        \"@babel/preset-typescript\": \"^7.23.0\",\n        \"@rollup/plugin-babel\": \"^6.0.4\",\n        \"@rollup/plugin-commonjs\": \"^25.0.5\",\n        \"@rollup/plugin-node-resolve\": \"^15.2.2\",\n        \"@rollup/plugin-terser\": \"^0.4.4\",\n        \"@testing-library/jest-dom\": \"^6.1.3\",\n        \"@testing-library/react\": \"^14.0.0\",\n        \"@types/jest\": \"^29.5.5\",\n        \"@types/react\": \"^18.2.25\",\n        \"@types/react-dom\": \"^18.2.10\",\n        \"@types/styled-components\": \"5.1.28\",\n        \"@typescript-eslint/eslint-plugin\": \"^6.7.4\",\n        \"@typescript-eslint/parser\": \"^6.7.4\",\n        \"babel-eslint\": \"10.1.0\",\n        \"babel-polyfill\": \"^6.26.0\",\n        \"cross-env\": \"^7.0.3\",\n        \"eslint\": \"^8.50.0\",\n        \"eslint-config-prettier\": \"^9.0.0\",\n        \"eslint-import-resolver-typescript\": \"^3.6.1\",\n        \"eslint-plugin-import\": \"^2.28.1\",\n        \"eslint-plugin-jsx-a11y\": \"^6.7.1\",\n        \"eslint-plugin-prettier\": \"^5.0.0\",\n        \"eslint-plugin-react\": \"^7.33.2\",\n        \"eslint-plugin-react-hooks\": \"^4.6.0\",\n        \"eslint-plugin-sort-destructure-keys\": \"^1.5.0\",\n        \"husky\": \"^4.3.8\",\n        \"jest\": \"^29.7.0\",\n        \"jest-environment-jsdom\": \"^29.7.0\",\n        \"lint-staged\": \"^10.5.4\",\n        \"prettier\": \"^3.0.3\",\n        \"react\": \"^18\",\n        \"react-dom\": \"^18\",\n        \"react-is\": \"^18\",\n        \"rollup\": \"^3.29.4\",\n        \"rollup-plugin-filesize\": \"^10.0.0\",\n        \"rollup-plugin-node-externals\": \"^6.1.2\",\n        \"styled-components\": \"^5.3.5\",\n        \"ts-jest\": \"^29.1.1\",\n        \"tslib\": \"^2.6.2\",\n        \"typescript\": \"^5.2.2\"\n    },\n    \"files\": [\n        \"dist\"\n    ],\n    \"dependencies\": {\n        \"@babel/runtime\": \"^7.23.1\",\n        \"@react-spring/web\": \"^9.7.3\",\n        \"react-use-gesture\": \"9.1.3\"\n    }\n}\n"
  },
  {
    "path": "rollup.config.mjs",
    "content": "import commonjs from '@rollup/plugin-commonjs';\nimport filesize from 'rollup-plugin-filesize';\nimport { nodeResolve } from '@rollup/plugin-node-resolve';\nimport { babel } from '@rollup/plugin-babel';\nimport terser from '@rollup/plugin-terser';\nimport { nodeExternals } from 'rollup-plugin-node-externals';\n\nexport default {\n    input: './src/index.tsx',\n    output: [\n        {\n            exports: 'default',\n            file: 'dist/index.cjs.js',\n            format: 'cjs',\n            interop: 'auto',\n            sourcemap: true,\n        },\n    ],\n    plugins: [\n        nodeExternals(),\n        nodeResolve(),\n        commonjs({\n            extensions: ['.js', '.jsx', '.ts', '.tsx'],\n            include: 'node_modules/**',\n        }),\n        babel({\n            babelHelpers: 'runtime',\n            exclude: 'node_modules/**',\n            extensions: ['.js', '.jsx', '.ts', '.tsx'],\n        }),\n        terser(),\n        filesize(),\n    ],\n};\n"
  },
  {
    "path": "src/__tests__/components/SimpleLightbox.tsx",
    "content": "import React, { useState } from 'react';\nimport Lightbox, { ImagesListType } from '../../index';\n\nconst images: ImagesListType = [\n    {\n        alt: 'Windows 10 Dark Mode Setting',\n        src: 'https://timellenberger.com/static/blog-content/dark-mode/win10-dark-mode.jpg',\n    },\n    {\n        alt: 'macOS Mojave Dark Mode Setting',\n        src: 'https://timellenberger.com/static/blog-content/dark-mode/macos-dark-mode.png',\n    },\n    {\n        alt: 'Android 9.0 Dark Mode Setting',\n        src: 'https://timellenberger.com/static/blog-content/dark-mode/android-9-dark-mode.jpg',\n    },\n];\n\nconst SimpleLightbox = (\n    props: Partial<React.ComponentProps<typeof Lightbox>>,\n) => {\n    const [currentImageIndex, setCurrentIndex] = useState(0);\n\n    const gotoPrevious = () =>\n        currentImageIndex > 0 && setCurrentIndex(currentImageIndex - 1);\n\n    const gotoNext = () =>\n        currentImageIndex + 1 < images.length &&\n        setCurrentIndex(currentImageIndex + 1);\n\n    return (\n        <Lightbox\n            currentIndex={currentImageIndex}\n            images={images}\n            isOpen\n            onClose={() => null}\n            onNext={gotoNext}\n            onPrev={gotoPrevious}\n            {...props}\n        />\n    );\n};\n\nexport default SimpleLightbox;\n"
  },
  {
    "path": "src/__tests__/lightbox.test.tsx",
    "content": "import React from 'react';\nimport { render, fireEvent, screen } from '@testing-library/react';\nimport Lightbox from './components/SimpleLightbox';\n\ndescribe('Lightbox', () => {\n    describe('CreatePortal', () => {\n        test('creates portal on render', () => {\n            render(<Lightbox />);\n            const portalEl = document.body.querySelector('.lightbox-portal');\n            expect(portalEl).toBeTruthy();\n        });\n    });\n\n    describe('renderHeader', () => {\n        test('renders custom header', () => {\n            render(\n                <Lightbox\n                    renderHeader={() => <header data-testid=\"header\" />}\n                />,\n            );\n\n            const lightboxContainer = screen.getByTestId('lightbox-container');\n            const lightboxHeader = screen.getByTestId('header');\n\n            // Lightbox container should have a header and pager body\n            expect(lightboxContainer.childElementCount).toBe(2);\n            expect(lightboxContainer).toBeInTheDocument();\n\n            // Header exists in the lightbox\n            expect(lightboxHeader).toBeInTheDocument();\n        });\n    });\n\n    describe('renderFooter', () => {\n        test('renders custom footer', () => {\n            render(\n                <Lightbox\n                    renderFooter={() => <footer data-testid=\"footer\" />}\n                />,\n            );\n\n            const lightboxContainer = screen.getByTestId('lightbox-container');\n            const lightboxFooter = screen.getByTestId('footer');\n\n            // Lightbox container exists and should have a pager body and footer\n            expect(lightboxContainer.childElementCount).toBe(2);\n            expect(lightboxContainer).toBeInTheDocument();\n\n            // Footer exists in the lightbox\n            expect(lightboxFooter).toBeInTheDocument();\n        });\n    });\n\n    describe('renderNextButton/renderPrevButton', () => {\n        test('renders custom prev/next buttons', () => {\n            render(\n                <Lightbox\n                    // eslint-disable-next-line jsx-a11y/control-has-associated-label\n                    renderNextButton={() => (\n                        <button data-testid=\"next-button\" type=\"button\" />\n                    )}\n                    // eslint-disable-next-line jsx-a11y/control-has-associated-label\n                    renderPrevButton={() => (\n                        <button data-testid=\"prev-button\" type=\"button\" />\n                    )}\n                />,\n            );\n\n            const prevButton = screen.getByTestId('next-button');\n            const nextButton = screen.getByTestId('prev-button');\n\n            expect(prevButton).toBeInTheDocument();\n            expect(nextButton).toBeInTheDocument();\n        });\n    });\n\n    describe('keyboard shortcuts', () => {\n        test('calls onNext() callback on ArrowRight keypress', () => {\n            const onNext = jest.fn();\n            render(<Lightbox onNext={onNext} />);\n\n            fireEvent.keyDown(document, { code: 39, key: 'ArrowRight' });\n            fireEvent.keyUp(document, { code: 39, key: 'ArrowRight' });\n\n            expect(onNext).toHaveBeenCalledTimes(1);\n        });\n\n        test('calls onPrev() callback on ArrowLeft keypress', () => {\n            const onPrev = jest.fn();\n            render(<Lightbox currentIndex={2} onPrev={onPrev} />);\n\n            fireEvent.keyDown(document, { code: 37, key: 'ArrowLeft' });\n            fireEvent.keyUp(document, { code: 37, key: 'ArrowLeft' });\n\n            expect(onPrev).toHaveBeenCalledTimes(1);\n        });\n\n        test('calls onClose() callback on Esc keypress', () => {\n            const onClose = jest.fn();\n            render(<Lightbox onClose={onClose} />);\n\n            fireEvent.keyDown(document, { code: 27, key: 'Escape' });\n            fireEvent.keyUp(document, { code: 27, key: 'Escape' });\n\n            expect(onClose).toHaveBeenCalledTimes(1);\n        });\n    });\n});\n"
  },
  {
    "path": "src/components/CreatePortal/index.tsx",
    "content": "import React from 'react';\nimport ReactDOM from 'react-dom';\n\ntype ICreatePortal = {\n    children: any;\n};\n\n/**\n * Creates a SSR + next.js friendly React Portal inside <body />\n *\n * Child components are rendered on the client side only\n\n * @see https://reactjs.org/docs/portals.html\n */\nclass CreatePortal extends React.Component<ICreatePortal> {\n    portalContainer: HTMLDivElement;\n    body: HTMLElement;\n\n    // Only executes on the client-side\n    componentDidMount() {\n        // Get the document body\n        this.body = document.body;\n\n        // Create a container <div /> for React Portal\n        this.portalContainer = document.createElement('div');\n        this.portalContainer.setAttribute('class', 'lightbox-portal');\n\n        // Append the container to the document body\n        this.body.appendChild(this.portalContainer);\n\n        // Force a re-render as we're on the client side now\n        // children prop will render to portalContainer\n        this.forceUpdate();\n\n        // Add event listener to prevent trackpad/ctrl+mousewheel zooming of lightbox\n        // Zooming is handled specifically within /ImageStage/components/Image\n        this.portalContainer.addEventListener('wheel', this.preventWheel);\n    }\n\n    componentWillUnmount() {\n        // Remove wheel event listener\n        this.portalContainer.removeEventListener('wheel', this.preventWheel);\n\n        // Cleanup Portal from DOM\n        this.body.removeChild(this.portalContainer);\n    }\n\n    preventWheel = (e: WheelEvent) => e.preventDefault();\n\n    render() {\n        // Return null during SSR\n        if (this.portalContainer === undefined) return null;\n\n        const { children } = this.props;\n\n        return <>{ReactDOM.createPortal(children, this.portalContainer)}</>;\n    }\n}\n\nexport default CreatePortal;\n"
  },
  {
    "path": "src/components/ImageStage/components/Image/index.tsx",
    "content": "import { animated, to, useSpring } from '@react-spring/web';\nimport {\n    getTranslateOffsetsFromScale,\n    imageIsOutOfBounds,\n    useDoubleClick,\n} from '../../utils';\nimport { useGesture } from 'react-use-gesture';\nimport React, { useEffect, useRef, useState } from 'react';\nimport styled, { AnyStyledComponent } from 'styled-components';\nimport type { ImagesListItem } from '../../../../types/ImagesList';\n\nconst defaultImageTransform = {\n    pinching: false,\n    scale: 1,\n    translateX: 0,\n    translateY: 0,\n};\n\ntype IImageProps = {\n    /** Any valid <img /> props to pass to the lightbox img element ie src, alt, caption etc*/\n    imgProps: ImagesListItem;\n    /** Affects Width calculation method, depending on whether the Lightbox is Inline or not */\n    inline: boolean;\n    /** True if this image is currently shown in pager, otherwise false */\n    isCurrentImage: boolean;\n    /** Fixed height of the image stage, used to restrict maximum height of images */\n    pagerHeight: '100%' | number;\n    /** Indicates parent ImagePager is in a state of dragging, if true click to zoom is disabled */\n    pagerIsDragging: boolean;\n    /** Function that can be called to disable dragging in the pager */\n    setDisableDrag: (disable: boolean) => void;\n    /** Overrides the default behavior of double clicking causing an image zoom to a single click */\n    singleClickToZoom: boolean;\n};\n\n/**\n * Animates pinch-zoom + panning on image using spring physics\n */\nconst Image = ({\n    imgProps: { style: imgStyleProp, ...restImgProps },\n    inline,\n    isCurrentImage,\n    pagerHeight,\n    pagerIsDragging,\n    setDisableDrag,\n    singleClickToZoom,\n}: IImageProps) => {\n    const [isPanningImage, setIsPanningImage] = useState<boolean>(false);\n    const imageRef = useRef<HTMLImageElement>(null);\n\n    /**\n     * Animates scale and translate offsets of Image as they change in gestures\n     *\n     * @see https://www.react-spring.io/docs/hooks/use-spring\n     */\n    const [{ scale, translateX, translateY }, springApi] = useSpring(() => ({\n        ...defaultImageTransform,\n        onChange: (result, instance) => {\n            if (result.value.scale < 1 || !result.value.pinching) {\n                instance.start(defaultImageTransform);\n            }\n\n            if (result.value.scale > 1 && imageIsOutOfBounds(imageRef)) {\n                instance.start(defaultImageTransform);\n            }\n        },\n        // Enable dragging in ImagePager if image is at the default size\n        onRest: (result, instance) => {\n            if (result.value.scale === 1) {\n                instance.start(defaultImageTransform);\n                setDisableDrag(false);\n            }\n        },\n    }));\n\n    // Reset scale of this image when dragging to new image in ImagePager\n    useEffect(() => {\n        if (!isCurrentImage && scale.get() !== 1) {\n            springApi.start(defaultImageTransform);\n        }\n    }, [isCurrentImage, scale, springApi]);\n\n    /**\n     * Update Image scale and translate offsets during pinch/pan gestures\n     *\n     * @see https://github.com/react-spring/react-use-gesture#usegesture-hook-supporting-multiple-gestures-at-once\n     */\n    useGesture(\n        {\n            onDrag: ({\n                cancel,\n                first,\n                memo = { initialTranslateX: 0, initialTranslateY: 0 },\n                movement: [xMovement, yMovement],\n                pinching,\n                tap,\n                touches,\n            }) => {\n                if (pagerIsDragging || scale.get() === 1 || tap) {\n                    return;\n                }\n\n                // Disable click to zoom during drag\n                if (xMovement && yMovement && !isPanningImage) {\n                    setIsPanningImage(true);\n                }\n\n                if (touches > 1) {\n                    return;\n                }\n                if (pinching || scale.get() <= 1) {\n                    return;\n                }\n\n                // Prevent dragging image out of viewport\n                if (scale.get() > 1 && imageIsOutOfBounds(imageRef)) {\n                    cancel();\n                    return;\n                } else {\n                    if (first) {\n                        return {\n                            initialTranslateX: translateX.get(),\n                            initialTranslateY: translateY.get(),\n                        };\n                    }\n\n                    // Translate image from dragging\n                    springApi.start({\n                        translateX: memo.initialTranslateX + xMovement,\n                        translateY: memo.initialTranslateY + yMovement,\n                    });\n\n                    return memo;\n                }\n            },\n            onDragEnd: ({ memo }) => {\n                if (memo !== undefined) {\n                    // Add small timeout to prevent onClick handler from firing after drag\n                    setTimeout(() => setIsPanningImage(false), 100);\n                }\n            },\n            onPinch: ({\n                cancel,\n                ctrlKey,\n                event,\n                last,\n                movement: [xMovement],\n                origin: [touchOriginX, touchOriginY],\n            }) => {\n                if (pagerIsDragging) {\n                    return;\n                }\n\n                // Prevent ImagePager from registering isDragging\n                setDisableDrag(true);\n\n                // Disable click to zoom during pinch\n                if (xMovement && !isPanningImage) {\n                    setIsPanningImage(true);\n                }\n\n                // Don't calculate new translate offsets on final frame\n                if (last) {\n                    cancel();\n                    return;\n                }\n\n                // Speed up pinch zoom when using mouse versus touch\n                const SCALE_FACTOR = ctrlKey ? 1000 : 250;\n                const pinchScale = scale.get() + xMovement / SCALE_FACTOR;\n                const pinchDelta = pinchScale - scale.get();\n\n                /**\n                 * Calculate touch origin for pinch/zoom\n                 *\n                 * if event is a touch event (React.TouchEvent<Element>, TouchEvent or WebKitGestureEvent) use touchOriginX/Y\n                 * if event is a wheel event (React.WheelEvent<Element> or WheelEvent) use the mouse cursor's clientX/Y\n                 */\n                let touchOrigin: [touchOriginX: number, touchOriginY: number] =\n                    [touchOriginX, touchOriginY];\n                if ('clientX' in event && 'clientY' in event && ctrlKey) {\n                    touchOrigin = [event.clientX, event.clientY];\n                }\n\n                // Calculate the amount of x, y translate offset needed to\n                // zoom-in to point as image scale grows\n                const [newTranslateX, newTranslateY] =\n                    getTranslateOffsetsFromScale({\n                        currentTranslate: [translateX.get(), translateY.get()],\n                        imageRef,\n                        pinchDelta,\n                        scale: scale.get(),\n                        // Use the [x, y] coords of mouse if a trackpad or ctrl + wheel event\n                        // Otherwise use touch origin\n                        touchOrigin,\n                    });\n\n                // Restrict the amount of zoom between half and 3x image size\n                if (pinchScale < 0.5) {\n                    springApi.start({ pinching: true, scale: 0.5 });\n                } else if (pinchScale > 3.0) {\n                    springApi.start({ pinching: true, scale: 3.0 });\n                } else {\n                    springApi.start({\n                        pinching: true,\n                        scale: pinchScale,\n                        translateX: newTranslateX,\n                        translateY: newTranslateY,\n                    });\n                }\n            },\n            onPinchEnd: () => {\n                if (!pagerIsDragging) {\n                    if (scale.get() > 1) {\n                        setDisableDrag(true);\n                    } else {\n                        springApi.start(defaultImageTransform);\n                    }\n                    // Add small timeout to prevent onClick handler from firing after panning\n                    setTimeout(() => setIsPanningImage(false), 100);\n                }\n            },\n        },\n        /**\n         * useGesture config\n         * @see https://github.com/react-spring/react-use-gesture#usegesture-config\n         */\n        {\n            domTarget: imageRef as React.RefObject<EventTarget>,\n            drag: {\n                filterTaps: true,\n            },\n            enabled: !inline,\n            eventOptions: {\n                passive: false,\n            },\n        },\n    );\n\n    // Handle click/tap on image\n    useDoubleClick({\n        [singleClickToZoom ? 'onSingleClick' : 'onDoubleClick']: (\n            e: MouseEvent,\n        ) => {\n            if (pagerIsDragging || isPanningImage) {\n                e.stopPropagation();\n                return;\n            }\n\n            // If tapped while already zoomed-in, zoom out to default scale\n            if (scale.get() !== 1) {\n                springApi.start(defaultImageTransform);\n                return;\n            }\n\n            // Zoom-in to origin of click on image\n            const { clientX: touchOriginX, clientY: touchOriginY } = e;\n            const pinchScale = scale.get() + 1;\n            const pinchDelta = pinchScale - scale.get();\n\n            // Calculate the amount of x, y translate offset needed to\n            // zoom-in to point as image scale grows\n            const [newTranslateX, newTranslateY] = getTranslateOffsetsFromScale(\n                {\n                    currentTranslate: [translateX.get(), translateY.get()],\n                    imageRef,\n                    pinchDelta,\n                    scale: scale.get(),\n                    touchOrigin: [touchOriginX, touchOriginY],\n                },\n            );\n\n            // Disable dragging in pager\n            setDisableDrag(true);\n            springApi.start({\n                pinching: true,\n                scale: pinchScale,\n                translateX: newTranslateX,\n                translateY: newTranslateY,\n            });\n        },\n        enabled: !inline,\n        latency: singleClickToZoom ? 0 : 200,\n        ref: imageRef,\n    });\n\n    return (\n        <AnimatedImage\n            $inline={inline}\n            className=\"lightbox-image\"\n            draggable=\"false\"\n            onClick={(e: React.MouseEvent<HTMLImageElement>) => {\n                // Don't close lighbox when clicking image\n                e.stopPropagation();\n                e.nativeEvent.stopImmediatePropagation();\n            }}\n            onDragStart={(e: React.DragEvent<HTMLImageElement>) => {\n                // Disable image ghost dragging in firefox\n                e.preventDefault();\n            }}\n            ref={imageRef}\n            style={{\n                ...imgStyleProp,\n                maxHeight: pagerHeight,\n                transform: to(\n                    [scale, translateX, translateY],\n                    (s, x, y) => `translate(${x}px, ${y}px) scale(${s})`,\n                ),\n                ...(isCurrentImage && { willChange: 'transform' }),\n            }}\n            // Include any valid img html attributes provided in the <Lightbox /> images prop\n            {...(restImgProps as React.ComponentProps<typeof animated.img>)}\n        />\n    );\n};\n\nImage.displayName = 'Image';\n\nexport default Image;\n\nconst AnimatedImage = styled(animated.img as AnyStyledComponent)`\n    width: auto;\n    height: auto;\n    max-width: 100%;\n    user-select: none;\n    touch-action: ${({ $inline }) => (!$inline ? 'none' : 'pan-y')};\n    ::selection {\n        background: none;\n    }\n`;\n"
  },
  {
    "path": "src/components/ImageStage/components/ImagePager/index.tsx",
    "content": "import { animated, useSprings } from '@react-spring/web';\nimport { useGesture } from 'react-use-gesture';\nimport Image from '../Image';\nimport React, { useEffect, useRef, useState } from 'react';\nimport styled, { AnyStyledComponent } from 'styled-components';\nimport type { ImagesList } from '../../../../types/ImagesList';\n\ntype IImagePager = {\n    /** Index of image in images array that is currently shown */\n    currentIndex: number;\n    /** image stage height */\n    imageStageHeight: number;\n    /** image stage width */\n    imageStageWidth: number;\n    /** Array of image objects to be shown in Lightbox */\n    images: ImagesList;\n    /** Affects Width calculation method, depending on whether the Lightbox is Inline or not */\n    inline: boolean;\n    /** Function that closes the Lightbox */\n    onClose?: () => void;\n    /** Function that can be called to disable dragging in the pager */\n    onNext: () => void;\n    /** True if this image is currently shown in pager, otherwise false */\n    onPrev: () => void;\n    /** A React component that renders inside the image stage, useful for making overlays over the image */\n    renderImageOverlay: () => React.ReactNode;\n    /** Overrides the default behavior of double clicking causing an image zoom to a single click */\n    singleClickToZoom: boolean;\n};\n\n/**\n * Gesture controlled surface that animates prev/next page changes via spring physics.\n */\nconst ImagePager = ({\n    currentIndex,\n    images,\n    imageStageHeight,\n    imageStageWidth,\n    inline,\n    onClose,\n    onNext,\n    onPrev,\n    renderImageOverlay,\n    singleClickToZoom,\n}: IImagePager) => {\n    const firstRender = useRef(true);\n\n    const [disableDrag, setDisableDrag] = useState<boolean>(false);\n    const [pagerHeight, setPagerHeight] = useState<'100%' | number>('100%');\n    const [isDragging, setIsDragging] = useState<boolean>(false);\n\n    //Determine the absolute height of the image pager\n    useEffect(() => {\n        const currPagerHeight = inline\n            ? imageStageHeight\n            : imageStageHeight - 50;\n\n        if (currPagerHeight !== pagerHeight) {\n            setPagerHeight(currPagerHeight);\n        }\n    }, [inline, pagerHeight, imageStageHeight]);\n\n    // Generate page positions based on current index\n    const getPagePositions = React.useCallback(\n        (i: number, down = false, xDelta = 0) => {\n            const x =\n                (i - currentIndex) * imageStageWidth + (down ? xDelta : 0);\n\n            if (i < currentIndex - 1 || i > currentIndex + 1) {\n                return { display: 'none', x };\n            }\n            return { display: 'flex', x };\n        },\n        [currentIndex, imageStageWidth],\n    );\n\n    /**\n     * Animates translateX of all images at the same time\n     *\n     * @see https://www.react-spring.io/docs/hooks/use-springs\n     */\n    const [pagerSprings, springsApi] = useSprings(images.length, (i) =>\n        getPagePositions(i),\n    );\n\n    // Animate page change if currentIndex changes\n    useEffect(() => {\n        // No need to set page position for initial render\n        if (firstRender.current) {\n            firstRender.current = false;\n            return;\n        }\n        // Update page positions after prev/next page state change\n        springsApi.start((i) => getPagePositions(i));\n    }, [currentIndex, getPagePositions, springsApi]);\n\n    /**\n     * Update each Image's visibility and translateX offset during dragging\n     *\n     * @see https://github.com/react-spring/react-use-gesture\n     */\n    const bind = useGesture(\n        {\n            onDrag: ({\n                active,\n                cancel,\n                direction: [xDir],\n                distance,\n                down,\n                movement: [xMovement],\n                tap,\n                touches,\n                velocity,\n            }) => {\n                // Disable drag if Image has been zoomed in to allow for panning\n                if (disableDrag || xMovement === 0 || tap) {\n                    return;\n                }\n                if (!isDragging) {\n                    setIsDragging(true);\n                }\n\n                const isHorizontalDrag = Math.abs(xDir) > 0.7;\n                const draggedFarEnough =\n                    down &&\n                    isHorizontalDrag &&\n                    distance > imageStageWidth / 3.5;\n                const draggedFastEnough =\n                    down && isHorizontalDrag && velocity > 2;\n\n                // Handle next/prev image from valid drag\n                if ((draggedFarEnough || draggedFastEnough) && active) {\n                    const goToIndex = xDir > 0 ? -1 : 1;\n\n                    // Cancel gesture event\n                    cancel();\n\n                    if (goToIndex > 0) {\n                        onNext();\n                    } else if (goToIndex < 0) {\n                        onPrev();\n                    }\n\n                    return;\n                }\n\n                // Don't move pager during two+ finger touch events, i.e. pinch-zoom\n                if (touches > 1) {\n                    cancel();\n                    return;\n                }\n\n                // Update page x-coordinates for single finger/mouse gestures\n                springsApi.start((i) => getPagePositions(i, down, xMovement));\n                return;\n            },\n            onDragEnd: () => {\n                if (isDragging) {\n                    springsApi.start((i) => getPagePositions(i));\n                    // Add small timeout buffer to prevent event handlers from firing in child Images\n                    setTimeout(() => setIsDragging(false), 100);\n                }\n            },\n            onWheel: ({ ctrlKey, direction: [xDir, yDir], velocity }) => {\n                // Disable drag if Image has been zoomed in to allow for panning\n                if (ctrlKey || disableDrag || velocity === 0) {\n                    return;\n                }\n\n                if (!isDragging) {\n                    setIsDragging(true);\n                }\n\n                const draggedFastEnough = velocity > 1.1;\n\n                // Handle next/prev image from valid drag\n                if (draggedFastEnough) {\n                    const goToIndex = xDir + yDir > 0 ? -1 : 1;\n\n                    if (goToIndex > 0) {\n                        onNext();\n                    } else if (goToIndex < 0) {\n                        onPrev();\n                    }\n                }\n            },\n            onWheelEnd: () => {\n                springsApi.start((i) => getPagePositions(i));\n                // Add small timeout buffer to prevent event handlers from firing in child Images\n                setTimeout(() => setIsDragging(false), 100);\n            },\n        },\n        {\n            drag: {\n                filterTaps: true,\n            },\n            wheel: {\n                enabled: !inline,\n            },\n        },\n    );\n\n    return (\n        <ImagePagerContainer>\n            {pagerSprings.map(({ display, x }, i) => (\n                <AnimatedImagePager\n                    $inline={inline}\n                    {...bind()}\n                    className=\"lightbox-image-pager\"\n                    key={i}\n                    onClick={() => {\n                        if (onClose) {\n                            return (\n                                Math.abs(x.get()) < 1 &&\n                                !disableDrag &&\n                                onClose()\n                            );\n                        }\n                    }}\n                    role=\"presentation\"\n                    style={{\n                        display,\n                        transform: x.to(\n                            (xInterp: number) => `translateX(${xInterp}px)`,\n                        ),\n                    }}\n                >\n                    <PagerContentWrapper>\n                        <PagerInnerContentWrapper>\n                            <ImageContainer\n                                $inline={inline}\n                                onClick={(e) => {\n                                    e.stopPropagation();\n                                    e.nativeEvent.stopImmediatePropagation();\n                                }}\n                            >\n                                <Image\n                                    imgProps={images[i]}\n                                    inline={inline}\n                                    isCurrentImage={i === currentIndex}\n                                    pagerHeight={pagerHeight}\n                                    pagerIsDragging={isDragging}\n                                    setDisableDrag={setDisableDrag}\n                                    singleClickToZoom={singleClickToZoom}\n                                />\n                                {renderImageOverlay()}\n                            </ImageContainer>\n                        </PagerInnerContentWrapper>\n                    </PagerContentWrapper>\n                </AnimatedImagePager>\n            ))}\n        </ImagePagerContainer>\n    );\n};\n\nImagePager.displayName = 'ImagePager';\n\nexport default ImagePager;\n\nconst ImagePagerContainer = styled.div`\n    height: 100%;\n    width: 100%;\n`;\n\nconst PagerInnerContentWrapper = styled.div`\n    display: flex;\n    justify-content: center;\n    align-items: center;\n`;\n\nconst PagerContentWrapper = styled.div`\n    width: 100%;\n    display: flex;\n    justify-content: center;\n`;\n\nconst AnimatedImagePager = styled(animated.span as AnyStyledComponent)<{\n    $inline: boolean;\n}>`\n    position: absolute;\n    top: 0px;\n    left: 0px;\n    right: 0px;\n    bottom: 0px;\n    height: 100%;\n    width: 100%;\n    will-change: transform;\n    touch-action: ${({ $inline }) => (!$inline ? 'none' : 'pan-y')};\n    display: flex;\n    flex-direction: column;\n    justify-content: center;\n    align-items: center;\n`;\n\nconst ImageContainer = styled.div<{ $inline: boolean }>`\n    position: relative;\n    touch-action: ${({ $inline }) => (!$inline ? 'none' : 'pan-y')};\n    user-select: none;\n    display: flex;\n    justify-content: center;\n    width: 100%;\n`;\n"
  },
  {
    "path": "src/components/ImageStage/components/SSRImagePager/SSRImagePager.tsx",
    "content": "import type { ImagesList } from '../../../../types/ImagesList';\nimport styled, { css } from 'styled-components';\nimport * as React from 'react';\n\ntype ISSRImagePagerProps = {\n    currentIndex: number;\n    images: ImagesList;\n};\n\nconst SSRImagePager = ({ currentIndex, images }: ISSRImagePagerProps) => {\n    return (\n        <ImagePagerContainer>\n            {images.map(({ alt, src }, i) => {\n                return (\n                    <Image\n                        $isCurrentImage={i === currentIndex}\n                        alt={alt}\n                        key={`${alt}-${src}-${i}`}\n                        src={src}\n                    />\n                );\n            })}\n        </ImagePagerContainer>\n    );\n};\n\nexport default SSRImagePager;\n\nconst ImagePagerContainer = styled.div`\n    width: 100%;\n    height: inherit;\n`;\n\nconst Image = styled.img<{ $isCurrentImage: boolean }>`\n    ${({ $isCurrentImage }) =>\n        !$isCurrentImage &&\n        css`\n            visibility: hidden;\n            display: none;\n        `}\n    height:100%;\n    width: 100%;\n    object-fit: contain;\n`;\n"
  },
  {
    "path": "src/components/ImageStage/index.tsx",
    "content": "import ImagePager from './components/ImagePager';\nimport React from 'react';\nimport styled from 'styled-components';\nimport useRefSize from './utils/useRefSize';\nimport type { ImagesList } from '../../types/ImagesList';\nimport SSRImagePager from './components/SSRImagePager/SSRImagePager';\n\ntype IImageStageProps = {\n    /** classnames are applied to the root ImageStage component */\n    className?: string;\n    /** Index of image in images array that is currently shown */\n    currentIndex: number;\n    /** Array of image objects to be shown in Lightbox */\n    images: ImagesList;\n    /** Affects Width calculation method, depending on whether the Lightbox is Inline or not */\n    inline: boolean;\n    /** Function that closes the Lightbox */\n    onClose?: () => void;\n    /** Function that can be called to disable dragging in the pager */\n    onNext: () => void;\n    /** True if this image is currently shown in pager, otherwise false */\n    onPrev: () => void;\n    /** A React component that renders inside the image stage, useful for making overlays over the image */\n    renderImageOverlay: () => React.ReactNode;\n    /** A React component that is used for next button in image pager */\n    renderNextButton: ({ canNext }: { canNext: boolean }) => React.ReactNode;\n    /** A React component that is used for previous button in image pager */\n    renderPrevButton: ({ canPrev }: { canPrev: boolean }) => React.ReactNode;\n    /** Overrides the default behavior of double clicking causing an image zoom to a single click */\n    singleClickToZoom: boolean;\n};\n\n/**\n * Containing element for ImagePager and prev/next button controls\n */\nconst ImageStage = ({\n    className = '',\n    currentIndex,\n    images,\n    inline,\n    onClose,\n    onNext,\n    onPrev,\n    renderImageOverlay,\n    renderNextButton,\n    renderPrevButton,\n    singleClickToZoom,\n}: IImageStageProps) => {\n    // Extra sanity check that the next/prev image exists before moving to it\n    const canPrev = currentIndex > 0;\n    const canNext = currentIndex + 1 < images.length;\n\n    const onNextImage = canNext ? onNext : () => null;\n    const onPrevImage = canPrev ? onPrev : () => null;\n\n    const [{ height: containerHeight, width: containerWidth }, containerRef] =\n        useRefSize();\n\n    return (\n        <ImageStageContainer\n            className={className}\n            data-testid=\"lightbox-image-stage\"\n            ref={containerRef}\n        >\n            {renderPrevButton({ canPrev })}\n            {containerWidth ? (\n                <ImagePager\n                    currentIndex={currentIndex}\n                    images={images}\n                    imageStageHeight={containerHeight}\n                    imageStageWidth={containerWidth}\n                    inline={inline}\n                    onClose={onClose}\n                    onNext={onNextImage}\n                    onPrev={onPrevImage}\n                    renderImageOverlay={renderImageOverlay}\n                    singleClickToZoom={singleClickToZoom}\n                />\n            ) : inline ? (\n                <SSRImagePager currentIndex={currentIndex} images={images} />\n            ) : null}\n            {renderNextButton({ canNext })}\n        </ImageStageContainer>\n    );\n};\n\nexport default ImageStage;\n\nconst ImageStageContainer = styled.div`\n    position: relative;\n    height: 100%;\n    width: 100%;\n    display: flex;\n    justify-content: center;\n    align-items: center;\n`;\n"
  },
  {
    "path": "src/components/ImageStage/utils/getTranslateOffsetsFromScale.ts",
    "content": "type IGetTranslateOffsetsFromScale = {\n    /** The current [x,y] translate values of image */\n    currentTranslate: [translateX: number, translateY: number];\n    /** The image dom node used as a reference to calculate translate offsets */\n    imageRef: React.RefObject<HTMLImageElement>;\n    /** The amount of change in the new transform scale */\n    pinchDelta: number;\n    /** The current transform scale of image */\n    scale: number;\n    /** The [x,y] coordinates of the zoom origin */\n    touchOrigin: [touchOriginX: number, touchOriginY: number];\n};\n\ntype ITranslateOffsetsReturnType = [translateX: number, translateY: number];\n\n/**\n * Calculates the the translate(x,y) coordinates needed to zoom-in\n * to a point in an image.\n *\n * @returns {array} The next [x,y] translate values to apply to image\n */\nconst getTranslateOffsetsFromScale = ({\n    currentTranslate: [translateX, translateY],\n    imageRef,\n    pinchDelta,\n    scale,\n    touchOrigin: [touchOriginX, touchOriginY],\n}: IGetTranslateOffsetsFromScale): ITranslateOffsetsReturnType => {\n    if (!imageRef?.current) {\n        return [0, 0];\n    }\n\n    const {\n        height: imageHeight,\n        left: imageTopLeftX,\n        top: imageTopLeftY,\n        width: imageWidth,\n    } = imageRef.current?.getBoundingClientRect();\n\n    // Get the (x,y) touch position relative to image origin at the current scale\n    const imageCoordX = (touchOriginX - imageTopLeftX - imageWidth / 2) / scale;\n    const imageCoordY =\n        (touchOriginY - imageTopLeftY - imageHeight / 2) / scale;\n\n    // Calculate translateX/Y offset at the next scale to zoom to touch position\n    const newTranslateX = -imageCoordX * pinchDelta + translateX;\n    const newTranslateY = -imageCoordY * pinchDelta + translateY;\n\n    return [newTranslateX, newTranslateY];\n};\n\nexport default getTranslateOffsetsFromScale;\n"
  },
  {
    "path": "src/components/ImageStage/utils/imageIsOutOfBounds.ts",
    "content": "/**\n * Determines if the provided image is within the viewport\n *\n * @returns True if image needs to be resized to fit viewport, otherwise false\n */\nconst imageIsOutOfBounds = (\n    imageRef: React.RefObject<HTMLImageElement>,\n): boolean => {\n    // If no ref is provided, return false\n    if (!imageRef.current) {\n        return false;\n    }\n\n    const {\n        bottom: bottomRightY,\n        left: topLeftX,\n        right: bottomRightX,\n        top: topLeftY,\n    } = imageRef.current?.getBoundingClientRect();\n    const { innerHeight: windowHeight, innerWidth: windowWidth } = window;\n\n    if (\n        topLeftX > windowWidth * (1 / 2) ||\n        topLeftY > windowHeight * (1 / 2) ||\n        bottomRightX < windowWidth * (1 / 2) ||\n        bottomRightY < windowHeight * (1 / 2)\n    )\n        return true;\n\n    return false;\n};\n\nexport default imageIsOutOfBounds;\n"
  },
  {
    "path": "src/components/ImageStage/utils/index.ts",
    "content": "import getTranslateOffsetsFromScale from './getTranslateOffsetsFromScale';\nimport imageIsOutOfBounds from './imageIsOutOfBounds';\nimport useDoubleClick from './useDoubleClick';\nimport useWindowSize from './useRefSize';\n\nexport {\n    getTranslateOffsetsFromScale,\n    imageIsOutOfBounds,\n    useDoubleClick,\n    useWindowSize,\n};\n"
  },
  {
    "path": "src/components/ImageStage/utils/useDoubleClick.tsx",
    "content": "import React, { useEffect } from 'react';\n\ntype IUseDoubleClickProps = {\n    /** Set to false to disable onDoubleClick/onSingleClick  */\n    enabled?: boolean;\n    /** The amount of time (in milliseconds) to wait before differentiating a single from a double click */\n    latency?: number;\n    /** A callback function for double click events */\n    onDoubleClick?: (event: MouseEvent) => void;\n    /** A callback function for single click events */\n    onSingleClick?: (event: MouseEvent) => void;\n    /** Dom node to watch for double clicks */\n    ref: React.RefObject<HTMLElement>;\n};\n\n/**\n * React Hook that returns the current window size\n * and report updates from the 'resize' window event\n */\nconst useDoubleClick = ({\n    enabled = true,\n    latency = 300,\n    onDoubleClick = () => null,\n    onSingleClick = () => null,\n    ref,\n}: IUseDoubleClickProps) => {\n    useEffect(() => {\n        const clickRef = ref.current;\n        let clickCount = 0;\n        let timer: ReturnType<typeof setTimeout>;\n\n        const handleClick = (e: MouseEvent) => {\n            if (enabled) {\n                clickCount += 1;\n\n                timer = setTimeout(() => {\n                    if (clickCount === 1) onSingleClick(e);\n                    else if (clickCount === 2) onDoubleClick(e);\n\n                    clickCount = 0;\n                }, latency);\n            }\n        };\n\n        // Add event listener for click events\n        clickRef?.addEventListener('click', handleClick);\n\n        // Remove event listener\n        return () => {\n            clickRef?.removeEventListener('click', handleClick);\n\n            if (timer) {\n                clearTimeout(timer);\n            }\n        };\n    });\n};\n\nexport default useDoubleClick;\n"
  },
  {
    "path": "src/components/ImageStage/utils/useRefSize.tsx",
    "content": "import { useCallback, useEffect, useRef, useState } from 'react';\n\ntype RefSize = {\n    height: number;\n    width: number;\n};\n\ntype Node = HTMLDivElement | null;\n\ntype IUseRefSize = [refSize: RefSize, elementRef: (node: any) => void | null];\n\n/**\n * React Hook that returns the current ref size\n * and report updates from the 'resize' ref event\n *\n * @returns {RefSize} An object containing the ref width and height\n * @returns {elementRef} A callback ref to be used on the container being measured\n */\nconst useRefSize = (): IUseRefSize => {\n    const ref = useRef<HTMLDivElement>(null);\n\n    const [node, setNode] = useState<Node>(null);\n    const [refSize, setRefSize] = useState<RefSize>({\n        height: ref.current?.clientHeight || 0,\n        width: ref.current?.clientWidth || 0,\n    });\n\n    const elementRef = useCallback((node: Node) => {\n        if (node !== null) {\n            setNode(node);\n\n            setRefSize({\n                height: node.clientHeight,\n                width: node.clientWidth,\n            });\n        }\n    }, []);\n\n    useEffect(() => {\n        const handleResize = () => {\n            if (node) {\n                const height = node.clientHeight;\n                const width = node.clientWidth;\n                if (height !== refSize.height || width !== refSize.width) {\n                    setRefSize({\n                        height,\n                        width,\n                    });\n                }\n            }\n        };\n\n        window.addEventListener('resize', handleResize);\n        window.addEventListener('orientationchange', handleResize);\n\n        return () => {\n            window.removeEventListener('resize', handleResize);\n            window.removeEventListener('orientationchange', handleResize);\n        };\n    }, [node, refSize.height, refSize.width]);\n\n    return [refSize, elementRef];\n};\n\nexport default useRefSize;\n"
  },
  {
    "path": "src/components/PageContainer/index.tsx",
    "content": "import React from 'react';\nimport { useTransition, animated, config } from '@react-spring/web';\nimport styled, { AnyStyledComponent } from 'styled-components';\n\ntype IPageContainerProps = {\n    /** All child components of Lightbox */\n    children: React.ReactNode[];\n    /** Classes are applied to the root lightbox component */\n    className: string;\n    /** Flag that dictates if the lightbox is open or closed */\n    isOpen: boolean;\n    /** React-Spring useTransition config for page open/close animation */\n    pageTransitionConfig: any;\n    /** Inline styles are applied to the root lightbox component */\n    style: React.CSSProperties;\n};\n\n/**\n * Animates the lightbox as it opens/closes\n */\nconst PageContainer = ({\n    children,\n    className,\n    isOpen,\n    pageTransitionConfig,\n    style,\n}: IPageContainerProps) => {\n    const defaultTransition = {\n        config: { ...config.default, friction: 32, mass: 1, tension: 320 },\n        enter: { opacity: 1, transform: 'scale(1)' },\n        from: { opacity: 0, transform: 'scale(0.75)' },\n        leave: { opacity: 0, transform: 'scale(0.75)' },\n    };\n\n    const transitions = useTransition(isOpen, {\n        ...defaultTransition,\n        ...pageTransitionConfig,\n    });\n\n    return (\n        <>\n            {transitions(\n                (animatedStyles, item) =>\n                    item && (\n                        <AnimatedPageContainer\n                            className={`lightbox-container${\n                                className ? ` ${className}` : ''\n                            }`}\n                            data-testid=\"lightbox-container\"\n                            style={{ ...animatedStyles, ...style }}\n                        >\n                            {children}\n                        </AnimatedPageContainer>\n                    ),\n            )}\n        </>\n    );\n};\n\nexport default PageContainer;\n\nconst AnimatedPageContainer = styled(animated.div as AnyStyledComponent)`\n    display: flex;\n    flex-direction: column;\n    position: fixed;\n    z-index: 400;\n    top: 0;\n    bottom: 0;\n    left: 0;\n    right: 0;\n`;\n"
  },
  {
    "path": "src/components/index.tsx",
    "content": "import ImageStage from './ImageStage';\nimport PageContainer from './PageContainer';\nimport CreatePortal from './CreatePortal';\n\nexport { ImageStage, PageContainer, CreatePortal };\n"
  },
  {
    "path": "src/index.tsx",
    "content": "import React, { useEffect } from 'react';\nimport { ImageStage, PageContainer, CreatePortal } from './components';\nimport type { ImagesList } from './types/ImagesList';\n\nexport type ImagesListType = ImagesList;\n\ntype ILightboxProps = {\n    /** classnames are applied to the root lightbox component */\n    className?: string;\n    /** Index of image in images array that is currently shown */\n    currentIndex: number;\n    /** Array of images to be shown in Lightbox, each image object may contain any valid 'img' attribute with the exceptions of 'draggable', 'onClick', 'onDragStart' and 'ref' */\n    images: ImagesList;\n    /** Determines whether the Lightbox returns just an Inline carousel (ImageStage) */\n    inline?: boolean;\n    /** Flag that dictates if the lightbox is open or closed */\n    isOpen: boolean;\n    /** Function that closes the Lightbox */\n    onClose?: () => void;\n    /** Function that changes currentIndex to next image in images */\n    onNext: () => void;\n    /** Function that changes currentIndex to previous image in images */\n    onPrev: () => void;\n    /** React-Spring useTransition config for page open/close animation */\n    pageTransitionConfig?: any;\n    /** A React component that renders below the image pager */\n    renderFooter?: () => React.ReactNode;\n    /** A React component that renders above the image pager */\n    renderHeader?: () => React.ReactNode;\n    /** A React component that renders inside the image stage, useful for making overlays over the image */\n    renderImageOverlay?: () => React.ReactNode;\n    /** A React component that is used for next button in image pager */\n    renderNextButton?: ({ canNext }: { canNext: boolean }) => React.ReactNode;\n    /** A React component that is used for previous button in image pager */\n    renderPrevButton?: ({ canPrev }: { canPrev: boolean }) => React.ReactNode;\n    /** Overrides the default behavior of double clicking causing an image zoom to a single click */\n    singleClickToZoom?: boolean;\n    /** Inline styles that are applied to the root lightbox component */\n    style?: React.CSSProperties;\n};\n\n/**\n * Gesture controlled lightbox that interpolates animations with spring physics.\n *\n * Demos and docs:\n * @see https://timellenberger.com/libraries/react-spring-lightbox\n *\n * GitHub repo:\n * @see https://github.com/tim-soft/react-spring-lightbox\n *\n * Built with:\n * @see https://github.com/react-spring/react-use-gesture\n * @see https://github.com/react-spring/react-spring\n * @see https://github.com/styled-components/styled-components\n */\nconst Lightbox = ({\n    className = '',\n    currentIndex,\n    images = [],\n    inline = false,\n    isOpen,\n    onClose,\n    onNext,\n    onPrev,\n    pageTransitionConfig = null,\n    renderFooter = () => null,\n    renderHeader = () => null,\n    renderImageOverlay = () => null,\n    renderNextButton = () => null,\n    renderPrevButton = () => null,\n    singleClickToZoom = false,\n    style = {},\n}: ILightboxProps) => {\n    // Handle event listeners for keyboard\n    useEffect(() => {\n        /**\n         * Prevent keyboard from controlling background page\n         * when lightbox is open\n         */\n        const preventBackgroundScroll = (e: KeyboardEvent) => {\n            const keysToIgnore = [\n                'ArrowUp',\n                'ArrowDown',\n                'End',\n                'Home',\n                'PageUp',\n                'PageDown',\n            ];\n\n            if (isOpen && keysToIgnore.includes(e.key)) e.preventDefault();\n        };\n\n        /**\n         * Navigate images with arrow keys, close on Esc key\n         */\n        const handleKeyboardInput = (e: KeyboardEvent) => {\n            if (isOpen) {\n                switch (e.key) {\n                    case 'ArrowLeft':\n                        onPrev();\n                        break;\n                    case 'ArrowRight':\n                        onNext();\n                        break;\n                    case 'Escape':\n                        onClose && onClose();\n                        break;\n                    default:\n                        e.preventDefault();\n                        break;\n                }\n            }\n        };\n\n        document.addEventListener('keyup', handleKeyboardInput);\n        document.addEventListener('keydown', preventBackgroundScroll);\n\n        return () => {\n            document.removeEventListener('keyup', handleKeyboardInput);\n            document.removeEventListener('keydown', preventBackgroundScroll);\n        };\n    });\n\n    const imageStage = (\n        <ImageStage\n            currentIndex={currentIndex}\n            images={images}\n            inline={inline}\n            onClose={onClose}\n            onNext={onNext}\n            onPrev={onPrev}\n            renderImageOverlay={renderImageOverlay}\n            renderNextButton={renderNextButton}\n            renderPrevButton={renderPrevButton}\n            singleClickToZoom={singleClickToZoom}\n        />\n    );\n\n    if (inline) {\n        return imageStage;\n    }\n\n    return (\n        <CreatePortal>\n            <PageContainer\n                className={className}\n                isOpen={isOpen}\n                pageTransitionConfig={pageTransitionConfig}\n                style={style}\n            >\n                {renderHeader()}\n                {imageStage}\n                {renderFooter()}\n            </PageContainer>\n        </CreatePortal>\n    );\n};\n\nexport default Lightbox;\n"
  },
  {
    "path": "src/types/ImagesList.ts",
    "content": "export type ImagesListItem = Omit<\n    React.HTMLProps<HTMLImageElement>,\n    'draggable' | 'onClick' | 'onDragStart' | 'ref'\n> & { alt: string; loading?: 'auto' | 'eager' | 'lazy'; src: string };\n\nexport type ImagesList = ImagesListItem[];\n"
  },
  {
    "path": "tsconfig.buildtypes.json",
    "content": "/* eslint-disable sort-keys */\n{\n    \"extends\": \"./tsconfig.json\",\n    \"include\": [\"src/index.tsx\"],\n    \"exclude\": [\"node_modules\", \"dist\", \"example\", \"src/__tests__\"]\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "/* eslint-disable sort-keys */\n{\n    \"compilerOptions\": {\n        \"outDir\": \"dist\",\n        \"module\": \"esnext\",\n        \"lib\": [\"dom\", \"esnext\"],\n        \"moduleResolution\": \"node\",\n        \"jsx\": \"react\",\n        \"sourceMap\": true,\n        \"declaration\": true,\n        \"esModuleInterop\": true,\n        \"noImplicitReturns\": true,\n        \"noImplicitThis\": true,\n        \"noImplicitAny\": true,\n        \"strictNullChecks\": true,\n        \"noUnusedLocals\": true,\n        \"noUnusedParameters\": true,\n        \"allowSyntheticDefaultImports\": true,\n        \"rootDir\": \"src\",\n        \"target\": \"ES5\",\n        \"isolatedModules\": true,\n        \"skipLibCheck\": true\n    },\n    \"include\": [\"src\"],\n    \"exclude\": [\"node_modules\", \"dist\", \"example\", \"src/__tests__\"],\n    \"types\": [\"node\", \"jest\", \"@testing-library/jest-dom\"]\n}\n"
  }
]