[
  {
    "path": ".babelrc",
    "content": "{\n  \"presets\": [\n    [\n      \"@babel/preset-env\",\n      {\n        \"modules\": false\n      }\n    ]\n  ],\n  \"env\": {\n    \"test\": {\n      \"plugins\": [\n        \"istanbul\"\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": ".browserslistrc",
    "content": "defaults\nie >= 9\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\nindent_size = 2\nindent_style = space\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n"
  },
  {
    "path": ".eslintignore",
    "content": "*.local*\n.husky\ncoverage\ndist\ndocs\nnode_modules\n"
  },
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n  root: true,\n  extends: 'airbnb-base',\n  env: {\n    browser: true,\n  },\n  plugins: [\n    'import',\n  ],\n  rules: {\n    'import/no-extraneous-dependencies': 'off',\n    'no-param-reassign': 'off',\n    'no-restricted-properties': 'off',\n    'valid-jsdoc': ['error', {\n      requireReturn: false,\n    }],\n  },\n  overrides: [\n    {\n      files: 'test/**/*.spec.js',\n      env: {\n        mocha: true,\n      },\n      globals: {\n        Viewer: true,\n        expect: true,\n      },\n      rules: {\n        'no-new': 'off',\n        'no-unused-expressions': 'off',\n      },\n    },\n  ],\n};\n"
  },
  {
    "path": ".gitattributes",
    "content": "# Auto detect text files and perform LF normalization\n* text=auto\n"
  },
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "content": "# Contributor Code of Conduct\n\nAs contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.\n\nWe are committed to making participation in this project a harassment-free experience for everyone, regardless of the level of experience, gender, gender identity, and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.\n\nExamples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.\n\nProject maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.\n\nThis Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.0.0, available at https://www.contributor-covenant.org/version/1/0/0/code-of-conduct.html\n"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "# Contributing to Viewer.js\n\n> Based on [Angular's contributing guidelines](https://github.com/angular/angular/blob/master/CONTRIBUTING.md).\n\nWe would love for you to contribute to Viewer.js and help make it even better than it is today! As a contributor, here are the guidelines we would like you to follow:\n\n- [Contributing to Viewer.js](#contributing-to-viewerjs)\n  - [Code of Conduct](#code-of-conduct)\n  - [Question or Problem](#question-or-problem)\n  - [Issues and Bugs](#issues-and-bugs)\n  - [Feature Requests](#feature-requests)\n  - [Submission Guidelines](#submission-guidelines)\n    - [Submitting an Issue](#submitting-an-issue)\n    - [Submitting a Pull Request (PR)](#submitting-a-pull-request-pr)\n      - [After your pull request is merged](#after-your-pull-request-is-merged)\n  - [Coding Rules](#coding-rules)\n  - [Commit Message Guidelines](#commit-message-guidelines)\n    - [Commit Message Format](#commit-message-format)\n    - [Revert](#revert)\n    - [Type](#type)\n    - [Scope](#scope)\n    - [Subject](#subject)\n    - [Body](#body)\n    - [Footer](#footer)\n\n## Code of Conduct\n\nHelp us keep Viewer.js open and inclusive. Please read and follow our [Code of Conduct](CODE_OF_CONDUCT.md).\n\n## Question or Problem\n\nDo not open issues for general support questions as we want to keep GitHub issues for bug reports and feature requests. You've got much better chances of getting your question answered on [Stack Overflow](https://stackoverflow.com/questions/tagged/viewerjs) where the questions should be tagged with tag `viewerjs`.\n\nStack Overflow is a much better place to ask questions since:\n\n- There are thousands of people willing to help on Stack Overflow.\n- Questions and answers stay available for public viewing so your question/answer might help someone else.\n- Stack Overflow's voting system assures that the best answers are prominently visible.\n\nTo save you and our time, we will systematically close all issues that are requests for general support and redirect people to Stack Overflow.\n\n## Issues and Bugs\n\nIf you find a bug in the source code, you can help us by [submitting an issue](#submitting-an-issue) to our [GitHub Repository](https://github.com/fengyuanchen/viewerjs). Even better, you can [submit a Pull Request](#submitting-a-pull-request-pr) with a fix.\n\n## Feature Requests\n\nYou can *request* a new feature by [submitting an issue](#submitting-an-issue) to our [GitHub Repository](https://github.com/fengyuanchen/viewerjs). If you would like to *implement* a new feature, please submit an issue with a proposal for your work first, to be sure that we can use it.\n\nPlease consider what kind of change it is:\n\n- For a **Major Feature**, first, open an issue and outline your proposal so that it can be discussed. This will also allow us to better coordinate our efforts, prevent duplication of work, and help you to craft the change so that it is successfully accepted into the project.\n- **Small Features** can be crafted and directly [submitted as a Pull Request](#submitting-a-pull-request-pr).\n\n## Submission Guidelines\n\n### Submitting an Issue\n\nBefore you submit an issue, please search the [issue tracker](https://github.com/fengyuanchen/viewerjs/issues), which may be an issue for your problem already exists and the discussion might inform you of workarounds readily available.\n\nWe want to fix all the issues as soon as possible, but before fixing a bug we need to reproduce and confirm it. To reproduce bugs, we will systematically ask you to provide a minimal reproduction scenario using [CodePen](https://codepen.io/pen). Having a live, reproducible scenario gives us a wealth of important information without going back & forth to you with additional questions like:\n\n- version of Viewer.js used\n- 3rd-party libraries and their versions\n- and most importantly - a use-case that fails\n\nA minimal reproduction scenario using [CodePen](https://codepen.io/pen) allows us to quickly confirm a bug (or point out a coding problem) as well as confirm that we are fixing the right problem. If [CodePen](https://codepen.io/pen) is not a suitable way to demonstrate the problem (for example for issues related to our npm packaging), please create a standalone git repository demonstrating the problem.\n\nWe will be insisting on a minimal reproduction scenario to save maintainers time and ultimately be able to fix more bugs. Interestingly, from our experience users often find coding problems themselves while preparing a minimal reproduction scenario. We understand that sometimes it might be hard to extract essentials bits of code from a larger code-base but we need to isolate the problem before we can fix it.\n\nUnfortunately, we are not able to investigate/fix bugs without a minimal reproduction scenario, so if we don't hear back from you we are going to close an issue that doesn't have enough info to be reproduced.\n\nYou can file new issues by filling out our [new issue form](https://github.com/fengyuanchen/viewerjs/issues/new).\n\n### Submitting a Pull Request (PR)\n\nBefore you submit your Pull Request (PR) consider the following guidelines:\n\n1. Search [GitHub](https://github.com/fengyuanchen/viewerjs/pulls) for an open or closed PR that relates to your submission. You don't want to duplicate effort.\n1. Fork the **fengyuanchen/viewerjs** repo.\n1. Make your changes in a new git branch:\n\n    ```shell\n    git checkout -b my-fix-branch main\n    ```\n\n1. Create your patch, **including appropriate test cases**.\n1. Follow our [Coding Rules](#coding-rules).\n1. Run the full Viewer.js test suite, and ensure that all tests pass.\n1. Commit your changes using a descriptive commit message that follows our [Commit Message Guidelines](#commit-message-guidelines). Adherence to these guidelines is necessary because release notes are automatically generated from these messages.\n\n    ```shell\n    git commit -a\n    ```\n\n    Note: the optional commit `-a` command-line option will automatically \"add\" and \"rm\" edited files.\n1. Push your branch to GitHub:\n\n    ```shell\n    git push origin my-fix-branch\n    ```\n\n1. In GitHub, send a pull request to `viewerjs:main`.\n1. If we suggest changes then:\n    - Make the required updates.\n    - Re-run the Viewer.js test suites to ensure tests are still passing.\n    - Rebase your branch and force push to your GitHub repository (this will update your Pull Request):\n\n    ```shell\n    git rebase main -i\n    git push -f\n    ```\n\nThat's it! Thank you for your contribution!\n\n#### After your pull request is merged\n\nAfter your pull request is merged, you can safely delete your branch and pull the changes from the main (upstream) repository:\n\n1. Delete the remote branch on GitHub either through the GitHub web UI or your local shell as follows:\n\n    ```shell\n    git push origin --delete my-fix-branch\n    ```\n\n1. Check out the main branch:\n\n    ```shell\n    git checkout main -f\n    ```\n\n1. Delete the local branch:\n\n    ```shell\n    git branch -D my-fix-branch\n    ```\n\n1. Update your main with the latest upstream version:\n\n    ```shell\n    git pull --ff upstream main\n    ```\n\n## Coding Rules\n\nTo ensure consistency throughout the source code, keep these rules in mind as you are working:\n\n- All features or bug fixes **must be tested** by one or more specs (unit-tests).\n- All public API methods **must be documented**.\n- We follow [Airbnb's JavaScript Style Guide](https://github.com/airbnb/javascript).\n\n## Commit Message Guidelines\n\n### Commit Message Format\n\nA commit message consists of a **header**, **body** and **footer**. The header has a **type**, **scope** and **subject**:\n\n```\n<type>(<scope>): <subject>\n<BLANK LINE>\n<body>\n<BLANK LINE>\n<footer>\n```\n\nThe **header** is mandatory and the **scope** of the header is optional.\n\nAny line of the commit message cannot be longer than 100 characters! This allows the message to be easier to read on GitHub as well as in various git tools.\n\nThe footer should contain a [closing reference to an issue](https://help.github.com/articles/closing-issues-via-commit-messages/) if any.\n\nHere are some [samples](https://github.com/fengyuanchen/viewerjs/commits/main).\n\n### Revert\n\nIf the commit reverts a previous commit, it should begin with `revert: `, followed by the header of the reverted commit. In the body, it should say: `This reverts commit <hash>.`, where the hash is the SHA of the commit being reverted.\n\n### Type\n\nMust be one of the following:\n\n- **build**: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)\n- **ci**: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)\n- **docs**: Documentation only changes\n- **feat**: A new feature\n- **fix**: A bug fix\n- **perf**: A code change that improves performance\n- **refactor**: A code change that neither fixes a bug nor adds a feature\n- **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)\n- **test**: Adding missing tests or correcting existing tests\n\n### Scope\n\nThe scope could be anything specifying the place of the commit change. For example `show`, `view`, `play`, etc...\n\n### Subject\n\nThe subject contains a succinct description of the change:\n\n- Use the imperative, present tense: \"change\" not \"changed\" nor \"changes\".\n- Don't capitalize the first letter.\n- No dot (.) at the end.\n\n### Body\n\nJust as in the **subject**, use the imperative, present tense: \"change\" not \"changed\" nor \"changes\". The body should include the motivation for the change and contrast this with previous behavior.\n\n### Footer\n\nThe footer should contain any information about **Breaking Changes** and is also the place to reference GitHub issues that this commit **Closes**.\n\n**Breaking Changes** should start with the word `BREAKING CHANGE:` with a space or two newlines. The rest of the commit message is then used for this.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: \"\\U0001F41E Bug report\"\ndescription: Create a report to help us improve\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        **Before You Start...**\n\n        This form is only for submitting bug reports. If you have a usage question\n        or are unsure if this is really a bug, make sure to:\n\n        - Read the [docs](https://fengyuanchen.github.io/viewerjs)\n        - Ask on [GitHub Discussions](https://github.com/fengyuanchen/viewerjs/discussions)\n        - Look for / ask questions on [Stack Overflow](https://stackoverflow.com/questions/ask?tags=viewerjs)\n\n        Also try to search for your issue - it may have already been answered or even fixed in the development branch.\n        However, if you find that an old, closed issue still persists in the latest version,\n        you should open a new issue using the form below instead of commenting on the old issue.\n  - type: input\n    id: version\n    attributes:\n      label: Viewer version\n    validations:\n      required: true\n  - type: input\n    id: reproduction-link\n    attributes:\n      label: Link to minimal reproduction\n      description: |\n        The easiest way to provide a reproduction is by showing the bug in the [Playground](https://fengyuanchen.github.io/viewerjs).\n        If it cannot be reproduced in the playground and requires a proper build setup, try [StackBlitz](https://stackblitz.com/?starters=frontend).\n        If neither of these are suitable, you can always provide a GitHub repository.\n\n        The reproduction should be **minimal** - i.e. it should contain only the bare minimum amount of code needed\n        to show the bug.\n\n        Please do not just fill in a random link. The issue will be closed if no valid reproduction is provided.\n      placeholder: Reproduction Link\n    validations:\n      required: true\n  - type: textarea\n    id: steps-to-reproduce\n    attributes:\n      label: Steps to reproduce\n      description: |\n        What do we need to do after opening your repro in order to make the bug happen? Clear and concise reproduction instructions are important for us to be able to triage your issue in a timely manner. Note that you can use [Markdown](https://guides.github.com/features/mastering-markdown/) to format lists and code.\n      placeholder: Steps to reproduce\n    validations:\n      required: true\n  - type: textarea\n    id: expected\n    attributes:\n      label: What is expected?\n    validations:\n      required: true\n  - type: textarea\n    id: actually-happening\n    attributes:\n      label: What is actually happening?\n    validations:\n      required: true\n  - type: textarea\n    id: system-info\n    attributes:\n      label: System Info\n      description: Output of `npx envinfo --system --npmPackages viewerjs --binaries --browsers`\n      render: shell\n      placeholder: System, Binaries, Browsers\n  - type: textarea\n    id: additional-comments\n    attributes:\n      label: Any additional comments?\n      description: e.g. some background/context of how you ran into this bug.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: Questions & Discussions\n    url: https://github.com/fengyuanchen/viewerjs/discussions\n    about: Use GitHub discussions for message-board style questions and discussions.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!-- Please don't delete this template -->\n\n<!-- PULL REQUEST TEMPLATE -->\n<!-- (Update \"[ ]\" to \"[x]\" to check a box) -->\n\n**Summary**\n\n**What kind of change does this PR introduce?** (check at least one)\n\n- [ ] Bugfix\n- [ ] Feature\n- [ ] Code style update\n- [ ] Refactor\n- [ ] Docs\n- [ ] Build-related changes\n- [ ] Other, please describe:\n\nIf changing the UI of the default theme, please provide the **before/after** screenshot:\n\n**Does this PR introduce a breaking change?** (check one)\n\n- [ ] Yes\n- [ ] No\n\nIf yes, please describe the impact and migration path for existing applications:\n\n**The PR fulfills these requirements:**\n\n- [ ] When resolving a specific issue, it's referenced in the PR's title (e.g. `fix #xxx[,#xxx]`, where \"xxx\" is the issue number)\n\nYou have tested in the following browsers: (Providing a detailed version will be better.)\n\n- [ ] Chrome\n- [ ] Firefox\n- [ ] Safari\n- [ ] Edge\n- [ ] IE\n\nIf adding a **new feature**, the PR's description includes:\n\n- [ ] A convincing reason for adding this feature\n- [ ] Related documents have been updated\n- [ ] Related tests have been updated\n\nTo avoid wasting your time, it's best to open a **feature request issue** first and wait for approval before working on it.\n\n**Other information:**\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 18\n      - run: npm install\n      - run: npm run lint\n      - run: npm run build\n      - run: npm test\n      - uses: codecov/codecov-action@v5\n        env:\n          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "*.local*\n*.log\n*.map\n.DS_Store\ncoverage\nnode_modules\n"
  },
  {
    "path": ".husky/commit-msg",
    "content": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\nnpx commitlint --edit $1\n"
  },
  {
    "path": ".husky/pre-commit",
    "content": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\nnpx lint-staged\n"
  },
  {
    "path": ".stylelintignore",
    "content": "*.local*\n.husky\ncoverage\ndist\ndocs/css/viewer.css\nnode_modules\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## 1.11.7 (Nov 24, 2024)\n\n- Use SVG icons for better visual effects (#637).\n\n## 1.11.6 (Sep 17, 2023)\n\n- Fix an issue where some CSS styles were incompatible with old browsers (#611).\n\n## 1.11.5 (Aug 26, 2023)\n\n- Fix the issue of title blinking when opening the same image again (#609).\n\n## 1.11.4 (Jul 23, 2023)\n\n- Fix the incorrect RegExp for Safari browser detection (#606).\n\n## 1.11.3 (Mar 5, 2023)\n\n- Not actually moving when the `offsetX/Y` is `0` (#585, #588).\n\n## 1.11.2 (Jan 1, 2023)\n\n- Do not close the viewer when dragging the image on the backdrop (#577).\n\n## 1.11.1 (Nov 6, 2022)\n\n- Add missing type definitions for `initialCoverage` option and `zoom` and `zoomTo` methods (#571).\n\n## 1.11.0 (Oct 16, 2022)\n\n- Add a new option: `initialCoverage` (#314, #526).\n- Don't load images in the list when hide the navbar (#451).\n- Support for providing pivot pointer coordinates to `zoom` and `zoomTo` methods (#202).\n- Don't override the padding right of the body when the width of the scrollbar is zero (#197).\n- Add keyboard support to playing view (#90).\n\n## 1.10.5 (Apr 5, 2022)\n\n- Continue to initialize even if some images fail to load in inline mode.\n- Avoid conflicts with nested modals (#540).\n\n## 1.10.4 (Feb 13, 2022)\n\n- Use legacy color function notation for better compatibility (#529).\n\n## 1.10.3 (Feb 2, 2022)\n\n- Get the pageX/Y properties from the original event when it is an emulated double click in touch devices (#527).\n- Improve the zoom experience on the touch screen (#510).\n\n## 1.10.2 (Oct 22, 2021)\n\n- Increase title height for avoiding truncation (#509).\n- Fix a `TypeError` when there are not any images (#504).\n- Remove loading class on image load error (#502).\n\n## 1.10.1 (Aug 1, 2021)\n\n- Check if the active item exists to avoid TypeError (#491).\n- Compute nav item gutter dynamically (#487).\n\n## 1.10.0 (Jun 12, 2021)\n\n- Enhance `fullscreen` option and `play` method to support [`FullscreenOptions`](https://developer.mozilla.org/en-US/docs/Web/API/FullscreenOptions) (#482, #483).\n- Refactor the `toggle` method for toggling image between current/natural ratio (#477).\n- Improve the `toggle` method for zooming by double-click (#422).\n\n## 1.9.2 (May 29, 2021)\n\n- Avoid conflicts with other modals (#474).\n- Allow `ToolbarOption` to be undefined (#473).\n\n## 1.9.1 (May 22, 2021)\n\n- Fix the missing declaration for the `focus` option (#470).\n\n## 1.9.0 (Dec 6, 2020)\n\n- Add 6 new events: `move`, `moved`, `rotate`, `rotated`, `scale`, and `scaled`.\n- Add an example for limiting the moving range with the help of the `move` event.\n\n## 1.8.0 (Nov 8, 2020)\n\n- Add a new option: `focus` .\n- Add ARIA attributes for better accessibility.\n- Add the `Tab` and `Enter` keys to the keyboard support.\n- Check if the `pointer` object is defined or not for better compatibility (#421).\n\n## 1.7.1 (Sep 29, 2020)\n\n- Fix an issue in the `types/index.d.ts` file (#414).\n\n## 1.7.0 (Sep 26, 2020)\n\n- Add 2 new events: `play` and `stop` (#411).\n- Let the `viewed`, `zoomed`, and `hidden` events not be canceled.\n- Improve the TypeScript declarations in the `types/index.d.ts` file.\n\n## 1.6.2 (Aug 30, 2020)\n\n- Improve the `hide` method for some edge cases (#407).\n- Improve the wheel zoom behavior (#396).\n- Fix wrong usage about `this` in ES6+ (#395).\n\n## 1.6.1 (Jun 14, 2020)\n\n- Improve image filtering.\n\n## 1.6.0 (Jun 6, 2020)\n\n- Add a new options: `inheritedAttributes`.\n- Remove unnecessary `padding-right: 0px` from the `body` element when close the viewer modal (#394).\n- Reset the `padding-right` of the `body` element when resizing (#379).\n- Improve the `hide` method for unexpected calling (#367).\n- Ignore images without the `src` attribute (#326).\n\n## 1.5.0 (Nov 23, 2019)\n\n- Force reflow element in a new way to avoid side-effect (#343).\n- Add a new option: `slideOnTouch` (#340).\n- Detect if the queried image is existing when updating the image list (#333).\n\n## 1.4.0 (Oct 26, 2019)\n\n- Add two new options: `zoomOnTouch` and `zoomOnWheel` (#329).\n\n## 1.3.7 (Oct 2, 2019)\n\n- Improve event type determining for iOS 13+ (#321).\n- Ignore invalid `element` parameter on the class utility functions (#317).\n- Do nothing if the `index` value is invalid when calling the `view` method (#312).\n\n## 1.3.6 (Jul 4, 2019)\n\n- Avoid escaping URLs (#298, #301).\n- Avoid using the `innerHTML` property for security (#269).\n\n## 1.3.5 (Jun 29, 2019)\n\n- Improve the escaping function to avoid escaping HTML entities repeatedly.\n\n## 1.3.4 (Jun 1, 2019)\n\n- Decode image name when it comes from URL (#282).\n- Fix the missing fade-out transition when hiding the viewer (#275).\n- Escape all strings that use in HTML for better security (#269).\n\n## 1.3.3 (Apr 6, 2019)\n\n- Fix unexpected modal exiting behavior when the mouse is pressed (#255).\n- Abort image downloading when cancel viewing for better performance.\n\n## 1.3.2 (Jan 24, 2019)\n\n- Fix `Document not active` error when calling the `exit` method.\n- Improve wheel event listening for better performance (#102).\n\n## 1.3.1 (Dec 9, 2018)\n\n- Ignore pointer events when not the primary button was pressed (#221).\n- Emulate click (single tap) and double click (double tap) in touch devices to support backdrop and image zooming (#210).\n\n## 1.3.0 (Oct 25, 2018)\n\n- Fix wrong click action when target image is ignored by the `filter` option (#211)\n- Add a new option: `className` (#209).\n\n## 1.2.1 (Oct 20, 2018)\n\n- Improve viewer instance storage to avoid side-effect.\n- Fix parameter error of `Object.assign` in iOS devices.\n\n## 1.2.0 (Jul 15, 2018)\n\n- Enhance the `title` option to support to customize title content (#54, #185).\n- Add 2 new options: `toggleOnDblclick` (#173) and `initialViewIndex` (#183).\n\n## 1.1.0 (May 27, 2018)\n\n- Make the touch zooming smoother (#162).\n- Add 2 new events: `zoom` and `zoomed` (#144).\n\n## 1.0.1 (May 20, 2018)\n\n- Add a namespace to data attribute names (from `data-*` to `data-viewer-*`) to avoid side-effect.\n- Make sure the image data is a non-null object to avoid unexpected errors.\n- Fix broken zoom feature in iOS browsers (#167).\n\n## 1.0.0 (Apr 1, 2018)\n\n- Add in browser checking to support import in Node.js.\n- Cancel update when there are no images when calling the `update` method.\n\n## 1.0.0-rc.1 (Mar 13, 2018)\n\n- Fix the wrong image switching behavior in iOS browsers.\n- Fix a `TypeError` in strict mode (#149).\n- Fix type definitions issue of the `show` and `hide` methods.\n\n## 1.0.0-rc (Mar 10, 2018)\n\n- Add a new option: `loading`.\n- Add type definitions file for TypeScript.\n- Enhance the `show`, `hide`, and `play` methods.\n- Change the default value of the `loop` option from `false` to `true`.\n\n## 1.0.0-beta.2 (Feb 13, 2018)\n\n- Add a new option: `container`.\n- Recover the missing default value of the `interval` option (#133).\n\n## 1.0.0-beta.1 (Dec 23, 2017)\n\n- Add a new option: `backdrop`.\n\n## 1.0.0-beta (Dec 12, 2017)\n\n- Add `style` field to `package.json`.\n- Fall back to `document.documentElement` if `document.body` is not existing (#120).\n- Fix the issue of NodeList deconstructing (#118).\n\n## 0.10.0 (Nov 5, 2017)\n\n- Add a new option: `loop`.\n- Enhance toolbar customization.\n\n## 0.9.0 (Nov 4, 2017)\n\n- Add a new option: `filter`.\n- Support to customize the layout of the toolbar (#79).\n- Enhance the `prev` and `next` methods (#47).\n- Disallow to show again if it had shown.\n\n## 0.8.0 (Oct 8, 2017)\n\n- Refactor - separate constants, simplify utilities, and so on.\n- Stop play after exited fullscreen.\n- Improve JSDoc.\n\n## 0.7.2 (Aug 19, 2017)\n\n- Fixed multiple active items in the navbar (#75).\n- Ignore the mouse down event when the viewer is hiding (#70).\n\n## 0.7.1 (May 14, 2017)\n\n- Support to use the viewer in a modal (#39).\n\n## 0.7.0 (Apr 30, 2017)\n\n- Changed the `main` field value from `dist/viewer.js` (UMD) to `dist/viewer.common.js` (CommonJS).\n- Added `module` and `browser` fields to `package.json`.\n- Fixed an issue of touch zoom.\n\n## 0.6.2 (Mar 4, 2017)\n\n- Fixed the issue of touch and move problem (#63).\n\n## 0.6.1 (Feb 18, 2017)\n\n- Prevented the default behavior of drag action (#63).\n\n## 0.6.0 (Jan 24, 2017)\n\n- Ported JavaScript code to ECMAScript 6.\n- Ported CSS code to CSSNext.\n\n## 0.5.1 (Jan 2, 2017)\n\n- Improve event handler for Pointer Events.\n\n## 0.5.0 (July 22, 2016)\n\n- Improve modal opening and closing.\n- Remove the `build` event.\n- Rename `built` event to `ready`.\n- Fixed a bug of `data-*` attributes setting and getting (#33).\n\n## 0.4.0 (Mar 20, 2016)\n\n- Added some properties to `event.detail` of the \"view\" and \"viewed\" events.\n\n## 0.3.3 (Mar 19, 2016)\n\n- Fix the issue of hiding the wrong element in the \"view\" method (#19).\n\n## 0.3.2 (Mar 11, 2016)\n\n- Fix the error of the parameters on the `url` option when it is a function.\n\n## 0.3.1 (Feb 2, 2016)\n\n- Added tests.\n- Ignored the invalid class name.\n- Re-render image only when viewed.\n\n## 0.3.0 (Jan 21, 2016)\n\n- Add more available values to the \"title\", \"toolbar\" and \"navbar\" options.\n- Support to toggle the visibility of title, toolbar, and navbar between different screen widths.\n- Exit fullscreen when stop playing.\n- Fixed title not generated bug.\n\n## 0.2.0 (Jan 1, 2016)\n\n- Added \"update\" method for update image dynamically.\n- Hides title and toolbar on small screen (width < 768px).\n\n## 0.1.1 (Dec 28, 2015)\n\n- Supports to zoom from event triggering point.\n- Optimized \"toggle\" method.\n- Fixed a bug about the index of the viewing image.\n\n## 0.1.0 (Dec 24, 2015)\n\n- Supports 2 modes: \"modal\" (default), \"inline\"\n- Supports 30 options: \"inline\", \"button\", \"navbar\", \"title\", \"toolbar\", \"tooltip\", \"movable\", \"zoomable\", \"rotatable\", \"scalable\", \"transition\", \"fullscreen\", \"keyboard\", \"interval\", \"minWidth\", \"minHeight\", \"zoomRatio\", \"minZoomRatio\", \"maxZoomRatio\", \"zIndex\", \"zIndexInline\", \"url\", \"build\", \"built\", \"show\", \"shown\", \"hide\", \"hidden\", \"view\", \"viewed\"\n- Supports 22 methods: \"show\", \"hide\", \"view\", \"prev\", \"next\", \"move\", \"moveTo\", \"zoom\", \"zoomTo\", \"rotate\", \"rotateTo\", \"scale\", \"scaleX\", \"scaleY\", \"play\", \"stop\", \"full\", \"exit\", \"tooltip\", \"toggle\", \"reset\", \"destroy\"\n- Supports 8 events: \"build\", \"built\", \"show\", \"shown\", \"hide\", \"hidden\", \"view\", \"viewed\"\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright 2015-present Chen Fengyuan\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\nall copies 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\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Viewer.js\n\n[![Downloads](https://img.shields.io/npm/dm/viewerjs.svg)](https://www.npmjs.com/package/viewerjs) [![Version](https://img.shields.io/npm/v/viewerjs.svg)](https://www.npmjs.com/package/viewerjs) [![Gzip Size](https://img.shields.io/bundlephobia/minzip/viewerjs.svg)](https://unpkg.com/viewerjs/dist/viewer.common.js)\n\n> JavaScript image viewer.\n\n- [Website](https://fengyuanchen.github.io/viewerjs)\n- [jquery-viewer](https://github.com/fengyuanchen/jquery-viewer) - A jQuery plugin wrapper for Viewer.js.\n\n## Table of contents\n\n- [Features](#features)\n- [Main Files](#main-files)\n- [Getting started](#getting-started)\n- [Keyboard support](#keyboard-support)\n- [Options](#options)\n- [Methods](#methods)\n- [Events](#events)\n- [No conflict](#no-conflict)\n- [Browser support](#browser-support)\n- [Contributing](#contributing)\n- [Versioning](#versioning)\n- [License](#license)\n\n## Features\n\n- Supports 53 [options](#options)\n- Supports 23 [methods](#methods)\n- Supports 17 [events](#events)\n- Supports modal and inline modes\n- Supports touch\n- Supports move\n- Supports zoom\n- Supports rotation\n- Supports scale (flip)\n- Supports keyboard\n- Cross-browser support\n\n## Main files\n\n```text\ndist/\n├── viewer.css\n├── viewer.min.css   (compressed)\n├── viewer.js        (UMD)\n├── viewer.min.js    (UMD, compressed)\n├── viewer.common.js (CommonJS, default)\n└── viewer.esm.js    (ES Module)\n```\n\n## Getting started\n\n### Installation\n\n```shell\nnpm install viewerjs\n```\n\nIn browser:\n\n```html\n<link  href=\"/path/to/viewer.css\" rel=\"stylesheet\">\n<script src=\"/path/to/viewer.js\"></script>\n```\n\nThe [cdnjs](https://github.com/cdnjs/cdnjs) provides CDN support for Viewer.js's CSS and JavaScript. You can find the links [here](https://cdnjs.com/libraries/viewerjs).\n\n### Usage\n\n#### Syntax\n\n```js\nnew Viewer(element[, options])\n```\n\n- **element**\n  - Type: `HTMLElement`\n  - The target image or container of images for viewing.\n\n- **options** (optional)\n  - Type: `Object`\n  - The options for viewing. Check out the available [options](#options).\n\n#### Example\n\n```html\n<!-- a block container is required -->\n<div>\n  <img id=\"image\" src=\"picture.jpg\" alt=\"Picture\">\n</div>\n\n<div>\n  <ul id=\"images\">\n    <li><img src=\"picture-1.jpg\" alt=\"Picture 1\"></li>\n    <li><img src=\"picture-2.jpg\" alt=\"Picture 2\"></li>\n    <li><img src=\"picture-3.jpg\" alt=\"Picture 3\"></li>\n  </ul>\n</div>\n```\n\n```js\n// You should import the CSS file.\n// import 'viewerjs/dist/viewer.css';\nimport Viewer from 'viewerjs';\n\n// View an image.\nconst viewer = new Viewer(document.getElementById('image'), {\n  inline: true,\n  viewed() {\n    viewer.zoomTo(1);\n  },\n});\n// Then, show the image by clicking it, or call `viewer.show()`.\n\n// View a list of images.\n// Note: All images within the container will be found by calling `element.querySelectorAll('img')`.\nconst gallery = new Viewer(document.getElementById('images'));\n// Then, show one image by click it, or call `gallery.show()`.\n```\n\n## Keyboard support\n\n> Only available in modal mode.\n\n- `Esc`: Exit full screen or close the viewer or exit modal mode or stop play.\n- `Space`: Stop play.\n- `Tab`: Switch the focus state on the buttons in the viewer.\n- `Enter`: Trigger the click event handler on the button.\n- `←`: View the previous image.\n- `→`: View the next image.\n- `↑`: Zoom in the image.\n- `↓`: Zoom out the image.\n- `Ctrl + 0`: Zoom out to initial size.\n- `Ctrl + 1`: Zoom in to natural size.\n\n[⬆ back to top](#table-of-contents)\n\n## Options\n\nYou may set viewer options with `new Viewer(image, options)`.\nIf you want to change the global default options, You may use `Viewer.setDefaults(options)`.\n\n### backdrop\n\n- Type: `Boolean` or `String`\n- Default: `true`\n\nEnable the modal backdrop, specify `static` for the backdrop that will not close the modal on click.\n\n### button\n\n- Type: `Boolean`\n- Default: `true`\n\nShow the button on the top-right of the viewer.\n\n### navbar\n\n- Type: `Boolean` or `Number`\n- Default: `true`\n- Options:\n  - `0` or `false`: hide the navbar\n  - `1` or `true`: show the navbar\n  - `2`: show the navbar only when the screen width is greater than 768 pixels\n  - `3`: show the navbar only when the screen width is greater than 992 pixels\n  - `4`: show the navbar only when the screen width is greater than 1200 pixels\n\nSpecify the visibility of the navbar.\n\n### title\n\n- Type: `Boolean` or `Number` or `Function` or `Array`\n- Default: `true`\n- Options:\n  - `0` or `false`: hide the title\n  - `1` or `true` or `Function` or `Array`: show the title\n  - `2`: show the title only when the screen width is greater than 768 pixels\n  - `3`: show the title only when the screen width is greater than 992 pixels\n  - `4`: show the title only when the screen width is greater than 1200 pixels\n  - `Function`: customize the title content\n  - `[Number, Function]`: the first element indicate the visibility, the second element customize the title content\n\nSpecify the visibility and the content of the title.\n\n> The name comes from the `alt` attribute of an image element or the image name parsed from its URL.\n\nFor example, `title: 4` equals to:\n\n```js\nnew Viewer(image, {\n  title: [4, (image, imageData) => `${image.alt} (${imageData.naturalWidth} × ${imageData.naturalHeight})`]\n});\n```\n\n### toolbar\n\n- Type: `Boolean` or `Number` or `Object`\n- Default: `true`\n- Options:\n  - `0` or `false`: hide the toolbar.\n  - `1` or `true`: show the toolbar.\n  - `2`: show the toolbar only when the screen width is greater than 768 pixels.\n  - `3`: show the toolbar only when the screen width is greater than 992 pixels.\n  - `4`: show the toolbar only when the screen width is greater than 1200 pixels.\n  - `{ key: Boolean | Number }`: show or hide the toolbar.\n  - `{ key: String }`: customize the size of the button.\n  - `{ key: Function }`: customize the click handler of the button.\n  - `{ key: { show: Boolean | Number, size: String, click: Function }`: customize each property of the button.\n  - Available built-in keys: \"zoomIn\", \"zoomOut\", \"oneToOne\", \"reset\", \"prev\", \"play\", \"next\", \"rotateLeft\", \"rotateRight\", \"flipHorizontal\", \"flipVertical\".\n  - Available built-in sizes: \"small\", \"medium\" (default) and \"large\".\n\nSpecify the visibility and layout of the toolbar its buttons.\n\nFor example, `toolbar: 4` equals to:\n\n```js\nnew Viewer(image, {\n  toolbar: {\n    zoomIn: 4,\n    zoomOut: 4,\n    oneToOne: 4,\n    reset: 4,\n    prev: 4,\n    play: {\n      show: 4,\n      size: 'large',\n    },\n    next: 4,\n    rotateLeft: 4,\n    rotateRight: 4,\n    flipHorizontal: 4,\n    flipVertical: 4,\n  },\n});\n```\n\n> see more for [custom toolbar](docs/examples/custom-toolbar.html).\n\n### className\n\n- Type: `String`\n- Default: `''`\n\nCustom class name(s) to add to the viewer's root element.\n\n### container\n\n- Type: `Element` or `String`\n- Default: `'body'`\n- An element or a valid selector for [Document.querySelector](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)\n\nContainer to place the viewer in the modal mode.\n\n> Only available when the `inline` option is set to `false`.\n\n### filter\n\n- Type: `Function`\n- Default: `null`\n\nFilter the images for viewing (should return `true` if the image is viewable, return `false` to ignore the image).\n\nFor example:\n\n```js\nnew Viewer(image, {\n  filter(image) {\n    return image.complete;\n  },\n});\n```\n\n> Note that images without the `src` attribute set will be ignored by default.\n\n### fullscreen\n\n- Type: `Boolean` or [`FullscreenOptions`](https://developer.mozilla.org/en-US/docs/Web/API/FullscreenOptions)\n- Default: `true`\n\nEnable to request full screen when play.\n\n> Requires the browser supports [Fullscreen API](https://caniuse.com/fullscreen).\n\n### inheritedAttributes\n\n- Type: `Array`\n- Default: `['crossOrigin', 'decoding', 'isMap', 'loading', 'referrerPolicy', 'sizes', 'srcset', 'useMap']`\n\nDefine the extra attributes to inherit from the original image.\n\n> Note that the basic attributes `src` and `alt` will always inherit from the original image.\n\n### initialCoverage\n\n- Type: `Number`\n- Default: `0.9`\n\nDefine the initial coverage of the viewing image. It must a positive number between 0 (0%) and 1 (100%).\n\n### initialViewIndex\n\n- Type: `Number`\n- Default: `0`\n\nDefine the initial index of the image for viewing.\n\n> Also used as the default parameter value of the `view` method.\n\n### inline\n\n- Type: `Boolean`\n- Default: `false`\n\nEnable inline mode.\n\n### interval\n\n- Type: `Number`\n- Default: `5000`\n\nThe amount of time to delay between automatically cycling an image when playing.\n\n### keyboard\n\n- Type: `Boolean`\n- Default: `true`\n\nEnable keyboard support.\n\n### focus\n\n- Type: `Boolean`\n- Default: `true`\n\nFocus the active item in the navbar when initialized.\n\n> Requires the `keyboard` option set to `true`.\n\n### loading\n\n- Type: `Boolean`\n- Default: `true`\n\nIndicate if showing a loading spinner when loading the image or not.\n\n### loop\n\n- Type: `Boolean`\n- Default: `true`\n\nIndicate if enabling loop viewing or not.\n\n> If the current image is the last one, then the next one to view is the first one, and vice versa.\n\n### minWidth\n\n- Type: `Number`\n- Default: 200\n\nDefine the minimum width of the viewer.\n\n> Only available in inline mode (set the `inline` option to `true`).\n\n### minHeight\n\n- Type: `Number`\n- Default: 100\n\nDefine the minimum height of the viewer.\n\n> Only available in inline mode (set the `inline` option to `true`).\n\n### movable\n\n- Type: `Boolean`\n- Default: `true`\n\nEnable to move the image.\n\n### rotatable\n\n- Type: `Boolean`\n- Default: `true`\n\nEnable to rotate the image.\n\n### scalable\n\n- Type: `Boolean`\n- Default: `true`\n\nEnable to scale the image.\n\n### zoomable\n\n- Type: `Boolean`\n- Default: `true`\n\nEnable to zoom the image.\n\n### zoomOnTouch\n\n- Type: `Boolean`\n- Default: `true`\n\nEnable to zoom the current image by dragging on the touch screen.\n\n### zoomOnWheel\n\n- Type: `Boolean`\n- Default: `true`\n\nEnable to zoom the image by wheeling the mouse.\n\n### slideOnTouch\n\n- Type: `Boolean`\n- Default: `true`\n\nEnable to slide to the next or previous image by swiping on the touch screen.\n\n### toggleOnDblclick\n\n- Type: `Boolean`\n- Default: `true`\n\nIndicate if toggle the image size between its natural size and initial size when double click on the image or not.\n\nIn other words, call the [`toggle`](#toggle) method automatically when double click on the image.\n\n> Requires [`dblclick`](https://developer.mozilla.org/en-US/docs/Web/Events/dblclick) event support.\n\n### tooltip\n\n- Type: `Boolean`\n- Default: `true`\n\nShow the tooltip with image ratio (percentage) when zooming in or zooming out.\n\n### transition\n\n- Type: `Boolean`\n- Default: `true`\n\nEnable CSS3 Transition for some special elements.\n\n### zIndex\n\n- Type: `Number`\n- Default: `2015`\n\nDefine the CSS `z-index` value of the viewer in modal mode.\n\n### zIndexInline\n\n- Type: `Number`\n- Default: `0`\n\nDefine the CSS `z-index` value of the viewer in inline mode.\n\n### zoomRatio\n\n- Type: `Number`\n- Default: `0.1`\n\nDefine the ratio when zooming the image by wheeling the mouse.\n\n### minZoomRatio\n\n- Type: `Number`\n- Default: `0.01`\n\nDefine the min ratio of the image when zooming out.\n\n### maxZoomRatio\n\n- Type: `Number`\n- Default: `100`\n\nDefine the max ratio of the image when zooming in.\n\n### url\n\n- Type: `String` or `Function`\n- Default: `'src'`\n\nDefine where to get the original image URL for viewing.\n\n> If it is a string, it should be one of the attributes of each image element.\n> If it is a function, it should return a valid image URL.\n\nFor example:\n\n```html\n<img src=\"picture.jpg?size=160\">\n```\n\n```js\nnew Viewer(image, {\n  url(image) {\n    return image.src.replace('?size=160', '');\n  },\n});\n```\n\n### ready\n\n- Type: `Function`\n- Default: `null`\n\nShortcut of the `ready` event.\n\n### show\n\n- Type: `Function`\n- Default: `null`\n\nShortcut of the `show` event.\n\n### shown\n\n- Type: `Function`\n- Default: `null`\n\nShortcut of the `shown` event.\n\n### hide\n\n- Type: `Function`\n- Default: `null`\n\nShortcut of the `hide` event.\n\n### hidden\n\n- Type: `Function`\n- Default: `null`\n\nShortcut of the `hidden` event.\n\n### view\n\n- Type: `Function`\n- Default: `null`\n\nShortcut of the `view` event.\n\n### viewed\n\n- Type: `Function`\n- Default: `null`\n\nShortcut of the `viewed` event.\n\n### move\n\n- Type: `Function`\n- Default: `null`\n\nShortcut of the `move` event.\n\n### moved\n\n- Type: `Function`\n- Default: `null`\n\nShortcut of the `moved` event.\n\n### rotate\n\n- Type: `Function`\n- Default: `null`\n\nShortcut of the `rotate` event.\n\n### rotated\n\n- Type: `Function`\n- Default: `null`\n\nShortcut of the `rotated` event.\n\n### scale\n\n- Type: `Function`\n- Default: `null`\n\nShortcut of the `scale` event.\n\n### scaled\n\n- Type: `Function`\n- Default: `null`\n\nShortcut of the `scaled` event.\n\n### zoom\n\n- Type: `Function`\n- Default: `null`\n\nShortcut of the `zoom` event.\n\n### zoomed\n\n- Type: `Function`\n- Default: `null`\n\nShortcut of the `zoomed` event.\n\n### play\n\n- Type: `Function`\n- Default: `null`\n\nShortcut of the `play` event.\n\n### stop\n\n- Type: `Function`\n- Default: `null`\n\nShortcut of the `stop` event.\n\n[⬆ back to top](#table-of-contents)\n\n## Methods\n\nAll methods allow chain composition.\n\nAs there are some **asynchronous** processes when start the viewer, you should call a method only when it is available, see the following **lifecycle**:\n\n```js\nnew Viewer(image, {\n  ready() {\n    // 2 methods are available here: \"show\" and \"destroy\".\n  },\n  shown() {\n    // 9 methods are available here: \"hide\", \"view\", \"prev\", \"next\", \"play\", \"stop\", \"full\", \"exit\" and \"destroy\".\n  },\n  viewed() {\n    // All methods are available here except \"show\".\n    this.viewer.zoomTo(1).rotateTo(180);\n  }\n});\n```\n\n### show([immediate])\n\n- **immediate** (optional):\n  - Type: `Boolean`\n  - Default: `false`\n  - Indicates if show the viewer immediately or not.\n\nShow the viewer.\n\n> Only available in modal mode.\n\n### hide([immediate])\n\n- **immediate** (optional):\n  - Type: `Boolean`\n  - Default: `false`\n  - Indicates if hide the viewer immediately or not.\n\nHide the viewer.\n\n> Only available in modal mode.\n\n### view([index])\n\n- **index** (optional):\n  - Type: `Number`\n  - Default: `0` (inherits from the `initialViewIndex` option)\n  - The index of the image for viewing\n\nView one of the images with the image index. If the viewer is hidden, it will be shown first.\n\n```js\nviewer.view(1); // View the second image\n```\n\n### prev([loop=false])\n\n- **loop** (optional):\n  - Type: `Boolean`\n  - Default: `false`\n  - Indicate if turn to view the last one when it is the first one at present.\n\nView the previous image.\n\n### next([loop=false])\n\n- **loop** (optional):\n  - Type: `Boolean`\n  - Default: `false`\n  - Indicate if turn to view the first one  when it is the last one at present.\n\nView the next image.\n\n### move(x[, y = x])\n\n- **x**:\n  - Type: `Number`\n  - The moving distance in the horizontal direction.\n\n- **y** (optional):\n  - Type: `Number`\n  - The moving distance in the vertical direction.\n  - If not present, its default value is `x`\n\nMove the image with relative offsets.\n\n```js\nviewer.move(1);\nviewer.move(-1, 0); // Move left\nviewer.move(1, 0);  // Move right\nviewer.move(0, -1); // Move up\nviewer.move(0, 1);  // Move down\n```\n\n### moveTo(x[, y = x])\n\n- **x**:\n  - Type: `Number`\n  - The new position in the horizontal direction.\n\n- **y** (optional):\n  - Type: `Number`\n  - The new position in the vertical direction.\n  - If not present, its default value is `x`.\n\nMove the image to an absolute point.\n\n### rotate(degree)\n\n- **degree**:\n  - Type: `Number`\n  - Rotate right: requires a positive number (degree > 0)\n  - Rotate left: requires a negative number (degree < 0)\n\nRotate the image with a relative degree.\n\n```js\nviewer.rotate(90);\nviewer.rotate(-90);\n```\n\n### rotateTo(degree)\n\n- **degree**:\n  - Type: `Number`\n\nRotate the image to an absolute degree.\n\n```js\nviewer.rotateTo(0); // Reset to zero degree\nviewer.rotateTo(360); // Rotate a full round\n```\n\n### scale(scaleX[, scaleY])\n\n- **scaleX**:\n  - Type: `Number`\n  - Default: `1`\n  - The scaling factor to apply on the abscissa of the image\n  - When equal to `1` it does nothing.\n\n- **scaleY** (optional):\n  - Type: `Number`\n  - The scaling factor to apply on the ordinate of the image\n  - If not present, its default value is `scaleX`.\n\nScale the image.\n\n```js\nviewer.scale(-1); // Flip both horizontal and vertical\nviewer.scale(-1, 1); // Flip horizontal\nviewer.scale(1, -1); // Flip vertical\n```\n\n### scaleX(scaleX)\n\n- **scaleX**:\n  - Type: `Number`\n  - Default: `1`\n  - The scaling factor to apply on the abscissa of the image\n  - When equal to `1` it does nothing\n\nScale the abscissa of the image.\n\n```js\nviewer.scaleX(-1); // Flip horizontal\n```\n\n### scaleY(scaleY)\n\n- **scaleY**:\n  - Type: `Number`\n  - Default: `1`\n  - The scaling factor to apply on the ordinate of the image\n  - When equal to `1` it does nothing\n\nScale the ordinate of the image.\n\n```js\nviewer.scaleY(-1); // Flip vertical\n```\n\n### zoom(ratio[, showTooltip[, pivot]])\n\n- **ratio**:\n  - Type: `Number`\n  - Zoom in: requires a positive number (ratio > 0)\n  - Zoom out: requires a negative number (ratio < 0)\n\n- **showTooltip** (optional):\n  - Type: `Boolean`\n  - Default: `false`\n  - Indicates whether to show the tooltip.\n\n- **pivot** (optional):\n  - Type: `Object`\n  - Default: `null`\n  - Schema: `{ x: Number, y: Number }`\n  - The pivot point coordinate for zooming.\n\nZoom the image with a relative ratio\n\n```js\nviewer.zoom(0.1);\nviewer.zoom(-0.1);\n```\n\n### zoomTo(ratio[, showTooltip[, pivot]])\n\n- **ratio**:\n  - Type: `Number`\n  - Requires a positive number (ratio > 0)\n\n- **showTooltip** (optional):\n  - Type: `Boolean`\n  - Default: `false`\n  - Indicates whether to show the tooltip.\n\n- **pivot** (optional):\n  - Type: `Object`\n  - Default: `null`\n  - Schema: `{ x: Number, y: Number }`\n  - The pivot point coordinate for zooming.\n\nZoom the image to an absolute ratio.\n\n```js\nviewer.zoomTo(0); // Zoom to zero size (0%)\nviewer.zoomTo(1); // Zoom to natural size (100%)\n\n// Zoom to 50% from the center of the window.\nviewer.zoomTo(.5, {\n  x: window.innerWidth / 2,\n  y: viewer.innerHeight / 2,\n});\n```\n\n### play([fullscreen])\n\n- **fullscreen** (optional):\n  - Type: `Boolean` or [`FullscreenOptions`](https://developer.mozilla.org/en-US/docs/Web/API/FullscreenOptions)\n  - Default: `false`\n  - Indicate if request fullscreen or not.\n\nPlay the images.\n\n### stop()\n\nStop play.\n\n### full()\n\nEnter the modal mode.\n\n> Only available in inline mode.\n\n### exit()\n\nExit the modal mode.\n\n> Only available in inline mode.\n\n### tooltip()\n\nShow the current ratio of the image by percentage.\n\n> Requires the `tooltip` option set to `true`.\n\n### toggle()\n\nToggle the image size between its current size and natural size.\n\n> Used by the [`toggleOnDblclick`](#toggleOnDblclick) option.\n\n### reset()\n\nReset the image to its initial state.\n\n### update()\n\nUpdate the viewer instance when the source images changed (added, removed, or sorted).\n\n> If you load images dynamically (with XMLHTTPRequest), you can use this method to add the new images to the viewer instance.\n\n### destroy()\n\nDestroy the viewer and remove the instance.\n\n[⬆ back to top](#table-of-contents)\n\n## Events\n\nAll events can access the viewer instance with `this.viewer` in its handler.\n\n> Be careful to use these events with other components which have the same event names, e.g.: [Bootstrap](https://getbootstrap.com/)'s modal.\n\n```js\nlet viewer;\n\nimage.addEventListener('viewed', function () {\n  console.log(this.viewer === viewer);\n  // > true\n});\n\nviewer = new Viewer(image);\n```\n\n### ready\n\n- **event.bubbles**: `true`\n- **event.cancelable**: `true`\n- **event.detail**: `null`\n\nThis event fires when a viewer instance is ready for viewing.\n\n> In modal mode, this event will not be triggered until you click on one of the images.\n\n### show\n\n- **event.bubbles**: `true`\n- **event.cancelable**: `true`\n- **event.detail**: `null`\n\nThis event fires when the viewer modal starts to show.\n\n> Only available in modal mode.\n\n### shown\n\n- **event.bubbles**: `true`\n- **event.cancelable**: `true`\n- **event.detail**: `null`\n\nThis event fires when the viewer modal has shown.\n\n> Only available in modal mode.\n\n### hide\n\n- **event.bubbles**: `true`\n- **event.cancelable**: `true`\n- **event.detail**: `null`\n\nThis event fires when the viewer modal starts to hide.\n\n> Only available in modal mode.\n\n### hidden\n\n- **event.bubbles**: `true`\n- **event.cancelable**: `false`\n- **event.detail**: `null`\n\nThis event fires when the viewer modal has hidden.\n\n> Only available in modal mode.\n\n### view\n\n- **event.bubbles**: `true`\n- **event.cancelable**: `true`\n- **event.detail.index**:\n  - Type: `Number`\n  - The index of the original image.\n- **event.detail.image**:\n  - Type: `HTMLImageElement`\n  - The current image (a clone of the original image).\n- **event.detail.originalImage**:\n  - Type: `HTMLImageElement`\n  - The original image.\n\nThis event fires when a viewer starts to show (view) an image.\n\n### viewed\n\n- **event.bubbles**: `true`\n- **event.cancelable**: `false`\n- **event.detail**: the same as the `view` event.\n\nThis event fires when a viewer has shown (viewed) an image.\n\n### move\n\n- **event.bubbles**: `true`\n- **event.cancelable**: `true`\n- **event.detail.x**:\n  - Type: `Number`\n  - The new position in the horizontal direction.\n- **event.detail.y**:\n  - Type: `Number`\n  - The new position in the vertical direction.\n- **event.detail.oldX**:\n  - Type: `Number`\n  - The old position in the horizontal direction.\n- **event.detail.oldY**:\n  - Type: `Number`\n  - The old position in the vertical direction.\n- **event.detail.originalEvent**:\n  - Type: `Event` or `null`\n  - Options: `pointermove`, `touchmove`, and `mousemove`.\n\nThis event fires when a viewer starts to move an image.\n\n### moved\n\n- **event.bubbles**: `true`\n- **event.cancelable**: `false`\n- **event.detail**: the same as the `move` event.\n\nThis event fires when a viewer has moved an image.\n\n### rotate\n\n- **event.bubbles**: `true`\n- **event.cancelable**: `true`\n- **event.detail.degree**:\n  - Type: `Number`\n  - The new rotation degrees.\n- **event.detail.oldDegree**:\n  - Type: `Number`\n  - The old rotation degrees.\n\nThis event fires when a viewer starts to rotate an image.\n\n### rotated\n\n- **event.bubbles**: `true`\n- **event.cancelable**: `false`\n- **event.detail**: the same as the `rotate` event.\n\nThis event fires when a viewer has rotated an image.\n\n### scale\n\n- **event.bubbles**: `true`\n- **event.cancelable**: `true`\n- **event.detail.scaleX**:\n  - Type: `Number`\n  - The new scaling factor in the horizontal direction.\n- **event.detail.scaleY**:\n  - Type: `Number`\n  - The new scaling factor in the vertical direction.\n- **event.detail.oldScaleX**:\n  - Type: `Number`\n  - The old scaling factor in the horizontal direction.\n- **event.detail.oldScaleY**:\n  - Type: `Number`\n  - The old scaling factor in the vertical direction.\n\nThis event fires when a viewer starts to scale an image.\n\n### scaled\n\n- **event.bubbles**: `true`\n- **event.cancelable**: `false`\n- **event.detail**: the same as the `scale` event.\n\nThis event fires when a viewer has scaled an image.\n\n### zoom\n\n- **event.bubbles**: `true`\n- **event.cancelable**: `true`\n- **event.detail.ratio**:\n  - Type: `Number`\n  - The new (next) ratio of the image (`imageData.width / imageData.naturalWidth`).\n- **event.detail.oldRatio**:\n  - Type: `Number`\n  - The old (current) ratio of the image.\n- **event.detail.originalEvent**:\n  - Type: `Event` or `null`\n  - Options: `wheel`, `pointermove`, `touchmove`, and `mousemove`.\n\nThis event fires when a viewer starts to zoom (in or out) an image.\n\n### zoomed\n\n- **event.bubbles**: `true`\n- **event.cancelable**: `false`\n- **event.detail**: the same as the `zoom` event.\n\nThis event fires when a viewer has zoomed (in or out) an image.\n\n### play\n\n- **event.bubbles**: `true`\n- **event.cancelable**: `true`\n- **event.detail**: `null`\n\nThis event fires when the viewer starts to play.\n\n> You can abort the playing process by calling `event.preventDefault()`.\n\n### stop\n\n- **event.bubbles**: `true`\n- **event.cancelable**: `true`\n- **event.detail**: `null`\n\nThis event fires when the viewer starts to stop.\n\n> You can abort the stopping process by calling `event.preventDefault()`.\n\n[⬆ back to top](#table-of-contents)\n\n## No conflict\n\nIf you have to use another viewer with the same namespace, call the `Viewer.noConflict` static method to revert to it.\n\n```html\n<script src=\"other-viewer.js\"></script>\n<script src=\"viewer.js\"></script>\n<script>\n  Viewer.noConflict();\n  // Code that uses other `Viewer` can follow here.\n</script>\n```\n\n## Browser support\n\n- Chrome (latest)\n- Firefox (latest)\n- Safari (latest)\n- Opera (latest)\n- Edge (latest)\n- Internet Explorer 9+\n\n## Contributing\n\nPlease read through our [contributing guidelines](.github/CONTRIBUTING.md).\n\n## Versioning\n\nMaintained under the [Semantic Versioning guidelines](https://semver.org/).\n\n## License\n\n[MIT](https://opensource.org/licenses/MIT) © [Chen Fengyuan](https://chenfengyuan.com/)\n\n[⬆ back to top](#table-of-contents)\n"
  },
  {
    "path": "commitlint.config.js",
    "content": "module.exports = {\n  extends: [\n    '@commitlint/config-conventional',\n  ],\n};\n"
  },
  {
    "path": "dist/viewer.common.js",
    "content": "/*!\n * Viewer.js v1.11.7\n * https://fengyuanchen.github.io/viewerjs\n *\n * Copyright 2015-present Chen Fengyuan\n * Released under the MIT license\n *\n * Date: 2024-11-24T04:32:19.116Z\n */\n\n'use strict';\n\nfunction _classCallCheck(a, n) {\n  if (!(a instanceof n)) throw new TypeError(\"Cannot call a class as a function\");\n}\nfunction _defineProperties(e, r) {\n  for (var t = 0; t < r.length; t++) {\n    var o = r[t];\n    o.enumerable = o.enumerable || !1, o.configurable = !0, \"value\" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o);\n  }\n}\nfunction _createClass(e, r, t) {\n  return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, \"prototype\", {\n    writable: !1\n  }), e;\n}\nfunction _defineProperty(e, r, t) {\n  return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {\n    value: t,\n    enumerable: !0,\n    configurable: !0,\n    writable: !0\n  }) : e[r] = t, e;\n}\nfunction ownKeys(e, r) {\n  var t = Object.keys(e);\n  if (Object.getOwnPropertySymbols) {\n    var o = Object.getOwnPropertySymbols(e);\n    r && (o = o.filter(function (r) {\n      return Object.getOwnPropertyDescriptor(e, r).enumerable;\n    })), t.push.apply(t, o);\n  }\n  return t;\n}\nfunction _objectSpread2(e) {\n  for (var r = 1; r < arguments.length; r++) {\n    var t = null != arguments[r] ? arguments[r] : {};\n    r % 2 ? ownKeys(Object(t), !0).forEach(function (r) {\n      _defineProperty(e, r, t[r]);\n    }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {\n      Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));\n    });\n  }\n  return e;\n}\nfunction _toPrimitive(t, r) {\n  if (\"object\" != typeof t || !t) return t;\n  var e = t[Symbol.toPrimitive];\n  if (void 0 !== e) {\n    var i = e.call(t, r || \"default\");\n    if (\"object\" != typeof i) return i;\n    throw new TypeError(\"@@toPrimitive must return a primitive value.\");\n  }\n  return (\"string\" === r ? String : Number)(t);\n}\nfunction _toPropertyKey(t) {\n  var i = _toPrimitive(t, \"string\");\n  return \"symbol\" == typeof i ? i : i + \"\";\n}\nfunction _typeof(o) {\n  \"@babel/helpers - typeof\";\n\n  return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function (o) {\n    return typeof o;\n  } : function (o) {\n    return o && \"function\" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? \"symbol\" : typeof o;\n  }, _typeof(o);\n}\n\nvar DEFAULTS = {\n  /**\n   * Enable a modal backdrop, specify `static` for a backdrop\n   * which doesn't close the modal on click.\n   * @type {boolean}\n   */\n  backdrop: true,\n  /**\n   * Show the button on the top-right of the viewer.\n   * @type {boolean}\n   */\n  button: true,\n  /**\n   * Show the navbar.\n   * @type {boolean | number}\n   */\n  navbar: true,\n  /**\n   * Specify the visibility and the content of the title.\n   * @type {boolean | number | Function | Array}\n   */\n  title: true,\n  /**\n   * Show the toolbar.\n   * @type {boolean | number | Object}\n   */\n  toolbar: true,\n  /**\n   * Custom class name(s) to add to the viewer's root element.\n   * @type {string}\n   */\n  className: '',\n  /**\n   * Define where to put the viewer in modal mode.\n   * @type {string | Element}\n   */\n  container: 'body',\n  /**\n   * Filter the images for viewing. Return true if the image is viewable.\n   * @type {Function}\n   */\n  filter: null,\n  /**\n   * Enable to request fullscreen when play.\n   * {@link https://developer.mozilla.org/en-US/docs/Web/API/FullscreenOptions}\n   * @type {boolean|FullscreenOptions}\n   */\n  fullscreen: true,\n  /**\n   * Define the extra attributes to inherit from the original image.\n   * @type {Array}\n   */\n  inheritedAttributes: ['crossOrigin', 'decoding', 'isMap', 'loading', 'referrerPolicy', 'sizes', 'srcset', 'useMap'],\n  /**\n   * Define the initial coverage of the viewing image.\n   * @type {number}\n   */\n  initialCoverage: 0.9,\n  /**\n   * Define the initial index of the image for viewing.\n   * @type {number}\n   */\n  initialViewIndex: 0,\n  /**\n   * Enable inline mode.\n   * @type {boolean}\n   */\n  inline: false,\n  /**\n   * The amount of time to delay between automatically cycling an image when playing.\n   * @type {number}\n   */\n  interval: 5000,\n  /**\n   * Enable keyboard support.\n   * @type {boolean}\n   */\n  keyboard: true,\n  /**\n   * Focus the viewer when initialized.\n   * @type {boolean}\n   */\n  focus: true,\n  /**\n   * Indicate if show a loading spinner when load image or not.\n   * @type {boolean}\n   */\n  loading: true,\n  /**\n   * Indicate if enable loop viewing or not.\n   * @type {boolean}\n   */\n  loop: true,\n  /**\n   * Min width of the viewer in inline mode.\n   * @type {number}\n   */\n  minWidth: 200,\n  /**\n   * Min height of the viewer in inline mode.\n   * @type {number}\n   */\n  minHeight: 100,\n  /**\n   * Enable to move the image.\n   * @type {boolean}\n   */\n  movable: true,\n  /**\n   * Enable to rotate the image.\n   * @type {boolean}\n   */\n  rotatable: true,\n  /**\n   * Enable to scale the image.\n   * @type {boolean}\n   */\n  scalable: true,\n  /**\n   * Enable to zoom the image.\n   * @type {boolean}\n   */\n  zoomable: true,\n  /**\n   * Enable to zoom the current image by dragging on the touch screen.\n   * @type {boolean}\n   */\n  zoomOnTouch: true,\n  /**\n   * Enable to zoom the image by wheeling mouse.\n   * @type {boolean}\n   */\n  zoomOnWheel: true,\n  /**\n   * Enable to slide to the next or previous image by swiping on the touch screen.\n   * @type {boolean}\n   */\n  slideOnTouch: true,\n  /**\n   * Indicate if toggle the image size between its natural size\n   * and initial size when double click on the image or not.\n   * @type {boolean}\n   */\n  toggleOnDblclick: true,\n  /**\n   * Show the tooltip with image ratio (percentage) when zoom in or zoom out.\n   * @type {boolean}\n   */\n  tooltip: true,\n  /**\n   * Enable CSS3 Transition for some special elements.\n   * @type {boolean}\n   */\n  transition: true,\n  /**\n   * Define the CSS `z-index` value of viewer in modal mode.\n   * @type {number}\n   */\n  zIndex: 2015,\n  /**\n   * Define the CSS `z-index` value of viewer in inline mode.\n   * @type {number}\n   */\n  zIndexInline: 0,\n  /**\n   * Define the ratio when zoom the image by wheeling mouse.\n   * @type {number}\n   */\n  zoomRatio: 0.1,\n  /**\n   * Define the min ratio of the image when zoom out.\n   * @type {number}\n   */\n  minZoomRatio: 0.01,\n  /**\n   * Define the max ratio of the image when zoom in.\n   * @type {number}\n   */\n  maxZoomRatio: 100,\n  /**\n   * Define where to get the original image URL for viewing.\n   * @type {string | Function}\n   */\n  url: 'src',\n  /**\n   * Event shortcuts.\n   * @type {Function}\n   */\n  ready: null,\n  show: null,\n  shown: null,\n  hide: null,\n  hidden: null,\n  view: null,\n  viewed: null,\n  move: null,\n  moved: null,\n  rotate: null,\n  rotated: null,\n  scale: null,\n  scaled: null,\n  zoom: null,\n  zoomed: null,\n  play: null,\n  stop: null\n};\n\nvar TEMPLATE = '<div class=\"viewer-container\" tabindex=\"-1\" touch-action=\"none\">' + '<div class=\"viewer-canvas\"></div>' + '<div class=\"viewer-footer\">' + '<div class=\"viewer-title\"></div>' + '<div class=\"viewer-toolbar\"></div>' + '<div class=\"viewer-navbar\">' + '<ul class=\"viewer-list\" role=\"navigation\"></ul>' + '</div>' + '</div>' + '<div class=\"viewer-tooltip\" role=\"alert\" aria-hidden=\"true\"></div>' + '<div class=\"viewer-button\" data-viewer-action=\"mix\" role=\"button\"></div>' + '<div class=\"viewer-player\"></div>' + '</div>';\n\nvar IS_BROWSER = typeof window !== 'undefined' && typeof window.document !== 'undefined';\nvar WINDOW = IS_BROWSER ? window : {};\nvar IS_TOUCH_DEVICE = IS_BROWSER && WINDOW.document.documentElement ? 'ontouchstart' in WINDOW.document.documentElement : false;\nvar HAS_POINTER_EVENT = IS_BROWSER ? 'PointerEvent' in WINDOW : false;\nvar NAMESPACE = 'viewer';\n\n// Actions\nvar ACTION_MOVE = 'move';\nvar ACTION_SWITCH = 'switch';\nvar ACTION_ZOOM = 'zoom';\n\n// Classes\nvar CLASS_ACTIVE = \"\".concat(NAMESPACE, \"-active\");\nvar CLASS_CLOSE = \"\".concat(NAMESPACE, \"-close\");\nvar CLASS_FADE = \"\".concat(NAMESPACE, \"-fade\");\nvar CLASS_FIXED = \"\".concat(NAMESPACE, \"-fixed\");\nvar CLASS_FULLSCREEN = \"\".concat(NAMESPACE, \"-fullscreen\");\nvar CLASS_FULLSCREEN_EXIT = \"\".concat(NAMESPACE, \"-fullscreen-exit\");\nvar CLASS_HIDE = \"\".concat(NAMESPACE, \"-hide\");\nvar CLASS_HIDE_MD_DOWN = \"\".concat(NAMESPACE, \"-hide-md-down\");\nvar CLASS_HIDE_SM_DOWN = \"\".concat(NAMESPACE, \"-hide-sm-down\");\nvar CLASS_HIDE_XS_DOWN = \"\".concat(NAMESPACE, \"-hide-xs-down\");\nvar CLASS_IN = \"\".concat(NAMESPACE, \"-in\");\nvar CLASS_INVISIBLE = \"\".concat(NAMESPACE, \"-invisible\");\nvar CLASS_LOADING = \"\".concat(NAMESPACE, \"-loading\");\nvar CLASS_MOVE = \"\".concat(NAMESPACE, \"-move\");\nvar CLASS_OPEN = \"\".concat(NAMESPACE, \"-open\");\nvar CLASS_SHOW = \"\".concat(NAMESPACE, \"-show\");\nvar CLASS_TRANSITION = \"\".concat(NAMESPACE, \"-transition\");\n\n// Native events\nvar EVENT_CLICK = 'click';\nvar EVENT_DBLCLICK = 'dblclick';\nvar EVENT_DRAG_START = 'dragstart';\nvar EVENT_FOCUSIN = 'focusin';\nvar EVENT_KEY_DOWN = 'keydown';\nvar EVENT_LOAD = 'load';\nvar EVENT_ERROR = 'error';\nvar EVENT_TOUCH_END = IS_TOUCH_DEVICE ? 'touchend touchcancel' : 'mouseup';\nvar EVENT_TOUCH_MOVE = IS_TOUCH_DEVICE ? 'touchmove' : 'mousemove';\nvar EVENT_TOUCH_START = IS_TOUCH_DEVICE ? 'touchstart' : 'mousedown';\nvar EVENT_POINTER_DOWN = HAS_POINTER_EVENT ? 'pointerdown' : EVENT_TOUCH_START;\nvar EVENT_POINTER_MOVE = HAS_POINTER_EVENT ? 'pointermove' : EVENT_TOUCH_MOVE;\nvar EVENT_POINTER_UP = HAS_POINTER_EVENT ? 'pointerup pointercancel' : EVENT_TOUCH_END;\nvar EVENT_RESIZE = 'resize';\nvar EVENT_TRANSITION_END = 'transitionend';\nvar EVENT_WHEEL = 'wheel';\n\n// Custom events\nvar EVENT_READY = 'ready';\nvar EVENT_SHOW = 'show';\nvar EVENT_SHOWN = 'shown';\nvar EVENT_HIDE = 'hide';\nvar EVENT_HIDDEN = 'hidden';\nvar EVENT_VIEW = 'view';\nvar EVENT_VIEWED = 'viewed';\nvar EVENT_MOVE = 'move';\nvar EVENT_MOVED = 'moved';\nvar EVENT_ROTATE = 'rotate';\nvar EVENT_ROTATED = 'rotated';\nvar EVENT_SCALE = 'scale';\nvar EVENT_SCALED = 'scaled';\nvar EVENT_ZOOM = 'zoom';\nvar EVENT_ZOOMED = 'zoomed';\nvar EVENT_PLAY = 'play';\nvar EVENT_STOP = 'stop';\n\n// Data keys\nvar DATA_ACTION = \"\".concat(NAMESPACE, \"Action\");\n\n// RegExps\nvar REGEXP_SPACES = /\\s\\s*/;\n\n// Misc\nvar BUTTONS = ['zoom-in', 'zoom-out', 'one-to-one', 'reset', 'prev', 'play', 'next', 'rotate-left', 'rotate-right', 'flip-horizontal', 'flip-vertical'];\n\n/**\n * Check if the given value is a string.\n * @param {*} value - The value to check.\n * @returns {boolean} Returns `true` if the given value is a string, else `false`.\n */\nfunction isString(value) {\n  return typeof value === 'string';\n}\n\n/**\n * Check if the given value is not a number.\n */\nvar isNaN = Number.isNaN || WINDOW.isNaN;\n\n/**\n * Check if the given value is a number.\n * @param {*} value - The value to check.\n * @returns {boolean} Returns `true` if the given value is a number, else `false`.\n */\nfunction isNumber(value) {\n  return typeof value === 'number' && !isNaN(value);\n}\n\n/**\n * Check if the given value is undefined.\n * @param {*} value - The value to check.\n * @returns {boolean} Returns `true` if the given value is undefined, else `false`.\n */\nfunction isUndefined(value) {\n  return typeof value === 'undefined';\n}\n\n/**\n * Check if the given value is an object.\n * @param {*} value - The value to check.\n * @returns {boolean} Returns `true` if the given value is an object, else `false`.\n */\nfunction isObject(value) {\n  return _typeof(value) === 'object' && value !== null;\n}\nvar hasOwnProperty = Object.prototype.hasOwnProperty;\n\n/**\n * Check if the given value is a plain object.\n * @param {*} value - The value to check.\n * @returns {boolean} Returns `true` if the given value is a plain object, else `false`.\n */\nfunction isPlainObject(value) {\n  if (!isObject(value)) {\n    return false;\n  }\n  try {\n    var _constructor = value.constructor;\n    var prototype = _constructor.prototype;\n    return _constructor && prototype && hasOwnProperty.call(prototype, 'isPrototypeOf');\n  } catch (error) {\n    return false;\n  }\n}\n\n/**\n * Check if the given value is a function.\n * @param {*} value - The value to check.\n * @returns {boolean} Returns `true` if the given value is a function, else `false`.\n */\nfunction isFunction(value) {\n  return typeof value === 'function';\n}\n\n/**\n * Iterate the given data.\n * @param {*} data - The data to iterate.\n * @param {Function} callback - The process function for each element.\n * @returns {*} The original data.\n */\nfunction forEach(data, callback) {\n  if (data && isFunction(callback)) {\n    if (Array.isArray(data) || isNumber(data.length) /* array-like */) {\n      var length = data.length;\n      var i;\n      for (i = 0; i < length; i += 1) {\n        if (callback.call(data, data[i], i, data) === false) {\n          break;\n        }\n      }\n    } else if (isObject(data)) {\n      Object.keys(data).forEach(function (key) {\n        callback.call(data, data[key], key, data);\n      });\n    }\n  }\n  return data;\n}\n\n/**\n * Extend the given object.\n * @param {*} obj - The object to be extended.\n * @param {*} args - The rest objects which will be merged to the first object.\n * @returns {Object} The extended object.\n */\nvar assign = Object.assign || function assign(obj) {\n  for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {\n    args[_key - 1] = arguments[_key];\n  }\n  if (isObject(obj) && args.length > 0) {\n    args.forEach(function (arg) {\n      if (isObject(arg)) {\n        Object.keys(arg).forEach(function (key) {\n          obj[key] = arg[key];\n        });\n      }\n    });\n  }\n  return obj;\n};\nvar REGEXP_SUFFIX = /^(?:width|height|left|top|marginLeft|marginTop)$/;\n\n/**\n * Apply styles to the given element.\n * @param {Element} element - The target element.\n * @param {Object} styles - The styles for applying.\n */\nfunction setStyle(element, styles) {\n  var style = element.style;\n  forEach(styles, function (value, property) {\n    if (REGEXP_SUFFIX.test(property) && isNumber(value)) {\n      value += 'px';\n    }\n    style[property] = value;\n  });\n}\n\n/**\n * Escape a string for using in HTML.\n * @param {String} value - The string to escape.\n * @returns {String} Returns the escaped string.\n */\nfunction escapeHTMLEntities(value) {\n  return isString(value) ? value.replace(/&(?!amp;|quot;|#39;|lt;|gt;)/g, '&amp;').replace(/\"/g, '&quot;').replace(/'/g, '&#39;').replace(/</g, '&lt;').replace(/>/g, '&gt;') : value;\n}\n\n/**\n * Check if the given element has a special class.\n * @param {Element} element - The element to check.\n * @param {string} value - The class to search.\n * @returns {boolean} Returns `true` if the special class was found.\n */\nfunction hasClass(element, value) {\n  if (!element || !value) {\n    return false;\n  }\n  return element.classList ? element.classList.contains(value) : element.className.indexOf(value) > -1;\n}\n\n/**\n * Add classes to the given element.\n * @param {Element} element - The target element.\n * @param {string} value - The classes to be added.\n */\nfunction addClass(element, value) {\n  if (!element || !value) {\n    return;\n  }\n  if (isNumber(element.length)) {\n    forEach(element, function (elem) {\n      addClass(elem, value);\n    });\n    return;\n  }\n  if (element.classList) {\n    element.classList.add(value);\n    return;\n  }\n  var className = element.className.trim();\n  if (!className) {\n    element.className = value;\n  } else if (className.indexOf(value) < 0) {\n    element.className = \"\".concat(className, \" \").concat(value);\n  }\n}\n\n/**\n * Remove classes from the given element.\n * @param {Element} element - The target element.\n * @param {string} value - The classes to be removed.\n */\nfunction removeClass(element, value) {\n  if (!element || !value) {\n    return;\n  }\n  if (isNumber(element.length)) {\n    forEach(element, function (elem) {\n      removeClass(elem, value);\n    });\n    return;\n  }\n  if (element.classList) {\n    element.classList.remove(value);\n    return;\n  }\n  if (element.className.indexOf(value) >= 0) {\n    element.className = element.className.replace(value, '');\n  }\n}\n\n/**\n * Add or remove classes from the given element.\n * @param {Element} element - The target element.\n * @param {string} value - The classes to be toggled.\n * @param {boolean} added - Add only.\n */\nfunction toggleClass(element, value, added) {\n  if (!value) {\n    return;\n  }\n  if (isNumber(element.length)) {\n    forEach(element, function (elem) {\n      toggleClass(elem, value, added);\n    });\n    return;\n  }\n\n  // IE10-11 doesn't support the second parameter of `classList.toggle`\n  if (added) {\n    addClass(element, value);\n  } else {\n    removeClass(element, value);\n  }\n}\nvar REGEXP_HYPHENATE = /([a-z\\d])([A-Z])/g;\n\n/**\n * Transform the given string from camelCase to kebab-case\n * @param {string} value - The value to transform.\n * @returns {string} The transformed value.\n */\nfunction hyphenate(value) {\n  return value.replace(REGEXP_HYPHENATE, '$1-$2').toLowerCase();\n}\n\n/**\n * Get data from the given element.\n * @param {Element} element - The target element.\n * @param {string} name - The data key to get.\n * @returns {string} The data value.\n */\nfunction getData(element, name) {\n  if (isObject(element[name])) {\n    return element[name];\n  }\n  if (element.dataset) {\n    return element.dataset[name];\n  }\n  return element.getAttribute(\"data-\".concat(hyphenate(name)));\n}\n\n/**\n * Set data to the given element.\n * @param {Element} element - The target element.\n * @param {string} name - The data key to set.\n * @param {string} data - The data value.\n */\nfunction setData(element, name, data) {\n  if (isObject(data)) {\n    element[name] = data;\n  } else if (element.dataset) {\n    element.dataset[name] = data;\n  } else {\n    element.setAttribute(\"data-\".concat(hyphenate(name)), data);\n  }\n}\nvar onceSupported = function () {\n  var supported = false;\n  if (IS_BROWSER) {\n    var once = false;\n    var listener = function listener() {};\n    var options = Object.defineProperty({}, 'once', {\n      get: function get() {\n        supported = true;\n        return once;\n      },\n      /**\n       * This setter can fix a `TypeError` in strict mode\n       * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Getter_only}\n       * @param {boolean} value - The value to set\n       */\n      set: function set(value) {\n        once = value;\n      }\n    });\n    WINDOW.addEventListener('test', listener, options);\n    WINDOW.removeEventListener('test', listener, options);\n  }\n  return supported;\n}();\n\n/**\n * Remove event listener from the target element.\n * @param {Element} element - The event target.\n * @param {string} type - The event type(s).\n * @param {Function} listener - The event listener.\n * @param {Object} options - The event options.\n */\nfunction removeListener(element, type, listener) {\n  var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};\n  var handler = listener;\n  type.trim().split(REGEXP_SPACES).forEach(function (event) {\n    if (!onceSupported) {\n      var listeners = element.listeners;\n      if (listeners && listeners[event] && listeners[event][listener]) {\n        handler = listeners[event][listener];\n        delete listeners[event][listener];\n        if (Object.keys(listeners[event]).length === 0) {\n          delete listeners[event];\n        }\n        if (Object.keys(listeners).length === 0) {\n          delete element.listeners;\n        }\n      }\n    }\n    element.removeEventListener(event, handler, options);\n  });\n}\n\n/**\n * Add event listener to the target element.\n * @param {Element} element - The event target.\n * @param {string} type - The event type(s).\n * @param {Function} listener - The event listener.\n * @param {Object} options - The event options.\n */\nfunction addListener(element, type, listener) {\n  var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};\n  var _handler = listener;\n  type.trim().split(REGEXP_SPACES).forEach(function (event) {\n    if (options.once && !onceSupported) {\n      var _element$listeners = element.listeners,\n        listeners = _element$listeners === void 0 ? {} : _element$listeners;\n      _handler = function handler() {\n        delete listeners[event][listener];\n        element.removeEventListener(event, _handler, options);\n        for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {\n          args[_key2] = arguments[_key2];\n        }\n        listener.apply(element, args);\n      };\n      if (!listeners[event]) {\n        listeners[event] = {};\n      }\n      if (listeners[event][listener]) {\n        element.removeEventListener(event, listeners[event][listener], options);\n      }\n      listeners[event][listener] = _handler;\n      element.listeners = listeners;\n    }\n    element.addEventListener(event, _handler, options);\n  });\n}\n\n/**\n * Dispatch event on the target element.\n * @param {Element} element - The event target.\n * @param {string} type - The event type(s).\n * @param {Object} data - The additional event data.\n * @param {Object} options - The additional event options.\n * @returns {boolean} Indicate if the event is default prevented or not.\n */\nfunction dispatchEvent(element, type, data, options) {\n  var event;\n\n  // Event and CustomEvent on IE9-11 are global objects, not constructors\n  if (isFunction(Event) && isFunction(CustomEvent)) {\n    event = new CustomEvent(type, _objectSpread2({\n      bubbles: true,\n      cancelable: true,\n      detail: data\n    }, options));\n  } else {\n    event = document.createEvent('CustomEvent');\n    event.initCustomEvent(type, true, true, data);\n  }\n  return element.dispatchEvent(event);\n}\n\n/**\n * Get the offset base on the document.\n * @param {Element} element - The target element.\n * @returns {Object} The offset data.\n */\nfunction getOffset(element) {\n  var box = element.getBoundingClientRect();\n  return {\n    left: box.left + (window.pageXOffset - document.documentElement.clientLeft),\n    top: box.top + (window.pageYOffset - document.documentElement.clientTop)\n  };\n}\n\n/**\n * Get transforms base on the given object.\n * @param {Object} obj - The target object.\n * @returns {string} A string contains transform values.\n */\nfunction getTransforms(_ref) {\n  var rotate = _ref.rotate,\n    scaleX = _ref.scaleX,\n    scaleY = _ref.scaleY,\n    translateX = _ref.translateX,\n    translateY = _ref.translateY;\n  var values = [];\n  if (isNumber(translateX) && translateX !== 0) {\n    values.push(\"translateX(\".concat(translateX, \"px)\"));\n  }\n  if (isNumber(translateY) && translateY !== 0) {\n    values.push(\"translateY(\".concat(translateY, \"px)\"));\n  }\n\n  // Rotate should come first before scale to match orientation transform\n  if (isNumber(rotate) && rotate !== 0) {\n    values.push(\"rotate(\".concat(rotate, \"deg)\"));\n  }\n  if (isNumber(scaleX) && scaleX !== 1) {\n    values.push(\"scaleX(\".concat(scaleX, \")\"));\n  }\n  if (isNumber(scaleY) && scaleY !== 1) {\n    values.push(\"scaleY(\".concat(scaleY, \")\"));\n  }\n  var transform = values.length ? values.join(' ') : 'none';\n  return {\n    WebkitTransform: transform,\n    msTransform: transform,\n    transform: transform\n  };\n}\n\n/**\n * Get an image name from an image url.\n * @param {string} url - The target url.\n * @example\n * // picture.jpg\n * getImageNameFromURL('https://domain.com/path/to/picture.jpg?size=1280×960')\n * @returns {string} A string contains the image name.\n */\nfunction getImageNameFromURL(url) {\n  return isString(url) ? decodeURIComponent(url.replace(/^.*\\//, '').replace(/[?&#].*$/, '')) : '';\n}\nvar IS_SAFARI = WINDOW.navigator && /Version\\/\\d+(\\.\\d+)+?\\s+Safari/i.test(WINDOW.navigator.userAgent);\n\n/**\n * Get an image's natural sizes.\n * @param {string} image - The target image.\n * @param {Object} options - The viewer options.\n * @param {Function} callback - The callback function.\n * @returns {HTMLImageElement} The new image.\n */\nfunction getImageNaturalSizes(image, options, callback) {\n  var newImage = document.createElement('img');\n\n  // Modern browsers (except Safari)\n  if (image.naturalWidth && !IS_SAFARI) {\n    callback(image.naturalWidth, image.naturalHeight);\n    return newImage;\n  }\n  var body = document.body || document.documentElement;\n  newImage.onload = function () {\n    callback(newImage.width, newImage.height);\n    if (!IS_SAFARI) {\n      body.removeChild(newImage);\n    }\n  };\n  forEach(options.inheritedAttributes, function (name) {\n    var value = image.getAttribute(name);\n    if (value !== null) {\n      newImage.setAttribute(name, value);\n    }\n  });\n  newImage.src = image.src;\n\n  // iOS Safari will convert the image automatically\n  // with its orientation once append it into DOM\n  if (!IS_SAFARI) {\n    newImage.style.cssText = 'left:0;' + 'max-height:none!important;' + 'max-width:none!important;' + 'min-height:0!important;' + 'min-width:0!important;' + 'opacity:0;' + 'position:absolute;' + 'top:0;' + 'z-index:-1;';\n    body.appendChild(newImage);\n  }\n  return newImage;\n}\n\n/**\n * Get the related class name of a responsive type number.\n * @param {string} type - The responsive type.\n * @returns {string} The related class name.\n */\nfunction getResponsiveClass(type) {\n  switch (type) {\n    case 2:\n      return CLASS_HIDE_XS_DOWN;\n    case 3:\n      return CLASS_HIDE_SM_DOWN;\n    case 4:\n      return CLASS_HIDE_MD_DOWN;\n    default:\n      return '';\n  }\n}\n\n/**\n * Get the max ratio of a group of pointers.\n * @param {string} pointers - The target pointers.\n * @returns {number} The result ratio.\n */\nfunction getMaxZoomRatio(pointers) {\n  var pointers2 = _objectSpread2({}, pointers);\n  var ratios = [];\n  forEach(pointers, function (pointer, pointerId) {\n    delete pointers2[pointerId];\n    forEach(pointers2, function (pointer2) {\n      var x1 = Math.abs(pointer.startX - pointer2.startX);\n      var y1 = Math.abs(pointer.startY - pointer2.startY);\n      var x2 = Math.abs(pointer.endX - pointer2.endX);\n      var y2 = Math.abs(pointer.endY - pointer2.endY);\n      var z1 = Math.sqrt(x1 * x1 + y1 * y1);\n      var z2 = Math.sqrt(x2 * x2 + y2 * y2);\n      var ratio = (z2 - z1) / z1;\n      ratios.push(ratio);\n    });\n  });\n  ratios.sort(function (a, b) {\n    return Math.abs(a) < Math.abs(b);\n  });\n  return ratios[0];\n}\n\n/**\n * Get a pointer from an event object.\n * @param {Object} event - The target event object.\n * @param {boolean} endOnly - Indicates if only returns the end point coordinate or not.\n * @returns {Object} The result pointer contains start and/or end point coordinates.\n */\nfunction getPointer(_ref2, endOnly) {\n  var pageX = _ref2.pageX,\n    pageY = _ref2.pageY;\n  var end = {\n    endX: pageX,\n    endY: pageY\n  };\n  return endOnly ? end : _objectSpread2({\n    timeStamp: Date.now(),\n    startX: pageX,\n    startY: pageY\n  }, end);\n}\n\n/**\n * Get the center point coordinate of a group of pointers.\n * @param {Object} pointers - The target pointers.\n * @returns {Object} The center point coordinate.\n */\nfunction getPointersCenter(pointers) {\n  var pageX = 0;\n  var pageY = 0;\n  var count = 0;\n  forEach(pointers, function (_ref3) {\n    var startX = _ref3.startX,\n      startY = _ref3.startY;\n    pageX += startX;\n    pageY += startY;\n    count += 1;\n  });\n  pageX /= count;\n  pageY /= count;\n  return {\n    pageX: pageX,\n    pageY: pageY\n  };\n}\n\nvar render = {\n  render: function render() {\n    this.initContainer();\n    this.initViewer();\n    this.initList();\n    this.renderViewer();\n  },\n  initBody: function initBody() {\n    var ownerDocument = this.element.ownerDocument;\n    var body = ownerDocument.body || ownerDocument.documentElement;\n    this.body = body;\n    this.scrollbarWidth = window.innerWidth - ownerDocument.documentElement.clientWidth;\n    this.initialBodyPaddingRight = body.style.paddingRight;\n    this.initialBodyComputedPaddingRight = window.getComputedStyle(body).paddingRight;\n  },\n  initContainer: function initContainer() {\n    this.containerData = {\n      width: window.innerWidth,\n      height: window.innerHeight\n    };\n  },\n  initViewer: function initViewer() {\n    var options = this.options,\n      parent = this.parent;\n    var viewerData;\n    if (options.inline) {\n      viewerData = {\n        width: Math.max(parent.offsetWidth, options.minWidth),\n        height: Math.max(parent.offsetHeight, options.minHeight)\n      };\n      this.parentData = viewerData;\n    }\n    if (this.fulled || !viewerData) {\n      viewerData = this.containerData;\n    }\n    this.viewerData = assign({}, viewerData);\n  },\n  renderViewer: function renderViewer() {\n    if (this.options.inline && !this.fulled) {\n      setStyle(this.viewer, this.viewerData);\n    }\n  },\n  initList: function initList() {\n    var _this = this;\n    var element = this.element,\n      options = this.options,\n      list = this.list;\n    var items = [];\n\n    // initList may be called in this.update, so should keep idempotent\n    list.innerHTML = '';\n    forEach(this.images, function (image, index) {\n      var src = image.src;\n      var alt = image.alt || getImageNameFromURL(src);\n      var url = _this.getImageURL(image);\n      if (src || url) {\n        var item = document.createElement('li');\n        var img = document.createElement('img');\n        forEach(options.inheritedAttributes, function (name) {\n          var value = image.getAttribute(name);\n          if (value !== null) {\n            img.setAttribute(name, value);\n          }\n        });\n        if (options.navbar) {\n          img.src = src || url;\n        }\n        img.alt = alt;\n        img.setAttribute('data-original-url', url || src);\n        item.setAttribute('data-index', index);\n        item.setAttribute('data-viewer-action', 'view');\n        item.setAttribute('role', 'button');\n        if (options.keyboard) {\n          item.setAttribute('tabindex', 0);\n        }\n        item.appendChild(img);\n        list.appendChild(item);\n        items.push(item);\n      }\n    });\n    this.items = items;\n    forEach(items, function (item) {\n      var image = item.firstElementChild;\n      var onLoad;\n      var onError;\n      setData(image, 'filled', true);\n      if (options.loading) {\n        addClass(item, CLASS_LOADING);\n      }\n      addListener(image, EVENT_LOAD, onLoad = function onLoad(event) {\n        removeListener(image, EVENT_ERROR, onError);\n        if (options.loading) {\n          removeClass(item, CLASS_LOADING);\n        }\n        _this.loadImage(event);\n      }, {\n        once: true\n      });\n      addListener(image, EVENT_ERROR, onError = function onError() {\n        removeListener(image, EVENT_LOAD, onLoad);\n        if (options.loading) {\n          removeClass(item, CLASS_LOADING);\n        }\n      }, {\n        once: true\n      });\n    });\n    if (options.transition) {\n      addListener(element, EVENT_VIEWED, function () {\n        addClass(list, CLASS_TRANSITION);\n      }, {\n        once: true\n      });\n    }\n  },\n  renderList: function renderList() {\n    var index = this.index;\n    var item = this.items[index];\n    if (!item) {\n      return;\n    }\n    var next = item.nextElementSibling;\n    var gutter = parseInt(window.getComputedStyle(next || item).marginLeft, 10);\n    var offsetWidth = item.offsetWidth;\n    var outerWidth = offsetWidth + gutter;\n\n    // Place the active item in the center of the screen\n    setStyle(this.list, assign({\n      width: outerWidth * this.length - gutter\n    }, getTransforms({\n      translateX: (this.viewerData.width - offsetWidth) / 2 - outerWidth * index\n    })));\n  },\n  resetList: function resetList() {\n    var list = this.list;\n    list.innerHTML = '';\n    removeClass(list, CLASS_TRANSITION);\n    setStyle(list, getTransforms({\n      translateX: 0\n    }));\n  },\n  initImage: function initImage(done) {\n    var _this2 = this;\n    var options = this.options,\n      image = this.image,\n      viewerData = this.viewerData;\n    var footerHeight = this.footer.offsetHeight;\n    var viewerWidth = viewerData.width;\n    var viewerHeight = Math.max(viewerData.height - footerHeight, footerHeight);\n    var oldImageData = this.imageData || {};\n    var sizingImage;\n    this.imageInitializing = {\n      abort: function abort() {\n        sizingImage.onload = null;\n      }\n    };\n    sizingImage = getImageNaturalSizes(image, options, function (naturalWidth, naturalHeight) {\n      var aspectRatio = naturalWidth / naturalHeight;\n      var initialCoverage = Math.max(0, Math.min(1, options.initialCoverage));\n      var width = viewerWidth;\n      var height = viewerHeight;\n      _this2.imageInitializing = false;\n      if (viewerHeight * aspectRatio > viewerWidth) {\n        height = viewerWidth / aspectRatio;\n      } else {\n        width = viewerHeight * aspectRatio;\n      }\n      initialCoverage = isNumber(initialCoverage) ? initialCoverage : 0.9;\n      width = Math.min(width * initialCoverage, naturalWidth);\n      height = Math.min(height * initialCoverage, naturalHeight);\n      var left = (viewerWidth - width) / 2;\n      var top = (viewerHeight - height) / 2;\n      var imageData = {\n        left: left,\n        top: top,\n        x: left,\n        y: top,\n        width: width,\n        height: height,\n        oldRatio: 1,\n        ratio: width / naturalWidth,\n        aspectRatio: aspectRatio,\n        naturalWidth: naturalWidth,\n        naturalHeight: naturalHeight\n      };\n      var initialImageData = assign({}, imageData);\n      if (options.rotatable) {\n        imageData.rotate = oldImageData.rotate || 0;\n        initialImageData.rotate = 0;\n      }\n      if (options.scalable) {\n        imageData.scaleX = oldImageData.scaleX || 1;\n        imageData.scaleY = oldImageData.scaleY || 1;\n        initialImageData.scaleX = 1;\n        initialImageData.scaleY = 1;\n      }\n      _this2.imageData = imageData;\n      _this2.initialImageData = initialImageData;\n      if (done) {\n        done();\n      }\n    });\n  },\n  renderImage: function renderImage(done) {\n    var _this3 = this;\n    var image = this.image,\n      imageData = this.imageData;\n    setStyle(image, assign({\n      width: imageData.width,\n      height: imageData.height,\n      // XXX: Not to use translateX/Y to avoid image shaking when zooming\n      marginLeft: imageData.x,\n      marginTop: imageData.y\n    }, getTransforms(imageData)));\n    if (done) {\n      if ((this.viewing || this.moving || this.rotating || this.scaling || this.zooming) && this.options.transition && hasClass(image, CLASS_TRANSITION)) {\n        var onTransitionEnd = function onTransitionEnd() {\n          _this3.imageRendering = false;\n          done();\n        };\n        this.imageRendering = {\n          abort: function abort() {\n            removeListener(image, EVENT_TRANSITION_END, onTransitionEnd);\n          }\n        };\n        addListener(image, EVENT_TRANSITION_END, onTransitionEnd, {\n          once: true\n        });\n      } else {\n        done();\n      }\n    }\n  },\n  resetImage: function resetImage() {\n    var image = this.image;\n    if (image) {\n      if (this.viewing) {\n        this.viewing.abort();\n      }\n      image.parentNode.removeChild(image);\n      this.image = null;\n      this.title.innerHTML = '';\n    }\n  }\n};\n\nvar events = {\n  bind: function bind() {\n    var options = this.options,\n      viewer = this.viewer,\n      canvas = this.canvas;\n    var document = this.element.ownerDocument;\n    addListener(viewer, EVENT_CLICK, this.onClick = this.click.bind(this));\n    addListener(viewer, EVENT_DRAG_START, this.onDragStart = this.dragstart.bind(this));\n    addListener(canvas, EVENT_POINTER_DOWN, this.onPointerDown = this.pointerdown.bind(this));\n    addListener(document, EVENT_POINTER_MOVE, this.onPointerMove = this.pointermove.bind(this));\n    addListener(document, EVENT_POINTER_UP, this.onPointerUp = this.pointerup.bind(this));\n    addListener(document, EVENT_KEY_DOWN, this.onKeyDown = this.keydown.bind(this));\n    addListener(window, EVENT_RESIZE, this.onResize = this.resize.bind(this));\n    if (options.zoomable && options.zoomOnWheel) {\n      addListener(viewer, EVENT_WHEEL, this.onWheel = this.wheel.bind(this), {\n        passive: false,\n        capture: true\n      });\n    }\n    if (options.toggleOnDblclick) {\n      addListener(canvas, EVENT_DBLCLICK, this.onDblclick = this.dblclick.bind(this));\n    }\n  },\n  unbind: function unbind() {\n    var options = this.options,\n      viewer = this.viewer,\n      canvas = this.canvas;\n    var document = this.element.ownerDocument;\n    removeListener(viewer, EVENT_CLICK, this.onClick);\n    removeListener(viewer, EVENT_DRAG_START, this.onDragStart);\n    removeListener(canvas, EVENT_POINTER_DOWN, this.onPointerDown);\n    removeListener(document, EVENT_POINTER_MOVE, this.onPointerMove);\n    removeListener(document, EVENT_POINTER_UP, this.onPointerUp);\n    removeListener(document, EVENT_KEY_DOWN, this.onKeyDown);\n    removeListener(window, EVENT_RESIZE, this.onResize);\n    if (options.zoomable && options.zoomOnWheel) {\n      removeListener(viewer, EVENT_WHEEL, this.onWheel, {\n        passive: false,\n        capture: true\n      });\n    }\n    if (options.toggleOnDblclick) {\n      removeListener(canvas, EVENT_DBLCLICK, this.onDblclick);\n    }\n  }\n};\n\nvar handlers = {\n  click: function click(event) {\n    var options = this.options,\n      imageData = this.imageData;\n    var target = event.target;\n    var action = getData(target, DATA_ACTION);\n    if (!action && target.localName === 'img' && target.parentElement.localName === 'li') {\n      target = target.parentElement;\n      action = getData(target, DATA_ACTION);\n    }\n\n    // Cancel the emulated click when the native click event was triggered.\n    if (IS_TOUCH_DEVICE && event.isTrusted && target === this.canvas) {\n      clearTimeout(this.clickCanvasTimeout);\n    }\n    switch (action) {\n      case 'mix':\n        if (this.played) {\n          this.stop();\n        } else if (options.inline) {\n          if (this.fulled) {\n            this.exit();\n          } else {\n            this.full();\n          }\n        } else {\n          this.hide();\n        }\n        break;\n      case 'hide':\n        if (!this.pointerMoved) {\n          this.hide();\n        }\n        break;\n      case 'view':\n        this.view(getData(target, 'index'));\n        break;\n      case 'zoom-in':\n        this.zoom(0.1, true);\n        break;\n      case 'zoom-out':\n        this.zoom(-0.1, true);\n        break;\n      case 'one-to-one':\n        this.toggle();\n        break;\n      case 'reset':\n        this.reset();\n        break;\n      case 'prev':\n        this.prev(options.loop);\n        break;\n      case 'play':\n        this.play(options.fullscreen);\n        break;\n      case 'next':\n        this.next(options.loop);\n        break;\n      case 'rotate-left':\n        this.rotate(-90);\n        break;\n      case 'rotate-right':\n        this.rotate(90);\n        break;\n      case 'flip-horizontal':\n        this.scaleX(-imageData.scaleX || -1);\n        break;\n      case 'flip-vertical':\n        this.scaleY(-imageData.scaleY || -1);\n        break;\n      default:\n        if (this.played) {\n          this.stop();\n        }\n    }\n  },\n  dblclick: function dblclick(event) {\n    event.preventDefault();\n    if (this.viewed && event.target === this.image) {\n      // Cancel the emulated double click when the native dblclick event was triggered.\n      if (IS_TOUCH_DEVICE && event.isTrusted) {\n        clearTimeout(this.doubleClickImageTimeout);\n      }\n\n      // XXX: No pageX/Y properties in custom event, fallback to the original event.\n      this.toggle(event.isTrusted ? event : event.detail && event.detail.originalEvent);\n    }\n  },\n  load: function load() {\n    var _this = this;\n    if (this.timeout) {\n      clearTimeout(this.timeout);\n      this.timeout = false;\n    }\n    var element = this.element,\n      options = this.options,\n      image = this.image,\n      index = this.index,\n      viewerData = this.viewerData;\n    removeClass(image, CLASS_INVISIBLE);\n    if (options.loading) {\n      removeClass(this.canvas, CLASS_LOADING);\n    }\n    image.style.cssText = 'height:0;' + \"margin-left:\".concat(viewerData.width / 2, \"px;\") + \"margin-top:\".concat(viewerData.height / 2, \"px;\") + 'max-width:none!important;' + 'position:relative;' + 'width:0;';\n    this.initImage(function () {\n      toggleClass(image, CLASS_MOVE, options.movable);\n      toggleClass(image, CLASS_TRANSITION, options.transition);\n      _this.renderImage(function () {\n        _this.viewed = true;\n        _this.viewing = false;\n        if (isFunction(options.viewed)) {\n          addListener(element, EVENT_VIEWED, options.viewed, {\n            once: true\n          });\n        }\n        dispatchEvent(element, EVENT_VIEWED, {\n          originalImage: _this.images[index],\n          index: index,\n          image: image\n        }, {\n          cancelable: false\n        });\n      });\n    });\n  },\n  loadImage: function loadImage(event) {\n    var image = event.target;\n    var parent = image.parentNode;\n    var parentWidth = parent.offsetWidth || 30;\n    var parentHeight = parent.offsetHeight || 50;\n    var filled = !!getData(image, 'filled');\n    getImageNaturalSizes(image, this.options, function (naturalWidth, naturalHeight) {\n      var aspectRatio = naturalWidth / naturalHeight;\n      var width = parentWidth;\n      var height = parentHeight;\n      if (parentHeight * aspectRatio > parentWidth) {\n        if (filled) {\n          width = parentHeight * aspectRatio;\n        } else {\n          height = parentWidth / aspectRatio;\n        }\n      } else if (filled) {\n        height = parentWidth / aspectRatio;\n      } else {\n        width = parentHeight * aspectRatio;\n      }\n      setStyle(image, assign({\n        width: width,\n        height: height\n      }, getTransforms({\n        translateX: (parentWidth - width) / 2,\n        translateY: (parentHeight - height) / 2\n      })));\n    });\n  },\n  keydown: function keydown(event) {\n    var options = this.options;\n    if (!options.keyboard) {\n      return;\n    }\n    var keyCode = event.keyCode || event.which || event.charCode;\n    switch (keyCode) {\n      // Enter\n      case 13:\n        if (this.viewer.contains(event.target)) {\n          this.click(event);\n        }\n        break;\n    }\n    if (!this.fulled) {\n      return;\n    }\n    switch (keyCode) {\n      // Escape\n      case 27:\n        if (this.played) {\n          this.stop();\n        } else if (options.inline) {\n          if (this.fulled) {\n            this.exit();\n          }\n        } else {\n          this.hide();\n        }\n        break;\n\n      // Space\n      case 32:\n        if (this.played) {\n          this.stop();\n        }\n        break;\n\n      // ArrowLeft\n      case 37:\n        if (this.played && this.playing) {\n          this.playing.prev();\n        } else {\n          this.prev(options.loop);\n        }\n        break;\n\n      // ArrowUp\n      case 38:\n        // Prevent scroll on Firefox\n        event.preventDefault();\n\n        // Zoom in\n        this.zoom(options.zoomRatio, true);\n        break;\n\n      // ArrowRight\n      case 39:\n        if (this.played && this.playing) {\n          this.playing.next();\n        } else {\n          this.next(options.loop);\n        }\n        break;\n\n      // ArrowDown\n      case 40:\n        // Prevent scroll on Firefox\n        event.preventDefault();\n\n        // Zoom out\n        this.zoom(-options.zoomRatio, true);\n        break;\n\n      // Ctrl + 0\n      case 48:\n      // Fall through\n\n      // Ctrl + 1\n      // eslint-disable-next-line no-fallthrough\n      case 49:\n        if (event.ctrlKey) {\n          event.preventDefault();\n          this.toggle();\n        }\n        break;\n    }\n  },\n  dragstart: function dragstart(event) {\n    if (event.target.localName === 'img') {\n      event.preventDefault();\n    }\n  },\n  pointerdown: function pointerdown(event) {\n    var options = this.options,\n      pointers = this.pointers;\n    var buttons = event.buttons,\n      button = event.button;\n    this.pointerMoved = false;\n    if (!this.viewed || this.showing || this.viewing || this.hiding\n\n    // Handle mouse event and pointer event and ignore touch event\n    || (event.type === 'mousedown' || event.type === 'pointerdown' && event.pointerType === 'mouse') && (\n    // No primary button (Usually the left button)\n    isNumber(buttons) && buttons !== 1 || isNumber(button) && button !== 0\n\n    // Open context menu\n    || event.ctrlKey)) {\n      return;\n    }\n\n    // Prevent default behaviours as page zooming in touch devices.\n    event.preventDefault();\n    if (event.changedTouches) {\n      forEach(event.changedTouches, function (touch) {\n        pointers[touch.identifier] = getPointer(touch);\n      });\n    } else {\n      pointers[event.pointerId || 0] = getPointer(event);\n    }\n    var action = options.movable ? ACTION_MOVE : false;\n    if (options.zoomOnTouch && options.zoomable && Object.keys(pointers).length > 1) {\n      action = ACTION_ZOOM;\n    } else if (options.slideOnTouch && (event.pointerType === 'touch' || event.type === 'touchstart') && this.isSwitchable()) {\n      action = ACTION_SWITCH;\n    }\n    if (options.transition && (action === ACTION_MOVE || action === ACTION_ZOOM)) {\n      removeClass(this.image, CLASS_TRANSITION);\n    }\n    this.action = action;\n  },\n  pointermove: function pointermove(event) {\n    var pointers = this.pointers,\n      action = this.action;\n    if (!this.viewed || !action) {\n      return;\n    }\n    event.preventDefault();\n    if (event.changedTouches) {\n      forEach(event.changedTouches, function (touch) {\n        assign(pointers[touch.identifier] || {}, getPointer(touch, true));\n      });\n    } else {\n      assign(pointers[event.pointerId || 0] || {}, getPointer(event, true));\n    }\n    this.change(event);\n  },\n  pointerup: function pointerup(event) {\n    var _this2 = this;\n    var options = this.options,\n      action = this.action,\n      pointers = this.pointers;\n    var pointer;\n    if (event.changedTouches) {\n      forEach(event.changedTouches, function (touch) {\n        pointer = pointers[touch.identifier];\n        delete pointers[touch.identifier];\n      });\n    } else {\n      pointer = pointers[event.pointerId || 0];\n      delete pointers[event.pointerId || 0];\n    }\n    if (!action) {\n      return;\n    }\n    event.preventDefault();\n    if (options.transition && (action === ACTION_MOVE || action === ACTION_ZOOM)) {\n      addClass(this.image, CLASS_TRANSITION);\n    }\n    this.action = false;\n\n    // Emulate click and double click in touch devices to support backdrop and image zooming (#210).\n    if (IS_TOUCH_DEVICE && action !== ACTION_ZOOM && pointer && Date.now() - pointer.timeStamp < 500) {\n      clearTimeout(this.clickCanvasTimeout);\n      clearTimeout(this.doubleClickImageTimeout);\n      if (options.toggleOnDblclick && this.viewed && event.target === this.image) {\n        if (this.imageClicked) {\n          this.imageClicked = false;\n\n          // This timeout will be cleared later when a native dblclick event is triggering\n          this.doubleClickImageTimeout = setTimeout(function () {\n            dispatchEvent(_this2.image, EVENT_DBLCLICK, {\n              originalEvent: event\n            });\n          }, 50);\n        } else {\n          this.imageClicked = true;\n\n          // The default timing of a double click in Windows is 500 ms\n          this.doubleClickImageTimeout = setTimeout(function () {\n            _this2.imageClicked = false;\n          }, 500);\n        }\n      } else {\n        this.imageClicked = false;\n        if (options.backdrop && options.backdrop !== 'static' && event.target === this.canvas) {\n          // This timeout will be cleared later when a native click event is triggering\n          this.clickCanvasTimeout = setTimeout(function () {\n            dispatchEvent(_this2.canvas, EVENT_CLICK, {\n              originalEvent: event\n            });\n          }, 50);\n        }\n      }\n    }\n  },\n  resize: function resize() {\n    var _this3 = this;\n    if (!this.isShown || this.hiding) {\n      return;\n    }\n    if (this.fulled) {\n      this.close();\n      this.initBody();\n      this.open();\n    }\n    this.initContainer();\n    this.initViewer();\n    this.renderViewer();\n    this.renderList();\n    if (this.viewed) {\n      this.initImage(function () {\n        _this3.renderImage();\n      });\n    }\n    if (this.played) {\n      if (this.options.fullscreen && this.fulled && !(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement)) {\n        this.stop();\n        return;\n      }\n      forEach(this.player.getElementsByTagName('img'), function (image) {\n        addListener(image, EVENT_LOAD, _this3.loadImage.bind(_this3), {\n          once: true\n        });\n        dispatchEvent(image, EVENT_LOAD);\n      });\n    }\n  },\n  wheel: function wheel(event) {\n    var _this4 = this;\n    if (!this.viewed) {\n      return;\n    }\n    event.preventDefault();\n\n    // Limit wheel speed to prevent zoom too fast\n    if (this.wheeling) {\n      return;\n    }\n    this.wheeling = true;\n    setTimeout(function () {\n      _this4.wheeling = false;\n    }, 50);\n    var ratio = Number(this.options.zoomRatio) || 0.1;\n    var delta = 1;\n    if (event.deltaY) {\n      delta = event.deltaY > 0 ? 1 : -1;\n    } else if (event.wheelDelta) {\n      delta = -event.wheelDelta / 120;\n    } else if (event.detail) {\n      delta = event.detail > 0 ? 1 : -1;\n    }\n    this.zoom(-delta * ratio, true, null, event);\n  }\n};\n\nvar methods = {\n  /** Show the viewer (only available in modal mode)\n   * @param {boolean} [immediate=false] - Indicates if show the viewer immediately or not.\n   * @returns {Viewer} this\n   */\n  show: function show() {\n    var immediate = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;\n    var element = this.element,\n      options = this.options;\n    if (options.inline || this.showing || this.isShown || this.showing) {\n      return this;\n    }\n    if (!this.ready) {\n      this.build();\n      if (this.ready) {\n        this.show(immediate);\n      }\n      return this;\n    }\n    if (isFunction(options.show)) {\n      addListener(element, EVENT_SHOW, options.show, {\n        once: true\n      });\n    }\n    if (dispatchEvent(element, EVENT_SHOW) === false || !this.ready) {\n      return this;\n    }\n    if (this.hiding) {\n      this.transitioning.abort();\n    }\n    this.showing = true;\n    this.open();\n    var viewer = this.viewer;\n    removeClass(viewer, CLASS_HIDE);\n    viewer.setAttribute('role', 'dialog');\n    viewer.setAttribute('aria-labelledby', this.title.id);\n    viewer.setAttribute('aria-modal', true);\n    viewer.removeAttribute('aria-hidden');\n    if (options.transition && !immediate) {\n      var shown = this.shown.bind(this);\n      this.transitioning = {\n        abort: function abort() {\n          removeListener(viewer, EVENT_TRANSITION_END, shown);\n          removeClass(viewer, CLASS_IN);\n        }\n      };\n      addClass(viewer, CLASS_TRANSITION);\n\n      // Force reflow to enable CSS3 transition\n      viewer.initialOffsetWidth = viewer.offsetWidth;\n      addListener(viewer, EVENT_TRANSITION_END, shown, {\n        once: true\n      });\n      addClass(viewer, CLASS_IN);\n    } else {\n      addClass(viewer, CLASS_IN);\n      this.shown();\n    }\n    return this;\n  },\n  /**\n   * Hide the viewer (only available in modal mode)\n   * @param {boolean} [immediate=false] - Indicates if hide the viewer immediately or not.\n   * @returns {Viewer} this\n   */\n  hide: function hide() {\n    var _this = this;\n    var immediate = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;\n    var element = this.element,\n      options = this.options;\n    if (options.inline || this.hiding || !(this.isShown || this.showing)) {\n      return this;\n    }\n    if (isFunction(options.hide)) {\n      addListener(element, EVENT_HIDE, options.hide, {\n        once: true\n      });\n    }\n    if (dispatchEvent(element, EVENT_HIDE) === false) {\n      return this;\n    }\n    if (this.showing) {\n      this.transitioning.abort();\n    }\n    this.hiding = true;\n    if (this.played) {\n      this.stop();\n    } else if (this.viewing) {\n      this.viewing.abort();\n    }\n    var viewer = this.viewer,\n      image = this.image;\n    var hideImmediately = function hideImmediately() {\n      removeClass(viewer, CLASS_IN);\n      _this.hidden();\n    };\n    if (options.transition && !immediate) {\n      var _onViewerTransitionEnd = function onViewerTransitionEnd(event) {\n        // Ignore all propagating `transitionend` events (#275).\n        if (event && event.target === viewer) {\n          removeListener(viewer, EVENT_TRANSITION_END, _onViewerTransitionEnd);\n          _this.hidden();\n        }\n      };\n      var onImageTransitionEnd = function onImageTransitionEnd() {\n        // In case of show the viewer by `viewer.show(true)` previously (#407).\n        if (hasClass(viewer, CLASS_TRANSITION)) {\n          addListener(viewer, EVENT_TRANSITION_END, _onViewerTransitionEnd);\n          removeClass(viewer, CLASS_IN);\n        } else {\n          hideImmediately();\n        }\n      };\n      this.transitioning = {\n        abort: function abort() {\n          if (_this.viewed && hasClass(image, CLASS_TRANSITION)) {\n            removeListener(image, EVENT_TRANSITION_END, onImageTransitionEnd);\n          } else if (hasClass(viewer, CLASS_TRANSITION)) {\n            removeListener(viewer, EVENT_TRANSITION_END, _onViewerTransitionEnd);\n          }\n        }\n      };\n\n      // In case of hiding the viewer when holding on the image (#255),\n      // note that the `CLASS_TRANSITION` class will be removed on pointer down.\n      if (this.viewed && hasClass(image, CLASS_TRANSITION)) {\n        addListener(image, EVENT_TRANSITION_END, onImageTransitionEnd, {\n          once: true\n        });\n        this.zoomTo(0, false, null, null, true);\n      } else {\n        onImageTransitionEnd();\n      }\n    } else {\n      hideImmediately();\n    }\n    return this;\n  },\n  /**\n   * View one of the images with image's index\n   * @param {number} index - The index of the image to view.\n   * @returns {Viewer} this\n   */\n  view: function view() {\n    var _this2 = this;\n    var index = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.options.initialViewIndex;\n    index = Number(index) || 0;\n    if (this.hiding || this.played || index < 0 || index >= this.length || this.viewed && index === this.index) {\n      return this;\n    }\n    if (!this.isShown) {\n      this.index = index;\n      return this.show();\n    }\n    if (this.viewing) {\n      this.viewing.abort();\n    }\n    var element = this.element,\n      options = this.options,\n      title = this.title,\n      canvas = this.canvas;\n    var item = this.items[index];\n    var img = item.querySelector('img');\n    var url = getData(img, 'originalUrl');\n    var alt = img.getAttribute('alt');\n    var image = document.createElement('img');\n    forEach(options.inheritedAttributes, function (name) {\n      var value = img.getAttribute(name);\n      if (value !== null) {\n        image.setAttribute(name, value);\n      }\n    });\n    image.src = url;\n    image.alt = alt;\n    if (isFunction(options.view)) {\n      addListener(element, EVENT_VIEW, options.view, {\n        once: true\n      });\n    }\n    if (dispatchEvent(element, EVENT_VIEW, {\n      originalImage: this.images[index],\n      index: index,\n      image: image\n    }) === false || !this.isShown || this.hiding || this.played) {\n      return this;\n    }\n    var activeItem = this.items[this.index];\n    if (activeItem) {\n      removeClass(activeItem, CLASS_ACTIVE);\n      activeItem.removeAttribute('aria-selected');\n    }\n    addClass(item, CLASS_ACTIVE);\n    item.setAttribute('aria-selected', true);\n    if (options.focus) {\n      item.focus();\n    }\n    this.image = image;\n    this.viewed = false;\n    this.index = index;\n    this.imageData = {};\n    addClass(image, CLASS_INVISIBLE);\n    if (options.loading) {\n      addClass(canvas, CLASS_LOADING);\n    }\n    canvas.innerHTML = '';\n    canvas.appendChild(image);\n\n    // Center current item\n    this.renderList();\n\n    // Clear title\n    title.innerHTML = '';\n\n    // Generate title after viewed\n    var onViewed = function onViewed() {\n      var imageData = _this2.imageData;\n      var render = Array.isArray(options.title) ? options.title[1] : options.title;\n      title.innerHTML = escapeHTMLEntities(isFunction(render) ? render.call(_this2, image, imageData) : \"\".concat(alt, \" (\").concat(imageData.naturalWidth, \" \\xD7 \").concat(imageData.naturalHeight, \")\"));\n    };\n    var onLoad;\n    var onError;\n    addListener(element, EVENT_VIEWED, onViewed, {\n      once: true\n    });\n    this.viewing = {\n      abort: function abort() {\n        removeListener(element, EVENT_VIEWED, onViewed);\n        if (image.complete) {\n          if (_this2.imageRendering) {\n            _this2.imageRendering.abort();\n          } else if (_this2.imageInitializing) {\n            _this2.imageInitializing.abort();\n          }\n        } else {\n          // Cancel download to save bandwidth.\n          image.src = '';\n          removeListener(image, EVENT_LOAD, onLoad);\n          if (_this2.timeout) {\n            clearTimeout(_this2.timeout);\n          }\n        }\n      }\n    };\n    if (image.complete) {\n      this.load();\n    } else {\n      addListener(image, EVENT_LOAD, onLoad = function onLoad() {\n        removeListener(image, EVENT_ERROR, onError);\n        _this2.load();\n      }, {\n        once: true\n      });\n      addListener(image, EVENT_ERROR, onError = function onError() {\n        removeListener(image, EVENT_LOAD, onLoad);\n        if (_this2.timeout) {\n          clearTimeout(_this2.timeout);\n          _this2.timeout = false;\n        }\n        removeClass(image, CLASS_INVISIBLE);\n        if (options.loading) {\n          removeClass(_this2.canvas, CLASS_LOADING);\n        }\n      }, {\n        once: true\n      });\n      if (this.timeout) {\n        clearTimeout(this.timeout);\n      }\n\n      // Make the image visible if it fails to load within 1s\n      this.timeout = setTimeout(function () {\n        removeClass(image, CLASS_INVISIBLE);\n        _this2.timeout = false;\n      }, 1000);\n    }\n    return this;\n  },\n  /**\n   * View the previous image\n   * @param {boolean} [loop=false] - Indicate if view the last one\n   * when it is the first one at present.\n   * @returns {Viewer} this\n   */\n  prev: function prev() {\n    var loop = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;\n    var index = this.index - 1;\n    if (index < 0) {\n      index = loop ? this.length - 1 : 0;\n    }\n    this.view(index);\n    return this;\n  },\n  /**\n   * View the next image\n   * @param {boolean} [loop=false] - Indicate if view the first one\n   * when it is the last one at present.\n   * @returns {Viewer} this\n   */\n  next: function next() {\n    var loop = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;\n    var maxIndex = this.length - 1;\n    var index = this.index + 1;\n    if (index > maxIndex) {\n      index = loop ? 0 : maxIndex;\n    }\n    this.view(index);\n    return this;\n  },\n  /**\n   * Move the image with relative offsets.\n   * @param {number} x - The moving distance in the horizontal direction.\n   * @param {number} [y=x] The moving distance in the vertical direction.\n   * @returns {Viewer} this\n   */\n  move: function move(x) {\n    var y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : x;\n    var imageData = this.imageData;\n    this.moveTo(isUndefined(x) ? x : imageData.x + Number(x), isUndefined(y) ? y : imageData.y + Number(y));\n    return this;\n  },\n  /**\n   * Move the image to an absolute point.\n   * @param {number} x - The new position in the horizontal direction.\n   * @param {number} [y=x] - The new position in the vertical direction.\n   * @param {Event} [_originalEvent=null] - The original event if any.\n   * @returns {Viewer} this\n   */\n  moveTo: function moveTo(x) {\n    var _this3 = this;\n    var y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : x;\n    var _originalEvent = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;\n    var element = this.element,\n      options = this.options,\n      imageData = this.imageData;\n    x = Number(x);\n    y = Number(y);\n    if (this.viewed && !this.played && options.movable) {\n      var oldX = imageData.x;\n      var oldY = imageData.y;\n      var changed = false;\n      if (isNumber(x)) {\n        changed = true;\n      } else {\n        x = oldX;\n      }\n      if (isNumber(y)) {\n        changed = true;\n      } else {\n        y = oldY;\n      }\n      if (changed) {\n        if (isFunction(options.move)) {\n          addListener(element, EVENT_MOVE, options.move, {\n            once: true\n          });\n        }\n        if (dispatchEvent(element, EVENT_MOVE, {\n          x: x,\n          y: y,\n          oldX: oldX,\n          oldY: oldY,\n          originalEvent: _originalEvent\n        }) === false) {\n          return this;\n        }\n        imageData.x = x;\n        imageData.y = y;\n        imageData.left = x;\n        imageData.top = y;\n        this.moving = true;\n        this.renderImage(function () {\n          _this3.moving = false;\n          if (isFunction(options.moved)) {\n            addListener(element, EVENT_MOVED, options.moved, {\n              once: true\n            });\n          }\n          dispatchEvent(element, EVENT_MOVED, {\n            x: x,\n            y: y,\n            oldX: oldX,\n            oldY: oldY,\n            originalEvent: _originalEvent\n          }, {\n            cancelable: false\n          });\n        });\n      }\n    }\n    return this;\n  },\n  /**\n   * Rotate the image with a relative degree.\n   * @param {number} degree - The rotate degree.\n   * @returns {Viewer} this\n   */\n  rotate: function rotate(degree) {\n    this.rotateTo((this.imageData.rotate || 0) + Number(degree));\n    return this;\n  },\n  /**\n   * Rotate the image to an absolute degree.\n   * @param {number} degree - The rotate degree.\n   * @returns {Viewer} this\n   */\n  rotateTo: function rotateTo(degree) {\n    var _this4 = this;\n    var element = this.element,\n      options = this.options,\n      imageData = this.imageData;\n    degree = Number(degree);\n    if (isNumber(degree) && this.viewed && !this.played && options.rotatable) {\n      var oldDegree = imageData.rotate;\n      if (isFunction(options.rotate)) {\n        addListener(element, EVENT_ROTATE, options.rotate, {\n          once: true\n        });\n      }\n      if (dispatchEvent(element, EVENT_ROTATE, {\n        degree: degree,\n        oldDegree: oldDegree\n      }) === false) {\n        return this;\n      }\n      imageData.rotate = degree;\n      this.rotating = true;\n      this.renderImage(function () {\n        _this4.rotating = false;\n        if (isFunction(options.rotated)) {\n          addListener(element, EVENT_ROTATED, options.rotated, {\n            once: true\n          });\n        }\n        dispatchEvent(element, EVENT_ROTATED, {\n          degree: degree,\n          oldDegree: oldDegree\n        }, {\n          cancelable: false\n        });\n      });\n    }\n    return this;\n  },\n  /**\n   * Scale the image on the x-axis.\n   * @param {number} scaleX - The scale ratio on the x-axis.\n   * @returns {Viewer} this\n   */\n  scaleX: function scaleX(_scaleX) {\n    this.scale(_scaleX, this.imageData.scaleY);\n    return this;\n  },\n  /**\n   * Scale the image on the y-axis.\n   * @param {number} scaleY - The scale ratio on the y-axis.\n   * @returns {Viewer} this\n   */\n  scaleY: function scaleY(_scaleY) {\n    this.scale(this.imageData.scaleX, _scaleY);\n    return this;\n  },\n  /**\n   * Scale the image.\n   * @param {number} scaleX - The scale ratio on the x-axis.\n   * @param {number} [scaleY=scaleX] - The scale ratio on the y-axis.\n   * @returns {Viewer} this\n   */\n  scale: function scale(scaleX) {\n    var _this5 = this;\n    var scaleY = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : scaleX;\n    var element = this.element,\n      options = this.options,\n      imageData = this.imageData;\n    scaleX = Number(scaleX);\n    scaleY = Number(scaleY);\n    if (this.viewed && !this.played && options.scalable) {\n      var oldScaleX = imageData.scaleX;\n      var oldScaleY = imageData.scaleY;\n      var changed = false;\n      if (isNumber(scaleX)) {\n        changed = true;\n      } else {\n        scaleX = oldScaleX;\n      }\n      if (isNumber(scaleY)) {\n        changed = true;\n      } else {\n        scaleY = oldScaleY;\n      }\n      if (changed) {\n        if (isFunction(options.scale)) {\n          addListener(element, EVENT_SCALE, options.scale, {\n            once: true\n          });\n        }\n        if (dispatchEvent(element, EVENT_SCALE, {\n          scaleX: scaleX,\n          scaleY: scaleY,\n          oldScaleX: oldScaleX,\n          oldScaleY: oldScaleY\n        }) === false) {\n          return this;\n        }\n        imageData.scaleX = scaleX;\n        imageData.scaleY = scaleY;\n        this.scaling = true;\n        this.renderImage(function () {\n          _this5.scaling = false;\n          if (isFunction(options.scaled)) {\n            addListener(element, EVENT_SCALED, options.scaled, {\n              once: true\n            });\n          }\n          dispatchEvent(element, EVENT_SCALED, {\n            scaleX: scaleX,\n            scaleY: scaleY,\n            oldScaleX: oldScaleX,\n            oldScaleY: oldScaleY\n          }, {\n            cancelable: false\n          });\n        });\n      }\n    }\n    return this;\n  },\n  /**\n   * Zoom the image with a relative ratio.\n   * @param {number} ratio - The target ratio.\n   * @param {boolean} [showTooltip=false] - Indicates whether to show the tooltip.\n   * @param {Object} [pivot] - The pivot point coordinate for zooming.\n   * @param {Event} [_originalEvent=null] - The original event if any.\n   * @returns {Viewer} this\n   */\n  zoom: function zoom(ratio) {\n    var showTooltip = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;\n    var pivot = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;\n    var _originalEvent = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;\n    var imageData = this.imageData;\n    ratio = Number(ratio);\n    if (ratio < 0) {\n      ratio = 1 / (1 - ratio);\n    } else {\n      ratio = 1 + ratio;\n    }\n    this.zoomTo(imageData.width * ratio / imageData.naturalWidth, showTooltip, pivot, _originalEvent);\n    return this;\n  },\n  /**\n   * Zoom the image to an absolute ratio.\n   * @param {number} ratio - The target ratio.\n   * @param {boolean} [showTooltip] - Indicates whether to show the tooltip.\n   * @param {Object} [pivot] - The pivot point coordinate for zooming.\n   * @param {Event} [_originalEvent=null] - The original event if any.\n   * @param {Event} [_zoomable=false] - Indicates if the current zoom is available or not.\n   * @returns {Viewer} this\n   */\n  zoomTo: function zoomTo(ratio) {\n    var _this6 = this;\n    var showTooltip = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;\n    var pivot = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;\n    var _originalEvent = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;\n    var _zoomable = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;\n    var element = this.element,\n      options = this.options,\n      pointers = this.pointers,\n      imageData = this.imageData;\n    var x = imageData.x,\n      y = imageData.y,\n      width = imageData.width,\n      height = imageData.height,\n      naturalWidth = imageData.naturalWidth,\n      naturalHeight = imageData.naturalHeight;\n    ratio = Math.max(0, ratio);\n    if (isNumber(ratio) && this.viewed && !this.played && (_zoomable || options.zoomable)) {\n      if (!_zoomable) {\n        var minZoomRatio = Math.max(0.01, options.minZoomRatio);\n        var maxZoomRatio = Math.min(100, options.maxZoomRatio);\n        ratio = Math.min(Math.max(ratio, minZoomRatio), maxZoomRatio);\n      }\n      if (_originalEvent) {\n        switch (_originalEvent.type) {\n          case 'wheel':\n            if (options.zoomRatio >= 0.055 && ratio > 0.95 && ratio < 1.05) {\n              ratio = 1;\n            }\n            break;\n          case 'pointermove':\n          case 'touchmove':\n          case 'mousemove':\n            if (ratio > 0.99 && ratio < 1.01) {\n              ratio = 1;\n            }\n            break;\n        }\n      }\n      var newWidth = naturalWidth * ratio;\n      var newHeight = naturalHeight * ratio;\n      var offsetWidth = newWidth - width;\n      var offsetHeight = newHeight - height;\n      var oldRatio = imageData.ratio;\n      if (isFunction(options.zoom)) {\n        addListener(element, EVENT_ZOOM, options.zoom, {\n          once: true\n        });\n      }\n      if (dispatchEvent(element, EVENT_ZOOM, {\n        ratio: ratio,\n        oldRatio: oldRatio,\n        originalEvent: _originalEvent\n      }) === false) {\n        return this;\n      }\n      this.zooming = true;\n      if (_originalEvent) {\n        var offset = getOffset(this.viewer);\n        var center = pointers && Object.keys(pointers).length > 0 ? getPointersCenter(pointers) : {\n          pageX: _originalEvent.pageX,\n          pageY: _originalEvent.pageY\n        };\n\n        // Zoom from the triggering point of the event\n        imageData.x -= offsetWidth * ((center.pageX - offset.left - x) / width);\n        imageData.y -= offsetHeight * ((center.pageY - offset.top - y) / height);\n      } else if (isPlainObject(pivot) && isNumber(pivot.x) && isNumber(pivot.y)) {\n        imageData.x -= offsetWidth * ((pivot.x - x) / width);\n        imageData.y -= offsetHeight * ((pivot.y - y) / height);\n      } else {\n        // Zoom from the center of the image\n        imageData.x -= offsetWidth / 2;\n        imageData.y -= offsetHeight / 2;\n      }\n      imageData.left = imageData.x;\n      imageData.top = imageData.y;\n      imageData.width = newWidth;\n      imageData.height = newHeight;\n      imageData.oldRatio = oldRatio;\n      imageData.ratio = ratio;\n      this.renderImage(function () {\n        _this6.zooming = false;\n        if (isFunction(options.zoomed)) {\n          addListener(element, EVENT_ZOOMED, options.zoomed, {\n            once: true\n          });\n        }\n        dispatchEvent(element, EVENT_ZOOMED, {\n          ratio: ratio,\n          oldRatio: oldRatio,\n          originalEvent: _originalEvent\n        }, {\n          cancelable: false\n        });\n      });\n      if (showTooltip) {\n        this.tooltip();\n      }\n    }\n    return this;\n  },\n  /**\n   * Play the images\n   * @param {boolean|FullscreenOptions} [fullscreen=false] - Indicate if request fullscreen or not.\n   * @returns {Viewer} this\n   */\n  play: function play() {\n    var _this7 = this;\n    var fullscreen = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;\n    if (!this.isShown || this.played) {\n      return this;\n    }\n    var element = this.element,\n      options = this.options;\n    if (isFunction(options.play)) {\n      addListener(element, EVENT_PLAY, options.play, {\n        once: true\n      });\n    }\n    if (dispatchEvent(element, EVENT_PLAY) === false) {\n      return this;\n    }\n    var player = this.player;\n    var onLoad = this.loadImage.bind(this);\n    var list = [];\n    var total = 0;\n    var index = 0;\n    this.played = true;\n    this.onLoadWhenPlay = onLoad;\n    if (fullscreen) {\n      this.requestFullscreen(fullscreen);\n    }\n    addClass(player, CLASS_SHOW);\n    forEach(this.items, function (item, i) {\n      var img = item.querySelector('img');\n      var image = document.createElement('img');\n      image.src = getData(img, 'originalUrl');\n      image.alt = img.getAttribute('alt');\n      image.referrerPolicy = img.referrerPolicy;\n      total += 1;\n      addClass(image, CLASS_FADE);\n      toggleClass(image, CLASS_TRANSITION, options.transition);\n      if (hasClass(item, CLASS_ACTIVE)) {\n        addClass(image, CLASS_IN);\n        index = i;\n      }\n      list.push(image);\n      addListener(image, EVENT_LOAD, onLoad, {\n        once: true\n      });\n      player.appendChild(image);\n    });\n    if (isNumber(options.interval) && options.interval > 0) {\n      var _prev = function prev() {\n        clearTimeout(_this7.playing.timeout);\n        removeClass(list[index], CLASS_IN);\n        index -= 1;\n        index = index >= 0 ? index : total - 1;\n        addClass(list[index], CLASS_IN);\n        _this7.playing.timeout = setTimeout(_prev, options.interval);\n      };\n      var _next = function next() {\n        clearTimeout(_this7.playing.timeout);\n        removeClass(list[index], CLASS_IN);\n        index += 1;\n        index = index < total ? index : 0;\n        addClass(list[index], CLASS_IN);\n        _this7.playing.timeout = setTimeout(_next, options.interval);\n      };\n      if (total > 1) {\n        this.playing = {\n          prev: _prev,\n          next: _next,\n          timeout: setTimeout(_next, options.interval)\n        };\n      }\n    }\n    return this;\n  },\n  // Stop play\n  stop: function stop() {\n    var _this8 = this;\n    if (!this.played) {\n      return this;\n    }\n    var element = this.element,\n      options = this.options;\n    if (isFunction(options.stop)) {\n      addListener(element, EVENT_STOP, options.stop, {\n        once: true\n      });\n    }\n    if (dispatchEvent(element, EVENT_STOP) === false) {\n      return this;\n    }\n    var player = this.player;\n    clearTimeout(this.playing.timeout);\n    this.playing = false;\n    this.played = false;\n    forEach(player.getElementsByTagName('img'), function (image) {\n      removeListener(image, EVENT_LOAD, _this8.onLoadWhenPlay);\n    });\n    removeClass(player, CLASS_SHOW);\n    player.innerHTML = '';\n    this.exitFullscreen();\n    return this;\n  },\n  // Enter modal mode (only available in inline mode)\n  full: function full() {\n    var _this9 = this;\n    var options = this.options,\n      viewer = this.viewer,\n      image = this.image,\n      list = this.list;\n    if (!this.isShown || this.played || this.fulled || !options.inline) {\n      return this;\n    }\n    this.fulled = true;\n    this.open();\n    addClass(this.button, CLASS_FULLSCREEN_EXIT);\n    if (options.transition) {\n      removeClass(list, CLASS_TRANSITION);\n      if (this.viewed) {\n        removeClass(image, CLASS_TRANSITION);\n      }\n    }\n    addClass(viewer, CLASS_FIXED);\n    viewer.setAttribute('role', 'dialog');\n    viewer.setAttribute('aria-labelledby', this.title.id);\n    viewer.setAttribute('aria-modal', true);\n    viewer.removeAttribute('style');\n    setStyle(viewer, {\n      zIndex: options.zIndex\n    });\n    if (options.focus) {\n      this.enforceFocus();\n    }\n    this.initContainer();\n    this.viewerData = assign({}, this.containerData);\n    this.renderList();\n    if (this.viewed) {\n      this.initImage(function () {\n        _this9.renderImage(function () {\n          if (options.transition) {\n            setTimeout(function () {\n              addClass(image, CLASS_TRANSITION);\n              addClass(list, CLASS_TRANSITION);\n            }, 0);\n          }\n        });\n      });\n    }\n    return this;\n  },\n  // Exit modal mode (only available in inline mode)\n  exit: function exit() {\n    var _this10 = this;\n    var options = this.options,\n      viewer = this.viewer,\n      image = this.image,\n      list = this.list;\n    if (!this.isShown || this.played || !this.fulled || !options.inline) {\n      return this;\n    }\n    this.fulled = false;\n    this.close();\n    removeClass(this.button, CLASS_FULLSCREEN_EXIT);\n    if (options.transition) {\n      removeClass(list, CLASS_TRANSITION);\n      if (this.viewed) {\n        removeClass(image, CLASS_TRANSITION);\n      }\n    }\n    if (options.focus) {\n      this.clearEnforceFocus();\n    }\n    viewer.removeAttribute('role');\n    viewer.removeAttribute('aria-labelledby');\n    viewer.removeAttribute('aria-modal');\n    removeClass(viewer, CLASS_FIXED);\n    setStyle(viewer, {\n      zIndex: options.zIndexInline\n    });\n    this.viewerData = assign({}, this.parentData);\n    this.renderViewer();\n    this.renderList();\n    if (this.viewed) {\n      this.initImage(function () {\n        _this10.renderImage(function () {\n          if (options.transition) {\n            setTimeout(function () {\n              addClass(image, CLASS_TRANSITION);\n              addClass(list, CLASS_TRANSITION);\n            }, 0);\n          }\n        });\n      });\n    }\n    return this;\n  },\n  // Show the current ratio of the image with percentage\n  tooltip: function tooltip() {\n    var _this11 = this;\n    var options = this.options,\n      tooltipBox = this.tooltipBox,\n      imageData = this.imageData;\n    if (!this.viewed || this.played || !options.tooltip) {\n      return this;\n    }\n    tooltipBox.textContent = \"\".concat(Math.round(imageData.ratio * 100), \"%\");\n    if (!this.tooltipping) {\n      if (options.transition) {\n        if (this.fading) {\n          dispatchEvent(tooltipBox, EVENT_TRANSITION_END);\n        }\n        addClass(tooltipBox, CLASS_SHOW);\n        addClass(tooltipBox, CLASS_FADE);\n        addClass(tooltipBox, CLASS_TRANSITION);\n        tooltipBox.removeAttribute('aria-hidden');\n\n        // Force reflow to enable CSS3 transition\n        tooltipBox.initialOffsetWidth = tooltipBox.offsetWidth;\n        addClass(tooltipBox, CLASS_IN);\n      } else {\n        addClass(tooltipBox, CLASS_SHOW);\n        tooltipBox.removeAttribute('aria-hidden');\n      }\n    } else {\n      clearTimeout(this.tooltipping);\n    }\n    this.tooltipping = setTimeout(function () {\n      if (options.transition) {\n        addListener(tooltipBox, EVENT_TRANSITION_END, function () {\n          removeClass(tooltipBox, CLASS_SHOW);\n          removeClass(tooltipBox, CLASS_FADE);\n          removeClass(tooltipBox, CLASS_TRANSITION);\n          tooltipBox.setAttribute('aria-hidden', true);\n          _this11.fading = false;\n        }, {\n          once: true\n        });\n        removeClass(tooltipBox, CLASS_IN);\n        _this11.fading = true;\n      } else {\n        removeClass(tooltipBox, CLASS_SHOW);\n        tooltipBox.setAttribute('aria-hidden', true);\n      }\n      _this11.tooltipping = false;\n    }, 1000);\n    return this;\n  },\n  /**\n   * Toggle the image size between its current size and natural size\n   * @param {Event} [_originalEvent=null] - The original event if any.\n   * @returns {Viewer} this\n   */\n  toggle: function toggle() {\n    var _originalEvent = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;\n    if (this.imageData.ratio === 1) {\n      this.zoomTo(this.imageData.oldRatio, true, null, _originalEvent);\n    } else {\n      this.zoomTo(1, true, null, _originalEvent);\n    }\n    return this;\n  },\n  // Reset the image to its initial state\n  reset: function reset() {\n    if (this.viewed && !this.played) {\n      this.imageData = assign({}, this.initialImageData);\n      this.renderImage();\n    }\n    return this;\n  },\n  // Update viewer when images changed\n  update: function update() {\n    var _this12 = this;\n    var element = this.element,\n      options = this.options,\n      isImg = this.isImg;\n\n    // Destroy viewer if the target image was deleted\n    if (isImg && !element.parentNode) {\n      return this.destroy();\n    }\n    var images = [];\n    forEach(isImg ? [element] : element.querySelectorAll('img'), function (image) {\n      if (isFunction(options.filter)) {\n        if (options.filter.call(_this12, image)) {\n          images.push(image);\n        }\n      } else if (_this12.getImageURL(image)) {\n        images.push(image);\n      }\n    });\n    if (!images.length) {\n      return this;\n    }\n    this.images = images;\n    this.length = images.length;\n    if (this.ready) {\n      var changedIndexes = [];\n      forEach(this.items, function (item, i) {\n        var img = item.querySelector('img');\n        var image = images[i];\n        if (image && img) {\n          if (image.src !== img.src\n\n          // Title changed (#408)\n          || image.alt !== img.alt) {\n            changedIndexes.push(i);\n          }\n        } else {\n          changedIndexes.push(i);\n        }\n      });\n      setStyle(this.list, {\n        width: 'auto'\n      });\n      this.initList();\n      if (this.isShown) {\n        if (this.length) {\n          if (this.viewed) {\n            var changedIndex = changedIndexes.indexOf(this.index);\n            if (changedIndex >= 0) {\n              this.viewed = false;\n              this.view(Math.max(Math.min(this.index - changedIndex, this.length - 1), 0));\n            } else {\n              var activeItem = this.items[this.index];\n\n              // Reactivate the current viewing item after reset the list.\n              addClass(activeItem, CLASS_ACTIVE);\n              activeItem.setAttribute('aria-selected', true);\n            }\n          }\n        } else {\n          this.image = null;\n          this.viewed = false;\n          this.index = 0;\n          this.imageData = {};\n          this.canvas.innerHTML = '';\n          this.title.innerHTML = '';\n        }\n      }\n    } else {\n      this.build();\n    }\n    return this;\n  },\n  // Destroy the viewer\n  destroy: function destroy() {\n    var element = this.element,\n      options = this.options;\n    if (!element[NAMESPACE]) {\n      return this;\n    }\n    this.destroyed = true;\n    if (this.ready) {\n      if (this.played) {\n        this.stop();\n      }\n      if (options.inline) {\n        if (this.fulled) {\n          this.exit();\n        }\n        this.unbind();\n      } else if (this.isShown) {\n        if (this.viewing) {\n          if (this.imageRendering) {\n            this.imageRendering.abort();\n          } else if (this.imageInitializing) {\n            this.imageInitializing.abort();\n          }\n        }\n        if (this.hiding) {\n          this.transitioning.abort();\n        }\n        this.hidden();\n      } else if (this.showing) {\n        this.transitioning.abort();\n        this.hidden();\n      }\n      this.ready = false;\n      this.viewer.parentNode.removeChild(this.viewer);\n    } else if (options.inline) {\n      if (this.delaying) {\n        this.delaying.abort();\n      } else if (this.initializing) {\n        this.initializing.abort();\n      }\n    }\n    if (!options.inline) {\n      removeListener(element, EVENT_CLICK, this.onStart);\n    }\n    element[NAMESPACE] = undefined;\n    return this;\n  }\n};\n\nvar others = {\n  getImageURL: function getImageURL(image) {\n    var url = this.options.url;\n    if (isString(url)) {\n      url = image.getAttribute(url);\n    } else if (isFunction(url)) {\n      url = url.call(this, image);\n    } else {\n      url = '';\n    }\n    return url;\n  },\n  enforceFocus: function enforceFocus() {\n    var _this = this;\n    this.clearEnforceFocus();\n    addListener(document, EVENT_FOCUSIN, this.onFocusin = function (event) {\n      var viewer = _this.viewer;\n      var target = event.target;\n      if (target === document || target === viewer || viewer.contains(target)) {\n        return;\n      }\n      while (target) {\n        // Avoid conflicts with other modals (#474, #540)\n        if (target.getAttribute('tabindex') !== null || target.getAttribute('aria-modal') === 'true') {\n          return;\n        }\n        target = target.parentElement;\n      }\n      viewer.focus();\n    });\n  },\n  clearEnforceFocus: function clearEnforceFocus() {\n    if (this.onFocusin) {\n      removeListener(document, EVENT_FOCUSIN, this.onFocusin);\n      this.onFocusin = null;\n    }\n  },\n  open: function open() {\n    var body = this.body;\n    addClass(body, CLASS_OPEN);\n    if (this.scrollbarWidth > 0) {\n      body.style.paddingRight = \"\".concat(this.scrollbarWidth + (parseFloat(this.initialBodyComputedPaddingRight) || 0), \"px\");\n    }\n  },\n  close: function close() {\n    var body = this.body;\n    removeClass(body, CLASS_OPEN);\n    if (this.scrollbarWidth > 0) {\n      body.style.paddingRight = this.initialBodyPaddingRight;\n    }\n  },\n  shown: function shown() {\n    var element = this.element,\n      options = this.options,\n      viewer = this.viewer;\n    this.fulled = true;\n    this.isShown = true;\n    this.render();\n    this.bind();\n    this.showing = false;\n    if (options.focus) {\n      viewer.focus();\n      this.enforceFocus();\n    }\n    if (isFunction(options.shown)) {\n      addListener(element, EVENT_SHOWN, options.shown, {\n        once: true\n      });\n    }\n    if (dispatchEvent(element, EVENT_SHOWN) === false) {\n      return;\n    }\n    if (this.ready && this.isShown && !this.hiding) {\n      this.view(this.index);\n    }\n  },\n  hidden: function hidden() {\n    var element = this.element,\n      options = this.options,\n      viewer = this.viewer;\n    if (options.fucus) {\n      this.clearEnforceFocus();\n    }\n    this.close();\n    this.unbind();\n    addClass(viewer, CLASS_HIDE);\n    viewer.removeAttribute('role');\n    viewer.removeAttribute('aria-labelledby');\n    viewer.removeAttribute('aria-modal');\n    viewer.setAttribute('aria-hidden', true);\n    this.resetList();\n    this.resetImage();\n    this.fulled = false;\n    this.viewed = false;\n    this.isShown = false;\n    this.hiding = false;\n    if (!this.destroyed) {\n      if (isFunction(options.hidden)) {\n        addListener(element, EVENT_HIDDEN, options.hidden, {\n          once: true\n        });\n      }\n      dispatchEvent(element, EVENT_HIDDEN, null, {\n        cancelable: false\n      });\n    }\n  },\n  requestFullscreen: function requestFullscreen(options) {\n    var document = this.element.ownerDocument;\n    if (this.fulled && !(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement)) {\n      var documentElement = document.documentElement;\n\n      // Element.requestFullscreen()\n      if (documentElement.requestFullscreen) {\n        // Avoid TypeError when convert `options` to dictionary\n        if (isPlainObject(options)) {\n          documentElement.requestFullscreen(options);\n        } else {\n          documentElement.requestFullscreen();\n        }\n      } else if (documentElement.webkitRequestFullscreen) {\n        documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);\n      } else if (documentElement.mozRequestFullScreen) {\n        documentElement.mozRequestFullScreen();\n      } else if (documentElement.msRequestFullscreen) {\n        documentElement.msRequestFullscreen();\n      }\n    }\n  },\n  exitFullscreen: function exitFullscreen() {\n    var document = this.element.ownerDocument;\n    if (this.fulled && (document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement)) {\n      // Document.exitFullscreen()\n      if (document.exitFullscreen) {\n        document.exitFullscreen();\n      } else if (document.webkitExitFullscreen) {\n        document.webkitExitFullscreen();\n      } else if (document.mozCancelFullScreen) {\n        document.mozCancelFullScreen();\n      } else if (document.msExitFullscreen) {\n        document.msExitFullscreen();\n      }\n    }\n  },\n  change: function change(event) {\n    var options = this.options,\n      pointers = this.pointers;\n    var pointer = pointers[Object.keys(pointers)[0]];\n\n    // In the case of the `pointers` object is empty (#421)\n    if (!pointer) {\n      return;\n    }\n    var offsetX = pointer.endX - pointer.startX;\n    var offsetY = pointer.endY - pointer.startY;\n    switch (this.action) {\n      // Move the current image\n      case ACTION_MOVE:\n        if (offsetX !== 0 || offsetY !== 0) {\n          this.pointerMoved = true;\n          this.move(offsetX, offsetY, event);\n        }\n        break;\n\n      // Zoom the current image\n      case ACTION_ZOOM:\n        this.zoom(getMaxZoomRatio(pointers), false, null, event);\n        break;\n      case ACTION_SWITCH:\n        {\n          this.action = 'switched';\n          var absoluteOffsetX = Math.abs(offsetX);\n          if (absoluteOffsetX > 1 && absoluteOffsetX > Math.abs(offsetY)) {\n            // Empty `pointers` as `touchend` event will not be fired after swiped in iOS browsers.\n            this.pointers = {};\n            if (offsetX > 1) {\n              this.prev(options.loop);\n            } else if (offsetX < -1) {\n              this.next(options.loop);\n            }\n          }\n          break;\n        }\n    }\n\n    // Override\n    forEach(pointers, function (p) {\n      p.startX = p.endX;\n      p.startY = p.endY;\n    });\n  },\n  isSwitchable: function isSwitchable() {\n    var imageData = this.imageData,\n      viewerData = this.viewerData;\n    return this.length > 1 && imageData.x >= 0 && imageData.y >= 0 && imageData.width <= viewerData.width && imageData.height <= viewerData.height;\n  }\n};\n\nvar AnotherViewer = WINDOW.Viewer;\nvar getUniqueID = function (id) {\n  return function () {\n    id += 1;\n    return id;\n  };\n}(-1);\nvar Viewer = /*#__PURE__*/function () {\n  /**\n   * Create a new Viewer.\n   * @param {Element} element - The target element for viewing.\n   * @param {Object} [options={}] - The configuration options.\n   */\n  function Viewer(element) {\n    var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n    _classCallCheck(this, Viewer);\n    if (!element || element.nodeType !== 1) {\n      throw new Error('The first argument is required and must be an element.');\n    }\n    this.element = element;\n    this.options = assign({}, DEFAULTS, isPlainObject(options) && options);\n    this.action = false;\n    this.fading = false;\n    this.fulled = false;\n    this.hiding = false;\n    this.imageClicked = false;\n    this.imageData = {};\n    this.index = this.options.initialViewIndex;\n    this.isImg = false;\n    this.isShown = false;\n    this.length = 0;\n    this.moving = false;\n    this.played = false;\n    this.playing = false;\n    this.pointers = {};\n    this.ready = false;\n    this.rotating = false;\n    this.scaling = false;\n    this.showing = false;\n    this.timeout = false;\n    this.tooltipping = false;\n    this.viewed = false;\n    this.viewing = false;\n    this.wheeling = false;\n    this.zooming = false;\n    this.pointerMoved = false;\n    this.id = getUniqueID();\n    this.init();\n  }\n  return _createClass(Viewer, [{\n    key: \"init\",\n    value: function init() {\n      var _this = this;\n      var element = this.element,\n        options = this.options;\n      if (element[NAMESPACE]) {\n        return;\n      }\n      element[NAMESPACE] = this;\n\n      // The `focus` option requires the `keyboard` option set to `true`.\n      if (options.focus && !options.keyboard) {\n        options.focus = false;\n      }\n      var isImg = element.localName === 'img';\n      var images = [];\n      forEach(isImg ? [element] : element.querySelectorAll('img'), function (image) {\n        if (isFunction(options.filter)) {\n          if (options.filter.call(_this, image)) {\n            images.push(image);\n          }\n        } else if (_this.getImageURL(image)) {\n          images.push(image);\n        }\n      });\n      this.isImg = isImg;\n      this.length = images.length;\n      this.images = images;\n      this.initBody();\n\n      // Override `transition` option if it is not supported\n      if (isUndefined(document.createElement(NAMESPACE).style.transition)) {\n        options.transition = false;\n      }\n      if (options.inline) {\n        var count = 0;\n        var progress = function progress() {\n          count += 1;\n          if (count === _this.length) {\n            var timeout;\n            _this.initializing = false;\n            _this.delaying = {\n              abort: function abort() {\n                clearTimeout(timeout);\n              }\n            };\n\n            // build asynchronously to keep `this.viewer` is accessible in `ready` event handler.\n            timeout = setTimeout(function () {\n              _this.delaying = false;\n              _this.build();\n            }, 0);\n          }\n        };\n        this.initializing = {\n          abort: function abort() {\n            forEach(images, function (image) {\n              if (!image.complete) {\n                removeListener(image, EVENT_LOAD, progress);\n                removeListener(image, EVENT_ERROR, progress);\n              }\n            });\n          }\n        };\n        forEach(images, function (image) {\n          if (image.complete) {\n            progress();\n          } else {\n            var onLoad;\n            var onError;\n            addListener(image, EVENT_LOAD, onLoad = function onLoad() {\n              removeListener(image, EVENT_ERROR, onError);\n              progress();\n            }, {\n              once: true\n            });\n            addListener(image, EVENT_ERROR, onError = function onError() {\n              removeListener(image, EVENT_LOAD, onLoad);\n              progress();\n            }, {\n              once: true\n            });\n          }\n        });\n      } else {\n        addListener(element, EVENT_CLICK, this.onStart = function (_ref) {\n          var target = _ref.target;\n          if (target.localName === 'img' && (!isFunction(options.filter) || options.filter.call(_this, target))) {\n            _this.view(_this.images.indexOf(target));\n          }\n        });\n      }\n    }\n  }, {\n    key: \"build\",\n    value: function build() {\n      if (this.ready) {\n        return;\n      }\n      var element = this.element,\n        options = this.options;\n      var parent = element.parentNode;\n      var template = document.createElement('div');\n      template.innerHTML = TEMPLATE;\n      var viewer = template.querySelector(\".\".concat(NAMESPACE, \"-container\"));\n      var title = viewer.querySelector(\".\".concat(NAMESPACE, \"-title\"));\n      var toolbar = viewer.querySelector(\".\".concat(NAMESPACE, \"-toolbar\"));\n      var navbar = viewer.querySelector(\".\".concat(NAMESPACE, \"-navbar\"));\n      var button = viewer.querySelector(\".\".concat(NAMESPACE, \"-button\"));\n      var canvas = viewer.querySelector(\".\".concat(NAMESPACE, \"-canvas\"));\n      this.parent = parent;\n      this.viewer = viewer;\n      this.title = title;\n      this.toolbar = toolbar;\n      this.navbar = navbar;\n      this.button = button;\n      this.canvas = canvas;\n      this.footer = viewer.querySelector(\".\".concat(NAMESPACE, \"-footer\"));\n      this.tooltipBox = viewer.querySelector(\".\".concat(NAMESPACE, \"-tooltip\"));\n      this.player = viewer.querySelector(\".\".concat(NAMESPACE, \"-player\"));\n      this.list = viewer.querySelector(\".\".concat(NAMESPACE, \"-list\"));\n      viewer.id = \"\".concat(NAMESPACE).concat(this.id);\n      title.id = \"\".concat(NAMESPACE, \"Title\").concat(this.id);\n      addClass(title, !options.title ? CLASS_HIDE : getResponsiveClass(Array.isArray(options.title) ? options.title[0] : options.title));\n      addClass(navbar, !options.navbar ? CLASS_HIDE : getResponsiveClass(options.navbar));\n      toggleClass(button, CLASS_HIDE, !options.button);\n      if (options.keyboard) {\n        button.setAttribute('tabindex', 0);\n      }\n      if (options.backdrop) {\n        addClass(viewer, \"\".concat(NAMESPACE, \"-backdrop\"));\n        if (!options.inline && options.backdrop !== 'static') {\n          setData(canvas, DATA_ACTION, 'hide');\n        }\n      }\n      if (isString(options.className) && options.className) {\n        // In case there are multiple class names\n        options.className.split(REGEXP_SPACES).forEach(function (className) {\n          addClass(viewer, className);\n        });\n      }\n      if (options.toolbar) {\n        var list = document.createElement('ul');\n        var custom = isPlainObject(options.toolbar);\n        var zoomButtons = BUTTONS.slice(0, 3);\n        var rotateButtons = BUTTONS.slice(7, 9);\n        var scaleButtons = BUTTONS.slice(9);\n        if (!custom) {\n          addClass(toolbar, getResponsiveClass(options.toolbar));\n        }\n        forEach(custom ? options.toolbar : BUTTONS, function (value, index) {\n          var deep = custom && isPlainObject(value);\n          var name = custom ? hyphenate(index) : value;\n          var show = deep && !isUndefined(value.show) ? value.show : value;\n          if (!show || !options.zoomable && zoomButtons.indexOf(name) !== -1 || !options.rotatable && rotateButtons.indexOf(name) !== -1 || !options.scalable && scaleButtons.indexOf(name) !== -1) {\n            return;\n          }\n          var size = deep && !isUndefined(value.size) ? value.size : value;\n          var click = deep && !isUndefined(value.click) ? value.click : value;\n          var item = document.createElement('li');\n          if (options.keyboard) {\n            item.setAttribute('tabindex', 0);\n          }\n          item.setAttribute('role', 'button');\n          addClass(item, \"\".concat(NAMESPACE, \"-\").concat(name));\n          if (!isFunction(click)) {\n            setData(item, DATA_ACTION, name);\n          }\n          if (isNumber(show)) {\n            addClass(item, getResponsiveClass(show));\n          }\n          if (['small', 'large'].indexOf(size) !== -1) {\n            addClass(item, \"\".concat(NAMESPACE, \"-\").concat(size));\n          } else if (name === 'play') {\n            addClass(item, \"\".concat(NAMESPACE, \"-large\"));\n          }\n          if (isFunction(click)) {\n            addListener(item, EVENT_CLICK, click);\n          }\n          list.appendChild(item);\n        });\n        toolbar.appendChild(list);\n      } else {\n        addClass(toolbar, CLASS_HIDE);\n      }\n      if (!options.rotatable) {\n        var rotates = toolbar.querySelectorAll('li[class*=\"rotate\"]');\n        addClass(rotates, CLASS_INVISIBLE);\n        forEach(rotates, function (rotate) {\n          toolbar.appendChild(rotate);\n        });\n      }\n      if (options.inline) {\n        addClass(button, CLASS_FULLSCREEN);\n        setStyle(viewer, {\n          zIndex: options.zIndexInline\n        });\n        if (window.getComputedStyle(parent).position === 'static') {\n          setStyle(parent, {\n            position: 'relative'\n          });\n        }\n        parent.insertBefore(viewer, element.nextSibling);\n      } else {\n        addClass(button, CLASS_CLOSE);\n        addClass(viewer, CLASS_FIXED);\n        addClass(viewer, CLASS_FADE);\n        addClass(viewer, CLASS_HIDE);\n        setStyle(viewer, {\n          zIndex: options.zIndex\n        });\n        var container = options.container;\n        if (isString(container)) {\n          container = element.ownerDocument.querySelector(container);\n        }\n        if (!container) {\n          container = this.body;\n        }\n        container.appendChild(viewer);\n      }\n      if (options.inline) {\n        this.render();\n        this.bind();\n        this.isShown = true;\n      }\n      this.ready = true;\n      if (isFunction(options.ready)) {\n        addListener(element, EVENT_READY, options.ready, {\n          once: true\n        });\n      }\n      if (dispatchEvent(element, EVENT_READY) === false) {\n        this.ready = false;\n        return;\n      }\n      if (this.ready && options.inline) {\n        this.view(this.index);\n      }\n    }\n\n    /**\n     * Get the no conflict viewer class.\n     * @returns {Viewer} The viewer class.\n     */\n  }], [{\n    key: \"noConflict\",\n    value: function noConflict() {\n      window.Viewer = AnotherViewer;\n      return Viewer;\n    }\n\n    /**\n     * Change the default options.\n     * @param {Object} options - The new default options.\n     */\n  }, {\n    key: \"setDefaults\",\n    value: function setDefaults(options) {\n      assign(DEFAULTS, isPlainObject(options) && options);\n    }\n  }]);\n}();\nassign(Viewer.prototype, render, events, handlers, methods, others);\n\nmodule.exports = Viewer;\n"
  },
  {
    "path": "dist/viewer.css",
    "content": "/*!\n * Viewer.js v1.11.7\n * https://fengyuanchen.github.io/viewerjs\n *\n * Copyright 2015-present Chen Fengyuan\n * Released under the MIT license\n *\n * Date: 2024-11-24T04:32:14.526Z\n */\n\n.viewer-zoom-in::before, .viewer-zoom-out::before, .viewer-one-to-one::before, .viewer-reset::before, .viewer-prev::before, .viewer-play::before, .viewer-next::before, .viewer-rotate-left::before, .viewer-rotate-right::before, .viewer-flip-horizontal::before, .viewer-flip-vertical::before, .viewer-fullscreen::before, .viewer-fullscreen-exit::before, .viewer-close::before {\n    background-image: url(\"data:image/svg+xml,%3Csvg xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22 viewBox%3D%220 0 560 40%22%3E%3Cpath fill%3D%22%23fff%22 d%3D%22M49.6 17.9h20.2v3.9H49.6zm123.1 2 10.9-11 2.7 2.8-8.2 8.2 8.2 8.2-2.7 2.7-10.9-10.9zm94 0-10.8-11-2.7 2.8 8.1 8.2-8.1 8.2 2.7 2.7 10.8-10.9zM212 9.3l20.1 10.6L212 30.5V9.3zm161.5 4.6-7.2 6 7.2 5.9v-4h12.4v4l7.3-5.9-7.3-6v4h-12.4v-4zm40.2 12.3 5.9 7.2 5.9-7.2h-4V13.6h4l-5.9-7.3-5.9 7.3h4v12.6h-4zm35.9-16.5h6.3v2h-4.3V16h-2V9.7Zm14 0h6.2V16h-2v-4.3h-4.2v-2Zm6.2 14V30h-6.2v-2h4.2v-4.3h2Zm-14 6.3h-6.2v-6.3h2v4.4h4.3v2Zm-438 .1v-8.3H9.6v-3.9h8.2V9.7h3.9v8.2h8.1v3.9h-8.1v8.3h-3.9zM93.6 9.7h-5.8v3.9h2V30h3.8V9.7zm16.1 0h-5.8v3.9h1.9V30h3.9V9.7zm-11.9 4.1h3.9v3.9h-3.9zm0 8.2h3.9v3.9h-3.9zm244.6-11.7 7.2 5.9-7.2 6v-3.6c-5.4-.4-7.8.8-8.7 2.8-.8 1.7-1.8 4.9 2.8 8.2-6.3-2-7.5-6.9-6-11.3 1.6-4.4 8-5 11.9-4.9v-3.1Zm147.2 13.4h6.3V30h-2v-4.3h-4.3v-2zm14 6.3v-6.3h6.2v2h-4.3V30h-1.9zm6.2-14h-6.2V9.7h1.9V14h4.3v2zm-13.9 0h-6.3v-2h4.3V9.7h2V16zm33.3 12.5 8.6-8.6-8.6-8.7 1.9-1.9 8.6 8.7 8.6-8.7 1.9 1.9-8.6 8.7 8.6 8.6-1.9 2-8.6-8.7-8.6 8.7-1.9-2zM297 10.3l-7.1 5.9 7.2 6v-3.6c5.3-.4 7.7.8 8.7 2.8.8 1.7 1.7 4.9-2.9 8.2 6.3-2 7.5-6.9 6-11.3-1.6-4.4-7.9-5-11.8-4.9v-3.1Zm-157.3-.6c2.3 0 4.4.7 6 2l2.5-3 1.9 9.2h-9.3l2.6-3.1a6.2 6.2 0 0 0-9.9 5.1c0 3.4 2.8 6.3 6.2 6.3 2.8 0 5.1-1.9 6-4.4h4c-1 4.7-5 8.3-10 8.3a10 10 0 0 1-10-10.2 10 10 0 0 1 10-10.2Z%22%2F%3E%3C%2Fsvg%3E\");\n    background-repeat: no-repeat;\n    background-size: 280px;\n    color: transparent;\n    display: block;\n    font-size: 0;\n    height: 20px;\n    line-height: 0;\n    width: 20px;\n  }\n\n.viewer-zoom-in::before {\n  background-position: 0 0;\n  content: 'Zoom In';\n}\n\n.viewer-zoom-out::before {\n  background-position: -20px 0;\n  content: 'Zoom Out';\n}\n\n.viewer-one-to-one::before {\n  background-position: -40px 0;\n  content: 'One to One';\n}\n\n.viewer-reset::before {\n  background-position: -60px 0;\n  content: 'Reset';\n}\n\n.viewer-prev::before {\n  background-position: -80px 0;\n  content: 'Previous';\n}\n\n.viewer-play::before {\n  background-position: -100px 0;\n  content: 'Play';\n}\n\n.viewer-next::before {\n  background-position: -120px 0;\n  content: 'Next';\n}\n\n.viewer-rotate-left::before {\n  background-position: -140px 0;\n  content: 'Rotate Left';\n}\n\n.viewer-rotate-right::before {\n  background-position: -160px 0;\n  content: 'Rotate Right';\n}\n\n.viewer-flip-horizontal::before {\n  background-position: -180px 0;\n  content: 'Flip Horizontal';\n}\n\n.viewer-flip-vertical::before {\n  background-position: -200px 0;\n  content: 'Flip Vertical';\n}\n\n.viewer-fullscreen::before {\n  background-position: -220px 0;\n  content: 'Enter Full Screen';\n}\n\n.viewer-fullscreen-exit::before {\n  background-position: -240px 0;\n  content: 'Exit Full Screen';\n}\n\n.viewer-close::before {\n  background-position: -260px 0;\n  content: 'Close';\n}\n\n.viewer-container {\n  bottom: 0;\n  direction: ltr;\n  font-size: 0;\n  left: 0;\n  line-height: 0;\n  overflow: hidden;\n  position: absolute;\n  right: 0;\n  -webkit-tap-highlight-color: transparent;\n  top: 0;\n  -ms-touch-action: none;\n      touch-action: none;\n  -webkit-touch-callout: none;\n  -webkit-user-select: none;\n     -moz-user-select: none;\n      -ms-user-select: none;\n          user-select: none;\n}\n\n.viewer-container::-moz-selection, .viewer-container *::-moz-selection {\n    background-color: transparent;\n  }\n\n.viewer-container::selection,\n  .viewer-container *::selection {\n    background-color: transparent;\n  }\n\n.viewer-container:focus {\n    outline: 0;\n  }\n\n.viewer-container img {\n    display: block;\n    height: auto;\n    max-height: none !important;\n    max-width: none !important;\n    min-height: 0 !important;\n    min-width: 0 !important;\n    width: 100%;\n  }\n\n.viewer-canvas {\n  bottom: 0;\n  left: 0;\n  overflow: hidden;\n  position: absolute;\n  right: 0;\n  top: 0;\n}\n\n.viewer-canvas > img {\n    height: auto;\n    margin: 15px auto;\n    max-width: 90% !important;\n    width: auto;\n  }\n\n.viewer-footer {\n  bottom: 0;\n  left: 0;\n  overflow: hidden;\n  position: absolute;\n  right: 0;\n  text-align: center;\n}\n\n.viewer-navbar {\n  background-color: rgba(0, 0, 0, 0.5);\n  overflow: hidden;\n}\n\n.viewer-list {\n  box-sizing: content-box;\n  height: 50px;\n  margin: 0;\n  overflow: hidden;\n  padding: 1px 0;\n}\n\n.viewer-list > li {\n    color: transparent;\n    cursor: pointer;\n    float: left;\n    font-size: 0;\n    height: 50px;\n    line-height: 0;\n    opacity: 0.5;\n    overflow: hidden;\n    transition: opacity 0.15s;\n    width: 30px;\n  }\n\n.viewer-list > li:focus,\n    .viewer-list > li:hover {\n      opacity: 0.75;\n    }\n\n.viewer-list > li:focus {\n      outline: 0;\n    }\n\n.viewer-list > li + li {\n      margin-left: 1px;\n    }\n\n.viewer-list > .viewer-loading {\n    position: relative;\n  }\n\n.viewer-list > .viewer-loading::after {\n      border-width: 2px;\n      height: 20px;\n      margin-left: -10px;\n      margin-top: -10px;\n      width: 20px;\n    }\n\n.viewer-list > .viewer-active,\n  .viewer-list > .viewer-active:focus,\n  .viewer-list > .viewer-active:hover {\n    opacity: 1;\n  }\n\n.viewer-player {\n  background-color: #000;\n  bottom: 0;\n  cursor: none;\n  display: none;\n  left: 0;\n  position: absolute;\n  right: 0;\n  top: 0;\n  z-index: 1;\n}\n\n.viewer-player > img {\n    left: 0;\n    position: absolute;\n    top: 0;\n  }\n\n.viewer-toolbar > ul {\n    display: inline-block;\n    margin: 0 auto 5px;\n    overflow: hidden;\n    padding: 6px 3px;\n  }\n\n.viewer-toolbar > ul > li {\n      background-color: rgba(0, 0, 0, 0.5);\n      border-radius: 50%;\n      cursor: pointer;\n      float: left;\n      height: 24px;\n      overflow: hidden;\n      transition: background-color 0.15s;\n      width: 24px;\n    }\n\n.viewer-toolbar > ul > li:focus,\n      .viewer-toolbar > ul > li:hover {\n        background-color: rgba(0, 0, 0, 0.8);\n      }\n\n.viewer-toolbar > ul > li:focus {\n        box-shadow: 0 0 3px #fff;\n        outline: 0;\n        position: relative;\n        z-index: 1;\n      }\n\n.viewer-toolbar > ul > li::before {\n        margin: 2px;\n      }\n\n.viewer-toolbar > ul > li + li {\n        margin-left: 1px;\n      }\n\n.viewer-toolbar > ul > .viewer-small {\n      height: 18px;\n      margin-bottom: 3px;\n      margin-top: 3px;\n      width: 18px;\n    }\n\n.viewer-toolbar > ul > .viewer-small::before {\n        margin: -1px;\n      }\n\n.viewer-toolbar > ul > .viewer-large {\n      height: 30px;\n      margin-bottom: -3px;\n      margin-top: -3px;\n      width: 30px;\n    }\n\n.viewer-toolbar > ul > .viewer-large::before {\n        margin: 5px;\n      }\n\n.viewer-tooltip {\n  background-color: rgba(0, 0, 0, 0.8);\n  border-radius: 10px;\n  color: #fff;\n  display: none;\n  font-size: 12px;\n  height: 20px;\n  left: 50%;\n  line-height: 20px;\n  margin-left: -25px;\n  margin-top: -10px;\n  position: absolute;\n  text-align: center;\n  top: 50%;\n  width: 50px;\n}\n\n.viewer-title {\n  color: #ccc;\n  display: inline-block;\n  font-size: 12px;\n  line-height: 1.2;\n  margin: 5px 5%;\n  max-width: 90%;\n  min-height: 14px;\n  opacity: 0.8;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  transition: opacity 0.15s;\n  white-space: nowrap;\n}\n\n.viewer-title:hover {\n    opacity: 1;\n  }\n\n.viewer-button {\n  -webkit-app-region: no-drag;\n  background-color: rgba(0, 0, 0, 0.5);\n  border-radius: 50%;\n  cursor: pointer;\n  height: 80px;\n  overflow: hidden;\n  position: absolute;\n  right: -40px;\n  top: -40px;\n  transition: background-color 0.15s;\n  width: 80px;\n}\n\n.viewer-button:focus,\n  .viewer-button:hover {\n    background-color: rgba(0, 0, 0, 0.8);\n  }\n\n.viewer-button:focus {\n    box-shadow: 0 0 3px #fff;\n    outline: 0;\n  }\n\n.viewer-button::before {\n    bottom: 15px;\n    left: 15px;\n    position: absolute;\n  }\n\n.viewer-fixed {\n  position: fixed;\n}\n\n.viewer-open {\n  overflow: hidden;\n}\n\n.viewer-show {\n  display: block;\n}\n\n.viewer-hide {\n  display: none;\n}\n\n.viewer-backdrop {\n  background-color: rgba(0, 0, 0, 0.5);\n}\n\n.viewer-invisible {\n  visibility: hidden;\n}\n\n.viewer-move {\n  cursor: move;\n  cursor: grab;\n}\n\n.viewer-fade {\n  opacity: 0;\n}\n\n.viewer-in {\n  opacity: 1;\n}\n\n.viewer-transition {\n  transition: all 0.3s;\n}\n\n@keyframes viewer-spinner {\n  0% {\n    transform: rotate(0deg);\n  }\n\n  100% {\n    transform: rotate(360deg);\n  }\n}\n\n.viewer-loading::after {\n    animation: viewer-spinner 1s linear infinite;\n    border: 4px solid rgba(255, 255, 255, 0.1);\n    border-left-color: rgba(255, 255, 255, 0.5);\n    border-radius: 50%;\n    content: '';\n    display: inline-block;\n    height: 40px;\n    left: 50%;\n    margin-left: -20px;\n    margin-top: -20px;\n    position: absolute;\n    top: 50%;\n    width: 40px;\n    z-index: 1;\n  }\n\n@media (max-width: 767px) {\n  .viewer-hide-xs-down {\n    display: none;\n  }\n}\n\n@media (max-width: 991px) {\n  .viewer-hide-sm-down {\n    display: none;\n  }\n}\n\n@media (max-width: 1199px) {\n  .viewer-hide-md-down {\n    display: none;\n  }\n}\n"
  },
  {
    "path": "dist/viewer.esm.js",
    "content": "/*!\n * Viewer.js v1.11.7\n * https://fengyuanchen.github.io/viewerjs\n *\n * Copyright 2015-present Chen Fengyuan\n * Released under the MIT license\n *\n * Date: 2024-11-24T04:32:19.116Z\n */\n\nfunction _classCallCheck(a, n) {\n  if (!(a instanceof n)) throw new TypeError(\"Cannot call a class as a function\");\n}\nfunction _defineProperties(e, r) {\n  for (var t = 0; t < r.length; t++) {\n    var o = r[t];\n    o.enumerable = o.enumerable || !1, o.configurable = !0, \"value\" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o);\n  }\n}\nfunction _createClass(e, r, t) {\n  return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, \"prototype\", {\n    writable: !1\n  }), e;\n}\nfunction _defineProperty(e, r, t) {\n  return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {\n    value: t,\n    enumerable: !0,\n    configurable: !0,\n    writable: !0\n  }) : e[r] = t, e;\n}\nfunction ownKeys(e, r) {\n  var t = Object.keys(e);\n  if (Object.getOwnPropertySymbols) {\n    var o = Object.getOwnPropertySymbols(e);\n    r && (o = o.filter(function (r) {\n      return Object.getOwnPropertyDescriptor(e, r).enumerable;\n    })), t.push.apply(t, o);\n  }\n  return t;\n}\nfunction _objectSpread2(e) {\n  for (var r = 1; r < arguments.length; r++) {\n    var t = null != arguments[r] ? arguments[r] : {};\n    r % 2 ? ownKeys(Object(t), !0).forEach(function (r) {\n      _defineProperty(e, r, t[r]);\n    }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {\n      Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));\n    });\n  }\n  return e;\n}\nfunction _toPrimitive(t, r) {\n  if (\"object\" != typeof t || !t) return t;\n  var e = t[Symbol.toPrimitive];\n  if (void 0 !== e) {\n    var i = e.call(t, r || \"default\");\n    if (\"object\" != typeof i) return i;\n    throw new TypeError(\"@@toPrimitive must return a primitive value.\");\n  }\n  return (\"string\" === r ? String : Number)(t);\n}\nfunction _toPropertyKey(t) {\n  var i = _toPrimitive(t, \"string\");\n  return \"symbol\" == typeof i ? i : i + \"\";\n}\nfunction _typeof(o) {\n  \"@babel/helpers - typeof\";\n\n  return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function (o) {\n    return typeof o;\n  } : function (o) {\n    return o && \"function\" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? \"symbol\" : typeof o;\n  }, _typeof(o);\n}\n\nvar DEFAULTS = {\n  /**\n   * Enable a modal backdrop, specify `static` for a backdrop\n   * which doesn't close the modal on click.\n   * @type {boolean}\n   */\n  backdrop: true,\n  /**\n   * Show the button on the top-right of the viewer.\n   * @type {boolean}\n   */\n  button: true,\n  /**\n   * Show the navbar.\n   * @type {boolean | number}\n   */\n  navbar: true,\n  /**\n   * Specify the visibility and the content of the title.\n   * @type {boolean | number | Function | Array}\n   */\n  title: true,\n  /**\n   * Show the toolbar.\n   * @type {boolean | number | Object}\n   */\n  toolbar: true,\n  /**\n   * Custom class name(s) to add to the viewer's root element.\n   * @type {string}\n   */\n  className: '',\n  /**\n   * Define where to put the viewer in modal mode.\n   * @type {string | Element}\n   */\n  container: 'body',\n  /**\n   * Filter the images for viewing. Return true if the image is viewable.\n   * @type {Function}\n   */\n  filter: null,\n  /**\n   * Enable to request fullscreen when play.\n   * {@link https://developer.mozilla.org/en-US/docs/Web/API/FullscreenOptions}\n   * @type {boolean|FullscreenOptions}\n   */\n  fullscreen: true,\n  /**\n   * Define the extra attributes to inherit from the original image.\n   * @type {Array}\n   */\n  inheritedAttributes: ['crossOrigin', 'decoding', 'isMap', 'loading', 'referrerPolicy', 'sizes', 'srcset', 'useMap'],\n  /**\n   * Define the initial coverage of the viewing image.\n   * @type {number}\n   */\n  initialCoverage: 0.9,\n  /**\n   * Define the initial index of the image for viewing.\n   * @type {number}\n   */\n  initialViewIndex: 0,\n  /**\n   * Enable inline mode.\n   * @type {boolean}\n   */\n  inline: false,\n  /**\n   * The amount of time to delay between automatically cycling an image when playing.\n   * @type {number}\n   */\n  interval: 5000,\n  /**\n   * Enable keyboard support.\n   * @type {boolean}\n   */\n  keyboard: true,\n  /**\n   * Focus the viewer when initialized.\n   * @type {boolean}\n   */\n  focus: true,\n  /**\n   * Indicate if show a loading spinner when load image or not.\n   * @type {boolean}\n   */\n  loading: true,\n  /**\n   * Indicate if enable loop viewing or not.\n   * @type {boolean}\n   */\n  loop: true,\n  /**\n   * Min width of the viewer in inline mode.\n   * @type {number}\n   */\n  minWidth: 200,\n  /**\n   * Min height of the viewer in inline mode.\n   * @type {number}\n   */\n  minHeight: 100,\n  /**\n   * Enable to move the image.\n   * @type {boolean}\n   */\n  movable: true,\n  /**\n   * Enable to rotate the image.\n   * @type {boolean}\n   */\n  rotatable: true,\n  /**\n   * Enable to scale the image.\n   * @type {boolean}\n   */\n  scalable: true,\n  /**\n   * Enable to zoom the image.\n   * @type {boolean}\n   */\n  zoomable: true,\n  /**\n   * Enable to zoom the current image by dragging on the touch screen.\n   * @type {boolean}\n   */\n  zoomOnTouch: true,\n  /**\n   * Enable to zoom the image by wheeling mouse.\n   * @type {boolean}\n   */\n  zoomOnWheel: true,\n  /**\n   * Enable to slide to the next or previous image by swiping on the touch screen.\n   * @type {boolean}\n   */\n  slideOnTouch: true,\n  /**\n   * Indicate if toggle the image size between its natural size\n   * and initial size when double click on the image or not.\n   * @type {boolean}\n   */\n  toggleOnDblclick: true,\n  /**\n   * Show the tooltip with image ratio (percentage) when zoom in or zoom out.\n   * @type {boolean}\n   */\n  tooltip: true,\n  /**\n   * Enable CSS3 Transition for some special elements.\n   * @type {boolean}\n   */\n  transition: true,\n  /**\n   * Define the CSS `z-index` value of viewer in modal mode.\n   * @type {number}\n   */\n  zIndex: 2015,\n  /**\n   * Define the CSS `z-index` value of viewer in inline mode.\n   * @type {number}\n   */\n  zIndexInline: 0,\n  /**\n   * Define the ratio when zoom the image by wheeling mouse.\n   * @type {number}\n   */\n  zoomRatio: 0.1,\n  /**\n   * Define the min ratio of the image when zoom out.\n   * @type {number}\n   */\n  minZoomRatio: 0.01,\n  /**\n   * Define the max ratio of the image when zoom in.\n   * @type {number}\n   */\n  maxZoomRatio: 100,\n  /**\n   * Define where to get the original image URL for viewing.\n   * @type {string | Function}\n   */\n  url: 'src',\n  /**\n   * Event shortcuts.\n   * @type {Function}\n   */\n  ready: null,\n  show: null,\n  shown: null,\n  hide: null,\n  hidden: null,\n  view: null,\n  viewed: null,\n  move: null,\n  moved: null,\n  rotate: null,\n  rotated: null,\n  scale: null,\n  scaled: null,\n  zoom: null,\n  zoomed: null,\n  play: null,\n  stop: null\n};\n\nvar TEMPLATE = '<div class=\"viewer-container\" tabindex=\"-1\" touch-action=\"none\">' + '<div class=\"viewer-canvas\"></div>' + '<div class=\"viewer-footer\">' + '<div class=\"viewer-title\"></div>' + '<div class=\"viewer-toolbar\"></div>' + '<div class=\"viewer-navbar\">' + '<ul class=\"viewer-list\" role=\"navigation\"></ul>' + '</div>' + '</div>' + '<div class=\"viewer-tooltip\" role=\"alert\" aria-hidden=\"true\"></div>' + '<div class=\"viewer-button\" data-viewer-action=\"mix\" role=\"button\"></div>' + '<div class=\"viewer-player\"></div>' + '</div>';\n\nvar IS_BROWSER = typeof window !== 'undefined' && typeof window.document !== 'undefined';\nvar WINDOW = IS_BROWSER ? window : {};\nvar IS_TOUCH_DEVICE = IS_BROWSER && WINDOW.document.documentElement ? 'ontouchstart' in WINDOW.document.documentElement : false;\nvar HAS_POINTER_EVENT = IS_BROWSER ? 'PointerEvent' in WINDOW : false;\nvar NAMESPACE = 'viewer';\n\n// Actions\nvar ACTION_MOVE = 'move';\nvar ACTION_SWITCH = 'switch';\nvar ACTION_ZOOM = 'zoom';\n\n// Classes\nvar CLASS_ACTIVE = \"\".concat(NAMESPACE, \"-active\");\nvar CLASS_CLOSE = \"\".concat(NAMESPACE, \"-close\");\nvar CLASS_FADE = \"\".concat(NAMESPACE, \"-fade\");\nvar CLASS_FIXED = \"\".concat(NAMESPACE, \"-fixed\");\nvar CLASS_FULLSCREEN = \"\".concat(NAMESPACE, \"-fullscreen\");\nvar CLASS_FULLSCREEN_EXIT = \"\".concat(NAMESPACE, \"-fullscreen-exit\");\nvar CLASS_HIDE = \"\".concat(NAMESPACE, \"-hide\");\nvar CLASS_HIDE_MD_DOWN = \"\".concat(NAMESPACE, \"-hide-md-down\");\nvar CLASS_HIDE_SM_DOWN = \"\".concat(NAMESPACE, \"-hide-sm-down\");\nvar CLASS_HIDE_XS_DOWN = \"\".concat(NAMESPACE, \"-hide-xs-down\");\nvar CLASS_IN = \"\".concat(NAMESPACE, \"-in\");\nvar CLASS_INVISIBLE = \"\".concat(NAMESPACE, \"-invisible\");\nvar CLASS_LOADING = \"\".concat(NAMESPACE, \"-loading\");\nvar CLASS_MOVE = \"\".concat(NAMESPACE, \"-move\");\nvar CLASS_OPEN = \"\".concat(NAMESPACE, \"-open\");\nvar CLASS_SHOW = \"\".concat(NAMESPACE, \"-show\");\nvar CLASS_TRANSITION = \"\".concat(NAMESPACE, \"-transition\");\n\n// Native events\nvar EVENT_CLICK = 'click';\nvar EVENT_DBLCLICK = 'dblclick';\nvar EVENT_DRAG_START = 'dragstart';\nvar EVENT_FOCUSIN = 'focusin';\nvar EVENT_KEY_DOWN = 'keydown';\nvar EVENT_LOAD = 'load';\nvar EVENT_ERROR = 'error';\nvar EVENT_TOUCH_END = IS_TOUCH_DEVICE ? 'touchend touchcancel' : 'mouseup';\nvar EVENT_TOUCH_MOVE = IS_TOUCH_DEVICE ? 'touchmove' : 'mousemove';\nvar EVENT_TOUCH_START = IS_TOUCH_DEVICE ? 'touchstart' : 'mousedown';\nvar EVENT_POINTER_DOWN = HAS_POINTER_EVENT ? 'pointerdown' : EVENT_TOUCH_START;\nvar EVENT_POINTER_MOVE = HAS_POINTER_EVENT ? 'pointermove' : EVENT_TOUCH_MOVE;\nvar EVENT_POINTER_UP = HAS_POINTER_EVENT ? 'pointerup pointercancel' : EVENT_TOUCH_END;\nvar EVENT_RESIZE = 'resize';\nvar EVENT_TRANSITION_END = 'transitionend';\nvar EVENT_WHEEL = 'wheel';\n\n// Custom events\nvar EVENT_READY = 'ready';\nvar EVENT_SHOW = 'show';\nvar EVENT_SHOWN = 'shown';\nvar EVENT_HIDE = 'hide';\nvar EVENT_HIDDEN = 'hidden';\nvar EVENT_VIEW = 'view';\nvar EVENT_VIEWED = 'viewed';\nvar EVENT_MOVE = 'move';\nvar EVENT_MOVED = 'moved';\nvar EVENT_ROTATE = 'rotate';\nvar EVENT_ROTATED = 'rotated';\nvar EVENT_SCALE = 'scale';\nvar EVENT_SCALED = 'scaled';\nvar EVENT_ZOOM = 'zoom';\nvar EVENT_ZOOMED = 'zoomed';\nvar EVENT_PLAY = 'play';\nvar EVENT_STOP = 'stop';\n\n// Data keys\nvar DATA_ACTION = \"\".concat(NAMESPACE, \"Action\");\n\n// RegExps\nvar REGEXP_SPACES = /\\s\\s*/;\n\n// Misc\nvar BUTTONS = ['zoom-in', 'zoom-out', 'one-to-one', 'reset', 'prev', 'play', 'next', 'rotate-left', 'rotate-right', 'flip-horizontal', 'flip-vertical'];\n\n/**\n * Check if the given value is a string.\n * @param {*} value - The value to check.\n * @returns {boolean} Returns `true` if the given value is a string, else `false`.\n */\nfunction isString(value) {\n  return typeof value === 'string';\n}\n\n/**\n * Check if the given value is not a number.\n */\nvar isNaN = Number.isNaN || WINDOW.isNaN;\n\n/**\n * Check if the given value is a number.\n * @param {*} value - The value to check.\n * @returns {boolean} Returns `true` if the given value is a number, else `false`.\n */\nfunction isNumber(value) {\n  return typeof value === 'number' && !isNaN(value);\n}\n\n/**\n * Check if the given value is undefined.\n * @param {*} value - The value to check.\n * @returns {boolean} Returns `true` if the given value is undefined, else `false`.\n */\nfunction isUndefined(value) {\n  return typeof value === 'undefined';\n}\n\n/**\n * Check if the given value is an object.\n * @param {*} value - The value to check.\n * @returns {boolean} Returns `true` if the given value is an object, else `false`.\n */\nfunction isObject(value) {\n  return _typeof(value) === 'object' && value !== null;\n}\nvar hasOwnProperty = Object.prototype.hasOwnProperty;\n\n/**\n * Check if the given value is a plain object.\n * @param {*} value - The value to check.\n * @returns {boolean} Returns `true` if the given value is a plain object, else `false`.\n */\nfunction isPlainObject(value) {\n  if (!isObject(value)) {\n    return false;\n  }\n  try {\n    var _constructor = value.constructor;\n    var prototype = _constructor.prototype;\n    return _constructor && prototype && hasOwnProperty.call(prototype, 'isPrototypeOf');\n  } catch (error) {\n    return false;\n  }\n}\n\n/**\n * Check if the given value is a function.\n * @param {*} value - The value to check.\n * @returns {boolean} Returns `true` if the given value is a function, else `false`.\n */\nfunction isFunction(value) {\n  return typeof value === 'function';\n}\n\n/**\n * Iterate the given data.\n * @param {*} data - The data to iterate.\n * @param {Function} callback - The process function for each element.\n * @returns {*} The original data.\n */\nfunction forEach(data, callback) {\n  if (data && isFunction(callback)) {\n    if (Array.isArray(data) || isNumber(data.length) /* array-like */) {\n      var length = data.length;\n      var i;\n      for (i = 0; i < length; i += 1) {\n        if (callback.call(data, data[i], i, data) === false) {\n          break;\n        }\n      }\n    } else if (isObject(data)) {\n      Object.keys(data).forEach(function (key) {\n        callback.call(data, data[key], key, data);\n      });\n    }\n  }\n  return data;\n}\n\n/**\n * Extend the given object.\n * @param {*} obj - The object to be extended.\n * @param {*} args - The rest objects which will be merged to the first object.\n * @returns {Object} The extended object.\n */\nvar assign = Object.assign || function assign(obj) {\n  for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {\n    args[_key - 1] = arguments[_key];\n  }\n  if (isObject(obj) && args.length > 0) {\n    args.forEach(function (arg) {\n      if (isObject(arg)) {\n        Object.keys(arg).forEach(function (key) {\n          obj[key] = arg[key];\n        });\n      }\n    });\n  }\n  return obj;\n};\nvar REGEXP_SUFFIX = /^(?:width|height|left|top|marginLeft|marginTop)$/;\n\n/**\n * Apply styles to the given element.\n * @param {Element} element - The target element.\n * @param {Object} styles - The styles for applying.\n */\nfunction setStyle(element, styles) {\n  var style = element.style;\n  forEach(styles, function (value, property) {\n    if (REGEXP_SUFFIX.test(property) && isNumber(value)) {\n      value += 'px';\n    }\n    style[property] = value;\n  });\n}\n\n/**\n * Escape a string for using in HTML.\n * @param {String} value - The string to escape.\n * @returns {String} Returns the escaped string.\n */\nfunction escapeHTMLEntities(value) {\n  return isString(value) ? value.replace(/&(?!amp;|quot;|#39;|lt;|gt;)/g, '&amp;').replace(/\"/g, '&quot;').replace(/'/g, '&#39;').replace(/</g, '&lt;').replace(/>/g, '&gt;') : value;\n}\n\n/**\n * Check if the given element has a special class.\n * @param {Element} element - The element to check.\n * @param {string} value - The class to search.\n * @returns {boolean} Returns `true` if the special class was found.\n */\nfunction hasClass(element, value) {\n  if (!element || !value) {\n    return false;\n  }\n  return element.classList ? element.classList.contains(value) : element.className.indexOf(value) > -1;\n}\n\n/**\n * Add classes to the given element.\n * @param {Element} element - The target element.\n * @param {string} value - The classes to be added.\n */\nfunction addClass(element, value) {\n  if (!element || !value) {\n    return;\n  }\n  if (isNumber(element.length)) {\n    forEach(element, function (elem) {\n      addClass(elem, value);\n    });\n    return;\n  }\n  if (element.classList) {\n    element.classList.add(value);\n    return;\n  }\n  var className = element.className.trim();\n  if (!className) {\n    element.className = value;\n  } else if (className.indexOf(value) < 0) {\n    element.className = \"\".concat(className, \" \").concat(value);\n  }\n}\n\n/**\n * Remove classes from the given element.\n * @param {Element} element - The target element.\n * @param {string} value - The classes to be removed.\n */\nfunction removeClass(element, value) {\n  if (!element || !value) {\n    return;\n  }\n  if (isNumber(element.length)) {\n    forEach(element, function (elem) {\n      removeClass(elem, value);\n    });\n    return;\n  }\n  if (element.classList) {\n    element.classList.remove(value);\n    return;\n  }\n  if (element.className.indexOf(value) >= 0) {\n    element.className = element.className.replace(value, '');\n  }\n}\n\n/**\n * Add or remove classes from the given element.\n * @param {Element} element - The target element.\n * @param {string} value - The classes to be toggled.\n * @param {boolean} added - Add only.\n */\nfunction toggleClass(element, value, added) {\n  if (!value) {\n    return;\n  }\n  if (isNumber(element.length)) {\n    forEach(element, function (elem) {\n      toggleClass(elem, value, added);\n    });\n    return;\n  }\n\n  // IE10-11 doesn't support the second parameter of `classList.toggle`\n  if (added) {\n    addClass(element, value);\n  } else {\n    removeClass(element, value);\n  }\n}\nvar REGEXP_HYPHENATE = /([a-z\\d])([A-Z])/g;\n\n/**\n * Transform the given string from camelCase to kebab-case\n * @param {string} value - The value to transform.\n * @returns {string} The transformed value.\n */\nfunction hyphenate(value) {\n  return value.replace(REGEXP_HYPHENATE, '$1-$2').toLowerCase();\n}\n\n/**\n * Get data from the given element.\n * @param {Element} element - The target element.\n * @param {string} name - The data key to get.\n * @returns {string} The data value.\n */\nfunction getData(element, name) {\n  if (isObject(element[name])) {\n    return element[name];\n  }\n  if (element.dataset) {\n    return element.dataset[name];\n  }\n  return element.getAttribute(\"data-\".concat(hyphenate(name)));\n}\n\n/**\n * Set data to the given element.\n * @param {Element} element - The target element.\n * @param {string} name - The data key to set.\n * @param {string} data - The data value.\n */\nfunction setData(element, name, data) {\n  if (isObject(data)) {\n    element[name] = data;\n  } else if (element.dataset) {\n    element.dataset[name] = data;\n  } else {\n    element.setAttribute(\"data-\".concat(hyphenate(name)), data);\n  }\n}\nvar onceSupported = function () {\n  var supported = false;\n  if (IS_BROWSER) {\n    var once = false;\n    var listener = function listener() {};\n    var options = Object.defineProperty({}, 'once', {\n      get: function get() {\n        supported = true;\n        return once;\n      },\n      /**\n       * This setter can fix a `TypeError` in strict mode\n       * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Getter_only}\n       * @param {boolean} value - The value to set\n       */\n      set: function set(value) {\n        once = value;\n      }\n    });\n    WINDOW.addEventListener('test', listener, options);\n    WINDOW.removeEventListener('test', listener, options);\n  }\n  return supported;\n}();\n\n/**\n * Remove event listener from the target element.\n * @param {Element} element - The event target.\n * @param {string} type - The event type(s).\n * @param {Function} listener - The event listener.\n * @param {Object} options - The event options.\n */\nfunction removeListener(element, type, listener) {\n  var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};\n  var handler = listener;\n  type.trim().split(REGEXP_SPACES).forEach(function (event) {\n    if (!onceSupported) {\n      var listeners = element.listeners;\n      if (listeners && listeners[event] && listeners[event][listener]) {\n        handler = listeners[event][listener];\n        delete listeners[event][listener];\n        if (Object.keys(listeners[event]).length === 0) {\n          delete listeners[event];\n        }\n        if (Object.keys(listeners).length === 0) {\n          delete element.listeners;\n        }\n      }\n    }\n    element.removeEventListener(event, handler, options);\n  });\n}\n\n/**\n * Add event listener to the target element.\n * @param {Element} element - The event target.\n * @param {string} type - The event type(s).\n * @param {Function} listener - The event listener.\n * @param {Object} options - The event options.\n */\nfunction addListener(element, type, listener) {\n  var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};\n  var _handler = listener;\n  type.trim().split(REGEXP_SPACES).forEach(function (event) {\n    if (options.once && !onceSupported) {\n      var _element$listeners = element.listeners,\n        listeners = _element$listeners === void 0 ? {} : _element$listeners;\n      _handler = function handler() {\n        delete listeners[event][listener];\n        element.removeEventListener(event, _handler, options);\n        for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {\n          args[_key2] = arguments[_key2];\n        }\n        listener.apply(element, args);\n      };\n      if (!listeners[event]) {\n        listeners[event] = {};\n      }\n      if (listeners[event][listener]) {\n        element.removeEventListener(event, listeners[event][listener], options);\n      }\n      listeners[event][listener] = _handler;\n      element.listeners = listeners;\n    }\n    element.addEventListener(event, _handler, options);\n  });\n}\n\n/**\n * Dispatch event on the target element.\n * @param {Element} element - The event target.\n * @param {string} type - The event type(s).\n * @param {Object} data - The additional event data.\n * @param {Object} options - The additional event options.\n * @returns {boolean} Indicate if the event is default prevented or not.\n */\nfunction dispatchEvent(element, type, data, options) {\n  var event;\n\n  // Event and CustomEvent on IE9-11 are global objects, not constructors\n  if (isFunction(Event) && isFunction(CustomEvent)) {\n    event = new CustomEvent(type, _objectSpread2({\n      bubbles: true,\n      cancelable: true,\n      detail: data\n    }, options));\n  } else {\n    event = document.createEvent('CustomEvent');\n    event.initCustomEvent(type, true, true, data);\n  }\n  return element.dispatchEvent(event);\n}\n\n/**\n * Get the offset base on the document.\n * @param {Element} element - The target element.\n * @returns {Object} The offset data.\n */\nfunction getOffset(element) {\n  var box = element.getBoundingClientRect();\n  return {\n    left: box.left + (window.pageXOffset - document.documentElement.clientLeft),\n    top: box.top + (window.pageYOffset - document.documentElement.clientTop)\n  };\n}\n\n/**\n * Get transforms base on the given object.\n * @param {Object} obj - The target object.\n * @returns {string} A string contains transform values.\n */\nfunction getTransforms(_ref) {\n  var rotate = _ref.rotate,\n    scaleX = _ref.scaleX,\n    scaleY = _ref.scaleY,\n    translateX = _ref.translateX,\n    translateY = _ref.translateY;\n  var values = [];\n  if (isNumber(translateX) && translateX !== 0) {\n    values.push(\"translateX(\".concat(translateX, \"px)\"));\n  }\n  if (isNumber(translateY) && translateY !== 0) {\n    values.push(\"translateY(\".concat(translateY, \"px)\"));\n  }\n\n  // Rotate should come first before scale to match orientation transform\n  if (isNumber(rotate) && rotate !== 0) {\n    values.push(\"rotate(\".concat(rotate, \"deg)\"));\n  }\n  if (isNumber(scaleX) && scaleX !== 1) {\n    values.push(\"scaleX(\".concat(scaleX, \")\"));\n  }\n  if (isNumber(scaleY) && scaleY !== 1) {\n    values.push(\"scaleY(\".concat(scaleY, \")\"));\n  }\n  var transform = values.length ? values.join(' ') : 'none';\n  return {\n    WebkitTransform: transform,\n    msTransform: transform,\n    transform: transform\n  };\n}\n\n/**\n * Get an image name from an image url.\n * @param {string} url - The target url.\n * @example\n * // picture.jpg\n * getImageNameFromURL('https://domain.com/path/to/picture.jpg?size=1280×960')\n * @returns {string} A string contains the image name.\n */\nfunction getImageNameFromURL(url) {\n  return isString(url) ? decodeURIComponent(url.replace(/^.*\\//, '').replace(/[?&#].*$/, '')) : '';\n}\nvar IS_SAFARI = WINDOW.navigator && /Version\\/\\d+(\\.\\d+)+?\\s+Safari/i.test(WINDOW.navigator.userAgent);\n\n/**\n * Get an image's natural sizes.\n * @param {string} image - The target image.\n * @param {Object} options - The viewer options.\n * @param {Function} callback - The callback function.\n * @returns {HTMLImageElement} The new image.\n */\nfunction getImageNaturalSizes(image, options, callback) {\n  var newImage = document.createElement('img');\n\n  // Modern browsers (except Safari)\n  if (image.naturalWidth && !IS_SAFARI) {\n    callback(image.naturalWidth, image.naturalHeight);\n    return newImage;\n  }\n  var body = document.body || document.documentElement;\n  newImage.onload = function () {\n    callback(newImage.width, newImage.height);\n    if (!IS_SAFARI) {\n      body.removeChild(newImage);\n    }\n  };\n  forEach(options.inheritedAttributes, function (name) {\n    var value = image.getAttribute(name);\n    if (value !== null) {\n      newImage.setAttribute(name, value);\n    }\n  });\n  newImage.src = image.src;\n\n  // iOS Safari will convert the image automatically\n  // with its orientation once append it into DOM\n  if (!IS_SAFARI) {\n    newImage.style.cssText = 'left:0;' + 'max-height:none!important;' + 'max-width:none!important;' + 'min-height:0!important;' + 'min-width:0!important;' + 'opacity:0;' + 'position:absolute;' + 'top:0;' + 'z-index:-1;';\n    body.appendChild(newImage);\n  }\n  return newImage;\n}\n\n/**\n * Get the related class name of a responsive type number.\n * @param {string} type - The responsive type.\n * @returns {string} The related class name.\n */\nfunction getResponsiveClass(type) {\n  switch (type) {\n    case 2:\n      return CLASS_HIDE_XS_DOWN;\n    case 3:\n      return CLASS_HIDE_SM_DOWN;\n    case 4:\n      return CLASS_HIDE_MD_DOWN;\n    default:\n      return '';\n  }\n}\n\n/**\n * Get the max ratio of a group of pointers.\n * @param {string} pointers - The target pointers.\n * @returns {number} The result ratio.\n */\nfunction getMaxZoomRatio(pointers) {\n  var pointers2 = _objectSpread2({}, pointers);\n  var ratios = [];\n  forEach(pointers, function (pointer, pointerId) {\n    delete pointers2[pointerId];\n    forEach(pointers2, function (pointer2) {\n      var x1 = Math.abs(pointer.startX - pointer2.startX);\n      var y1 = Math.abs(pointer.startY - pointer2.startY);\n      var x2 = Math.abs(pointer.endX - pointer2.endX);\n      var y2 = Math.abs(pointer.endY - pointer2.endY);\n      var z1 = Math.sqrt(x1 * x1 + y1 * y1);\n      var z2 = Math.sqrt(x2 * x2 + y2 * y2);\n      var ratio = (z2 - z1) / z1;\n      ratios.push(ratio);\n    });\n  });\n  ratios.sort(function (a, b) {\n    return Math.abs(a) < Math.abs(b);\n  });\n  return ratios[0];\n}\n\n/**\n * Get a pointer from an event object.\n * @param {Object} event - The target event object.\n * @param {boolean} endOnly - Indicates if only returns the end point coordinate or not.\n * @returns {Object} The result pointer contains start and/or end point coordinates.\n */\nfunction getPointer(_ref2, endOnly) {\n  var pageX = _ref2.pageX,\n    pageY = _ref2.pageY;\n  var end = {\n    endX: pageX,\n    endY: pageY\n  };\n  return endOnly ? end : _objectSpread2({\n    timeStamp: Date.now(),\n    startX: pageX,\n    startY: pageY\n  }, end);\n}\n\n/**\n * Get the center point coordinate of a group of pointers.\n * @param {Object} pointers - The target pointers.\n * @returns {Object} The center point coordinate.\n */\nfunction getPointersCenter(pointers) {\n  var pageX = 0;\n  var pageY = 0;\n  var count = 0;\n  forEach(pointers, function (_ref3) {\n    var startX = _ref3.startX,\n      startY = _ref3.startY;\n    pageX += startX;\n    pageY += startY;\n    count += 1;\n  });\n  pageX /= count;\n  pageY /= count;\n  return {\n    pageX: pageX,\n    pageY: pageY\n  };\n}\n\nvar render = {\n  render: function render() {\n    this.initContainer();\n    this.initViewer();\n    this.initList();\n    this.renderViewer();\n  },\n  initBody: function initBody() {\n    var ownerDocument = this.element.ownerDocument;\n    var body = ownerDocument.body || ownerDocument.documentElement;\n    this.body = body;\n    this.scrollbarWidth = window.innerWidth - ownerDocument.documentElement.clientWidth;\n    this.initialBodyPaddingRight = body.style.paddingRight;\n    this.initialBodyComputedPaddingRight = window.getComputedStyle(body).paddingRight;\n  },\n  initContainer: function initContainer() {\n    this.containerData = {\n      width: window.innerWidth,\n      height: window.innerHeight\n    };\n  },\n  initViewer: function initViewer() {\n    var options = this.options,\n      parent = this.parent;\n    var viewerData;\n    if (options.inline) {\n      viewerData = {\n        width: Math.max(parent.offsetWidth, options.minWidth),\n        height: Math.max(parent.offsetHeight, options.minHeight)\n      };\n      this.parentData = viewerData;\n    }\n    if (this.fulled || !viewerData) {\n      viewerData = this.containerData;\n    }\n    this.viewerData = assign({}, viewerData);\n  },\n  renderViewer: function renderViewer() {\n    if (this.options.inline && !this.fulled) {\n      setStyle(this.viewer, this.viewerData);\n    }\n  },\n  initList: function initList() {\n    var _this = this;\n    var element = this.element,\n      options = this.options,\n      list = this.list;\n    var items = [];\n\n    // initList may be called in this.update, so should keep idempotent\n    list.innerHTML = '';\n    forEach(this.images, function (image, index) {\n      var src = image.src;\n      var alt = image.alt || getImageNameFromURL(src);\n      var url = _this.getImageURL(image);\n      if (src || url) {\n        var item = document.createElement('li');\n        var img = document.createElement('img');\n        forEach(options.inheritedAttributes, function (name) {\n          var value = image.getAttribute(name);\n          if (value !== null) {\n            img.setAttribute(name, value);\n          }\n        });\n        if (options.navbar) {\n          img.src = src || url;\n        }\n        img.alt = alt;\n        img.setAttribute('data-original-url', url || src);\n        item.setAttribute('data-index', index);\n        item.setAttribute('data-viewer-action', 'view');\n        item.setAttribute('role', 'button');\n        if (options.keyboard) {\n          item.setAttribute('tabindex', 0);\n        }\n        item.appendChild(img);\n        list.appendChild(item);\n        items.push(item);\n      }\n    });\n    this.items = items;\n    forEach(items, function (item) {\n      var image = item.firstElementChild;\n      var onLoad;\n      var onError;\n      setData(image, 'filled', true);\n      if (options.loading) {\n        addClass(item, CLASS_LOADING);\n      }\n      addListener(image, EVENT_LOAD, onLoad = function onLoad(event) {\n        removeListener(image, EVENT_ERROR, onError);\n        if (options.loading) {\n          removeClass(item, CLASS_LOADING);\n        }\n        _this.loadImage(event);\n      }, {\n        once: true\n      });\n      addListener(image, EVENT_ERROR, onError = function onError() {\n        removeListener(image, EVENT_LOAD, onLoad);\n        if (options.loading) {\n          removeClass(item, CLASS_LOADING);\n        }\n      }, {\n        once: true\n      });\n    });\n    if (options.transition) {\n      addListener(element, EVENT_VIEWED, function () {\n        addClass(list, CLASS_TRANSITION);\n      }, {\n        once: true\n      });\n    }\n  },\n  renderList: function renderList() {\n    var index = this.index;\n    var item = this.items[index];\n    if (!item) {\n      return;\n    }\n    var next = item.nextElementSibling;\n    var gutter = parseInt(window.getComputedStyle(next || item).marginLeft, 10);\n    var offsetWidth = item.offsetWidth;\n    var outerWidth = offsetWidth + gutter;\n\n    // Place the active item in the center of the screen\n    setStyle(this.list, assign({\n      width: outerWidth * this.length - gutter\n    }, getTransforms({\n      translateX: (this.viewerData.width - offsetWidth) / 2 - outerWidth * index\n    })));\n  },\n  resetList: function resetList() {\n    var list = this.list;\n    list.innerHTML = '';\n    removeClass(list, CLASS_TRANSITION);\n    setStyle(list, getTransforms({\n      translateX: 0\n    }));\n  },\n  initImage: function initImage(done) {\n    var _this2 = this;\n    var options = this.options,\n      image = this.image,\n      viewerData = this.viewerData;\n    var footerHeight = this.footer.offsetHeight;\n    var viewerWidth = viewerData.width;\n    var viewerHeight = Math.max(viewerData.height - footerHeight, footerHeight);\n    var oldImageData = this.imageData || {};\n    var sizingImage;\n    this.imageInitializing = {\n      abort: function abort() {\n        sizingImage.onload = null;\n      }\n    };\n    sizingImage = getImageNaturalSizes(image, options, function (naturalWidth, naturalHeight) {\n      var aspectRatio = naturalWidth / naturalHeight;\n      var initialCoverage = Math.max(0, Math.min(1, options.initialCoverage));\n      var width = viewerWidth;\n      var height = viewerHeight;\n      _this2.imageInitializing = false;\n      if (viewerHeight * aspectRatio > viewerWidth) {\n        height = viewerWidth / aspectRatio;\n      } else {\n        width = viewerHeight * aspectRatio;\n      }\n      initialCoverage = isNumber(initialCoverage) ? initialCoverage : 0.9;\n      width = Math.min(width * initialCoverage, naturalWidth);\n      height = Math.min(height * initialCoverage, naturalHeight);\n      var left = (viewerWidth - width) / 2;\n      var top = (viewerHeight - height) / 2;\n      var imageData = {\n        left: left,\n        top: top,\n        x: left,\n        y: top,\n        width: width,\n        height: height,\n        oldRatio: 1,\n        ratio: width / naturalWidth,\n        aspectRatio: aspectRatio,\n        naturalWidth: naturalWidth,\n        naturalHeight: naturalHeight\n      };\n      var initialImageData = assign({}, imageData);\n      if (options.rotatable) {\n        imageData.rotate = oldImageData.rotate || 0;\n        initialImageData.rotate = 0;\n      }\n      if (options.scalable) {\n        imageData.scaleX = oldImageData.scaleX || 1;\n        imageData.scaleY = oldImageData.scaleY || 1;\n        initialImageData.scaleX = 1;\n        initialImageData.scaleY = 1;\n      }\n      _this2.imageData = imageData;\n      _this2.initialImageData = initialImageData;\n      if (done) {\n        done();\n      }\n    });\n  },\n  renderImage: function renderImage(done) {\n    var _this3 = this;\n    var image = this.image,\n      imageData = this.imageData;\n    setStyle(image, assign({\n      width: imageData.width,\n      height: imageData.height,\n      // XXX: Not to use translateX/Y to avoid image shaking when zooming\n      marginLeft: imageData.x,\n      marginTop: imageData.y\n    }, getTransforms(imageData)));\n    if (done) {\n      if ((this.viewing || this.moving || this.rotating || this.scaling || this.zooming) && this.options.transition && hasClass(image, CLASS_TRANSITION)) {\n        var onTransitionEnd = function onTransitionEnd() {\n          _this3.imageRendering = false;\n          done();\n        };\n        this.imageRendering = {\n          abort: function abort() {\n            removeListener(image, EVENT_TRANSITION_END, onTransitionEnd);\n          }\n        };\n        addListener(image, EVENT_TRANSITION_END, onTransitionEnd, {\n          once: true\n        });\n      } else {\n        done();\n      }\n    }\n  },\n  resetImage: function resetImage() {\n    var image = this.image;\n    if (image) {\n      if (this.viewing) {\n        this.viewing.abort();\n      }\n      image.parentNode.removeChild(image);\n      this.image = null;\n      this.title.innerHTML = '';\n    }\n  }\n};\n\nvar events = {\n  bind: function bind() {\n    var options = this.options,\n      viewer = this.viewer,\n      canvas = this.canvas;\n    var document = this.element.ownerDocument;\n    addListener(viewer, EVENT_CLICK, this.onClick = this.click.bind(this));\n    addListener(viewer, EVENT_DRAG_START, this.onDragStart = this.dragstart.bind(this));\n    addListener(canvas, EVENT_POINTER_DOWN, this.onPointerDown = this.pointerdown.bind(this));\n    addListener(document, EVENT_POINTER_MOVE, this.onPointerMove = this.pointermove.bind(this));\n    addListener(document, EVENT_POINTER_UP, this.onPointerUp = this.pointerup.bind(this));\n    addListener(document, EVENT_KEY_DOWN, this.onKeyDown = this.keydown.bind(this));\n    addListener(window, EVENT_RESIZE, this.onResize = this.resize.bind(this));\n    if (options.zoomable && options.zoomOnWheel) {\n      addListener(viewer, EVENT_WHEEL, this.onWheel = this.wheel.bind(this), {\n        passive: false,\n        capture: true\n      });\n    }\n    if (options.toggleOnDblclick) {\n      addListener(canvas, EVENT_DBLCLICK, this.onDblclick = this.dblclick.bind(this));\n    }\n  },\n  unbind: function unbind() {\n    var options = this.options,\n      viewer = this.viewer,\n      canvas = this.canvas;\n    var document = this.element.ownerDocument;\n    removeListener(viewer, EVENT_CLICK, this.onClick);\n    removeListener(viewer, EVENT_DRAG_START, this.onDragStart);\n    removeListener(canvas, EVENT_POINTER_DOWN, this.onPointerDown);\n    removeListener(document, EVENT_POINTER_MOVE, this.onPointerMove);\n    removeListener(document, EVENT_POINTER_UP, this.onPointerUp);\n    removeListener(document, EVENT_KEY_DOWN, this.onKeyDown);\n    removeListener(window, EVENT_RESIZE, this.onResize);\n    if (options.zoomable && options.zoomOnWheel) {\n      removeListener(viewer, EVENT_WHEEL, this.onWheel, {\n        passive: false,\n        capture: true\n      });\n    }\n    if (options.toggleOnDblclick) {\n      removeListener(canvas, EVENT_DBLCLICK, this.onDblclick);\n    }\n  }\n};\n\nvar handlers = {\n  click: function click(event) {\n    var options = this.options,\n      imageData = this.imageData;\n    var target = event.target;\n    var action = getData(target, DATA_ACTION);\n    if (!action && target.localName === 'img' && target.parentElement.localName === 'li') {\n      target = target.parentElement;\n      action = getData(target, DATA_ACTION);\n    }\n\n    // Cancel the emulated click when the native click event was triggered.\n    if (IS_TOUCH_DEVICE && event.isTrusted && target === this.canvas) {\n      clearTimeout(this.clickCanvasTimeout);\n    }\n    switch (action) {\n      case 'mix':\n        if (this.played) {\n          this.stop();\n        } else if (options.inline) {\n          if (this.fulled) {\n            this.exit();\n          } else {\n            this.full();\n          }\n        } else {\n          this.hide();\n        }\n        break;\n      case 'hide':\n        if (!this.pointerMoved) {\n          this.hide();\n        }\n        break;\n      case 'view':\n        this.view(getData(target, 'index'));\n        break;\n      case 'zoom-in':\n        this.zoom(0.1, true);\n        break;\n      case 'zoom-out':\n        this.zoom(-0.1, true);\n        break;\n      case 'one-to-one':\n        this.toggle();\n        break;\n      case 'reset':\n        this.reset();\n        break;\n      case 'prev':\n        this.prev(options.loop);\n        break;\n      case 'play':\n        this.play(options.fullscreen);\n        break;\n      case 'next':\n        this.next(options.loop);\n        break;\n      case 'rotate-left':\n        this.rotate(-90);\n        break;\n      case 'rotate-right':\n        this.rotate(90);\n        break;\n      case 'flip-horizontal':\n        this.scaleX(-imageData.scaleX || -1);\n        break;\n      case 'flip-vertical':\n        this.scaleY(-imageData.scaleY || -1);\n        break;\n      default:\n        if (this.played) {\n          this.stop();\n        }\n    }\n  },\n  dblclick: function dblclick(event) {\n    event.preventDefault();\n    if (this.viewed && event.target === this.image) {\n      // Cancel the emulated double click when the native dblclick event was triggered.\n      if (IS_TOUCH_DEVICE && event.isTrusted) {\n        clearTimeout(this.doubleClickImageTimeout);\n      }\n\n      // XXX: No pageX/Y properties in custom event, fallback to the original event.\n      this.toggle(event.isTrusted ? event : event.detail && event.detail.originalEvent);\n    }\n  },\n  load: function load() {\n    var _this = this;\n    if (this.timeout) {\n      clearTimeout(this.timeout);\n      this.timeout = false;\n    }\n    var element = this.element,\n      options = this.options,\n      image = this.image,\n      index = this.index,\n      viewerData = this.viewerData;\n    removeClass(image, CLASS_INVISIBLE);\n    if (options.loading) {\n      removeClass(this.canvas, CLASS_LOADING);\n    }\n    image.style.cssText = 'height:0;' + \"margin-left:\".concat(viewerData.width / 2, \"px;\") + \"margin-top:\".concat(viewerData.height / 2, \"px;\") + 'max-width:none!important;' + 'position:relative;' + 'width:0;';\n    this.initImage(function () {\n      toggleClass(image, CLASS_MOVE, options.movable);\n      toggleClass(image, CLASS_TRANSITION, options.transition);\n      _this.renderImage(function () {\n        _this.viewed = true;\n        _this.viewing = false;\n        if (isFunction(options.viewed)) {\n          addListener(element, EVENT_VIEWED, options.viewed, {\n            once: true\n          });\n        }\n        dispatchEvent(element, EVENT_VIEWED, {\n          originalImage: _this.images[index],\n          index: index,\n          image: image\n        }, {\n          cancelable: false\n        });\n      });\n    });\n  },\n  loadImage: function loadImage(event) {\n    var image = event.target;\n    var parent = image.parentNode;\n    var parentWidth = parent.offsetWidth || 30;\n    var parentHeight = parent.offsetHeight || 50;\n    var filled = !!getData(image, 'filled');\n    getImageNaturalSizes(image, this.options, function (naturalWidth, naturalHeight) {\n      var aspectRatio = naturalWidth / naturalHeight;\n      var width = parentWidth;\n      var height = parentHeight;\n      if (parentHeight * aspectRatio > parentWidth) {\n        if (filled) {\n          width = parentHeight * aspectRatio;\n        } else {\n          height = parentWidth / aspectRatio;\n        }\n      } else if (filled) {\n        height = parentWidth / aspectRatio;\n      } else {\n        width = parentHeight * aspectRatio;\n      }\n      setStyle(image, assign({\n        width: width,\n        height: height\n      }, getTransforms({\n        translateX: (parentWidth - width) / 2,\n        translateY: (parentHeight - height) / 2\n      })));\n    });\n  },\n  keydown: function keydown(event) {\n    var options = this.options;\n    if (!options.keyboard) {\n      return;\n    }\n    var keyCode = event.keyCode || event.which || event.charCode;\n    switch (keyCode) {\n      // Enter\n      case 13:\n        if (this.viewer.contains(event.target)) {\n          this.click(event);\n        }\n        break;\n    }\n    if (!this.fulled) {\n      return;\n    }\n    switch (keyCode) {\n      // Escape\n      case 27:\n        if (this.played) {\n          this.stop();\n        } else if (options.inline) {\n          if (this.fulled) {\n            this.exit();\n          }\n        } else {\n          this.hide();\n        }\n        break;\n\n      // Space\n      case 32:\n        if (this.played) {\n          this.stop();\n        }\n        break;\n\n      // ArrowLeft\n      case 37:\n        if (this.played && this.playing) {\n          this.playing.prev();\n        } else {\n          this.prev(options.loop);\n        }\n        break;\n\n      // ArrowUp\n      case 38:\n        // Prevent scroll on Firefox\n        event.preventDefault();\n\n        // Zoom in\n        this.zoom(options.zoomRatio, true);\n        break;\n\n      // ArrowRight\n      case 39:\n        if (this.played && this.playing) {\n          this.playing.next();\n        } else {\n          this.next(options.loop);\n        }\n        break;\n\n      // ArrowDown\n      case 40:\n        // Prevent scroll on Firefox\n        event.preventDefault();\n\n        // Zoom out\n        this.zoom(-options.zoomRatio, true);\n        break;\n\n      // Ctrl + 0\n      case 48:\n      // Fall through\n\n      // Ctrl + 1\n      // eslint-disable-next-line no-fallthrough\n      case 49:\n        if (event.ctrlKey) {\n          event.preventDefault();\n          this.toggle();\n        }\n        break;\n    }\n  },\n  dragstart: function dragstart(event) {\n    if (event.target.localName === 'img') {\n      event.preventDefault();\n    }\n  },\n  pointerdown: function pointerdown(event) {\n    var options = this.options,\n      pointers = this.pointers;\n    var buttons = event.buttons,\n      button = event.button;\n    this.pointerMoved = false;\n    if (!this.viewed || this.showing || this.viewing || this.hiding\n\n    // Handle mouse event and pointer event and ignore touch event\n    || (event.type === 'mousedown' || event.type === 'pointerdown' && event.pointerType === 'mouse') && (\n    // No primary button (Usually the left button)\n    isNumber(buttons) && buttons !== 1 || isNumber(button) && button !== 0\n\n    // Open context menu\n    || event.ctrlKey)) {\n      return;\n    }\n\n    // Prevent default behaviours as page zooming in touch devices.\n    event.preventDefault();\n    if (event.changedTouches) {\n      forEach(event.changedTouches, function (touch) {\n        pointers[touch.identifier] = getPointer(touch);\n      });\n    } else {\n      pointers[event.pointerId || 0] = getPointer(event);\n    }\n    var action = options.movable ? ACTION_MOVE : false;\n    if (options.zoomOnTouch && options.zoomable && Object.keys(pointers).length > 1) {\n      action = ACTION_ZOOM;\n    } else if (options.slideOnTouch && (event.pointerType === 'touch' || event.type === 'touchstart') && this.isSwitchable()) {\n      action = ACTION_SWITCH;\n    }\n    if (options.transition && (action === ACTION_MOVE || action === ACTION_ZOOM)) {\n      removeClass(this.image, CLASS_TRANSITION);\n    }\n    this.action = action;\n  },\n  pointermove: function pointermove(event) {\n    var pointers = this.pointers,\n      action = this.action;\n    if (!this.viewed || !action) {\n      return;\n    }\n    event.preventDefault();\n    if (event.changedTouches) {\n      forEach(event.changedTouches, function (touch) {\n        assign(pointers[touch.identifier] || {}, getPointer(touch, true));\n      });\n    } else {\n      assign(pointers[event.pointerId || 0] || {}, getPointer(event, true));\n    }\n    this.change(event);\n  },\n  pointerup: function pointerup(event) {\n    var _this2 = this;\n    var options = this.options,\n      action = this.action,\n      pointers = this.pointers;\n    var pointer;\n    if (event.changedTouches) {\n      forEach(event.changedTouches, function (touch) {\n        pointer = pointers[touch.identifier];\n        delete pointers[touch.identifier];\n      });\n    } else {\n      pointer = pointers[event.pointerId || 0];\n      delete pointers[event.pointerId || 0];\n    }\n    if (!action) {\n      return;\n    }\n    event.preventDefault();\n    if (options.transition && (action === ACTION_MOVE || action === ACTION_ZOOM)) {\n      addClass(this.image, CLASS_TRANSITION);\n    }\n    this.action = false;\n\n    // Emulate click and double click in touch devices to support backdrop and image zooming (#210).\n    if (IS_TOUCH_DEVICE && action !== ACTION_ZOOM && pointer && Date.now() - pointer.timeStamp < 500) {\n      clearTimeout(this.clickCanvasTimeout);\n      clearTimeout(this.doubleClickImageTimeout);\n      if (options.toggleOnDblclick && this.viewed && event.target === this.image) {\n        if (this.imageClicked) {\n          this.imageClicked = false;\n\n          // This timeout will be cleared later when a native dblclick event is triggering\n          this.doubleClickImageTimeout = setTimeout(function () {\n            dispatchEvent(_this2.image, EVENT_DBLCLICK, {\n              originalEvent: event\n            });\n          }, 50);\n        } else {\n          this.imageClicked = true;\n\n          // The default timing of a double click in Windows is 500 ms\n          this.doubleClickImageTimeout = setTimeout(function () {\n            _this2.imageClicked = false;\n          }, 500);\n        }\n      } else {\n        this.imageClicked = false;\n        if (options.backdrop && options.backdrop !== 'static' && event.target === this.canvas) {\n          // This timeout will be cleared later when a native click event is triggering\n          this.clickCanvasTimeout = setTimeout(function () {\n            dispatchEvent(_this2.canvas, EVENT_CLICK, {\n              originalEvent: event\n            });\n          }, 50);\n        }\n      }\n    }\n  },\n  resize: function resize() {\n    var _this3 = this;\n    if (!this.isShown || this.hiding) {\n      return;\n    }\n    if (this.fulled) {\n      this.close();\n      this.initBody();\n      this.open();\n    }\n    this.initContainer();\n    this.initViewer();\n    this.renderViewer();\n    this.renderList();\n    if (this.viewed) {\n      this.initImage(function () {\n        _this3.renderImage();\n      });\n    }\n    if (this.played) {\n      if (this.options.fullscreen && this.fulled && !(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement)) {\n        this.stop();\n        return;\n      }\n      forEach(this.player.getElementsByTagName('img'), function (image) {\n        addListener(image, EVENT_LOAD, _this3.loadImage.bind(_this3), {\n          once: true\n        });\n        dispatchEvent(image, EVENT_LOAD);\n      });\n    }\n  },\n  wheel: function wheel(event) {\n    var _this4 = this;\n    if (!this.viewed) {\n      return;\n    }\n    event.preventDefault();\n\n    // Limit wheel speed to prevent zoom too fast\n    if (this.wheeling) {\n      return;\n    }\n    this.wheeling = true;\n    setTimeout(function () {\n      _this4.wheeling = false;\n    }, 50);\n    var ratio = Number(this.options.zoomRatio) || 0.1;\n    var delta = 1;\n    if (event.deltaY) {\n      delta = event.deltaY > 0 ? 1 : -1;\n    } else if (event.wheelDelta) {\n      delta = -event.wheelDelta / 120;\n    } else if (event.detail) {\n      delta = event.detail > 0 ? 1 : -1;\n    }\n    this.zoom(-delta * ratio, true, null, event);\n  }\n};\n\nvar methods = {\n  /** Show the viewer (only available in modal mode)\n   * @param {boolean} [immediate=false] - Indicates if show the viewer immediately or not.\n   * @returns {Viewer} this\n   */\n  show: function show() {\n    var immediate = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;\n    var element = this.element,\n      options = this.options;\n    if (options.inline || this.showing || this.isShown || this.showing) {\n      return this;\n    }\n    if (!this.ready) {\n      this.build();\n      if (this.ready) {\n        this.show(immediate);\n      }\n      return this;\n    }\n    if (isFunction(options.show)) {\n      addListener(element, EVENT_SHOW, options.show, {\n        once: true\n      });\n    }\n    if (dispatchEvent(element, EVENT_SHOW) === false || !this.ready) {\n      return this;\n    }\n    if (this.hiding) {\n      this.transitioning.abort();\n    }\n    this.showing = true;\n    this.open();\n    var viewer = this.viewer;\n    removeClass(viewer, CLASS_HIDE);\n    viewer.setAttribute('role', 'dialog');\n    viewer.setAttribute('aria-labelledby', this.title.id);\n    viewer.setAttribute('aria-modal', true);\n    viewer.removeAttribute('aria-hidden');\n    if (options.transition && !immediate) {\n      var shown = this.shown.bind(this);\n      this.transitioning = {\n        abort: function abort() {\n          removeListener(viewer, EVENT_TRANSITION_END, shown);\n          removeClass(viewer, CLASS_IN);\n        }\n      };\n      addClass(viewer, CLASS_TRANSITION);\n\n      // Force reflow to enable CSS3 transition\n      viewer.initialOffsetWidth = viewer.offsetWidth;\n      addListener(viewer, EVENT_TRANSITION_END, shown, {\n        once: true\n      });\n      addClass(viewer, CLASS_IN);\n    } else {\n      addClass(viewer, CLASS_IN);\n      this.shown();\n    }\n    return this;\n  },\n  /**\n   * Hide the viewer (only available in modal mode)\n   * @param {boolean} [immediate=false] - Indicates if hide the viewer immediately or not.\n   * @returns {Viewer} this\n   */\n  hide: function hide() {\n    var _this = this;\n    var immediate = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;\n    var element = this.element,\n      options = this.options;\n    if (options.inline || this.hiding || !(this.isShown || this.showing)) {\n      return this;\n    }\n    if (isFunction(options.hide)) {\n      addListener(element, EVENT_HIDE, options.hide, {\n        once: true\n      });\n    }\n    if (dispatchEvent(element, EVENT_HIDE) === false) {\n      return this;\n    }\n    if (this.showing) {\n      this.transitioning.abort();\n    }\n    this.hiding = true;\n    if (this.played) {\n      this.stop();\n    } else if (this.viewing) {\n      this.viewing.abort();\n    }\n    var viewer = this.viewer,\n      image = this.image;\n    var hideImmediately = function hideImmediately() {\n      removeClass(viewer, CLASS_IN);\n      _this.hidden();\n    };\n    if (options.transition && !immediate) {\n      var _onViewerTransitionEnd = function onViewerTransitionEnd(event) {\n        // Ignore all propagating `transitionend` events (#275).\n        if (event && event.target === viewer) {\n          removeListener(viewer, EVENT_TRANSITION_END, _onViewerTransitionEnd);\n          _this.hidden();\n        }\n      };\n      var onImageTransitionEnd = function onImageTransitionEnd() {\n        // In case of show the viewer by `viewer.show(true)` previously (#407).\n        if (hasClass(viewer, CLASS_TRANSITION)) {\n          addListener(viewer, EVENT_TRANSITION_END, _onViewerTransitionEnd);\n          removeClass(viewer, CLASS_IN);\n        } else {\n          hideImmediately();\n        }\n      };\n      this.transitioning = {\n        abort: function abort() {\n          if (_this.viewed && hasClass(image, CLASS_TRANSITION)) {\n            removeListener(image, EVENT_TRANSITION_END, onImageTransitionEnd);\n          } else if (hasClass(viewer, CLASS_TRANSITION)) {\n            removeListener(viewer, EVENT_TRANSITION_END, _onViewerTransitionEnd);\n          }\n        }\n      };\n\n      // In case of hiding the viewer when holding on the image (#255),\n      // note that the `CLASS_TRANSITION` class will be removed on pointer down.\n      if (this.viewed && hasClass(image, CLASS_TRANSITION)) {\n        addListener(image, EVENT_TRANSITION_END, onImageTransitionEnd, {\n          once: true\n        });\n        this.zoomTo(0, false, null, null, true);\n      } else {\n        onImageTransitionEnd();\n      }\n    } else {\n      hideImmediately();\n    }\n    return this;\n  },\n  /**\n   * View one of the images with image's index\n   * @param {number} index - The index of the image to view.\n   * @returns {Viewer} this\n   */\n  view: function view() {\n    var _this2 = this;\n    var index = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.options.initialViewIndex;\n    index = Number(index) || 0;\n    if (this.hiding || this.played || index < 0 || index >= this.length || this.viewed && index === this.index) {\n      return this;\n    }\n    if (!this.isShown) {\n      this.index = index;\n      return this.show();\n    }\n    if (this.viewing) {\n      this.viewing.abort();\n    }\n    var element = this.element,\n      options = this.options,\n      title = this.title,\n      canvas = this.canvas;\n    var item = this.items[index];\n    var img = item.querySelector('img');\n    var url = getData(img, 'originalUrl');\n    var alt = img.getAttribute('alt');\n    var image = document.createElement('img');\n    forEach(options.inheritedAttributes, function (name) {\n      var value = img.getAttribute(name);\n      if (value !== null) {\n        image.setAttribute(name, value);\n      }\n    });\n    image.src = url;\n    image.alt = alt;\n    if (isFunction(options.view)) {\n      addListener(element, EVENT_VIEW, options.view, {\n        once: true\n      });\n    }\n    if (dispatchEvent(element, EVENT_VIEW, {\n      originalImage: this.images[index],\n      index: index,\n      image: image\n    }) === false || !this.isShown || this.hiding || this.played) {\n      return this;\n    }\n    var activeItem = this.items[this.index];\n    if (activeItem) {\n      removeClass(activeItem, CLASS_ACTIVE);\n      activeItem.removeAttribute('aria-selected');\n    }\n    addClass(item, CLASS_ACTIVE);\n    item.setAttribute('aria-selected', true);\n    if (options.focus) {\n      item.focus();\n    }\n    this.image = image;\n    this.viewed = false;\n    this.index = index;\n    this.imageData = {};\n    addClass(image, CLASS_INVISIBLE);\n    if (options.loading) {\n      addClass(canvas, CLASS_LOADING);\n    }\n    canvas.innerHTML = '';\n    canvas.appendChild(image);\n\n    // Center current item\n    this.renderList();\n\n    // Clear title\n    title.innerHTML = '';\n\n    // Generate title after viewed\n    var onViewed = function onViewed() {\n      var imageData = _this2.imageData;\n      var render = Array.isArray(options.title) ? options.title[1] : options.title;\n      title.innerHTML = escapeHTMLEntities(isFunction(render) ? render.call(_this2, image, imageData) : \"\".concat(alt, \" (\").concat(imageData.naturalWidth, \" \\xD7 \").concat(imageData.naturalHeight, \")\"));\n    };\n    var onLoad;\n    var onError;\n    addListener(element, EVENT_VIEWED, onViewed, {\n      once: true\n    });\n    this.viewing = {\n      abort: function abort() {\n        removeListener(element, EVENT_VIEWED, onViewed);\n        if (image.complete) {\n          if (_this2.imageRendering) {\n            _this2.imageRendering.abort();\n          } else if (_this2.imageInitializing) {\n            _this2.imageInitializing.abort();\n          }\n        } else {\n          // Cancel download to save bandwidth.\n          image.src = '';\n          removeListener(image, EVENT_LOAD, onLoad);\n          if (_this2.timeout) {\n            clearTimeout(_this2.timeout);\n          }\n        }\n      }\n    };\n    if (image.complete) {\n      this.load();\n    } else {\n      addListener(image, EVENT_LOAD, onLoad = function onLoad() {\n        removeListener(image, EVENT_ERROR, onError);\n        _this2.load();\n      }, {\n        once: true\n      });\n      addListener(image, EVENT_ERROR, onError = function onError() {\n        removeListener(image, EVENT_LOAD, onLoad);\n        if (_this2.timeout) {\n          clearTimeout(_this2.timeout);\n          _this2.timeout = false;\n        }\n        removeClass(image, CLASS_INVISIBLE);\n        if (options.loading) {\n          removeClass(_this2.canvas, CLASS_LOADING);\n        }\n      }, {\n        once: true\n      });\n      if (this.timeout) {\n        clearTimeout(this.timeout);\n      }\n\n      // Make the image visible if it fails to load within 1s\n      this.timeout = setTimeout(function () {\n        removeClass(image, CLASS_INVISIBLE);\n        _this2.timeout = false;\n      }, 1000);\n    }\n    return this;\n  },\n  /**\n   * View the previous image\n   * @param {boolean} [loop=false] - Indicate if view the last one\n   * when it is the first one at present.\n   * @returns {Viewer} this\n   */\n  prev: function prev() {\n    var loop = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;\n    var index = this.index - 1;\n    if (index < 0) {\n      index = loop ? this.length - 1 : 0;\n    }\n    this.view(index);\n    return this;\n  },\n  /**\n   * View the next image\n   * @param {boolean} [loop=false] - Indicate if view the first one\n   * when it is the last one at present.\n   * @returns {Viewer} this\n   */\n  next: function next() {\n    var loop = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;\n    var maxIndex = this.length - 1;\n    var index = this.index + 1;\n    if (index > maxIndex) {\n      index = loop ? 0 : maxIndex;\n    }\n    this.view(index);\n    return this;\n  },\n  /**\n   * Move the image with relative offsets.\n   * @param {number} x - The moving distance in the horizontal direction.\n   * @param {number} [y=x] The moving distance in the vertical direction.\n   * @returns {Viewer} this\n   */\n  move: function move(x) {\n    var y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : x;\n    var imageData = this.imageData;\n    this.moveTo(isUndefined(x) ? x : imageData.x + Number(x), isUndefined(y) ? y : imageData.y + Number(y));\n    return this;\n  },\n  /**\n   * Move the image to an absolute point.\n   * @param {number} x - The new position in the horizontal direction.\n   * @param {number} [y=x] - The new position in the vertical direction.\n   * @param {Event} [_originalEvent=null] - The original event if any.\n   * @returns {Viewer} this\n   */\n  moveTo: function moveTo(x) {\n    var _this3 = this;\n    var y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : x;\n    var _originalEvent = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;\n    var element = this.element,\n      options = this.options,\n      imageData = this.imageData;\n    x = Number(x);\n    y = Number(y);\n    if (this.viewed && !this.played && options.movable) {\n      var oldX = imageData.x;\n      var oldY = imageData.y;\n      var changed = false;\n      if (isNumber(x)) {\n        changed = true;\n      } else {\n        x = oldX;\n      }\n      if (isNumber(y)) {\n        changed = true;\n      } else {\n        y = oldY;\n      }\n      if (changed) {\n        if (isFunction(options.move)) {\n          addListener(element, EVENT_MOVE, options.move, {\n            once: true\n          });\n        }\n        if (dispatchEvent(element, EVENT_MOVE, {\n          x: x,\n          y: y,\n          oldX: oldX,\n          oldY: oldY,\n          originalEvent: _originalEvent\n        }) === false) {\n          return this;\n        }\n        imageData.x = x;\n        imageData.y = y;\n        imageData.left = x;\n        imageData.top = y;\n        this.moving = true;\n        this.renderImage(function () {\n          _this3.moving = false;\n          if (isFunction(options.moved)) {\n            addListener(element, EVENT_MOVED, options.moved, {\n              once: true\n            });\n          }\n          dispatchEvent(element, EVENT_MOVED, {\n            x: x,\n            y: y,\n            oldX: oldX,\n            oldY: oldY,\n            originalEvent: _originalEvent\n          }, {\n            cancelable: false\n          });\n        });\n      }\n    }\n    return this;\n  },\n  /**\n   * Rotate the image with a relative degree.\n   * @param {number} degree - The rotate degree.\n   * @returns {Viewer} this\n   */\n  rotate: function rotate(degree) {\n    this.rotateTo((this.imageData.rotate || 0) + Number(degree));\n    return this;\n  },\n  /**\n   * Rotate the image to an absolute degree.\n   * @param {number} degree - The rotate degree.\n   * @returns {Viewer} this\n   */\n  rotateTo: function rotateTo(degree) {\n    var _this4 = this;\n    var element = this.element,\n      options = this.options,\n      imageData = this.imageData;\n    degree = Number(degree);\n    if (isNumber(degree) && this.viewed && !this.played && options.rotatable) {\n      var oldDegree = imageData.rotate;\n      if (isFunction(options.rotate)) {\n        addListener(element, EVENT_ROTATE, options.rotate, {\n          once: true\n        });\n      }\n      if (dispatchEvent(element, EVENT_ROTATE, {\n        degree: degree,\n        oldDegree: oldDegree\n      }) === false) {\n        return this;\n      }\n      imageData.rotate = degree;\n      this.rotating = true;\n      this.renderImage(function () {\n        _this4.rotating = false;\n        if (isFunction(options.rotated)) {\n          addListener(element, EVENT_ROTATED, options.rotated, {\n            once: true\n          });\n        }\n        dispatchEvent(element, EVENT_ROTATED, {\n          degree: degree,\n          oldDegree: oldDegree\n        }, {\n          cancelable: false\n        });\n      });\n    }\n    return this;\n  },\n  /**\n   * Scale the image on the x-axis.\n   * @param {number} scaleX - The scale ratio on the x-axis.\n   * @returns {Viewer} this\n   */\n  scaleX: function scaleX(_scaleX) {\n    this.scale(_scaleX, this.imageData.scaleY);\n    return this;\n  },\n  /**\n   * Scale the image on the y-axis.\n   * @param {number} scaleY - The scale ratio on the y-axis.\n   * @returns {Viewer} this\n   */\n  scaleY: function scaleY(_scaleY) {\n    this.scale(this.imageData.scaleX, _scaleY);\n    return this;\n  },\n  /**\n   * Scale the image.\n   * @param {number} scaleX - The scale ratio on the x-axis.\n   * @param {number} [scaleY=scaleX] - The scale ratio on the y-axis.\n   * @returns {Viewer} this\n   */\n  scale: function scale(scaleX) {\n    var _this5 = this;\n    var scaleY = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : scaleX;\n    var element = this.element,\n      options = this.options,\n      imageData = this.imageData;\n    scaleX = Number(scaleX);\n    scaleY = Number(scaleY);\n    if (this.viewed && !this.played && options.scalable) {\n      var oldScaleX = imageData.scaleX;\n      var oldScaleY = imageData.scaleY;\n      var changed = false;\n      if (isNumber(scaleX)) {\n        changed = true;\n      } else {\n        scaleX = oldScaleX;\n      }\n      if (isNumber(scaleY)) {\n        changed = true;\n      } else {\n        scaleY = oldScaleY;\n      }\n      if (changed) {\n        if (isFunction(options.scale)) {\n          addListener(element, EVENT_SCALE, options.scale, {\n            once: true\n          });\n        }\n        if (dispatchEvent(element, EVENT_SCALE, {\n          scaleX: scaleX,\n          scaleY: scaleY,\n          oldScaleX: oldScaleX,\n          oldScaleY: oldScaleY\n        }) === false) {\n          return this;\n        }\n        imageData.scaleX = scaleX;\n        imageData.scaleY = scaleY;\n        this.scaling = true;\n        this.renderImage(function () {\n          _this5.scaling = false;\n          if (isFunction(options.scaled)) {\n            addListener(element, EVENT_SCALED, options.scaled, {\n              once: true\n            });\n          }\n          dispatchEvent(element, EVENT_SCALED, {\n            scaleX: scaleX,\n            scaleY: scaleY,\n            oldScaleX: oldScaleX,\n            oldScaleY: oldScaleY\n          }, {\n            cancelable: false\n          });\n        });\n      }\n    }\n    return this;\n  },\n  /**\n   * Zoom the image with a relative ratio.\n   * @param {number} ratio - The target ratio.\n   * @param {boolean} [showTooltip=false] - Indicates whether to show the tooltip.\n   * @param {Object} [pivot] - The pivot point coordinate for zooming.\n   * @param {Event} [_originalEvent=null] - The original event if any.\n   * @returns {Viewer} this\n   */\n  zoom: function zoom(ratio) {\n    var showTooltip = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;\n    var pivot = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;\n    var _originalEvent = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;\n    var imageData = this.imageData;\n    ratio = Number(ratio);\n    if (ratio < 0) {\n      ratio = 1 / (1 - ratio);\n    } else {\n      ratio = 1 + ratio;\n    }\n    this.zoomTo(imageData.width * ratio / imageData.naturalWidth, showTooltip, pivot, _originalEvent);\n    return this;\n  },\n  /**\n   * Zoom the image to an absolute ratio.\n   * @param {number} ratio - The target ratio.\n   * @param {boolean} [showTooltip] - Indicates whether to show the tooltip.\n   * @param {Object} [pivot] - The pivot point coordinate for zooming.\n   * @param {Event} [_originalEvent=null] - The original event if any.\n   * @param {Event} [_zoomable=false] - Indicates if the current zoom is available or not.\n   * @returns {Viewer} this\n   */\n  zoomTo: function zoomTo(ratio) {\n    var _this6 = this;\n    var showTooltip = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;\n    var pivot = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;\n    var _originalEvent = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;\n    var _zoomable = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;\n    var element = this.element,\n      options = this.options,\n      pointers = this.pointers,\n      imageData = this.imageData;\n    var x = imageData.x,\n      y = imageData.y,\n      width = imageData.width,\n      height = imageData.height,\n      naturalWidth = imageData.naturalWidth,\n      naturalHeight = imageData.naturalHeight;\n    ratio = Math.max(0, ratio);\n    if (isNumber(ratio) && this.viewed && !this.played && (_zoomable || options.zoomable)) {\n      if (!_zoomable) {\n        var minZoomRatio = Math.max(0.01, options.minZoomRatio);\n        var maxZoomRatio = Math.min(100, options.maxZoomRatio);\n        ratio = Math.min(Math.max(ratio, minZoomRatio), maxZoomRatio);\n      }\n      if (_originalEvent) {\n        switch (_originalEvent.type) {\n          case 'wheel':\n            if (options.zoomRatio >= 0.055 && ratio > 0.95 && ratio < 1.05) {\n              ratio = 1;\n            }\n            break;\n          case 'pointermove':\n          case 'touchmove':\n          case 'mousemove':\n            if (ratio > 0.99 && ratio < 1.01) {\n              ratio = 1;\n            }\n            break;\n        }\n      }\n      var newWidth = naturalWidth * ratio;\n      var newHeight = naturalHeight * ratio;\n      var offsetWidth = newWidth - width;\n      var offsetHeight = newHeight - height;\n      var oldRatio = imageData.ratio;\n      if (isFunction(options.zoom)) {\n        addListener(element, EVENT_ZOOM, options.zoom, {\n          once: true\n        });\n      }\n      if (dispatchEvent(element, EVENT_ZOOM, {\n        ratio: ratio,\n        oldRatio: oldRatio,\n        originalEvent: _originalEvent\n      }) === false) {\n        return this;\n      }\n      this.zooming = true;\n      if (_originalEvent) {\n        var offset = getOffset(this.viewer);\n        var center = pointers && Object.keys(pointers).length > 0 ? getPointersCenter(pointers) : {\n          pageX: _originalEvent.pageX,\n          pageY: _originalEvent.pageY\n        };\n\n        // Zoom from the triggering point of the event\n        imageData.x -= offsetWidth * ((center.pageX - offset.left - x) / width);\n        imageData.y -= offsetHeight * ((center.pageY - offset.top - y) / height);\n      } else if (isPlainObject(pivot) && isNumber(pivot.x) && isNumber(pivot.y)) {\n        imageData.x -= offsetWidth * ((pivot.x - x) / width);\n        imageData.y -= offsetHeight * ((pivot.y - y) / height);\n      } else {\n        // Zoom from the center of the image\n        imageData.x -= offsetWidth / 2;\n        imageData.y -= offsetHeight / 2;\n      }\n      imageData.left = imageData.x;\n      imageData.top = imageData.y;\n      imageData.width = newWidth;\n      imageData.height = newHeight;\n      imageData.oldRatio = oldRatio;\n      imageData.ratio = ratio;\n      this.renderImage(function () {\n        _this6.zooming = false;\n        if (isFunction(options.zoomed)) {\n          addListener(element, EVENT_ZOOMED, options.zoomed, {\n            once: true\n          });\n        }\n        dispatchEvent(element, EVENT_ZOOMED, {\n          ratio: ratio,\n          oldRatio: oldRatio,\n          originalEvent: _originalEvent\n        }, {\n          cancelable: false\n        });\n      });\n      if (showTooltip) {\n        this.tooltip();\n      }\n    }\n    return this;\n  },\n  /**\n   * Play the images\n   * @param {boolean|FullscreenOptions} [fullscreen=false] - Indicate if request fullscreen or not.\n   * @returns {Viewer} this\n   */\n  play: function play() {\n    var _this7 = this;\n    var fullscreen = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;\n    if (!this.isShown || this.played) {\n      return this;\n    }\n    var element = this.element,\n      options = this.options;\n    if (isFunction(options.play)) {\n      addListener(element, EVENT_PLAY, options.play, {\n        once: true\n      });\n    }\n    if (dispatchEvent(element, EVENT_PLAY) === false) {\n      return this;\n    }\n    var player = this.player;\n    var onLoad = this.loadImage.bind(this);\n    var list = [];\n    var total = 0;\n    var index = 0;\n    this.played = true;\n    this.onLoadWhenPlay = onLoad;\n    if (fullscreen) {\n      this.requestFullscreen(fullscreen);\n    }\n    addClass(player, CLASS_SHOW);\n    forEach(this.items, function (item, i) {\n      var img = item.querySelector('img');\n      var image = document.createElement('img');\n      image.src = getData(img, 'originalUrl');\n      image.alt = img.getAttribute('alt');\n      image.referrerPolicy = img.referrerPolicy;\n      total += 1;\n      addClass(image, CLASS_FADE);\n      toggleClass(image, CLASS_TRANSITION, options.transition);\n      if (hasClass(item, CLASS_ACTIVE)) {\n        addClass(image, CLASS_IN);\n        index = i;\n      }\n      list.push(image);\n      addListener(image, EVENT_LOAD, onLoad, {\n        once: true\n      });\n      player.appendChild(image);\n    });\n    if (isNumber(options.interval) && options.interval > 0) {\n      var _prev = function prev() {\n        clearTimeout(_this7.playing.timeout);\n        removeClass(list[index], CLASS_IN);\n        index -= 1;\n        index = index >= 0 ? index : total - 1;\n        addClass(list[index], CLASS_IN);\n        _this7.playing.timeout = setTimeout(_prev, options.interval);\n      };\n      var _next = function next() {\n        clearTimeout(_this7.playing.timeout);\n        removeClass(list[index], CLASS_IN);\n        index += 1;\n        index = index < total ? index : 0;\n        addClass(list[index], CLASS_IN);\n        _this7.playing.timeout = setTimeout(_next, options.interval);\n      };\n      if (total > 1) {\n        this.playing = {\n          prev: _prev,\n          next: _next,\n          timeout: setTimeout(_next, options.interval)\n        };\n      }\n    }\n    return this;\n  },\n  // Stop play\n  stop: function stop() {\n    var _this8 = this;\n    if (!this.played) {\n      return this;\n    }\n    var element = this.element,\n      options = this.options;\n    if (isFunction(options.stop)) {\n      addListener(element, EVENT_STOP, options.stop, {\n        once: true\n      });\n    }\n    if (dispatchEvent(element, EVENT_STOP) === false) {\n      return this;\n    }\n    var player = this.player;\n    clearTimeout(this.playing.timeout);\n    this.playing = false;\n    this.played = false;\n    forEach(player.getElementsByTagName('img'), function (image) {\n      removeListener(image, EVENT_LOAD, _this8.onLoadWhenPlay);\n    });\n    removeClass(player, CLASS_SHOW);\n    player.innerHTML = '';\n    this.exitFullscreen();\n    return this;\n  },\n  // Enter modal mode (only available in inline mode)\n  full: function full() {\n    var _this9 = this;\n    var options = this.options,\n      viewer = this.viewer,\n      image = this.image,\n      list = this.list;\n    if (!this.isShown || this.played || this.fulled || !options.inline) {\n      return this;\n    }\n    this.fulled = true;\n    this.open();\n    addClass(this.button, CLASS_FULLSCREEN_EXIT);\n    if (options.transition) {\n      removeClass(list, CLASS_TRANSITION);\n      if (this.viewed) {\n        removeClass(image, CLASS_TRANSITION);\n      }\n    }\n    addClass(viewer, CLASS_FIXED);\n    viewer.setAttribute('role', 'dialog');\n    viewer.setAttribute('aria-labelledby', this.title.id);\n    viewer.setAttribute('aria-modal', true);\n    viewer.removeAttribute('style');\n    setStyle(viewer, {\n      zIndex: options.zIndex\n    });\n    if (options.focus) {\n      this.enforceFocus();\n    }\n    this.initContainer();\n    this.viewerData = assign({}, this.containerData);\n    this.renderList();\n    if (this.viewed) {\n      this.initImage(function () {\n        _this9.renderImage(function () {\n          if (options.transition) {\n            setTimeout(function () {\n              addClass(image, CLASS_TRANSITION);\n              addClass(list, CLASS_TRANSITION);\n            }, 0);\n          }\n        });\n      });\n    }\n    return this;\n  },\n  // Exit modal mode (only available in inline mode)\n  exit: function exit() {\n    var _this10 = this;\n    var options = this.options,\n      viewer = this.viewer,\n      image = this.image,\n      list = this.list;\n    if (!this.isShown || this.played || !this.fulled || !options.inline) {\n      return this;\n    }\n    this.fulled = false;\n    this.close();\n    removeClass(this.button, CLASS_FULLSCREEN_EXIT);\n    if (options.transition) {\n      removeClass(list, CLASS_TRANSITION);\n      if (this.viewed) {\n        removeClass(image, CLASS_TRANSITION);\n      }\n    }\n    if (options.focus) {\n      this.clearEnforceFocus();\n    }\n    viewer.removeAttribute('role');\n    viewer.removeAttribute('aria-labelledby');\n    viewer.removeAttribute('aria-modal');\n    removeClass(viewer, CLASS_FIXED);\n    setStyle(viewer, {\n      zIndex: options.zIndexInline\n    });\n    this.viewerData = assign({}, this.parentData);\n    this.renderViewer();\n    this.renderList();\n    if (this.viewed) {\n      this.initImage(function () {\n        _this10.renderImage(function () {\n          if (options.transition) {\n            setTimeout(function () {\n              addClass(image, CLASS_TRANSITION);\n              addClass(list, CLASS_TRANSITION);\n            }, 0);\n          }\n        });\n      });\n    }\n    return this;\n  },\n  // Show the current ratio of the image with percentage\n  tooltip: function tooltip() {\n    var _this11 = this;\n    var options = this.options,\n      tooltipBox = this.tooltipBox,\n      imageData = this.imageData;\n    if (!this.viewed || this.played || !options.tooltip) {\n      return this;\n    }\n    tooltipBox.textContent = \"\".concat(Math.round(imageData.ratio * 100), \"%\");\n    if (!this.tooltipping) {\n      if (options.transition) {\n        if (this.fading) {\n          dispatchEvent(tooltipBox, EVENT_TRANSITION_END);\n        }\n        addClass(tooltipBox, CLASS_SHOW);\n        addClass(tooltipBox, CLASS_FADE);\n        addClass(tooltipBox, CLASS_TRANSITION);\n        tooltipBox.removeAttribute('aria-hidden');\n\n        // Force reflow to enable CSS3 transition\n        tooltipBox.initialOffsetWidth = tooltipBox.offsetWidth;\n        addClass(tooltipBox, CLASS_IN);\n      } else {\n        addClass(tooltipBox, CLASS_SHOW);\n        tooltipBox.removeAttribute('aria-hidden');\n      }\n    } else {\n      clearTimeout(this.tooltipping);\n    }\n    this.tooltipping = setTimeout(function () {\n      if (options.transition) {\n        addListener(tooltipBox, EVENT_TRANSITION_END, function () {\n          removeClass(tooltipBox, CLASS_SHOW);\n          removeClass(tooltipBox, CLASS_FADE);\n          removeClass(tooltipBox, CLASS_TRANSITION);\n          tooltipBox.setAttribute('aria-hidden', true);\n          _this11.fading = false;\n        }, {\n          once: true\n        });\n        removeClass(tooltipBox, CLASS_IN);\n        _this11.fading = true;\n      } else {\n        removeClass(tooltipBox, CLASS_SHOW);\n        tooltipBox.setAttribute('aria-hidden', true);\n      }\n      _this11.tooltipping = false;\n    }, 1000);\n    return this;\n  },\n  /**\n   * Toggle the image size between its current size and natural size\n   * @param {Event} [_originalEvent=null] - The original event if any.\n   * @returns {Viewer} this\n   */\n  toggle: function toggle() {\n    var _originalEvent = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;\n    if (this.imageData.ratio === 1) {\n      this.zoomTo(this.imageData.oldRatio, true, null, _originalEvent);\n    } else {\n      this.zoomTo(1, true, null, _originalEvent);\n    }\n    return this;\n  },\n  // Reset the image to its initial state\n  reset: function reset() {\n    if (this.viewed && !this.played) {\n      this.imageData = assign({}, this.initialImageData);\n      this.renderImage();\n    }\n    return this;\n  },\n  // Update viewer when images changed\n  update: function update() {\n    var _this12 = this;\n    var element = this.element,\n      options = this.options,\n      isImg = this.isImg;\n\n    // Destroy viewer if the target image was deleted\n    if (isImg && !element.parentNode) {\n      return this.destroy();\n    }\n    var images = [];\n    forEach(isImg ? [element] : element.querySelectorAll('img'), function (image) {\n      if (isFunction(options.filter)) {\n        if (options.filter.call(_this12, image)) {\n          images.push(image);\n        }\n      } else if (_this12.getImageURL(image)) {\n        images.push(image);\n      }\n    });\n    if (!images.length) {\n      return this;\n    }\n    this.images = images;\n    this.length = images.length;\n    if (this.ready) {\n      var changedIndexes = [];\n      forEach(this.items, function (item, i) {\n        var img = item.querySelector('img');\n        var image = images[i];\n        if (image && img) {\n          if (image.src !== img.src\n\n          // Title changed (#408)\n          || image.alt !== img.alt) {\n            changedIndexes.push(i);\n          }\n        } else {\n          changedIndexes.push(i);\n        }\n      });\n      setStyle(this.list, {\n        width: 'auto'\n      });\n      this.initList();\n      if (this.isShown) {\n        if (this.length) {\n          if (this.viewed) {\n            var changedIndex = changedIndexes.indexOf(this.index);\n            if (changedIndex >= 0) {\n              this.viewed = false;\n              this.view(Math.max(Math.min(this.index - changedIndex, this.length - 1), 0));\n            } else {\n              var activeItem = this.items[this.index];\n\n              // Reactivate the current viewing item after reset the list.\n              addClass(activeItem, CLASS_ACTIVE);\n              activeItem.setAttribute('aria-selected', true);\n            }\n          }\n        } else {\n          this.image = null;\n          this.viewed = false;\n          this.index = 0;\n          this.imageData = {};\n          this.canvas.innerHTML = '';\n          this.title.innerHTML = '';\n        }\n      }\n    } else {\n      this.build();\n    }\n    return this;\n  },\n  // Destroy the viewer\n  destroy: function destroy() {\n    var element = this.element,\n      options = this.options;\n    if (!element[NAMESPACE]) {\n      return this;\n    }\n    this.destroyed = true;\n    if (this.ready) {\n      if (this.played) {\n        this.stop();\n      }\n      if (options.inline) {\n        if (this.fulled) {\n          this.exit();\n        }\n        this.unbind();\n      } else if (this.isShown) {\n        if (this.viewing) {\n          if (this.imageRendering) {\n            this.imageRendering.abort();\n          } else if (this.imageInitializing) {\n            this.imageInitializing.abort();\n          }\n        }\n        if (this.hiding) {\n          this.transitioning.abort();\n        }\n        this.hidden();\n      } else if (this.showing) {\n        this.transitioning.abort();\n        this.hidden();\n      }\n      this.ready = false;\n      this.viewer.parentNode.removeChild(this.viewer);\n    } else if (options.inline) {\n      if (this.delaying) {\n        this.delaying.abort();\n      } else if (this.initializing) {\n        this.initializing.abort();\n      }\n    }\n    if (!options.inline) {\n      removeListener(element, EVENT_CLICK, this.onStart);\n    }\n    element[NAMESPACE] = undefined;\n    return this;\n  }\n};\n\nvar others = {\n  getImageURL: function getImageURL(image) {\n    var url = this.options.url;\n    if (isString(url)) {\n      url = image.getAttribute(url);\n    } else if (isFunction(url)) {\n      url = url.call(this, image);\n    } else {\n      url = '';\n    }\n    return url;\n  },\n  enforceFocus: function enforceFocus() {\n    var _this = this;\n    this.clearEnforceFocus();\n    addListener(document, EVENT_FOCUSIN, this.onFocusin = function (event) {\n      var viewer = _this.viewer;\n      var target = event.target;\n      if (target === document || target === viewer || viewer.contains(target)) {\n        return;\n      }\n      while (target) {\n        // Avoid conflicts with other modals (#474, #540)\n        if (target.getAttribute('tabindex') !== null || target.getAttribute('aria-modal') === 'true') {\n          return;\n        }\n        target = target.parentElement;\n      }\n      viewer.focus();\n    });\n  },\n  clearEnforceFocus: function clearEnforceFocus() {\n    if (this.onFocusin) {\n      removeListener(document, EVENT_FOCUSIN, this.onFocusin);\n      this.onFocusin = null;\n    }\n  },\n  open: function open() {\n    var body = this.body;\n    addClass(body, CLASS_OPEN);\n    if (this.scrollbarWidth > 0) {\n      body.style.paddingRight = \"\".concat(this.scrollbarWidth + (parseFloat(this.initialBodyComputedPaddingRight) || 0), \"px\");\n    }\n  },\n  close: function close() {\n    var body = this.body;\n    removeClass(body, CLASS_OPEN);\n    if (this.scrollbarWidth > 0) {\n      body.style.paddingRight = this.initialBodyPaddingRight;\n    }\n  },\n  shown: function shown() {\n    var element = this.element,\n      options = this.options,\n      viewer = this.viewer;\n    this.fulled = true;\n    this.isShown = true;\n    this.render();\n    this.bind();\n    this.showing = false;\n    if (options.focus) {\n      viewer.focus();\n      this.enforceFocus();\n    }\n    if (isFunction(options.shown)) {\n      addListener(element, EVENT_SHOWN, options.shown, {\n        once: true\n      });\n    }\n    if (dispatchEvent(element, EVENT_SHOWN) === false) {\n      return;\n    }\n    if (this.ready && this.isShown && !this.hiding) {\n      this.view(this.index);\n    }\n  },\n  hidden: function hidden() {\n    var element = this.element,\n      options = this.options,\n      viewer = this.viewer;\n    if (options.fucus) {\n      this.clearEnforceFocus();\n    }\n    this.close();\n    this.unbind();\n    addClass(viewer, CLASS_HIDE);\n    viewer.removeAttribute('role');\n    viewer.removeAttribute('aria-labelledby');\n    viewer.removeAttribute('aria-modal');\n    viewer.setAttribute('aria-hidden', true);\n    this.resetList();\n    this.resetImage();\n    this.fulled = false;\n    this.viewed = false;\n    this.isShown = false;\n    this.hiding = false;\n    if (!this.destroyed) {\n      if (isFunction(options.hidden)) {\n        addListener(element, EVENT_HIDDEN, options.hidden, {\n          once: true\n        });\n      }\n      dispatchEvent(element, EVENT_HIDDEN, null, {\n        cancelable: false\n      });\n    }\n  },\n  requestFullscreen: function requestFullscreen(options) {\n    var document = this.element.ownerDocument;\n    if (this.fulled && !(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement)) {\n      var documentElement = document.documentElement;\n\n      // Element.requestFullscreen()\n      if (documentElement.requestFullscreen) {\n        // Avoid TypeError when convert `options` to dictionary\n        if (isPlainObject(options)) {\n          documentElement.requestFullscreen(options);\n        } else {\n          documentElement.requestFullscreen();\n        }\n      } else if (documentElement.webkitRequestFullscreen) {\n        documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);\n      } else if (documentElement.mozRequestFullScreen) {\n        documentElement.mozRequestFullScreen();\n      } else if (documentElement.msRequestFullscreen) {\n        documentElement.msRequestFullscreen();\n      }\n    }\n  },\n  exitFullscreen: function exitFullscreen() {\n    var document = this.element.ownerDocument;\n    if (this.fulled && (document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement)) {\n      // Document.exitFullscreen()\n      if (document.exitFullscreen) {\n        document.exitFullscreen();\n      } else if (document.webkitExitFullscreen) {\n        document.webkitExitFullscreen();\n      } else if (document.mozCancelFullScreen) {\n        document.mozCancelFullScreen();\n      } else if (document.msExitFullscreen) {\n        document.msExitFullscreen();\n      }\n    }\n  },\n  change: function change(event) {\n    var options = this.options,\n      pointers = this.pointers;\n    var pointer = pointers[Object.keys(pointers)[0]];\n\n    // In the case of the `pointers` object is empty (#421)\n    if (!pointer) {\n      return;\n    }\n    var offsetX = pointer.endX - pointer.startX;\n    var offsetY = pointer.endY - pointer.startY;\n    switch (this.action) {\n      // Move the current image\n      case ACTION_MOVE:\n        if (offsetX !== 0 || offsetY !== 0) {\n          this.pointerMoved = true;\n          this.move(offsetX, offsetY, event);\n        }\n        break;\n\n      // Zoom the current image\n      case ACTION_ZOOM:\n        this.zoom(getMaxZoomRatio(pointers), false, null, event);\n        break;\n      case ACTION_SWITCH:\n        {\n          this.action = 'switched';\n          var absoluteOffsetX = Math.abs(offsetX);\n          if (absoluteOffsetX > 1 && absoluteOffsetX > Math.abs(offsetY)) {\n            // Empty `pointers` as `touchend` event will not be fired after swiped in iOS browsers.\n            this.pointers = {};\n            if (offsetX > 1) {\n              this.prev(options.loop);\n            } else if (offsetX < -1) {\n              this.next(options.loop);\n            }\n          }\n          break;\n        }\n    }\n\n    // Override\n    forEach(pointers, function (p) {\n      p.startX = p.endX;\n      p.startY = p.endY;\n    });\n  },\n  isSwitchable: function isSwitchable() {\n    var imageData = this.imageData,\n      viewerData = this.viewerData;\n    return this.length > 1 && imageData.x >= 0 && imageData.y >= 0 && imageData.width <= viewerData.width && imageData.height <= viewerData.height;\n  }\n};\n\nvar AnotherViewer = WINDOW.Viewer;\nvar getUniqueID = function (id) {\n  return function () {\n    id += 1;\n    return id;\n  };\n}(-1);\nvar Viewer = /*#__PURE__*/function () {\n  /**\n   * Create a new Viewer.\n   * @param {Element} element - The target element for viewing.\n   * @param {Object} [options={}] - The configuration options.\n   */\n  function Viewer(element) {\n    var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n    _classCallCheck(this, Viewer);\n    if (!element || element.nodeType !== 1) {\n      throw new Error('The first argument is required and must be an element.');\n    }\n    this.element = element;\n    this.options = assign({}, DEFAULTS, isPlainObject(options) && options);\n    this.action = false;\n    this.fading = false;\n    this.fulled = false;\n    this.hiding = false;\n    this.imageClicked = false;\n    this.imageData = {};\n    this.index = this.options.initialViewIndex;\n    this.isImg = false;\n    this.isShown = false;\n    this.length = 0;\n    this.moving = false;\n    this.played = false;\n    this.playing = false;\n    this.pointers = {};\n    this.ready = false;\n    this.rotating = false;\n    this.scaling = false;\n    this.showing = false;\n    this.timeout = false;\n    this.tooltipping = false;\n    this.viewed = false;\n    this.viewing = false;\n    this.wheeling = false;\n    this.zooming = false;\n    this.pointerMoved = false;\n    this.id = getUniqueID();\n    this.init();\n  }\n  return _createClass(Viewer, [{\n    key: \"init\",\n    value: function init() {\n      var _this = this;\n      var element = this.element,\n        options = this.options;\n      if (element[NAMESPACE]) {\n        return;\n      }\n      element[NAMESPACE] = this;\n\n      // The `focus` option requires the `keyboard` option set to `true`.\n      if (options.focus && !options.keyboard) {\n        options.focus = false;\n      }\n      var isImg = element.localName === 'img';\n      var images = [];\n      forEach(isImg ? [element] : element.querySelectorAll('img'), function (image) {\n        if (isFunction(options.filter)) {\n          if (options.filter.call(_this, image)) {\n            images.push(image);\n          }\n        } else if (_this.getImageURL(image)) {\n          images.push(image);\n        }\n      });\n      this.isImg = isImg;\n      this.length = images.length;\n      this.images = images;\n      this.initBody();\n\n      // Override `transition` option if it is not supported\n      if (isUndefined(document.createElement(NAMESPACE).style.transition)) {\n        options.transition = false;\n      }\n      if (options.inline) {\n        var count = 0;\n        var progress = function progress() {\n          count += 1;\n          if (count === _this.length) {\n            var timeout;\n            _this.initializing = false;\n            _this.delaying = {\n              abort: function abort() {\n                clearTimeout(timeout);\n              }\n            };\n\n            // build asynchronously to keep `this.viewer` is accessible in `ready` event handler.\n            timeout = setTimeout(function () {\n              _this.delaying = false;\n              _this.build();\n            }, 0);\n          }\n        };\n        this.initializing = {\n          abort: function abort() {\n            forEach(images, function (image) {\n              if (!image.complete) {\n                removeListener(image, EVENT_LOAD, progress);\n                removeListener(image, EVENT_ERROR, progress);\n              }\n            });\n          }\n        };\n        forEach(images, function (image) {\n          if (image.complete) {\n            progress();\n          } else {\n            var onLoad;\n            var onError;\n            addListener(image, EVENT_LOAD, onLoad = function onLoad() {\n              removeListener(image, EVENT_ERROR, onError);\n              progress();\n            }, {\n              once: true\n            });\n            addListener(image, EVENT_ERROR, onError = function onError() {\n              removeListener(image, EVENT_LOAD, onLoad);\n              progress();\n            }, {\n              once: true\n            });\n          }\n        });\n      } else {\n        addListener(element, EVENT_CLICK, this.onStart = function (_ref) {\n          var target = _ref.target;\n          if (target.localName === 'img' && (!isFunction(options.filter) || options.filter.call(_this, target))) {\n            _this.view(_this.images.indexOf(target));\n          }\n        });\n      }\n    }\n  }, {\n    key: \"build\",\n    value: function build() {\n      if (this.ready) {\n        return;\n      }\n      var element = this.element,\n        options = this.options;\n      var parent = element.parentNode;\n      var template = document.createElement('div');\n      template.innerHTML = TEMPLATE;\n      var viewer = template.querySelector(\".\".concat(NAMESPACE, \"-container\"));\n      var title = viewer.querySelector(\".\".concat(NAMESPACE, \"-title\"));\n      var toolbar = viewer.querySelector(\".\".concat(NAMESPACE, \"-toolbar\"));\n      var navbar = viewer.querySelector(\".\".concat(NAMESPACE, \"-navbar\"));\n      var button = viewer.querySelector(\".\".concat(NAMESPACE, \"-button\"));\n      var canvas = viewer.querySelector(\".\".concat(NAMESPACE, \"-canvas\"));\n      this.parent = parent;\n      this.viewer = viewer;\n      this.title = title;\n      this.toolbar = toolbar;\n      this.navbar = navbar;\n      this.button = button;\n      this.canvas = canvas;\n      this.footer = viewer.querySelector(\".\".concat(NAMESPACE, \"-footer\"));\n      this.tooltipBox = viewer.querySelector(\".\".concat(NAMESPACE, \"-tooltip\"));\n      this.player = viewer.querySelector(\".\".concat(NAMESPACE, \"-player\"));\n      this.list = viewer.querySelector(\".\".concat(NAMESPACE, \"-list\"));\n      viewer.id = \"\".concat(NAMESPACE).concat(this.id);\n      title.id = \"\".concat(NAMESPACE, \"Title\").concat(this.id);\n      addClass(title, !options.title ? CLASS_HIDE : getResponsiveClass(Array.isArray(options.title) ? options.title[0] : options.title));\n      addClass(navbar, !options.navbar ? CLASS_HIDE : getResponsiveClass(options.navbar));\n      toggleClass(button, CLASS_HIDE, !options.button);\n      if (options.keyboard) {\n        button.setAttribute('tabindex', 0);\n      }\n      if (options.backdrop) {\n        addClass(viewer, \"\".concat(NAMESPACE, \"-backdrop\"));\n        if (!options.inline && options.backdrop !== 'static') {\n          setData(canvas, DATA_ACTION, 'hide');\n        }\n      }\n      if (isString(options.className) && options.className) {\n        // In case there are multiple class names\n        options.className.split(REGEXP_SPACES).forEach(function (className) {\n          addClass(viewer, className);\n        });\n      }\n      if (options.toolbar) {\n        var list = document.createElement('ul');\n        var custom = isPlainObject(options.toolbar);\n        var zoomButtons = BUTTONS.slice(0, 3);\n        var rotateButtons = BUTTONS.slice(7, 9);\n        var scaleButtons = BUTTONS.slice(9);\n        if (!custom) {\n          addClass(toolbar, getResponsiveClass(options.toolbar));\n        }\n        forEach(custom ? options.toolbar : BUTTONS, function (value, index) {\n          var deep = custom && isPlainObject(value);\n          var name = custom ? hyphenate(index) : value;\n          var show = deep && !isUndefined(value.show) ? value.show : value;\n          if (!show || !options.zoomable && zoomButtons.indexOf(name) !== -1 || !options.rotatable && rotateButtons.indexOf(name) !== -1 || !options.scalable && scaleButtons.indexOf(name) !== -1) {\n            return;\n          }\n          var size = deep && !isUndefined(value.size) ? value.size : value;\n          var click = deep && !isUndefined(value.click) ? value.click : value;\n          var item = document.createElement('li');\n          if (options.keyboard) {\n            item.setAttribute('tabindex', 0);\n          }\n          item.setAttribute('role', 'button');\n          addClass(item, \"\".concat(NAMESPACE, \"-\").concat(name));\n          if (!isFunction(click)) {\n            setData(item, DATA_ACTION, name);\n          }\n          if (isNumber(show)) {\n            addClass(item, getResponsiveClass(show));\n          }\n          if (['small', 'large'].indexOf(size) !== -1) {\n            addClass(item, \"\".concat(NAMESPACE, \"-\").concat(size));\n          } else if (name === 'play') {\n            addClass(item, \"\".concat(NAMESPACE, \"-large\"));\n          }\n          if (isFunction(click)) {\n            addListener(item, EVENT_CLICK, click);\n          }\n          list.appendChild(item);\n        });\n        toolbar.appendChild(list);\n      } else {\n        addClass(toolbar, CLASS_HIDE);\n      }\n      if (!options.rotatable) {\n        var rotates = toolbar.querySelectorAll('li[class*=\"rotate\"]');\n        addClass(rotates, CLASS_INVISIBLE);\n        forEach(rotates, function (rotate) {\n          toolbar.appendChild(rotate);\n        });\n      }\n      if (options.inline) {\n        addClass(button, CLASS_FULLSCREEN);\n        setStyle(viewer, {\n          zIndex: options.zIndexInline\n        });\n        if (window.getComputedStyle(parent).position === 'static') {\n          setStyle(parent, {\n            position: 'relative'\n          });\n        }\n        parent.insertBefore(viewer, element.nextSibling);\n      } else {\n        addClass(button, CLASS_CLOSE);\n        addClass(viewer, CLASS_FIXED);\n        addClass(viewer, CLASS_FADE);\n        addClass(viewer, CLASS_HIDE);\n        setStyle(viewer, {\n          zIndex: options.zIndex\n        });\n        var container = options.container;\n        if (isString(container)) {\n          container = element.ownerDocument.querySelector(container);\n        }\n        if (!container) {\n          container = this.body;\n        }\n        container.appendChild(viewer);\n      }\n      if (options.inline) {\n        this.render();\n        this.bind();\n        this.isShown = true;\n      }\n      this.ready = true;\n      if (isFunction(options.ready)) {\n        addListener(element, EVENT_READY, options.ready, {\n          once: true\n        });\n      }\n      if (dispatchEvent(element, EVENT_READY) === false) {\n        this.ready = false;\n        return;\n      }\n      if (this.ready && options.inline) {\n        this.view(this.index);\n      }\n    }\n\n    /**\n     * Get the no conflict viewer class.\n     * @returns {Viewer} The viewer class.\n     */\n  }], [{\n    key: \"noConflict\",\n    value: function noConflict() {\n      window.Viewer = AnotherViewer;\n      return Viewer;\n    }\n\n    /**\n     * Change the default options.\n     * @param {Object} options - The new default options.\n     */\n  }, {\n    key: \"setDefaults\",\n    value: function setDefaults(options) {\n      assign(DEFAULTS, isPlainObject(options) && options);\n    }\n  }]);\n}();\nassign(Viewer.prototype, render, events, handlers, methods, others);\n\nexport { Viewer as default };\n"
  },
  {
    "path": "dist/viewer.js",
    "content": "/*!\n * Viewer.js v1.11.7\n * https://fengyuanchen.github.io/viewerjs\n *\n * Copyright 2015-present Chen Fengyuan\n * Released under the MIT license\n *\n * Date: 2024-11-24T04:32:19.116Z\n */\n\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :\n  typeof define === 'function' && define.amd ? define(factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Viewer = factory());\n})(this, (function () { 'use strict';\n\n  function _classCallCheck(a, n) {\n    if (!(a instanceof n)) throw new TypeError(\"Cannot call a class as a function\");\n  }\n  function _defineProperties(e, r) {\n    for (var t = 0; t < r.length; t++) {\n      var o = r[t];\n      o.enumerable = o.enumerable || !1, o.configurable = !0, \"value\" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o);\n    }\n  }\n  function _createClass(e, r, t) {\n    return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, \"prototype\", {\n      writable: !1\n    }), e;\n  }\n  function _defineProperty(e, r, t) {\n    return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {\n      value: t,\n      enumerable: !0,\n      configurable: !0,\n      writable: !0\n    }) : e[r] = t, e;\n  }\n  function ownKeys(e, r) {\n    var t = Object.keys(e);\n    if (Object.getOwnPropertySymbols) {\n      var o = Object.getOwnPropertySymbols(e);\n      r && (o = o.filter(function (r) {\n        return Object.getOwnPropertyDescriptor(e, r).enumerable;\n      })), t.push.apply(t, o);\n    }\n    return t;\n  }\n  function _objectSpread2(e) {\n    for (var r = 1; r < arguments.length; r++) {\n      var t = null != arguments[r] ? arguments[r] : {};\n      r % 2 ? ownKeys(Object(t), !0).forEach(function (r) {\n        _defineProperty(e, r, t[r]);\n      }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {\n        Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));\n      });\n    }\n    return e;\n  }\n  function _toPrimitive(t, r) {\n    if (\"object\" != typeof t || !t) return t;\n    var e = t[Symbol.toPrimitive];\n    if (void 0 !== e) {\n      var i = e.call(t, r || \"default\");\n      if (\"object\" != typeof i) return i;\n      throw new TypeError(\"@@toPrimitive must return a primitive value.\");\n    }\n    return (\"string\" === r ? String : Number)(t);\n  }\n  function _toPropertyKey(t) {\n    var i = _toPrimitive(t, \"string\");\n    return \"symbol\" == typeof i ? i : i + \"\";\n  }\n  function _typeof(o) {\n    \"@babel/helpers - typeof\";\n\n    return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function (o) {\n      return typeof o;\n    } : function (o) {\n      return o && \"function\" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? \"symbol\" : typeof o;\n    }, _typeof(o);\n  }\n\n  var DEFAULTS = {\n    /**\n     * Enable a modal backdrop, specify `static` for a backdrop\n     * which doesn't close the modal on click.\n     * @type {boolean}\n     */\n    backdrop: true,\n    /**\n     * Show the button on the top-right of the viewer.\n     * @type {boolean}\n     */\n    button: true,\n    /**\n     * Show the navbar.\n     * @type {boolean | number}\n     */\n    navbar: true,\n    /**\n     * Specify the visibility and the content of the title.\n     * @type {boolean | number | Function | Array}\n     */\n    title: true,\n    /**\n     * Show the toolbar.\n     * @type {boolean | number | Object}\n     */\n    toolbar: true,\n    /**\n     * Custom class name(s) to add to the viewer's root element.\n     * @type {string}\n     */\n    className: '',\n    /**\n     * Define where to put the viewer in modal mode.\n     * @type {string | Element}\n     */\n    container: 'body',\n    /**\n     * Filter the images for viewing. Return true if the image is viewable.\n     * @type {Function}\n     */\n    filter: null,\n    /**\n     * Enable to request fullscreen when play.\n     * {@link https://developer.mozilla.org/en-US/docs/Web/API/FullscreenOptions}\n     * @type {boolean|FullscreenOptions}\n     */\n    fullscreen: true,\n    /**\n     * Define the extra attributes to inherit from the original image.\n     * @type {Array}\n     */\n    inheritedAttributes: ['crossOrigin', 'decoding', 'isMap', 'loading', 'referrerPolicy', 'sizes', 'srcset', 'useMap'],\n    /**\n     * Define the initial coverage of the viewing image.\n     * @type {number}\n     */\n    initialCoverage: 0.9,\n    /**\n     * Define the initial index of the image for viewing.\n     * @type {number}\n     */\n    initialViewIndex: 0,\n    /**\n     * Enable inline mode.\n     * @type {boolean}\n     */\n    inline: false,\n    /**\n     * The amount of time to delay between automatically cycling an image when playing.\n     * @type {number}\n     */\n    interval: 5000,\n    /**\n     * Enable keyboard support.\n     * @type {boolean}\n     */\n    keyboard: true,\n    /**\n     * Focus the viewer when initialized.\n     * @type {boolean}\n     */\n    focus: true,\n    /**\n     * Indicate if show a loading spinner when load image or not.\n     * @type {boolean}\n     */\n    loading: true,\n    /**\n     * Indicate if enable loop viewing or not.\n     * @type {boolean}\n     */\n    loop: true,\n    /**\n     * Min width of the viewer in inline mode.\n     * @type {number}\n     */\n    minWidth: 200,\n    /**\n     * Min height of the viewer in inline mode.\n     * @type {number}\n     */\n    minHeight: 100,\n    /**\n     * Enable to move the image.\n     * @type {boolean}\n     */\n    movable: true,\n    /**\n     * Enable to rotate the image.\n     * @type {boolean}\n     */\n    rotatable: true,\n    /**\n     * Enable to scale the image.\n     * @type {boolean}\n     */\n    scalable: true,\n    /**\n     * Enable to zoom the image.\n     * @type {boolean}\n     */\n    zoomable: true,\n    /**\n     * Enable to zoom the current image by dragging on the touch screen.\n     * @type {boolean}\n     */\n    zoomOnTouch: true,\n    /**\n     * Enable to zoom the image by wheeling mouse.\n     * @type {boolean}\n     */\n    zoomOnWheel: true,\n    /**\n     * Enable to slide to the next or previous image by swiping on the touch screen.\n     * @type {boolean}\n     */\n    slideOnTouch: true,\n    /**\n     * Indicate if toggle the image size between its natural size\n     * and initial size when double click on the image or not.\n     * @type {boolean}\n     */\n    toggleOnDblclick: true,\n    /**\n     * Show the tooltip with image ratio (percentage) when zoom in or zoom out.\n     * @type {boolean}\n     */\n    tooltip: true,\n    /**\n     * Enable CSS3 Transition for some special elements.\n     * @type {boolean}\n     */\n    transition: true,\n    /**\n     * Define the CSS `z-index` value of viewer in modal mode.\n     * @type {number}\n     */\n    zIndex: 2015,\n    /**\n     * Define the CSS `z-index` value of viewer in inline mode.\n     * @type {number}\n     */\n    zIndexInline: 0,\n    /**\n     * Define the ratio when zoom the image by wheeling mouse.\n     * @type {number}\n     */\n    zoomRatio: 0.1,\n    /**\n     * Define the min ratio of the image when zoom out.\n     * @type {number}\n     */\n    minZoomRatio: 0.01,\n    /**\n     * Define the max ratio of the image when zoom in.\n     * @type {number}\n     */\n    maxZoomRatio: 100,\n    /**\n     * Define where to get the original image URL for viewing.\n     * @type {string | Function}\n     */\n    url: 'src',\n    /**\n     * Event shortcuts.\n     * @type {Function}\n     */\n    ready: null,\n    show: null,\n    shown: null,\n    hide: null,\n    hidden: null,\n    view: null,\n    viewed: null,\n    move: null,\n    moved: null,\n    rotate: null,\n    rotated: null,\n    scale: null,\n    scaled: null,\n    zoom: null,\n    zoomed: null,\n    play: null,\n    stop: null\n  };\n\n  var TEMPLATE = '<div class=\"viewer-container\" tabindex=\"-1\" touch-action=\"none\">' + '<div class=\"viewer-canvas\"></div>' + '<div class=\"viewer-footer\">' + '<div class=\"viewer-title\"></div>' + '<div class=\"viewer-toolbar\"></div>' + '<div class=\"viewer-navbar\">' + '<ul class=\"viewer-list\" role=\"navigation\"></ul>' + '</div>' + '</div>' + '<div class=\"viewer-tooltip\" role=\"alert\" aria-hidden=\"true\"></div>' + '<div class=\"viewer-button\" data-viewer-action=\"mix\" role=\"button\"></div>' + '<div class=\"viewer-player\"></div>' + '</div>';\n\n  var IS_BROWSER = typeof window !== 'undefined' && typeof window.document !== 'undefined';\n  var WINDOW = IS_BROWSER ? window : {};\n  var IS_TOUCH_DEVICE = IS_BROWSER && WINDOW.document.documentElement ? 'ontouchstart' in WINDOW.document.documentElement : false;\n  var HAS_POINTER_EVENT = IS_BROWSER ? 'PointerEvent' in WINDOW : false;\n  var NAMESPACE = 'viewer';\n\n  // Actions\n  var ACTION_MOVE = 'move';\n  var ACTION_SWITCH = 'switch';\n  var ACTION_ZOOM = 'zoom';\n\n  // Classes\n  var CLASS_ACTIVE = \"\".concat(NAMESPACE, \"-active\");\n  var CLASS_CLOSE = \"\".concat(NAMESPACE, \"-close\");\n  var CLASS_FADE = \"\".concat(NAMESPACE, \"-fade\");\n  var CLASS_FIXED = \"\".concat(NAMESPACE, \"-fixed\");\n  var CLASS_FULLSCREEN = \"\".concat(NAMESPACE, \"-fullscreen\");\n  var CLASS_FULLSCREEN_EXIT = \"\".concat(NAMESPACE, \"-fullscreen-exit\");\n  var CLASS_HIDE = \"\".concat(NAMESPACE, \"-hide\");\n  var CLASS_HIDE_MD_DOWN = \"\".concat(NAMESPACE, \"-hide-md-down\");\n  var CLASS_HIDE_SM_DOWN = \"\".concat(NAMESPACE, \"-hide-sm-down\");\n  var CLASS_HIDE_XS_DOWN = \"\".concat(NAMESPACE, \"-hide-xs-down\");\n  var CLASS_IN = \"\".concat(NAMESPACE, \"-in\");\n  var CLASS_INVISIBLE = \"\".concat(NAMESPACE, \"-invisible\");\n  var CLASS_LOADING = \"\".concat(NAMESPACE, \"-loading\");\n  var CLASS_MOVE = \"\".concat(NAMESPACE, \"-move\");\n  var CLASS_OPEN = \"\".concat(NAMESPACE, \"-open\");\n  var CLASS_SHOW = \"\".concat(NAMESPACE, \"-show\");\n  var CLASS_TRANSITION = \"\".concat(NAMESPACE, \"-transition\");\n\n  // Native events\n  var EVENT_CLICK = 'click';\n  var EVENT_DBLCLICK = 'dblclick';\n  var EVENT_DRAG_START = 'dragstart';\n  var EVENT_FOCUSIN = 'focusin';\n  var EVENT_KEY_DOWN = 'keydown';\n  var EVENT_LOAD = 'load';\n  var EVENT_ERROR = 'error';\n  var EVENT_TOUCH_END = IS_TOUCH_DEVICE ? 'touchend touchcancel' : 'mouseup';\n  var EVENT_TOUCH_MOVE = IS_TOUCH_DEVICE ? 'touchmove' : 'mousemove';\n  var EVENT_TOUCH_START = IS_TOUCH_DEVICE ? 'touchstart' : 'mousedown';\n  var EVENT_POINTER_DOWN = HAS_POINTER_EVENT ? 'pointerdown' : EVENT_TOUCH_START;\n  var EVENT_POINTER_MOVE = HAS_POINTER_EVENT ? 'pointermove' : EVENT_TOUCH_MOVE;\n  var EVENT_POINTER_UP = HAS_POINTER_EVENT ? 'pointerup pointercancel' : EVENT_TOUCH_END;\n  var EVENT_RESIZE = 'resize';\n  var EVENT_TRANSITION_END = 'transitionend';\n  var EVENT_WHEEL = 'wheel';\n\n  // Custom events\n  var EVENT_READY = 'ready';\n  var EVENT_SHOW = 'show';\n  var EVENT_SHOWN = 'shown';\n  var EVENT_HIDE = 'hide';\n  var EVENT_HIDDEN = 'hidden';\n  var EVENT_VIEW = 'view';\n  var EVENT_VIEWED = 'viewed';\n  var EVENT_MOVE = 'move';\n  var EVENT_MOVED = 'moved';\n  var EVENT_ROTATE = 'rotate';\n  var EVENT_ROTATED = 'rotated';\n  var EVENT_SCALE = 'scale';\n  var EVENT_SCALED = 'scaled';\n  var EVENT_ZOOM = 'zoom';\n  var EVENT_ZOOMED = 'zoomed';\n  var EVENT_PLAY = 'play';\n  var EVENT_STOP = 'stop';\n\n  // Data keys\n  var DATA_ACTION = \"\".concat(NAMESPACE, \"Action\");\n\n  // RegExps\n  var REGEXP_SPACES = /\\s\\s*/;\n\n  // Misc\n  var BUTTONS = ['zoom-in', 'zoom-out', 'one-to-one', 'reset', 'prev', 'play', 'next', 'rotate-left', 'rotate-right', 'flip-horizontal', 'flip-vertical'];\n\n  /**\n   * Check if the given value is a string.\n   * @param {*} value - The value to check.\n   * @returns {boolean} Returns `true` if the given value is a string, else `false`.\n   */\n  function isString(value) {\n    return typeof value === 'string';\n  }\n\n  /**\n   * Check if the given value is not a number.\n   */\n  var isNaN = Number.isNaN || WINDOW.isNaN;\n\n  /**\n   * Check if the given value is a number.\n   * @param {*} value - The value to check.\n   * @returns {boolean} Returns `true` if the given value is a number, else `false`.\n   */\n  function isNumber(value) {\n    return typeof value === 'number' && !isNaN(value);\n  }\n\n  /**\n   * Check if the given value is undefined.\n   * @param {*} value - The value to check.\n   * @returns {boolean} Returns `true` if the given value is undefined, else `false`.\n   */\n  function isUndefined(value) {\n    return typeof value === 'undefined';\n  }\n\n  /**\n   * Check if the given value is an object.\n   * @param {*} value - The value to check.\n   * @returns {boolean} Returns `true` if the given value is an object, else `false`.\n   */\n  function isObject(value) {\n    return _typeof(value) === 'object' && value !== null;\n  }\n  var hasOwnProperty = Object.prototype.hasOwnProperty;\n\n  /**\n   * Check if the given value is a plain object.\n   * @param {*} value - The value to check.\n   * @returns {boolean} Returns `true` if the given value is a plain object, else `false`.\n   */\n  function isPlainObject(value) {\n    if (!isObject(value)) {\n      return false;\n    }\n    try {\n      var _constructor = value.constructor;\n      var prototype = _constructor.prototype;\n      return _constructor && prototype && hasOwnProperty.call(prototype, 'isPrototypeOf');\n    } catch (error) {\n      return false;\n    }\n  }\n\n  /**\n   * Check if the given value is a function.\n   * @param {*} value - The value to check.\n   * @returns {boolean} Returns `true` if the given value is a function, else `false`.\n   */\n  function isFunction(value) {\n    return typeof value === 'function';\n  }\n\n  /**\n   * Iterate the given data.\n   * @param {*} data - The data to iterate.\n   * @param {Function} callback - The process function for each element.\n   * @returns {*} The original data.\n   */\n  function forEach(data, callback) {\n    if (data && isFunction(callback)) {\n      if (Array.isArray(data) || isNumber(data.length) /* array-like */) {\n        var length = data.length;\n        var i;\n        for (i = 0; i < length; i += 1) {\n          if (callback.call(data, data[i], i, data) === false) {\n            break;\n          }\n        }\n      } else if (isObject(data)) {\n        Object.keys(data).forEach(function (key) {\n          callback.call(data, data[key], key, data);\n        });\n      }\n    }\n    return data;\n  }\n\n  /**\n   * Extend the given object.\n   * @param {*} obj - The object to be extended.\n   * @param {*} args - The rest objects which will be merged to the first object.\n   * @returns {Object} The extended object.\n   */\n  var assign = Object.assign || function assign(obj) {\n    for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {\n      args[_key - 1] = arguments[_key];\n    }\n    if (isObject(obj) && args.length > 0) {\n      args.forEach(function (arg) {\n        if (isObject(arg)) {\n          Object.keys(arg).forEach(function (key) {\n            obj[key] = arg[key];\n          });\n        }\n      });\n    }\n    return obj;\n  };\n  var REGEXP_SUFFIX = /^(?:width|height|left|top|marginLeft|marginTop)$/;\n\n  /**\n   * Apply styles to the given element.\n   * @param {Element} element - The target element.\n   * @param {Object} styles - The styles for applying.\n   */\n  function setStyle(element, styles) {\n    var style = element.style;\n    forEach(styles, function (value, property) {\n      if (REGEXP_SUFFIX.test(property) && isNumber(value)) {\n        value += 'px';\n      }\n      style[property] = value;\n    });\n  }\n\n  /**\n   * Escape a string for using in HTML.\n   * @param {String} value - The string to escape.\n   * @returns {String} Returns the escaped string.\n   */\n  function escapeHTMLEntities(value) {\n    return isString(value) ? value.replace(/&(?!amp;|quot;|#39;|lt;|gt;)/g, '&amp;').replace(/\"/g, '&quot;').replace(/'/g, '&#39;').replace(/</g, '&lt;').replace(/>/g, '&gt;') : value;\n  }\n\n  /**\n   * Check if the given element has a special class.\n   * @param {Element} element - The element to check.\n   * @param {string} value - The class to search.\n   * @returns {boolean} Returns `true` if the special class was found.\n   */\n  function hasClass(element, value) {\n    if (!element || !value) {\n      return false;\n    }\n    return element.classList ? element.classList.contains(value) : element.className.indexOf(value) > -1;\n  }\n\n  /**\n   * Add classes to the given element.\n   * @param {Element} element - The target element.\n   * @param {string} value - The classes to be added.\n   */\n  function addClass(element, value) {\n    if (!element || !value) {\n      return;\n    }\n    if (isNumber(element.length)) {\n      forEach(element, function (elem) {\n        addClass(elem, value);\n      });\n      return;\n    }\n    if (element.classList) {\n      element.classList.add(value);\n      return;\n    }\n    var className = element.className.trim();\n    if (!className) {\n      element.className = value;\n    } else if (className.indexOf(value) < 0) {\n      element.className = \"\".concat(className, \" \").concat(value);\n    }\n  }\n\n  /**\n   * Remove classes from the given element.\n   * @param {Element} element - The target element.\n   * @param {string} value - The classes to be removed.\n   */\n  function removeClass(element, value) {\n    if (!element || !value) {\n      return;\n    }\n    if (isNumber(element.length)) {\n      forEach(element, function (elem) {\n        removeClass(elem, value);\n      });\n      return;\n    }\n    if (element.classList) {\n      element.classList.remove(value);\n      return;\n    }\n    if (element.className.indexOf(value) >= 0) {\n      element.className = element.className.replace(value, '');\n    }\n  }\n\n  /**\n   * Add or remove classes from the given element.\n   * @param {Element} element - The target element.\n   * @param {string} value - The classes to be toggled.\n   * @param {boolean} added - Add only.\n   */\n  function toggleClass(element, value, added) {\n    if (!value) {\n      return;\n    }\n    if (isNumber(element.length)) {\n      forEach(element, function (elem) {\n        toggleClass(elem, value, added);\n      });\n      return;\n    }\n\n    // IE10-11 doesn't support the second parameter of `classList.toggle`\n    if (added) {\n      addClass(element, value);\n    } else {\n      removeClass(element, value);\n    }\n  }\n  var REGEXP_HYPHENATE = /([a-z\\d])([A-Z])/g;\n\n  /**\n   * Transform the given string from camelCase to kebab-case\n   * @param {string} value - The value to transform.\n   * @returns {string} The transformed value.\n   */\n  function hyphenate(value) {\n    return value.replace(REGEXP_HYPHENATE, '$1-$2').toLowerCase();\n  }\n\n  /**\n   * Get data from the given element.\n   * @param {Element} element - The target element.\n   * @param {string} name - The data key to get.\n   * @returns {string} The data value.\n   */\n  function getData(element, name) {\n    if (isObject(element[name])) {\n      return element[name];\n    }\n    if (element.dataset) {\n      return element.dataset[name];\n    }\n    return element.getAttribute(\"data-\".concat(hyphenate(name)));\n  }\n\n  /**\n   * Set data to the given element.\n   * @param {Element} element - The target element.\n   * @param {string} name - The data key to set.\n   * @param {string} data - The data value.\n   */\n  function setData(element, name, data) {\n    if (isObject(data)) {\n      element[name] = data;\n    } else if (element.dataset) {\n      element.dataset[name] = data;\n    } else {\n      element.setAttribute(\"data-\".concat(hyphenate(name)), data);\n    }\n  }\n  var onceSupported = function () {\n    var supported = false;\n    if (IS_BROWSER) {\n      var once = false;\n      var listener = function listener() {};\n      var options = Object.defineProperty({}, 'once', {\n        get: function get() {\n          supported = true;\n          return once;\n        },\n        /**\n         * This setter can fix a `TypeError` in strict mode\n         * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Getter_only}\n         * @param {boolean} value - The value to set\n         */\n        set: function set(value) {\n          once = value;\n        }\n      });\n      WINDOW.addEventListener('test', listener, options);\n      WINDOW.removeEventListener('test', listener, options);\n    }\n    return supported;\n  }();\n\n  /**\n   * Remove event listener from the target element.\n   * @param {Element} element - The event target.\n   * @param {string} type - The event type(s).\n   * @param {Function} listener - The event listener.\n   * @param {Object} options - The event options.\n   */\n  function removeListener(element, type, listener) {\n    var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};\n    var handler = listener;\n    type.trim().split(REGEXP_SPACES).forEach(function (event) {\n      if (!onceSupported) {\n        var listeners = element.listeners;\n        if (listeners && listeners[event] && listeners[event][listener]) {\n          handler = listeners[event][listener];\n          delete listeners[event][listener];\n          if (Object.keys(listeners[event]).length === 0) {\n            delete listeners[event];\n          }\n          if (Object.keys(listeners).length === 0) {\n            delete element.listeners;\n          }\n        }\n      }\n      element.removeEventListener(event, handler, options);\n    });\n  }\n\n  /**\n   * Add event listener to the target element.\n   * @param {Element} element - The event target.\n   * @param {string} type - The event type(s).\n   * @param {Function} listener - The event listener.\n   * @param {Object} options - The event options.\n   */\n  function addListener(element, type, listener) {\n    var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};\n    var _handler = listener;\n    type.trim().split(REGEXP_SPACES).forEach(function (event) {\n      if (options.once && !onceSupported) {\n        var _element$listeners = element.listeners,\n          listeners = _element$listeners === void 0 ? {} : _element$listeners;\n        _handler = function handler() {\n          delete listeners[event][listener];\n          element.removeEventListener(event, _handler, options);\n          for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {\n            args[_key2] = arguments[_key2];\n          }\n          listener.apply(element, args);\n        };\n        if (!listeners[event]) {\n          listeners[event] = {};\n        }\n        if (listeners[event][listener]) {\n          element.removeEventListener(event, listeners[event][listener], options);\n        }\n        listeners[event][listener] = _handler;\n        element.listeners = listeners;\n      }\n      element.addEventListener(event, _handler, options);\n    });\n  }\n\n  /**\n   * Dispatch event on the target element.\n   * @param {Element} element - The event target.\n   * @param {string} type - The event type(s).\n   * @param {Object} data - The additional event data.\n   * @param {Object} options - The additional event options.\n   * @returns {boolean} Indicate if the event is default prevented or not.\n   */\n  function dispatchEvent(element, type, data, options) {\n    var event;\n\n    // Event and CustomEvent on IE9-11 are global objects, not constructors\n    if (isFunction(Event) && isFunction(CustomEvent)) {\n      event = new CustomEvent(type, _objectSpread2({\n        bubbles: true,\n        cancelable: true,\n        detail: data\n      }, options));\n    } else {\n      event = document.createEvent('CustomEvent');\n      event.initCustomEvent(type, true, true, data);\n    }\n    return element.dispatchEvent(event);\n  }\n\n  /**\n   * Get the offset base on the document.\n   * @param {Element} element - The target element.\n   * @returns {Object} The offset data.\n   */\n  function getOffset(element) {\n    var box = element.getBoundingClientRect();\n    return {\n      left: box.left + (window.pageXOffset - document.documentElement.clientLeft),\n      top: box.top + (window.pageYOffset - document.documentElement.clientTop)\n    };\n  }\n\n  /**\n   * Get transforms base on the given object.\n   * @param {Object} obj - The target object.\n   * @returns {string} A string contains transform values.\n   */\n  function getTransforms(_ref) {\n    var rotate = _ref.rotate,\n      scaleX = _ref.scaleX,\n      scaleY = _ref.scaleY,\n      translateX = _ref.translateX,\n      translateY = _ref.translateY;\n    var values = [];\n    if (isNumber(translateX) && translateX !== 0) {\n      values.push(\"translateX(\".concat(translateX, \"px)\"));\n    }\n    if (isNumber(translateY) && translateY !== 0) {\n      values.push(\"translateY(\".concat(translateY, \"px)\"));\n    }\n\n    // Rotate should come first before scale to match orientation transform\n    if (isNumber(rotate) && rotate !== 0) {\n      values.push(\"rotate(\".concat(rotate, \"deg)\"));\n    }\n    if (isNumber(scaleX) && scaleX !== 1) {\n      values.push(\"scaleX(\".concat(scaleX, \")\"));\n    }\n    if (isNumber(scaleY) && scaleY !== 1) {\n      values.push(\"scaleY(\".concat(scaleY, \")\"));\n    }\n    var transform = values.length ? values.join(' ') : 'none';\n    return {\n      WebkitTransform: transform,\n      msTransform: transform,\n      transform: transform\n    };\n  }\n\n  /**\n   * Get an image name from an image url.\n   * @param {string} url - The target url.\n   * @example\n   * // picture.jpg\n   * getImageNameFromURL('https://domain.com/path/to/picture.jpg?size=1280×960')\n   * @returns {string} A string contains the image name.\n   */\n  function getImageNameFromURL(url) {\n    return isString(url) ? decodeURIComponent(url.replace(/^.*\\//, '').replace(/[?&#].*$/, '')) : '';\n  }\n  var IS_SAFARI = WINDOW.navigator && /Version\\/\\d+(\\.\\d+)+?\\s+Safari/i.test(WINDOW.navigator.userAgent);\n\n  /**\n   * Get an image's natural sizes.\n   * @param {string} image - The target image.\n   * @param {Object} options - The viewer options.\n   * @param {Function} callback - The callback function.\n   * @returns {HTMLImageElement} The new image.\n   */\n  function getImageNaturalSizes(image, options, callback) {\n    var newImage = document.createElement('img');\n\n    // Modern browsers (except Safari)\n    if (image.naturalWidth && !IS_SAFARI) {\n      callback(image.naturalWidth, image.naturalHeight);\n      return newImage;\n    }\n    var body = document.body || document.documentElement;\n    newImage.onload = function () {\n      callback(newImage.width, newImage.height);\n      if (!IS_SAFARI) {\n        body.removeChild(newImage);\n      }\n    };\n    forEach(options.inheritedAttributes, function (name) {\n      var value = image.getAttribute(name);\n      if (value !== null) {\n        newImage.setAttribute(name, value);\n      }\n    });\n    newImage.src = image.src;\n\n    // iOS Safari will convert the image automatically\n    // with its orientation once append it into DOM\n    if (!IS_SAFARI) {\n      newImage.style.cssText = 'left:0;' + 'max-height:none!important;' + 'max-width:none!important;' + 'min-height:0!important;' + 'min-width:0!important;' + 'opacity:0;' + 'position:absolute;' + 'top:0;' + 'z-index:-1;';\n      body.appendChild(newImage);\n    }\n    return newImage;\n  }\n\n  /**\n   * Get the related class name of a responsive type number.\n   * @param {string} type - The responsive type.\n   * @returns {string} The related class name.\n   */\n  function getResponsiveClass(type) {\n    switch (type) {\n      case 2:\n        return CLASS_HIDE_XS_DOWN;\n      case 3:\n        return CLASS_HIDE_SM_DOWN;\n      case 4:\n        return CLASS_HIDE_MD_DOWN;\n      default:\n        return '';\n    }\n  }\n\n  /**\n   * Get the max ratio of a group of pointers.\n   * @param {string} pointers - The target pointers.\n   * @returns {number} The result ratio.\n   */\n  function getMaxZoomRatio(pointers) {\n    var pointers2 = _objectSpread2({}, pointers);\n    var ratios = [];\n    forEach(pointers, function (pointer, pointerId) {\n      delete pointers2[pointerId];\n      forEach(pointers2, function (pointer2) {\n        var x1 = Math.abs(pointer.startX - pointer2.startX);\n        var y1 = Math.abs(pointer.startY - pointer2.startY);\n        var x2 = Math.abs(pointer.endX - pointer2.endX);\n        var y2 = Math.abs(pointer.endY - pointer2.endY);\n        var z1 = Math.sqrt(x1 * x1 + y1 * y1);\n        var z2 = Math.sqrt(x2 * x2 + y2 * y2);\n        var ratio = (z2 - z1) / z1;\n        ratios.push(ratio);\n      });\n    });\n    ratios.sort(function (a, b) {\n      return Math.abs(a) < Math.abs(b);\n    });\n    return ratios[0];\n  }\n\n  /**\n   * Get a pointer from an event object.\n   * @param {Object} event - The target event object.\n   * @param {boolean} endOnly - Indicates if only returns the end point coordinate or not.\n   * @returns {Object} The result pointer contains start and/or end point coordinates.\n   */\n  function getPointer(_ref2, endOnly) {\n    var pageX = _ref2.pageX,\n      pageY = _ref2.pageY;\n    var end = {\n      endX: pageX,\n      endY: pageY\n    };\n    return endOnly ? end : _objectSpread2({\n      timeStamp: Date.now(),\n      startX: pageX,\n      startY: pageY\n    }, end);\n  }\n\n  /**\n   * Get the center point coordinate of a group of pointers.\n   * @param {Object} pointers - The target pointers.\n   * @returns {Object} The center point coordinate.\n   */\n  function getPointersCenter(pointers) {\n    var pageX = 0;\n    var pageY = 0;\n    var count = 0;\n    forEach(pointers, function (_ref3) {\n      var startX = _ref3.startX,\n        startY = _ref3.startY;\n      pageX += startX;\n      pageY += startY;\n      count += 1;\n    });\n    pageX /= count;\n    pageY /= count;\n    return {\n      pageX: pageX,\n      pageY: pageY\n    };\n  }\n\n  var render = {\n    render: function render() {\n      this.initContainer();\n      this.initViewer();\n      this.initList();\n      this.renderViewer();\n    },\n    initBody: function initBody() {\n      var ownerDocument = this.element.ownerDocument;\n      var body = ownerDocument.body || ownerDocument.documentElement;\n      this.body = body;\n      this.scrollbarWidth = window.innerWidth - ownerDocument.documentElement.clientWidth;\n      this.initialBodyPaddingRight = body.style.paddingRight;\n      this.initialBodyComputedPaddingRight = window.getComputedStyle(body).paddingRight;\n    },\n    initContainer: function initContainer() {\n      this.containerData = {\n        width: window.innerWidth,\n        height: window.innerHeight\n      };\n    },\n    initViewer: function initViewer() {\n      var options = this.options,\n        parent = this.parent;\n      var viewerData;\n      if (options.inline) {\n        viewerData = {\n          width: Math.max(parent.offsetWidth, options.minWidth),\n          height: Math.max(parent.offsetHeight, options.minHeight)\n        };\n        this.parentData = viewerData;\n      }\n      if (this.fulled || !viewerData) {\n        viewerData = this.containerData;\n      }\n      this.viewerData = assign({}, viewerData);\n    },\n    renderViewer: function renderViewer() {\n      if (this.options.inline && !this.fulled) {\n        setStyle(this.viewer, this.viewerData);\n      }\n    },\n    initList: function initList() {\n      var _this = this;\n      var element = this.element,\n        options = this.options,\n        list = this.list;\n      var items = [];\n\n      // initList may be called in this.update, so should keep idempotent\n      list.innerHTML = '';\n      forEach(this.images, function (image, index) {\n        var src = image.src;\n        var alt = image.alt || getImageNameFromURL(src);\n        var url = _this.getImageURL(image);\n        if (src || url) {\n          var item = document.createElement('li');\n          var img = document.createElement('img');\n          forEach(options.inheritedAttributes, function (name) {\n            var value = image.getAttribute(name);\n            if (value !== null) {\n              img.setAttribute(name, value);\n            }\n          });\n          if (options.navbar) {\n            img.src = src || url;\n          }\n          img.alt = alt;\n          img.setAttribute('data-original-url', url || src);\n          item.setAttribute('data-index', index);\n          item.setAttribute('data-viewer-action', 'view');\n          item.setAttribute('role', 'button');\n          if (options.keyboard) {\n            item.setAttribute('tabindex', 0);\n          }\n          item.appendChild(img);\n          list.appendChild(item);\n          items.push(item);\n        }\n      });\n      this.items = items;\n      forEach(items, function (item) {\n        var image = item.firstElementChild;\n        var onLoad;\n        var onError;\n        setData(image, 'filled', true);\n        if (options.loading) {\n          addClass(item, CLASS_LOADING);\n        }\n        addListener(image, EVENT_LOAD, onLoad = function onLoad(event) {\n          removeListener(image, EVENT_ERROR, onError);\n          if (options.loading) {\n            removeClass(item, CLASS_LOADING);\n          }\n          _this.loadImage(event);\n        }, {\n          once: true\n        });\n        addListener(image, EVENT_ERROR, onError = function onError() {\n          removeListener(image, EVENT_LOAD, onLoad);\n          if (options.loading) {\n            removeClass(item, CLASS_LOADING);\n          }\n        }, {\n          once: true\n        });\n      });\n      if (options.transition) {\n        addListener(element, EVENT_VIEWED, function () {\n          addClass(list, CLASS_TRANSITION);\n        }, {\n          once: true\n        });\n      }\n    },\n    renderList: function renderList() {\n      var index = this.index;\n      var item = this.items[index];\n      if (!item) {\n        return;\n      }\n      var next = item.nextElementSibling;\n      var gutter = parseInt(window.getComputedStyle(next || item).marginLeft, 10);\n      var offsetWidth = item.offsetWidth;\n      var outerWidth = offsetWidth + gutter;\n\n      // Place the active item in the center of the screen\n      setStyle(this.list, assign({\n        width: outerWidth * this.length - gutter\n      }, getTransforms({\n        translateX: (this.viewerData.width - offsetWidth) / 2 - outerWidth * index\n      })));\n    },\n    resetList: function resetList() {\n      var list = this.list;\n      list.innerHTML = '';\n      removeClass(list, CLASS_TRANSITION);\n      setStyle(list, getTransforms({\n        translateX: 0\n      }));\n    },\n    initImage: function initImage(done) {\n      var _this2 = this;\n      var options = this.options,\n        image = this.image,\n        viewerData = this.viewerData;\n      var footerHeight = this.footer.offsetHeight;\n      var viewerWidth = viewerData.width;\n      var viewerHeight = Math.max(viewerData.height - footerHeight, footerHeight);\n      var oldImageData = this.imageData || {};\n      var sizingImage;\n      this.imageInitializing = {\n        abort: function abort() {\n          sizingImage.onload = null;\n        }\n      };\n      sizingImage = getImageNaturalSizes(image, options, function (naturalWidth, naturalHeight) {\n        var aspectRatio = naturalWidth / naturalHeight;\n        var initialCoverage = Math.max(0, Math.min(1, options.initialCoverage));\n        var width = viewerWidth;\n        var height = viewerHeight;\n        _this2.imageInitializing = false;\n        if (viewerHeight * aspectRatio > viewerWidth) {\n          height = viewerWidth / aspectRatio;\n        } else {\n          width = viewerHeight * aspectRatio;\n        }\n        initialCoverage = isNumber(initialCoverage) ? initialCoverage : 0.9;\n        width = Math.min(width * initialCoverage, naturalWidth);\n        height = Math.min(height * initialCoverage, naturalHeight);\n        var left = (viewerWidth - width) / 2;\n        var top = (viewerHeight - height) / 2;\n        var imageData = {\n          left: left,\n          top: top,\n          x: left,\n          y: top,\n          width: width,\n          height: height,\n          oldRatio: 1,\n          ratio: width / naturalWidth,\n          aspectRatio: aspectRatio,\n          naturalWidth: naturalWidth,\n          naturalHeight: naturalHeight\n        };\n        var initialImageData = assign({}, imageData);\n        if (options.rotatable) {\n          imageData.rotate = oldImageData.rotate || 0;\n          initialImageData.rotate = 0;\n        }\n        if (options.scalable) {\n          imageData.scaleX = oldImageData.scaleX || 1;\n          imageData.scaleY = oldImageData.scaleY || 1;\n          initialImageData.scaleX = 1;\n          initialImageData.scaleY = 1;\n        }\n        _this2.imageData = imageData;\n        _this2.initialImageData = initialImageData;\n        if (done) {\n          done();\n        }\n      });\n    },\n    renderImage: function renderImage(done) {\n      var _this3 = this;\n      var image = this.image,\n        imageData = this.imageData;\n      setStyle(image, assign({\n        width: imageData.width,\n        height: imageData.height,\n        // XXX: Not to use translateX/Y to avoid image shaking when zooming\n        marginLeft: imageData.x,\n        marginTop: imageData.y\n      }, getTransforms(imageData)));\n      if (done) {\n        if ((this.viewing || this.moving || this.rotating || this.scaling || this.zooming) && this.options.transition && hasClass(image, CLASS_TRANSITION)) {\n          var onTransitionEnd = function onTransitionEnd() {\n            _this3.imageRendering = false;\n            done();\n          };\n          this.imageRendering = {\n            abort: function abort() {\n              removeListener(image, EVENT_TRANSITION_END, onTransitionEnd);\n            }\n          };\n          addListener(image, EVENT_TRANSITION_END, onTransitionEnd, {\n            once: true\n          });\n        } else {\n          done();\n        }\n      }\n    },\n    resetImage: function resetImage() {\n      var image = this.image;\n      if (image) {\n        if (this.viewing) {\n          this.viewing.abort();\n        }\n        image.parentNode.removeChild(image);\n        this.image = null;\n        this.title.innerHTML = '';\n      }\n    }\n  };\n\n  var events = {\n    bind: function bind() {\n      var options = this.options,\n        viewer = this.viewer,\n        canvas = this.canvas;\n      var document = this.element.ownerDocument;\n      addListener(viewer, EVENT_CLICK, this.onClick = this.click.bind(this));\n      addListener(viewer, EVENT_DRAG_START, this.onDragStart = this.dragstart.bind(this));\n      addListener(canvas, EVENT_POINTER_DOWN, this.onPointerDown = this.pointerdown.bind(this));\n      addListener(document, EVENT_POINTER_MOVE, this.onPointerMove = this.pointermove.bind(this));\n      addListener(document, EVENT_POINTER_UP, this.onPointerUp = this.pointerup.bind(this));\n      addListener(document, EVENT_KEY_DOWN, this.onKeyDown = this.keydown.bind(this));\n      addListener(window, EVENT_RESIZE, this.onResize = this.resize.bind(this));\n      if (options.zoomable && options.zoomOnWheel) {\n        addListener(viewer, EVENT_WHEEL, this.onWheel = this.wheel.bind(this), {\n          passive: false,\n          capture: true\n        });\n      }\n      if (options.toggleOnDblclick) {\n        addListener(canvas, EVENT_DBLCLICK, this.onDblclick = this.dblclick.bind(this));\n      }\n    },\n    unbind: function unbind() {\n      var options = this.options,\n        viewer = this.viewer,\n        canvas = this.canvas;\n      var document = this.element.ownerDocument;\n      removeListener(viewer, EVENT_CLICK, this.onClick);\n      removeListener(viewer, EVENT_DRAG_START, this.onDragStart);\n      removeListener(canvas, EVENT_POINTER_DOWN, this.onPointerDown);\n      removeListener(document, EVENT_POINTER_MOVE, this.onPointerMove);\n      removeListener(document, EVENT_POINTER_UP, this.onPointerUp);\n      removeListener(document, EVENT_KEY_DOWN, this.onKeyDown);\n      removeListener(window, EVENT_RESIZE, this.onResize);\n      if (options.zoomable && options.zoomOnWheel) {\n        removeListener(viewer, EVENT_WHEEL, this.onWheel, {\n          passive: false,\n          capture: true\n        });\n      }\n      if (options.toggleOnDblclick) {\n        removeListener(canvas, EVENT_DBLCLICK, this.onDblclick);\n      }\n    }\n  };\n\n  var handlers = {\n    click: function click(event) {\n      var options = this.options,\n        imageData = this.imageData;\n      var target = event.target;\n      var action = getData(target, DATA_ACTION);\n      if (!action && target.localName === 'img' && target.parentElement.localName === 'li') {\n        target = target.parentElement;\n        action = getData(target, DATA_ACTION);\n      }\n\n      // Cancel the emulated click when the native click event was triggered.\n      if (IS_TOUCH_DEVICE && event.isTrusted && target === this.canvas) {\n        clearTimeout(this.clickCanvasTimeout);\n      }\n      switch (action) {\n        case 'mix':\n          if (this.played) {\n            this.stop();\n          } else if (options.inline) {\n            if (this.fulled) {\n              this.exit();\n            } else {\n              this.full();\n            }\n          } else {\n            this.hide();\n          }\n          break;\n        case 'hide':\n          if (!this.pointerMoved) {\n            this.hide();\n          }\n          break;\n        case 'view':\n          this.view(getData(target, 'index'));\n          break;\n        case 'zoom-in':\n          this.zoom(0.1, true);\n          break;\n        case 'zoom-out':\n          this.zoom(-0.1, true);\n          break;\n        case 'one-to-one':\n          this.toggle();\n          break;\n        case 'reset':\n          this.reset();\n          break;\n        case 'prev':\n          this.prev(options.loop);\n          break;\n        case 'play':\n          this.play(options.fullscreen);\n          break;\n        case 'next':\n          this.next(options.loop);\n          break;\n        case 'rotate-left':\n          this.rotate(-90);\n          break;\n        case 'rotate-right':\n          this.rotate(90);\n          break;\n        case 'flip-horizontal':\n          this.scaleX(-imageData.scaleX || -1);\n          break;\n        case 'flip-vertical':\n          this.scaleY(-imageData.scaleY || -1);\n          break;\n        default:\n          if (this.played) {\n            this.stop();\n          }\n      }\n    },\n    dblclick: function dblclick(event) {\n      event.preventDefault();\n      if (this.viewed && event.target === this.image) {\n        // Cancel the emulated double click when the native dblclick event was triggered.\n        if (IS_TOUCH_DEVICE && event.isTrusted) {\n          clearTimeout(this.doubleClickImageTimeout);\n        }\n\n        // XXX: No pageX/Y properties in custom event, fallback to the original event.\n        this.toggle(event.isTrusted ? event : event.detail && event.detail.originalEvent);\n      }\n    },\n    load: function load() {\n      var _this = this;\n      if (this.timeout) {\n        clearTimeout(this.timeout);\n        this.timeout = false;\n      }\n      var element = this.element,\n        options = this.options,\n        image = this.image,\n        index = this.index,\n        viewerData = this.viewerData;\n      removeClass(image, CLASS_INVISIBLE);\n      if (options.loading) {\n        removeClass(this.canvas, CLASS_LOADING);\n      }\n      image.style.cssText = 'height:0;' + \"margin-left:\".concat(viewerData.width / 2, \"px;\") + \"margin-top:\".concat(viewerData.height / 2, \"px;\") + 'max-width:none!important;' + 'position:relative;' + 'width:0;';\n      this.initImage(function () {\n        toggleClass(image, CLASS_MOVE, options.movable);\n        toggleClass(image, CLASS_TRANSITION, options.transition);\n        _this.renderImage(function () {\n          _this.viewed = true;\n          _this.viewing = false;\n          if (isFunction(options.viewed)) {\n            addListener(element, EVENT_VIEWED, options.viewed, {\n              once: true\n            });\n          }\n          dispatchEvent(element, EVENT_VIEWED, {\n            originalImage: _this.images[index],\n            index: index,\n            image: image\n          }, {\n            cancelable: false\n          });\n        });\n      });\n    },\n    loadImage: function loadImage(event) {\n      var image = event.target;\n      var parent = image.parentNode;\n      var parentWidth = parent.offsetWidth || 30;\n      var parentHeight = parent.offsetHeight || 50;\n      var filled = !!getData(image, 'filled');\n      getImageNaturalSizes(image, this.options, function (naturalWidth, naturalHeight) {\n        var aspectRatio = naturalWidth / naturalHeight;\n        var width = parentWidth;\n        var height = parentHeight;\n        if (parentHeight * aspectRatio > parentWidth) {\n          if (filled) {\n            width = parentHeight * aspectRatio;\n          } else {\n            height = parentWidth / aspectRatio;\n          }\n        } else if (filled) {\n          height = parentWidth / aspectRatio;\n        } else {\n          width = parentHeight * aspectRatio;\n        }\n        setStyle(image, assign({\n          width: width,\n          height: height\n        }, getTransforms({\n          translateX: (parentWidth - width) / 2,\n          translateY: (parentHeight - height) / 2\n        })));\n      });\n    },\n    keydown: function keydown(event) {\n      var options = this.options;\n      if (!options.keyboard) {\n        return;\n      }\n      var keyCode = event.keyCode || event.which || event.charCode;\n      switch (keyCode) {\n        // Enter\n        case 13:\n          if (this.viewer.contains(event.target)) {\n            this.click(event);\n          }\n          break;\n      }\n      if (!this.fulled) {\n        return;\n      }\n      switch (keyCode) {\n        // Escape\n        case 27:\n          if (this.played) {\n            this.stop();\n          } else if (options.inline) {\n            if (this.fulled) {\n              this.exit();\n            }\n          } else {\n            this.hide();\n          }\n          break;\n\n        // Space\n        case 32:\n          if (this.played) {\n            this.stop();\n          }\n          break;\n\n        // ArrowLeft\n        case 37:\n          if (this.played && this.playing) {\n            this.playing.prev();\n          } else {\n            this.prev(options.loop);\n          }\n          break;\n\n        // ArrowUp\n        case 38:\n          // Prevent scroll on Firefox\n          event.preventDefault();\n\n          // Zoom in\n          this.zoom(options.zoomRatio, true);\n          break;\n\n        // ArrowRight\n        case 39:\n          if (this.played && this.playing) {\n            this.playing.next();\n          } else {\n            this.next(options.loop);\n          }\n          break;\n\n        // ArrowDown\n        case 40:\n          // Prevent scroll on Firefox\n          event.preventDefault();\n\n          // Zoom out\n          this.zoom(-options.zoomRatio, true);\n          break;\n\n        // Ctrl + 0\n        case 48:\n        // Fall through\n\n        // Ctrl + 1\n        // eslint-disable-next-line no-fallthrough\n        case 49:\n          if (event.ctrlKey) {\n            event.preventDefault();\n            this.toggle();\n          }\n          break;\n      }\n    },\n    dragstart: function dragstart(event) {\n      if (event.target.localName === 'img') {\n        event.preventDefault();\n      }\n    },\n    pointerdown: function pointerdown(event) {\n      var options = this.options,\n        pointers = this.pointers;\n      var buttons = event.buttons,\n        button = event.button;\n      this.pointerMoved = false;\n      if (!this.viewed || this.showing || this.viewing || this.hiding\n\n      // Handle mouse event and pointer event and ignore touch event\n      || (event.type === 'mousedown' || event.type === 'pointerdown' && event.pointerType === 'mouse') && (\n      // No primary button (Usually the left button)\n      isNumber(buttons) && buttons !== 1 || isNumber(button) && button !== 0\n\n      // Open context menu\n      || event.ctrlKey)) {\n        return;\n      }\n\n      // Prevent default behaviours as page zooming in touch devices.\n      event.preventDefault();\n      if (event.changedTouches) {\n        forEach(event.changedTouches, function (touch) {\n          pointers[touch.identifier] = getPointer(touch);\n        });\n      } else {\n        pointers[event.pointerId || 0] = getPointer(event);\n      }\n      var action = options.movable ? ACTION_MOVE : false;\n      if (options.zoomOnTouch && options.zoomable && Object.keys(pointers).length > 1) {\n        action = ACTION_ZOOM;\n      } else if (options.slideOnTouch && (event.pointerType === 'touch' || event.type === 'touchstart') && this.isSwitchable()) {\n        action = ACTION_SWITCH;\n      }\n      if (options.transition && (action === ACTION_MOVE || action === ACTION_ZOOM)) {\n        removeClass(this.image, CLASS_TRANSITION);\n      }\n      this.action = action;\n    },\n    pointermove: function pointermove(event) {\n      var pointers = this.pointers,\n        action = this.action;\n      if (!this.viewed || !action) {\n        return;\n      }\n      event.preventDefault();\n      if (event.changedTouches) {\n        forEach(event.changedTouches, function (touch) {\n          assign(pointers[touch.identifier] || {}, getPointer(touch, true));\n        });\n      } else {\n        assign(pointers[event.pointerId || 0] || {}, getPointer(event, true));\n      }\n      this.change(event);\n    },\n    pointerup: function pointerup(event) {\n      var _this2 = this;\n      var options = this.options,\n        action = this.action,\n        pointers = this.pointers;\n      var pointer;\n      if (event.changedTouches) {\n        forEach(event.changedTouches, function (touch) {\n          pointer = pointers[touch.identifier];\n          delete pointers[touch.identifier];\n        });\n      } else {\n        pointer = pointers[event.pointerId || 0];\n        delete pointers[event.pointerId || 0];\n      }\n      if (!action) {\n        return;\n      }\n      event.preventDefault();\n      if (options.transition && (action === ACTION_MOVE || action === ACTION_ZOOM)) {\n        addClass(this.image, CLASS_TRANSITION);\n      }\n      this.action = false;\n\n      // Emulate click and double click in touch devices to support backdrop and image zooming (#210).\n      if (IS_TOUCH_DEVICE && action !== ACTION_ZOOM && pointer && Date.now() - pointer.timeStamp < 500) {\n        clearTimeout(this.clickCanvasTimeout);\n        clearTimeout(this.doubleClickImageTimeout);\n        if (options.toggleOnDblclick && this.viewed && event.target === this.image) {\n          if (this.imageClicked) {\n            this.imageClicked = false;\n\n            // This timeout will be cleared later when a native dblclick event is triggering\n            this.doubleClickImageTimeout = setTimeout(function () {\n              dispatchEvent(_this2.image, EVENT_DBLCLICK, {\n                originalEvent: event\n              });\n            }, 50);\n          } else {\n            this.imageClicked = true;\n\n            // The default timing of a double click in Windows is 500 ms\n            this.doubleClickImageTimeout = setTimeout(function () {\n              _this2.imageClicked = false;\n            }, 500);\n          }\n        } else {\n          this.imageClicked = false;\n          if (options.backdrop && options.backdrop !== 'static' && event.target === this.canvas) {\n            // This timeout will be cleared later when a native click event is triggering\n            this.clickCanvasTimeout = setTimeout(function () {\n              dispatchEvent(_this2.canvas, EVENT_CLICK, {\n                originalEvent: event\n              });\n            }, 50);\n          }\n        }\n      }\n    },\n    resize: function resize() {\n      var _this3 = this;\n      if (!this.isShown || this.hiding) {\n        return;\n      }\n      if (this.fulled) {\n        this.close();\n        this.initBody();\n        this.open();\n      }\n      this.initContainer();\n      this.initViewer();\n      this.renderViewer();\n      this.renderList();\n      if (this.viewed) {\n        this.initImage(function () {\n          _this3.renderImage();\n        });\n      }\n      if (this.played) {\n        if (this.options.fullscreen && this.fulled && !(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement)) {\n          this.stop();\n          return;\n        }\n        forEach(this.player.getElementsByTagName('img'), function (image) {\n          addListener(image, EVENT_LOAD, _this3.loadImage.bind(_this3), {\n            once: true\n          });\n          dispatchEvent(image, EVENT_LOAD);\n        });\n      }\n    },\n    wheel: function wheel(event) {\n      var _this4 = this;\n      if (!this.viewed) {\n        return;\n      }\n      event.preventDefault();\n\n      // Limit wheel speed to prevent zoom too fast\n      if (this.wheeling) {\n        return;\n      }\n      this.wheeling = true;\n      setTimeout(function () {\n        _this4.wheeling = false;\n      }, 50);\n      var ratio = Number(this.options.zoomRatio) || 0.1;\n      var delta = 1;\n      if (event.deltaY) {\n        delta = event.deltaY > 0 ? 1 : -1;\n      } else if (event.wheelDelta) {\n        delta = -event.wheelDelta / 120;\n      } else if (event.detail) {\n        delta = event.detail > 0 ? 1 : -1;\n      }\n      this.zoom(-delta * ratio, true, null, event);\n    }\n  };\n\n  var methods = {\n    /** Show the viewer (only available in modal mode)\n     * @param {boolean} [immediate=false] - Indicates if show the viewer immediately or not.\n     * @returns {Viewer} this\n     */\n    show: function show() {\n      var immediate = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;\n      var element = this.element,\n        options = this.options;\n      if (options.inline || this.showing || this.isShown || this.showing) {\n        return this;\n      }\n      if (!this.ready) {\n        this.build();\n        if (this.ready) {\n          this.show(immediate);\n        }\n        return this;\n      }\n      if (isFunction(options.show)) {\n        addListener(element, EVENT_SHOW, options.show, {\n          once: true\n        });\n      }\n      if (dispatchEvent(element, EVENT_SHOW) === false || !this.ready) {\n        return this;\n      }\n      if (this.hiding) {\n        this.transitioning.abort();\n      }\n      this.showing = true;\n      this.open();\n      var viewer = this.viewer;\n      removeClass(viewer, CLASS_HIDE);\n      viewer.setAttribute('role', 'dialog');\n      viewer.setAttribute('aria-labelledby', this.title.id);\n      viewer.setAttribute('aria-modal', true);\n      viewer.removeAttribute('aria-hidden');\n      if (options.transition && !immediate) {\n        var shown = this.shown.bind(this);\n        this.transitioning = {\n          abort: function abort() {\n            removeListener(viewer, EVENT_TRANSITION_END, shown);\n            removeClass(viewer, CLASS_IN);\n          }\n        };\n        addClass(viewer, CLASS_TRANSITION);\n\n        // Force reflow to enable CSS3 transition\n        viewer.initialOffsetWidth = viewer.offsetWidth;\n        addListener(viewer, EVENT_TRANSITION_END, shown, {\n          once: true\n        });\n        addClass(viewer, CLASS_IN);\n      } else {\n        addClass(viewer, CLASS_IN);\n        this.shown();\n      }\n      return this;\n    },\n    /**\n     * Hide the viewer (only available in modal mode)\n     * @param {boolean} [immediate=false] - Indicates if hide the viewer immediately or not.\n     * @returns {Viewer} this\n     */\n    hide: function hide() {\n      var _this = this;\n      var immediate = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;\n      var element = this.element,\n        options = this.options;\n      if (options.inline || this.hiding || !(this.isShown || this.showing)) {\n        return this;\n      }\n      if (isFunction(options.hide)) {\n        addListener(element, EVENT_HIDE, options.hide, {\n          once: true\n        });\n      }\n      if (dispatchEvent(element, EVENT_HIDE) === false) {\n        return this;\n      }\n      if (this.showing) {\n        this.transitioning.abort();\n      }\n      this.hiding = true;\n      if (this.played) {\n        this.stop();\n      } else if (this.viewing) {\n        this.viewing.abort();\n      }\n      var viewer = this.viewer,\n        image = this.image;\n      var hideImmediately = function hideImmediately() {\n        removeClass(viewer, CLASS_IN);\n        _this.hidden();\n      };\n      if (options.transition && !immediate) {\n        var _onViewerTransitionEnd = function onViewerTransitionEnd(event) {\n          // Ignore all propagating `transitionend` events (#275).\n          if (event && event.target === viewer) {\n            removeListener(viewer, EVENT_TRANSITION_END, _onViewerTransitionEnd);\n            _this.hidden();\n          }\n        };\n        var onImageTransitionEnd = function onImageTransitionEnd() {\n          // In case of show the viewer by `viewer.show(true)` previously (#407).\n          if (hasClass(viewer, CLASS_TRANSITION)) {\n            addListener(viewer, EVENT_TRANSITION_END, _onViewerTransitionEnd);\n            removeClass(viewer, CLASS_IN);\n          } else {\n            hideImmediately();\n          }\n        };\n        this.transitioning = {\n          abort: function abort() {\n            if (_this.viewed && hasClass(image, CLASS_TRANSITION)) {\n              removeListener(image, EVENT_TRANSITION_END, onImageTransitionEnd);\n            } else if (hasClass(viewer, CLASS_TRANSITION)) {\n              removeListener(viewer, EVENT_TRANSITION_END, _onViewerTransitionEnd);\n            }\n          }\n        };\n\n        // In case of hiding the viewer when holding on the image (#255),\n        // note that the `CLASS_TRANSITION` class will be removed on pointer down.\n        if (this.viewed && hasClass(image, CLASS_TRANSITION)) {\n          addListener(image, EVENT_TRANSITION_END, onImageTransitionEnd, {\n            once: true\n          });\n          this.zoomTo(0, false, null, null, true);\n        } else {\n          onImageTransitionEnd();\n        }\n      } else {\n        hideImmediately();\n      }\n      return this;\n    },\n    /**\n     * View one of the images with image's index\n     * @param {number} index - The index of the image to view.\n     * @returns {Viewer} this\n     */\n    view: function view() {\n      var _this2 = this;\n      var index = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.options.initialViewIndex;\n      index = Number(index) || 0;\n      if (this.hiding || this.played || index < 0 || index >= this.length || this.viewed && index === this.index) {\n        return this;\n      }\n      if (!this.isShown) {\n        this.index = index;\n        return this.show();\n      }\n      if (this.viewing) {\n        this.viewing.abort();\n      }\n      var element = this.element,\n        options = this.options,\n        title = this.title,\n        canvas = this.canvas;\n      var item = this.items[index];\n      var img = item.querySelector('img');\n      var url = getData(img, 'originalUrl');\n      var alt = img.getAttribute('alt');\n      var image = document.createElement('img');\n      forEach(options.inheritedAttributes, function (name) {\n        var value = img.getAttribute(name);\n        if (value !== null) {\n          image.setAttribute(name, value);\n        }\n      });\n      image.src = url;\n      image.alt = alt;\n      if (isFunction(options.view)) {\n        addListener(element, EVENT_VIEW, options.view, {\n          once: true\n        });\n      }\n      if (dispatchEvent(element, EVENT_VIEW, {\n        originalImage: this.images[index],\n        index: index,\n        image: image\n      }) === false || !this.isShown || this.hiding || this.played) {\n        return this;\n      }\n      var activeItem = this.items[this.index];\n      if (activeItem) {\n        removeClass(activeItem, CLASS_ACTIVE);\n        activeItem.removeAttribute('aria-selected');\n      }\n      addClass(item, CLASS_ACTIVE);\n      item.setAttribute('aria-selected', true);\n      if (options.focus) {\n        item.focus();\n      }\n      this.image = image;\n      this.viewed = false;\n      this.index = index;\n      this.imageData = {};\n      addClass(image, CLASS_INVISIBLE);\n      if (options.loading) {\n        addClass(canvas, CLASS_LOADING);\n      }\n      canvas.innerHTML = '';\n      canvas.appendChild(image);\n\n      // Center current item\n      this.renderList();\n\n      // Clear title\n      title.innerHTML = '';\n\n      // Generate title after viewed\n      var onViewed = function onViewed() {\n        var imageData = _this2.imageData;\n        var render = Array.isArray(options.title) ? options.title[1] : options.title;\n        title.innerHTML = escapeHTMLEntities(isFunction(render) ? render.call(_this2, image, imageData) : \"\".concat(alt, \" (\").concat(imageData.naturalWidth, \" \\xD7 \").concat(imageData.naturalHeight, \")\"));\n      };\n      var onLoad;\n      var onError;\n      addListener(element, EVENT_VIEWED, onViewed, {\n        once: true\n      });\n      this.viewing = {\n        abort: function abort() {\n          removeListener(element, EVENT_VIEWED, onViewed);\n          if (image.complete) {\n            if (_this2.imageRendering) {\n              _this2.imageRendering.abort();\n            } else if (_this2.imageInitializing) {\n              _this2.imageInitializing.abort();\n            }\n          } else {\n            // Cancel download to save bandwidth.\n            image.src = '';\n            removeListener(image, EVENT_LOAD, onLoad);\n            if (_this2.timeout) {\n              clearTimeout(_this2.timeout);\n            }\n          }\n        }\n      };\n      if (image.complete) {\n        this.load();\n      } else {\n        addListener(image, EVENT_LOAD, onLoad = function onLoad() {\n          removeListener(image, EVENT_ERROR, onError);\n          _this2.load();\n        }, {\n          once: true\n        });\n        addListener(image, EVENT_ERROR, onError = function onError() {\n          removeListener(image, EVENT_LOAD, onLoad);\n          if (_this2.timeout) {\n            clearTimeout(_this2.timeout);\n            _this2.timeout = false;\n          }\n          removeClass(image, CLASS_INVISIBLE);\n          if (options.loading) {\n            removeClass(_this2.canvas, CLASS_LOADING);\n          }\n        }, {\n          once: true\n        });\n        if (this.timeout) {\n          clearTimeout(this.timeout);\n        }\n\n        // Make the image visible if it fails to load within 1s\n        this.timeout = setTimeout(function () {\n          removeClass(image, CLASS_INVISIBLE);\n          _this2.timeout = false;\n        }, 1000);\n      }\n      return this;\n    },\n    /**\n     * View the previous image\n     * @param {boolean} [loop=false] - Indicate if view the last one\n     * when it is the first one at present.\n     * @returns {Viewer} this\n     */\n    prev: function prev() {\n      var loop = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;\n      var index = this.index - 1;\n      if (index < 0) {\n        index = loop ? this.length - 1 : 0;\n      }\n      this.view(index);\n      return this;\n    },\n    /**\n     * View the next image\n     * @param {boolean} [loop=false] - Indicate if view the first one\n     * when it is the last one at present.\n     * @returns {Viewer} this\n     */\n    next: function next() {\n      var loop = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;\n      var maxIndex = this.length - 1;\n      var index = this.index + 1;\n      if (index > maxIndex) {\n        index = loop ? 0 : maxIndex;\n      }\n      this.view(index);\n      return this;\n    },\n    /**\n     * Move the image with relative offsets.\n     * @param {number} x - The moving distance in the horizontal direction.\n     * @param {number} [y=x] The moving distance in the vertical direction.\n     * @returns {Viewer} this\n     */\n    move: function move(x) {\n      var y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : x;\n      var imageData = this.imageData;\n      this.moveTo(isUndefined(x) ? x : imageData.x + Number(x), isUndefined(y) ? y : imageData.y + Number(y));\n      return this;\n    },\n    /**\n     * Move the image to an absolute point.\n     * @param {number} x - The new position in the horizontal direction.\n     * @param {number} [y=x] - The new position in the vertical direction.\n     * @param {Event} [_originalEvent=null] - The original event if any.\n     * @returns {Viewer} this\n     */\n    moveTo: function moveTo(x) {\n      var _this3 = this;\n      var y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : x;\n      var _originalEvent = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;\n      var element = this.element,\n        options = this.options,\n        imageData = this.imageData;\n      x = Number(x);\n      y = Number(y);\n      if (this.viewed && !this.played && options.movable) {\n        var oldX = imageData.x;\n        var oldY = imageData.y;\n        var changed = false;\n        if (isNumber(x)) {\n          changed = true;\n        } else {\n          x = oldX;\n        }\n        if (isNumber(y)) {\n          changed = true;\n        } else {\n          y = oldY;\n        }\n        if (changed) {\n          if (isFunction(options.move)) {\n            addListener(element, EVENT_MOVE, options.move, {\n              once: true\n            });\n          }\n          if (dispatchEvent(element, EVENT_MOVE, {\n            x: x,\n            y: y,\n            oldX: oldX,\n            oldY: oldY,\n            originalEvent: _originalEvent\n          }) === false) {\n            return this;\n          }\n          imageData.x = x;\n          imageData.y = y;\n          imageData.left = x;\n          imageData.top = y;\n          this.moving = true;\n          this.renderImage(function () {\n            _this3.moving = false;\n            if (isFunction(options.moved)) {\n              addListener(element, EVENT_MOVED, options.moved, {\n                once: true\n              });\n            }\n            dispatchEvent(element, EVENT_MOVED, {\n              x: x,\n              y: y,\n              oldX: oldX,\n              oldY: oldY,\n              originalEvent: _originalEvent\n            }, {\n              cancelable: false\n            });\n          });\n        }\n      }\n      return this;\n    },\n    /**\n     * Rotate the image with a relative degree.\n     * @param {number} degree - The rotate degree.\n     * @returns {Viewer} this\n     */\n    rotate: function rotate(degree) {\n      this.rotateTo((this.imageData.rotate || 0) + Number(degree));\n      return this;\n    },\n    /**\n     * Rotate the image to an absolute degree.\n     * @param {number} degree - The rotate degree.\n     * @returns {Viewer} this\n     */\n    rotateTo: function rotateTo(degree) {\n      var _this4 = this;\n      var element = this.element,\n        options = this.options,\n        imageData = this.imageData;\n      degree = Number(degree);\n      if (isNumber(degree) && this.viewed && !this.played && options.rotatable) {\n        var oldDegree = imageData.rotate;\n        if (isFunction(options.rotate)) {\n          addListener(element, EVENT_ROTATE, options.rotate, {\n            once: true\n          });\n        }\n        if (dispatchEvent(element, EVENT_ROTATE, {\n          degree: degree,\n          oldDegree: oldDegree\n        }) === false) {\n          return this;\n        }\n        imageData.rotate = degree;\n        this.rotating = true;\n        this.renderImage(function () {\n          _this4.rotating = false;\n          if (isFunction(options.rotated)) {\n            addListener(element, EVENT_ROTATED, options.rotated, {\n              once: true\n            });\n          }\n          dispatchEvent(element, EVENT_ROTATED, {\n            degree: degree,\n            oldDegree: oldDegree\n          }, {\n            cancelable: false\n          });\n        });\n      }\n      return this;\n    },\n    /**\n     * Scale the image on the x-axis.\n     * @param {number} scaleX - The scale ratio on the x-axis.\n     * @returns {Viewer} this\n     */\n    scaleX: function scaleX(_scaleX) {\n      this.scale(_scaleX, this.imageData.scaleY);\n      return this;\n    },\n    /**\n     * Scale the image on the y-axis.\n     * @param {number} scaleY - The scale ratio on the y-axis.\n     * @returns {Viewer} this\n     */\n    scaleY: function scaleY(_scaleY) {\n      this.scale(this.imageData.scaleX, _scaleY);\n      return this;\n    },\n    /**\n     * Scale the image.\n     * @param {number} scaleX - The scale ratio on the x-axis.\n     * @param {number} [scaleY=scaleX] - The scale ratio on the y-axis.\n     * @returns {Viewer} this\n     */\n    scale: function scale(scaleX) {\n      var _this5 = this;\n      var scaleY = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : scaleX;\n      var element = this.element,\n        options = this.options,\n        imageData = this.imageData;\n      scaleX = Number(scaleX);\n      scaleY = Number(scaleY);\n      if (this.viewed && !this.played && options.scalable) {\n        var oldScaleX = imageData.scaleX;\n        var oldScaleY = imageData.scaleY;\n        var changed = false;\n        if (isNumber(scaleX)) {\n          changed = true;\n        } else {\n          scaleX = oldScaleX;\n        }\n        if (isNumber(scaleY)) {\n          changed = true;\n        } else {\n          scaleY = oldScaleY;\n        }\n        if (changed) {\n          if (isFunction(options.scale)) {\n            addListener(element, EVENT_SCALE, options.scale, {\n              once: true\n            });\n          }\n          if (dispatchEvent(element, EVENT_SCALE, {\n            scaleX: scaleX,\n            scaleY: scaleY,\n            oldScaleX: oldScaleX,\n            oldScaleY: oldScaleY\n          }) === false) {\n            return this;\n          }\n          imageData.scaleX = scaleX;\n          imageData.scaleY = scaleY;\n          this.scaling = true;\n          this.renderImage(function () {\n            _this5.scaling = false;\n            if (isFunction(options.scaled)) {\n              addListener(element, EVENT_SCALED, options.scaled, {\n                once: true\n              });\n            }\n            dispatchEvent(element, EVENT_SCALED, {\n              scaleX: scaleX,\n              scaleY: scaleY,\n              oldScaleX: oldScaleX,\n              oldScaleY: oldScaleY\n            }, {\n              cancelable: false\n            });\n          });\n        }\n      }\n      return this;\n    },\n    /**\n     * Zoom the image with a relative ratio.\n     * @param {number} ratio - The target ratio.\n     * @param {boolean} [showTooltip=false] - Indicates whether to show the tooltip.\n     * @param {Object} [pivot] - The pivot point coordinate for zooming.\n     * @param {Event} [_originalEvent=null] - The original event if any.\n     * @returns {Viewer} this\n     */\n    zoom: function zoom(ratio) {\n      var showTooltip = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;\n      var pivot = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;\n      var _originalEvent = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;\n      var imageData = this.imageData;\n      ratio = Number(ratio);\n      if (ratio < 0) {\n        ratio = 1 / (1 - ratio);\n      } else {\n        ratio = 1 + ratio;\n      }\n      this.zoomTo(imageData.width * ratio / imageData.naturalWidth, showTooltip, pivot, _originalEvent);\n      return this;\n    },\n    /**\n     * Zoom the image to an absolute ratio.\n     * @param {number} ratio - The target ratio.\n     * @param {boolean} [showTooltip] - Indicates whether to show the tooltip.\n     * @param {Object} [pivot] - The pivot point coordinate for zooming.\n     * @param {Event} [_originalEvent=null] - The original event if any.\n     * @param {Event} [_zoomable=false] - Indicates if the current zoom is available or not.\n     * @returns {Viewer} this\n     */\n    zoomTo: function zoomTo(ratio) {\n      var _this6 = this;\n      var showTooltip = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;\n      var pivot = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;\n      var _originalEvent = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;\n      var _zoomable = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;\n      var element = this.element,\n        options = this.options,\n        pointers = this.pointers,\n        imageData = this.imageData;\n      var x = imageData.x,\n        y = imageData.y,\n        width = imageData.width,\n        height = imageData.height,\n        naturalWidth = imageData.naturalWidth,\n        naturalHeight = imageData.naturalHeight;\n      ratio = Math.max(0, ratio);\n      if (isNumber(ratio) && this.viewed && !this.played && (_zoomable || options.zoomable)) {\n        if (!_zoomable) {\n          var minZoomRatio = Math.max(0.01, options.minZoomRatio);\n          var maxZoomRatio = Math.min(100, options.maxZoomRatio);\n          ratio = Math.min(Math.max(ratio, minZoomRatio), maxZoomRatio);\n        }\n        if (_originalEvent) {\n          switch (_originalEvent.type) {\n            case 'wheel':\n              if (options.zoomRatio >= 0.055 && ratio > 0.95 && ratio < 1.05) {\n                ratio = 1;\n              }\n              break;\n            case 'pointermove':\n            case 'touchmove':\n            case 'mousemove':\n              if (ratio > 0.99 && ratio < 1.01) {\n                ratio = 1;\n              }\n              break;\n          }\n        }\n        var newWidth = naturalWidth * ratio;\n        var newHeight = naturalHeight * ratio;\n        var offsetWidth = newWidth - width;\n        var offsetHeight = newHeight - height;\n        var oldRatio = imageData.ratio;\n        if (isFunction(options.zoom)) {\n          addListener(element, EVENT_ZOOM, options.zoom, {\n            once: true\n          });\n        }\n        if (dispatchEvent(element, EVENT_ZOOM, {\n          ratio: ratio,\n          oldRatio: oldRatio,\n          originalEvent: _originalEvent\n        }) === false) {\n          return this;\n        }\n        this.zooming = true;\n        if (_originalEvent) {\n          var offset = getOffset(this.viewer);\n          var center = pointers && Object.keys(pointers).length > 0 ? getPointersCenter(pointers) : {\n            pageX: _originalEvent.pageX,\n            pageY: _originalEvent.pageY\n          };\n\n          // Zoom from the triggering point of the event\n          imageData.x -= offsetWidth * ((center.pageX - offset.left - x) / width);\n          imageData.y -= offsetHeight * ((center.pageY - offset.top - y) / height);\n        } else if (isPlainObject(pivot) && isNumber(pivot.x) && isNumber(pivot.y)) {\n          imageData.x -= offsetWidth * ((pivot.x - x) / width);\n          imageData.y -= offsetHeight * ((pivot.y - y) / height);\n        } else {\n          // Zoom from the center of the image\n          imageData.x -= offsetWidth / 2;\n          imageData.y -= offsetHeight / 2;\n        }\n        imageData.left = imageData.x;\n        imageData.top = imageData.y;\n        imageData.width = newWidth;\n        imageData.height = newHeight;\n        imageData.oldRatio = oldRatio;\n        imageData.ratio = ratio;\n        this.renderImage(function () {\n          _this6.zooming = false;\n          if (isFunction(options.zoomed)) {\n            addListener(element, EVENT_ZOOMED, options.zoomed, {\n              once: true\n            });\n          }\n          dispatchEvent(element, EVENT_ZOOMED, {\n            ratio: ratio,\n            oldRatio: oldRatio,\n            originalEvent: _originalEvent\n          }, {\n            cancelable: false\n          });\n        });\n        if (showTooltip) {\n          this.tooltip();\n        }\n      }\n      return this;\n    },\n    /**\n     * Play the images\n     * @param {boolean|FullscreenOptions} [fullscreen=false] - Indicate if request fullscreen or not.\n     * @returns {Viewer} this\n     */\n    play: function play() {\n      var _this7 = this;\n      var fullscreen = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;\n      if (!this.isShown || this.played) {\n        return this;\n      }\n      var element = this.element,\n        options = this.options;\n      if (isFunction(options.play)) {\n        addListener(element, EVENT_PLAY, options.play, {\n          once: true\n        });\n      }\n      if (dispatchEvent(element, EVENT_PLAY) === false) {\n        return this;\n      }\n      var player = this.player;\n      var onLoad = this.loadImage.bind(this);\n      var list = [];\n      var total = 0;\n      var index = 0;\n      this.played = true;\n      this.onLoadWhenPlay = onLoad;\n      if (fullscreen) {\n        this.requestFullscreen(fullscreen);\n      }\n      addClass(player, CLASS_SHOW);\n      forEach(this.items, function (item, i) {\n        var img = item.querySelector('img');\n        var image = document.createElement('img');\n        image.src = getData(img, 'originalUrl');\n        image.alt = img.getAttribute('alt');\n        image.referrerPolicy = img.referrerPolicy;\n        total += 1;\n        addClass(image, CLASS_FADE);\n        toggleClass(image, CLASS_TRANSITION, options.transition);\n        if (hasClass(item, CLASS_ACTIVE)) {\n          addClass(image, CLASS_IN);\n          index = i;\n        }\n        list.push(image);\n        addListener(image, EVENT_LOAD, onLoad, {\n          once: true\n        });\n        player.appendChild(image);\n      });\n      if (isNumber(options.interval) && options.interval > 0) {\n        var _prev = function prev() {\n          clearTimeout(_this7.playing.timeout);\n          removeClass(list[index], CLASS_IN);\n          index -= 1;\n          index = index >= 0 ? index : total - 1;\n          addClass(list[index], CLASS_IN);\n          _this7.playing.timeout = setTimeout(_prev, options.interval);\n        };\n        var _next = function next() {\n          clearTimeout(_this7.playing.timeout);\n          removeClass(list[index], CLASS_IN);\n          index += 1;\n          index = index < total ? index : 0;\n          addClass(list[index], CLASS_IN);\n          _this7.playing.timeout = setTimeout(_next, options.interval);\n        };\n        if (total > 1) {\n          this.playing = {\n            prev: _prev,\n            next: _next,\n            timeout: setTimeout(_next, options.interval)\n          };\n        }\n      }\n      return this;\n    },\n    // Stop play\n    stop: function stop() {\n      var _this8 = this;\n      if (!this.played) {\n        return this;\n      }\n      var element = this.element,\n        options = this.options;\n      if (isFunction(options.stop)) {\n        addListener(element, EVENT_STOP, options.stop, {\n          once: true\n        });\n      }\n      if (dispatchEvent(element, EVENT_STOP) === false) {\n        return this;\n      }\n      var player = this.player;\n      clearTimeout(this.playing.timeout);\n      this.playing = false;\n      this.played = false;\n      forEach(player.getElementsByTagName('img'), function (image) {\n        removeListener(image, EVENT_LOAD, _this8.onLoadWhenPlay);\n      });\n      removeClass(player, CLASS_SHOW);\n      player.innerHTML = '';\n      this.exitFullscreen();\n      return this;\n    },\n    // Enter modal mode (only available in inline mode)\n    full: function full() {\n      var _this9 = this;\n      var options = this.options,\n        viewer = this.viewer,\n        image = this.image,\n        list = this.list;\n      if (!this.isShown || this.played || this.fulled || !options.inline) {\n        return this;\n      }\n      this.fulled = true;\n      this.open();\n      addClass(this.button, CLASS_FULLSCREEN_EXIT);\n      if (options.transition) {\n        removeClass(list, CLASS_TRANSITION);\n        if (this.viewed) {\n          removeClass(image, CLASS_TRANSITION);\n        }\n      }\n      addClass(viewer, CLASS_FIXED);\n      viewer.setAttribute('role', 'dialog');\n      viewer.setAttribute('aria-labelledby', this.title.id);\n      viewer.setAttribute('aria-modal', true);\n      viewer.removeAttribute('style');\n      setStyle(viewer, {\n        zIndex: options.zIndex\n      });\n      if (options.focus) {\n        this.enforceFocus();\n      }\n      this.initContainer();\n      this.viewerData = assign({}, this.containerData);\n      this.renderList();\n      if (this.viewed) {\n        this.initImage(function () {\n          _this9.renderImage(function () {\n            if (options.transition) {\n              setTimeout(function () {\n                addClass(image, CLASS_TRANSITION);\n                addClass(list, CLASS_TRANSITION);\n              }, 0);\n            }\n          });\n        });\n      }\n      return this;\n    },\n    // Exit modal mode (only available in inline mode)\n    exit: function exit() {\n      var _this10 = this;\n      var options = this.options,\n        viewer = this.viewer,\n        image = this.image,\n        list = this.list;\n      if (!this.isShown || this.played || !this.fulled || !options.inline) {\n        return this;\n      }\n      this.fulled = false;\n      this.close();\n      removeClass(this.button, CLASS_FULLSCREEN_EXIT);\n      if (options.transition) {\n        removeClass(list, CLASS_TRANSITION);\n        if (this.viewed) {\n          removeClass(image, CLASS_TRANSITION);\n        }\n      }\n      if (options.focus) {\n        this.clearEnforceFocus();\n      }\n      viewer.removeAttribute('role');\n      viewer.removeAttribute('aria-labelledby');\n      viewer.removeAttribute('aria-modal');\n      removeClass(viewer, CLASS_FIXED);\n      setStyle(viewer, {\n        zIndex: options.zIndexInline\n      });\n      this.viewerData = assign({}, this.parentData);\n      this.renderViewer();\n      this.renderList();\n      if (this.viewed) {\n        this.initImage(function () {\n          _this10.renderImage(function () {\n            if (options.transition) {\n              setTimeout(function () {\n                addClass(image, CLASS_TRANSITION);\n                addClass(list, CLASS_TRANSITION);\n              }, 0);\n            }\n          });\n        });\n      }\n      return this;\n    },\n    // Show the current ratio of the image with percentage\n    tooltip: function tooltip() {\n      var _this11 = this;\n      var options = this.options,\n        tooltipBox = this.tooltipBox,\n        imageData = this.imageData;\n      if (!this.viewed || this.played || !options.tooltip) {\n        return this;\n      }\n      tooltipBox.textContent = \"\".concat(Math.round(imageData.ratio * 100), \"%\");\n      if (!this.tooltipping) {\n        if (options.transition) {\n          if (this.fading) {\n            dispatchEvent(tooltipBox, EVENT_TRANSITION_END);\n          }\n          addClass(tooltipBox, CLASS_SHOW);\n          addClass(tooltipBox, CLASS_FADE);\n          addClass(tooltipBox, CLASS_TRANSITION);\n          tooltipBox.removeAttribute('aria-hidden');\n\n          // Force reflow to enable CSS3 transition\n          tooltipBox.initialOffsetWidth = tooltipBox.offsetWidth;\n          addClass(tooltipBox, CLASS_IN);\n        } else {\n          addClass(tooltipBox, CLASS_SHOW);\n          tooltipBox.removeAttribute('aria-hidden');\n        }\n      } else {\n        clearTimeout(this.tooltipping);\n      }\n      this.tooltipping = setTimeout(function () {\n        if (options.transition) {\n          addListener(tooltipBox, EVENT_TRANSITION_END, function () {\n            removeClass(tooltipBox, CLASS_SHOW);\n            removeClass(tooltipBox, CLASS_FADE);\n            removeClass(tooltipBox, CLASS_TRANSITION);\n            tooltipBox.setAttribute('aria-hidden', true);\n            _this11.fading = false;\n          }, {\n            once: true\n          });\n          removeClass(tooltipBox, CLASS_IN);\n          _this11.fading = true;\n        } else {\n          removeClass(tooltipBox, CLASS_SHOW);\n          tooltipBox.setAttribute('aria-hidden', true);\n        }\n        _this11.tooltipping = false;\n      }, 1000);\n      return this;\n    },\n    /**\n     * Toggle the image size between its current size and natural size\n     * @param {Event} [_originalEvent=null] - The original event if any.\n     * @returns {Viewer} this\n     */\n    toggle: function toggle() {\n      var _originalEvent = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;\n      if (this.imageData.ratio === 1) {\n        this.zoomTo(this.imageData.oldRatio, true, null, _originalEvent);\n      } else {\n        this.zoomTo(1, true, null, _originalEvent);\n      }\n      return this;\n    },\n    // Reset the image to its initial state\n    reset: function reset() {\n      if (this.viewed && !this.played) {\n        this.imageData = assign({}, this.initialImageData);\n        this.renderImage();\n      }\n      return this;\n    },\n    // Update viewer when images changed\n    update: function update() {\n      var _this12 = this;\n      var element = this.element,\n        options = this.options,\n        isImg = this.isImg;\n\n      // Destroy viewer if the target image was deleted\n      if (isImg && !element.parentNode) {\n        return this.destroy();\n      }\n      var images = [];\n      forEach(isImg ? [element] : element.querySelectorAll('img'), function (image) {\n        if (isFunction(options.filter)) {\n          if (options.filter.call(_this12, image)) {\n            images.push(image);\n          }\n        } else if (_this12.getImageURL(image)) {\n          images.push(image);\n        }\n      });\n      if (!images.length) {\n        return this;\n      }\n      this.images = images;\n      this.length = images.length;\n      if (this.ready) {\n        var changedIndexes = [];\n        forEach(this.items, function (item, i) {\n          var img = item.querySelector('img');\n          var image = images[i];\n          if (image && img) {\n            if (image.src !== img.src\n\n            // Title changed (#408)\n            || image.alt !== img.alt) {\n              changedIndexes.push(i);\n            }\n          } else {\n            changedIndexes.push(i);\n          }\n        });\n        setStyle(this.list, {\n          width: 'auto'\n        });\n        this.initList();\n        if (this.isShown) {\n          if (this.length) {\n            if (this.viewed) {\n              var changedIndex = changedIndexes.indexOf(this.index);\n              if (changedIndex >= 0) {\n                this.viewed = false;\n                this.view(Math.max(Math.min(this.index - changedIndex, this.length - 1), 0));\n              } else {\n                var activeItem = this.items[this.index];\n\n                // Reactivate the current viewing item after reset the list.\n                addClass(activeItem, CLASS_ACTIVE);\n                activeItem.setAttribute('aria-selected', true);\n              }\n            }\n          } else {\n            this.image = null;\n            this.viewed = false;\n            this.index = 0;\n            this.imageData = {};\n            this.canvas.innerHTML = '';\n            this.title.innerHTML = '';\n          }\n        }\n      } else {\n        this.build();\n      }\n      return this;\n    },\n    // Destroy the viewer\n    destroy: function destroy() {\n      var element = this.element,\n        options = this.options;\n      if (!element[NAMESPACE]) {\n        return this;\n      }\n      this.destroyed = true;\n      if (this.ready) {\n        if (this.played) {\n          this.stop();\n        }\n        if (options.inline) {\n          if (this.fulled) {\n            this.exit();\n          }\n          this.unbind();\n        } else if (this.isShown) {\n          if (this.viewing) {\n            if (this.imageRendering) {\n              this.imageRendering.abort();\n            } else if (this.imageInitializing) {\n              this.imageInitializing.abort();\n            }\n          }\n          if (this.hiding) {\n            this.transitioning.abort();\n          }\n          this.hidden();\n        } else if (this.showing) {\n          this.transitioning.abort();\n          this.hidden();\n        }\n        this.ready = false;\n        this.viewer.parentNode.removeChild(this.viewer);\n      } else if (options.inline) {\n        if (this.delaying) {\n          this.delaying.abort();\n        } else if (this.initializing) {\n          this.initializing.abort();\n        }\n      }\n      if (!options.inline) {\n        removeListener(element, EVENT_CLICK, this.onStart);\n      }\n      element[NAMESPACE] = undefined;\n      return this;\n    }\n  };\n\n  var others = {\n    getImageURL: function getImageURL(image) {\n      var url = this.options.url;\n      if (isString(url)) {\n        url = image.getAttribute(url);\n      } else if (isFunction(url)) {\n        url = url.call(this, image);\n      } else {\n        url = '';\n      }\n      return url;\n    },\n    enforceFocus: function enforceFocus() {\n      var _this = this;\n      this.clearEnforceFocus();\n      addListener(document, EVENT_FOCUSIN, this.onFocusin = function (event) {\n        var viewer = _this.viewer;\n        var target = event.target;\n        if (target === document || target === viewer || viewer.contains(target)) {\n          return;\n        }\n        while (target) {\n          // Avoid conflicts with other modals (#474, #540)\n          if (target.getAttribute('tabindex') !== null || target.getAttribute('aria-modal') === 'true') {\n            return;\n          }\n          target = target.parentElement;\n        }\n        viewer.focus();\n      });\n    },\n    clearEnforceFocus: function clearEnforceFocus() {\n      if (this.onFocusin) {\n        removeListener(document, EVENT_FOCUSIN, this.onFocusin);\n        this.onFocusin = null;\n      }\n    },\n    open: function open() {\n      var body = this.body;\n      addClass(body, CLASS_OPEN);\n      if (this.scrollbarWidth > 0) {\n        body.style.paddingRight = \"\".concat(this.scrollbarWidth + (parseFloat(this.initialBodyComputedPaddingRight) || 0), \"px\");\n      }\n    },\n    close: function close() {\n      var body = this.body;\n      removeClass(body, CLASS_OPEN);\n      if (this.scrollbarWidth > 0) {\n        body.style.paddingRight = this.initialBodyPaddingRight;\n      }\n    },\n    shown: function shown() {\n      var element = this.element,\n        options = this.options,\n        viewer = this.viewer;\n      this.fulled = true;\n      this.isShown = true;\n      this.render();\n      this.bind();\n      this.showing = false;\n      if (options.focus) {\n        viewer.focus();\n        this.enforceFocus();\n      }\n      if (isFunction(options.shown)) {\n        addListener(element, EVENT_SHOWN, options.shown, {\n          once: true\n        });\n      }\n      if (dispatchEvent(element, EVENT_SHOWN) === false) {\n        return;\n      }\n      if (this.ready && this.isShown && !this.hiding) {\n        this.view(this.index);\n      }\n    },\n    hidden: function hidden() {\n      var element = this.element,\n        options = this.options,\n        viewer = this.viewer;\n      if (options.fucus) {\n        this.clearEnforceFocus();\n      }\n      this.close();\n      this.unbind();\n      addClass(viewer, CLASS_HIDE);\n      viewer.removeAttribute('role');\n      viewer.removeAttribute('aria-labelledby');\n      viewer.removeAttribute('aria-modal');\n      viewer.setAttribute('aria-hidden', true);\n      this.resetList();\n      this.resetImage();\n      this.fulled = false;\n      this.viewed = false;\n      this.isShown = false;\n      this.hiding = false;\n      if (!this.destroyed) {\n        if (isFunction(options.hidden)) {\n          addListener(element, EVENT_HIDDEN, options.hidden, {\n            once: true\n          });\n        }\n        dispatchEvent(element, EVENT_HIDDEN, null, {\n          cancelable: false\n        });\n      }\n    },\n    requestFullscreen: function requestFullscreen(options) {\n      var document = this.element.ownerDocument;\n      if (this.fulled && !(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement)) {\n        var documentElement = document.documentElement;\n\n        // Element.requestFullscreen()\n        if (documentElement.requestFullscreen) {\n          // Avoid TypeError when convert `options` to dictionary\n          if (isPlainObject(options)) {\n            documentElement.requestFullscreen(options);\n          } else {\n            documentElement.requestFullscreen();\n          }\n        } else if (documentElement.webkitRequestFullscreen) {\n          documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);\n        } else if (documentElement.mozRequestFullScreen) {\n          documentElement.mozRequestFullScreen();\n        } else if (documentElement.msRequestFullscreen) {\n          documentElement.msRequestFullscreen();\n        }\n      }\n    },\n    exitFullscreen: function exitFullscreen() {\n      var document = this.element.ownerDocument;\n      if (this.fulled && (document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement)) {\n        // Document.exitFullscreen()\n        if (document.exitFullscreen) {\n          document.exitFullscreen();\n        } else if (document.webkitExitFullscreen) {\n          document.webkitExitFullscreen();\n        } else if (document.mozCancelFullScreen) {\n          document.mozCancelFullScreen();\n        } else if (document.msExitFullscreen) {\n          document.msExitFullscreen();\n        }\n      }\n    },\n    change: function change(event) {\n      var options = this.options,\n        pointers = this.pointers;\n      var pointer = pointers[Object.keys(pointers)[0]];\n\n      // In the case of the `pointers` object is empty (#421)\n      if (!pointer) {\n        return;\n      }\n      var offsetX = pointer.endX - pointer.startX;\n      var offsetY = pointer.endY - pointer.startY;\n      switch (this.action) {\n        // Move the current image\n        case ACTION_MOVE:\n          if (offsetX !== 0 || offsetY !== 0) {\n            this.pointerMoved = true;\n            this.move(offsetX, offsetY, event);\n          }\n          break;\n\n        // Zoom the current image\n        case ACTION_ZOOM:\n          this.zoom(getMaxZoomRatio(pointers), false, null, event);\n          break;\n        case ACTION_SWITCH:\n          {\n            this.action = 'switched';\n            var absoluteOffsetX = Math.abs(offsetX);\n            if (absoluteOffsetX > 1 && absoluteOffsetX > Math.abs(offsetY)) {\n              // Empty `pointers` as `touchend` event will not be fired after swiped in iOS browsers.\n              this.pointers = {};\n              if (offsetX > 1) {\n                this.prev(options.loop);\n              } else if (offsetX < -1) {\n                this.next(options.loop);\n              }\n            }\n            break;\n          }\n      }\n\n      // Override\n      forEach(pointers, function (p) {\n        p.startX = p.endX;\n        p.startY = p.endY;\n      });\n    },\n    isSwitchable: function isSwitchable() {\n      var imageData = this.imageData,\n        viewerData = this.viewerData;\n      return this.length > 1 && imageData.x >= 0 && imageData.y >= 0 && imageData.width <= viewerData.width && imageData.height <= viewerData.height;\n    }\n  };\n\n  var AnotherViewer = WINDOW.Viewer;\n  var getUniqueID = function (id) {\n    return function () {\n      id += 1;\n      return id;\n    };\n  }(-1);\n  var Viewer = /*#__PURE__*/function () {\n    /**\n     * Create a new Viewer.\n     * @param {Element} element - The target element for viewing.\n     * @param {Object} [options={}] - The configuration options.\n     */\n    function Viewer(element) {\n      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n      _classCallCheck(this, Viewer);\n      if (!element || element.nodeType !== 1) {\n        throw new Error('The first argument is required and must be an element.');\n      }\n      this.element = element;\n      this.options = assign({}, DEFAULTS, isPlainObject(options) && options);\n      this.action = false;\n      this.fading = false;\n      this.fulled = false;\n      this.hiding = false;\n      this.imageClicked = false;\n      this.imageData = {};\n      this.index = this.options.initialViewIndex;\n      this.isImg = false;\n      this.isShown = false;\n      this.length = 0;\n      this.moving = false;\n      this.played = false;\n      this.playing = false;\n      this.pointers = {};\n      this.ready = false;\n      this.rotating = false;\n      this.scaling = false;\n      this.showing = false;\n      this.timeout = false;\n      this.tooltipping = false;\n      this.viewed = false;\n      this.viewing = false;\n      this.wheeling = false;\n      this.zooming = false;\n      this.pointerMoved = false;\n      this.id = getUniqueID();\n      this.init();\n    }\n    return _createClass(Viewer, [{\n      key: \"init\",\n      value: function init() {\n        var _this = this;\n        var element = this.element,\n          options = this.options;\n        if (element[NAMESPACE]) {\n          return;\n        }\n        element[NAMESPACE] = this;\n\n        // The `focus` option requires the `keyboard` option set to `true`.\n        if (options.focus && !options.keyboard) {\n          options.focus = false;\n        }\n        var isImg = element.localName === 'img';\n        var images = [];\n        forEach(isImg ? [element] : element.querySelectorAll('img'), function (image) {\n          if (isFunction(options.filter)) {\n            if (options.filter.call(_this, image)) {\n              images.push(image);\n            }\n          } else if (_this.getImageURL(image)) {\n            images.push(image);\n          }\n        });\n        this.isImg = isImg;\n        this.length = images.length;\n        this.images = images;\n        this.initBody();\n\n        // Override `transition` option if it is not supported\n        if (isUndefined(document.createElement(NAMESPACE).style.transition)) {\n          options.transition = false;\n        }\n        if (options.inline) {\n          var count = 0;\n          var progress = function progress() {\n            count += 1;\n            if (count === _this.length) {\n              var timeout;\n              _this.initializing = false;\n              _this.delaying = {\n                abort: function abort() {\n                  clearTimeout(timeout);\n                }\n              };\n\n              // build asynchronously to keep `this.viewer` is accessible in `ready` event handler.\n              timeout = setTimeout(function () {\n                _this.delaying = false;\n                _this.build();\n              }, 0);\n            }\n          };\n          this.initializing = {\n            abort: function abort() {\n              forEach(images, function (image) {\n                if (!image.complete) {\n                  removeListener(image, EVENT_LOAD, progress);\n                  removeListener(image, EVENT_ERROR, progress);\n                }\n              });\n            }\n          };\n          forEach(images, function (image) {\n            if (image.complete) {\n              progress();\n            } else {\n              var onLoad;\n              var onError;\n              addListener(image, EVENT_LOAD, onLoad = function onLoad() {\n                removeListener(image, EVENT_ERROR, onError);\n                progress();\n              }, {\n                once: true\n              });\n              addListener(image, EVENT_ERROR, onError = function onError() {\n                removeListener(image, EVENT_LOAD, onLoad);\n                progress();\n              }, {\n                once: true\n              });\n            }\n          });\n        } else {\n          addListener(element, EVENT_CLICK, this.onStart = function (_ref) {\n            var target = _ref.target;\n            if (target.localName === 'img' && (!isFunction(options.filter) || options.filter.call(_this, target))) {\n              _this.view(_this.images.indexOf(target));\n            }\n          });\n        }\n      }\n    }, {\n      key: \"build\",\n      value: function build() {\n        if (this.ready) {\n          return;\n        }\n        var element = this.element,\n          options = this.options;\n        var parent = element.parentNode;\n        var template = document.createElement('div');\n        template.innerHTML = TEMPLATE;\n        var viewer = template.querySelector(\".\".concat(NAMESPACE, \"-container\"));\n        var title = viewer.querySelector(\".\".concat(NAMESPACE, \"-title\"));\n        var toolbar = viewer.querySelector(\".\".concat(NAMESPACE, \"-toolbar\"));\n        var navbar = viewer.querySelector(\".\".concat(NAMESPACE, \"-navbar\"));\n        var button = viewer.querySelector(\".\".concat(NAMESPACE, \"-button\"));\n        var canvas = viewer.querySelector(\".\".concat(NAMESPACE, \"-canvas\"));\n        this.parent = parent;\n        this.viewer = viewer;\n        this.title = title;\n        this.toolbar = toolbar;\n        this.navbar = navbar;\n        this.button = button;\n        this.canvas = canvas;\n        this.footer = viewer.querySelector(\".\".concat(NAMESPACE, \"-footer\"));\n        this.tooltipBox = viewer.querySelector(\".\".concat(NAMESPACE, \"-tooltip\"));\n        this.player = viewer.querySelector(\".\".concat(NAMESPACE, \"-player\"));\n        this.list = viewer.querySelector(\".\".concat(NAMESPACE, \"-list\"));\n        viewer.id = \"\".concat(NAMESPACE).concat(this.id);\n        title.id = \"\".concat(NAMESPACE, \"Title\").concat(this.id);\n        addClass(title, !options.title ? CLASS_HIDE : getResponsiveClass(Array.isArray(options.title) ? options.title[0] : options.title));\n        addClass(navbar, !options.navbar ? CLASS_HIDE : getResponsiveClass(options.navbar));\n        toggleClass(button, CLASS_HIDE, !options.button);\n        if (options.keyboard) {\n          button.setAttribute('tabindex', 0);\n        }\n        if (options.backdrop) {\n          addClass(viewer, \"\".concat(NAMESPACE, \"-backdrop\"));\n          if (!options.inline && options.backdrop !== 'static') {\n            setData(canvas, DATA_ACTION, 'hide');\n          }\n        }\n        if (isString(options.className) && options.className) {\n          // In case there are multiple class names\n          options.className.split(REGEXP_SPACES).forEach(function (className) {\n            addClass(viewer, className);\n          });\n        }\n        if (options.toolbar) {\n          var list = document.createElement('ul');\n          var custom = isPlainObject(options.toolbar);\n          var zoomButtons = BUTTONS.slice(0, 3);\n          var rotateButtons = BUTTONS.slice(7, 9);\n          var scaleButtons = BUTTONS.slice(9);\n          if (!custom) {\n            addClass(toolbar, getResponsiveClass(options.toolbar));\n          }\n          forEach(custom ? options.toolbar : BUTTONS, function (value, index) {\n            var deep = custom && isPlainObject(value);\n            var name = custom ? hyphenate(index) : value;\n            var show = deep && !isUndefined(value.show) ? value.show : value;\n            if (!show || !options.zoomable && zoomButtons.indexOf(name) !== -1 || !options.rotatable && rotateButtons.indexOf(name) !== -1 || !options.scalable && scaleButtons.indexOf(name) !== -1) {\n              return;\n            }\n            var size = deep && !isUndefined(value.size) ? value.size : value;\n            var click = deep && !isUndefined(value.click) ? value.click : value;\n            var item = document.createElement('li');\n            if (options.keyboard) {\n              item.setAttribute('tabindex', 0);\n            }\n            item.setAttribute('role', 'button');\n            addClass(item, \"\".concat(NAMESPACE, \"-\").concat(name));\n            if (!isFunction(click)) {\n              setData(item, DATA_ACTION, name);\n            }\n            if (isNumber(show)) {\n              addClass(item, getResponsiveClass(show));\n            }\n            if (['small', 'large'].indexOf(size) !== -1) {\n              addClass(item, \"\".concat(NAMESPACE, \"-\").concat(size));\n            } else if (name === 'play') {\n              addClass(item, \"\".concat(NAMESPACE, \"-large\"));\n            }\n            if (isFunction(click)) {\n              addListener(item, EVENT_CLICK, click);\n            }\n            list.appendChild(item);\n          });\n          toolbar.appendChild(list);\n        } else {\n          addClass(toolbar, CLASS_HIDE);\n        }\n        if (!options.rotatable) {\n          var rotates = toolbar.querySelectorAll('li[class*=\"rotate\"]');\n          addClass(rotates, CLASS_INVISIBLE);\n          forEach(rotates, function (rotate) {\n            toolbar.appendChild(rotate);\n          });\n        }\n        if (options.inline) {\n          addClass(button, CLASS_FULLSCREEN);\n          setStyle(viewer, {\n            zIndex: options.zIndexInline\n          });\n          if (window.getComputedStyle(parent).position === 'static') {\n            setStyle(parent, {\n              position: 'relative'\n            });\n          }\n          parent.insertBefore(viewer, element.nextSibling);\n        } else {\n          addClass(button, CLASS_CLOSE);\n          addClass(viewer, CLASS_FIXED);\n          addClass(viewer, CLASS_FADE);\n          addClass(viewer, CLASS_HIDE);\n          setStyle(viewer, {\n            zIndex: options.zIndex\n          });\n          var container = options.container;\n          if (isString(container)) {\n            container = element.ownerDocument.querySelector(container);\n          }\n          if (!container) {\n            container = this.body;\n          }\n          container.appendChild(viewer);\n        }\n        if (options.inline) {\n          this.render();\n          this.bind();\n          this.isShown = true;\n        }\n        this.ready = true;\n        if (isFunction(options.ready)) {\n          addListener(element, EVENT_READY, options.ready, {\n            once: true\n          });\n        }\n        if (dispatchEvent(element, EVENT_READY) === false) {\n          this.ready = false;\n          return;\n        }\n        if (this.ready && options.inline) {\n          this.view(this.index);\n        }\n      }\n\n      /**\n       * Get the no conflict viewer class.\n       * @returns {Viewer} The viewer class.\n       */\n    }], [{\n      key: \"noConflict\",\n      value: function noConflict() {\n        window.Viewer = AnotherViewer;\n        return Viewer;\n      }\n\n      /**\n       * Change the default options.\n       * @param {Object} options - The new default options.\n       */\n    }, {\n      key: \"setDefaults\",\n      value: function setDefaults(options) {\n        assign(DEFAULTS, isPlainObject(options) && options);\n      }\n    }]);\n  }();\n  assign(Viewer.prototype, render, events, handlers, methods, others);\n\n  return Viewer;\n\n}));\n"
  },
  {
    "path": "docs/css/main.css",
    "content": ".btn {\n  padding-left: 0.75rem;\n  padding-right: 0.75rem;\n}\n\n.list-group-item {\n  padding: 0 1rem;\n}\n\n.d-flex > .btn {\n  flex: 1;\n}\n\n.carbonads {\n  border: 1px solid #ccc;\n  border-radius: 0.25rem;\n  font-size: 0.875rem;\n  overflow: hidden;\n  padding: 1rem;\n}\n\n.carbon-wrap {\n  overflow: hidden;\n}\n\n.carbon-img {\n  clear: left;\n  display: block;\n  float: left;\n}\n\n.carbon-text,\n.carbon-poweredby {\n  display: block;\n  margin-left: 140px;\n}\n\n.carbon-text,\n.carbon-text:hover,\n.carbon-text:focus {\n  color: #fff;\n  text-decoration: none;\n}\n\n.carbon-poweredby,\n.carbon-poweredby:hover,\n.carbon-poweredby:focus {\n  color: #ddd;\n  text-decoration: none;\n}\n\n@media (min-width: 768px) {\n  .carbonads {\n    float: right;\n    margin-bottom: -1rem;\n    margin-top: -1rem;\n    max-width: 360px;\n  }\n}\n\n.footer {\n  font-size: 0.875rem;\n}\n\n.heart {\n  color: #ddd;\n  display: block;\n  height: 2rem;\n  line-height: 2rem;\n  margin-bottom: 0;\n  margin-top: 1rem;\n  position: relative;\n  text-align: center;\n  width: 100%;\n}\n\n.heart:hover {\n  color: #ff4136;\n}\n\n.heart::before {\n  border-top: 1px solid #eee;\n  content: \" \";\n  display: block;\n  height: 0;\n  left: 0;\n  position: absolute;\n  right: 0;\n  top: 50%;\n}\n\n.heart::after {\n  background-color: #fff;\n  content: \"♥\";\n  padding-left: 0.5rem;\n  padding-right: 0.5rem;\n  position: relative;\n  z-index: 1;\n}\n\n.docs-pictures {\n  list-style: none;\n  margin: 0;\n  padding: 0;\n}\n\n.docs-pictures > li {\n  border: 1px solid transparent;\n  float: left;\n  height: calc(100% / 3);\n  margin: 0 -1px -1px 0;\n  overflow: hidden;\n  width: calc(100% / 3);\n}\n\n.docs-pictures > li > img {\n  cursor: -webkit-zoom-in;\n  cursor: zoom-in;\n  width: 100%;\n}\n\n.docs-buttons > .btn-group,\n.docs-buttons > .input-group {\n  margin-bottom: 5px;\n  width: 100%;\n}\n\n.docs-buttons .input-group-prepend {\n  width: 50%;\n}\n\n.docs-buttons .input-group-prepend .btn {\n  width: 100%;\n}\n"
  },
  {
    "path": "docs/css/viewer.css",
    "content": "/*!\n * Viewer.js v1.11.7\n * https://fengyuanchen.github.io/viewerjs\n *\n * Copyright 2015-present Chen Fengyuan\n * Released under the MIT license\n *\n * Date: 2024-11-24T04:32:14.526Z\n */\n\n.viewer-zoom-in::before, .viewer-zoom-out::before, .viewer-one-to-one::before, .viewer-reset::before, .viewer-prev::before, .viewer-play::before, .viewer-next::before, .viewer-rotate-left::before, .viewer-rotate-right::before, .viewer-flip-horizontal::before, .viewer-flip-vertical::before, .viewer-fullscreen::before, .viewer-fullscreen-exit::before, .viewer-close::before {\n    background-image: url(\"data:image/svg+xml,%3Csvg xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22 viewBox%3D%220 0 560 40%22%3E%3Cpath fill%3D%22%23fff%22 d%3D%22M49.6 17.9h20.2v3.9H49.6zm123.1 2 10.9-11 2.7 2.8-8.2 8.2 8.2 8.2-2.7 2.7-10.9-10.9zm94 0-10.8-11-2.7 2.8 8.1 8.2-8.1 8.2 2.7 2.7 10.8-10.9zM212 9.3l20.1 10.6L212 30.5V9.3zm161.5 4.6-7.2 6 7.2 5.9v-4h12.4v4l7.3-5.9-7.3-6v4h-12.4v-4zm40.2 12.3 5.9 7.2 5.9-7.2h-4V13.6h4l-5.9-7.3-5.9 7.3h4v12.6h-4zm35.9-16.5h6.3v2h-4.3V16h-2V9.7Zm14 0h6.2V16h-2v-4.3h-4.2v-2Zm6.2 14V30h-6.2v-2h4.2v-4.3h2Zm-14 6.3h-6.2v-6.3h2v4.4h4.3v2Zm-438 .1v-8.3H9.6v-3.9h8.2V9.7h3.9v8.2h8.1v3.9h-8.1v8.3h-3.9zM93.6 9.7h-5.8v3.9h2V30h3.8V9.7zm16.1 0h-5.8v3.9h1.9V30h3.9V9.7zm-11.9 4.1h3.9v3.9h-3.9zm0 8.2h3.9v3.9h-3.9zm244.6-11.7 7.2 5.9-7.2 6v-3.6c-5.4-.4-7.8.8-8.7 2.8-.8 1.7-1.8 4.9 2.8 8.2-6.3-2-7.5-6.9-6-11.3 1.6-4.4 8-5 11.9-4.9v-3.1Zm147.2 13.4h6.3V30h-2v-4.3h-4.3v-2zm14 6.3v-6.3h6.2v2h-4.3V30h-1.9zm6.2-14h-6.2V9.7h1.9V14h4.3v2zm-13.9 0h-6.3v-2h4.3V9.7h2V16zm33.3 12.5 8.6-8.6-8.6-8.7 1.9-1.9 8.6 8.7 8.6-8.7 1.9 1.9-8.6 8.7 8.6 8.6-1.9 2-8.6-8.7-8.6 8.7-1.9-2zM297 10.3l-7.1 5.9 7.2 6v-3.6c5.3-.4 7.7.8 8.7 2.8.8 1.7 1.7 4.9-2.9 8.2 6.3-2 7.5-6.9 6-11.3-1.6-4.4-7.9-5-11.8-4.9v-3.1Zm-157.3-.6c2.3 0 4.4.7 6 2l2.5-3 1.9 9.2h-9.3l2.6-3.1a6.2 6.2 0 0 0-9.9 5.1c0 3.4 2.8 6.3 6.2 6.3 2.8 0 5.1-1.9 6-4.4h4c-1 4.7-5 8.3-10 8.3a10 10 0 0 1-10-10.2 10 10 0 0 1 10-10.2Z%22%2F%3E%3C%2Fsvg%3E\");\n    background-repeat: no-repeat;\n    background-size: 280px;\n    color: transparent;\n    display: block;\n    font-size: 0;\n    height: 20px;\n    line-height: 0;\n    width: 20px;\n  }\n\n.viewer-zoom-in::before {\n  background-position: 0 0;\n  content: 'Zoom In';\n}\n\n.viewer-zoom-out::before {\n  background-position: -20px 0;\n  content: 'Zoom Out';\n}\n\n.viewer-one-to-one::before {\n  background-position: -40px 0;\n  content: 'One to One';\n}\n\n.viewer-reset::before {\n  background-position: -60px 0;\n  content: 'Reset';\n}\n\n.viewer-prev::before {\n  background-position: -80px 0;\n  content: 'Previous';\n}\n\n.viewer-play::before {\n  background-position: -100px 0;\n  content: 'Play';\n}\n\n.viewer-next::before {\n  background-position: -120px 0;\n  content: 'Next';\n}\n\n.viewer-rotate-left::before {\n  background-position: -140px 0;\n  content: 'Rotate Left';\n}\n\n.viewer-rotate-right::before {\n  background-position: -160px 0;\n  content: 'Rotate Right';\n}\n\n.viewer-flip-horizontal::before {\n  background-position: -180px 0;\n  content: 'Flip Horizontal';\n}\n\n.viewer-flip-vertical::before {\n  background-position: -200px 0;\n  content: 'Flip Vertical';\n}\n\n.viewer-fullscreen::before {\n  background-position: -220px 0;\n  content: 'Enter Full Screen';\n}\n\n.viewer-fullscreen-exit::before {\n  background-position: -240px 0;\n  content: 'Exit Full Screen';\n}\n\n.viewer-close::before {\n  background-position: -260px 0;\n  content: 'Close';\n}\n\n.viewer-container {\n  bottom: 0;\n  direction: ltr;\n  font-size: 0;\n  left: 0;\n  line-height: 0;\n  overflow: hidden;\n  position: absolute;\n  right: 0;\n  -webkit-tap-highlight-color: transparent;\n  top: 0;\n  -ms-touch-action: none;\n      touch-action: none;\n  -webkit-touch-callout: none;\n  -webkit-user-select: none;\n     -moz-user-select: none;\n      -ms-user-select: none;\n          user-select: none;\n}\n\n.viewer-container::-moz-selection, .viewer-container *::-moz-selection {\n    background-color: transparent;\n  }\n\n.viewer-container::selection,\n  .viewer-container *::selection {\n    background-color: transparent;\n  }\n\n.viewer-container:focus {\n    outline: 0;\n  }\n\n.viewer-container img {\n    display: block;\n    height: auto;\n    max-height: none !important;\n    max-width: none !important;\n    min-height: 0 !important;\n    min-width: 0 !important;\n    width: 100%;\n  }\n\n.viewer-canvas {\n  bottom: 0;\n  left: 0;\n  overflow: hidden;\n  position: absolute;\n  right: 0;\n  top: 0;\n}\n\n.viewer-canvas > img {\n    height: auto;\n    margin: 15px auto;\n    max-width: 90% !important;\n    width: auto;\n  }\n\n.viewer-footer {\n  bottom: 0;\n  left: 0;\n  overflow: hidden;\n  position: absolute;\n  right: 0;\n  text-align: center;\n}\n\n.viewer-navbar {\n  background-color: rgba(0, 0, 0, 0.5);\n  overflow: hidden;\n}\n\n.viewer-list {\n  box-sizing: content-box;\n  height: 50px;\n  margin: 0;\n  overflow: hidden;\n  padding: 1px 0;\n}\n\n.viewer-list > li {\n    color: transparent;\n    cursor: pointer;\n    float: left;\n    font-size: 0;\n    height: 50px;\n    line-height: 0;\n    opacity: 0.5;\n    overflow: hidden;\n    transition: opacity 0.15s;\n    width: 30px;\n  }\n\n.viewer-list > li:focus,\n    .viewer-list > li:hover {\n      opacity: 0.75;\n    }\n\n.viewer-list > li:focus {\n      outline: 0;\n    }\n\n.viewer-list > li + li {\n      margin-left: 1px;\n    }\n\n.viewer-list > .viewer-loading {\n    position: relative;\n  }\n\n.viewer-list > .viewer-loading::after {\n      border-width: 2px;\n      height: 20px;\n      margin-left: -10px;\n      margin-top: -10px;\n      width: 20px;\n    }\n\n.viewer-list > .viewer-active,\n  .viewer-list > .viewer-active:focus,\n  .viewer-list > .viewer-active:hover {\n    opacity: 1;\n  }\n\n.viewer-player {\n  background-color: #000;\n  bottom: 0;\n  cursor: none;\n  display: none;\n  left: 0;\n  position: absolute;\n  right: 0;\n  top: 0;\n  z-index: 1;\n}\n\n.viewer-player > img {\n    left: 0;\n    position: absolute;\n    top: 0;\n  }\n\n.viewer-toolbar > ul {\n    display: inline-block;\n    margin: 0 auto 5px;\n    overflow: hidden;\n    padding: 6px 3px;\n  }\n\n.viewer-toolbar > ul > li {\n      background-color: rgba(0, 0, 0, 0.5);\n      border-radius: 50%;\n      cursor: pointer;\n      float: left;\n      height: 24px;\n      overflow: hidden;\n      transition: background-color 0.15s;\n      width: 24px;\n    }\n\n.viewer-toolbar > ul > li:focus,\n      .viewer-toolbar > ul > li:hover {\n        background-color: rgba(0, 0, 0, 0.8);\n      }\n\n.viewer-toolbar > ul > li:focus {\n        box-shadow: 0 0 3px #fff;\n        outline: 0;\n        position: relative;\n        z-index: 1;\n      }\n\n.viewer-toolbar > ul > li::before {\n        margin: 2px;\n      }\n\n.viewer-toolbar > ul > li + li {\n        margin-left: 1px;\n      }\n\n.viewer-toolbar > ul > .viewer-small {\n      height: 18px;\n      margin-bottom: 3px;\n      margin-top: 3px;\n      width: 18px;\n    }\n\n.viewer-toolbar > ul > .viewer-small::before {\n        margin: -1px;\n      }\n\n.viewer-toolbar > ul > .viewer-large {\n      height: 30px;\n      margin-bottom: -3px;\n      margin-top: -3px;\n      width: 30px;\n    }\n\n.viewer-toolbar > ul > .viewer-large::before {\n        margin: 5px;\n      }\n\n.viewer-tooltip {\n  background-color: rgba(0, 0, 0, 0.8);\n  border-radius: 10px;\n  color: #fff;\n  display: none;\n  font-size: 12px;\n  height: 20px;\n  left: 50%;\n  line-height: 20px;\n  margin-left: -25px;\n  margin-top: -10px;\n  position: absolute;\n  text-align: center;\n  top: 50%;\n  width: 50px;\n}\n\n.viewer-title {\n  color: #ccc;\n  display: inline-block;\n  font-size: 12px;\n  line-height: 1.2;\n  margin: 5px 5%;\n  max-width: 90%;\n  min-height: 14px;\n  opacity: 0.8;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  transition: opacity 0.15s;\n  white-space: nowrap;\n}\n\n.viewer-title:hover {\n    opacity: 1;\n  }\n\n.viewer-button {\n  -webkit-app-region: no-drag;\n  background-color: rgba(0, 0, 0, 0.5);\n  border-radius: 50%;\n  cursor: pointer;\n  height: 80px;\n  overflow: hidden;\n  position: absolute;\n  right: -40px;\n  top: -40px;\n  transition: background-color 0.15s;\n  width: 80px;\n}\n\n.viewer-button:focus,\n  .viewer-button:hover {\n    background-color: rgba(0, 0, 0, 0.8);\n  }\n\n.viewer-button:focus {\n    box-shadow: 0 0 3px #fff;\n    outline: 0;\n  }\n\n.viewer-button::before {\n    bottom: 15px;\n    left: 15px;\n    position: absolute;\n  }\n\n.viewer-fixed {\n  position: fixed;\n}\n\n.viewer-open {\n  overflow: hidden;\n}\n\n.viewer-show {\n  display: block;\n}\n\n.viewer-hide {\n  display: none;\n}\n\n.viewer-backdrop {\n  background-color: rgba(0, 0, 0, 0.5);\n}\n\n.viewer-invisible {\n  visibility: hidden;\n}\n\n.viewer-move {\n  cursor: move;\n  cursor: grab;\n}\n\n.viewer-fade {\n  opacity: 0;\n}\n\n.viewer-in {\n  opacity: 1;\n}\n\n.viewer-transition {\n  transition: all 0.3s;\n}\n\n@keyframes viewer-spinner {\n  0% {\n    transform: rotate(0deg);\n  }\n\n  100% {\n    transform: rotate(360deg);\n  }\n}\n\n.viewer-loading::after {\n    animation: viewer-spinner 1s linear infinite;\n    border: 4px solid rgba(255, 255, 255, 0.1);\n    border-left-color: rgba(255, 255, 255, 0.5);\n    border-radius: 50%;\n    content: '';\n    display: inline-block;\n    height: 40px;\n    left: 50%;\n    margin-left: -20px;\n    margin-top: -20px;\n    position: absolute;\n    top: 50%;\n    width: 40px;\n    z-index: 1;\n  }\n\n@media (max-width: 767px) {\n  .viewer-hide-xs-down {\n    display: none;\n  }\n}\n\n@media (max-width: 991px) {\n  .viewer-hide-sm-down {\n    display: none;\n  }\n}\n\n@media (max-width: 1199px) {\n  .viewer-hide-md-down {\n    display: none;\n  }\n}\n"
  },
  {
    "path": "docs/examples/custom-title.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n  <meta http-equiv=\"x-ua-compatible\" content=\"ie=edge\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n  <title>Viewer.js</title>\n  <link rel=\"stylesheet\" href=\"https://unpkg.com/bootstrap@4/dist/css/bootstrap.min.css\" crossorigin=\"anonymous\">\n  <link rel=\"stylesheet\" href=\"../css/viewer.css\">\n  <style>\n    .pictures {\n      list-style: none;\n      margin: 0;\n      max-width: 30rem;\n      padding: 0;\n    }\n\n    .pictures > li {\n      border: 1px solid transparent;\n      float: left;\n      height: calc(100% / 3);\n      margin: 0 -1px -1px 0;\n      overflow: hidden;\n      width: calc(100% / 3);\n    }\n\n    .pictures > li > img {\n      cursor: zoom-in;\n      width: 100%;\n    }\n  </style>\n</head>\n<body>\n  <div class=\"container\">\n    <h1>Viewer with custom title</h1>\n    <div id=\"galley\">\n      <ul class=\"pictures\">\n        <li><img data-original=\"../images/tibet-1.jpg\" src=\"../images/thumbnails/tibet-1.jpg\" alt=\"Cuo Na Lake\"></li>\n        <li><img data-original=\"../images/tibet-2.jpg\" src=\"../images/thumbnails/tibet-2.jpg\" alt=\"Tibetan Plateau\"></li>\n        <li><img data-original=\"../images/tibet-3.jpg\" src=\"../images/thumbnails/tibet-3.jpg\" alt=\"Jokhang Temple\"></li>\n        <li><img data-original=\"../images/tibet-4.jpg\" src=\"../images/thumbnails/tibet-4.jpg\" alt=\"Potala Palace 1\"></li>\n        <li><img data-original=\"../images/tibet-5.jpg\" src=\"../images/thumbnails/tibet-5.jpg\" alt=\"Potala Palace 2\"></li>\n        <li><img data-original=\"../images/tibet-6.jpg\" src=\"../images/thumbnails/tibet-6.jpg\" alt=\"Potala Palace 3\"></li>\n        <li><img data-original=\"../images/tibet-7.jpg\" src=\"../images/thumbnails/tibet-7.jpg\" alt=\"Lhasa River\"></li>\n        <li><img data-original=\"../images/tibet-8.jpg\" src=\"../images/thumbnails/tibet-8.jpg\" alt=\"Namtso 1\"></li>\n        <li><img data-original=\"../images/tibet-9.jpg\" src=\"../images/thumbnails/tibet-9.jpg\" alt=\"Namtso 2\"></li>\n      </ul>\n    </div>\n  </div>\n\n  <script src=\"https://unpkg.com/jquery@3/dist/jquery.slim.min.js\" crossorigin=\"anonymous\"></script>\n  <script src=\"https://unpkg.com/bootstrap@4/dist/js/bootstrap.bundle.min.js\" crossorigin=\"anonymous\"></script>\n  <script src=\"../js/viewer.js\"></script>\n  <script>\n    window.addEventListener('DOMContentLoaded', function () {\n      var galley = document.getElementById('galley');\n      var viewer = new Viewer(galley, {\n        url: 'data-original',\n        title: function (image) {\n          return image.alt + ' (' + (this.index + 1) + '/' + this.length + ')';\n        },\n      });\n    });\n  </script>\n</body>\n</html>\n"
  },
  {
    "path": "docs/examples/custom-toolbar.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n  <meta http-equiv=\"x-ua-compatible\" content=\"ie=edge\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n  <title>Viewer.js</title>\n  <link rel=\"stylesheet\" href=\"https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css\" crossorigin=\"anonymous\">\n  <link rel=\"stylesheet\" href=\"https://unpkg.com/bootstrap@4/dist/css/bootstrap.min.css\" crossorigin=\"anonymous\">\n  <link rel=\"stylesheet\" href=\"../css/viewer.css\">\n  <style>\n    .pictures {\n      list-style: none;\n      margin: 0;\n      max-width: 30rem;\n      padding: 0;\n    }\n\n    .pictures > li {\n      border: 1px solid transparent;\n      float: left;\n      height: calc(100% / 3);\n      margin: 0 -1px -1px 0;\n      overflow: hidden;\n      width: calc(100% / 3);\n    }\n\n    .pictures > li > img {\n      cursor: zoom-in;\n      width: 100%;\n    }\n\n    .viewer-download {\n      color: #fff;\n      font-family: FontAwesome, serif;\n      font-size: 0.75rem;\n      line-height: 1.5rem;\n      text-align: center;\n    }\n\n    .viewer-download::before {\n      content: \"\\f019\";\n    }\n  </style>\n</head>\n<body>\n  <div class=\"container\">\n    <h1>Viewer with custom toolbar</h1>\n    <div id=\"galley\">\n      <ul class=\"pictures\">\n        <li><img data-original=\"../images/tibet-1.jpg\" src=\"../images/thumbnails/tibet-1.jpg\" alt=\"Cuo Na Lake\"></li>\n        <li><img data-original=\"../images/tibet-2.jpg\" src=\"../images/thumbnails/tibet-2.jpg\" alt=\"Tibetan Plateau\"></li>\n        <li><img data-original=\"../images/tibet-3.jpg\" src=\"../images/thumbnails/tibet-3.jpg\" alt=\"Jokhang Temple\"></li>\n        <li><img data-original=\"../images/tibet-4.jpg\" src=\"../images/thumbnails/tibet-4.jpg\" alt=\"Potala Palace 1\"></li>\n        <li><img data-original=\"../images/tibet-5.jpg\" src=\"../images/thumbnails/tibet-5.jpg\" alt=\"Potala Palace 2\"></li>\n        <li><img data-original=\"../images/tibet-6.jpg\" src=\"../images/thumbnails/tibet-6.jpg\" alt=\"Potala Palace 3\"></li>\n        <li><img data-original=\"../images/tibet-7.jpg\" src=\"../images/thumbnails/tibet-7.jpg\" alt=\"Lhasa River\"></li>\n        <li><img data-original=\"../images/tibet-8.jpg\" src=\"../images/thumbnails/tibet-8.jpg\" alt=\"Namtso 1\"></li>\n        <li><img data-original=\"../images/tibet-9.jpg\" src=\"../images/thumbnails/tibet-9.jpg\" alt=\"Namtso 2\"></li>\n      </ul>\n    </div>\n  </div>\n\n  <script src=\"https://unpkg.com/jquery@3/dist/jquery.slim.min.js\" crossorigin=\"anonymous\"></script>\n  <script src=\"https://unpkg.com/bootstrap@4/dist/js/bootstrap.bundle.min.js\" crossorigin=\"anonymous\"></script>\n  <script src=\"../js/viewer.js\"></script>\n  <script>\n    window.addEventListener('DOMContentLoaded', function () {\n      var galley = document.getElementById('galley');\n      var viewer = new Viewer(galley, {\n        url: 'data-original',\n        toolbar: {\n          oneToOne: true,\n\n          prev: function() {\n            viewer.prev(true);\n          },\n\n          play: true,\n\n          next: function() {\n            viewer.next(true);\n          },\n\n          download: function() {\n            const a = document.createElement('a');\n\n            a.href = viewer.image.src;\n            a.download = viewer.image.alt;\n            document.body.appendChild(a);\n            a.click();\n            document.body.removeChild(a);\n          },\n        },\n      });\n    });\n  </script>\n</body>\n</html>\n"
  },
  {
    "path": "docs/examples/dynamic-viewer.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n  <meta http-equiv=\"x-ua-compatible\" content=\"ie=edge\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n  <title>Viewer.js</title>\n  <link rel=\"stylesheet\" href=\"https://unpkg.com/bootstrap@4/dist/css/bootstrap.min.css\" crossorigin=\"anonymous\">\n  <link rel=\"stylesheet\" href=\"../css/viewer.css\">\n</head>\n<body>\n  <div class=\"container\">\n    <h1>Show a viewer dynamically on click a button</h1>\n    <button type=\"button\" class=\"btn btn-primary\" id=\"button\">\n      Launch the demo\n    </button>\n  </div>\n\n  <script src=\"https://unpkg.com/jquery@3/dist/jquery.slim.min.js\" crossorigin=\"anonymous\"></script>\n  <script src=\"https://unpkg.com/bootstrap@4/dist/js/bootstrap.bundle.min.js\" crossorigin=\"anonymous\"></script>\n  <script src=\"../js/viewer.js\"></script>\n  <script>\n    window.addEventListener('DOMContentLoaded', function () {\n      document.getElementById('button').addEventListener('click', function () {\n        var image = new Image();\n\n        image.src = '../images/tibet-1.jpg';\n\n        var viewer = new Viewer(image, {\n          hidden: function () {\n            viewer.destroy();\n          },\n        });\n\n        // image.click();\n        viewer.show();\n      });\n    });\n  </script>\n</body>\n</html>\n"
  },
  {
    "path": "docs/examples/moving-range-limit.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n  <meta http-equiv=\"x-ua-compatible\" content=\"ie=edge\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n  <title>Viewer.js</title>\n  <link rel=\"stylesheet\" href=\"https://unpkg.com/bootstrap@4/dist/css/bootstrap.min.css\" crossorigin=\"anonymous\">\n  <link rel=\"stylesheet\" href=\"../css/viewer.css\">\n  <style>\n    .pictures {\n      list-style: none;\n      margin: 0;\n      max-width: 30rem;\n      padding: 0;\n    }\n\n    .pictures > li {\n      border: 1px solid transparent;\n      float: left;\n      height: calc(100% / 3);\n      margin: 0 -1px -1px 0;\n      overflow: hidden;\n      width: calc(100% / 3);\n    }\n\n    .pictures > li > img {\n      cursor: zoom-in;\n      width: 100%;\n    }\n  </style>\n</head>\n<body>\n  <div class=\"container\">\n    <h1>Moving range limit</h1>\n    <div id=\"galley\">\n      <ul class=\"pictures\">\n        <li><img data-original=\"../images/tibet-1.jpg\" src=\"../images/thumbnails/tibet-1.jpg\" alt=\"Cuo Na Lake\"></li>\n        <li><img data-original=\"../images/tibet-2.jpg\" src=\"../images/thumbnails/tibet-2.jpg\" alt=\"Tibetan Plateau\"></li>\n        <li><img data-original=\"../images/tibet-3.jpg\" src=\"../images/thumbnails/tibet-3.jpg\" alt=\"Jokhang Temple\"></li>\n        <li><img data-original=\"../images/tibet-4.jpg\" src=\"../images/thumbnails/tibet-4.jpg\" alt=\"Potala Palace 1\"></li>\n        <li><img data-original=\"../images/tibet-5.jpg\" src=\"../images/thumbnails/tibet-5.jpg\" alt=\"Potala Palace 2\"></li>\n        <li><img data-original=\"../images/tibet-6.jpg\" src=\"../images/thumbnails/tibet-6.jpg\" alt=\"Potala Palace 3\"></li>\n        <li><img data-original=\"../images/tibet-7.jpg\" src=\"../images/thumbnails/tibet-7.jpg\" alt=\"Lhasa River\"></li>\n        <li><img data-original=\"../images/tibet-8.jpg\" src=\"../images/thumbnails/tibet-8.jpg\" alt=\"Namtso 1\"></li>\n        <li><img data-original=\"../images/tibet-9.jpg\" src=\"../images/thumbnails/tibet-9.jpg\" alt=\"Namtso 2\"></li>\n      </ul>\n    </div>\n  </div>\n\n  <script src=\"https://unpkg.com/jquery@3/dist/jquery.slim.min.js\" crossorigin=\"anonymous\"></script>\n  <script src=\"https://unpkg.com/bootstrap@4/dist/js/bootstrap.bundle.min.js\" crossorigin=\"anonymous\"></script>\n  <script src=\"../js/viewer.js\"></script>\n  <script>\n    window.addEventListener('DOMContentLoaded', function () {\n      var galley = document.getElementById('galley');\n      var maxOffsetPercentage = 0.9;\n      var viewer = new Viewer(galley, {\n        url: 'data-original',\n        backdrop: 'static',\n        move: function (event) {\n          var viewerData = viewer.viewerData;\n          var imageData = viewer.imageData;\n          var maxOffsetHorizontal = viewerData.width * maxOffsetPercentage;\n          var maxOffsetVertical = viewerData.height * maxOffsetPercentage;\n          var detail = event.detail;\n          var left = detail.x;\n          var top = detail.y;\n          var right = viewerData.width - (left + imageData.width);\n          var bottom = viewerData.height - (top + imageData.height);\n\n          if (\n            // Move left\n            (detail.x < detail.oldX && right > 0 && right > maxOffsetHorizontal)\n\n            // Move right\n            || (detail.x > detail.oldX && left > 0 && left > maxOffsetHorizontal)\n\n            // Move up\n            || (detail.y < detail.oldY && bottom > 0 && bottom > maxOffsetVertical)\n\n            // Move down\n            || (detail.y > detail.oldY && top > 0 && top > maxOffsetVertical)\n          ) {\n            event.preventDefault();\n          }\n        },\n        zoomed: function (event) {\n          var detail = event.detail;\n\n          // Zoom out\n          if (detail.ratio < detail.oldRatio) {\n            var viewerData = viewer.viewerData;\n            var imageData = viewer.imageData;\n            var maxOffsetHorizontal = viewerData.width * maxOffsetPercentage;\n            var maxOffsetVertical = viewerData.height * maxOffsetPercentage;\n            var left = imageData.x;\n            var top = imageData.y;\n            var right = viewerData.width - (left + imageData.width);\n            var bottom = viewerData.height - (top + imageData.height);\n            var x = 0;\n            var y = 0;\n\n            if (right > 0 && right > maxOffsetHorizontal) {\n              x = maxOffsetHorizontal - right;\n            }\n\n            if (left > 0 && left > maxOffsetHorizontal) {\n              x = maxOffsetHorizontal - left;\n            }\n\n            if (bottom > 0 && bottom > maxOffsetVertical) {\n              y = bottom - maxOffsetVertical;\n            }\n\n            if (top > 0 && top > maxOffsetVertical) {\n              y = top - maxOffsetVertical;\n            }\n\n            // Move the image into view if it is invisible\n            if (x !== 0 || y !== 0) {\n              viewer.move(x, y);\n            }\n          }\n        },\n      });\n    });\n  </script>\n</body>\n</html>\n"
  },
  {
    "path": "docs/examples/viewer-in-modal.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n  <meta http-equiv=\"x-ua-compatible\" content=\"ie=edge\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n  <title>Viewer.js</title>\n  <link rel=\"stylesheet\" href=\"https://unpkg.com/bootstrap@4/dist/css/bootstrap.min.css\" crossorigin=\"anonymous\">\n  <link rel=\"stylesheet\" href=\"../css/viewer.css\">\n  <style>\n    .pictures {\n      list-style: none;\n      margin: 0;\n      padding: 0;\n    }\n\n    .pictures > li {\n      border: 1px solid transparent;\n      float: left;\n      height: calc(100% / 3);\n      margin: 0 -1px -1px 0;\n      overflow: hidden;\n      width: calc(100% / 3);\n    }\n\n    .pictures > li > img {\n      cursor: zoom-in;\n      width: 100%;\n    }\n  </style>\n</head>\n<body>\n  <div class=\"container\">\n    <h1>Viewer in a Bootstrap modal</h1>\n\n    <!-- Button trigger modal -->\n    <button type=\"button\" class=\"btn btn-primary\" data-target=\"#modal\" data-toggle=\"modal\">\n      Launch the demo\n    </button>\n\n    <!-- Modal -->\n    <div class=\"modal fade\" id=\"modal\" role=\"dialog\" aria-labelledby=\"modalLabel\" tabindex=\"-1\">\n      <div class=\"modal-dialog\" role=\"document\">\n        <div class=\"modal-content\">\n          <div class=\"modal-header\">\n            <h5 class=\"modal-title\" id=\"modalLabel\">Viewer</h5>\n            <button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-label=\"Close\">\n              <span aria-hidden=\"true\">&times;</span>\n            </button>\n          </div>\n          <div class=\"modal-body\">\n            <div id=\"galley\">\n              <ul class=\"pictures\">\n                <li><img data-original=\"../images/tibet-1.jpg\" src=\"../images/thumbnails/tibet-1.jpg\" alt=\"Cuo Na Lake\"></li>\n                <li><img data-original=\"../images/tibet-2.jpg\" src=\"../images/thumbnails/tibet-2.jpg\" alt=\"Tibetan Plateau\"></li>\n                <li><img data-original=\"../images/tibet-3.jpg\" src=\"../images/thumbnails/tibet-3.jpg\" alt=\"Jokhang Temple\"></li>\n                <li><img data-original=\"../images/tibet-4.jpg\" src=\"../images/thumbnails/tibet-4.jpg\" alt=\"Potala Palace 1\"></li>\n                <li><img data-original=\"../images/tibet-5.jpg\" src=\"../images/thumbnails/tibet-5.jpg\" alt=\"Potala Palace 2\"></li>\n                <li><img data-original=\"../images/tibet-6.jpg\" src=\"../images/thumbnails/tibet-6.jpg\" alt=\"Potala Palace 3\"></li>\n                <li><img data-original=\"../images/tibet-7.jpg\" src=\"../images/thumbnails/tibet-7.jpg\" alt=\"Lhasa River\"></li>\n                <li><img data-original=\"../images/tibet-8.jpg\" src=\"../images/thumbnails/tibet-8.jpg\" alt=\"Namtso 1\"></li>\n                <li><img data-original=\"../images/tibet-9.jpg\" src=\"../images/thumbnails/tibet-9.jpg\" alt=\"Namtso 2\"></li>\n              </ul>\n            </div>\n          </div>\n          <div class=\"modal-footer\">\n            <button type=\"button\" class=\"btn btn-secondary\" data-dismiss=\"modal\">Close</button>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n\n  <script src=\"https://unpkg.com/jquery@3/dist/jquery.slim.min.js\" crossorigin=\"anonymous\"></script>\n  <script src=\"https://unpkg.com/bootstrap@4/dist/js/bootstrap.bundle.min.js\" crossorigin=\"anonymous\"></script>\n  <script src=\"../js/viewer.js\"></script>\n  <script>\n    window.addEventListener('DOMContentLoaded', function () {\n      var galley = document.getElementById('galley');\n      var viewer;\n\n      $('#modal').on('shown.bs.modal', function (e) {\n        // WARNING: should ignore Viewer's `shown` event here.\n        if(e.namespace === 'bs.modal') {\n          viewer = new Viewer(galley, {\n            url: 'data-original',\n          });\n        }\n      }).on('hidden.bs.modal', function (e) {\n        // WARNING: should ignore Viewer's `hidden` event here.\n        if(e.namespace === 'bs.modal') {\n          viewer.destroy();\n        }\n      });\n    });\n  </script>\n</body>\n</html>\n"
  },
  {
    "path": "docs/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n  <meta http-equiv=\"x-ua-compatible\" content=\"ie=edge\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n  <meta name=\"description\" content=\"JavaScript image viewer.\">\n  <meta name=\"author\" content=\"Chen Fengyuan\">\n  <title>Viewer.js</title>\n  <link rel=\"stylesheet\" href=\"https://unpkg.com/bootstrap@4/dist/css/bootstrap.min.css\" crossorigin=\"anonymous\">\n  <link rel=\"stylesheet\" href=\"css/viewer.css\">\n  <link rel=\"stylesheet\" href=\"css/main.css\">\n</head>\n<body>\n  <!--[if lt IE 9]>\n  <div class=\"alert alert-warning alert-dismissible fade show m-0 rounded-0\" role=\"alert\">\n    <button type=\"button\" class=\"close\" data-dismiss=\"alert\" aria-label=\"Close\">\n      <span aria-hidden=\"true\">&times;</span>\n    </button>\n    You are using an <strong>outdated</strong> browser. Please <a href=\"https://browsehappy.com/\">upgrade your browser</a> to improve your experience.\n  </div>\n  <![endif]-->\n\n  <!-- Header -->\n  <header class=\"navbar navbar-light navbar-expand-md\">\n    <div class=\"container\">\n      <a class=\"navbar-brand\" href=\"./\">Viewer.js</a>\n      <button class=\"navbar-toggler\" type=\"button\" data-toggle=\"collapse\" data-target=\"#navbar-collapse\" aria-controls=\"navbar-collapse\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n      <div class=\"collapse navbar-collapse justify-content-end\" id=\"navbar-collapse\" role=\"navigation\">\n        <ul class=\"nav navbar-nav\">\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"https://github.com/fengyuanchen/viewerjs/blob/main/README.md\" data-toggle=\"tooltip\" title=\"View the documentation\">Docs</a>\n          </li>\n          <li class=\"nav-item dropdown\">\n            <a class=\"nav-link dropdown-toggle\" href=\"#\" id=\"navbarDropdownMenuLink\" role=\"button\" data-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\">\n              Examples\n            </a>\n            <div class=\"dropdown-menu\" aria-labelledby=\"navbarDropdownMenuLink\">\n              <a class=\"dropdown-item\" href=\"examples/custom-title.html\">Custom title</a>\n              <a class=\"dropdown-item\" href=\"examples/custom-toolbar.html\">Custom toolbar</a>\n              <a class=\"dropdown-item\" href=\"examples/dynamic-viewer.html\">Dynamic Viewer</a>\n              <a class=\"dropdown-item\" href=\"examples/moving-range-limit.html\">Moving range limit</a>\n              <a class=\"dropdown-item\" href=\"examples/viewer-in-modal.html\">Viewer in modal</a>\n            </div>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"https://github.com/fengyuanchen/viewerjs\" data-toggle=\"tooltip\" title=\"View the GitHub project\">GitHub</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"https://fengyuanchen.github.io/\" data-toggle=\"tooltip\" title=\"Explore more projects\">Explore</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"https://chenfengyuan.com/\" data-toggle=\"tooltip\" title=\"About the author\">About</a>\n          </li>\n        </ul>\n      </div>\n    </div>\n  </header>\n\n  <!-- Jumbotron -->\n  <div class=\"jumbotron bg-primary text-white rounded-0\">\n    <div class=\"container\">\n      <div class=\"row\">\n        <div class=\"col-md\">\n          <h1>Viewer.js <small class=\"h6\">v1.11.7</small></h1>\n          <p class=\"lead\">JavaScript image viewer.</p>\n        </div>\n        <div class=\"col-md\">\n          <div class=\"carbonads\">\n            <script id=\"_carbonads_js\" src=\"https://cdn.carbonads.com/carbon.js?serve=CKYI55Q7&placement=fengyuanchengithubio\" async></script>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n\n  <!-- Content -->\n  <div class=\"container\">\n    <h1>Overview</h1>\n    <hr>\n    <div class=\"row\">\n      <div class=\"col-sm-12 col-md-3\">\n        <h3>Options</h3>\n        <hr>\n        <div class=\"docs-toggles\">\n          <div class=\"btn-group d-flex\" data-toggle=\"buttons\" role=\"group\">\n            <label class=\"btn btn-primary active\">\n              <input type=\"radio\" class=\"sr-only\" name=\"inline\" data-value=\"false\" autocomplete=\"off\" checked> Modal mode\n            </label>\n            <label class=\"btn btn-primary\">\n              <input type=\"radio\" class=\"sr-only\" name=\"inline\" data-value=\"true\" autocomplete=\"off\"> Inline mode\n            </label>\n          </div>\n          <button class=\"btn btn-primary btn-block mb-3 d-md-none\" id=\"toggle-options\" data-target=\"#options\" data-toggle=\"collapse\" aria-expanded=\"true\">Toggle options</button>\n          <div class=\"docs-collapse collapse d-md-block mb-3\" id=\"options\" aria-labelledby=\"toggle-options\">\n            <ul class=\"list-group\">\n              <li class=\"list-group-item\">\n                <div class=\"form-check\">\n                  <input class=\"form-check-input\" id=\"backdrop\" type=\"checkbox\" name=\"backdrop\" checked>\n                  <label for=\"backdrop\" class=\"form-check-label\">backdrop</label>\n                </div>\n              </li>\n              <li class=\"list-group-item\">\n                <div class=\"form-check\">\n                  <input class=\"form-check-input\" id=\"button\" type=\"checkbox\" name=\"button\" checked>\n                  <label for=\"button\" class=\"form-check-label\">button</label>\n                </div>\n              </li>\n              <li class=\"list-group-item\">\n                <div class=\"form-check\">\n                  <input class=\"form-check-input\" id=\"focus\" type=\"checkbox\" name=\"focus\" checked>\n                  <label for=\"focus\" class=\"form-check-label\">focus</label>\n                </div>\n              </li>\n              <li class=\"list-group-item\">\n                <div class=\"form-check\">\n                  <input class=\"form-check-input\" id=\"fullscreen\" type=\"checkbox\" name=\"fullscreen\" checked>\n                  <label for=\"fullscreen\" class=\"form-check-label\">fullscreen</label>\n                </div>\n              </li>\n              <li class=\"list-group-item\">\n                <div class=\"form-check\">\n                  <input class=\"form-check-input\" id=\"loading\" type=\"checkbox\" name=\"loading\" checked>\n                  <label for=\"loading\" class=\"form-check-label\">loading</label>\n                </div>\n              </li>\n              <li class=\"list-group-item\">\n                <div class=\"form-check\">\n                  <input class=\"form-check-input\" id=\"loop\" type=\"checkbox\" name=\"loop\" checked>\n                  <label for=\"loop\" class=\"form-check-label\">loop</label>\n                </div>\n              </li>\n              <li class=\"list-group-item\">\n                <div class=\"form-check\">\n                  <input class=\"form-check-input\" id=\"keyboard\" type=\"checkbox\" name=\"keyboard\" checked>\n                  <label for=\"keyboard\" class=\"form-check-label\">keyboard</label>\n                </div>\n              </li>\n              <li class=\"list-group-item\">\n                <div class=\"form-check\">\n                  <input class=\"form-check-input\" id=\"movable\" type=\"checkbox\" name=\"movable\" checked>\n                  <label for=\"movable\" class=\"form-check-label\">movable</label>\n                </div>\n              </li>\n              <li class=\"list-group-item\">\n                <div class=\"form-check\">\n                  <input class=\"form-check-input\" id=\"navbar\" type=\"checkbox\" name=\"navbar\" checked>\n                  <label for=\"navbar\" class=\"form-check-label\">navbar</label>\n                </div>\n              </li>\n              <li class=\"list-group-item\">\n                <div class=\"form-check\">\n                  <input class=\"form-check-input\" id=\"rotatable\" type=\"checkbox\" name=\"rotatable\" checked>\n                  <label for=\"rotatable\" class=\"form-check-label\">rotatable</label>\n                </div>\n              </li>\n              <li class=\"list-group-item\">\n                <div class=\"form-check\">\n                  <input class=\"form-check-input\" id=\"scalable\" type=\"checkbox\" name=\"scalable\" checked>\n                  <label for=\"scalable\" class=\"form-check-label\">scalable</label>\n                </div>\n              </li>\n              <li class=\"list-group-item\">\n                <div class=\"form-check\">\n                  <input class=\"form-check-input\" id=\"slideOnTouch\" type=\"checkbox\" name=\"slideOnTouch\" checked>\n                  <label for=\"slideOnTouch\" class=\"form-check-label\">slideOnTouch</label>\n                </div>\n              </li>\n              <li class=\"list-group-item\">\n                <div class=\"form-check\">\n                  <input class=\"form-check-input\" id=\"title\" type=\"checkbox\" name=\"title\" checked>\n                  <label for=\"title\" class=\"form-check-label\">title</label>\n                </div>\n              </li>\n              <li class=\"list-group-item\">\n                <div class=\"form-check\">\n                  <input class=\"form-check-input\" id=\"toggleOnDblclick\" type=\"checkbox\" name=\"toggleOnDblclick\" checked>\n                  <label for=\"toggleOnDblclick\" class=\"form-check-label\">toggleOnDblclick</label>\n                </div>\n              </li>\n              <li class=\"list-group-item\">\n                <div class=\"form-check\">\n                  <input class=\"form-check-input\" id=\"toolbar\" type=\"checkbox\" name=\"toolbar\" checked>\n                  <label for=\"toolbar\" class=\"form-check-label\">toolbar</label>\n                </div>\n              </li>\n              <li class=\"list-group-item\">\n                <div class=\"form-check\">\n                  <input class=\"form-check-input\" id=\"tooltip\" type=\"checkbox\" name=\"tooltip\" checked>\n                  <label for=\"tooltip\" class=\"form-check-label\">tooltip</label>\n                </div>\n              </li>\n              <li class=\"list-group-item\">\n                <div class=\"form-check\">\n                  <input class=\"form-check-input\" id=\"transition\" type=\"checkbox\" name=\"transition\" checked>\n                  <label for=\"transition\" class=\"form-check-label\">transition</label>\n                </div>\n              </li>\n              <li class=\"list-group-item\">\n                <div class=\"form-check\">\n                  <input class=\"form-check-input\" id=\"zoomable\" type=\"checkbox\" name=\"zoomable\" checked>\n                  <label for=\"zoomable\" class=\"form-check-label\">zoomable</label>\n                </div>\n              </li>\n              <li class=\"list-group-item\">\n                <div class=\"form-check\">\n                  <input class=\"form-check-input\" id=\"zoomOnTouch\" type=\"checkbox\" name=\"zoomOnTouch\" checked>\n                  <label for=\"zoomOnTouch\" class=\"form-check-label\">zoomOnTouch</label>\n                </div>\n              </li>\n              <li class=\"list-group-item\">\n                <div class=\"form-check\">\n                  <input class=\"form-check-input\" id=\"zoomOnWheel\" type=\"checkbox\" name=\"zoomOnWheel\" checked>\n                  <label for=\"zoomOnWheel\" class=\"form-check-label\">zoomOnWheel</label>\n                </div>\n              </li>\n            </ul>\n          </div>\n        </div>\n      </div>\n      <div class=\"col-sm-8 col-md-6\">\n        <h3>Demo</h3>\n        <hr>\n        <div class=\"docs-galley mb-3\">\n          <ul class=\"docs-pictures clearfix\">\n            <li><img data-original=\"images/tibet-1.jpg\" src=\"images/thumbnails/tibet-1.jpg\" alt=\"Cuo Na Lake\"></li>\n            <li><img data-original=\"images/tibet-2.jpg\" src=\"images/thumbnails/tibet-2.jpg\" alt=\"Tibetan Plateau\"></li>\n            <li><img data-original=\"images/tibet-3.jpg\" src=\"images/thumbnails/tibet-3.jpg\" alt=\"Jokhang Temple\"></li>\n            <li><img data-original=\"images/tibet-4.jpg\" src=\"images/thumbnails/tibet-4.jpg\" alt=\"Potala Palace 1\"></li>\n            <li><img data-original=\"images/tibet-5.jpg\" src=\"images/thumbnails/tibet-5.jpg\" alt=\"Potala Palace 2\"></li>\n            <li><img data-original=\"images/tibet-6.jpg\" src=\"images/thumbnails/tibet-6.jpg\" alt=\"Potala Palace 3\"></li>\n            <li><img data-original=\"images/tibet-7.jpg\" src=\"images/thumbnails/tibet-7.jpg\" alt=\"Lhasa River\"></li>\n            <li><img data-original=\"images/tibet-8.jpg\" src=\"images/thumbnails/tibet-8.jpg\" alt=\"Namtso 1\"></li>\n            <li><img data-original=\"images/tibet-9.jpg\" src=\"images/thumbnails/tibet-9.jpg\" alt=\"Namtso 2\"></li>\n          </ul>\n        </div>\n      </div>\n      <div class=\"col-sm-4 col-md-3\">\n        <h3>Methods</h3>\n        <hr>\n        <div class=\"docs-buttons\" role=\"group\">\n          <div class=\"input-group\">\n            <span class=\"input-group-prepend\">\n              <button type=\"button\" class=\"btn btn-primary\" data-method=\"view\" data-target=\"#viewIndex\" title=\"View one of the images with image's index\">View</button>\n            </span>\n            <input type=\"text\" class=\"form-control\" id=\"viewIndex\" name=\"index\" value=\"4\" placeholder=\"index\">\n          </div>\n          <div class=\"input-group\">\n            <span class=\"input-group-prepend\">\n              <button type=\"button\" class=\"btn btn-primary\" data-enable=\"inline\" data-method=\"zoom\" data-target=\"#zoomRatio\" title=\"Zoom the image\">Zoom</button>\n            </span>\n            <input type=\"text\" class=\"form-control\" id=\"zoomRatio\" name=\"ratio\" value=\"0.5\" placeholder=\"ratio\">\n          </div>\n          <div class=\"input-group\">\n            <span class=\"input-group-prepend\">\n              <button type=\"button\" class=\"btn btn-primary\" data-enable=\"inline\" data-method=\"zoomTo\" data-target=\"#zoomToRatio\" title=\"Zoom the image to a special ratio\">Zoom To</button>\n            </span>\n            <input type=\"text\" class=\"form-control\" id=\"zoomToRatio\" name=\"ratio\" value=\"1\" placeholder=\"ratio\">\n          </div>\n          <div class=\"input-group\">\n            <span class=\"input-group-prepend\">\n              <button type=\"button\" class=\"btn btn-primary\" data-enable=\"inline\" data-method=\"rotate\" data-target=\"#rotateDegrees\" title=\"Rotate the image\">Rotate</button>\n            </span>\n            <input type=\"text\" class=\"form-control\" id=\"rotateDegrees\" name=\"degrees\" value=\"90\" placeholder=\"degrees\">\n          </div>\n          <div class=\"input-group\">\n            <span class=\"input-group-prepend\">\n              <button type=\"button\" class=\"btn btn-primary\" data-enable=\"inline\" data-method=\"rotateTo\" data-target=\"#rotateToDegrees\" title=\"Rotate the image to a special angle\">Rotate To</button>\n            </span>\n            <input type=\"text\" class=\"form-control\" id=\"rotateToDegrees\" name=\"degrees\" value=\"360\" placeholder=\"degrees\">\n          </div>\n          <div class=\"btn-group d-flex\" role=\"group\">\n            <button type=\"button\" class=\"btn btn-primary\" data-arguments=\"[0.5]\" data-enable=\"inline\" data-method=\"zoom\" title=\"Zoom in\">Zoom In</button>\n            <button type=\"button\" class=\"btn btn-primary\" data-arguments=\"[-0.5]\" data-enable=\"inline\" data-method=\"zoom\" title=\"Zoom out\">Zoom out</button>\n          </div>\n          <div class=\"btn-group d-flex\" role=\"group\">\n            <button type=\"button\" class=\"btn btn-primary\" data-arguments=\"[-90]\" data-enable=\"inline\" data-method=\"rotate\" title=\"Rotate left\">Rotate Left</button>\n            <button type=\"button\" class=\"btn btn-primary\" data-arguments=\"[90]\" data-enable=\"inline\" data-method=\"rotate\" title=\"Rotate right\">Rotate Right</button>\n          </div>\n          <div class=\"btn-group d-flex\" role=\"group\">\n            <button type=\"button\" class=\"btn btn-primary\" data-arguments=\"[-1]\" data-enable=\"inline\" data-method=\"scaleX\" title=\"Flip horizontal\">Flip horizontal</button>\n            <button type=\"button\" class=\"btn btn-primary\" data-arguments=\"[-1]\" data-enable=\"inline\" data-method=\"scaleY\" title=\"Flip vertical\">Flip vertical</button>\n          </div>\n          <div class=\"btn-group d-flex\" role=\"group\">\n            <button type=\"button\" class=\"btn btn-primary\" data-arguments=\"[-10,0]\" data-enable=\"inline\" data-method=\"move\" title=\"Move left\">Left</button>\n            <button type=\"button\" class=\"btn btn-primary\" data-arguments=\"[10,0]\" data-enable=\"inline\" data-method=\"move\" title=\"Move right\">Right</button>\n            <button type=\"button\" class=\"btn btn-primary\" data-arguments=\"[0,-10]\" data-enable=\"inline\" data-method=\"move\" title=\"Move up\">Up</button>\n            <button type=\"button\" class=\"btn btn-primary\" data-arguments=\"[0,10]\" data-enable=\"inline\" data-method=\"move\" title=\"Move down\">Down</button>\n          </div>\n          <div class=\"btn-group d-flex\" role=\"group\">\n            <button type=\"button\" class=\"btn btn-primary\" data-enable=\"inline\" data-method=\"prev\" title=\"View previous image\">Prev</button>\n            <button type=\"button\" class=\"btn btn-primary\" data-enable=\"inline\" data-method=\"next\" title=\"View next image\">Next</button>\n            <button type=\"button\" class=\"btn btn-primary\" data-enable=\"inline\" data-method=\"play\" title=\"Play the images\">Play</button>\n            <button type=\"button\" class=\"btn btn-primary\" data-enable=\"inline\" data-method=\"stop\" title=\"Stop the playing\">Stop</button>\n          </div>\n          <div class=\"btn-group d-flex\" role=\"group\">\n            <button type=\"button\" class=\"btn btn-primary\" data-enable=\"modal\" data-method=\"show\" title=\"Show the viewer\">Show</button>\n            <button type=\"button\" class=\"btn btn-primary\" data-method=\"hide\" title=\"Hide the viewer\" disabled>Hide</button>\n            <button type=\"button\" class=\"btn btn-primary\" data-enable=\"inline\" data-method=\"full\" title=\"Enter modal mode\">Full</button>\n            <button type=\"button\" class=\"btn btn-primary\" data-method=\"exit\" title=\"Exit modal mode\" disabled>Exit</button>\n          </div>\n          <div class=\"btn-group d-flex\" role=\"group\">\n            <button type=\"button\" class=\"btn btn-primary\" data-enable=\"inline\" data-method=\"reset\">Reset</button>\n            <button type=\"button\" class=\"btn btn-primary\" data-enable=\"inline\" data-method=\"tooltip\">Tooltip</button>\n          </div>\n          <button type=\"button\" class=\"btn btn-block btn-danger\" data-enable=\"modal inline\" data-method=\"destroy\">Destroy</button>\n        </div>\n      </div>\n    </div>\n  </div>\n\n  <!-- Footer -->\n  <footer class=\"footer\">\n    <div class=\"container\">\n      <p class=\"heart\"></p>\n      <nav class=\"nav flex-wrap justify-content-center mb-3\">\n        <a class=\"nav-link\" href=\"https://github.com/fengyuanchen/viewerjs\">GitHub</a>\n        <a class=\"nav-link\" href=\"https://github.com/fengyuanchen/viewerjs/releases\">Releases</a>\n        <a class=\"nav-link\" href=\"https://github.com/fengyuanchen/viewerjs/blob/main/LICENSE\">License</a>\n        <a class=\"nav-link\" href=\"https://chenfengyuan.com/\">About</a>\n      </nav>\n    </div>\n  </footer>\n\n  <!-- Scripts -->\n  <script src=\"https://unpkg.com/jquery@3/dist/jquery.slim.min.js\" crossorigin=\"anonymous\"></script>\n  <script src=\"https://unpkg.com/bootstrap@4/dist/js/bootstrap.bundle.min.js\" crossorigin=\"anonymous\"></script>\n  <script src=\"https://fengyuanchen.github.io/shared/google-analytics.js\" crossorigin=\"anonymous\"></script>\n  <script src=\"js/viewer.js\"></script>\n  <script src=\"js/main.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "docs/js/main.js",
    "content": "window.onload = function () {\n  'use strict';\n\n  var Viewer = window.Viewer;\n  var console = window.console || { log: function () {} };\n  var pictures = document.querySelector('.docs-pictures');\n  var toggles = document.querySelector('.docs-toggles');\n  var buttons = document.querySelector('.docs-buttons');\n  var options = {\n    // inline: true,\n    url: 'data-original',\n    ready: function (e) {\n      console.log(e.type);\n    },\n    show: function (e) {\n      console.log(e.type);\n    },\n    shown: function (e) {\n      console.log(e.type);\n    },\n    hide: function (e) {\n      console.log(e.type);\n    },\n    hidden: function (e) {\n      console.log(e.type);\n    },\n    view: function (e) {\n      console.log(e.type);\n    },\n    viewed: function (e) {\n      console.log(e.type);\n    },\n    move: function (e) {\n      console.log(e.type);\n    },\n    moved: function (e) {\n      console.log(e.type);\n    },\n    rotate: function (e) {\n      console.log(e.type);\n    },\n    rotated: function (e) {\n      console.log(e.type);\n    },\n    scale: function (e) {\n      console.log(e.type);\n    },\n    scaled: function (e) {\n      console.log(e.type);\n    },\n    zoom: function (e) {\n      console.log(e.type);\n    },\n    zoomed: function (e) {\n      console.log(e.type);\n    },\n    play: function (e) {\n      console.log(e.type);\n    },\n    stop: function (e) {\n      console.log(e.type);\n    }\n  };\n  var viewer = new Viewer(pictures, options);\n\n  function toggleButtons(mode) {\n    var targets;\n    var target;\n    var length;\n    var i;\n\n    if (/modal|inline|none/.test(mode)) {\n      targets = buttons.querySelectorAll('button[data-enable]');\n\n      for (i = 0, length = targets.length; i < length; i++) {\n        target = targets[i];\n        target.disabled = true;\n\n        if (String(target.getAttribute('data-enable')).indexOf(mode) > -1) {\n          target.disabled = false;\n        }\n      }\n    }\n  }\n\n  function addEventListener(element, type, handler) {\n    if (element.addEventListener) {\n      element.addEventListener(type, handler, false);\n    } else if (element.attachEvent) {\n      element.attachEvent('on' + type, handler);\n    }\n  }\n\n  toggleButtons(options.inline ? 'inline' : 'modal');\n\n  toggles.onchange = function (event) {\n    var e = event || window.event;\n    var input = e.target || e.srcElement;\n    var name;\n\n    if (viewer) {\n      name = input.getAttribute('name');\n      options[name] = name === 'inline' ? JSON.parse(input.getAttribute('data-value')) : input.checked;\n      viewer.destroy();\n      viewer = new Viewer(pictures, options);\n      toggleButtons(options.inline ? 'inline' : 'modal');\n    }\n  };\n\n  buttons.onclick = function (event) {\n    var e = event || window.event;\n    var button = e.target || e.srcElement;\n    var method = button.getAttribute('data-method');\n    var target = button.getAttribute('data-target');\n    var args = JSON.parse(button.getAttribute('data-arguments')) || [];\n\n    if (viewer && method) {\n      if (target) {\n        viewer[method](document.querySelector(target).value);\n      } else {\n        viewer[method](args[0], args[1]);\n      }\n\n      switch (method) {\n        case 'scaleX':\n        case 'scaleY':\n          args[0] = -args[0];\n          break;\n\n        case 'destroy':\n          viewer = null;\n          toggleButtons('none');\n          break;\n      }\n    }\n  };\n\n  $('[data-toggle=\"tooltip\"]').tooltip();\n};\n"
  },
  {
    "path": "docs/js/viewer.js",
    "content": "/*!\n * Viewer.js v1.11.7\n * https://fengyuanchen.github.io/viewerjs\n *\n * Copyright 2015-present Chen Fengyuan\n * Released under the MIT license\n *\n * Date: 2024-11-24T04:32:19.116Z\n */\n\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :\n  typeof define === 'function' && define.amd ? define(factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Viewer = factory());\n})(this, (function () { 'use strict';\n\n  function _classCallCheck(a, n) {\n    if (!(a instanceof n)) throw new TypeError(\"Cannot call a class as a function\");\n  }\n  function _defineProperties(e, r) {\n    for (var t = 0; t < r.length; t++) {\n      var o = r[t];\n      o.enumerable = o.enumerable || !1, o.configurable = !0, \"value\" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o);\n    }\n  }\n  function _createClass(e, r, t) {\n    return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, \"prototype\", {\n      writable: !1\n    }), e;\n  }\n  function _defineProperty(e, r, t) {\n    return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {\n      value: t,\n      enumerable: !0,\n      configurable: !0,\n      writable: !0\n    }) : e[r] = t, e;\n  }\n  function ownKeys(e, r) {\n    var t = Object.keys(e);\n    if (Object.getOwnPropertySymbols) {\n      var o = Object.getOwnPropertySymbols(e);\n      r && (o = o.filter(function (r) {\n        return Object.getOwnPropertyDescriptor(e, r).enumerable;\n      })), t.push.apply(t, o);\n    }\n    return t;\n  }\n  function _objectSpread2(e) {\n    for (var r = 1; r < arguments.length; r++) {\n      var t = null != arguments[r] ? arguments[r] : {};\n      r % 2 ? ownKeys(Object(t), !0).forEach(function (r) {\n        _defineProperty(e, r, t[r]);\n      }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {\n        Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));\n      });\n    }\n    return e;\n  }\n  function _toPrimitive(t, r) {\n    if (\"object\" != typeof t || !t) return t;\n    var e = t[Symbol.toPrimitive];\n    if (void 0 !== e) {\n      var i = e.call(t, r || \"default\");\n      if (\"object\" != typeof i) return i;\n      throw new TypeError(\"@@toPrimitive must return a primitive value.\");\n    }\n    return (\"string\" === r ? String : Number)(t);\n  }\n  function _toPropertyKey(t) {\n    var i = _toPrimitive(t, \"string\");\n    return \"symbol\" == typeof i ? i : i + \"\";\n  }\n  function _typeof(o) {\n    \"@babel/helpers - typeof\";\n\n    return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function (o) {\n      return typeof o;\n    } : function (o) {\n      return o && \"function\" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? \"symbol\" : typeof o;\n    }, _typeof(o);\n  }\n\n  var DEFAULTS = {\n    /**\n     * Enable a modal backdrop, specify `static` for a backdrop\n     * which doesn't close the modal on click.\n     * @type {boolean}\n     */\n    backdrop: true,\n    /**\n     * Show the button on the top-right of the viewer.\n     * @type {boolean}\n     */\n    button: true,\n    /**\n     * Show the navbar.\n     * @type {boolean | number}\n     */\n    navbar: true,\n    /**\n     * Specify the visibility and the content of the title.\n     * @type {boolean | number | Function | Array}\n     */\n    title: true,\n    /**\n     * Show the toolbar.\n     * @type {boolean | number | Object}\n     */\n    toolbar: true,\n    /**\n     * Custom class name(s) to add to the viewer's root element.\n     * @type {string}\n     */\n    className: '',\n    /**\n     * Define where to put the viewer in modal mode.\n     * @type {string | Element}\n     */\n    container: 'body',\n    /**\n     * Filter the images for viewing. Return true if the image is viewable.\n     * @type {Function}\n     */\n    filter: null,\n    /**\n     * Enable to request fullscreen when play.\n     * {@link https://developer.mozilla.org/en-US/docs/Web/API/FullscreenOptions}\n     * @type {boolean|FullscreenOptions}\n     */\n    fullscreen: true,\n    /**\n     * Define the extra attributes to inherit from the original image.\n     * @type {Array}\n     */\n    inheritedAttributes: ['crossOrigin', 'decoding', 'isMap', 'loading', 'referrerPolicy', 'sizes', 'srcset', 'useMap'],\n    /**\n     * Define the initial coverage of the viewing image.\n     * @type {number}\n     */\n    initialCoverage: 0.9,\n    /**\n     * Define the initial index of the image for viewing.\n     * @type {number}\n     */\n    initialViewIndex: 0,\n    /**\n     * Enable inline mode.\n     * @type {boolean}\n     */\n    inline: false,\n    /**\n     * The amount of time to delay between automatically cycling an image when playing.\n     * @type {number}\n     */\n    interval: 5000,\n    /**\n     * Enable keyboard support.\n     * @type {boolean}\n     */\n    keyboard: true,\n    /**\n     * Focus the viewer when initialized.\n     * @type {boolean}\n     */\n    focus: true,\n    /**\n     * Indicate if show a loading spinner when load image or not.\n     * @type {boolean}\n     */\n    loading: true,\n    /**\n     * Indicate if enable loop viewing or not.\n     * @type {boolean}\n     */\n    loop: true,\n    /**\n     * Min width of the viewer in inline mode.\n     * @type {number}\n     */\n    minWidth: 200,\n    /**\n     * Min height of the viewer in inline mode.\n     * @type {number}\n     */\n    minHeight: 100,\n    /**\n     * Enable to move the image.\n     * @type {boolean}\n     */\n    movable: true,\n    /**\n     * Enable to rotate the image.\n     * @type {boolean}\n     */\n    rotatable: true,\n    /**\n     * Enable to scale the image.\n     * @type {boolean}\n     */\n    scalable: true,\n    /**\n     * Enable to zoom the image.\n     * @type {boolean}\n     */\n    zoomable: true,\n    /**\n     * Enable to zoom the current image by dragging on the touch screen.\n     * @type {boolean}\n     */\n    zoomOnTouch: true,\n    /**\n     * Enable to zoom the image by wheeling mouse.\n     * @type {boolean}\n     */\n    zoomOnWheel: true,\n    /**\n     * Enable to slide to the next or previous image by swiping on the touch screen.\n     * @type {boolean}\n     */\n    slideOnTouch: true,\n    /**\n     * Indicate if toggle the image size between its natural size\n     * and initial size when double click on the image or not.\n     * @type {boolean}\n     */\n    toggleOnDblclick: true,\n    /**\n     * Show the tooltip with image ratio (percentage) when zoom in or zoom out.\n     * @type {boolean}\n     */\n    tooltip: true,\n    /**\n     * Enable CSS3 Transition for some special elements.\n     * @type {boolean}\n     */\n    transition: true,\n    /**\n     * Define the CSS `z-index` value of viewer in modal mode.\n     * @type {number}\n     */\n    zIndex: 2015,\n    /**\n     * Define the CSS `z-index` value of viewer in inline mode.\n     * @type {number}\n     */\n    zIndexInline: 0,\n    /**\n     * Define the ratio when zoom the image by wheeling mouse.\n     * @type {number}\n     */\n    zoomRatio: 0.1,\n    /**\n     * Define the min ratio of the image when zoom out.\n     * @type {number}\n     */\n    minZoomRatio: 0.01,\n    /**\n     * Define the max ratio of the image when zoom in.\n     * @type {number}\n     */\n    maxZoomRatio: 100,\n    /**\n     * Define where to get the original image URL for viewing.\n     * @type {string | Function}\n     */\n    url: 'src',\n    /**\n     * Event shortcuts.\n     * @type {Function}\n     */\n    ready: null,\n    show: null,\n    shown: null,\n    hide: null,\n    hidden: null,\n    view: null,\n    viewed: null,\n    move: null,\n    moved: null,\n    rotate: null,\n    rotated: null,\n    scale: null,\n    scaled: null,\n    zoom: null,\n    zoomed: null,\n    play: null,\n    stop: null\n  };\n\n  var TEMPLATE = '<div class=\"viewer-container\" tabindex=\"-1\" touch-action=\"none\">' + '<div class=\"viewer-canvas\"></div>' + '<div class=\"viewer-footer\">' + '<div class=\"viewer-title\"></div>' + '<div class=\"viewer-toolbar\"></div>' + '<div class=\"viewer-navbar\">' + '<ul class=\"viewer-list\" role=\"navigation\"></ul>' + '</div>' + '</div>' + '<div class=\"viewer-tooltip\" role=\"alert\" aria-hidden=\"true\"></div>' + '<div class=\"viewer-button\" data-viewer-action=\"mix\" role=\"button\"></div>' + '<div class=\"viewer-player\"></div>' + '</div>';\n\n  var IS_BROWSER = typeof window !== 'undefined' && typeof window.document !== 'undefined';\n  var WINDOW = IS_BROWSER ? window : {};\n  var IS_TOUCH_DEVICE = IS_BROWSER && WINDOW.document.documentElement ? 'ontouchstart' in WINDOW.document.documentElement : false;\n  var HAS_POINTER_EVENT = IS_BROWSER ? 'PointerEvent' in WINDOW : false;\n  var NAMESPACE = 'viewer';\n\n  // Actions\n  var ACTION_MOVE = 'move';\n  var ACTION_SWITCH = 'switch';\n  var ACTION_ZOOM = 'zoom';\n\n  // Classes\n  var CLASS_ACTIVE = \"\".concat(NAMESPACE, \"-active\");\n  var CLASS_CLOSE = \"\".concat(NAMESPACE, \"-close\");\n  var CLASS_FADE = \"\".concat(NAMESPACE, \"-fade\");\n  var CLASS_FIXED = \"\".concat(NAMESPACE, \"-fixed\");\n  var CLASS_FULLSCREEN = \"\".concat(NAMESPACE, \"-fullscreen\");\n  var CLASS_FULLSCREEN_EXIT = \"\".concat(NAMESPACE, \"-fullscreen-exit\");\n  var CLASS_HIDE = \"\".concat(NAMESPACE, \"-hide\");\n  var CLASS_HIDE_MD_DOWN = \"\".concat(NAMESPACE, \"-hide-md-down\");\n  var CLASS_HIDE_SM_DOWN = \"\".concat(NAMESPACE, \"-hide-sm-down\");\n  var CLASS_HIDE_XS_DOWN = \"\".concat(NAMESPACE, \"-hide-xs-down\");\n  var CLASS_IN = \"\".concat(NAMESPACE, \"-in\");\n  var CLASS_INVISIBLE = \"\".concat(NAMESPACE, \"-invisible\");\n  var CLASS_LOADING = \"\".concat(NAMESPACE, \"-loading\");\n  var CLASS_MOVE = \"\".concat(NAMESPACE, \"-move\");\n  var CLASS_OPEN = \"\".concat(NAMESPACE, \"-open\");\n  var CLASS_SHOW = \"\".concat(NAMESPACE, \"-show\");\n  var CLASS_TRANSITION = \"\".concat(NAMESPACE, \"-transition\");\n\n  // Native events\n  var EVENT_CLICK = 'click';\n  var EVENT_DBLCLICK = 'dblclick';\n  var EVENT_DRAG_START = 'dragstart';\n  var EVENT_FOCUSIN = 'focusin';\n  var EVENT_KEY_DOWN = 'keydown';\n  var EVENT_LOAD = 'load';\n  var EVENT_ERROR = 'error';\n  var EVENT_TOUCH_END = IS_TOUCH_DEVICE ? 'touchend touchcancel' : 'mouseup';\n  var EVENT_TOUCH_MOVE = IS_TOUCH_DEVICE ? 'touchmove' : 'mousemove';\n  var EVENT_TOUCH_START = IS_TOUCH_DEVICE ? 'touchstart' : 'mousedown';\n  var EVENT_POINTER_DOWN = HAS_POINTER_EVENT ? 'pointerdown' : EVENT_TOUCH_START;\n  var EVENT_POINTER_MOVE = HAS_POINTER_EVENT ? 'pointermove' : EVENT_TOUCH_MOVE;\n  var EVENT_POINTER_UP = HAS_POINTER_EVENT ? 'pointerup pointercancel' : EVENT_TOUCH_END;\n  var EVENT_RESIZE = 'resize';\n  var EVENT_TRANSITION_END = 'transitionend';\n  var EVENT_WHEEL = 'wheel';\n\n  // Custom events\n  var EVENT_READY = 'ready';\n  var EVENT_SHOW = 'show';\n  var EVENT_SHOWN = 'shown';\n  var EVENT_HIDE = 'hide';\n  var EVENT_HIDDEN = 'hidden';\n  var EVENT_VIEW = 'view';\n  var EVENT_VIEWED = 'viewed';\n  var EVENT_MOVE = 'move';\n  var EVENT_MOVED = 'moved';\n  var EVENT_ROTATE = 'rotate';\n  var EVENT_ROTATED = 'rotated';\n  var EVENT_SCALE = 'scale';\n  var EVENT_SCALED = 'scaled';\n  var EVENT_ZOOM = 'zoom';\n  var EVENT_ZOOMED = 'zoomed';\n  var EVENT_PLAY = 'play';\n  var EVENT_STOP = 'stop';\n\n  // Data keys\n  var DATA_ACTION = \"\".concat(NAMESPACE, \"Action\");\n\n  // RegExps\n  var REGEXP_SPACES = /\\s\\s*/;\n\n  // Misc\n  var BUTTONS = ['zoom-in', 'zoom-out', 'one-to-one', 'reset', 'prev', 'play', 'next', 'rotate-left', 'rotate-right', 'flip-horizontal', 'flip-vertical'];\n\n  /**\n   * Check if the given value is a string.\n   * @param {*} value - The value to check.\n   * @returns {boolean} Returns `true` if the given value is a string, else `false`.\n   */\n  function isString(value) {\n    return typeof value === 'string';\n  }\n\n  /**\n   * Check if the given value is not a number.\n   */\n  var isNaN = Number.isNaN || WINDOW.isNaN;\n\n  /**\n   * Check if the given value is a number.\n   * @param {*} value - The value to check.\n   * @returns {boolean} Returns `true` if the given value is a number, else `false`.\n   */\n  function isNumber(value) {\n    return typeof value === 'number' && !isNaN(value);\n  }\n\n  /**\n   * Check if the given value is undefined.\n   * @param {*} value - The value to check.\n   * @returns {boolean} Returns `true` if the given value is undefined, else `false`.\n   */\n  function isUndefined(value) {\n    return typeof value === 'undefined';\n  }\n\n  /**\n   * Check if the given value is an object.\n   * @param {*} value - The value to check.\n   * @returns {boolean} Returns `true` if the given value is an object, else `false`.\n   */\n  function isObject(value) {\n    return _typeof(value) === 'object' && value !== null;\n  }\n  var hasOwnProperty = Object.prototype.hasOwnProperty;\n\n  /**\n   * Check if the given value is a plain object.\n   * @param {*} value - The value to check.\n   * @returns {boolean} Returns `true` if the given value is a plain object, else `false`.\n   */\n  function isPlainObject(value) {\n    if (!isObject(value)) {\n      return false;\n    }\n    try {\n      var _constructor = value.constructor;\n      var prototype = _constructor.prototype;\n      return _constructor && prototype && hasOwnProperty.call(prototype, 'isPrototypeOf');\n    } catch (error) {\n      return false;\n    }\n  }\n\n  /**\n   * Check if the given value is a function.\n   * @param {*} value - The value to check.\n   * @returns {boolean} Returns `true` if the given value is a function, else `false`.\n   */\n  function isFunction(value) {\n    return typeof value === 'function';\n  }\n\n  /**\n   * Iterate the given data.\n   * @param {*} data - The data to iterate.\n   * @param {Function} callback - The process function for each element.\n   * @returns {*} The original data.\n   */\n  function forEach(data, callback) {\n    if (data && isFunction(callback)) {\n      if (Array.isArray(data) || isNumber(data.length) /* array-like */) {\n        var length = data.length;\n        var i;\n        for (i = 0; i < length; i += 1) {\n          if (callback.call(data, data[i], i, data) === false) {\n            break;\n          }\n        }\n      } else if (isObject(data)) {\n        Object.keys(data).forEach(function (key) {\n          callback.call(data, data[key], key, data);\n        });\n      }\n    }\n    return data;\n  }\n\n  /**\n   * Extend the given object.\n   * @param {*} obj - The object to be extended.\n   * @param {*} args - The rest objects which will be merged to the first object.\n   * @returns {Object} The extended object.\n   */\n  var assign = Object.assign || function assign(obj) {\n    for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {\n      args[_key - 1] = arguments[_key];\n    }\n    if (isObject(obj) && args.length > 0) {\n      args.forEach(function (arg) {\n        if (isObject(arg)) {\n          Object.keys(arg).forEach(function (key) {\n            obj[key] = arg[key];\n          });\n        }\n      });\n    }\n    return obj;\n  };\n  var REGEXP_SUFFIX = /^(?:width|height|left|top|marginLeft|marginTop)$/;\n\n  /**\n   * Apply styles to the given element.\n   * @param {Element} element - The target element.\n   * @param {Object} styles - The styles for applying.\n   */\n  function setStyle(element, styles) {\n    var style = element.style;\n    forEach(styles, function (value, property) {\n      if (REGEXP_SUFFIX.test(property) && isNumber(value)) {\n        value += 'px';\n      }\n      style[property] = value;\n    });\n  }\n\n  /**\n   * Escape a string for using in HTML.\n   * @param {String} value - The string to escape.\n   * @returns {String} Returns the escaped string.\n   */\n  function escapeHTMLEntities(value) {\n    return isString(value) ? value.replace(/&(?!amp;|quot;|#39;|lt;|gt;)/g, '&amp;').replace(/\"/g, '&quot;').replace(/'/g, '&#39;').replace(/</g, '&lt;').replace(/>/g, '&gt;') : value;\n  }\n\n  /**\n   * Check if the given element has a special class.\n   * @param {Element} element - The element to check.\n   * @param {string} value - The class to search.\n   * @returns {boolean} Returns `true` if the special class was found.\n   */\n  function hasClass(element, value) {\n    if (!element || !value) {\n      return false;\n    }\n    return element.classList ? element.classList.contains(value) : element.className.indexOf(value) > -1;\n  }\n\n  /**\n   * Add classes to the given element.\n   * @param {Element} element - The target element.\n   * @param {string} value - The classes to be added.\n   */\n  function addClass(element, value) {\n    if (!element || !value) {\n      return;\n    }\n    if (isNumber(element.length)) {\n      forEach(element, function (elem) {\n        addClass(elem, value);\n      });\n      return;\n    }\n    if (element.classList) {\n      element.classList.add(value);\n      return;\n    }\n    var className = element.className.trim();\n    if (!className) {\n      element.className = value;\n    } else if (className.indexOf(value) < 0) {\n      element.className = \"\".concat(className, \" \").concat(value);\n    }\n  }\n\n  /**\n   * Remove classes from the given element.\n   * @param {Element} element - The target element.\n   * @param {string} value - The classes to be removed.\n   */\n  function removeClass(element, value) {\n    if (!element || !value) {\n      return;\n    }\n    if (isNumber(element.length)) {\n      forEach(element, function (elem) {\n        removeClass(elem, value);\n      });\n      return;\n    }\n    if (element.classList) {\n      element.classList.remove(value);\n      return;\n    }\n    if (element.className.indexOf(value) >= 0) {\n      element.className = element.className.replace(value, '');\n    }\n  }\n\n  /**\n   * Add or remove classes from the given element.\n   * @param {Element} element - The target element.\n   * @param {string} value - The classes to be toggled.\n   * @param {boolean} added - Add only.\n   */\n  function toggleClass(element, value, added) {\n    if (!value) {\n      return;\n    }\n    if (isNumber(element.length)) {\n      forEach(element, function (elem) {\n        toggleClass(elem, value, added);\n      });\n      return;\n    }\n\n    // IE10-11 doesn't support the second parameter of `classList.toggle`\n    if (added) {\n      addClass(element, value);\n    } else {\n      removeClass(element, value);\n    }\n  }\n  var REGEXP_HYPHENATE = /([a-z\\d])([A-Z])/g;\n\n  /**\n   * Transform the given string from camelCase to kebab-case\n   * @param {string} value - The value to transform.\n   * @returns {string} The transformed value.\n   */\n  function hyphenate(value) {\n    return value.replace(REGEXP_HYPHENATE, '$1-$2').toLowerCase();\n  }\n\n  /**\n   * Get data from the given element.\n   * @param {Element} element - The target element.\n   * @param {string} name - The data key to get.\n   * @returns {string} The data value.\n   */\n  function getData(element, name) {\n    if (isObject(element[name])) {\n      return element[name];\n    }\n    if (element.dataset) {\n      return element.dataset[name];\n    }\n    return element.getAttribute(\"data-\".concat(hyphenate(name)));\n  }\n\n  /**\n   * Set data to the given element.\n   * @param {Element} element - The target element.\n   * @param {string} name - The data key to set.\n   * @param {string} data - The data value.\n   */\n  function setData(element, name, data) {\n    if (isObject(data)) {\n      element[name] = data;\n    } else if (element.dataset) {\n      element.dataset[name] = data;\n    } else {\n      element.setAttribute(\"data-\".concat(hyphenate(name)), data);\n    }\n  }\n  var onceSupported = function () {\n    var supported = false;\n    if (IS_BROWSER) {\n      var once = false;\n      var listener = function listener() {};\n      var options = Object.defineProperty({}, 'once', {\n        get: function get() {\n          supported = true;\n          return once;\n        },\n        /**\n         * This setter can fix a `TypeError` in strict mode\n         * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Getter_only}\n         * @param {boolean} value - The value to set\n         */\n        set: function set(value) {\n          once = value;\n        }\n      });\n      WINDOW.addEventListener('test', listener, options);\n      WINDOW.removeEventListener('test', listener, options);\n    }\n    return supported;\n  }();\n\n  /**\n   * Remove event listener from the target element.\n   * @param {Element} element - The event target.\n   * @param {string} type - The event type(s).\n   * @param {Function} listener - The event listener.\n   * @param {Object} options - The event options.\n   */\n  function removeListener(element, type, listener) {\n    var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};\n    var handler = listener;\n    type.trim().split(REGEXP_SPACES).forEach(function (event) {\n      if (!onceSupported) {\n        var listeners = element.listeners;\n        if (listeners && listeners[event] && listeners[event][listener]) {\n          handler = listeners[event][listener];\n          delete listeners[event][listener];\n          if (Object.keys(listeners[event]).length === 0) {\n            delete listeners[event];\n          }\n          if (Object.keys(listeners).length === 0) {\n            delete element.listeners;\n          }\n        }\n      }\n      element.removeEventListener(event, handler, options);\n    });\n  }\n\n  /**\n   * Add event listener to the target element.\n   * @param {Element} element - The event target.\n   * @param {string} type - The event type(s).\n   * @param {Function} listener - The event listener.\n   * @param {Object} options - The event options.\n   */\n  function addListener(element, type, listener) {\n    var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};\n    var _handler = listener;\n    type.trim().split(REGEXP_SPACES).forEach(function (event) {\n      if (options.once && !onceSupported) {\n        var _element$listeners = element.listeners,\n          listeners = _element$listeners === void 0 ? {} : _element$listeners;\n        _handler = function handler() {\n          delete listeners[event][listener];\n          element.removeEventListener(event, _handler, options);\n          for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {\n            args[_key2] = arguments[_key2];\n          }\n          listener.apply(element, args);\n        };\n        if (!listeners[event]) {\n          listeners[event] = {};\n        }\n        if (listeners[event][listener]) {\n          element.removeEventListener(event, listeners[event][listener], options);\n        }\n        listeners[event][listener] = _handler;\n        element.listeners = listeners;\n      }\n      element.addEventListener(event, _handler, options);\n    });\n  }\n\n  /**\n   * Dispatch event on the target element.\n   * @param {Element} element - The event target.\n   * @param {string} type - The event type(s).\n   * @param {Object} data - The additional event data.\n   * @param {Object} options - The additional event options.\n   * @returns {boolean} Indicate if the event is default prevented or not.\n   */\n  function dispatchEvent(element, type, data, options) {\n    var event;\n\n    // Event and CustomEvent on IE9-11 are global objects, not constructors\n    if (isFunction(Event) && isFunction(CustomEvent)) {\n      event = new CustomEvent(type, _objectSpread2({\n        bubbles: true,\n        cancelable: true,\n        detail: data\n      }, options));\n    } else {\n      event = document.createEvent('CustomEvent');\n      event.initCustomEvent(type, true, true, data);\n    }\n    return element.dispatchEvent(event);\n  }\n\n  /**\n   * Get the offset base on the document.\n   * @param {Element} element - The target element.\n   * @returns {Object} The offset data.\n   */\n  function getOffset(element) {\n    var box = element.getBoundingClientRect();\n    return {\n      left: box.left + (window.pageXOffset - document.documentElement.clientLeft),\n      top: box.top + (window.pageYOffset - document.documentElement.clientTop)\n    };\n  }\n\n  /**\n   * Get transforms base on the given object.\n   * @param {Object} obj - The target object.\n   * @returns {string} A string contains transform values.\n   */\n  function getTransforms(_ref) {\n    var rotate = _ref.rotate,\n      scaleX = _ref.scaleX,\n      scaleY = _ref.scaleY,\n      translateX = _ref.translateX,\n      translateY = _ref.translateY;\n    var values = [];\n    if (isNumber(translateX) && translateX !== 0) {\n      values.push(\"translateX(\".concat(translateX, \"px)\"));\n    }\n    if (isNumber(translateY) && translateY !== 0) {\n      values.push(\"translateY(\".concat(translateY, \"px)\"));\n    }\n\n    // Rotate should come first before scale to match orientation transform\n    if (isNumber(rotate) && rotate !== 0) {\n      values.push(\"rotate(\".concat(rotate, \"deg)\"));\n    }\n    if (isNumber(scaleX) && scaleX !== 1) {\n      values.push(\"scaleX(\".concat(scaleX, \")\"));\n    }\n    if (isNumber(scaleY) && scaleY !== 1) {\n      values.push(\"scaleY(\".concat(scaleY, \")\"));\n    }\n    var transform = values.length ? values.join(' ') : 'none';\n    return {\n      WebkitTransform: transform,\n      msTransform: transform,\n      transform: transform\n    };\n  }\n\n  /**\n   * Get an image name from an image url.\n   * @param {string} url - The target url.\n   * @example\n   * // picture.jpg\n   * getImageNameFromURL('https://domain.com/path/to/picture.jpg?size=1280×960')\n   * @returns {string} A string contains the image name.\n   */\n  function getImageNameFromURL(url) {\n    return isString(url) ? decodeURIComponent(url.replace(/^.*\\//, '').replace(/[?&#].*$/, '')) : '';\n  }\n  var IS_SAFARI = WINDOW.navigator && /Version\\/\\d+(\\.\\d+)+?\\s+Safari/i.test(WINDOW.navigator.userAgent);\n\n  /**\n   * Get an image's natural sizes.\n   * @param {string} image - The target image.\n   * @param {Object} options - The viewer options.\n   * @param {Function} callback - The callback function.\n   * @returns {HTMLImageElement} The new image.\n   */\n  function getImageNaturalSizes(image, options, callback) {\n    var newImage = document.createElement('img');\n\n    // Modern browsers (except Safari)\n    if (image.naturalWidth && !IS_SAFARI) {\n      callback(image.naturalWidth, image.naturalHeight);\n      return newImage;\n    }\n    var body = document.body || document.documentElement;\n    newImage.onload = function () {\n      callback(newImage.width, newImage.height);\n      if (!IS_SAFARI) {\n        body.removeChild(newImage);\n      }\n    };\n    forEach(options.inheritedAttributes, function (name) {\n      var value = image.getAttribute(name);\n      if (value !== null) {\n        newImage.setAttribute(name, value);\n      }\n    });\n    newImage.src = image.src;\n\n    // iOS Safari will convert the image automatically\n    // with its orientation once append it into DOM\n    if (!IS_SAFARI) {\n      newImage.style.cssText = 'left:0;' + 'max-height:none!important;' + 'max-width:none!important;' + 'min-height:0!important;' + 'min-width:0!important;' + 'opacity:0;' + 'position:absolute;' + 'top:0;' + 'z-index:-1;';\n      body.appendChild(newImage);\n    }\n    return newImage;\n  }\n\n  /**\n   * Get the related class name of a responsive type number.\n   * @param {string} type - The responsive type.\n   * @returns {string} The related class name.\n   */\n  function getResponsiveClass(type) {\n    switch (type) {\n      case 2:\n        return CLASS_HIDE_XS_DOWN;\n      case 3:\n        return CLASS_HIDE_SM_DOWN;\n      case 4:\n        return CLASS_HIDE_MD_DOWN;\n      default:\n        return '';\n    }\n  }\n\n  /**\n   * Get the max ratio of a group of pointers.\n   * @param {string} pointers - The target pointers.\n   * @returns {number} The result ratio.\n   */\n  function getMaxZoomRatio(pointers) {\n    var pointers2 = _objectSpread2({}, pointers);\n    var ratios = [];\n    forEach(pointers, function (pointer, pointerId) {\n      delete pointers2[pointerId];\n      forEach(pointers2, function (pointer2) {\n        var x1 = Math.abs(pointer.startX - pointer2.startX);\n        var y1 = Math.abs(pointer.startY - pointer2.startY);\n        var x2 = Math.abs(pointer.endX - pointer2.endX);\n        var y2 = Math.abs(pointer.endY - pointer2.endY);\n        var z1 = Math.sqrt(x1 * x1 + y1 * y1);\n        var z2 = Math.sqrt(x2 * x2 + y2 * y2);\n        var ratio = (z2 - z1) / z1;\n        ratios.push(ratio);\n      });\n    });\n    ratios.sort(function (a, b) {\n      return Math.abs(a) < Math.abs(b);\n    });\n    return ratios[0];\n  }\n\n  /**\n   * Get a pointer from an event object.\n   * @param {Object} event - The target event object.\n   * @param {boolean} endOnly - Indicates if only returns the end point coordinate or not.\n   * @returns {Object} The result pointer contains start and/or end point coordinates.\n   */\n  function getPointer(_ref2, endOnly) {\n    var pageX = _ref2.pageX,\n      pageY = _ref2.pageY;\n    var end = {\n      endX: pageX,\n      endY: pageY\n    };\n    return endOnly ? end : _objectSpread2({\n      timeStamp: Date.now(),\n      startX: pageX,\n      startY: pageY\n    }, end);\n  }\n\n  /**\n   * Get the center point coordinate of a group of pointers.\n   * @param {Object} pointers - The target pointers.\n   * @returns {Object} The center point coordinate.\n   */\n  function getPointersCenter(pointers) {\n    var pageX = 0;\n    var pageY = 0;\n    var count = 0;\n    forEach(pointers, function (_ref3) {\n      var startX = _ref3.startX,\n        startY = _ref3.startY;\n      pageX += startX;\n      pageY += startY;\n      count += 1;\n    });\n    pageX /= count;\n    pageY /= count;\n    return {\n      pageX: pageX,\n      pageY: pageY\n    };\n  }\n\n  var render = {\n    render: function render() {\n      this.initContainer();\n      this.initViewer();\n      this.initList();\n      this.renderViewer();\n    },\n    initBody: function initBody() {\n      var ownerDocument = this.element.ownerDocument;\n      var body = ownerDocument.body || ownerDocument.documentElement;\n      this.body = body;\n      this.scrollbarWidth = window.innerWidth - ownerDocument.documentElement.clientWidth;\n      this.initialBodyPaddingRight = body.style.paddingRight;\n      this.initialBodyComputedPaddingRight = window.getComputedStyle(body).paddingRight;\n    },\n    initContainer: function initContainer() {\n      this.containerData = {\n        width: window.innerWidth,\n        height: window.innerHeight\n      };\n    },\n    initViewer: function initViewer() {\n      var options = this.options,\n        parent = this.parent;\n      var viewerData;\n      if (options.inline) {\n        viewerData = {\n          width: Math.max(parent.offsetWidth, options.minWidth),\n          height: Math.max(parent.offsetHeight, options.minHeight)\n        };\n        this.parentData = viewerData;\n      }\n      if (this.fulled || !viewerData) {\n        viewerData = this.containerData;\n      }\n      this.viewerData = assign({}, viewerData);\n    },\n    renderViewer: function renderViewer() {\n      if (this.options.inline && !this.fulled) {\n        setStyle(this.viewer, this.viewerData);\n      }\n    },\n    initList: function initList() {\n      var _this = this;\n      var element = this.element,\n        options = this.options,\n        list = this.list;\n      var items = [];\n\n      // initList may be called in this.update, so should keep idempotent\n      list.innerHTML = '';\n      forEach(this.images, function (image, index) {\n        var src = image.src;\n        var alt = image.alt || getImageNameFromURL(src);\n        var url = _this.getImageURL(image);\n        if (src || url) {\n          var item = document.createElement('li');\n          var img = document.createElement('img');\n          forEach(options.inheritedAttributes, function (name) {\n            var value = image.getAttribute(name);\n            if (value !== null) {\n              img.setAttribute(name, value);\n            }\n          });\n          if (options.navbar) {\n            img.src = src || url;\n          }\n          img.alt = alt;\n          img.setAttribute('data-original-url', url || src);\n          item.setAttribute('data-index', index);\n          item.setAttribute('data-viewer-action', 'view');\n          item.setAttribute('role', 'button');\n          if (options.keyboard) {\n            item.setAttribute('tabindex', 0);\n          }\n          item.appendChild(img);\n          list.appendChild(item);\n          items.push(item);\n        }\n      });\n      this.items = items;\n      forEach(items, function (item) {\n        var image = item.firstElementChild;\n        var onLoad;\n        var onError;\n        setData(image, 'filled', true);\n        if (options.loading) {\n          addClass(item, CLASS_LOADING);\n        }\n        addListener(image, EVENT_LOAD, onLoad = function onLoad(event) {\n          removeListener(image, EVENT_ERROR, onError);\n          if (options.loading) {\n            removeClass(item, CLASS_LOADING);\n          }\n          _this.loadImage(event);\n        }, {\n          once: true\n        });\n        addListener(image, EVENT_ERROR, onError = function onError() {\n          removeListener(image, EVENT_LOAD, onLoad);\n          if (options.loading) {\n            removeClass(item, CLASS_LOADING);\n          }\n        }, {\n          once: true\n        });\n      });\n      if (options.transition) {\n        addListener(element, EVENT_VIEWED, function () {\n          addClass(list, CLASS_TRANSITION);\n        }, {\n          once: true\n        });\n      }\n    },\n    renderList: function renderList() {\n      var index = this.index;\n      var item = this.items[index];\n      if (!item) {\n        return;\n      }\n      var next = item.nextElementSibling;\n      var gutter = parseInt(window.getComputedStyle(next || item).marginLeft, 10);\n      var offsetWidth = item.offsetWidth;\n      var outerWidth = offsetWidth + gutter;\n\n      // Place the active item in the center of the screen\n      setStyle(this.list, assign({\n        width: outerWidth * this.length - gutter\n      }, getTransforms({\n        translateX: (this.viewerData.width - offsetWidth) / 2 - outerWidth * index\n      })));\n    },\n    resetList: function resetList() {\n      var list = this.list;\n      list.innerHTML = '';\n      removeClass(list, CLASS_TRANSITION);\n      setStyle(list, getTransforms({\n        translateX: 0\n      }));\n    },\n    initImage: function initImage(done) {\n      var _this2 = this;\n      var options = this.options,\n        image = this.image,\n        viewerData = this.viewerData;\n      var footerHeight = this.footer.offsetHeight;\n      var viewerWidth = viewerData.width;\n      var viewerHeight = Math.max(viewerData.height - footerHeight, footerHeight);\n      var oldImageData = this.imageData || {};\n      var sizingImage;\n      this.imageInitializing = {\n        abort: function abort() {\n          sizingImage.onload = null;\n        }\n      };\n      sizingImage = getImageNaturalSizes(image, options, function (naturalWidth, naturalHeight) {\n        var aspectRatio = naturalWidth / naturalHeight;\n        var initialCoverage = Math.max(0, Math.min(1, options.initialCoverage));\n        var width = viewerWidth;\n        var height = viewerHeight;\n        _this2.imageInitializing = false;\n        if (viewerHeight * aspectRatio > viewerWidth) {\n          height = viewerWidth / aspectRatio;\n        } else {\n          width = viewerHeight * aspectRatio;\n        }\n        initialCoverage = isNumber(initialCoverage) ? initialCoverage : 0.9;\n        width = Math.min(width * initialCoverage, naturalWidth);\n        height = Math.min(height * initialCoverage, naturalHeight);\n        var left = (viewerWidth - width) / 2;\n        var top = (viewerHeight - height) / 2;\n        var imageData = {\n          left: left,\n          top: top,\n          x: left,\n          y: top,\n          width: width,\n          height: height,\n          oldRatio: 1,\n          ratio: width / naturalWidth,\n          aspectRatio: aspectRatio,\n          naturalWidth: naturalWidth,\n          naturalHeight: naturalHeight\n        };\n        var initialImageData = assign({}, imageData);\n        if (options.rotatable) {\n          imageData.rotate = oldImageData.rotate || 0;\n          initialImageData.rotate = 0;\n        }\n        if (options.scalable) {\n          imageData.scaleX = oldImageData.scaleX || 1;\n          imageData.scaleY = oldImageData.scaleY || 1;\n          initialImageData.scaleX = 1;\n          initialImageData.scaleY = 1;\n        }\n        _this2.imageData = imageData;\n        _this2.initialImageData = initialImageData;\n        if (done) {\n          done();\n        }\n      });\n    },\n    renderImage: function renderImage(done) {\n      var _this3 = this;\n      var image = this.image,\n        imageData = this.imageData;\n      setStyle(image, assign({\n        width: imageData.width,\n        height: imageData.height,\n        // XXX: Not to use translateX/Y to avoid image shaking when zooming\n        marginLeft: imageData.x,\n        marginTop: imageData.y\n      }, getTransforms(imageData)));\n      if (done) {\n        if ((this.viewing || this.moving || this.rotating || this.scaling || this.zooming) && this.options.transition && hasClass(image, CLASS_TRANSITION)) {\n          var onTransitionEnd = function onTransitionEnd() {\n            _this3.imageRendering = false;\n            done();\n          };\n          this.imageRendering = {\n            abort: function abort() {\n              removeListener(image, EVENT_TRANSITION_END, onTransitionEnd);\n            }\n          };\n          addListener(image, EVENT_TRANSITION_END, onTransitionEnd, {\n            once: true\n          });\n        } else {\n          done();\n        }\n      }\n    },\n    resetImage: function resetImage() {\n      var image = this.image;\n      if (image) {\n        if (this.viewing) {\n          this.viewing.abort();\n        }\n        image.parentNode.removeChild(image);\n        this.image = null;\n        this.title.innerHTML = '';\n      }\n    }\n  };\n\n  var events = {\n    bind: function bind() {\n      var options = this.options,\n        viewer = this.viewer,\n        canvas = this.canvas;\n      var document = this.element.ownerDocument;\n      addListener(viewer, EVENT_CLICK, this.onClick = this.click.bind(this));\n      addListener(viewer, EVENT_DRAG_START, this.onDragStart = this.dragstart.bind(this));\n      addListener(canvas, EVENT_POINTER_DOWN, this.onPointerDown = this.pointerdown.bind(this));\n      addListener(document, EVENT_POINTER_MOVE, this.onPointerMove = this.pointermove.bind(this));\n      addListener(document, EVENT_POINTER_UP, this.onPointerUp = this.pointerup.bind(this));\n      addListener(document, EVENT_KEY_DOWN, this.onKeyDown = this.keydown.bind(this));\n      addListener(window, EVENT_RESIZE, this.onResize = this.resize.bind(this));\n      if (options.zoomable && options.zoomOnWheel) {\n        addListener(viewer, EVENT_WHEEL, this.onWheel = this.wheel.bind(this), {\n          passive: false,\n          capture: true\n        });\n      }\n      if (options.toggleOnDblclick) {\n        addListener(canvas, EVENT_DBLCLICK, this.onDblclick = this.dblclick.bind(this));\n      }\n    },\n    unbind: function unbind() {\n      var options = this.options,\n        viewer = this.viewer,\n        canvas = this.canvas;\n      var document = this.element.ownerDocument;\n      removeListener(viewer, EVENT_CLICK, this.onClick);\n      removeListener(viewer, EVENT_DRAG_START, this.onDragStart);\n      removeListener(canvas, EVENT_POINTER_DOWN, this.onPointerDown);\n      removeListener(document, EVENT_POINTER_MOVE, this.onPointerMove);\n      removeListener(document, EVENT_POINTER_UP, this.onPointerUp);\n      removeListener(document, EVENT_KEY_DOWN, this.onKeyDown);\n      removeListener(window, EVENT_RESIZE, this.onResize);\n      if (options.zoomable && options.zoomOnWheel) {\n        removeListener(viewer, EVENT_WHEEL, this.onWheel, {\n          passive: false,\n          capture: true\n        });\n      }\n      if (options.toggleOnDblclick) {\n        removeListener(canvas, EVENT_DBLCLICK, this.onDblclick);\n      }\n    }\n  };\n\n  var handlers = {\n    click: function click(event) {\n      var options = this.options,\n        imageData = this.imageData;\n      var target = event.target;\n      var action = getData(target, DATA_ACTION);\n      if (!action && target.localName === 'img' && target.parentElement.localName === 'li') {\n        target = target.parentElement;\n        action = getData(target, DATA_ACTION);\n      }\n\n      // Cancel the emulated click when the native click event was triggered.\n      if (IS_TOUCH_DEVICE && event.isTrusted && target === this.canvas) {\n        clearTimeout(this.clickCanvasTimeout);\n      }\n      switch (action) {\n        case 'mix':\n          if (this.played) {\n            this.stop();\n          } else if (options.inline) {\n            if (this.fulled) {\n              this.exit();\n            } else {\n              this.full();\n            }\n          } else {\n            this.hide();\n          }\n          break;\n        case 'hide':\n          if (!this.pointerMoved) {\n            this.hide();\n          }\n          break;\n        case 'view':\n          this.view(getData(target, 'index'));\n          break;\n        case 'zoom-in':\n          this.zoom(0.1, true);\n          break;\n        case 'zoom-out':\n          this.zoom(-0.1, true);\n          break;\n        case 'one-to-one':\n          this.toggle();\n          break;\n        case 'reset':\n          this.reset();\n          break;\n        case 'prev':\n          this.prev(options.loop);\n          break;\n        case 'play':\n          this.play(options.fullscreen);\n          break;\n        case 'next':\n          this.next(options.loop);\n          break;\n        case 'rotate-left':\n          this.rotate(-90);\n          break;\n        case 'rotate-right':\n          this.rotate(90);\n          break;\n        case 'flip-horizontal':\n          this.scaleX(-imageData.scaleX || -1);\n          break;\n        case 'flip-vertical':\n          this.scaleY(-imageData.scaleY || -1);\n          break;\n        default:\n          if (this.played) {\n            this.stop();\n          }\n      }\n    },\n    dblclick: function dblclick(event) {\n      event.preventDefault();\n      if (this.viewed && event.target === this.image) {\n        // Cancel the emulated double click when the native dblclick event was triggered.\n        if (IS_TOUCH_DEVICE && event.isTrusted) {\n          clearTimeout(this.doubleClickImageTimeout);\n        }\n\n        // XXX: No pageX/Y properties in custom event, fallback to the original event.\n        this.toggle(event.isTrusted ? event : event.detail && event.detail.originalEvent);\n      }\n    },\n    load: function load() {\n      var _this = this;\n      if (this.timeout) {\n        clearTimeout(this.timeout);\n        this.timeout = false;\n      }\n      var element = this.element,\n        options = this.options,\n        image = this.image,\n        index = this.index,\n        viewerData = this.viewerData;\n      removeClass(image, CLASS_INVISIBLE);\n      if (options.loading) {\n        removeClass(this.canvas, CLASS_LOADING);\n      }\n      image.style.cssText = 'height:0;' + \"margin-left:\".concat(viewerData.width / 2, \"px;\") + \"margin-top:\".concat(viewerData.height / 2, \"px;\") + 'max-width:none!important;' + 'position:relative;' + 'width:0;';\n      this.initImage(function () {\n        toggleClass(image, CLASS_MOVE, options.movable);\n        toggleClass(image, CLASS_TRANSITION, options.transition);\n        _this.renderImage(function () {\n          _this.viewed = true;\n          _this.viewing = false;\n          if (isFunction(options.viewed)) {\n            addListener(element, EVENT_VIEWED, options.viewed, {\n              once: true\n            });\n          }\n          dispatchEvent(element, EVENT_VIEWED, {\n            originalImage: _this.images[index],\n            index: index,\n            image: image\n          }, {\n            cancelable: false\n          });\n        });\n      });\n    },\n    loadImage: function loadImage(event) {\n      var image = event.target;\n      var parent = image.parentNode;\n      var parentWidth = parent.offsetWidth || 30;\n      var parentHeight = parent.offsetHeight || 50;\n      var filled = !!getData(image, 'filled');\n      getImageNaturalSizes(image, this.options, function (naturalWidth, naturalHeight) {\n        var aspectRatio = naturalWidth / naturalHeight;\n        var width = parentWidth;\n        var height = parentHeight;\n        if (parentHeight * aspectRatio > parentWidth) {\n          if (filled) {\n            width = parentHeight * aspectRatio;\n          } else {\n            height = parentWidth / aspectRatio;\n          }\n        } else if (filled) {\n          height = parentWidth / aspectRatio;\n        } else {\n          width = parentHeight * aspectRatio;\n        }\n        setStyle(image, assign({\n          width: width,\n          height: height\n        }, getTransforms({\n          translateX: (parentWidth - width) / 2,\n          translateY: (parentHeight - height) / 2\n        })));\n      });\n    },\n    keydown: function keydown(event) {\n      var options = this.options;\n      if (!options.keyboard) {\n        return;\n      }\n      var keyCode = event.keyCode || event.which || event.charCode;\n      switch (keyCode) {\n        // Enter\n        case 13:\n          if (this.viewer.contains(event.target)) {\n            this.click(event);\n          }\n          break;\n      }\n      if (!this.fulled) {\n        return;\n      }\n      switch (keyCode) {\n        // Escape\n        case 27:\n          if (this.played) {\n            this.stop();\n          } else if (options.inline) {\n            if (this.fulled) {\n              this.exit();\n            }\n          } else {\n            this.hide();\n          }\n          break;\n\n        // Space\n        case 32:\n          if (this.played) {\n            this.stop();\n          }\n          break;\n\n        // ArrowLeft\n        case 37:\n          if (this.played && this.playing) {\n            this.playing.prev();\n          } else {\n            this.prev(options.loop);\n          }\n          break;\n\n        // ArrowUp\n        case 38:\n          // Prevent scroll on Firefox\n          event.preventDefault();\n\n          // Zoom in\n          this.zoom(options.zoomRatio, true);\n          break;\n\n        // ArrowRight\n        case 39:\n          if (this.played && this.playing) {\n            this.playing.next();\n          } else {\n            this.next(options.loop);\n          }\n          break;\n\n        // ArrowDown\n        case 40:\n          // Prevent scroll on Firefox\n          event.preventDefault();\n\n          // Zoom out\n          this.zoom(-options.zoomRatio, true);\n          break;\n\n        // Ctrl + 0\n        case 48:\n        // Fall through\n\n        // Ctrl + 1\n        // eslint-disable-next-line no-fallthrough\n        case 49:\n          if (event.ctrlKey) {\n            event.preventDefault();\n            this.toggle();\n          }\n          break;\n      }\n    },\n    dragstart: function dragstart(event) {\n      if (event.target.localName === 'img') {\n        event.preventDefault();\n      }\n    },\n    pointerdown: function pointerdown(event) {\n      var options = this.options,\n        pointers = this.pointers;\n      var buttons = event.buttons,\n        button = event.button;\n      this.pointerMoved = false;\n      if (!this.viewed || this.showing || this.viewing || this.hiding\n\n      // Handle mouse event and pointer event and ignore touch event\n      || (event.type === 'mousedown' || event.type === 'pointerdown' && event.pointerType === 'mouse') && (\n      // No primary button (Usually the left button)\n      isNumber(buttons) && buttons !== 1 || isNumber(button) && button !== 0\n\n      // Open context menu\n      || event.ctrlKey)) {\n        return;\n      }\n\n      // Prevent default behaviours as page zooming in touch devices.\n      event.preventDefault();\n      if (event.changedTouches) {\n        forEach(event.changedTouches, function (touch) {\n          pointers[touch.identifier] = getPointer(touch);\n        });\n      } else {\n        pointers[event.pointerId || 0] = getPointer(event);\n      }\n      var action = options.movable ? ACTION_MOVE : false;\n      if (options.zoomOnTouch && options.zoomable && Object.keys(pointers).length > 1) {\n        action = ACTION_ZOOM;\n      } else if (options.slideOnTouch && (event.pointerType === 'touch' || event.type === 'touchstart') && this.isSwitchable()) {\n        action = ACTION_SWITCH;\n      }\n      if (options.transition && (action === ACTION_MOVE || action === ACTION_ZOOM)) {\n        removeClass(this.image, CLASS_TRANSITION);\n      }\n      this.action = action;\n    },\n    pointermove: function pointermove(event) {\n      var pointers = this.pointers,\n        action = this.action;\n      if (!this.viewed || !action) {\n        return;\n      }\n      event.preventDefault();\n      if (event.changedTouches) {\n        forEach(event.changedTouches, function (touch) {\n          assign(pointers[touch.identifier] || {}, getPointer(touch, true));\n        });\n      } else {\n        assign(pointers[event.pointerId || 0] || {}, getPointer(event, true));\n      }\n      this.change(event);\n    },\n    pointerup: function pointerup(event) {\n      var _this2 = this;\n      var options = this.options,\n        action = this.action,\n        pointers = this.pointers;\n      var pointer;\n      if (event.changedTouches) {\n        forEach(event.changedTouches, function (touch) {\n          pointer = pointers[touch.identifier];\n          delete pointers[touch.identifier];\n        });\n      } else {\n        pointer = pointers[event.pointerId || 0];\n        delete pointers[event.pointerId || 0];\n      }\n      if (!action) {\n        return;\n      }\n      event.preventDefault();\n      if (options.transition && (action === ACTION_MOVE || action === ACTION_ZOOM)) {\n        addClass(this.image, CLASS_TRANSITION);\n      }\n      this.action = false;\n\n      // Emulate click and double click in touch devices to support backdrop and image zooming (#210).\n      if (IS_TOUCH_DEVICE && action !== ACTION_ZOOM && pointer && Date.now() - pointer.timeStamp < 500) {\n        clearTimeout(this.clickCanvasTimeout);\n        clearTimeout(this.doubleClickImageTimeout);\n        if (options.toggleOnDblclick && this.viewed && event.target === this.image) {\n          if (this.imageClicked) {\n            this.imageClicked = false;\n\n            // This timeout will be cleared later when a native dblclick event is triggering\n            this.doubleClickImageTimeout = setTimeout(function () {\n              dispatchEvent(_this2.image, EVENT_DBLCLICK, {\n                originalEvent: event\n              });\n            }, 50);\n          } else {\n            this.imageClicked = true;\n\n            // The default timing of a double click in Windows is 500 ms\n            this.doubleClickImageTimeout = setTimeout(function () {\n              _this2.imageClicked = false;\n            }, 500);\n          }\n        } else {\n          this.imageClicked = false;\n          if (options.backdrop && options.backdrop !== 'static' && event.target === this.canvas) {\n            // This timeout will be cleared later when a native click event is triggering\n            this.clickCanvasTimeout = setTimeout(function () {\n              dispatchEvent(_this2.canvas, EVENT_CLICK, {\n                originalEvent: event\n              });\n            }, 50);\n          }\n        }\n      }\n    },\n    resize: function resize() {\n      var _this3 = this;\n      if (!this.isShown || this.hiding) {\n        return;\n      }\n      if (this.fulled) {\n        this.close();\n        this.initBody();\n        this.open();\n      }\n      this.initContainer();\n      this.initViewer();\n      this.renderViewer();\n      this.renderList();\n      if (this.viewed) {\n        this.initImage(function () {\n          _this3.renderImage();\n        });\n      }\n      if (this.played) {\n        if (this.options.fullscreen && this.fulled && !(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement)) {\n          this.stop();\n          return;\n        }\n        forEach(this.player.getElementsByTagName('img'), function (image) {\n          addListener(image, EVENT_LOAD, _this3.loadImage.bind(_this3), {\n            once: true\n          });\n          dispatchEvent(image, EVENT_LOAD);\n        });\n      }\n    },\n    wheel: function wheel(event) {\n      var _this4 = this;\n      if (!this.viewed) {\n        return;\n      }\n      event.preventDefault();\n\n      // Limit wheel speed to prevent zoom too fast\n      if (this.wheeling) {\n        return;\n      }\n      this.wheeling = true;\n      setTimeout(function () {\n        _this4.wheeling = false;\n      }, 50);\n      var ratio = Number(this.options.zoomRatio) || 0.1;\n      var delta = 1;\n      if (event.deltaY) {\n        delta = event.deltaY > 0 ? 1 : -1;\n      } else if (event.wheelDelta) {\n        delta = -event.wheelDelta / 120;\n      } else if (event.detail) {\n        delta = event.detail > 0 ? 1 : -1;\n      }\n      this.zoom(-delta * ratio, true, null, event);\n    }\n  };\n\n  var methods = {\n    /** Show the viewer (only available in modal mode)\n     * @param {boolean} [immediate=false] - Indicates if show the viewer immediately or not.\n     * @returns {Viewer} this\n     */\n    show: function show() {\n      var immediate = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;\n      var element = this.element,\n        options = this.options;\n      if (options.inline || this.showing || this.isShown || this.showing) {\n        return this;\n      }\n      if (!this.ready) {\n        this.build();\n        if (this.ready) {\n          this.show(immediate);\n        }\n        return this;\n      }\n      if (isFunction(options.show)) {\n        addListener(element, EVENT_SHOW, options.show, {\n          once: true\n        });\n      }\n      if (dispatchEvent(element, EVENT_SHOW) === false || !this.ready) {\n        return this;\n      }\n      if (this.hiding) {\n        this.transitioning.abort();\n      }\n      this.showing = true;\n      this.open();\n      var viewer = this.viewer;\n      removeClass(viewer, CLASS_HIDE);\n      viewer.setAttribute('role', 'dialog');\n      viewer.setAttribute('aria-labelledby', this.title.id);\n      viewer.setAttribute('aria-modal', true);\n      viewer.removeAttribute('aria-hidden');\n      if (options.transition && !immediate) {\n        var shown = this.shown.bind(this);\n        this.transitioning = {\n          abort: function abort() {\n            removeListener(viewer, EVENT_TRANSITION_END, shown);\n            removeClass(viewer, CLASS_IN);\n          }\n        };\n        addClass(viewer, CLASS_TRANSITION);\n\n        // Force reflow to enable CSS3 transition\n        viewer.initialOffsetWidth = viewer.offsetWidth;\n        addListener(viewer, EVENT_TRANSITION_END, shown, {\n          once: true\n        });\n        addClass(viewer, CLASS_IN);\n      } else {\n        addClass(viewer, CLASS_IN);\n        this.shown();\n      }\n      return this;\n    },\n    /**\n     * Hide the viewer (only available in modal mode)\n     * @param {boolean} [immediate=false] - Indicates if hide the viewer immediately or not.\n     * @returns {Viewer} this\n     */\n    hide: function hide() {\n      var _this = this;\n      var immediate = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;\n      var element = this.element,\n        options = this.options;\n      if (options.inline || this.hiding || !(this.isShown || this.showing)) {\n        return this;\n      }\n      if (isFunction(options.hide)) {\n        addListener(element, EVENT_HIDE, options.hide, {\n          once: true\n        });\n      }\n      if (dispatchEvent(element, EVENT_HIDE) === false) {\n        return this;\n      }\n      if (this.showing) {\n        this.transitioning.abort();\n      }\n      this.hiding = true;\n      if (this.played) {\n        this.stop();\n      } else if (this.viewing) {\n        this.viewing.abort();\n      }\n      var viewer = this.viewer,\n        image = this.image;\n      var hideImmediately = function hideImmediately() {\n        removeClass(viewer, CLASS_IN);\n        _this.hidden();\n      };\n      if (options.transition && !immediate) {\n        var _onViewerTransitionEnd = function onViewerTransitionEnd(event) {\n          // Ignore all propagating `transitionend` events (#275).\n          if (event && event.target === viewer) {\n            removeListener(viewer, EVENT_TRANSITION_END, _onViewerTransitionEnd);\n            _this.hidden();\n          }\n        };\n        var onImageTransitionEnd = function onImageTransitionEnd() {\n          // In case of show the viewer by `viewer.show(true)` previously (#407).\n          if (hasClass(viewer, CLASS_TRANSITION)) {\n            addListener(viewer, EVENT_TRANSITION_END, _onViewerTransitionEnd);\n            removeClass(viewer, CLASS_IN);\n          } else {\n            hideImmediately();\n          }\n        };\n        this.transitioning = {\n          abort: function abort() {\n            if (_this.viewed && hasClass(image, CLASS_TRANSITION)) {\n              removeListener(image, EVENT_TRANSITION_END, onImageTransitionEnd);\n            } else if (hasClass(viewer, CLASS_TRANSITION)) {\n              removeListener(viewer, EVENT_TRANSITION_END, _onViewerTransitionEnd);\n            }\n          }\n        };\n\n        // In case of hiding the viewer when holding on the image (#255),\n        // note that the `CLASS_TRANSITION` class will be removed on pointer down.\n        if (this.viewed && hasClass(image, CLASS_TRANSITION)) {\n          addListener(image, EVENT_TRANSITION_END, onImageTransitionEnd, {\n            once: true\n          });\n          this.zoomTo(0, false, null, null, true);\n        } else {\n          onImageTransitionEnd();\n        }\n      } else {\n        hideImmediately();\n      }\n      return this;\n    },\n    /**\n     * View one of the images with image's index\n     * @param {number} index - The index of the image to view.\n     * @returns {Viewer} this\n     */\n    view: function view() {\n      var _this2 = this;\n      var index = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.options.initialViewIndex;\n      index = Number(index) || 0;\n      if (this.hiding || this.played || index < 0 || index >= this.length || this.viewed && index === this.index) {\n        return this;\n      }\n      if (!this.isShown) {\n        this.index = index;\n        return this.show();\n      }\n      if (this.viewing) {\n        this.viewing.abort();\n      }\n      var element = this.element,\n        options = this.options,\n        title = this.title,\n        canvas = this.canvas;\n      var item = this.items[index];\n      var img = item.querySelector('img');\n      var url = getData(img, 'originalUrl');\n      var alt = img.getAttribute('alt');\n      var image = document.createElement('img');\n      forEach(options.inheritedAttributes, function (name) {\n        var value = img.getAttribute(name);\n        if (value !== null) {\n          image.setAttribute(name, value);\n        }\n      });\n      image.src = url;\n      image.alt = alt;\n      if (isFunction(options.view)) {\n        addListener(element, EVENT_VIEW, options.view, {\n          once: true\n        });\n      }\n      if (dispatchEvent(element, EVENT_VIEW, {\n        originalImage: this.images[index],\n        index: index,\n        image: image\n      }) === false || !this.isShown || this.hiding || this.played) {\n        return this;\n      }\n      var activeItem = this.items[this.index];\n      if (activeItem) {\n        removeClass(activeItem, CLASS_ACTIVE);\n        activeItem.removeAttribute('aria-selected');\n      }\n      addClass(item, CLASS_ACTIVE);\n      item.setAttribute('aria-selected', true);\n      if (options.focus) {\n        item.focus();\n      }\n      this.image = image;\n      this.viewed = false;\n      this.index = index;\n      this.imageData = {};\n      addClass(image, CLASS_INVISIBLE);\n      if (options.loading) {\n        addClass(canvas, CLASS_LOADING);\n      }\n      canvas.innerHTML = '';\n      canvas.appendChild(image);\n\n      // Center current item\n      this.renderList();\n\n      // Clear title\n      title.innerHTML = '';\n\n      // Generate title after viewed\n      var onViewed = function onViewed() {\n        var imageData = _this2.imageData;\n        var render = Array.isArray(options.title) ? options.title[1] : options.title;\n        title.innerHTML = escapeHTMLEntities(isFunction(render) ? render.call(_this2, image, imageData) : \"\".concat(alt, \" (\").concat(imageData.naturalWidth, \" \\xD7 \").concat(imageData.naturalHeight, \")\"));\n      };\n      var onLoad;\n      var onError;\n      addListener(element, EVENT_VIEWED, onViewed, {\n        once: true\n      });\n      this.viewing = {\n        abort: function abort() {\n          removeListener(element, EVENT_VIEWED, onViewed);\n          if (image.complete) {\n            if (_this2.imageRendering) {\n              _this2.imageRendering.abort();\n            } else if (_this2.imageInitializing) {\n              _this2.imageInitializing.abort();\n            }\n          } else {\n            // Cancel download to save bandwidth.\n            image.src = '';\n            removeListener(image, EVENT_LOAD, onLoad);\n            if (_this2.timeout) {\n              clearTimeout(_this2.timeout);\n            }\n          }\n        }\n      };\n      if (image.complete) {\n        this.load();\n      } else {\n        addListener(image, EVENT_LOAD, onLoad = function onLoad() {\n          removeListener(image, EVENT_ERROR, onError);\n          _this2.load();\n        }, {\n          once: true\n        });\n        addListener(image, EVENT_ERROR, onError = function onError() {\n          removeListener(image, EVENT_LOAD, onLoad);\n          if (_this2.timeout) {\n            clearTimeout(_this2.timeout);\n            _this2.timeout = false;\n          }\n          removeClass(image, CLASS_INVISIBLE);\n          if (options.loading) {\n            removeClass(_this2.canvas, CLASS_LOADING);\n          }\n        }, {\n          once: true\n        });\n        if (this.timeout) {\n          clearTimeout(this.timeout);\n        }\n\n        // Make the image visible if it fails to load within 1s\n        this.timeout = setTimeout(function () {\n          removeClass(image, CLASS_INVISIBLE);\n          _this2.timeout = false;\n        }, 1000);\n      }\n      return this;\n    },\n    /**\n     * View the previous image\n     * @param {boolean} [loop=false] - Indicate if view the last one\n     * when it is the first one at present.\n     * @returns {Viewer} this\n     */\n    prev: function prev() {\n      var loop = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;\n      var index = this.index - 1;\n      if (index < 0) {\n        index = loop ? this.length - 1 : 0;\n      }\n      this.view(index);\n      return this;\n    },\n    /**\n     * View the next image\n     * @param {boolean} [loop=false] - Indicate if view the first one\n     * when it is the last one at present.\n     * @returns {Viewer} this\n     */\n    next: function next() {\n      var loop = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;\n      var maxIndex = this.length - 1;\n      var index = this.index + 1;\n      if (index > maxIndex) {\n        index = loop ? 0 : maxIndex;\n      }\n      this.view(index);\n      return this;\n    },\n    /**\n     * Move the image with relative offsets.\n     * @param {number} x - The moving distance in the horizontal direction.\n     * @param {number} [y=x] The moving distance in the vertical direction.\n     * @returns {Viewer} this\n     */\n    move: function move(x) {\n      var y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : x;\n      var imageData = this.imageData;\n      this.moveTo(isUndefined(x) ? x : imageData.x + Number(x), isUndefined(y) ? y : imageData.y + Number(y));\n      return this;\n    },\n    /**\n     * Move the image to an absolute point.\n     * @param {number} x - The new position in the horizontal direction.\n     * @param {number} [y=x] - The new position in the vertical direction.\n     * @param {Event} [_originalEvent=null] - The original event if any.\n     * @returns {Viewer} this\n     */\n    moveTo: function moveTo(x) {\n      var _this3 = this;\n      var y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : x;\n      var _originalEvent = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;\n      var element = this.element,\n        options = this.options,\n        imageData = this.imageData;\n      x = Number(x);\n      y = Number(y);\n      if (this.viewed && !this.played && options.movable) {\n        var oldX = imageData.x;\n        var oldY = imageData.y;\n        var changed = false;\n        if (isNumber(x)) {\n          changed = true;\n        } else {\n          x = oldX;\n        }\n        if (isNumber(y)) {\n          changed = true;\n        } else {\n          y = oldY;\n        }\n        if (changed) {\n          if (isFunction(options.move)) {\n            addListener(element, EVENT_MOVE, options.move, {\n              once: true\n            });\n          }\n          if (dispatchEvent(element, EVENT_MOVE, {\n            x: x,\n            y: y,\n            oldX: oldX,\n            oldY: oldY,\n            originalEvent: _originalEvent\n          }) === false) {\n            return this;\n          }\n          imageData.x = x;\n          imageData.y = y;\n          imageData.left = x;\n          imageData.top = y;\n          this.moving = true;\n          this.renderImage(function () {\n            _this3.moving = false;\n            if (isFunction(options.moved)) {\n              addListener(element, EVENT_MOVED, options.moved, {\n                once: true\n              });\n            }\n            dispatchEvent(element, EVENT_MOVED, {\n              x: x,\n              y: y,\n              oldX: oldX,\n              oldY: oldY,\n              originalEvent: _originalEvent\n            }, {\n              cancelable: false\n            });\n          });\n        }\n      }\n      return this;\n    },\n    /**\n     * Rotate the image with a relative degree.\n     * @param {number} degree - The rotate degree.\n     * @returns {Viewer} this\n     */\n    rotate: function rotate(degree) {\n      this.rotateTo((this.imageData.rotate || 0) + Number(degree));\n      return this;\n    },\n    /**\n     * Rotate the image to an absolute degree.\n     * @param {number} degree - The rotate degree.\n     * @returns {Viewer} this\n     */\n    rotateTo: function rotateTo(degree) {\n      var _this4 = this;\n      var element = this.element,\n        options = this.options,\n        imageData = this.imageData;\n      degree = Number(degree);\n      if (isNumber(degree) && this.viewed && !this.played && options.rotatable) {\n        var oldDegree = imageData.rotate;\n        if (isFunction(options.rotate)) {\n          addListener(element, EVENT_ROTATE, options.rotate, {\n            once: true\n          });\n        }\n        if (dispatchEvent(element, EVENT_ROTATE, {\n          degree: degree,\n          oldDegree: oldDegree\n        }) === false) {\n          return this;\n        }\n        imageData.rotate = degree;\n        this.rotating = true;\n        this.renderImage(function () {\n          _this4.rotating = false;\n          if (isFunction(options.rotated)) {\n            addListener(element, EVENT_ROTATED, options.rotated, {\n              once: true\n            });\n          }\n          dispatchEvent(element, EVENT_ROTATED, {\n            degree: degree,\n            oldDegree: oldDegree\n          }, {\n            cancelable: false\n          });\n        });\n      }\n      return this;\n    },\n    /**\n     * Scale the image on the x-axis.\n     * @param {number} scaleX - The scale ratio on the x-axis.\n     * @returns {Viewer} this\n     */\n    scaleX: function scaleX(_scaleX) {\n      this.scale(_scaleX, this.imageData.scaleY);\n      return this;\n    },\n    /**\n     * Scale the image on the y-axis.\n     * @param {number} scaleY - The scale ratio on the y-axis.\n     * @returns {Viewer} this\n     */\n    scaleY: function scaleY(_scaleY) {\n      this.scale(this.imageData.scaleX, _scaleY);\n      return this;\n    },\n    /**\n     * Scale the image.\n     * @param {number} scaleX - The scale ratio on the x-axis.\n     * @param {number} [scaleY=scaleX] - The scale ratio on the y-axis.\n     * @returns {Viewer} this\n     */\n    scale: function scale(scaleX) {\n      var _this5 = this;\n      var scaleY = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : scaleX;\n      var element = this.element,\n        options = this.options,\n        imageData = this.imageData;\n      scaleX = Number(scaleX);\n      scaleY = Number(scaleY);\n      if (this.viewed && !this.played && options.scalable) {\n        var oldScaleX = imageData.scaleX;\n        var oldScaleY = imageData.scaleY;\n        var changed = false;\n        if (isNumber(scaleX)) {\n          changed = true;\n        } else {\n          scaleX = oldScaleX;\n        }\n        if (isNumber(scaleY)) {\n          changed = true;\n        } else {\n          scaleY = oldScaleY;\n        }\n        if (changed) {\n          if (isFunction(options.scale)) {\n            addListener(element, EVENT_SCALE, options.scale, {\n              once: true\n            });\n          }\n          if (dispatchEvent(element, EVENT_SCALE, {\n            scaleX: scaleX,\n            scaleY: scaleY,\n            oldScaleX: oldScaleX,\n            oldScaleY: oldScaleY\n          }) === false) {\n            return this;\n          }\n          imageData.scaleX = scaleX;\n          imageData.scaleY = scaleY;\n          this.scaling = true;\n          this.renderImage(function () {\n            _this5.scaling = false;\n            if (isFunction(options.scaled)) {\n              addListener(element, EVENT_SCALED, options.scaled, {\n                once: true\n              });\n            }\n            dispatchEvent(element, EVENT_SCALED, {\n              scaleX: scaleX,\n              scaleY: scaleY,\n              oldScaleX: oldScaleX,\n              oldScaleY: oldScaleY\n            }, {\n              cancelable: false\n            });\n          });\n        }\n      }\n      return this;\n    },\n    /**\n     * Zoom the image with a relative ratio.\n     * @param {number} ratio - The target ratio.\n     * @param {boolean} [showTooltip=false] - Indicates whether to show the tooltip.\n     * @param {Object} [pivot] - The pivot point coordinate for zooming.\n     * @param {Event} [_originalEvent=null] - The original event if any.\n     * @returns {Viewer} this\n     */\n    zoom: function zoom(ratio) {\n      var showTooltip = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;\n      var pivot = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;\n      var _originalEvent = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;\n      var imageData = this.imageData;\n      ratio = Number(ratio);\n      if (ratio < 0) {\n        ratio = 1 / (1 - ratio);\n      } else {\n        ratio = 1 + ratio;\n      }\n      this.zoomTo(imageData.width * ratio / imageData.naturalWidth, showTooltip, pivot, _originalEvent);\n      return this;\n    },\n    /**\n     * Zoom the image to an absolute ratio.\n     * @param {number} ratio - The target ratio.\n     * @param {boolean} [showTooltip] - Indicates whether to show the tooltip.\n     * @param {Object} [pivot] - The pivot point coordinate for zooming.\n     * @param {Event} [_originalEvent=null] - The original event if any.\n     * @param {Event} [_zoomable=false] - Indicates if the current zoom is available or not.\n     * @returns {Viewer} this\n     */\n    zoomTo: function zoomTo(ratio) {\n      var _this6 = this;\n      var showTooltip = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;\n      var pivot = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;\n      var _originalEvent = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;\n      var _zoomable = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;\n      var element = this.element,\n        options = this.options,\n        pointers = this.pointers,\n        imageData = this.imageData;\n      var x = imageData.x,\n        y = imageData.y,\n        width = imageData.width,\n        height = imageData.height,\n        naturalWidth = imageData.naturalWidth,\n        naturalHeight = imageData.naturalHeight;\n      ratio = Math.max(0, ratio);\n      if (isNumber(ratio) && this.viewed && !this.played && (_zoomable || options.zoomable)) {\n        if (!_zoomable) {\n          var minZoomRatio = Math.max(0.01, options.minZoomRatio);\n          var maxZoomRatio = Math.min(100, options.maxZoomRatio);\n          ratio = Math.min(Math.max(ratio, minZoomRatio), maxZoomRatio);\n        }\n        if (_originalEvent) {\n          switch (_originalEvent.type) {\n            case 'wheel':\n              if (options.zoomRatio >= 0.055 && ratio > 0.95 && ratio < 1.05) {\n                ratio = 1;\n              }\n              break;\n            case 'pointermove':\n            case 'touchmove':\n            case 'mousemove':\n              if (ratio > 0.99 && ratio < 1.01) {\n                ratio = 1;\n              }\n              break;\n          }\n        }\n        var newWidth = naturalWidth * ratio;\n        var newHeight = naturalHeight * ratio;\n        var offsetWidth = newWidth - width;\n        var offsetHeight = newHeight - height;\n        var oldRatio = imageData.ratio;\n        if (isFunction(options.zoom)) {\n          addListener(element, EVENT_ZOOM, options.zoom, {\n            once: true\n          });\n        }\n        if (dispatchEvent(element, EVENT_ZOOM, {\n          ratio: ratio,\n          oldRatio: oldRatio,\n          originalEvent: _originalEvent\n        }) === false) {\n          return this;\n        }\n        this.zooming = true;\n        if (_originalEvent) {\n          var offset = getOffset(this.viewer);\n          var center = pointers && Object.keys(pointers).length > 0 ? getPointersCenter(pointers) : {\n            pageX: _originalEvent.pageX,\n            pageY: _originalEvent.pageY\n          };\n\n          // Zoom from the triggering point of the event\n          imageData.x -= offsetWidth * ((center.pageX - offset.left - x) / width);\n          imageData.y -= offsetHeight * ((center.pageY - offset.top - y) / height);\n        } else if (isPlainObject(pivot) && isNumber(pivot.x) && isNumber(pivot.y)) {\n          imageData.x -= offsetWidth * ((pivot.x - x) / width);\n          imageData.y -= offsetHeight * ((pivot.y - y) / height);\n        } else {\n          // Zoom from the center of the image\n          imageData.x -= offsetWidth / 2;\n          imageData.y -= offsetHeight / 2;\n        }\n        imageData.left = imageData.x;\n        imageData.top = imageData.y;\n        imageData.width = newWidth;\n        imageData.height = newHeight;\n        imageData.oldRatio = oldRatio;\n        imageData.ratio = ratio;\n        this.renderImage(function () {\n          _this6.zooming = false;\n          if (isFunction(options.zoomed)) {\n            addListener(element, EVENT_ZOOMED, options.zoomed, {\n              once: true\n            });\n          }\n          dispatchEvent(element, EVENT_ZOOMED, {\n            ratio: ratio,\n            oldRatio: oldRatio,\n            originalEvent: _originalEvent\n          }, {\n            cancelable: false\n          });\n        });\n        if (showTooltip) {\n          this.tooltip();\n        }\n      }\n      return this;\n    },\n    /**\n     * Play the images\n     * @param {boolean|FullscreenOptions} [fullscreen=false] - Indicate if request fullscreen or not.\n     * @returns {Viewer} this\n     */\n    play: function play() {\n      var _this7 = this;\n      var fullscreen = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;\n      if (!this.isShown || this.played) {\n        return this;\n      }\n      var element = this.element,\n        options = this.options;\n      if (isFunction(options.play)) {\n        addListener(element, EVENT_PLAY, options.play, {\n          once: true\n        });\n      }\n      if (dispatchEvent(element, EVENT_PLAY) === false) {\n        return this;\n      }\n      var player = this.player;\n      var onLoad = this.loadImage.bind(this);\n      var list = [];\n      var total = 0;\n      var index = 0;\n      this.played = true;\n      this.onLoadWhenPlay = onLoad;\n      if (fullscreen) {\n        this.requestFullscreen(fullscreen);\n      }\n      addClass(player, CLASS_SHOW);\n      forEach(this.items, function (item, i) {\n        var img = item.querySelector('img');\n        var image = document.createElement('img');\n        image.src = getData(img, 'originalUrl');\n        image.alt = img.getAttribute('alt');\n        image.referrerPolicy = img.referrerPolicy;\n        total += 1;\n        addClass(image, CLASS_FADE);\n        toggleClass(image, CLASS_TRANSITION, options.transition);\n        if (hasClass(item, CLASS_ACTIVE)) {\n          addClass(image, CLASS_IN);\n          index = i;\n        }\n        list.push(image);\n        addListener(image, EVENT_LOAD, onLoad, {\n          once: true\n        });\n        player.appendChild(image);\n      });\n      if (isNumber(options.interval) && options.interval > 0) {\n        var _prev = function prev() {\n          clearTimeout(_this7.playing.timeout);\n          removeClass(list[index], CLASS_IN);\n          index -= 1;\n          index = index >= 0 ? index : total - 1;\n          addClass(list[index], CLASS_IN);\n          _this7.playing.timeout = setTimeout(_prev, options.interval);\n        };\n        var _next = function next() {\n          clearTimeout(_this7.playing.timeout);\n          removeClass(list[index], CLASS_IN);\n          index += 1;\n          index = index < total ? index : 0;\n          addClass(list[index], CLASS_IN);\n          _this7.playing.timeout = setTimeout(_next, options.interval);\n        };\n        if (total > 1) {\n          this.playing = {\n            prev: _prev,\n            next: _next,\n            timeout: setTimeout(_next, options.interval)\n          };\n        }\n      }\n      return this;\n    },\n    // Stop play\n    stop: function stop() {\n      var _this8 = this;\n      if (!this.played) {\n        return this;\n      }\n      var element = this.element,\n        options = this.options;\n      if (isFunction(options.stop)) {\n        addListener(element, EVENT_STOP, options.stop, {\n          once: true\n        });\n      }\n      if (dispatchEvent(element, EVENT_STOP) === false) {\n        return this;\n      }\n      var player = this.player;\n      clearTimeout(this.playing.timeout);\n      this.playing = false;\n      this.played = false;\n      forEach(player.getElementsByTagName('img'), function (image) {\n        removeListener(image, EVENT_LOAD, _this8.onLoadWhenPlay);\n      });\n      removeClass(player, CLASS_SHOW);\n      player.innerHTML = '';\n      this.exitFullscreen();\n      return this;\n    },\n    // Enter modal mode (only available in inline mode)\n    full: function full() {\n      var _this9 = this;\n      var options = this.options,\n        viewer = this.viewer,\n        image = this.image,\n        list = this.list;\n      if (!this.isShown || this.played || this.fulled || !options.inline) {\n        return this;\n      }\n      this.fulled = true;\n      this.open();\n      addClass(this.button, CLASS_FULLSCREEN_EXIT);\n      if (options.transition) {\n        removeClass(list, CLASS_TRANSITION);\n        if (this.viewed) {\n          removeClass(image, CLASS_TRANSITION);\n        }\n      }\n      addClass(viewer, CLASS_FIXED);\n      viewer.setAttribute('role', 'dialog');\n      viewer.setAttribute('aria-labelledby', this.title.id);\n      viewer.setAttribute('aria-modal', true);\n      viewer.removeAttribute('style');\n      setStyle(viewer, {\n        zIndex: options.zIndex\n      });\n      if (options.focus) {\n        this.enforceFocus();\n      }\n      this.initContainer();\n      this.viewerData = assign({}, this.containerData);\n      this.renderList();\n      if (this.viewed) {\n        this.initImage(function () {\n          _this9.renderImage(function () {\n            if (options.transition) {\n              setTimeout(function () {\n                addClass(image, CLASS_TRANSITION);\n                addClass(list, CLASS_TRANSITION);\n              }, 0);\n            }\n          });\n        });\n      }\n      return this;\n    },\n    // Exit modal mode (only available in inline mode)\n    exit: function exit() {\n      var _this10 = this;\n      var options = this.options,\n        viewer = this.viewer,\n        image = this.image,\n        list = this.list;\n      if (!this.isShown || this.played || !this.fulled || !options.inline) {\n        return this;\n      }\n      this.fulled = false;\n      this.close();\n      removeClass(this.button, CLASS_FULLSCREEN_EXIT);\n      if (options.transition) {\n        removeClass(list, CLASS_TRANSITION);\n        if (this.viewed) {\n          removeClass(image, CLASS_TRANSITION);\n        }\n      }\n      if (options.focus) {\n        this.clearEnforceFocus();\n      }\n      viewer.removeAttribute('role');\n      viewer.removeAttribute('aria-labelledby');\n      viewer.removeAttribute('aria-modal');\n      removeClass(viewer, CLASS_FIXED);\n      setStyle(viewer, {\n        zIndex: options.zIndexInline\n      });\n      this.viewerData = assign({}, this.parentData);\n      this.renderViewer();\n      this.renderList();\n      if (this.viewed) {\n        this.initImage(function () {\n          _this10.renderImage(function () {\n            if (options.transition) {\n              setTimeout(function () {\n                addClass(image, CLASS_TRANSITION);\n                addClass(list, CLASS_TRANSITION);\n              }, 0);\n            }\n          });\n        });\n      }\n      return this;\n    },\n    // Show the current ratio of the image with percentage\n    tooltip: function tooltip() {\n      var _this11 = this;\n      var options = this.options,\n        tooltipBox = this.tooltipBox,\n        imageData = this.imageData;\n      if (!this.viewed || this.played || !options.tooltip) {\n        return this;\n      }\n      tooltipBox.textContent = \"\".concat(Math.round(imageData.ratio * 100), \"%\");\n      if (!this.tooltipping) {\n        if (options.transition) {\n          if (this.fading) {\n            dispatchEvent(tooltipBox, EVENT_TRANSITION_END);\n          }\n          addClass(tooltipBox, CLASS_SHOW);\n          addClass(tooltipBox, CLASS_FADE);\n          addClass(tooltipBox, CLASS_TRANSITION);\n          tooltipBox.removeAttribute('aria-hidden');\n\n          // Force reflow to enable CSS3 transition\n          tooltipBox.initialOffsetWidth = tooltipBox.offsetWidth;\n          addClass(tooltipBox, CLASS_IN);\n        } else {\n          addClass(tooltipBox, CLASS_SHOW);\n          tooltipBox.removeAttribute('aria-hidden');\n        }\n      } else {\n        clearTimeout(this.tooltipping);\n      }\n      this.tooltipping = setTimeout(function () {\n        if (options.transition) {\n          addListener(tooltipBox, EVENT_TRANSITION_END, function () {\n            removeClass(tooltipBox, CLASS_SHOW);\n            removeClass(tooltipBox, CLASS_FADE);\n            removeClass(tooltipBox, CLASS_TRANSITION);\n            tooltipBox.setAttribute('aria-hidden', true);\n            _this11.fading = false;\n          }, {\n            once: true\n          });\n          removeClass(tooltipBox, CLASS_IN);\n          _this11.fading = true;\n        } else {\n          removeClass(tooltipBox, CLASS_SHOW);\n          tooltipBox.setAttribute('aria-hidden', true);\n        }\n        _this11.tooltipping = false;\n      }, 1000);\n      return this;\n    },\n    /**\n     * Toggle the image size between its current size and natural size\n     * @param {Event} [_originalEvent=null] - The original event if any.\n     * @returns {Viewer} this\n     */\n    toggle: function toggle() {\n      var _originalEvent = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;\n      if (this.imageData.ratio === 1) {\n        this.zoomTo(this.imageData.oldRatio, true, null, _originalEvent);\n      } else {\n        this.zoomTo(1, true, null, _originalEvent);\n      }\n      return this;\n    },\n    // Reset the image to its initial state\n    reset: function reset() {\n      if (this.viewed && !this.played) {\n        this.imageData = assign({}, this.initialImageData);\n        this.renderImage();\n      }\n      return this;\n    },\n    // Update viewer when images changed\n    update: function update() {\n      var _this12 = this;\n      var element = this.element,\n        options = this.options,\n        isImg = this.isImg;\n\n      // Destroy viewer if the target image was deleted\n      if (isImg && !element.parentNode) {\n        return this.destroy();\n      }\n      var images = [];\n      forEach(isImg ? [element] : element.querySelectorAll('img'), function (image) {\n        if (isFunction(options.filter)) {\n          if (options.filter.call(_this12, image)) {\n            images.push(image);\n          }\n        } else if (_this12.getImageURL(image)) {\n          images.push(image);\n        }\n      });\n      if (!images.length) {\n        return this;\n      }\n      this.images = images;\n      this.length = images.length;\n      if (this.ready) {\n        var changedIndexes = [];\n        forEach(this.items, function (item, i) {\n          var img = item.querySelector('img');\n          var image = images[i];\n          if (image && img) {\n            if (image.src !== img.src\n\n            // Title changed (#408)\n            || image.alt !== img.alt) {\n              changedIndexes.push(i);\n            }\n          } else {\n            changedIndexes.push(i);\n          }\n        });\n        setStyle(this.list, {\n          width: 'auto'\n        });\n        this.initList();\n        if (this.isShown) {\n          if (this.length) {\n            if (this.viewed) {\n              var changedIndex = changedIndexes.indexOf(this.index);\n              if (changedIndex >= 0) {\n                this.viewed = false;\n                this.view(Math.max(Math.min(this.index - changedIndex, this.length - 1), 0));\n              } else {\n                var activeItem = this.items[this.index];\n\n                // Reactivate the current viewing item after reset the list.\n                addClass(activeItem, CLASS_ACTIVE);\n                activeItem.setAttribute('aria-selected', true);\n              }\n            }\n          } else {\n            this.image = null;\n            this.viewed = false;\n            this.index = 0;\n            this.imageData = {};\n            this.canvas.innerHTML = '';\n            this.title.innerHTML = '';\n          }\n        }\n      } else {\n        this.build();\n      }\n      return this;\n    },\n    // Destroy the viewer\n    destroy: function destroy() {\n      var element = this.element,\n        options = this.options;\n      if (!element[NAMESPACE]) {\n        return this;\n      }\n      this.destroyed = true;\n      if (this.ready) {\n        if (this.played) {\n          this.stop();\n        }\n        if (options.inline) {\n          if (this.fulled) {\n            this.exit();\n          }\n          this.unbind();\n        } else if (this.isShown) {\n          if (this.viewing) {\n            if (this.imageRendering) {\n              this.imageRendering.abort();\n            } else if (this.imageInitializing) {\n              this.imageInitializing.abort();\n            }\n          }\n          if (this.hiding) {\n            this.transitioning.abort();\n          }\n          this.hidden();\n        } else if (this.showing) {\n          this.transitioning.abort();\n          this.hidden();\n        }\n        this.ready = false;\n        this.viewer.parentNode.removeChild(this.viewer);\n      } else if (options.inline) {\n        if (this.delaying) {\n          this.delaying.abort();\n        } else if (this.initializing) {\n          this.initializing.abort();\n        }\n      }\n      if (!options.inline) {\n        removeListener(element, EVENT_CLICK, this.onStart);\n      }\n      element[NAMESPACE] = undefined;\n      return this;\n    }\n  };\n\n  var others = {\n    getImageURL: function getImageURL(image) {\n      var url = this.options.url;\n      if (isString(url)) {\n        url = image.getAttribute(url);\n      } else if (isFunction(url)) {\n        url = url.call(this, image);\n      } else {\n        url = '';\n      }\n      return url;\n    },\n    enforceFocus: function enforceFocus() {\n      var _this = this;\n      this.clearEnforceFocus();\n      addListener(document, EVENT_FOCUSIN, this.onFocusin = function (event) {\n        var viewer = _this.viewer;\n        var target = event.target;\n        if (target === document || target === viewer || viewer.contains(target)) {\n          return;\n        }\n        while (target) {\n          // Avoid conflicts with other modals (#474, #540)\n          if (target.getAttribute('tabindex') !== null || target.getAttribute('aria-modal') === 'true') {\n            return;\n          }\n          target = target.parentElement;\n        }\n        viewer.focus();\n      });\n    },\n    clearEnforceFocus: function clearEnforceFocus() {\n      if (this.onFocusin) {\n        removeListener(document, EVENT_FOCUSIN, this.onFocusin);\n        this.onFocusin = null;\n      }\n    },\n    open: function open() {\n      var body = this.body;\n      addClass(body, CLASS_OPEN);\n      if (this.scrollbarWidth > 0) {\n        body.style.paddingRight = \"\".concat(this.scrollbarWidth + (parseFloat(this.initialBodyComputedPaddingRight) || 0), \"px\");\n      }\n    },\n    close: function close() {\n      var body = this.body;\n      removeClass(body, CLASS_OPEN);\n      if (this.scrollbarWidth > 0) {\n        body.style.paddingRight = this.initialBodyPaddingRight;\n      }\n    },\n    shown: function shown() {\n      var element = this.element,\n        options = this.options,\n        viewer = this.viewer;\n      this.fulled = true;\n      this.isShown = true;\n      this.render();\n      this.bind();\n      this.showing = false;\n      if (options.focus) {\n        viewer.focus();\n        this.enforceFocus();\n      }\n      if (isFunction(options.shown)) {\n        addListener(element, EVENT_SHOWN, options.shown, {\n          once: true\n        });\n      }\n      if (dispatchEvent(element, EVENT_SHOWN) === false) {\n        return;\n      }\n      if (this.ready && this.isShown && !this.hiding) {\n        this.view(this.index);\n      }\n    },\n    hidden: function hidden() {\n      var element = this.element,\n        options = this.options,\n        viewer = this.viewer;\n      if (options.fucus) {\n        this.clearEnforceFocus();\n      }\n      this.close();\n      this.unbind();\n      addClass(viewer, CLASS_HIDE);\n      viewer.removeAttribute('role');\n      viewer.removeAttribute('aria-labelledby');\n      viewer.removeAttribute('aria-modal');\n      viewer.setAttribute('aria-hidden', true);\n      this.resetList();\n      this.resetImage();\n      this.fulled = false;\n      this.viewed = false;\n      this.isShown = false;\n      this.hiding = false;\n      if (!this.destroyed) {\n        if (isFunction(options.hidden)) {\n          addListener(element, EVENT_HIDDEN, options.hidden, {\n            once: true\n          });\n        }\n        dispatchEvent(element, EVENT_HIDDEN, null, {\n          cancelable: false\n        });\n      }\n    },\n    requestFullscreen: function requestFullscreen(options) {\n      var document = this.element.ownerDocument;\n      if (this.fulled && !(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement)) {\n        var documentElement = document.documentElement;\n\n        // Element.requestFullscreen()\n        if (documentElement.requestFullscreen) {\n          // Avoid TypeError when convert `options` to dictionary\n          if (isPlainObject(options)) {\n            documentElement.requestFullscreen(options);\n          } else {\n            documentElement.requestFullscreen();\n          }\n        } else if (documentElement.webkitRequestFullscreen) {\n          documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);\n        } else if (documentElement.mozRequestFullScreen) {\n          documentElement.mozRequestFullScreen();\n        } else if (documentElement.msRequestFullscreen) {\n          documentElement.msRequestFullscreen();\n        }\n      }\n    },\n    exitFullscreen: function exitFullscreen() {\n      var document = this.element.ownerDocument;\n      if (this.fulled && (document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement)) {\n        // Document.exitFullscreen()\n        if (document.exitFullscreen) {\n          document.exitFullscreen();\n        } else if (document.webkitExitFullscreen) {\n          document.webkitExitFullscreen();\n        } else if (document.mozCancelFullScreen) {\n          document.mozCancelFullScreen();\n        } else if (document.msExitFullscreen) {\n          document.msExitFullscreen();\n        }\n      }\n    },\n    change: function change(event) {\n      var options = this.options,\n        pointers = this.pointers;\n      var pointer = pointers[Object.keys(pointers)[0]];\n\n      // In the case of the `pointers` object is empty (#421)\n      if (!pointer) {\n        return;\n      }\n      var offsetX = pointer.endX - pointer.startX;\n      var offsetY = pointer.endY - pointer.startY;\n      switch (this.action) {\n        // Move the current image\n        case ACTION_MOVE:\n          if (offsetX !== 0 || offsetY !== 0) {\n            this.pointerMoved = true;\n            this.move(offsetX, offsetY, event);\n          }\n          break;\n\n        // Zoom the current image\n        case ACTION_ZOOM:\n          this.zoom(getMaxZoomRatio(pointers), false, null, event);\n          break;\n        case ACTION_SWITCH:\n          {\n            this.action = 'switched';\n            var absoluteOffsetX = Math.abs(offsetX);\n            if (absoluteOffsetX > 1 && absoluteOffsetX > Math.abs(offsetY)) {\n              // Empty `pointers` as `touchend` event will not be fired after swiped in iOS browsers.\n              this.pointers = {};\n              if (offsetX > 1) {\n                this.prev(options.loop);\n              } else if (offsetX < -1) {\n                this.next(options.loop);\n              }\n            }\n            break;\n          }\n      }\n\n      // Override\n      forEach(pointers, function (p) {\n        p.startX = p.endX;\n        p.startY = p.endY;\n      });\n    },\n    isSwitchable: function isSwitchable() {\n      var imageData = this.imageData,\n        viewerData = this.viewerData;\n      return this.length > 1 && imageData.x >= 0 && imageData.y >= 0 && imageData.width <= viewerData.width && imageData.height <= viewerData.height;\n    }\n  };\n\n  var AnotherViewer = WINDOW.Viewer;\n  var getUniqueID = function (id) {\n    return function () {\n      id += 1;\n      return id;\n    };\n  }(-1);\n  var Viewer = /*#__PURE__*/function () {\n    /**\n     * Create a new Viewer.\n     * @param {Element} element - The target element for viewing.\n     * @param {Object} [options={}] - The configuration options.\n     */\n    function Viewer(element) {\n      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n      _classCallCheck(this, Viewer);\n      if (!element || element.nodeType !== 1) {\n        throw new Error('The first argument is required and must be an element.');\n      }\n      this.element = element;\n      this.options = assign({}, DEFAULTS, isPlainObject(options) && options);\n      this.action = false;\n      this.fading = false;\n      this.fulled = false;\n      this.hiding = false;\n      this.imageClicked = false;\n      this.imageData = {};\n      this.index = this.options.initialViewIndex;\n      this.isImg = false;\n      this.isShown = false;\n      this.length = 0;\n      this.moving = false;\n      this.played = false;\n      this.playing = false;\n      this.pointers = {};\n      this.ready = false;\n      this.rotating = false;\n      this.scaling = false;\n      this.showing = false;\n      this.timeout = false;\n      this.tooltipping = false;\n      this.viewed = false;\n      this.viewing = false;\n      this.wheeling = false;\n      this.zooming = false;\n      this.pointerMoved = false;\n      this.id = getUniqueID();\n      this.init();\n    }\n    return _createClass(Viewer, [{\n      key: \"init\",\n      value: function init() {\n        var _this = this;\n        var element = this.element,\n          options = this.options;\n        if (element[NAMESPACE]) {\n          return;\n        }\n        element[NAMESPACE] = this;\n\n        // The `focus` option requires the `keyboard` option set to `true`.\n        if (options.focus && !options.keyboard) {\n          options.focus = false;\n        }\n        var isImg = element.localName === 'img';\n        var images = [];\n        forEach(isImg ? [element] : element.querySelectorAll('img'), function (image) {\n          if (isFunction(options.filter)) {\n            if (options.filter.call(_this, image)) {\n              images.push(image);\n            }\n          } else if (_this.getImageURL(image)) {\n            images.push(image);\n          }\n        });\n        this.isImg = isImg;\n        this.length = images.length;\n        this.images = images;\n        this.initBody();\n\n        // Override `transition` option if it is not supported\n        if (isUndefined(document.createElement(NAMESPACE).style.transition)) {\n          options.transition = false;\n        }\n        if (options.inline) {\n          var count = 0;\n          var progress = function progress() {\n            count += 1;\n            if (count === _this.length) {\n              var timeout;\n              _this.initializing = false;\n              _this.delaying = {\n                abort: function abort() {\n                  clearTimeout(timeout);\n                }\n              };\n\n              // build asynchronously to keep `this.viewer` is accessible in `ready` event handler.\n              timeout = setTimeout(function () {\n                _this.delaying = false;\n                _this.build();\n              }, 0);\n            }\n          };\n          this.initializing = {\n            abort: function abort() {\n              forEach(images, function (image) {\n                if (!image.complete) {\n                  removeListener(image, EVENT_LOAD, progress);\n                  removeListener(image, EVENT_ERROR, progress);\n                }\n              });\n            }\n          };\n          forEach(images, function (image) {\n            if (image.complete) {\n              progress();\n            } else {\n              var onLoad;\n              var onError;\n              addListener(image, EVENT_LOAD, onLoad = function onLoad() {\n                removeListener(image, EVENT_ERROR, onError);\n                progress();\n              }, {\n                once: true\n              });\n              addListener(image, EVENT_ERROR, onError = function onError() {\n                removeListener(image, EVENT_LOAD, onLoad);\n                progress();\n              }, {\n                once: true\n              });\n            }\n          });\n        } else {\n          addListener(element, EVENT_CLICK, this.onStart = function (_ref) {\n            var target = _ref.target;\n            if (target.localName === 'img' && (!isFunction(options.filter) || options.filter.call(_this, target))) {\n              _this.view(_this.images.indexOf(target));\n            }\n          });\n        }\n      }\n    }, {\n      key: \"build\",\n      value: function build() {\n        if (this.ready) {\n          return;\n        }\n        var element = this.element,\n          options = this.options;\n        var parent = element.parentNode;\n        var template = document.createElement('div');\n        template.innerHTML = TEMPLATE;\n        var viewer = template.querySelector(\".\".concat(NAMESPACE, \"-container\"));\n        var title = viewer.querySelector(\".\".concat(NAMESPACE, \"-title\"));\n        var toolbar = viewer.querySelector(\".\".concat(NAMESPACE, \"-toolbar\"));\n        var navbar = viewer.querySelector(\".\".concat(NAMESPACE, \"-navbar\"));\n        var button = viewer.querySelector(\".\".concat(NAMESPACE, \"-button\"));\n        var canvas = viewer.querySelector(\".\".concat(NAMESPACE, \"-canvas\"));\n        this.parent = parent;\n        this.viewer = viewer;\n        this.title = title;\n        this.toolbar = toolbar;\n        this.navbar = navbar;\n        this.button = button;\n        this.canvas = canvas;\n        this.footer = viewer.querySelector(\".\".concat(NAMESPACE, \"-footer\"));\n        this.tooltipBox = viewer.querySelector(\".\".concat(NAMESPACE, \"-tooltip\"));\n        this.player = viewer.querySelector(\".\".concat(NAMESPACE, \"-player\"));\n        this.list = viewer.querySelector(\".\".concat(NAMESPACE, \"-list\"));\n        viewer.id = \"\".concat(NAMESPACE).concat(this.id);\n        title.id = \"\".concat(NAMESPACE, \"Title\").concat(this.id);\n        addClass(title, !options.title ? CLASS_HIDE : getResponsiveClass(Array.isArray(options.title) ? options.title[0] : options.title));\n        addClass(navbar, !options.navbar ? CLASS_HIDE : getResponsiveClass(options.navbar));\n        toggleClass(button, CLASS_HIDE, !options.button);\n        if (options.keyboard) {\n          button.setAttribute('tabindex', 0);\n        }\n        if (options.backdrop) {\n          addClass(viewer, \"\".concat(NAMESPACE, \"-backdrop\"));\n          if (!options.inline && options.backdrop !== 'static') {\n            setData(canvas, DATA_ACTION, 'hide');\n          }\n        }\n        if (isString(options.className) && options.className) {\n          // In case there are multiple class names\n          options.className.split(REGEXP_SPACES).forEach(function (className) {\n            addClass(viewer, className);\n          });\n        }\n        if (options.toolbar) {\n          var list = document.createElement('ul');\n          var custom = isPlainObject(options.toolbar);\n          var zoomButtons = BUTTONS.slice(0, 3);\n          var rotateButtons = BUTTONS.slice(7, 9);\n          var scaleButtons = BUTTONS.slice(9);\n          if (!custom) {\n            addClass(toolbar, getResponsiveClass(options.toolbar));\n          }\n          forEach(custom ? options.toolbar : BUTTONS, function (value, index) {\n            var deep = custom && isPlainObject(value);\n            var name = custom ? hyphenate(index) : value;\n            var show = deep && !isUndefined(value.show) ? value.show : value;\n            if (!show || !options.zoomable && zoomButtons.indexOf(name) !== -1 || !options.rotatable && rotateButtons.indexOf(name) !== -1 || !options.scalable && scaleButtons.indexOf(name) !== -1) {\n              return;\n            }\n            var size = deep && !isUndefined(value.size) ? value.size : value;\n            var click = deep && !isUndefined(value.click) ? value.click : value;\n            var item = document.createElement('li');\n            if (options.keyboard) {\n              item.setAttribute('tabindex', 0);\n            }\n            item.setAttribute('role', 'button');\n            addClass(item, \"\".concat(NAMESPACE, \"-\").concat(name));\n            if (!isFunction(click)) {\n              setData(item, DATA_ACTION, name);\n            }\n            if (isNumber(show)) {\n              addClass(item, getResponsiveClass(show));\n            }\n            if (['small', 'large'].indexOf(size) !== -1) {\n              addClass(item, \"\".concat(NAMESPACE, \"-\").concat(size));\n            } else if (name === 'play') {\n              addClass(item, \"\".concat(NAMESPACE, \"-large\"));\n            }\n            if (isFunction(click)) {\n              addListener(item, EVENT_CLICK, click);\n            }\n            list.appendChild(item);\n          });\n          toolbar.appendChild(list);\n        } else {\n          addClass(toolbar, CLASS_HIDE);\n        }\n        if (!options.rotatable) {\n          var rotates = toolbar.querySelectorAll('li[class*=\"rotate\"]');\n          addClass(rotates, CLASS_INVISIBLE);\n          forEach(rotates, function (rotate) {\n            toolbar.appendChild(rotate);\n          });\n        }\n        if (options.inline) {\n          addClass(button, CLASS_FULLSCREEN);\n          setStyle(viewer, {\n            zIndex: options.zIndexInline\n          });\n          if (window.getComputedStyle(parent).position === 'static') {\n            setStyle(parent, {\n              position: 'relative'\n            });\n          }\n          parent.insertBefore(viewer, element.nextSibling);\n        } else {\n          addClass(button, CLASS_CLOSE);\n          addClass(viewer, CLASS_FIXED);\n          addClass(viewer, CLASS_FADE);\n          addClass(viewer, CLASS_HIDE);\n          setStyle(viewer, {\n            zIndex: options.zIndex\n          });\n          var container = options.container;\n          if (isString(container)) {\n            container = element.ownerDocument.querySelector(container);\n          }\n          if (!container) {\n            container = this.body;\n          }\n          container.appendChild(viewer);\n        }\n        if (options.inline) {\n          this.render();\n          this.bind();\n          this.isShown = true;\n        }\n        this.ready = true;\n        if (isFunction(options.ready)) {\n          addListener(element, EVENT_READY, options.ready, {\n            once: true\n          });\n        }\n        if (dispatchEvent(element, EVENT_READY) === false) {\n          this.ready = false;\n          return;\n        }\n        if (this.ready && options.inline) {\n          this.view(this.index);\n        }\n      }\n\n      /**\n       * Get the no conflict viewer class.\n       * @returns {Viewer} The viewer class.\n       */\n    }], [{\n      key: \"noConflict\",\n      value: function noConflict() {\n        window.Viewer = AnotherViewer;\n        return Viewer;\n      }\n\n      /**\n       * Change the default options.\n       * @param {Object} options - The new default options.\n       */\n    }, {\n      key: \"setDefaults\",\n      value: function setDefaults(options) {\n        assign(DEFAULTS, isPlainObject(options) && options);\n      }\n    }]);\n  }();\n  assign(Viewer.prototype, render, events, handlers, methods, others);\n\n  return Viewer;\n\n}));\n"
  },
  {
    "path": "karma.conf.js",
    "content": "const puppeteer = require('puppeteer');\nconst rollupConfig = require('./rollup.config');\n\nprocess.env.CHROME_BIN = puppeteer.executablePath();\nprocess.env.NODE_ENV = 'test';\n\nmodule.exports = (config) => {\n  config.set({\n    autoWatch: false,\n    browsers: ['ChromeHeadless'],\n    client: {\n      mocha: {\n        timeout: 10000,\n      },\n    },\n    coverageIstanbulReporter: {\n      reports: ['html', 'lcovonly', 'text-summary'],\n    },\n    files: [\n      'src/index.js',\n      'dist/viewer.css',\n      'test/helpers.js',\n      'test/specs/**/*.spec.js',\n      {\n        pattern: 'docs/images/*',\n        included: false,\n      },\n    ],\n    frameworks: ['mocha', 'chai'],\n    preprocessors: {\n      'src/index.js': ['rollup'],\n      'test/helpers.js': ['rollup'],\n      'test/specs/**/*.spec.js': ['rollup'],\n    },\n    reporters: ['mocha', 'coverage-istanbul'],\n    rollupPreprocessor: {\n      plugins: rollupConfig.plugins,\n      output: {\n        format: 'iife',\n        name: 'Viewer',\n        sourcemap: 'inline',\n      },\n    },\n    singleRun: true,\n  });\n};\n"
  },
  {
    "path": "lint-staged.config.js",
    "content": "module.exports = {\n  '*.js': 'eslint --fix',\n  '*.{css,scss}': 'stylelint --fix',\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"viewerjs\",\n  \"description\": \"JavaScript image viewer.\",\n  \"version\": \"1.11.7\",\n  \"main\": \"dist/viewer.common.js\",\n  \"module\": \"dist/viewer.esm.js\",\n  \"browser\": \"dist/viewer.js\",\n  \"style\": \"dist/viewer.css\",\n  \"types\": \"types/index.d.ts\",\n  \"files\": [\n    \"src\",\n    \"dist\",\n    \"types\"\n  ],\n  \"scripts\": {\n    \"build\": \"npm run build:css && npm run build:js\",\n    \"build:css\": \"postcss src/index.css -o dist/viewer.css --no-map\",\n    \"build:js\": \"rollup -c\",\n    \"clean\": \"del-cli dist\",\n    \"compress\": \"npm run compress:css && npm run compress:js\",\n    \"compress:css\": \"postcss dist/viewer.css -u cssnano -o dist/viewer.min.css --no-map\",\n    \"compress:js\": \"uglifyjs dist/viewer.js -o dist/viewer.min.js -c -m --comments /^!/\",\n    \"copy\": \"cpy dist/viewer.css docs/css --flat\",\n    \"lint\": \"npm run lint:js && npm run lint:css\",\n    \"lint:css\": \"stylelint **/*.{css,scss} --fix\",\n    \"lint:js\": \"eslint . --fix\",\n    \"prepare\": \"husky install\",\n    \"release\": \"npm run clean && npm run lint && npm run build && npm run compress && npm run copy && npm test\",\n    \"start\": \"npm-run-all --parallel watch:*\",\n    \"test\": \"karma start\",\n    \"watch:css\": \"postcss src/index.css -o docs/css/viewer.css -m -w\",\n    \"watch:js\": \"rollup -c -m -w\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/fengyuanchen/viewerjs.git\"\n  },\n  \"keywords\": [\n    \"image\",\n    \"viewer\",\n    \"viewerjs\",\n    \"viewer.js\",\n    \"html\",\n    \"css\",\n    \"javascript\",\n    \"front-end\",\n    \"web\"\n  ],\n  \"author\": {\n    \"name\": \"Chen Fengyuan\",\n    \"url\": \"https://chenfengyuan.com/\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/fengyuanchen/viewerjs/issues\"\n  },\n  \"homepage\": \"https://fengyuanchen.github.io/viewerjs\",\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.26.0\",\n    \"@babel/preset-env\": \"^7.26.0\",\n    \"@commitlint/cli\": \"^17.8.1\",\n    \"@commitlint/config-conventional\": \"^17.8.1\",\n    \"@rollup/plugin-babel\": \"^5.3.1\",\n    \"babel-plugin-istanbul\": \"^6.1.1\",\n    \"chai\": \"^4.5.0\",\n    \"change-case\": \"^4.1.2\",\n    \"codecov\": \"^3.8.3\",\n    \"cpy-cli\": \"^5.0.0\",\n    \"create-banner\": \"^2.0.0\",\n    \"cssnano\": \"^5.1.15\",\n    \"del-cli\": \"^5.1.0\",\n    \"eslint\": \"^8.49.0\",\n    \"eslint-config-airbnb-base\": \"^15.0.0\",\n    \"eslint-plugin-import\": \"^2.31.0\",\n    \"husky\": \"^8.0.3\",\n    \"karma\": \"^6.4.4\",\n    \"karma-chai\": \"^0.1.0\",\n    \"karma-chrome-launcher\": \"^3.2.0\",\n    \"karma-coverage-istanbul-reporter\": \"^3.0.3\",\n    \"karma-mocha\": \"^2.0.1\",\n    \"karma-mocha-reporter\": \"^2.2.5\",\n    \"karma-rollup-preprocessor\": \"^7.0.8\",\n    \"lint-staged\": \"^13.3.0\",\n    \"mocha\": \"^9.2.2\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"postcss\": \"^8.4.49\",\n    \"postcss-cli\": \"^10.1.0\",\n    \"postcss-header\": \"^3.0.3\",\n    \"postcss-import\": \"^15.1.0\",\n    \"postcss-preset-env\": \"^7.8.3\",\n    \"postcss-url\": \"^10.1.3\",\n    \"puppeteer\": \"^19.11.1\",\n    \"rollup\": \"^2.79.2\",\n    \"stylelint\": \"^13.13.1\",\n    \"stylelint-config-standard\": \"^22.0.0\",\n    \"stylelint-order\": \"^4.1.0\",\n    \"uglify-js\": \"^3.19.3\"\n  }\n}\n"
  },
  {
    "path": "postcss.config.js",
    "content": "const rollupConfig = require('./rollup.config');\n\nmodule.exports = {\n  plugins: {\n    'postcss-import': {},\n    'postcss-preset-env': {\n      stage: 3,\n      features: {\n        'nesting-rules': true,\n      },\n    },\n    'postcss-url': {\n      url: 'inline',\n    },\n    'postcss-header': {\n      header: rollupConfig.output[0].banner,\n    },\n    stylelint: {\n      fix: true,\n    },\n  },\n};\n"
  },
  {
    "path": "rollup.config.js",
    "content": "const { babel } = require('@rollup/plugin-babel');\nconst changeCase = require('change-case');\nconst createBanner = require('create-banner');\nconst pkg = require('./package.json');\n\npkg.name = pkg.name.replace('js', '');\n\nconst name = changeCase.pascalCase(pkg.name);\nconst banner = createBanner({\n  data: {\n    name: `${name}.js`,\n    year: '2015-present',\n  },\n});\n\nmodule.exports = {\n  input: 'src/index.js',\n  output: [\n    {\n      banner,\n      name,\n      file: `dist/${pkg.name}.js`,\n      format: 'umd',\n    },\n    {\n      banner,\n      file: `dist/${pkg.name}.common.js`,\n      format: 'cjs',\n      exports: 'auto',\n    },\n    {\n      banner,\n      file: `dist/${pkg.name}.esm.js`,\n      format: 'esm',\n    },\n    {\n      banner,\n      name,\n      file: `docs/js/${pkg.name}.js`,\n      format: 'umd',\n    },\n  ],\n  plugins: [\n    babel({\n      babelHelpers: 'bundled',\n    }),\n  ],\n};\n"
  },
  {
    "path": "src/css/viewer.css",
    "content": ".viewer-zoom-in,\n.viewer-zoom-out,\n.viewer-one-to-one,\n.viewer-reset,\n.viewer-prev,\n.viewer-play,\n.viewer-next,\n.viewer-rotate-left,\n.viewer-rotate-right,\n.viewer-flip-horizontal,\n.viewer-flip-vertical,\n.viewer-fullscreen,\n.viewer-fullscreen-exit,\n.viewer-close {\n  &::before {\n    background-image: url('../images/icons.svg');\n    background-repeat: no-repeat;\n    background-size: 280px;\n    color: transparent;\n    display: block;\n    font-size: 0;\n    height: 20px;\n    line-height: 0;\n    width: 20px;\n  }\n}\n\n.viewer-zoom-in::before {\n  background-position: 0 0;\n  content: 'Zoom In';\n}\n\n.viewer-zoom-out::before {\n  background-position: -20px 0;\n  content: 'Zoom Out';\n}\n\n.viewer-one-to-one::before {\n  background-position: -40px 0;\n  content: 'One to One';\n}\n\n.viewer-reset::before {\n  background-position: -60px 0;\n  content: 'Reset';\n}\n\n.viewer-prev::before {\n  background-position: -80px 0;\n  content: 'Previous';\n}\n\n.viewer-play::before {\n  background-position: -100px 0;\n  content: 'Play';\n}\n\n.viewer-next::before {\n  background-position: -120px 0;\n  content: 'Next';\n}\n\n.viewer-rotate-left::before {\n  background-position: -140px 0;\n  content: 'Rotate Left';\n}\n\n.viewer-rotate-right::before {\n  background-position: -160px 0;\n  content: 'Rotate Right';\n}\n\n.viewer-flip-horizontal::before {\n  background-position: -180px 0;\n  content: 'Flip Horizontal';\n}\n\n.viewer-flip-vertical::before {\n  background-position: -200px 0;\n  content: 'Flip Vertical';\n}\n\n.viewer-fullscreen::before {\n  background-position: -220px 0;\n  content: 'Enter Full Screen';\n}\n\n.viewer-fullscreen-exit::before {\n  background-position: -240px 0;\n  content: 'Exit Full Screen';\n}\n\n.viewer-close::before {\n  background-position: -260px 0;\n  content: 'Close';\n}\n\n.viewer-container {\n  bottom: 0;\n  direction: ltr;\n  font-size: 0;\n  left: 0;\n  line-height: 0;\n  overflow: hidden;\n  position: absolute;\n  right: 0;\n  -webkit-tap-highlight-color: transparent;\n  top: 0;\n  touch-action: none;\n  -webkit-touch-callout: none;\n  user-select: none;\n\n  &::selection,\n  & *::selection {\n    background-color: transparent;\n  }\n\n  &:focus {\n    outline: 0;\n  }\n\n  & img {\n    display: block;\n    height: auto;\n    max-height: none !important;\n    max-width: none !important;\n    min-height: 0 !important;\n    min-width: 0 !important;\n    width: 100%;\n  }\n}\n\n.viewer-canvas {\n  bottom: 0;\n  left: 0;\n  overflow: hidden;\n  position: absolute;\n  right: 0;\n  top: 0;\n\n  & > img {\n    height: auto;\n    margin: 15px auto;\n    max-width: 90% !important;\n    width: auto;\n  }\n}\n\n.viewer-footer {\n  bottom: 0;\n  left: 0;\n  overflow: hidden;\n  position: absolute;\n  right: 0;\n  text-align: center;\n}\n\n.viewer-navbar {\n  background-color: rgba(0, 0, 0, 0.5);\n  overflow: hidden;\n}\n\n.viewer-list {\n  box-sizing: content-box;\n  height: 50px;\n  margin: 0;\n  overflow: hidden;\n  padding: 1px 0;\n\n  & > li {\n    color: transparent;\n    cursor: pointer;\n    float: left;\n    font-size: 0;\n    height: 50px;\n    line-height: 0;\n    opacity: 0.5;\n    overflow: hidden;\n    transition: opacity 0.15s;\n    width: 30px;\n\n    &:focus,\n    &:hover {\n      opacity: 0.75;\n    }\n\n    &:focus {\n      outline: 0;\n    }\n\n    & + li {\n      margin-left: 1px;\n    }\n  }\n\n  & > .viewer-loading {\n    position: relative;\n\n    &::after {\n      border-width: 2px;\n      height: 20px;\n      margin-left: -10px;\n      margin-top: -10px;\n      width: 20px;\n    }\n  }\n\n  & > .viewer-active,\n  & > .viewer-active:focus,\n  & > .viewer-active:hover {\n    opacity: 1;\n  }\n}\n\n.viewer-player {\n  background-color: #000;\n  bottom: 0;\n  cursor: none;\n  display: none;\n  left: 0;\n  position: absolute;\n  right: 0;\n  top: 0;\n  z-index: 1;\n\n  & > img {\n    left: 0;\n    position: absolute;\n    top: 0;\n  }\n}\n\n.viewer-toolbar {\n  & > ul {\n    display: inline-block;\n    margin: 0 auto 5px;\n    overflow: hidden;\n    padding: 6px 3px;\n\n    & > li {\n      background-color: rgba(0, 0, 0, 0.5);\n      border-radius: 50%;\n      cursor: pointer;\n      float: left;\n      height: 24px;\n      overflow: hidden;\n      transition: background-color 0.15s;\n      width: 24px;\n\n      &:focus,\n      &:hover {\n        background-color: rgba(0, 0, 0, 0.8);\n      }\n\n      &:focus {\n        box-shadow: 0 0 3px #fff;\n        outline: 0;\n        position: relative;\n        z-index: 1;\n      }\n\n      &::before {\n        margin: 2px;\n      }\n\n      & + li {\n        margin-left: 1px;\n      }\n    }\n\n    & > .viewer-small {\n      height: 18px;\n      margin-bottom: 3px;\n      margin-top: 3px;\n      width: 18px;\n\n      &::before {\n        margin: -1px;\n      }\n    }\n\n    & > .viewer-large {\n      height: 30px;\n      margin-bottom: -3px;\n      margin-top: -3px;\n      width: 30px;\n\n      &::before {\n        margin: 5px;\n      }\n    }\n  }\n}\n\n.viewer-tooltip {\n  background-color: rgba(0, 0, 0, 0.8);\n  border-radius: 10px;\n  color: #fff;\n  display: none;\n  font-size: 12px;\n  height: 20px;\n  left: 50%;\n  line-height: 20px;\n  margin-left: -25px;\n  margin-top: -10px;\n  position: absolute;\n  text-align: center;\n  top: 50%;\n  width: 50px;\n}\n\n.viewer-title {\n  color: #ccc;\n  display: inline-block;\n  font-size: 12px;\n  line-height: 1.2;\n  margin: 5px 5%;\n  max-width: 90%;\n  min-height: 14px;\n  opacity: 0.8;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  transition: opacity 0.15s;\n  white-space: nowrap;\n\n  &:hover {\n    opacity: 1;\n  }\n}\n\n.viewer-button {\n  background-color: rgba(0, 0, 0, 0.5);\n  border-radius: 50%;\n  cursor: pointer;\n  height: 80px;\n  overflow: hidden;\n  position: absolute;\n  right: -40px;\n  top: -40px;\n  transition: background-color 0.15s;\n  width: 80px;\n  -webkit-app-region: no-drag;\n\n  &:focus,\n  &:hover {\n    background-color: rgba(0, 0, 0, 0.8);\n  }\n\n  &:focus {\n    box-shadow: 0 0 3px #fff;\n    outline: 0;\n  }\n\n  &::before {\n    bottom: 15px;\n    left: 15px;\n    position: absolute;\n  }\n}\n\n.viewer-fixed {\n  position: fixed;\n}\n\n.viewer-open {\n  overflow: hidden;\n}\n\n.viewer-show {\n  display: block;\n}\n\n.viewer-hide {\n  display: none;\n}\n\n.viewer-backdrop {\n  background-color: rgba(0, 0, 0, 0.5);\n}\n\n.viewer-invisible {\n  visibility: hidden;\n}\n\n.viewer-move {\n  cursor: move;\n  cursor: grab;\n}\n\n.viewer-fade {\n  opacity: 0;\n}\n\n.viewer-in {\n  opacity: 1;\n}\n\n.viewer-transition {\n  transition: all 0.3s;\n}\n\n@keyframes viewer-spinner {\n  0% {\n    transform: rotate(0deg);\n  }\n\n  100% {\n    transform: rotate(360deg);\n  }\n}\n\n.viewer-loading {\n  &::after {\n    animation: viewer-spinner 1s linear infinite;\n    border: 4px solid rgba(255, 255, 255, 0.1);\n    border-left-color: rgba(255, 255, 255, 0.5);\n    border-radius: 50%;\n    content: '';\n    display: inline-block;\n    height: 40px;\n    left: 50%;\n    margin-left: -20px;\n    margin-top: -20px;\n    position: absolute;\n    top: 50%;\n    width: 40px;\n    z-index: 1;\n  }\n}\n\n@media (max-width: 767px) {\n  .viewer-hide-xs-down {\n    display: none;\n  }\n}\n\n@media (max-width: 991px) {\n  .viewer-hide-sm-down {\n    display: none;\n  }\n}\n\n@media (max-width: 1199px) {\n  .viewer-hide-md-down {\n    display: none;\n  }\n}\n"
  },
  {
    "path": "src/css/viewer.scss",
    "content": ".viewer {\n  &-zoom-in,\n  &-zoom-out,\n  &-one-to-one,\n  &-reset,\n  &-prev,\n  &-play,\n  &-next,\n  &-rotate-left,\n  &-rotate-right,\n  &-flip-horizontal,\n  &-flip-vertical,\n  &-fullscreen,\n  &-fullscreen-exit,\n  &-close {\n    &::before {\n      background-image: url('../images/icons.svg');\n      background-repeat: no-repeat;\n      background-size: 280px;\n      color: transparent;\n      display: block;\n      font-size: 0;\n      height: 20px;\n      line-height: 0;\n      width: 20px;\n    }\n  }\n\n  &-zoom-in::before {\n    background-position: 0 0;\n    content: 'Zoom In';\n  }\n\n  &-zoom-out::before {\n    background-position: -20px 0;\n    content: 'Zoom Out';\n  }\n\n  &-one-to-one::before {\n    background-position: -40px 0;\n    content: 'One to One';\n  }\n\n  &-reset::before {\n    background-position: -60px 0;\n    content: 'Reset';\n  }\n\n  &-prev::before {\n    background-position: -80px 0;\n    content: 'Previous';\n  }\n\n  &-play::before {\n    background-position: -100px 0;\n    content: 'Play';\n  }\n\n  &-next::before {\n    background-position: -120px 0;\n    content: 'Next';\n  }\n\n  &-rotate-left::before {\n    background-position: -140px 0;\n    content: 'Rotate Left';\n  }\n\n  &-rotate-right::before {\n    background-position: -160px 0;\n    content: 'Rotate Right';\n  }\n\n  &-flip-horizontal::before {\n    background-position: -180px 0;\n    content: 'Flip Horizontal';\n  }\n\n  &-flip-vertical::before {\n    background-position: -200px 0;\n    content: 'Flip Vertical';\n  }\n\n  &-fullscreen::before {\n    background-position: -220px 0;\n    content: 'Enter Full Screen';\n  }\n\n  &-fullscreen-exit::before {\n    background-position: -240px 0;\n    content: 'Exit Full Screen';\n  }\n\n  &-close::before {\n    background-position: -260px 0;\n    content: 'Close';\n  }\n\n  &-container {\n    bottom: 0;\n    direction: ltr;\n    font-size: 0;\n    left: 0;\n    line-height: 0;\n    overflow: hidden;\n    position: absolute;\n    right: 0;\n    -webkit-tap-highlight-color: transparent;\n    top: 0;\n    touch-action: none;\n    -webkit-touch-callout: none;\n    user-select: none;\n\n    &::selection,\n    & *::selection {\n      background-color: transparent;\n    }\n\n    &:focus {\n      outline: 0;\n    }\n\n    & img {\n      display: block;\n      height: auto;\n      max-height: none !important;\n      max-width: none !important;\n      min-height: 0 !important;\n      min-width: 0 !important;\n      width: 100%;\n    }\n  }\n\n  &-canvas {\n    bottom: 0;\n    left: 0;\n    overflow: hidden;\n    position: absolute;\n    right: 0;\n    top: 0;\n\n    & > img {\n      height: auto;\n      margin: 15px auto;\n      max-width: 90% !important;\n      width: auto;\n    }\n  }\n\n  &-footer {\n    bottom: 0;\n    left: 0;\n    overflow: hidden;\n    position: absolute;\n    right: 0;\n    text-align: center;\n  }\n\n  &-navbar {\n    background-color: rgba(0, 0, 0, 0.5);\n    overflow: hidden;\n  }\n\n  &-list {\n    box-sizing: content-box;\n    height: 50px;\n    margin: 0;\n    overflow: hidden;\n    padding: 1px 0;\n\n    & > li {\n      color: transparent;\n      cursor: pointer;\n      float: left;\n      font-size: 0;\n      height: 50px;\n      line-height: 0;\n      opacity: 0.5;\n      overflow: hidden;\n      transition: opacity 0.15s;\n      width: 30px;\n\n      &:focus,\n      &:hover {\n        opacity: 0.75;\n      }\n\n      &:focus {\n        outline: 0;\n      }\n\n      & + li {\n        margin-left: 1px;\n      }\n    }\n\n    & > .viewer-loading {\n      position: relative;\n\n      &::after {\n        border-width: 2px;\n        height: 20px;\n        margin-left: -10px;\n        margin-top: -10px;\n        width: 20px;\n      }\n    }\n\n    & > .viewer-active {\n      &,\n      &:focus,\n      &:hover {\n        opacity: 1;\n      }\n    }\n  }\n\n  &-player {\n    background-color: #000;\n    bottom: 0;\n    cursor: none;\n    display: none;\n    left: 0;\n    position: absolute;\n    right: 0;\n    top: 0;\n    z-index: 1;\n\n    & > img {\n      left: 0;\n      position: absolute;\n      top: 0;\n    }\n  }\n\n  &-toolbar {\n    & > ul {\n      display: inline-block;\n      margin: 0 auto 5px;\n      overflow: hidden;\n      padding: 6px 3px;\n\n      & > li {\n        background-color: rgba(0, 0, 0, 0.5);\n        border-radius: 50%;\n        cursor: pointer;\n        float: left;\n        height: 24px;\n        overflow: hidden;\n        transition: background-color 0.15s;\n        width: 24px;\n\n        &:focus,\n        &:hover {\n          background-color: rgba(0, 0, 0, 0.8);\n        }\n\n        &:focus {\n          box-shadow: 0 0 3px #fff;\n          outline: 0;\n          position: relative;\n          z-index: 1;\n        }\n\n        &::before {\n          margin: 2px;\n        }\n\n        & + li {\n          margin-left: 1px;\n        }\n      }\n\n      & > .viewer-small {\n        height: 18px;\n        margin-bottom: 3px;\n        margin-top: 3px;\n        width: 18px;\n\n        &::before {\n          margin: -1px;\n        }\n      }\n\n      & > .viewer-large {\n        height: 30px;\n        margin-bottom: -3px;\n        margin-top: -3px;\n        width: 30px;\n\n        &::before {\n          margin: 5px;\n        }\n      }\n    }\n  }\n\n  &-tooltip {\n    background-color: rgba(0, 0, 0, 0.8);\n    border-radius: 10px;\n    color: #fff;\n    display: none;\n    font-size: 12px;\n    height: 20px;\n    left: 50%;\n    line-height: 20px;\n    margin-left: -25px;\n    margin-top: -10px;\n    position: absolute;\n    text-align: center;\n    top: 50%;\n    width: 50px;\n  }\n\n  &-title {\n    color: #ccc;\n    display: inline-block;\n    font-size: 12px;\n    line-height: 1.2;\n    margin: 5px 5%;\n    max-width: 90%;\n    min-height: 14px;\n    opacity: 0.8;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    transition: opacity 0.15s;\n    white-space: nowrap;\n\n    &:hover {\n      opacity: 1;\n    }\n  }\n\n  &-button {\n    background-color: rgba(0, 0, 0, 0.5);\n    border-radius: 50%;\n    cursor: pointer;\n    height: 80px;\n    overflow: hidden;\n    position: absolute;\n    right: -40px;\n    top: -40px;\n    transition: background-color 0.15s;\n    width: 80px;\n    -webkit-app-region: no-drag;\n\n    &:focus,\n    &:hover {\n      background-color: rgba(0, 0, 0, 0.8);\n    }\n\n    &:focus {\n      box-shadow: 0 0 3px #fff;\n      outline: 0;\n    }\n\n    &::before {\n      bottom: 15px;\n      left: 15px;\n      position: absolute;\n    }\n  }\n\n  &-fixed {\n    position: fixed;\n  }\n\n  &-open {\n    overflow: hidden;\n  }\n\n  &-show {\n    display: block;\n  }\n\n  &-hide {\n    display: none;\n  }\n\n  &-backdrop {\n    background-color: rgba(0, 0, 0, 0.5);\n  }\n\n  &-invisible {\n    visibility: hidden;\n  }\n\n  &-move {\n    cursor: move;\n    cursor: grab;\n  }\n\n  &-fade {\n    opacity: 0;\n  }\n\n  &-in {\n    opacity: 1;\n  }\n\n  &-transition {\n    transition: all 0.3s;\n  }\n\n  @keyframes viewer-spinner {\n    0% {\n      transform: rotate(0deg);\n    }\n\n    100% {\n      transform: rotate(360deg);\n    }\n  }\n\n  &-loading {\n    &::after {\n      animation: viewer-spinner 1s linear infinite;\n      border: 4px solid rgba(255, 255, 255, 0.1);\n      border-left-color: rgba(255, 255, 255, 0.5);\n      border-radius: 50%;\n      content: '';\n      display: inline-block;\n      height: 40px;\n      left: 50%;\n      margin-left: -20px;\n      margin-top: -20px;\n      position: absolute;\n      top: 50%;\n      width: 40px;\n      z-index: 1;\n    }\n  }\n\n  @media (max-width: 767px) {\n    &-hide-xs-down {\n      display: none;\n    }\n  }\n\n  @media (max-width: 991px) {\n    &-hide-sm-down {\n      display: none;\n    }\n  }\n\n  @media (max-width: 1199px) {\n    &-hide-md-down {\n      display: none;\n    }\n  }\n}\n"
  },
  {
    "path": "src/index.css",
    "content": "@import \"./css/viewer.css\";\n"
  },
  {
    "path": "src/index.js",
    "content": "import Viewer from './js/viewer';\n\nexport default Viewer;\n"
  },
  {
    "path": "src/index.scss",
    "content": "@import \"./css/viewer\";\n"
  },
  {
    "path": "src/js/constants.js",
    "content": "export const IS_BROWSER = typeof window !== 'undefined' && typeof window.document !== 'undefined';\nexport const WINDOW = IS_BROWSER ? window : {};\nexport const IS_TOUCH_DEVICE = IS_BROWSER && WINDOW.document.documentElement ? 'ontouchstart' in WINDOW.document.documentElement : false;\nexport const HAS_POINTER_EVENT = IS_BROWSER ? 'PointerEvent' in WINDOW : false;\nexport const NAMESPACE = 'viewer';\n\n// Actions\nexport const ACTION_MOVE = 'move';\nexport const ACTION_SWITCH = 'switch';\nexport const ACTION_ZOOM = 'zoom';\n\n// Classes\nexport const CLASS_ACTIVE = `${NAMESPACE}-active`;\nexport const CLASS_CLOSE = `${NAMESPACE}-close`;\nexport const CLASS_FADE = `${NAMESPACE}-fade`;\nexport const CLASS_FIXED = `${NAMESPACE}-fixed`;\nexport const CLASS_FULLSCREEN = `${NAMESPACE}-fullscreen`;\nexport const CLASS_FULLSCREEN_EXIT = `${NAMESPACE}-fullscreen-exit`;\nexport const CLASS_HIDE = `${NAMESPACE}-hide`;\nexport const CLASS_HIDE_MD_DOWN = `${NAMESPACE}-hide-md-down`;\nexport const CLASS_HIDE_SM_DOWN = `${NAMESPACE}-hide-sm-down`;\nexport const CLASS_HIDE_XS_DOWN = `${NAMESPACE}-hide-xs-down`;\nexport const CLASS_IN = `${NAMESPACE}-in`;\nexport const CLASS_INVISIBLE = `${NAMESPACE}-invisible`;\nexport const CLASS_LOADING = `${NAMESPACE}-loading`;\nexport const CLASS_MOVE = `${NAMESPACE}-move`;\nexport const CLASS_OPEN = `${NAMESPACE}-open`;\nexport const CLASS_SHOW = `${NAMESPACE}-show`;\nexport const CLASS_TRANSITION = `${NAMESPACE}-transition`;\n\n// Native events\nexport const EVENT_CLICK = 'click';\nexport const EVENT_DBLCLICK = 'dblclick';\nexport const EVENT_DRAG_START = 'dragstart';\nexport const EVENT_FOCUSIN = 'focusin';\nexport const EVENT_KEY_DOWN = 'keydown';\nexport const EVENT_LOAD = 'load';\nexport const EVENT_ERROR = 'error';\nexport const EVENT_TOUCH_END = IS_TOUCH_DEVICE ? 'touchend touchcancel' : 'mouseup';\nexport const EVENT_TOUCH_MOVE = IS_TOUCH_DEVICE ? 'touchmove' : 'mousemove';\nexport const EVENT_TOUCH_START = IS_TOUCH_DEVICE ? 'touchstart' : 'mousedown';\nexport const EVENT_POINTER_DOWN = HAS_POINTER_EVENT ? 'pointerdown' : EVENT_TOUCH_START;\nexport const EVENT_POINTER_MOVE = HAS_POINTER_EVENT ? 'pointermove' : EVENT_TOUCH_MOVE;\nexport const EVENT_POINTER_UP = HAS_POINTER_EVENT ? 'pointerup pointercancel' : EVENT_TOUCH_END;\nexport const EVENT_RESIZE = 'resize';\nexport const EVENT_TRANSITION_END = 'transitionend';\nexport const EVENT_WHEEL = 'wheel';\n\n// Custom events\nexport const EVENT_READY = 'ready';\nexport const EVENT_SHOW = 'show';\nexport const EVENT_SHOWN = 'shown';\nexport const EVENT_HIDE = 'hide';\nexport const EVENT_HIDDEN = 'hidden';\nexport const EVENT_VIEW = 'view';\nexport const EVENT_VIEWED = 'viewed';\nexport const EVENT_MOVE = 'move';\nexport const EVENT_MOVED = 'moved';\nexport const EVENT_ROTATE = 'rotate';\nexport const EVENT_ROTATED = 'rotated';\nexport const EVENT_SCALE = 'scale';\nexport const EVENT_SCALED = 'scaled';\nexport const EVENT_ZOOM = 'zoom';\nexport const EVENT_ZOOMED = 'zoomed';\nexport const EVENT_PLAY = 'play';\nexport const EVENT_STOP = 'stop';\n\n// Data keys\nexport const DATA_ACTION = `${NAMESPACE}Action`;\n\n// RegExps\nexport const REGEXP_SPACES = /\\s\\s*/;\n\n// Misc\nexport const BUTTONS = [\n  'zoom-in',\n  'zoom-out',\n  'one-to-one',\n  'reset',\n  'prev',\n  'play',\n  'next',\n  'rotate-left',\n  'rotate-right',\n  'flip-horizontal',\n  'flip-vertical',\n];\n"
  },
  {
    "path": "src/js/defaults.js",
    "content": "export default {\n  /**\n   * Enable a modal backdrop, specify `static` for a backdrop\n   * which doesn't close the modal on click.\n   * @type {boolean}\n   */\n  backdrop: true,\n\n  /**\n   * Show the button on the top-right of the viewer.\n   * @type {boolean}\n   */\n  button: true,\n\n  /**\n   * Show the navbar.\n   * @type {boolean | number}\n   */\n  navbar: true,\n\n  /**\n   * Specify the visibility and the content of the title.\n   * @type {boolean | number | Function | Array}\n   */\n  title: true,\n\n  /**\n   * Show the toolbar.\n   * @type {boolean | number | Object}\n   */\n  toolbar: true,\n\n  /**\n   * Custom class name(s) to add to the viewer's root element.\n   * @type {string}\n   */\n  className: '',\n\n  /**\n   * Define where to put the viewer in modal mode.\n   * @type {string | Element}\n   */\n  container: 'body',\n\n  /**\n   * Filter the images for viewing. Return true if the image is viewable.\n   * @type {Function}\n   */\n  filter: null,\n\n  /**\n   * Enable to request fullscreen when play.\n   * {@link https://developer.mozilla.org/en-US/docs/Web/API/FullscreenOptions}\n   * @type {boolean|FullscreenOptions}\n   */\n  fullscreen: true,\n\n  /**\n   * Define the extra attributes to inherit from the original image.\n   * @type {Array}\n   */\n  inheritedAttributes: [\n    'crossOrigin',\n    'decoding',\n    'isMap',\n    'loading',\n    'referrerPolicy',\n    'sizes',\n    'srcset',\n    'useMap',\n  ],\n\n  /**\n   * Define the initial coverage of the viewing image.\n   * @type {number}\n   */\n  initialCoverage: 0.9,\n\n  /**\n   * Define the initial index of the image for viewing.\n   * @type {number}\n   */\n  initialViewIndex: 0,\n\n  /**\n   * Enable inline mode.\n   * @type {boolean}\n   */\n  inline: false,\n\n  /**\n   * The amount of time to delay between automatically cycling an image when playing.\n   * @type {number}\n   */\n  interval: 5000,\n\n  /**\n   * Enable keyboard support.\n   * @type {boolean}\n   */\n  keyboard: true,\n\n  /**\n   * Focus the viewer when initialized.\n   * @type {boolean}\n   */\n  focus: true,\n\n  /**\n   * Indicate if show a loading spinner when load image or not.\n   * @type {boolean}\n   */\n  loading: true,\n\n  /**\n   * Indicate if enable loop viewing or not.\n   * @type {boolean}\n   */\n  loop: true,\n\n  /**\n   * Min width of the viewer in inline mode.\n   * @type {number}\n   */\n  minWidth: 200,\n\n  /**\n   * Min height of the viewer in inline mode.\n   * @type {number}\n   */\n  minHeight: 100,\n\n  /**\n   * Enable to move the image.\n   * @type {boolean}\n   */\n  movable: true,\n\n  /**\n   * Enable to rotate the image.\n   * @type {boolean}\n   */\n  rotatable: true,\n\n  /**\n   * Enable to scale the image.\n   * @type {boolean}\n   */\n  scalable: true,\n\n  /**\n   * Enable to zoom the image.\n   * @type {boolean}\n   */\n  zoomable: true,\n\n  /**\n   * Enable to zoom the current image by dragging on the touch screen.\n   * @type {boolean}\n   */\n  zoomOnTouch: true,\n\n  /**\n   * Enable to zoom the image by wheeling mouse.\n   * @type {boolean}\n   */\n  zoomOnWheel: true,\n\n  /**\n   * Enable to slide to the next or previous image by swiping on the touch screen.\n   * @type {boolean}\n   */\n  slideOnTouch: true,\n\n  /**\n   * Indicate if toggle the image size between its natural size\n   * and initial size when double click on the image or not.\n   * @type {boolean}\n   */\n  toggleOnDblclick: true,\n\n  /**\n   * Show the tooltip with image ratio (percentage) when zoom in or zoom out.\n   * @type {boolean}\n   */\n  tooltip: true,\n\n  /**\n   * Enable CSS3 Transition for some special elements.\n   * @type {boolean}\n   */\n  transition: true,\n\n  /**\n   * Define the CSS `z-index` value of viewer in modal mode.\n   * @type {number}\n   */\n  zIndex: 2015,\n\n  /**\n   * Define the CSS `z-index` value of viewer in inline mode.\n   * @type {number}\n   */\n  zIndexInline: 0,\n\n  /**\n   * Define the ratio when zoom the image by wheeling mouse.\n   * @type {number}\n   */\n  zoomRatio: 0.1,\n\n  /**\n   * Define the min ratio of the image when zoom out.\n   * @type {number}\n   */\n  minZoomRatio: 0.01,\n\n  /**\n   * Define the max ratio of the image when zoom in.\n   * @type {number}\n   */\n  maxZoomRatio: 100,\n\n  /**\n   * Define where to get the original image URL for viewing.\n   * @type {string | Function}\n   */\n  url: 'src',\n\n  /**\n   * Event shortcuts.\n   * @type {Function}\n   */\n  ready: null,\n  show: null,\n  shown: null,\n  hide: null,\n  hidden: null,\n  view: null,\n  viewed: null,\n  move: null,\n  moved: null,\n  rotate: null,\n  rotated: null,\n  scale: null,\n  scaled: null,\n  zoom: null,\n  zoomed: null,\n  play: null,\n  stop: null,\n};\n"
  },
  {
    "path": "src/js/events.js",
    "content": "import {\n  EVENT_CLICK,\n  EVENT_DBLCLICK,\n  EVENT_DRAG_START,\n  EVENT_KEY_DOWN,\n  EVENT_POINTER_DOWN,\n  EVENT_POINTER_MOVE,\n  EVENT_POINTER_UP,\n  EVENT_RESIZE,\n  EVENT_WHEEL,\n} from './constants';\nimport {\n  addListener,\n  removeListener,\n} from './utilities';\n\nexport default {\n  bind() {\n    const { options, viewer, canvas } = this;\n    const document = this.element.ownerDocument;\n\n    addListener(viewer, EVENT_CLICK, (this.onClick = this.click.bind(this)));\n    addListener(viewer, EVENT_DRAG_START, (this.onDragStart = this.dragstart.bind(this)));\n    addListener(canvas, EVENT_POINTER_DOWN, (this.onPointerDown = this.pointerdown.bind(this)));\n    addListener(document, EVENT_POINTER_MOVE, (this.onPointerMove = this.pointermove.bind(this)));\n    addListener(document, EVENT_POINTER_UP, (this.onPointerUp = this.pointerup.bind(this)));\n    addListener(document, EVENT_KEY_DOWN, (this.onKeyDown = this.keydown.bind(this)));\n    addListener(window, EVENT_RESIZE, (this.onResize = this.resize.bind(this)));\n\n    if (options.zoomable && options.zoomOnWheel) {\n      addListener(viewer, EVENT_WHEEL, (this.onWheel = this.wheel.bind(this)), {\n        passive: false,\n        capture: true,\n      });\n    }\n\n    if (options.toggleOnDblclick) {\n      addListener(canvas, EVENT_DBLCLICK, (this.onDblclick = this.dblclick.bind(this)));\n    }\n  },\n\n  unbind() {\n    const { options, viewer, canvas } = this;\n    const document = this.element.ownerDocument;\n\n    removeListener(viewer, EVENT_CLICK, this.onClick);\n    removeListener(viewer, EVENT_DRAG_START, this.onDragStart);\n    removeListener(canvas, EVENT_POINTER_DOWN, this.onPointerDown);\n    removeListener(document, EVENT_POINTER_MOVE, this.onPointerMove);\n    removeListener(document, EVENT_POINTER_UP, this.onPointerUp);\n    removeListener(document, EVENT_KEY_DOWN, this.onKeyDown);\n    removeListener(window, EVENT_RESIZE, this.onResize);\n\n    if (options.zoomable && options.zoomOnWheel) {\n      removeListener(viewer, EVENT_WHEEL, this.onWheel, {\n        passive: false,\n        capture: true,\n      });\n    }\n\n    if (options.toggleOnDblclick) {\n      removeListener(canvas, EVENT_DBLCLICK, this.onDblclick);\n    }\n  },\n};\n"
  },
  {
    "path": "src/js/handlers.js",
    "content": "import {\n  ACTION_MOVE,\n  ACTION_SWITCH,\n  ACTION_ZOOM,\n  CLASS_INVISIBLE,\n  CLASS_LOADING,\n  CLASS_MOVE,\n  CLASS_TRANSITION,\n  DATA_ACTION,\n  EVENT_CLICK,\n  EVENT_DBLCLICK,\n  EVENT_LOAD,\n  EVENT_VIEWED,\n  IS_TOUCH_DEVICE,\n} from './constants';\nimport {\n  addClass,\n  addListener,\n  assign,\n  dispatchEvent,\n  forEach,\n  getData,\n  getImageNaturalSizes,\n  getPointer,\n  getTransforms,\n  isFunction,\n  isNumber,\n  removeClass,\n  setStyle,\n  toggleClass,\n} from './utilities';\n\nexport default {\n  click(event) {\n    const { options, imageData } = this;\n    let { target } = event;\n    let action = getData(target, DATA_ACTION);\n\n    if (!action && target.localName === 'img' && target.parentElement.localName === 'li') {\n      target = target.parentElement;\n      action = getData(target, DATA_ACTION);\n    }\n\n    // Cancel the emulated click when the native click event was triggered.\n    if (IS_TOUCH_DEVICE && event.isTrusted && target === this.canvas) {\n      clearTimeout(this.clickCanvasTimeout);\n    }\n\n    switch (action) {\n      case 'mix':\n        if (this.played) {\n          this.stop();\n        } else if (options.inline) {\n          if (this.fulled) {\n            this.exit();\n          } else {\n            this.full();\n          }\n        } else {\n          this.hide();\n        }\n\n        break;\n\n      case 'hide':\n        if (!this.pointerMoved) {\n          this.hide();\n        }\n        break;\n\n      case 'view':\n        this.view(getData(target, 'index'));\n        break;\n\n      case 'zoom-in':\n        this.zoom(0.1, true);\n        break;\n\n      case 'zoom-out':\n        this.zoom(-0.1, true);\n        break;\n\n      case 'one-to-one':\n        this.toggle();\n        break;\n\n      case 'reset':\n        this.reset();\n        break;\n\n      case 'prev':\n        this.prev(options.loop);\n        break;\n\n      case 'play':\n        this.play(options.fullscreen);\n        break;\n\n      case 'next':\n        this.next(options.loop);\n        break;\n\n      case 'rotate-left':\n        this.rotate(-90);\n        break;\n\n      case 'rotate-right':\n        this.rotate(90);\n        break;\n\n      case 'flip-horizontal':\n        this.scaleX(-imageData.scaleX || -1);\n        break;\n\n      case 'flip-vertical':\n        this.scaleY(-imageData.scaleY || -1);\n        break;\n\n      default:\n        if (this.played) {\n          this.stop();\n        }\n    }\n  },\n\n  dblclick(event) {\n    event.preventDefault();\n\n    if (this.viewed && event.target === this.image) {\n      // Cancel the emulated double click when the native dblclick event was triggered.\n      if (IS_TOUCH_DEVICE && event.isTrusted) {\n        clearTimeout(this.doubleClickImageTimeout);\n      }\n\n      // XXX: No pageX/Y properties in custom event, fallback to the original event.\n      this.toggle(event.isTrusted ? event : (event.detail && event.detail.originalEvent));\n    }\n  },\n\n  load() {\n    if (this.timeout) {\n      clearTimeout(this.timeout);\n      this.timeout = false;\n    }\n\n    const {\n      element,\n      options,\n      image,\n      index,\n      viewerData,\n    } = this;\n\n    removeClass(image, CLASS_INVISIBLE);\n\n    if (options.loading) {\n      removeClass(this.canvas, CLASS_LOADING);\n    }\n\n    image.style.cssText = (\n      'height:0;'\n      + `margin-left:${viewerData.width / 2}px;`\n      + `margin-top:${viewerData.height / 2}px;`\n      + 'max-width:none!important;'\n      + 'position:relative;'\n      + 'width:0;'\n    );\n\n    this.initImage(() => {\n      toggleClass(image, CLASS_MOVE, options.movable);\n      toggleClass(image, CLASS_TRANSITION, options.transition);\n\n      this.renderImage(() => {\n        this.viewed = true;\n        this.viewing = false;\n\n        if (isFunction(options.viewed)) {\n          addListener(element, EVENT_VIEWED, options.viewed, {\n            once: true,\n          });\n        }\n\n        dispatchEvent(element, EVENT_VIEWED, {\n          originalImage: this.images[index],\n          index,\n          image,\n        }, {\n          cancelable: false,\n        });\n      });\n    });\n  },\n\n  loadImage(event) {\n    const image = event.target;\n    const parent = image.parentNode;\n    const parentWidth = parent.offsetWidth || 30;\n    const parentHeight = parent.offsetHeight || 50;\n    const filled = !!getData(image, 'filled');\n\n    getImageNaturalSizes(image, this.options, (naturalWidth, naturalHeight) => {\n      const aspectRatio = naturalWidth / naturalHeight;\n      let width = parentWidth;\n      let height = parentHeight;\n\n      if (parentHeight * aspectRatio > parentWidth) {\n        if (filled) {\n          width = parentHeight * aspectRatio;\n        } else {\n          height = parentWidth / aspectRatio;\n        }\n      } else if (filled) {\n        height = parentWidth / aspectRatio;\n      } else {\n        width = parentHeight * aspectRatio;\n      }\n\n      setStyle(image, assign({\n        width,\n        height,\n      }, getTransforms({\n        translateX: (parentWidth - width) / 2,\n        translateY: (parentHeight - height) / 2,\n      })));\n    });\n  },\n\n  keydown(event) {\n    const { options } = this;\n\n    if (!options.keyboard) {\n      return;\n    }\n\n    const keyCode = event.keyCode || event.which || event.charCode;\n\n    switch (keyCode) {\n      // Enter\n      case 13:\n        if (this.viewer.contains(event.target)) {\n          this.click(event);\n        }\n\n        break;\n\n      default:\n    }\n\n    if (!this.fulled) {\n      return;\n    }\n\n    switch (keyCode) {\n      // Escape\n      case 27:\n        if (this.played) {\n          this.stop();\n        } else if (options.inline) {\n          if (this.fulled) {\n            this.exit();\n          }\n        } else {\n          this.hide();\n        }\n\n        break;\n\n      // Space\n      case 32:\n        if (this.played) {\n          this.stop();\n        }\n\n        break;\n\n      // ArrowLeft\n      case 37:\n        if (this.played && this.playing) {\n          this.playing.prev();\n        } else {\n          this.prev(options.loop);\n        }\n        break;\n\n      // ArrowUp\n      case 38:\n        // Prevent scroll on Firefox\n        event.preventDefault();\n\n        // Zoom in\n        this.zoom(options.zoomRatio, true);\n        break;\n\n      // ArrowRight\n      case 39:\n        if (this.played && this.playing) {\n          this.playing.next();\n        } else {\n          this.next(options.loop);\n        }\n        break;\n\n      // ArrowDown\n      case 40:\n        // Prevent scroll on Firefox\n        event.preventDefault();\n\n        // Zoom out\n        this.zoom(-options.zoomRatio, true);\n        break;\n\n      // Ctrl + 0\n      case 48:\n        // Fall through\n\n      // Ctrl + 1\n      // eslint-disable-next-line no-fallthrough\n      case 49:\n        if (event.ctrlKey) {\n          event.preventDefault();\n          this.toggle();\n        }\n\n        break;\n\n      default:\n    }\n  },\n\n  dragstart(event) {\n    if (event.target.localName === 'img') {\n      event.preventDefault();\n    }\n  },\n\n  pointerdown(event) {\n    const { options, pointers } = this;\n    const { buttons, button } = event;\n\n    this.pointerMoved = false;\n\n    if (\n      !this.viewed\n      || this.showing\n      || this.viewing\n      || this.hiding\n\n      // Handle mouse event and pointer event and ignore touch event\n      || ((\n        event.type === 'mousedown'\n        || (event.type === 'pointerdown' && event.pointerType === 'mouse')\n      ) && (\n        // No primary button (Usually the left button)\n        (isNumber(buttons) && buttons !== 1)\n        || (isNumber(button) && button !== 0)\n\n        // Open context menu\n        || event.ctrlKey\n      ))\n    ) {\n      return;\n    }\n\n    // Prevent default behaviours as page zooming in touch devices.\n    event.preventDefault();\n\n    if (event.changedTouches) {\n      forEach(event.changedTouches, (touch) => {\n        pointers[touch.identifier] = getPointer(touch);\n      });\n    } else {\n      pointers[event.pointerId || 0] = getPointer(event);\n    }\n\n    let action = options.movable ? ACTION_MOVE : false;\n\n    if (options.zoomOnTouch && options.zoomable && Object.keys(pointers).length > 1) {\n      action = ACTION_ZOOM;\n    } else if (options.slideOnTouch && (event.pointerType === 'touch' || event.type === 'touchstart') && this.isSwitchable()) {\n      action = ACTION_SWITCH;\n    }\n\n    if (options.transition && (action === ACTION_MOVE || action === ACTION_ZOOM)) {\n      removeClass(this.image, CLASS_TRANSITION);\n    }\n\n    this.action = action;\n  },\n\n  pointermove(event) {\n    const { pointers, action } = this;\n\n    if (!this.viewed || !action) {\n      return;\n    }\n\n    event.preventDefault();\n\n    if (event.changedTouches) {\n      forEach(event.changedTouches, (touch) => {\n        assign(pointers[touch.identifier] || {}, getPointer(touch, true));\n      });\n    } else {\n      assign(pointers[event.pointerId || 0] || {}, getPointer(event, true));\n    }\n\n    this.change(event);\n  },\n\n  pointerup(event) {\n    const { options, action, pointers } = this;\n    let pointer;\n\n    if (event.changedTouches) {\n      forEach(event.changedTouches, (touch) => {\n        pointer = pointers[touch.identifier];\n        delete pointers[touch.identifier];\n      });\n    } else {\n      pointer = pointers[event.pointerId || 0];\n      delete pointers[event.pointerId || 0];\n    }\n\n    if (!action) {\n      return;\n    }\n\n    event.preventDefault();\n\n    if (options.transition && (action === ACTION_MOVE || action === ACTION_ZOOM)) {\n      addClass(this.image, CLASS_TRANSITION);\n    }\n\n    this.action = false;\n\n    // Emulate click and double click in touch devices to support backdrop and image zooming (#210).\n    if (\n      IS_TOUCH_DEVICE\n      && action !== ACTION_ZOOM\n      && pointer\n      && (Date.now() - pointer.timeStamp < 500)\n    ) {\n      clearTimeout(this.clickCanvasTimeout);\n      clearTimeout(this.doubleClickImageTimeout);\n\n      if (options.toggleOnDblclick && this.viewed && event.target === this.image) {\n        if (this.imageClicked) {\n          this.imageClicked = false;\n\n          // This timeout will be cleared later when a native dblclick event is triggering\n          this.doubleClickImageTimeout = setTimeout(() => {\n            dispatchEvent(this.image, EVENT_DBLCLICK, {\n              originalEvent: event,\n            });\n          }, 50);\n        } else {\n          this.imageClicked = true;\n\n          // The default timing of a double click in Windows is 500 ms\n          this.doubleClickImageTimeout = setTimeout(() => {\n            this.imageClicked = false;\n          }, 500);\n        }\n      } else {\n        this.imageClicked = false;\n\n        if (options.backdrop && options.backdrop !== 'static' && event.target === this.canvas) {\n          // This timeout will be cleared later when a native click event is triggering\n          this.clickCanvasTimeout = setTimeout(() => {\n            dispatchEvent(this.canvas, EVENT_CLICK, {\n              originalEvent: event,\n            });\n          }, 50);\n        }\n      }\n    }\n  },\n\n  resize() {\n    if (!this.isShown || this.hiding) {\n      return;\n    }\n\n    if (this.fulled) {\n      this.close();\n      this.initBody();\n      this.open();\n    }\n\n    this.initContainer();\n    this.initViewer();\n    this.renderViewer();\n    this.renderList();\n\n    if (this.viewed) {\n      this.initImage(() => {\n        this.renderImage();\n      });\n    }\n\n    if (this.played) {\n      if (this.options.fullscreen && this.fulled && !(\n        document.fullscreenElement\n        || document.webkitFullscreenElement\n        || document.mozFullScreenElement\n        || document.msFullscreenElement\n      )) {\n        this.stop();\n        return;\n      }\n\n      forEach(this.player.getElementsByTagName('img'), (image) => {\n        addListener(image, EVENT_LOAD, this.loadImage.bind(this), {\n          once: true,\n        });\n        dispatchEvent(image, EVENT_LOAD);\n      });\n    }\n  },\n\n  wheel(event) {\n    if (!this.viewed) {\n      return;\n    }\n\n    event.preventDefault();\n\n    // Limit wheel speed to prevent zoom too fast\n    if (this.wheeling) {\n      return;\n    }\n\n    this.wheeling = true;\n\n    setTimeout(() => {\n      this.wheeling = false;\n    }, 50);\n\n    const ratio = Number(this.options.zoomRatio) || 0.1;\n    let delta = 1;\n\n    if (event.deltaY) {\n      delta = event.deltaY > 0 ? 1 : -1;\n    } else if (event.wheelDelta) {\n      delta = -event.wheelDelta / 120;\n    } else if (event.detail) {\n      delta = event.detail > 0 ? 1 : -1;\n    }\n\n    this.zoom(-delta * ratio, true, null, event);\n  },\n};\n"
  },
  {
    "path": "src/js/methods.js",
    "content": "import {\n  CLASS_ACTIVE,\n  CLASS_FADE,\n  CLASS_FIXED,\n  CLASS_FULLSCREEN_EXIT,\n  CLASS_HIDE,\n  CLASS_IN,\n  CLASS_INVISIBLE,\n  CLASS_LOADING,\n  CLASS_SHOW,\n  CLASS_TRANSITION,\n  EVENT_CLICK,\n  EVENT_ERROR,\n  EVENT_HIDE,\n  EVENT_LOAD,\n  EVENT_MOVE,\n  EVENT_MOVED,\n  EVENT_PLAY,\n  EVENT_ROTATE,\n  EVENT_ROTATED,\n  EVENT_SCALE,\n  EVENT_SCALED,\n  EVENT_SHOW,\n  EVENT_STOP,\n  EVENT_TRANSITION_END,\n  EVENT_VIEW,\n  EVENT_VIEWED,\n  EVENT_ZOOM,\n  EVENT_ZOOMED,\n  NAMESPACE,\n} from './constants';\nimport {\n  addClass,\n  addListener,\n  assign,\n  dispatchEvent,\n  escapeHTMLEntities,\n  forEach,\n  getData,\n  getOffset,\n  getPointersCenter,\n  hasClass,\n  isFunction,\n  isNumber,\n  isPlainObject,\n  isUndefined,\n  removeClass,\n  removeListener,\n  setStyle,\n  toggleClass,\n} from './utilities';\n\nexport default {\n  /** Show the viewer (only available in modal mode)\n   * @param {boolean} [immediate=false] - Indicates if show the viewer immediately or not.\n   * @returns {Viewer} this\n   */\n  show(immediate = false) {\n    const { element, options } = this;\n\n    if (options.inline || this.showing || this.isShown || this.showing) {\n      return this;\n    }\n\n    if (!this.ready) {\n      this.build();\n\n      if (this.ready) {\n        this.show(immediate);\n      }\n\n      return this;\n    }\n\n    if (isFunction(options.show)) {\n      addListener(element, EVENT_SHOW, options.show, {\n        once: true,\n      });\n    }\n\n    if (dispatchEvent(element, EVENT_SHOW) === false || !this.ready) {\n      return this;\n    }\n\n    if (this.hiding) {\n      this.transitioning.abort();\n    }\n\n    this.showing = true;\n    this.open();\n\n    const { viewer } = this;\n\n    removeClass(viewer, CLASS_HIDE);\n    viewer.setAttribute('role', 'dialog');\n    viewer.setAttribute('aria-labelledby', this.title.id);\n    viewer.setAttribute('aria-modal', true);\n    viewer.removeAttribute('aria-hidden');\n\n    if (options.transition && !immediate) {\n      const shown = this.shown.bind(this);\n\n      this.transitioning = {\n        abort: () => {\n          removeListener(viewer, EVENT_TRANSITION_END, shown);\n          removeClass(viewer, CLASS_IN);\n        },\n      };\n\n      addClass(viewer, CLASS_TRANSITION);\n\n      // Force reflow to enable CSS3 transition\n      viewer.initialOffsetWidth = viewer.offsetWidth;\n      addListener(viewer, EVENT_TRANSITION_END, shown, {\n        once: true,\n      });\n      addClass(viewer, CLASS_IN);\n    } else {\n      addClass(viewer, CLASS_IN);\n      this.shown();\n    }\n\n    return this;\n  },\n\n  /**\n   * Hide the viewer (only available in modal mode)\n   * @param {boolean} [immediate=false] - Indicates if hide the viewer immediately or not.\n   * @returns {Viewer} this\n   */\n  hide(immediate = false) {\n    const { element, options } = this;\n\n    if (options.inline || this.hiding || !(this.isShown || this.showing)) {\n      return this;\n    }\n\n    if (isFunction(options.hide)) {\n      addListener(element, EVENT_HIDE, options.hide, {\n        once: true,\n      });\n    }\n\n    if (dispatchEvent(element, EVENT_HIDE) === false) {\n      return this;\n    }\n\n    if (this.showing) {\n      this.transitioning.abort();\n    }\n\n    this.hiding = true;\n\n    if (this.played) {\n      this.stop();\n    } else if (this.viewing) {\n      this.viewing.abort();\n    }\n\n    const { viewer, image } = this;\n    const hideImmediately = () => {\n      removeClass(viewer, CLASS_IN);\n      this.hidden();\n    };\n\n    if (options.transition && !immediate) {\n      const onViewerTransitionEnd = (event) => {\n        // Ignore all propagating `transitionend` events (#275).\n        if (event && event.target === viewer) {\n          removeListener(viewer, EVENT_TRANSITION_END, onViewerTransitionEnd);\n          this.hidden();\n        }\n      };\n      const onImageTransitionEnd = () => {\n        // In case of show the viewer by `viewer.show(true)` previously (#407).\n        if (hasClass(viewer, CLASS_TRANSITION)) {\n          addListener(viewer, EVENT_TRANSITION_END, onViewerTransitionEnd);\n          removeClass(viewer, CLASS_IN);\n        } else {\n          hideImmediately();\n        }\n      };\n\n      this.transitioning = {\n        abort: () => {\n          if (this.viewed && hasClass(image, CLASS_TRANSITION)) {\n            removeListener(image, EVENT_TRANSITION_END, onImageTransitionEnd);\n          } else if (hasClass(viewer, CLASS_TRANSITION)) {\n            removeListener(viewer, EVENT_TRANSITION_END, onViewerTransitionEnd);\n          }\n        },\n      };\n\n      // In case of hiding the viewer when holding on the image (#255),\n      // note that the `CLASS_TRANSITION` class will be removed on pointer down.\n      if (this.viewed && hasClass(image, CLASS_TRANSITION)) {\n        addListener(image, EVENT_TRANSITION_END, onImageTransitionEnd, {\n          once: true,\n        });\n        this.zoomTo(0, false, null, null, true);\n      } else {\n        onImageTransitionEnd();\n      }\n    } else {\n      hideImmediately();\n    }\n\n    return this;\n  },\n\n  /**\n   * View one of the images with image's index\n   * @param {number} index - The index of the image to view.\n   * @returns {Viewer} this\n   */\n  view(index = this.options.initialViewIndex) {\n    index = Number(index) || 0;\n\n    if (this.hiding || this.played || index < 0 || index >= this.length\n      || (this.viewed && index === this.index)) {\n      return this;\n    }\n\n    if (!this.isShown) {\n      this.index = index;\n      return this.show();\n    }\n\n    if (this.viewing) {\n      this.viewing.abort();\n    }\n\n    const {\n      element,\n      options,\n      title,\n      canvas,\n    } = this;\n    const item = this.items[index];\n    const img = item.querySelector('img');\n    const url = getData(img, 'originalUrl');\n    const alt = img.getAttribute('alt');\n    const image = document.createElement('img');\n\n    forEach(options.inheritedAttributes, (name) => {\n      const value = img.getAttribute(name);\n\n      if (value !== null) {\n        image.setAttribute(name, value);\n      }\n    });\n\n    image.src = url;\n    image.alt = alt;\n\n    if (isFunction(options.view)) {\n      addListener(element, EVENT_VIEW, options.view, {\n        once: true,\n      });\n    }\n\n    if (dispatchEvent(element, EVENT_VIEW, {\n      originalImage: this.images[index],\n      index,\n      image,\n    }) === false || !this.isShown || this.hiding || this.played) {\n      return this;\n    }\n\n    const activeItem = this.items[this.index];\n\n    if (activeItem) {\n      removeClass(activeItem, CLASS_ACTIVE);\n      activeItem.removeAttribute('aria-selected');\n    }\n\n    addClass(item, CLASS_ACTIVE);\n    item.setAttribute('aria-selected', true);\n\n    if (options.focus) {\n      item.focus();\n    }\n\n    this.image = image;\n    this.viewed = false;\n    this.index = index;\n    this.imageData = {};\n    addClass(image, CLASS_INVISIBLE);\n\n    if (options.loading) {\n      addClass(canvas, CLASS_LOADING);\n    }\n\n    canvas.innerHTML = '';\n    canvas.appendChild(image);\n\n    // Center current item\n    this.renderList();\n\n    // Clear title\n    title.innerHTML = '';\n\n    // Generate title after viewed\n    const onViewed = () => {\n      const { imageData } = this;\n      const render = Array.isArray(options.title) ? options.title[1] : options.title;\n\n      title.innerHTML = escapeHTMLEntities(isFunction(render)\n        ? render.call(this, image, imageData)\n        : `${alt} (${imageData.naturalWidth} × ${imageData.naturalHeight})`);\n    };\n    let onLoad;\n    let onError;\n\n    addListener(element, EVENT_VIEWED, onViewed, {\n      once: true,\n    });\n\n    this.viewing = {\n      abort: () => {\n        removeListener(element, EVENT_VIEWED, onViewed);\n\n        if (image.complete) {\n          if (this.imageRendering) {\n            this.imageRendering.abort();\n          } else if (this.imageInitializing) {\n            this.imageInitializing.abort();\n          }\n        } else {\n          // Cancel download to save bandwidth.\n          image.src = '';\n          removeListener(image, EVENT_LOAD, onLoad);\n\n          if (this.timeout) {\n            clearTimeout(this.timeout);\n          }\n        }\n      },\n    };\n\n    if (image.complete) {\n      this.load();\n    } else {\n      addListener(image, EVENT_LOAD, onLoad = () => {\n        removeListener(image, EVENT_ERROR, onError);\n        this.load();\n      }, {\n        once: true,\n      });\n      addListener(image, EVENT_ERROR, onError = () => {\n        removeListener(image, EVENT_LOAD, onLoad);\n\n        if (this.timeout) {\n          clearTimeout(this.timeout);\n          this.timeout = false;\n        }\n\n        removeClass(image, CLASS_INVISIBLE);\n\n        if (options.loading) {\n          removeClass(this.canvas, CLASS_LOADING);\n        }\n      }, {\n        once: true,\n      });\n\n      if (this.timeout) {\n        clearTimeout(this.timeout);\n      }\n\n      // Make the image visible if it fails to load within 1s\n      this.timeout = setTimeout(() => {\n        removeClass(image, CLASS_INVISIBLE);\n        this.timeout = false;\n      }, 1000);\n    }\n\n    return this;\n  },\n\n  /**\n   * View the previous image\n   * @param {boolean} [loop=false] - Indicate if view the last one\n   * when it is the first one at present.\n   * @returns {Viewer} this\n   */\n  prev(loop = false) {\n    let index = this.index - 1;\n\n    if (index < 0) {\n      index = loop ? this.length - 1 : 0;\n    }\n\n    this.view(index);\n    return this;\n  },\n\n  /**\n   * View the next image\n   * @param {boolean} [loop=false] - Indicate if view the first one\n   * when it is the last one at present.\n   * @returns {Viewer} this\n   */\n  next(loop = false) {\n    const maxIndex = this.length - 1;\n    let index = this.index + 1;\n\n    if (index > maxIndex) {\n      index = loop ? 0 : maxIndex;\n    }\n\n    this.view(index);\n    return this;\n  },\n\n  /**\n   * Move the image with relative offsets.\n   * @param {number} x - The moving distance in the horizontal direction.\n   * @param {number} [y=x] The moving distance in the vertical direction.\n   * @returns {Viewer} this\n   */\n  move(x, y = x) {\n    const { imageData } = this;\n\n    this.moveTo(\n      isUndefined(x) ? x : imageData.x + Number(x),\n      isUndefined(y) ? y : imageData.y + Number(y),\n    );\n\n    return this;\n  },\n\n  /**\n   * Move the image to an absolute point.\n   * @param {number} x - The new position in the horizontal direction.\n   * @param {number} [y=x] - The new position in the vertical direction.\n   * @param {Event} [_originalEvent=null] - The original event if any.\n   * @returns {Viewer} this\n   */\n  moveTo(x, y = x, _originalEvent = null) {\n    const { element, options, imageData } = this;\n\n    x = Number(x);\n    y = Number(y);\n\n    if (this.viewed && !this.played && options.movable) {\n      const oldX = imageData.x;\n      const oldY = imageData.y;\n      let changed = false;\n\n      if (isNumber(x)) {\n        changed = true;\n      } else {\n        x = oldX;\n      }\n\n      if (isNumber(y)) {\n        changed = true;\n      } else {\n        y = oldY;\n      }\n\n      if (changed) {\n        if (isFunction(options.move)) {\n          addListener(element, EVENT_MOVE, options.move, {\n            once: true,\n          });\n        }\n\n        if (dispatchEvent(element, EVENT_MOVE, {\n          x,\n          y,\n          oldX,\n          oldY,\n          originalEvent: _originalEvent,\n        }) === false) {\n          return this;\n        }\n\n        imageData.x = x;\n        imageData.y = y;\n        imageData.left = x;\n        imageData.top = y;\n        this.moving = true;\n        this.renderImage(() => {\n          this.moving = false;\n\n          if (isFunction(options.moved)) {\n            addListener(element, EVENT_MOVED, options.moved, {\n              once: true,\n            });\n          }\n\n          dispatchEvent(element, EVENT_MOVED, {\n            x,\n            y,\n            oldX,\n            oldY,\n            originalEvent: _originalEvent,\n          }, {\n            cancelable: false,\n          });\n        });\n      }\n    }\n\n    return this;\n  },\n\n  /**\n   * Rotate the image with a relative degree.\n   * @param {number} degree - The rotate degree.\n   * @returns {Viewer} this\n   */\n  rotate(degree) {\n    this.rotateTo((this.imageData.rotate || 0) + Number(degree));\n\n    return this;\n  },\n\n  /**\n   * Rotate the image to an absolute degree.\n   * @param {number} degree - The rotate degree.\n   * @returns {Viewer} this\n   */\n  rotateTo(degree) {\n    const { element, options, imageData } = this;\n\n    degree = Number(degree);\n\n    if (isNumber(degree) && this.viewed && !this.played && options.rotatable) {\n      const oldDegree = imageData.rotate;\n\n      if (isFunction(options.rotate)) {\n        addListener(element, EVENT_ROTATE, options.rotate, {\n          once: true,\n        });\n      }\n\n      if (dispatchEvent(element, EVENT_ROTATE, {\n        degree,\n        oldDegree,\n      }) === false) {\n        return this;\n      }\n\n      imageData.rotate = degree;\n      this.rotating = true;\n      this.renderImage(() => {\n        this.rotating = false;\n\n        if (isFunction(options.rotated)) {\n          addListener(element, EVENT_ROTATED, options.rotated, {\n            once: true,\n          });\n        }\n\n        dispatchEvent(element, EVENT_ROTATED, {\n          degree,\n          oldDegree,\n        }, {\n          cancelable: false,\n        });\n      });\n    }\n\n    return this;\n  },\n\n  /**\n   * Scale the image on the x-axis.\n   * @param {number} scaleX - The scale ratio on the x-axis.\n   * @returns {Viewer} this\n   */\n  scaleX(scaleX) {\n    this.scale(scaleX, this.imageData.scaleY);\n\n    return this;\n  },\n\n  /**\n   * Scale the image on the y-axis.\n   * @param {number} scaleY - The scale ratio on the y-axis.\n   * @returns {Viewer} this\n   */\n  scaleY(scaleY) {\n    this.scale(this.imageData.scaleX, scaleY);\n\n    return this;\n  },\n\n  /**\n   * Scale the image.\n   * @param {number} scaleX - The scale ratio on the x-axis.\n   * @param {number} [scaleY=scaleX] - The scale ratio on the y-axis.\n   * @returns {Viewer} this\n   */\n  scale(scaleX, scaleY = scaleX) {\n    const { element, options, imageData } = this;\n\n    scaleX = Number(scaleX);\n    scaleY = Number(scaleY);\n\n    if (this.viewed && !this.played && options.scalable) {\n      const oldScaleX = imageData.scaleX;\n      const oldScaleY = imageData.scaleY;\n      let changed = false;\n\n      if (isNumber(scaleX)) {\n        changed = true;\n      } else {\n        scaleX = oldScaleX;\n      }\n\n      if (isNumber(scaleY)) {\n        changed = true;\n      } else {\n        scaleY = oldScaleY;\n      }\n\n      if (changed) {\n        if (isFunction(options.scale)) {\n          addListener(element, EVENT_SCALE, options.scale, {\n            once: true,\n          });\n        }\n\n        if (dispatchEvent(element, EVENT_SCALE, {\n          scaleX,\n          scaleY,\n          oldScaleX,\n          oldScaleY,\n        }) === false) {\n          return this;\n        }\n\n        imageData.scaleX = scaleX;\n        imageData.scaleY = scaleY;\n        this.scaling = true;\n        this.renderImage(() => {\n          this.scaling = false;\n\n          if (isFunction(options.scaled)) {\n            addListener(element, EVENT_SCALED, options.scaled, {\n              once: true,\n            });\n          }\n\n          dispatchEvent(element, EVENT_SCALED, {\n            scaleX,\n            scaleY,\n            oldScaleX,\n            oldScaleY,\n          }, {\n            cancelable: false,\n          });\n        });\n      }\n    }\n\n    return this;\n  },\n\n  /**\n   * Zoom the image with a relative ratio.\n   * @param {number} ratio - The target ratio.\n   * @param {boolean} [showTooltip=false] - Indicates whether to show the tooltip.\n   * @param {Object} [pivot] - The pivot point coordinate for zooming.\n   * @param {Event} [_originalEvent=null] - The original event if any.\n   * @returns {Viewer} this\n   */\n  zoom(ratio, showTooltip = false, pivot = null, _originalEvent = null) {\n    const { imageData } = this;\n\n    ratio = Number(ratio);\n\n    if (ratio < 0) {\n      ratio = 1 / (1 - ratio);\n    } else {\n      ratio = 1 + ratio;\n    }\n\n    this.zoomTo(\n      (imageData.width * ratio) / imageData.naturalWidth,\n      showTooltip,\n      pivot,\n      _originalEvent,\n    );\n\n    return this;\n  },\n\n  /**\n   * Zoom the image to an absolute ratio.\n   * @param {number} ratio - The target ratio.\n   * @param {boolean} [showTooltip] - Indicates whether to show the tooltip.\n   * @param {Object} [pivot] - The pivot point coordinate for zooming.\n   * @param {Event} [_originalEvent=null] - The original event if any.\n   * @param {Event} [_zoomable=false] - Indicates if the current zoom is available or not.\n   * @returns {Viewer} this\n   */\n  zoomTo(ratio, showTooltip = false, pivot = null, _originalEvent = null, _zoomable = false) {\n    const {\n      element,\n      options,\n      pointers,\n      imageData,\n    } = this;\n    const {\n      x,\n      y,\n      width,\n      height,\n      naturalWidth,\n      naturalHeight,\n    } = imageData;\n\n    ratio = Math.max(0, ratio);\n\n    if (isNumber(ratio) && this.viewed && !this.played && (_zoomable || options.zoomable)) {\n      if (!_zoomable) {\n        const minZoomRatio = Math.max(0.01, options.minZoomRatio);\n        const maxZoomRatio = Math.min(100, options.maxZoomRatio);\n\n        ratio = Math.min(Math.max(ratio, minZoomRatio), maxZoomRatio);\n      }\n\n      if (_originalEvent) {\n        switch (_originalEvent.type) {\n          case 'wheel':\n            if (options.zoomRatio >= 0.055 && ratio > 0.95 && ratio < 1.05) {\n              ratio = 1;\n            }\n            break;\n\n          case 'pointermove':\n          case 'touchmove':\n          case 'mousemove':\n            if (ratio > 0.99 && ratio < 1.01) {\n              ratio = 1;\n            }\n            break;\n\n          default:\n        }\n      }\n\n      const newWidth = naturalWidth * ratio;\n      const newHeight = naturalHeight * ratio;\n      const offsetWidth = newWidth - width;\n      const offsetHeight = newHeight - height;\n      const oldRatio = imageData.ratio;\n\n      if (isFunction(options.zoom)) {\n        addListener(element, EVENT_ZOOM, options.zoom, {\n          once: true,\n        });\n      }\n\n      if (dispatchEvent(element, EVENT_ZOOM, {\n        ratio,\n        oldRatio,\n        originalEvent: _originalEvent,\n      }) === false) {\n        return this;\n      }\n\n      this.zooming = true;\n\n      if (_originalEvent) {\n        const offset = getOffset(this.viewer);\n        const center = pointers && Object.keys(pointers).length > 0\n          ? getPointersCenter(pointers)\n          : {\n            pageX: _originalEvent.pageX,\n            pageY: _originalEvent.pageY,\n          };\n\n        // Zoom from the triggering point of the event\n        imageData.x -= offsetWidth * (((center.pageX - offset.left) - x) / width);\n        imageData.y -= offsetHeight * (((center.pageY - offset.top) - y) / height);\n      } else if (isPlainObject(pivot) && isNumber(pivot.x) && isNumber(pivot.y)) {\n        imageData.x -= offsetWidth * ((pivot.x - x) / width);\n        imageData.y -= offsetHeight * ((pivot.y - y) / height);\n      } else {\n        // Zoom from the center of the image\n        imageData.x -= offsetWidth / 2;\n        imageData.y -= offsetHeight / 2;\n      }\n\n      imageData.left = imageData.x;\n      imageData.top = imageData.y;\n      imageData.width = newWidth;\n      imageData.height = newHeight;\n      imageData.oldRatio = oldRatio;\n      imageData.ratio = ratio;\n      this.renderImage(() => {\n        this.zooming = false;\n\n        if (isFunction(options.zoomed)) {\n          addListener(element, EVENT_ZOOMED, options.zoomed, {\n            once: true,\n          });\n        }\n\n        dispatchEvent(element, EVENT_ZOOMED, {\n          ratio,\n          oldRatio,\n          originalEvent: _originalEvent,\n        }, {\n          cancelable: false,\n        });\n      });\n\n      if (showTooltip) {\n        this.tooltip();\n      }\n    }\n\n    return this;\n  },\n\n  /**\n   * Play the images\n   * @param {boolean|FullscreenOptions} [fullscreen=false] - Indicate if request fullscreen or not.\n   * @returns {Viewer} this\n   */\n  play(fullscreen = false) {\n    if (!this.isShown || this.played) {\n      return this;\n    }\n\n    const { element, options } = this;\n\n    if (isFunction(options.play)) {\n      addListener(element, EVENT_PLAY, options.play, {\n        once: true,\n      });\n    }\n\n    if (dispatchEvent(element, EVENT_PLAY) === false) {\n      return this;\n    }\n\n    const { player } = this;\n    const onLoad = this.loadImage.bind(this);\n    const list = [];\n    let total = 0;\n    let index = 0;\n\n    this.played = true;\n    this.onLoadWhenPlay = onLoad;\n\n    if (fullscreen) {\n      this.requestFullscreen(fullscreen);\n    }\n\n    addClass(player, CLASS_SHOW);\n    forEach(this.items, (item, i) => {\n      const img = item.querySelector('img');\n      const image = document.createElement('img');\n\n      image.src = getData(img, 'originalUrl');\n      image.alt = img.getAttribute('alt');\n      image.referrerPolicy = img.referrerPolicy;\n      total += 1;\n      addClass(image, CLASS_FADE);\n      toggleClass(image, CLASS_TRANSITION, options.transition);\n\n      if (hasClass(item, CLASS_ACTIVE)) {\n        addClass(image, CLASS_IN);\n        index = i;\n      }\n\n      list.push(image);\n      addListener(image, EVENT_LOAD, onLoad, {\n        once: true,\n      });\n      player.appendChild(image);\n    });\n\n    if (isNumber(options.interval) && options.interval > 0) {\n      const prev = () => {\n        clearTimeout(this.playing.timeout);\n        removeClass(list[index], CLASS_IN);\n        index -= 1;\n        index = index >= 0 ? index : total - 1;\n        addClass(list[index], CLASS_IN);\n        this.playing.timeout = setTimeout(prev, options.interval);\n      };\n      const next = () => {\n        clearTimeout(this.playing.timeout);\n        removeClass(list[index], CLASS_IN);\n        index += 1;\n        index = index < total ? index : 0;\n        addClass(list[index], CLASS_IN);\n        this.playing.timeout = setTimeout(next, options.interval);\n      };\n\n      if (total > 1) {\n        this.playing = {\n          prev,\n          next,\n          timeout: setTimeout(next, options.interval),\n        };\n      }\n    }\n\n    return this;\n  },\n\n  // Stop play\n  stop() {\n    if (!this.played) {\n      return this;\n    }\n\n    const { element, options } = this;\n\n    if (isFunction(options.stop)) {\n      addListener(element, EVENT_STOP, options.stop, {\n        once: true,\n      });\n    }\n\n    if (dispatchEvent(element, EVENT_STOP) === false) {\n      return this;\n    }\n\n    const { player } = this;\n\n    clearTimeout(this.playing.timeout);\n    this.playing = false;\n    this.played = false;\n    forEach(player.getElementsByTagName('img'), (image) => {\n      removeListener(image, EVENT_LOAD, this.onLoadWhenPlay);\n    });\n    removeClass(player, CLASS_SHOW);\n    player.innerHTML = '';\n    this.exitFullscreen();\n\n    return this;\n  },\n\n  // Enter modal mode (only available in inline mode)\n  full() {\n    const {\n      options,\n      viewer,\n      image,\n      list,\n    } = this;\n\n    if (!this.isShown || this.played || this.fulled || !options.inline) {\n      return this;\n    }\n\n    this.fulled = true;\n    this.open();\n    addClass(this.button, CLASS_FULLSCREEN_EXIT);\n\n    if (options.transition) {\n      removeClass(list, CLASS_TRANSITION);\n\n      if (this.viewed) {\n        removeClass(image, CLASS_TRANSITION);\n      }\n    }\n\n    addClass(viewer, CLASS_FIXED);\n    viewer.setAttribute('role', 'dialog');\n    viewer.setAttribute('aria-labelledby', this.title.id);\n    viewer.setAttribute('aria-modal', true);\n    viewer.removeAttribute('style');\n    setStyle(viewer, {\n      zIndex: options.zIndex,\n    });\n\n    if (options.focus) {\n      this.enforceFocus();\n    }\n\n    this.initContainer();\n    this.viewerData = assign({}, this.containerData);\n    this.renderList();\n\n    if (this.viewed) {\n      this.initImage(() => {\n        this.renderImage(() => {\n          if (options.transition) {\n            setTimeout(() => {\n              addClass(image, CLASS_TRANSITION);\n              addClass(list, CLASS_TRANSITION);\n            }, 0);\n          }\n        });\n      });\n    }\n\n    return this;\n  },\n\n  // Exit modal mode (only available in inline mode)\n  exit() {\n    const {\n      options,\n      viewer,\n      image,\n      list,\n    } = this;\n\n    if (!this.isShown || this.played || !this.fulled || !options.inline) {\n      return this;\n    }\n\n    this.fulled = false;\n    this.close();\n    removeClass(this.button, CLASS_FULLSCREEN_EXIT);\n\n    if (options.transition) {\n      removeClass(list, CLASS_TRANSITION);\n\n      if (this.viewed) {\n        removeClass(image, CLASS_TRANSITION);\n      }\n    }\n\n    if (options.focus) {\n      this.clearEnforceFocus();\n    }\n\n    viewer.removeAttribute('role');\n    viewer.removeAttribute('aria-labelledby');\n    viewer.removeAttribute('aria-modal');\n    removeClass(viewer, CLASS_FIXED);\n    setStyle(viewer, {\n      zIndex: options.zIndexInline,\n    });\n\n    this.viewerData = assign({}, this.parentData);\n    this.renderViewer();\n    this.renderList();\n\n    if (this.viewed) {\n      this.initImage(() => {\n        this.renderImage(() => {\n          if (options.transition) {\n            setTimeout(() => {\n              addClass(image, CLASS_TRANSITION);\n              addClass(list, CLASS_TRANSITION);\n            }, 0);\n          }\n        });\n      });\n    }\n\n    return this;\n  },\n\n  // Show the current ratio of the image with percentage\n  tooltip() {\n    const { options, tooltipBox, imageData } = this;\n\n    if (!this.viewed || this.played || !options.tooltip) {\n      return this;\n    }\n\n    tooltipBox.textContent = `${Math.round(imageData.ratio * 100)}%`;\n\n    if (!this.tooltipping) {\n      if (options.transition) {\n        if (this.fading) {\n          dispatchEvent(tooltipBox, EVENT_TRANSITION_END);\n        }\n\n        addClass(tooltipBox, CLASS_SHOW);\n        addClass(tooltipBox, CLASS_FADE);\n        addClass(tooltipBox, CLASS_TRANSITION);\n        tooltipBox.removeAttribute('aria-hidden');\n\n        // Force reflow to enable CSS3 transition\n        tooltipBox.initialOffsetWidth = tooltipBox.offsetWidth;\n        addClass(tooltipBox, CLASS_IN);\n      } else {\n        addClass(tooltipBox, CLASS_SHOW);\n        tooltipBox.removeAttribute('aria-hidden');\n      }\n    } else {\n      clearTimeout(this.tooltipping);\n    }\n\n    this.tooltipping = setTimeout(() => {\n      if (options.transition) {\n        addListener(tooltipBox, EVENT_TRANSITION_END, () => {\n          removeClass(tooltipBox, CLASS_SHOW);\n          removeClass(tooltipBox, CLASS_FADE);\n          removeClass(tooltipBox, CLASS_TRANSITION);\n          tooltipBox.setAttribute('aria-hidden', true);\n          this.fading = false;\n        }, {\n          once: true,\n        });\n\n        removeClass(tooltipBox, CLASS_IN);\n        this.fading = true;\n      } else {\n        removeClass(tooltipBox, CLASS_SHOW);\n        tooltipBox.setAttribute('aria-hidden', true);\n      }\n\n      this.tooltipping = false;\n    }, 1000);\n\n    return this;\n  },\n\n  /**\n   * Toggle the image size between its current size and natural size\n   * @param {Event} [_originalEvent=null] - The original event if any.\n   * @returns {Viewer} this\n   */\n  toggle(_originalEvent = null) {\n    if (this.imageData.ratio === 1) {\n      this.zoomTo(this.imageData.oldRatio, true, null, _originalEvent);\n    } else {\n      this.zoomTo(1, true, null, _originalEvent);\n    }\n\n    return this;\n  },\n\n  // Reset the image to its initial state\n  reset() {\n    if (this.viewed && !this.played) {\n      this.imageData = assign({}, this.initialImageData);\n      this.renderImage();\n    }\n\n    return this;\n  },\n\n  // Update viewer when images changed\n  update() {\n    const { element, options, isImg } = this;\n\n    // Destroy viewer if the target image was deleted\n    if (isImg && !element.parentNode) {\n      return this.destroy();\n    }\n\n    const images = [];\n\n    forEach(isImg ? [element] : element.querySelectorAll('img'), (image) => {\n      if (isFunction(options.filter)) {\n        if (options.filter.call(this, image)) {\n          images.push(image);\n        }\n      } else if (this.getImageURL(image)) {\n        images.push(image);\n      }\n    });\n\n    if (!images.length) {\n      return this;\n    }\n\n    this.images = images;\n    this.length = images.length;\n\n    if (this.ready) {\n      const changedIndexes = [];\n\n      forEach(this.items, (item, i) => {\n        const img = item.querySelector('img');\n        const image = images[i];\n\n        if (image && img) {\n          if (\n            image.src !== img.src\n\n            // Title changed (#408)\n            || image.alt !== img.alt\n          ) {\n            changedIndexes.push(i);\n          }\n        } else {\n          changedIndexes.push(i);\n        }\n      });\n\n      setStyle(this.list, {\n        width: 'auto',\n      });\n\n      this.initList();\n\n      if (this.isShown) {\n        if (this.length) {\n          if (this.viewed) {\n            const changedIndex = changedIndexes.indexOf(this.index);\n\n            if (changedIndex >= 0) {\n              this.viewed = false;\n              this.view(Math.max(Math.min(this.index - changedIndex, this.length - 1), 0));\n            } else {\n              const activeItem = this.items[this.index];\n\n              // Reactivate the current viewing item after reset the list.\n              addClass(activeItem, CLASS_ACTIVE);\n              activeItem.setAttribute('aria-selected', true);\n            }\n          }\n        } else {\n          this.image = null;\n          this.viewed = false;\n          this.index = 0;\n          this.imageData = {};\n          this.canvas.innerHTML = '';\n          this.title.innerHTML = '';\n        }\n      }\n    } else {\n      this.build();\n    }\n\n    return this;\n  },\n\n  // Destroy the viewer\n  destroy() {\n    const { element, options } = this;\n\n    if (!element[NAMESPACE]) {\n      return this;\n    }\n\n    this.destroyed = true;\n\n    if (this.ready) {\n      if (this.played) {\n        this.stop();\n      }\n\n      if (options.inline) {\n        if (this.fulled) {\n          this.exit();\n        }\n\n        this.unbind();\n      } else if (this.isShown) {\n        if (this.viewing) {\n          if (this.imageRendering) {\n            this.imageRendering.abort();\n          } else if (this.imageInitializing) {\n            this.imageInitializing.abort();\n          }\n        }\n\n        if (this.hiding) {\n          this.transitioning.abort();\n        }\n\n        this.hidden();\n      } else if (this.showing) {\n        this.transitioning.abort();\n        this.hidden();\n      }\n\n      this.ready = false;\n      this.viewer.parentNode.removeChild(this.viewer);\n    } else if (options.inline) {\n      if (this.delaying) {\n        this.delaying.abort();\n      } else if (this.initializing) {\n        this.initializing.abort();\n      }\n    }\n\n    if (!options.inline) {\n      removeListener(element, EVENT_CLICK, this.onStart);\n    }\n\n    element[NAMESPACE] = undefined;\n    return this;\n  },\n};\n"
  },
  {
    "path": "src/js/others.js",
    "content": "import {\n  ACTION_MOVE,\n  ACTION_SWITCH,\n  ACTION_ZOOM,\n  CLASS_HIDE,\n  CLASS_OPEN,\n  EVENT_FOCUSIN,\n  EVENT_HIDDEN,\n  EVENT_SHOWN,\n} from './constants';\nimport {\n  addClass,\n  addListener,\n  dispatchEvent,\n  forEach,\n  getMaxZoomRatio,\n  isFunction,\n  isPlainObject,\n  isString,\n  removeClass,\n  removeListener,\n} from './utilities';\n\nexport default {\n  getImageURL(image) {\n    let { url } = this.options;\n\n    if (isString(url)) {\n      url = image.getAttribute(url);\n    } else if (isFunction(url)) {\n      url = url.call(this, image);\n    } else {\n      url = '';\n    }\n\n    return url;\n  },\n\n  enforceFocus() {\n    this.clearEnforceFocus();\n    addListener(document, EVENT_FOCUSIN, (this.onFocusin = (event) => {\n      const { viewer } = this;\n      let { target } = event;\n\n      if (target === document || target === viewer || viewer.contains(target)) {\n        return;\n      }\n\n      while (target) {\n        // Avoid conflicts with other modals (#474, #540)\n        if ((target.getAttribute('tabindex') !== null || target.getAttribute('aria-modal') === 'true')) {\n          return;\n        }\n\n        target = target.parentElement;\n      }\n\n      viewer.focus();\n    }));\n  },\n\n  clearEnforceFocus() {\n    if (this.onFocusin) {\n      removeListener(document, EVENT_FOCUSIN, this.onFocusin);\n      this.onFocusin = null;\n    }\n  },\n\n  open() {\n    const { body } = this;\n\n    addClass(body, CLASS_OPEN);\n\n    if (this.scrollbarWidth > 0) {\n      body.style.paddingRight = `${this.scrollbarWidth + (parseFloat(this.initialBodyComputedPaddingRight) || 0)}px`;\n    }\n  },\n\n  close() {\n    const { body } = this;\n\n    removeClass(body, CLASS_OPEN);\n\n    if (this.scrollbarWidth > 0) {\n      body.style.paddingRight = this.initialBodyPaddingRight;\n    }\n  },\n\n  shown() {\n    const { element, options, viewer } = this;\n\n    this.fulled = true;\n    this.isShown = true;\n    this.render();\n    this.bind();\n    this.showing = false;\n\n    if (options.focus) {\n      viewer.focus();\n      this.enforceFocus();\n    }\n\n    if (isFunction(options.shown)) {\n      addListener(element, EVENT_SHOWN, options.shown, {\n        once: true,\n      });\n    }\n\n    if (dispatchEvent(element, EVENT_SHOWN) === false) {\n      return;\n    }\n\n    if (this.ready && this.isShown && !this.hiding) {\n      this.view(this.index);\n    }\n  },\n\n  hidden() {\n    const { element, options, viewer } = this;\n\n    if (options.fucus) {\n      this.clearEnforceFocus();\n    }\n\n    this.close();\n    this.unbind();\n    addClass(viewer, CLASS_HIDE);\n    viewer.removeAttribute('role');\n    viewer.removeAttribute('aria-labelledby');\n    viewer.removeAttribute('aria-modal');\n    viewer.setAttribute('aria-hidden', true);\n    this.resetList();\n    this.resetImage();\n    this.fulled = false;\n    this.viewed = false;\n    this.isShown = false;\n    this.hiding = false;\n\n    if (!this.destroyed) {\n      if (isFunction(options.hidden)) {\n        addListener(element, EVENT_HIDDEN, options.hidden, {\n          once: true,\n        });\n      }\n\n      dispatchEvent(element, EVENT_HIDDEN, null, {\n        cancelable: false,\n      });\n    }\n  },\n\n  requestFullscreen(options) {\n    const document = this.element.ownerDocument;\n\n    if (this.fulled && !(\n      document.fullscreenElement\n      || document.webkitFullscreenElement\n      || document.mozFullScreenElement\n      || document.msFullscreenElement\n    )) {\n      const { documentElement } = document;\n\n      // Element.requestFullscreen()\n      if (documentElement.requestFullscreen) {\n        // Avoid TypeError when convert `options` to dictionary\n        if (isPlainObject(options)) {\n          documentElement.requestFullscreen(options);\n        } else {\n          documentElement.requestFullscreen();\n        }\n      } else if (documentElement.webkitRequestFullscreen) {\n        documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);\n      } else if (documentElement.mozRequestFullScreen) {\n        documentElement.mozRequestFullScreen();\n      } else if (documentElement.msRequestFullscreen) {\n        documentElement.msRequestFullscreen();\n      }\n    }\n  },\n\n  exitFullscreen() {\n    const document = this.element.ownerDocument;\n\n    if (this.fulled && (\n      document.fullscreenElement\n      || document.webkitFullscreenElement\n      || document.mozFullScreenElement\n      || document.msFullscreenElement\n    )) {\n      // Document.exitFullscreen()\n      if (document.exitFullscreen) {\n        document.exitFullscreen();\n      } else if (document.webkitExitFullscreen) {\n        document.webkitExitFullscreen();\n      } else if (document.mozCancelFullScreen) {\n        document.mozCancelFullScreen();\n      } else if (document.msExitFullscreen) {\n        document.msExitFullscreen();\n      }\n    }\n  },\n\n  change(event) {\n    const { options, pointers } = this;\n    const pointer = pointers[Object.keys(pointers)[0]];\n\n    // In the case of the `pointers` object is empty (#421)\n    if (!pointer) {\n      return;\n    }\n\n    const offsetX = pointer.endX - pointer.startX;\n    const offsetY = pointer.endY - pointer.startY;\n\n    switch (this.action) {\n      // Move the current image\n      case ACTION_MOVE:\n        if (offsetX !== 0 || offsetY !== 0) {\n          this.pointerMoved = true;\n          this.move(offsetX, offsetY, event);\n        }\n        break;\n\n      // Zoom the current image\n      case ACTION_ZOOM:\n        this.zoom(getMaxZoomRatio(pointers), false, null, event);\n        break;\n\n      case ACTION_SWITCH: {\n        this.action = 'switched';\n\n        const absoluteOffsetX = Math.abs(offsetX);\n\n        if (absoluteOffsetX > 1 && absoluteOffsetX > Math.abs(offsetY)) {\n          // Empty `pointers` as `touchend` event will not be fired after swiped in iOS browsers.\n          this.pointers = {};\n\n          if (offsetX > 1) {\n            this.prev(options.loop);\n          } else if (offsetX < -1) {\n            this.next(options.loop);\n          }\n        }\n\n        break;\n      }\n\n      default:\n    }\n\n    // Override\n    forEach(pointers, (p) => {\n      p.startX = p.endX;\n      p.startY = p.endY;\n    });\n  },\n\n  isSwitchable() {\n    const { imageData, viewerData } = this;\n\n    return this.length > 1 && imageData.x >= 0 && imageData.y >= 0\n      && imageData.width <= viewerData.width\n      && imageData.height <= viewerData.height;\n  },\n};\n"
  },
  {
    "path": "src/js/render.js",
    "content": "import {\n  CLASS_LOADING,\n  CLASS_TRANSITION,\n  EVENT_ERROR,\n  EVENT_LOAD,\n  EVENT_TRANSITION_END,\n  EVENT_VIEWED,\n} from './constants';\nimport {\n  addClass,\n  addListener,\n  assign,\n  forEach,\n  getImageNameFromURL,\n  getImageNaturalSizes,\n  getTransforms,\n  hasClass,\n  isNumber,\n  removeClass,\n  removeListener,\n  setData,\n  setStyle,\n} from './utilities';\n\nexport default {\n  render() {\n    this.initContainer();\n    this.initViewer();\n    this.initList();\n    this.renderViewer();\n  },\n\n  initBody() {\n    const { ownerDocument } = this.element;\n    const body = ownerDocument.body || ownerDocument.documentElement;\n\n    this.body = body;\n    this.scrollbarWidth = window.innerWidth - ownerDocument.documentElement.clientWidth;\n    this.initialBodyPaddingRight = body.style.paddingRight;\n    this.initialBodyComputedPaddingRight = window.getComputedStyle(body).paddingRight;\n  },\n\n  initContainer() {\n    this.containerData = {\n      width: window.innerWidth,\n      height: window.innerHeight,\n    };\n  },\n\n  initViewer() {\n    const { options, parent } = this;\n    let viewerData;\n\n    if (options.inline) {\n      viewerData = {\n        width: Math.max(parent.offsetWidth, options.minWidth),\n        height: Math.max(parent.offsetHeight, options.minHeight),\n      };\n\n      this.parentData = viewerData;\n    }\n\n    if (this.fulled || !viewerData) {\n      viewerData = this.containerData;\n    }\n\n    this.viewerData = assign({}, viewerData);\n  },\n\n  renderViewer() {\n    if (this.options.inline && !this.fulled) {\n      setStyle(this.viewer, this.viewerData);\n    }\n  },\n\n  initList() {\n    const { element, options, list } = this;\n    const items = [];\n\n    // initList may be called in this.update, so should keep idempotent\n    list.innerHTML = '';\n\n    forEach(this.images, (image, index) => {\n      const { src } = image;\n      const alt = image.alt || getImageNameFromURL(src);\n      const url = this.getImageURL(image);\n\n      if (src || url) {\n        const item = document.createElement('li');\n        const img = document.createElement('img');\n\n        forEach(options.inheritedAttributes, (name) => {\n          const value = image.getAttribute(name);\n\n          if (value !== null) {\n            img.setAttribute(name, value);\n          }\n        });\n\n        if (options.navbar) {\n          img.src = src || url;\n        }\n\n        img.alt = alt;\n        img.setAttribute('data-original-url', url || src);\n        item.setAttribute('data-index', index);\n        item.setAttribute('data-viewer-action', 'view');\n        item.setAttribute('role', 'button');\n\n        if (options.keyboard) {\n          item.setAttribute('tabindex', 0);\n        }\n\n        item.appendChild(img);\n        list.appendChild(item);\n        items.push(item);\n      }\n    });\n\n    this.items = items;\n\n    forEach(items, (item) => {\n      const image = item.firstElementChild;\n      let onLoad;\n      let onError;\n\n      setData(image, 'filled', true);\n\n      if (options.loading) {\n        addClass(item, CLASS_LOADING);\n      }\n\n      addListener(image, EVENT_LOAD, onLoad = (event) => {\n        removeListener(image, EVENT_ERROR, onError);\n\n        if (options.loading) {\n          removeClass(item, CLASS_LOADING);\n        }\n\n        this.loadImage(event);\n      }, {\n        once: true,\n      });\n      addListener(image, EVENT_ERROR, onError = () => {\n        removeListener(image, EVENT_LOAD, onLoad);\n\n        if (options.loading) {\n          removeClass(item, CLASS_LOADING);\n        }\n      }, {\n        once: true,\n      });\n    });\n\n    if (options.transition) {\n      addListener(element, EVENT_VIEWED, () => {\n        addClass(list, CLASS_TRANSITION);\n      }, {\n        once: true,\n      });\n    }\n  },\n\n  renderList() {\n    const { index } = this;\n    const item = this.items[index];\n\n    if (!item) {\n      return;\n    }\n\n    const next = item.nextElementSibling;\n    const gutter = parseInt(window.getComputedStyle(next || item).marginLeft, 10);\n    const { offsetWidth } = item;\n    const outerWidth = offsetWidth + gutter;\n\n    // Place the active item in the center of the screen\n    setStyle(this.list, assign({\n      width: outerWidth * this.length - gutter,\n    }, getTransforms({\n      translateX: ((this.viewerData.width - offsetWidth) / 2) - outerWidth * index,\n    })));\n  },\n\n  resetList() {\n    const { list } = this;\n\n    list.innerHTML = '';\n    removeClass(list, CLASS_TRANSITION);\n    setStyle(list, assign({\n      width: 0,\n    }, getTransforms({\n      translateX: 0,\n    })));\n  },\n\n  initImage(done) {\n    const { options, image, viewerData } = this;\n    const footerHeight = this.footer.offsetHeight;\n    const viewerWidth = viewerData.width;\n    const viewerHeight = Math.max(viewerData.height - footerHeight, footerHeight);\n    const oldImageData = this.imageData || {};\n    let sizingImage;\n\n    this.imageInitializing = {\n      abort: () => {\n        sizingImage.onload = null;\n      },\n    };\n\n    sizingImage = getImageNaturalSizes(image, options, (naturalWidth, naturalHeight) => {\n      const aspectRatio = naturalWidth / naturalHeight;\n      let initialCoverage = Math.max(0, Math.min(1, options.initialCoverage));\n      let width = viewerWidth;\n      let height = viewerHeight;\n\n      this.imageInitializing = false;\n\n      if (viewerHeight * aspectRatio > viewerWidth) {\n        height = viewerWidth / aspectRatio;\n      } else {\n        width = viewerHeight * aspectRatio;\n      }\n\n      initialCoverage = isNumber(initialCoverage) ? initialCoverage : 0.9;\n      width = Math.min(width * initialCoverage, naturalWidth);\n      height = Math.min(height * initialCoverage, naturalHeight);\n\n      const left = (viewerWidth - width) / 2;\n      const top = (viewerHeight - height) / 2;\n\n      const imageData = {\n        left,\n        top,\n        x: left,\n        y: top,\n        width,\n        height,\n        oldRatio: 1,\n        ratio: width / naturalWidth,\n        aspectRatio,\n        naturalWidth,\n        naturalHeight,\n      };\n      const initialImageData = assign({}, imageData);\n\n      if (options.rotatable) {\n        imageData.rotate = oldImageData.rotate || 0;\n        initialImageData.rotate = 0;\n      }\n\n      if (options.scalable) {\n        imageData.scaleX = oldImageData.scaleX || 1;\n        imageData.scaleY = oldImageData.scaleY || 1;\n        initialImageData.scaleX = 1;\n        initialImageData.scaleY = 1;\n      }\n\n      this.imageData = imageData;\n      this.initialImageData = initialImageData;\n\n      if (done) {\n        done();\n      }\n    });\n  },\n\n  renderImage(done) {\n    const { image, imageData } = this;\n\n    setStyle(image, assign({\n      width: imageData.width,\n      height: imageData.height,\n\n      // XXX: Not to use translateX/Y to avoid image shaking when zooming\n      marginLeft: imageData.x,\n      marginTop: imageData.y,\n    }, getTransforms(imageData)));\n\n    if (done) {\n      if ((this.viewing || this.moving || this.rotating || this.scaling || this.zooming)\n        && this.options.transition\n        && hasClass(image, CLASS_TRANSITION)) {\n        const onTransitionEnd = () => {\n          this.imageRendering = false;\n          done();\n        };\n\n        this.imageRendering = {\n          abort: () => {\n            removeListener(image, EVENT_TRANSITION_END, onTransitionEnd);\n          },\n        };\n\n        addListener(image, EVENT_TRANSITION_END, onTransitionEnd, {\n          once: true,\n        });\n      } else {\n        done();\n      }\n    }\n  },\n\n  resetImage() {\n    const { image } = this;\n\n    if (image) {\n      if (this.viewing) {\n        this.viewing.abort();\n      }\n\n      image.parentNode.removeChild(image);\n      this.image = null;\n      this.title.innerHTML = '';\n    }\n  },\n};\n"
  },
  {
    "path": "src/js/template.js",
    "content": "export default (\n  '<div class=\"viewer-container\" tabindex=\"-1\" touch-action=\"none\">'\n    + '<div class=\"viewer-canvas\"></div>'\n    + '<div class=\"viewer-footer\">'\n      + '<div class=\"viewer-title\"></div>'\n      + '<div class=\"viewer-toolbar\"></div>'\n      + '<div class=\"viewer-navbar\">'\n        + '<ul class=\"viewer-list\" role=\"navigation\"></ul>'\n      + '</div>'\n    + '</div>'\n    + '<div class=\"viewer-tooltip\" role=\"alert\" aria-hidden=\"true\"></div>'\n    + '<div class=\"viewer-button\" data-viewer-action=\"mix\" role=\"button\"></div>'\n    + '<div class=\"viewer-player\"></div>'\n  + '</div>'\n);\n"
  },
  {
    "path": "src/js/utilities.js",
    "content": "import {\n  CLASS_HIDE_MD_DOWN,\n  CLASS_HIDE_SM_DOWN,\n  CLASS_HIDE_XS_DOWN,\n  IS_BROWSER,\n  REGEXP_SPACES,\n  WINDOW,\n} from './constants';\n\n/**\n * Check if the given value is a string.\n * @param {*} value - The value to check.\n * @returns {boolean} Returns `true` if the given value is a string, else `false`.\n */\nexport function isString(value) {\n  return typeof value === 'string';\n}\n\n/**\n * Check if the given value is not a number.\n */\nexport const isNaN = Number.isNaN || WINDOW.isNaN;\n\n/**\n * Check if the given value is a number.\n * @param {*} value - The value to check.\n * @returns {boolean} Returns `true` if the given value is a number, else `false`.\n */\nexport function isNumber(value) {\n  return typeof value === 'number' && !isNaN(value);\n}\n\n/**\n * Check if the given value is undefined.\n * @param {*} value - The value to check.\n * @returns {boolean} Returns `true` if the given value is undefined, else `false`.\n */\nexport function isUndefined(value) {\n  return typeof value === 'undefined';\n}\n\n/**\n * Check if the given value is an object.\n * @param {*} value - The value to check.\n * @returns {boolean} Returns `true` if the given value is an object, else `false`.\n */\nexport function isObject(value) {\n  return typeof value === 'object' && value !== null;\n}\n\nconst { hasOwnProperty } = Object.prototype;\n\n/**\n * Check if the given value is a plain object.\n * @param {*} value - The value to check.\n * @returns {boolean} Returns `true` if the given value is a plain object, else `false`.\n */\nexport function isPlainObject(value) {\n  if (!isObject(value)) {\n    return false;\n  }\n\n  try {\n    const { constructor } = value;\n    const { prototype } = constructor;\n\n    return constructor && prototype && hasOwnProperty.call(prototype, 'isPrototypeOf');\n  } catch (error) {\n    return false;\n  }\n}\n\n/**\n * Check if the given value is a function.\n * @param {*} value - The value to check.\n * @returns {boolean} Returns `true` if the given value is a function, else `false`.\n */\nexport function isFunction(value) {\n  return typeof value === 'function';\n}\n\n/**\n * Iterate the given data.\n * @param {*} data - The data to iterate.\n * @param {Function} callback - The process function for each element.\n * @returns {*} The original data.\n */\nexport function forEach(data, callback) {\n  if (data && isFunction(callback)) {\n    if (Array.isArray(data) || isNumber(data.length)/* array-like */) {\n      const { length } = data;\n      let i;\n\n      for (i = 0; i < length; i += 1) {\n        if (callback.call(data, data[i], i, data) === false) {\n          break;\n        }\n      }\n    } else if (isObject(data)) {\n      Object.keys(data).forEach((key) => {\n        callback.call(data, data[key], key, data);\n      });\n    }\n  }\n\n  return data;\n}\n\n/**\n * Extend the given object.\n * @param {*} obj - The object to be extended.\n * @param {*} args - The rest objects which will be merged to the first object.\n * @returns {Object} The extended object.\n */\nexport const assign = Object.assign || function assign(obj, ...args) {\n  if (isObject(obj) && args.length > 0) {\n    args.forEach((arg) => {\n      if (isObject(arg)) {\n        Object.keys(arg).forEach((key) => {\n          obj[key] = arg[key];\n        });\n      }\n    });\n  }\n\n  return obj;\n};\n\nconst REGEXP_SUFFIX = /^(?:width|height|left|top|marginLeft|marginTop)$/;\n\n/**\n * Apply styles to the given element.\n * @param {Element} element - The target element.\n * @param {Object} styles - The styles for applying.\n */\nexport function setStyle(element, styles) {\n  const { style } = element;\n\n  forEach(styles, (value, property) => {\n    if (REGEXP_SUFFIX.test(property) && isNumber(value)) {\n      value += 'px';\n    }\n\n    style[property] = value;\n  });\n}\n\n/**\n * Escape a string for using in HTML.\n * @param {String} value - The string to escape.\n * @returns {String} Returns the escaped string.\n */\nexport function escapeHTMLEntities(value) {\n  return isString(value) ? value\n    .replace(/&(?!amp;|quot;|#39;|lt;|gt;)/g, '&amp;')\n    .replace(/\"/g, '&quot;')\n    .replace(/'/g, '&#39;')\n    .replace(/</g, '&lt;')\n    .replace(/>/g, '&gt;') : value;\n}\n\n/**\n * Check if the given element has a special class.\n * @param {Element} element - The element to check.\n * @param {string} value - The class to search.\n * @returns {boolean} Returns `true` if the special class was found.\n */\nexport function hasClass(element, value) {\n  if (!element || !value) {\n    return false;\n  }\n\n  return element.classList\n    ? element.classList.contains(value)\n    : element.className.indexOf(value) > -1;\n}\n\n/**\n * Add classes to the given element.\n * @param {Element} element - The target element.\n * @param {string} value - The classes to be added.\n */\nexport function addClass(element, value) {\n  if (!element || !value) {\n    return;\n  }\n\n  if (isNumber(element.length)) {\n    forEach(element, (elem) => {\n      addClass(elem, value);\n    });\n    return;\n  }\n\n  if (element.classList) {\n    element.classList.add(value);\n    return;\n  }\n\n  const className = element.className.trim();\n\n  if (!className) {\n    element.className = value;\n  } else if (className.indexOf(value) < 0) {\n    element.className = `${className} ${value}`;\n  }\n}\n\n/**\n * Remove classes from the given element.\n * @param {Element} element - The target element.\n * @param {string} value - The classes to be removed.\n */\nexport function removeClass(element, value) {\n  if (!element || !value) {\n    return;\n  }\n\n  if (isNumber(element.length)) {\n    forEach(element, (elem) => {\n      removeClass(elem, value);\n    });\n    return;\n  }\n\n  if (element.classList) {\n    element.classList.remove(value);\n    return;\n  }\n\n  if (element.className.indexOf(value) >= 0) {\n    element.className = element.className.replace(value, '');\n  }\n}\n\n/**\n * Add or remove classes from the given element.\n * @param {Element} element - The target element.\n * @param {string} value - The classes to be toggled.\n * @param {boolean} added - Add only.\n */\nexport function toggleClass(element, value, added) {\n  if (!value) {\n    return;\n  }\n\n  if (isNumber(element.length)) {\n    forEach(element, (elem) => {\n      toggleClass(elem, value, added);\n    });\n    return;\n  }\n\n  // IE10-11 doesn't support the second parameter of `classList.toggle`\n  if (added) {\n    addClass(element, value);\n  } else {\n    removeClass(element, value);\n  }\n}\n\nconst REGEXP_HYPHENATE = /([a-z\\d])([A-Z])/g;\n\n/**\n * Transform the given string from camelCase to kebab-case\n * @param {string} value - The value to transform.\n * @returns {string} The transformed value.\n */\nexport function hyphenate(value) {\n  return value.replace(REGEXP_HYPHENATE, '$1-$2').toLowerCase();\n}\n\n/**\n * Get data from the given element.\n * @param {Element} element - The target element.\n * @param {string} name - The data key to get.\n * @returns {string} The data value.\n */\nexport function getData(element, name) {\n  if (isObject(element[name])) {\n    return element[name];\n  }\n\n  if (element.dataset) {\n    return element.dataset[name];\n  }\n\n  return element.getAttribute(`data-${hyphenate(name)}`);\n}\n\n/**\n * Set data to the given element.\n * @param {Element} element - The target element.\n * @param {string} name - The data key to set.\n * @param {string} data - The data value.\n */\nexport function setData(element, name, data) {\n  if (isObject(data)) {\n    element[name] = data;\n  } else if (element.dataset) {\n    element.dataset[name] = data;\n  } else {\n    element.setAttribute(`data-${hyphenate(name)}`, data);\n  }\n}\n\n/**\n * Remove data from the given element.\n * @param {Element} element - The target element.\n * @param {string} name - The data key to remove.\n */\nexport function removeData(element, name) {\n  if (isObject(element[name])) {\n    try {\n      delete element[name];\n    } catch (error) {\n      element[name] = undefined;\n    }\n  } else if (element.dataset) {\n    // #128 Safari not allows to delete dataset property\n    try {\n      delete element.dataset[name];\n    } catch (error) {\n      element.dataset[name] = undefined;\n    }\n  } else {\n    element.removeAttribute(`data-${hyphenate(name)}`);\n  }\n}\n\nconst onceSupported = (() => {\n  let supported = false;\n\n  if (IS_BROWSER) {\n    let once = false;\n    const listener = () => {};\n    const options = Object.defineProperty({}, 'once', {\n      get() {\n        supported = true;\n        return once;\n      },\n\n      /**\n       * This setter can fix a `TypeError` in strict mode\n       * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Getter_only}\n       * @param {boolean} value - The value to set\n       */\n      set(value) {\n        once = value;\n      },\n    });\n\n    WINDOW.addEventListener('test', listener, options);\n    WINDOW.removeEventListener('test', listener, options);\n  }\n\n  return supported;\n})();\n\n/**\n * Remove event listener from the target element.\n * @param {Element} element - The event target.\n * @param {string} type - The event type(s).\n * @param {Function} listener - The event listener.\n * @param {Object} options - The event options.\n */\nexport function removeListener(element, type, listener, options = {}) {\n  let handler = listener;\n\n  type.trim().split(REGEXP_SPACES).forEach((event) => {\n    if (!onceSupported) {\n      const { listeners } = element;\n\n      if (listeners && listeners[event] && listeners[event][listener]) {\n        handler = listeners[event][listener];\n        delete listeners[event][listener];\n\n        if (Object.keys(listeners[event]).length === 0) {\n          delete listeners[event];\n        }\n\n        if (Object.keys(listeners).length === 0) {\n          delete element.listeners;\n        }\n      }\n    }\n\n    element.removeEventListener(event, handler, options);\n  });\n}\n\n/**\n * Add event listener to the target element.\n * @param {Element} element - The event target.\n * @param {string} type - The event type(s).\n * @param {Function} listener - The event listener.\n * @param {Object} options - The event options.\n */\nexport function addListener(element, type, listener, options = {}) {\n  let handler = listener;\n\n  type.trim().split(REGEXP_SPACES).forEach((event) => {\n    if (options.once && !onceSupported) {\n      const { listeners = {} } = element;\n\n      handler = (...args) => {\n        delete listeners[event][listener];\n        element.removeEventListener(event, handler, options);\n        listener.apply(element, args);\n      };\n\n      if (!listeners[event]) {\n        listeners[event] = {};\n      }\n\n      if (listeners[event][listener]) {\n        element.removeEventListener(event, listeners[event][listener], options);\n      }\n\n      listeners[event][listener] = handler;\n      element.listeners = listeners;\n    }\n\n    element.addEventListener(event, handler, options);\n  });\n}\n\n/**\n * Dispatch event on the target element.\n * @param {Element} element - The event target.\n * @param {string} type - The event type(s).\n * @param {Object} data - The additional event data.\n * @param {Object} options - The additional event options.\n * @returns {boolean} Indicate if the event is default prevented or not.\n */\nexport function dispatchEvent(element, type, data, options) {\n  let event;\n\n  // Event and CustomEvent on IE9-11 are global objects, not constructors\n  if (isFunction(Event) && isFunction(CustomEvent)) {\n    event = new CustomEvent(type, {\n      bubbles: true,\n      cancelable: true,\n      detail: data,\n      ...options,\n    });\n  } else {\n    event = document.createEvent('CustomEvent');\n    event.initCustomEvent(type, true, true, data);\n  }\n\n  return element.dispatchEvent(event);\n}\n\n/**\n * Get the offset base on the document.\n * @param {Element} element - The target element.\n * @returns {Object} The offset data.\n */\nexport function getOffset(element) {\n  const box = element.getBoundingClientRect();\n\n  return {\n    left: box.left + (window.pageXOffset - document.documentElement.clientLeft),\n    top: box.top + (window.pageYOffset - document.documentElement.clientTop),\n  };\n}\n\n/**\n * Get transforms base on the given object.\n * @param {Object} obj - The target object.\n * @returns {string} A string contains transform values.\n */\nexport function getTransforms({\n  rotate,\n  scaleX,\n  scaleY,\n  translateX,\n  translateY,\n}) {\n  const values = [];\n\n  if (isNumber(translateX) && translateX !== 0) {\n    values.push(`translateX(${translateX}px)`);\n  }\n\n  if (isNumber(translateY) && translateY !== 0) {\n    values.push(`translateY(${translateY}px)`);\n  }\n\n  // Rotate should come first before scale to match orientation transform\n  if (isNumber(rotate) && rotate !== 0) {\n    values.push(`rotate(${rotate}deg)`);\n  }\n\n  if (isNumber(scaleX) && scaleX !== 1) {\n    values.push(`scaleX(${scaleX})`);\n  }\n\n  if (isNumber(scaleY) && scaleY !== 1) {\n    values.push(`scaleY(${scaleY})`);\n  }\n\n  const transform = values.length ? values.join(' ') : 'none';\n\n  return {\n    WebkitTransform: transform,\n    msTransform: transform,\n    transform,\n  };\n}\n\n/**\n * Get an image name from an image url.\n * @param {string} url - The target url.\n * @example\n * // picture.jpg\n * getImageNameFromURL('https://domain.com/path/to/picture.jpg?size=1280×960')\n * @returns {string} A string contains the image name.\n */\nexport function getImageNameFromURL(url) {\n  return isString(url) ? decodeURIComponent(url.replace(/^.*\\//, '').replace(/[?&#].*$/, '')) : '';\n}\n\nconst IS_SAFARI = WINDOW.navigator && /Version\\/\\d+(\\.\\d+)+?\\s+Safari/i.test(WINDOW.navigator.userAgent);\n\n/**\n * Get an image's natural sizes.\n * @param {string} image - The target image.\n * @param {Object} options - The viewer options.\n * @param {Function} callback - The callback function.\n * @returns {HTMLImageElement} The new image.\n */\nexport function getImageNaturalSizes(image, options, callback) {\n  const newImage = document.createElement('img');\n\n  // Modern browsers (except Safari)\n  if (image.naturalWidth && !IS_SAFARI) {\n    callback(image.naturalWidth, image.naturalHeight);\n    return newImage;\n  }\n\n  const body = document.body || document.documentElement;\n\n  newImage.onload = () => {\n    callback(newImage.width, newImage.height);\n\n    if (!IS_SAFARI) {\n      body.removeChild(newImage);\n    }\n  };\n\n  forEach(options.inheritedAttributes, (name) => {\n    const value = image.getAttribute(name);\n\n    if (value !== null) {\n      newImage.setAttribute(name, value);\n    }\n  });\n\n  newImage.src = image.src;\n\n  // iOS Safari will convert the image automatically\n  // with its orientation once append it into DOM\n  if (!IS_SAFARI) {\n    newImage.style.cssText = (\n      'left:0;'\n      + 'max-height:none!important;'\n      + 'max-width:none!important;'\n      + 'min-height:0!important;'\n      + 'min-width:0!important;'\n      + 'opacity:0;'\n      + 'position:absolute;'\n      + 'top:0;'\n      + 'z-index:-1;'\n    );\n    body.appendChild(newImage);\n  }\n\n  return newImage;\n}\n\n/**\n * Get the related class name of a responsive type number.\n * @param {string} type - The responsive type.\n * @returns {string} The related class name.\n */\nexport function getResponsiveClass(type) {\n  switch (type) {\n    case 2:\n      return CLASS_HIDE_XS_DOWN;\n\n    case 3:\n      return CLASS_HIDE_SM_DOWN;\n\n    case 4:\n      return CLASS_HIDE_MD_DOWN;\n\n    default:\n      return '';\n  }\n}\n\n/**\n * Get the max ratio of a group of pointers.\n * @param {string} pointers - The target pointers.\n * @returns {number} The result ratio.\n */\nexport function getMaxZoomRatio(pointers) {\n  const pointers2 = { ...pointers };\n  const ratios = [];\n\n  forEach(pointers, (pointer, pointerId) => {\n    delete pointers2[pointerId];\n\n    forEach(pointers2, (pointer2) => {\n      const x1 = Math.abs(pointer.startX - pointer2.startX);\n      const y1 = Math.abs(pointer.startY - pointer2.startY);\n      const x2 = Math.abs(pointer.endX - pointer2.endX);\n      const y2 = Math.abs(pointer.endY - pointer2.endY);\n      const z1 = Math.sqrt((x1 * x1) + (y1 * y1));\n      const z2 = Math.sqrt((x2 * x2) + (y2 * y2));\n      const ratio = (z2 - z1) / z1;\n\n      ratios.push(ratio);\n    });\n  });\n\n  ratios.sort((a, b) => Math.abs(a) < Math.abs(b));\n\n  return ratios[0];\n}\n\n/**\n * Get a pointer from an event object.\n * @param {Object} event - The target event object.\n * @param {boolean} endOnly - Indicates if only returns the end point coordinate or not.\n * @returns {Object} The result pointer contains start and/or end point coordinates.\n */\nexport function getPointer({ pageX, pageY }, endOnly) {\n  const end = {\n    endX: pageX,\n    endY: pageY,\n  };\n\n  return endOnly ? end : ({\n    timeStamp: Date.now(),\n    startX: pageX,\n    startY: pageY,\n    ...end,\n  });\n}\n\n/**\n * Get the center point coordinate of a group of pointers.\n * @param {Object} pointers - The target pointers.\n * @returns {Object} The center point coordinate.\n */\nexport function getPointersCenter(pointers) {\n  let pageX = 0;\n  let pageY = 0;\n  let count = 0;\n\n  forEach(pointers, ({ startX, startY }) => {\n    pageX += startX;\n    pageY += startY;\n    count += 1;\n  });\n\n  pageX /= count;\n  pageY /= count;\n\n  return {\n    pageX,\n    pageY,\n  };\n}\n"
  },
  {
    "path": "src/js/viewer.js",
    "content": "import DEFAULTS from './defaults';\nimport TEMPLATE from './template';\nimport render from './render';\nimport events from './events';\nimport handlers from './handlers';\nimport methods from './methods';\nimport others from './others';\nimport {\n  BUTTONS,\n  CLASS_CLOSE,\n  CLASS_FADE,\n  CLASS_FIXED,\n  CLASS_FULLSCREEN,\n  CLASS_HIDE,\n  CLASS_INVISIBLE,\n  DATA_ACTION,\n  EVENT_CLICK,\n  EVENT_ERROR,\n  EVENT_LOAD,\n  EVENT_READY,\n  NAMESPACE,\n  REGEXP_SPACES,\n  WINDOW,\n} from './constants';\nimport {\n  addClass,\n  addListener,\n  assign,\n  dispatchEvent,\n  forEach,\n  getResponsiveClass,\n  hyphenate,\n  isFunction,\n  isNumber,\n  isPlainObject,\n  isString,\n  isUndefined,\n  removeListener,\n  setData,\n  setStyle,\n  toggleClass,\n} from './utilities';\n\nconst AnotherViewer = WINDOW.Viewer;\nconst getUniqueID = ((id) => (() => {\n  id += 1;\n  return id;\n}))(-1);\n\nclass Viewer {\n  /**\n   * Create a new Viewer.\n   * @param {Element} element - The target element for viewing.\n   * @param {Object} [options={}] - The configuration options.\n   */\n  constructor(element, options = {}) {\n    if (!element || element.nodeType !== 1) {\n      throw new Error('The first argument is required and must be an element.');\n    }\n\n    this.element = element;\n    this.options = assign({}, DEFAULTS, isPlainObject(options) && options);\n    this.action = false;\n    this.fading = false;\n    this.fulled = false;\n    this.hiding = false;\n    this.imageClicked = false;\n    this.imageData = {};\n    this.index = this.options.initialViewIndex;\n    this.isImg = false;\n    this.isShown = false;\n    this.length = 0;\n    this.moving = false;\n    this.played = false;\n    this.playing = false;\n    this.pointers = {};\n    this.ready = false;\n    this.rotating = false;\n    this.scaling = false;\n    this.showing = false;\n    this.timeout = false;\n    this.tooltipping = false;\n    this.viewed = false;\n    this.viewing = false;\n    this.wheeling = false;\n    this.zooming = false;\n    this.pointerMoved = false;\n    this.id = getUniqueID();\n    this.init();\n  }\n\n  init() {\n    const { element, options } = this;\n\n    if (element[NAMESPACE]) {\n      return;\n    }\n\n    element[NAMESPACE] = this;\n\n    // The `focus` option requires the `keyboard` option set to `true`.\n    if (options.focus && !options.keyboard) {\n      options.focus = false;\n    }\n\n    const isImg = element.localName === 'img';\n    const images = [];\n\n    forEach(isImg ? [element] : element.querySelectorAll('img'), (image) => {\n      if (isFunction(options.filter)) {\n        if (options.filter.call(this, image)) {\n          images.push(image);\n        }\n      } else if (this.getImageURL(image)) {\n        images.push(image);\n      }\n    });\n\n    this.isImg = isImg;\n    this.length = images.length;\n    this.images = images;\n    this.initBody();\n\n    // Override `transition` option if it is not supported\n    if (isUndefined(document.createElement(NAMESPACE).style.transition)) {\n      options.transition = false;\n    }\n\n    if (options.inline) {\n      let count = 0;\n      const progress = () => {\n        count += 1;\n\n        if (count === this.length) {\n          let timeout;\n\n          this.initializing = false;\n          this.delaying = {\n            abort: () => {\n              clearTimeout(timeout);\n            },\n          };\n\n          // build asynchronously to keep `this.viewer` is accessible in `ready` event handler.\n          timeout = setTimeout(() => {\n            this.delaying = false;\n            this.build();\n          }, 0);\n        }\n      };\n\n      this.initializing = {\n        abort: () => {\n          forEach(images, (image) => {\n            if (!image.complete) {\n              removeListener(image, EVENT_LOAD, progress);\n              removeListener(image, EVENT_ERROR, progress);\n            }\n          });\n        },\n      };\n\n      forEach(images, (image) => {\n        if (image.complete) {\n          progress();\n        } else {\n          let onLoad;\n          let onError;\n\n          addListener(image, EVENT_LOAD, onLoad = () => {\n            removeListener(image, EVENT_ERROR, onError);\n            progress();\n          }, {\n            once: true,\n          });\n          addListener(image, EVENT_ERROR, onError = () => {\n            removeListener(image, EVENT_LOAD, onLoad);\n            progress();\n          }, {\n            once: true,\n          });\n        }\n      });\n    } else {\n      addListener(element, EVENT_CLICK, (this.onStart = ({ target }) => {\n        if (target.localName === 'img' && (!isFunction(options.filter) || options.filter.call(this, target))) {\n          this.view(this.images.indexOf(target));\n        }\n      }));\n    }\n  }\n\n  build() {\n    if (this.ready) {\n      return;\n    }\n\n    const { element, options } = this;\n    const parent = element.parentNode;\n    const template = document.createElement('div');\n\n    template.innerHTML = TEMPLATE;\n\n    const viewer = template.querySelector(`.${NAMESPACE}-container`);\n    const title = viewer.querySelector(`.${NAMESPACE}-title`);\n    const toolbar = viewer.querySelector(`.${NAMESPACE}-toolbar`);\n    const navbar = viewer.querySelector(`.${NAMESPACE}-navbar`);\n    const button = viewer.querySelector(`.${NAMESPACE}-button`);\n    const canvas = viewer.querySelector(`.${NAMESPACE}-canvas`);\n\n    this.parent = parent;\n    this.viewer = viewer;\n    this.title = title;\n    this.toolbar = toolbar;\n    this.navbar = navbar;\n    this.button = button;\n    this.canvas = canvas;\n    this.footer = viewer.querySelector(`.${NAMESPACE}-footer`);\n    this.tooltipBox = viewer.querySelector(`.${NAMESPACE}-tooltip`);\n    this.player = viewer.querySelector(`.${NAMESPACE}-player`);\n    this.list = viewer.querySelector(`.${NAMESPACE}-list`);\n\n    viewer.id = `${NAMESPACE}${this.id}`;\n    title.id = `${NAMESPACE}Title${this.id}`;\n    addClass(title, !options.title ? CLASS_HIDE : getResponsiveClass(Array.isArray(options.title)\n      ? options.title[0]\n      : options.title));\n    addClass(navbar, !options.navbar ? CLASS_HIDE : getResponsiveClass(options.navbar));\n    toggleClass(button, CLASS_HIDE, !options.button);\n\n    if (options.keyboard) {\n      button.setAttribute('tabindex', 0);\n    }\n\n    if (options.backdrop) {\n      addClass(viewer, `${NAMESPACE}-backdrop`);\n\n      if (!options.inline && options.backdrop !== 'static') {\n        setData(canvas, DATA_ACTION, 'hide');\n      }\n    }\n\n    if (isString(options.className) && options.className) {\n      // In case there are multiple class names\n      options.className.split(REGEXP_SPACES).forEach((className) => {\n        addClass(viewer, className);\n      });\n    }\n\n    if (options.toolbar) {\n      const list = document.createElement('ul');\n      const custom = isPlainObject(options.toolbar);\n      const zoomButtons = BUTTONS.slice(0, 3);\n      const rotateButtons = BUTTONS.slice(7, 9);\n      const scaleButtons = BUTTONS.slice(9);\n\n      if (!custom) {\n        addClass(toolbar, getResponsiveClass(options.toolbar));\n      }\n\n      forEach(custom ? options.toolbar : BUTTONS, (value, index) => {\n        const deep = custom && isPlainObject(value);\n        const name = custom ? hyphenate(index) : value;\n        const show = deep && !isUndefined(value.show) ? value.show : value;\n\n        if (\n          !show\n          || (!options.zoomable && zoomButtons.indexOf(name) !== -1)\n          || (!options.rotatable && rotateButtons.indexOf(name) !== -1)\n          || (!options.scalable && scaleButtons.indexOf(name) !== -1)\n        ) {\n          return;\n        }\n\n        const size = deep && !isUndefined(value.size) ? value.size : value;\n        const click = deep && !isUndefined(value.click) ? value.click : value;\n        const item = document.createElement('li');\n\n        if (options.keyboard) {\n          item.setAttribute('tabindex', 0);\n        }\n\n        item.setAttribute('role', 'button');\n        addClass(item, `${NAMESPACE}-${name}`);\n\n        if (!isFunction(click)) {\n          setData(item, DATA_ACTION, name);\n        }\n\n        if (isNumber(show)) {\n          addClass(item, getResponsiveClass(show));\n        }\n\n        if (['small', 'large'].indexOf(size) !== -1) {\n          addClass(item, `${NAMESPACE}-${size}`);\n        } else if (name === 'play') {\n          addClass(item, `${NAMESPACE}-large`);\n        }\n\n        if (isFunction(click)) {\n          addListener(item, EVENT_CLICK, click);\n        }\n\n        list.appendChild(item);\n      });\n\n      toolbar.appendChild(list);\n    } else {\n      addClass(toolbar, CLASS_HIDE);\n    }\n\n    if (!options.rotatable) {\n      const rotates = toolbar.querySelectorAll('li[class*=\"rotate\"]');\n\n      addClass(rotates, CLASS_INVISIBLE);\n      forEach(rotates, (rotate) => {\n        toolbar.appendChild(rotate);\n      });\n    }\n\n    if (options.inline) {\n      addClass(button, CLASS_FULLSCREEN);\n      setStyle(viewer, {\n        zIndex: options.zIndexInline,\n      });\n\n      if (window.getComputedStyle(parent).position === 'static') {\n        setStyle(parent, {\n          position: 'relative',\n        });\n      }\n\n      parent.insertBefore(viewer, element.nextSibling);\n    } else {\n      addClass(button, CLASS_CLOSE);\n      addClass(viewer, CLASS_FIXED);\n      addClass(viewer, CLASS_FADE);\n      addClass(viewer, CLASS_HIDE);\n\n      setStyle(viewer, {\n        zIndex: options.zIndex,\n      });\n\n      let { container } = options;\n\n      if (isString(container)) {\n        container = element.ownerDocument.querySelector(container);\n      }\n\n      if (!container) {\n        container = this.body;\n      }\n\n      container.appendChild(viewer);\n    }\n\n    if (options.inline) {\n      this.render();\n      this.bind();\n      this.isShown = true;\n    }\n\n    this.ready = true;\n\n    if (isFunction(options.ready)) {\n      addListener(element, EVENT_READY, options.ready, {\n        once: true,\n      });\n    }\n\n    if (dispatchEvent(element, EVENT_READY) === false) {\n      this.ready = false;\n      return;\n    }\n\n    if (this.ready && options.inline) {\n      this.view(this.index);\n    }\n  }\n\n  /**\n   * Get the no conflict viewer class.\n   * @returns {Viewer} The viewer class.\n   */\n  static noConflict() {\n    window.Viewer = AnotherViewer;\n    return Viewer;\n  }\n\n  /**\n   * Change the default options.\n   * @param {Object} options - The new default options.\n   */\n  static setDefaults(options) {\n    assign(DEFAULTS, isPlainObject(options) && options);\n  }\n}\n\nassign(Viewer.prototype, render, events, handlers, methods, others);\n\nexport default Viewer;\n"
  },
  {
    "path": "stylelint.config.js",
    "content": "module.exports = {\n  extends: 'stylelint-config-standard',\n  plugins: [\n    'stylelint-order',\n  ],\n  rules: {\n    'no-descending-specificity': null,\n    'order/properties-alphabetical-order': true,\n  },\n};\n"
  },
  {
    "path": "test/helpers.js",
    "content": "// XXX: Disable the `focus` option globally to avoid side effects.\nwindow.Viewer.setDefaults({\n  focus: false,\n});\n\nwindow.createContainer = () => {\n  const container = document.createElement('div');\n\n  container.className = 'container';\n  document.body.appendChild(container);\n\n  return container;\n};\n\nwindow.createImage = (attributes = {}) => {\n  const container = window.createContainer();\n  const image = document.createElement('img');\n\n  Object.keys(attributes).forEach((key) => {\n    image.setAttribute(key, attributes[key]);\n  });\n  image.src = '/base/docs/images/tibet-1.jpg';\n  container.appendChild(image);\n\n  return image;\n};\n\nwindow.createImageList = () => {\n  const container = window.createContainer();\n  const list = document.createElement('ul');\n\n  list.innerHTML = (\n    '<li><img src=\"/base/docs/images/tibet-1.jpg\"></li>'\n    + '<li><img src=\"/base/docs/images/tibet-2.jpg\"></li>'\n    + '<li><img src=\"/base/docs/images/tibet-3.jpg\"></li>'\n    + '<li><img src=\"/base/docs/images/tibet-4.jpg\"></li>'\n    + '<li><img src=\"/base/docs/images/tibet-5.jpg\"></li>'\n  );\n  container.appendChild(list);\n\n  return list;\n};\n\nwindow.createEvent = (type, data = {}) => {\n  const { detail } = data;\n  let event;\n\n  if (typeof Event === 'function' && typeof CustomEvent === 'function') {\n    if (typeof detail === 'undefined') {\n      event = new Event(type, {\n        bubbles: true,\n        cancelable: true,\n      });\n    } else {\n      event = new CustomEvent(type, {\n        detail,\n        bubbles: true,\n        cancelable: true,\n      });\n    }\n  } else if (typeof detail === 'undefined') {\n    event = document.createEvent('Event');\n    event.initEvent(type, true, true);\n  } else {\n    event = document.createEvent('CustomEvent');\n    event.initCustomEvent(type, true, true, detail);\n  }\n\n  Object.keys(data).forEach((key) => {\n    if (key !== 'detail') {\n      event[key] = data[key];\n    }\n  });\n\n  return event;\n};\n"
  },
  {
    "path": "test/specs/Viewer.spec.js",
    "content": "describe('Viewer', () => {\n  it('should be a class (function)', () => {\n    expect(Viewer).to.be.a('function');\n  });\n\n  it('should throw error when the first argument is not an element', () => {\n    expect(() => {\n      new Viewer(document);\n    }).to.throw('The first argument is required and must be an element.');\n  });\n});\n"
  },
  {
    "path": "test/specs/events/hidden.spec.js",
    "content": "describe('hidden (event)', () => {\n  it('should trigger the `hidden` event', (done) => {\n    const image = window.createImage();\n\n    image.addEventListener('hidden', (event) => {\n      expect(event.type).to.equal('hidden');\n      done();\n    });\n\n    const viewer = new Viewer(image, {\n      shown() {\n        viewer.hide(true);\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should not trigger the `hidden` event in inline mode', (done) => {\n    const image = window.createImage();\n\n    image.addEventListener('hidden', () => {\n      expect.fail(1, 0);\n    });\n\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      ready() {\n        viewer.hide(true);\n        done();\n      },\n    });\n  });\n\n  it('should not cancel the `hidden` event', (done) => {\n    const image = window.createImage();\n\n    image.addEventListener('hidden', (event) => {\n      expect(event.defaultPrevented).to.false;\n      event.preventDefault();\n      expect(event.defaultPrevented).to.false;\n      done();\n    });\n\n    const viewer = new Viewer(image, {\n      shown() {\n        viewer.hide(true);\n      },\n    });\n\n    viewer.show();\n  });\n});\n"
  },
  {
    "path": "test/specs/events/hide.spec.js",
    "content": "describe('hide (event)', () => {\n  it('should trigger the `hide` event', (done) => {\n    const image = window.createImage();\n\n    image.addEventListener('hide', (event) => {\n      expect(event.type).to.equal('hide');\n    });\n\n    const viewer = new Viewer(image, {\n      shown() {\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should not trigger the `hidden` event when default prevented', (done) => {\n    const image = window.createImage();\n    let count = 0;\n    let viewer;\n\n    image.addEventListener('hide', (event) => {\n      count += 1;\n\n      if (count === 1) {\n        event.preventDefault();\n        viewer.hide(true);\n        done();\n      }\n    });\n\n    image.addEventListener('hidden', () => {\n      if (count === 1) {\n        expect.fail(1, 0);\n      }\n    });\n\n    viewer = new Viewer(image, {\n      shown() {\n        viewer.hide(true);\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should not trigger the `hide` event in inline mode', (done) => {\n    const image = window.createImage();\n\n    image.addEventListener('hide', () => {\n      expect.fail(1, 0);\n    });\n\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      viewed() {\n        viewer.hide(true);\n        done();\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/events/move.spec.js",
    "content": "describe('move (event)', () => {\n  it('should trigger the `move` event', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', () => {\n      viewer.moveTo(0);\n    });\n\n    image.addEventListener('move', (event) => {\n      expect(event.type).to.equal('move');\n      event.preventDefault();\n      viewer.hide(true);\n      done();\n    });\n\n    viewer = new Viewer(image);\n    viewer.show();\n  });\n\n  it('should have expected properties in `event.detail`', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', () => {\n      viewer.moveTo(0);\n    });\n\n    image.addEventListener('move', (event) => {\n      const { detail } = event;\n\n      expect(detail).to.be.an('object').that.has.all.keys('x', 'y', 'oldX', 'oldY', 'originalEvent');\n      expect(detail.x).to.be.a('number');\n      expect(detail.y).to.be.a('number');\n      expect(detail.oldX).to.be.a('number');\n      expect(detail.oldY).to.be.a('number');\n      expect(detail.originalEvent).to.be.null;\n      event.preventDefault();\n      viewer.hide(true);\n      done();\n    });\n\n    viewer = new Viewer(image);\n    viewer.show();\n  });\n\n  it('should not trigger the `moved` event when default prevented', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', () => {\n      viewer.moveTo(0);\n    });\n\n    image.addEventListener('move', (event) => {\n      event.preventDefault();\n      viewer.hide(true);\n      done();\n    });\n\n    image.addEventListener('moved', () => {\n      expect.fail(1, 0);\n    });\n\n    viewer = new Viewer(image);\n    viewer.show();\n  });\n\n  it('should trigger the `move` event in inline mode', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', () => {\n      viewer.moveTo(0);\n    });\n\n    image.addEventListener('move', (event) => {\n      expect(event.type).to.equal('move');\n      done();\n    });\n\n    viewer = new Viewer(image, {\n      inline: true,\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/events/moved.spec.js",
    "content": "describe('moved (event)', () => {\n  it('should trigger the `moved` event', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', () => {\n      viewer.moveTo(0);\n    });\n\n    image.addEventListener('moved', (event) => {\n      expect(event.type).to.equal('moved');\n      viewer.hide(true);\n      done();\n    });\n\n    viewer = new Viewer(image);\n    viewer.show();\n  });\n\n  it('should have expected properties in `event.detail`', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', () => {\n      viewer.moveTo(0);\n    });\n\n    image.addEventListener('moved', (event) => {\n      const { detail } = event;\n\n      expect(detail).to.be.an('object').that.has.all.keys('x', 'y', 'oldX', 'oldY', 'originalEvent');\n      expect(detail.x).to.be.a('number');\n      expect(detail.y).to.be.a('number');\n      expect(detail.oldX).to.be.a('number');\n      expect(detail.oldY).to.be.a('number');\n      expect(detail.originalEvent).to.be.null;\n      viewer.hide(true);\n      done();\n    });\n\n    viewer = new Viewer(image);\n    viewer.show();\n  });\n\n  it('should trigger the `moved` event in inline mode', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', () => {\n      viewer.moveTo(0);\n    });\n\n    image.addEventListener('moved', (event) => {\n      expect(event.type).to.equal('moved');\n      done();\n    });\n\n    viewer = new Viewer(image, {\n      inline: true,\n    });\n  });\n\n  it('should not cancel the `moved` event', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', () => {\n      viewer.moveTo(0);\n    });\n\n    image.addEventListener('moved', (event) => {\n      expect(event.defaultPrevented).to.false;\n      event.preventDefault();\n      expect(event.defaultPrevented).to.false;\n      viewer.hide(true);\n      done();\n    });\n\n    viewer = new Viewer(image);\n    viewer.show();\n  });\n});\n"
  },
  {
    "path": "test/specs/events/play.spec.js",
    "content": "describe('play (event)', () => {\n  it('should trigger the `play` event', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', () => {\n      expect(viewer.played).to.false;\n      viewer.play();\n      expect(viewer.played).to.true;\n      done();\n    });\n\n    image.addEventListener('play', (event) => {\n      expect(event.type).to.equal('play');\n    });\n\n    viewer = new Viewer(image);\n    viewer.show();\n  });\n\n  it('should not play when default prevented', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', () => {\n      expect(viewer.played).to.false;\n      viewer.play();\n      expect(viewer.played).to.false;\n      done();\n    });\n\n    image.addEventListener('play', (event) => {\n      expect(event.type).to.equal('play');\n      event.preventDefault();\n    });\n\n    viewer = new Viewer(image);\n    viewer.show();\n  });\n});\n"
  },
  {
    "path": "test/specs/events/ready.spec.js",
    "content": "describe('ready (event)', () => {\n  it('should not trigger the `ready` event by default', () => {\n    const image = window.createImage();\n\n    image.addEventListener('ready', () => {\n      expect.fail(1, 0);\n    });\n\n    new Viewer(image);\n  });\n\n  it('should trigger the `ready` event when show the viewer', (done) => {\n    const image = window.createImage();\n\n    image.addEventListener('ready', (event) => {\n      expect(event.type).to.equal('ready');\n      event.preventDefault();\n      done();\n    });\n\n    const viewer = new Viewer(image);\n\n    viewer.show();\n  });\n\n  it('should trigger the `ready` event immediately in inline mode', (done) => {\n    const image = window.createImage();\n\n    image.addEventListener('ready', (event) => {\n      expect(event.type).to.equal('ready');\n      done();\n    });\n\n    new Viewer(image, {\n      inline: true,\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/events/rotate.spec.js",
    "content": "describe('rotate (event)', () => {\n  it('should trigger the `rotate` event', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', () => {\n      viewer.rotateTo(45);\n    });\n\n    image.addEventListener('rotate', (event) => {\n      expect(event.type).to.equal('rotate');\n      event.preventDefault();\n      viewer.hide(true);\n      done();\n    });\n\n    viewer = new Viewer(image);\n    viewer.show();\n  });\n\n  it('should have expected properties in `event.detail`', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', () => {\n      viewer.rotateTo(45);\n    });\n\n    image.addEventListener('rotate', (event) => {\n      const { detail } = event;\n\n      expect(detail).to.be.an('object').that.has.all.keys('degree', 'oldDegree');\n      expect(detail.degree).to.be.a('number');\n      expect(detail.oldDegree).to.be.a('number');\n      event.preventDefault();\n      viewer.hide(true);\n      done();\n    });\n\n    viewer = new Viewer(image);\n    viewer.show();\n  });\n\n  it('should not trigger the `rotated` event when default prevented', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', () => {\n      viewer.rotateTo(45);\n    });\n\n    image.addEventListener('rotate', (event) => {\n      event.preventDefault();\n      viewer.hide(true);\n      done();\n    });\n\n    image.addEventListener('rotated', () => {\n      expect.fail(1, 0);\n    });\n\n    viewer = new Viewer(image);\n    viewer.show();\n  });\n\n  it('should trigger the `rotate` event in inline mode', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', () => {\n      viewer.rotateTo(45);\n    });\n\n    image.addEventListener('rotate', (event) => {\n      expect(event.type).to.equal('rotate');\n      done();\n    });\n\n    viewer = new Viewer(image, {\n      inline: true,\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/events/rotated.spec.js",
    "content": "describe('rotated (event)', () => {\n  it('should trigger the `rotated` event', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', () => {\n      viewer.rotateTo(45);\n    });\n\n    image.addEventListener('rotated', (event) => {\n      expect(event.type).to.equal('rotated');\n      viewer.hide(true);\n      done();\n    });\n\n    viewer = new Viewer(image);\n    viewer.show();\n  });\n\n  it('should have expected properties in `event.detail`', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', () => {\n      viewer.rotateTo(45);\n    });\n\n    image.addEventListener('rotated', (event) => {\n      const { detail } = event;\n\n      expect(detail).to.be.an('object').that.has.all.keys('degree', 'oldDegree');\n      expect(detail.degree).to.be.a('number');\n      expect(detail.oldDegree).to.be.a('number');\n      viewer.hide(true);\n      done();\n    });\n\n    viewer = new Viewer(image);\n    viewer.show();\n  });\n\n  it('should trigger the `rotated` event in inline mode', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', () => {\n      viewer.rotateTo(45);\n    });\n\n    image.addEventListener('rotated', (event) => {\n      expect(event.type).to.equal('rotated');\n      done();\n    });\n\n    viewer = new Viewer(image, {\n      inline: true,\n    });\n  });\n\n  it('should not cancel the `rotated` event', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', () => {\n      viewer.rotateTo(45);\n    });\n\n    image.addEventListener('rotated', (event) => {\n      expect(event.defaultPrevented).to.false;\n      event.preventDefault();\n      expect(event.defaultPrevented).to.false;\n      viewer.hide(true);\n      done();\n    });\n\n    viewer = new Viewer(image);\n    viewer.show();\n  });\n});\n"
  },
  {
    "path": "test/specs/events/scale.spec.js",
    "content": "describe('scale (event)', () => {\n  it('should trigger the `scale` event', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', () => {\n      viewer.scale(-1);\n    });\n\n    image.addEventListener('scale', (event) => {\n      expect(event.type).to.equal('scale');\n      event.preventDefault();\n      viewer.hide(true);\n      done();\n    });\n\n    viewer = new Viewer(image);\n    viewer.show();\n  });\n\n  it('should have expected properties in `event.detail`', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', () => {\n      viewer.scale(-1);\n    });\n\n    image.addEventListener('scale', (event) => {\n      const { detail } = event;\n\n      expect(detail).to.be.an('object').that.has.all.keys('scaleX', 'scaleY', 'oldScaleX', 'oldScaleY');\n      expect(detail.scaleX).to.be.a('number');\n      expect(detail.scaleY).to.be.a('number');\n      expect(detail.oldScaleX).to.be.a('number');\n      expect(detail.oldScaleY).to.be.a('number');\n      event.preventDefault();\n      viewer.hide(true);\n      done();\n    });\n\n    viewer = new Viewer(image);\n    viewer.show();\n  });\n\n  it('should not trigger the `scaled` event when default prevented', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', () => {\n      viewer.scale(-1);\n    });\n\n    image.addEventListener('scale', (event) => {\n      event.preventDefault();\n      viewer.hide(true);\n      done();\n    });\n\n    image.addEventListener('scaled', () => {\n      expect.fail(1, 0);\n    });\n\n    viewer = new Viewer(image);\n    viewer.show();\n  });\n\n  it('should trigger the `scale` event in inline mode', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', () => {\n      viewer.scale(-1);\n    });\n\n    image.addEventListener('scale', (event) => {\n      expect(event.type).to.equal('scale');\n      done();\n    });\n\n    viewer = new Viewer(image, {\n      inline: true,\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/events/scaled.spec.js",
    "content": "describe('scaled (event)', () => {\n  it('should trigger the `scaled` event', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', () => {\n      viewer.scale(-1);\n    });\n\n    image.addEventListener('scaled', (event) => {\n      expect(event.type).to.equal('scaled');\n      viewer.hide(true);\n      done();\n    });\n\n    viewer = new Viewer(image);\n    viewer.show();\n  });\n\n  it('should have expected properties in `event.detail`', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', () => {\n      viewer.scale(-1);\n    });\n\n    image.addEventListener('scaled', (event) => {\n      const { detail } = event;\n\n      expect(detail).to.be.an('object').that.has.all.keys('scaleX', 'scaleY', 'oldScaleX', 'oldScaleY');\n      expect(detail.scaleX).to.be.a('number');\n      expect(detail.scaleY).to.be.a('number');\n      expect(detail.oldScaleX).to.be.a('number');\n      expect(detail.oldScaleY).to.be.a('number');\n      viewer.hide(true);\n      done();\n    });\n\n    viewer = new Viewer(image);\n    viewer.show();\n  });\n\n  it('should trigger the `scaled` event in inline mode', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', () => {\n      viewer.scale(-1);\n    });\n\n    image.addEventListener('scaled', (event) => {\n      expect(event.type).to.equal('scaled');\n      done();\n    });\n\n    viewer = new Viewer(image, {\n      inline: true,\n    });\n  });\n\n  it('should not cancel the `scaled` event', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', () => {\n      viewer.scale(-1);\n    });\n\n    image.addEventListener('scaled', (event) => {\n      expect(event.defaultPrevented).to.false;\n      event.preventDefault();\n      expect(event.defaultPrevented).to.false;\n      viewer.hide(true);\n      done();\n    });\n\n    viewer = new Viewer(image);\n    viewer.show();\n  });\n});\n"
  },
  {
    "path": "test/specs/events/show.spec.js",
    "content": "describe('show (event)', () => {\n  it('should trigger the `show` event', (done) => {\n    const image = window.createImage();\n\n    image.addEventListener('show', (event) => {\n      expect(event.type).to.equal('show');\n      event.preventDefault();\n      done();\n    });\n\n    const viewer = new Viewer(image);\n\n    viewer.show();\n  });\n\n  it('should not trigger the `shown` event when default prevented', (done) => {\n    const image = window.createImage();\n\n    image.addEventListener('show', (event) => {\n      event.preventDefault();\n      done();\n    });\n\n    image.addEventListener('shown', () => {\n      expect.fail(1, 0);\n    });\n\n    const viewer = new Viewer(image);\n\n    viewer.show();\n  });\n\n  it('should not trigger the `show` event in inline mode', () => {\n    const image = window.createImage();\n\n    image.addEventListener('show', () => {\n      expect.fail(1, 0);\n    });\n\n    new Viewer(image, {\n      inline: true,\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/events/shown.spec.js",
    "content": "describe('shown (event)', () => {\n  it('should trigger the `shown` event', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('shown', (event) => {\n      expect(event.type).to.equal('shown');\n      done();\n      event.preventDefault();\n      viewer.hide(true);\n    });\n\n    viewer = new Viewer(image);\n    viewer.show();\n  });\n\n  it('should not trigger the `shown` event in inline mode', () => {\n    const image = window.createImage();\n\n    image.addEventListener('shown', () => {\n      expect.fail(1, 0);\n    });\n\n    new Viewer(image, {\n      inline: true,\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/events/stop.spec.js",
    "content": "describe('stop (event)', () => {\n  it('should trigger the `stop` event', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', () => {\n      viewer.play();\n      setTimeout(() => {\n        expect(viewer.played).to.true;\n        viewer.stop();\n        expect(viewer.played).to.false;\n        done();\n      }, 500);\n    });\n\n    image.addEventListener('stop', (event) => {\n      expect(event.type).to.equal('stop');\n    });\n\n    viewer = new Viewer(image);\n    viewer.show();\n  });\n\n  it('should not stop when default prevented', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', () => {\n      viewer.play();\n      setTimeout(() => {\n        expect(viewer.played).to.true;\n        viewer.stop();\n        expect(viewer.played).to.true;\n        done();\n      }, 500);\n    });\n\n    image.addEventListener('stop', (event) => {\n      expect(event.type).to.equal('stop');\n      event.preventDefault();\n    });\n\n    viewer = new Viewer(image);\n    viewer.show();\n  });\n});\n"
  },
  {
    "path": "test/specs/events/view.spec.js",
    "content": "describe('view (event)', () => {\n  it('should trigger the `view` event', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('view', (event) => {\n      expect(event.type).to.equal('view');\n      event.preventDefault();\n      viewer.hide(true);\n      done();\n    });\n\n    viewer = new Viewer(image);\n    viewer.show();\n  });\n\n  it('should have expected properties in `event.detail`', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('view', (event) => {\n      const { detail } = event;\n\n      expect(detail).to.be.an('object').that.has.all.keys('image', 'index', 'originalImage');\n      expect(detail.image.src).to.equal(image.src);\n      expect(detail.index).to.equal(0);\n      expect(detail.originalImage).to.equal(image);\n      event.preventDefault();\n      viewer.hide(true);\n      done();\n    });\n\n    viewer = new Viewer(image);\n    viewer.show();\n  });\n\n  it('should not trigger the `viewed` event when default prevented', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('view', (event) => {\n      event.preventDefault();\n      viewer.hide(true);\n      done();\n    });\n\n    image.addEventListener('viewed', () => {\n      expect.fail(1, 0);\n    });\n\n    viewer = new Viewer(image);\n    viewer.show();\n  });\n\n  it('should trigger the `view` event in inline mode', (done) => {\n    const image = window.createImage();\n\n    image.addEventListener('view', (event) => {\n      expect(event.type).to.equal('view');\n      done();\n    });\n\n    new Viewer(image, {\n      inline: true,\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/events/viewed.spec.js",
    "content": "describe('viewed (event)', () => {\n  it('should trigger the `viewed` event', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', (event) => {\n      expect(event.type).to.equal('viewed');\n      viewer.hide(true);\n      done();\n    });\n\n    viewer = new Viewer(image);\n    viewer.show();\n  });\n\n  it('should have expected properties in `event.detail`', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', (event) => {\n      const { detail } = event;\n\n      expect(detail).to.be.an('object').that.has.all.keys('image', 'index', 'originalImage');\n      expect(detail.image.src).to.equal(image.src);\n      expect(detail.index).to.equal(0);\n      expect(detail.originalImage).to.equal(image);\n      viewer.hide(true);\n      done();\n    });\n\n    viewer = new Viewer(image);\n    viewer.show();\n  });\n\n  it('should trigger the `viewed` event in inline mode', (done) => {\n    const image = window.createImage();\n\n    image.addEventListener('viewed', (event) => {\n      expect(event.type).to.equal('viewed');\n      done();\n    });\n\n    new Viewer(image, {\n      inline: true,\n    });\n  });\n\n  it('should not cancel the `viewed` event', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', (event) => {\n      expect(event.defaultPrevented).to.false;\n      event.preventDefault();\n      expect(event.defaultPrevented).to.false;\n      viewer.hide(true);\n      done();\n    });\n\n    viewer = new Viewer(image);\n    viewer.show();\n  });\n});\n"
  },
  {
    "path": "test/specs/events/zoom.spec.js",
    "content": "describe('zoom (event)', () => {\n  it('should trigger the `zoom` event', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', () => {\n      viewer.zoomTo(1);\n    });\n\n    image.addEventListener('zoom', (event) => {\n      expect(event.type).to.equal('zoom');\n      event.preventDefault();\n      viewer.hide(true);\n      done();\n    });\n\n    viewer = new Viewer(image);\n    viewer.show();\n  });\n\n  it('should have expected properties in `event.detail`', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', () => {\n      viewer.zoomTo(1);\n    });\n\n    image.addEventListener('zoom', (event) => {\n      const { detail } = event;\n\n      expect(detail).to.be.an('object').that.has.all.keys('ratio', 'oldRatio', 'originalEvent');\n      expect(detail.ratio).to.be.a('number');\n      expect(detail.oldRatio).to.be.a('number');\n      expect(detail.originalEvent).to.be.null;\n      event.preventDefault();\n      viewer.hide(true);\n      done();\n    });\n\n    viewer = new Viewer(image);\n    viewer.show();\n  });\n\n  it('should not trigger the `zoomed` event when default prevented', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', () => {\n      viewer.zoomTo(1);\n    });\n\n    image.addEventListener('zoom', (event) => {\n      event.preventDefault();\n      viewer.hide(true);\n      done();\n    });\n\n    image.addEventListener('zoomed', () => {\n      expect.fail(1, 0);\n    });\n\n    viewer = new Viewer(image);\n    viewer.show();\n  });\n\n  it('should trigger the `zoom` event in inline mode', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', () => {\n      viewer.zoomTo(1);\n    });\n\n    image.addEventListener('zoom', (event) => {\n      expect(event.type).to.equal('zoom');\n      done();\n    });\n\n    viewer = new Viewer(image, {\n      inline: true,\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/events/zoomed.spec.js",
    "content": "describe('zoomed (event)', () => {\n  it('should trigger the `zoomed` event', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', () => {\n      viewer.zoomTo(1);\n    });\n\n    image.addEventListener('zoomed', (event) => {\n      expect(event.type).to.equal('zoomed');\n      viewer.hide(true);\n      done();\n    });\n\n    viewer = new Viewer(image);\n    viewer.show();\n  });\n\n  it('should have expected properties in `event.detail`', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', () => {\n      viewer.zoomTo(1);\n    });\n\n    image.addEventListener('zoomed', (event) => {\n      const { detail } = event;\n\n      expect(detail).to.be.an('object').that.has.all.keys('ratio', 'oldRatio', 'originalEvent');\n      expect(detail.ratio).to.be.a('number');\n      expect(detail.oldRatio).to.be.a('number');\n      expect(detail.originalEvent).to.be.null;\n      viewer.hide(true);\n      done();\n    });\n\n    viewer = new Viewer(image);\n    viewer.show();\n  });\n\n  it('should trigger the `zoomed` event in inline mode', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', () => {\n      viewer.zoomTo(1);\n    });\n\n    image.addEventListener('zoomed', (event) => {\n      expect(event.type).to.equal('zoomed');\n      done();\n    });\n\n    viewer = new Viewer(image, {\n      inline: true,\n    });\n  });\n\n  it('should not cancel the `zoomed` event', (done) => {\n    const image = window.createImage();\n    let viewer;\n\n    image.addEventListener('viewed', () => {\n      viewer.zoomTo(1);\n    });\n\n    image.addEventListener('zoomed', (event) => {\n      expect(event.defaultPrevented).to.false;\n      event.preventDefault();\n      expect(event.defaultPrevented).to.false;\n      viewer.hide(true);\n      done();\n    });\n\n    viewer = new Viewer(image);\n    viewer.show();\n  });\n});\n"
  },
  {
    "path": "test/specs/methods/destroy.spec.js",
    "content": "describe('destroy (method)', () => {\n  it('should destroy successfully when there are not any images', () => {\n    const container = window.createContainer();\n    const viewer = new Viewer(container);\n\n    expect(container.viewer).to.be.an.instanceof(Viewer);\n    viewer.destroy();\n    expect(container.viewer).to.be.undefined;\n  });\n\n  it('should destroy successfully when there are not any images in inline mode', () => {\n    const container = window.createContainer();\n    const viewer = new Viewer(container, {\n      inline: true,\n    });\n\n    expect(container.viewer).to.be.an.instanceof(Viewer);\n    viewer.destroy();\n    expect(container.viewer).to.be.undefined;\n  });\n\n  it('should destroy before ready', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image);\n\n    viewer.show();\n    expect(image.viewer).to.be.an.instanceof(Viewer);\n    viewer.destroy();\n    expect(image.viewer).to.be.undefined;\n  });\n\n  it('should destroy before ready in inline mode', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n    });\n\n    expect(image.viewer).to.be.an.instanceof(Viewer);\n    viewer.destroy();\n    expect(image.viewer).to.be.undefined;\n  });\n\n  it('should destroy after ready', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      ready() {\n        expect(image.viewer).to.be.an.instanceof(Viewer);\n        viewer.destroy();\n        expect(image.viewer).to.be.undefined;\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should destroy after ready in inline mode', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      ready() {\n        expect(image.viewer).to.be.an.instanceof(Viewer);\n        viewer.destroy();\n        expect(image.viewer).to.be.undefined;\n        done();\n      },\n    });\n  });\n\n  it('should destroy successfully when show', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      show() {\n        expect(image.viewer).to.be.an.instanceof(Viewer);\n        viewer.destroy();\n        expect(image.viewer).to.be.undefined;\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should destroy successfully when shown', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      shown() {\n        expect(image.viewer).to.be.an.instanceof(Viewer);\n        viewer.destroy();\n        expect(image.viewer).to.be.undefined;\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should destroy successfully when hide', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      shown() {\n        viewer.hide();\n      },\n\n      hide() {\n        expect(image.viewer).to.be.an.instanceof(Viewer);\n        viewer.destroy();\n        expect(image.viewer).to.be.undefined;\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should destroy successfully when hidden', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      shown() {\n        viewer.hide();\n      },\n\n      hidden() {\n        expect(image.viewer).to.be.an.instanceof(Viewer);\n        viewer.destroy();\n        expect(image.viewer).to.be.undefined;\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should destroy successfully when view', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      view() {\n        expect(image.viewer).to.be.an.instanceof(Viewer);\n        viewer.destroy();\n        expect(image.viewer).to.be.undefined;\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should destroy successfully when viewed', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      viewed() {\n        expect(image.viewer).to.be.an.instanceof(Viewer);\n        viewer.destroy();\n        expect(image.viewer).to.be.undefined;\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n});\n"
  },
  {
    "path": "test/specs/methods/exit.spec.js",
    "content": "describe('exit (method)', () => {\n  it('should exit modal mode in inline mode', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      ready() {\n        expect(viewer.fulled).to.be.false;\n        expect(viewer.button.className).to.not.include('viewer-fullscreen-exit');\n        expect(viewer.viewer.className).to.not.include('viewer-fixed');\n        viewer.full();\n        expect(viewer.fulled).to.be.true;\n        expect(viewer.button.className).to.include('viewer-fullscreen-exit');\n        expect(viewer.viewer.className).to.include('viewer-fixed');\n        viewer.exit();\n        expect(viewer.fulled).to.be.false;\n        expect(viewer.button.className).to.not.include('viewer-fullscreen-exit');\n        expect(viewer.viewer.className).to.not.include('viewer-fixed');\n        done();\n      },\n    });\n  });\n\n  it('should not work in modal mode', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      shown() {\n        expect(viewer.fulled).to.be.true;\n        expect(viewer.viewer.className).to.include('viewer-fixed');\n        viewer.exit();\n        expect(viewer.fulled).to.be.true;\n        expect(viewer.viewer.className).to.include('viewer-fixed');\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n});\n"
  },
  {
    "path": "test/specs/methods/full.spec.js",
    "content": "describe('full (method)', () => {\n  it('should enter modal mode in inline mode', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      ready() {\n        expect(viewer.fulled).to.be.false;\n        expect(viewer.button.className).to.not.include('viewer-fullscreen-exit');\n        expect(viewer.viewer.className).to.not.include('viewer-fixed');\n        viewer.full();\n        expect(viewer.fulled).to.be.true;\n        expect(viewer.button.className).to.include('viewer-fullscreen-exit');\n        expect(viewer.viewer.className).to.include('viewer-fixed');\n        viewer.exit();\n        done();\n      },\n    });\n  });\n\n  it('should not work in modal mode', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      shown() {\n        expect(viewer.fulled).to.be.true;\n        expect(viewer.button.className).to.not.include('viewer-fullscreen-exit');\n        expect(viewer.viewer.className).to.include('viewer-fixed');\n        viewer.full();\n        expect(viewer.fulled).to.be.true;\n        expect(viewer.button.className).to.not.include('viewer-fullscreen-exit');\n        expect(viewer.viewer.className).to.include('viewer-fixed');\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n});\n"
  },
  {
    "path": "test/specs/methods/hide.spec.js",
    "content": "describe('hide (method)', () => {\n  it('should hide the viewer in modal mode', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      shown() {\n        expect(viewer.isShown).to.be.true;\n        viewer.hide();\n      },\n\n      hidden() {\n        expect(viewer.isShown).to.be.false;\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should hide immediately the viewer in modal mode', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      shown() {\n        expect(viewer.isShown).to.be.true;\n        viewer.hide(true);\n        expect(viewer.isShown).to.be.false;\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should not work in inline mode', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      ready() {\n        expect(viewer.isShown).to.be.true;\n        viewer.hide(true);\n        expect(viewer.isShown).to.be.true;\n        done();\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/methods/move.spec.js",
    "content": "describe('move (method)', () => {\n  it('should move with the given offsets', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      viewed() {\n        const { imageData } = viewer;\n        const { left, top } = imageData;\n\n        viewer.move(10, 10);\n        expect(imageData.left).to.equal(left + 10);\n        expect(imageData.top).to.equal(top + 10);\n        viewer.move(-10, -10);\n        expect(imageData.left).to.equal(left);\n        expect(imageData.top).to.equal(top);\n        done();\n      },\n    });\n  });\n\n  it('should not work when it is not movable', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n      movable: false,\n\n      viewed() {\n        const { imageData } = viewer;\n        const { left, top } = imageData;\n\n        viewer.move(10, 10);\n        expect(imageData.left).to.equal(left);\n        expect(imageData.top).to.equal(top);\n        done();\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/methods/moveTo.spec.js",
    "content": "describe('moveTo (method)', () => {\n  it('should move to the expected position', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      viewed() {\n        const { imageData } = viewer;\n\n        viewer.moveTo(0, 0);\n        expect(imageData.left).to.equal(0);\n        expect(imageData.top).to.equal(0);\n        viewer.moveTo(-10, -10);\n        expect(imageData.left).to.equal(-10);\n        expect(imageData.top).to.equal(-10);\n        done();\n      },\n    });\n  });\n\n  it('should not work when it is not movable', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n      movable: false,\n\n      viewed() {\n        const { imageData } = viewer;\n        const { left, top } = imageData;\n\n        viewer.moveTo(0, 0);\n        expect(imageData.left).to.equal(left);\n        expect(imageData.top).to.equal(top);\n        done();\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/methods/next.spec.js",
    "content": "describe('next (method)', () => {\n  it('should view the next image', (done) => {\n    const imageList = window.createImageList();\n    const viewer = new Viewer(imageList, {\n      viewed(event) {\n        switch (event.detail.index) {\n          case 0:\n            viewer.next();\n            break;\n\n          case 1:\n            viewer.hide(true);\n            done();\n            break;\n\n          default:\n        }\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should not work when the current image already is the last one', (done) => {\n    const imageList = window.createImageList();\n    const viewer = new Viewer(imageList, {\n      viewed(event) {\n        switch (event.detail.index) {\n          case 0:\n            expect.fail(1, 0);\n            break;\n\n          case 4:\n            viewer.next();\n            setTimeout(() => {\n              viewer.hide(true);\n              done();\n            }, 500);\n            break;\n\n          default:\n        }\n      },\n    });\n\n    viewer.view(4);\n  });\n\n  it('should work when the current image is the last one and the first argument is set to `true`', (done) => {\n    const imageList = window.createImageList();\n    const viewer = new Viewer(imageList, {\n      viewed(event) {\n        switch (event.detail.index) {\n          case 0:\n            viewer.hide(true);\n            done();\n            break;\n\n          case 4:\n            viewer.next(true);\n            break;\n\n          default:\n        }\n      },\n    });\n\n    viewer.view(4);\n  });\n\n  it('should work in inline mode', (done) => {\n    const imageList = window.createImageList();\n    const viewer = new Viewer(imageList, {\n      inline: true,\n\n      viewed(event) {\n        switch (event.detail.index) {\n          case 0:\n            viewer.next();\n            break;\n\n          case 1:\n            done();\n            break;\n\n          default:\n        }\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/methods/noConflict.spec.js",
    "content": "describe('noConflict', () => {\n  it('should be a static method', () => {\n    expect(Viewer.noConflict).to.be.a('function');\n  });\n\n  it('should return the Viewer class itself', () => {\n    const { Viewer } = window;\n    const ImageViewer = Viewer.noConflict();\n\n    expect(ImageViewer).to.equal(Viewer);\n    expect(window.Viewer).to.be.undefined;\n\n    // Reverts it for the rest test suites\n    window.Viewer = ImageViewer;\n  });\n});\n"
  },
  {
    "path": "test/specs/methods/play.spec.js",
    "content": "describe('play (method)', () => {\n  it('should play the images', (done) => {\n    const imageList = window.createImageList();\n    const viewer = new Viewer(imageList, {\n      viewed() {\n        expect(viewer.played).to.be.false;\n        expect(window.getComputedStyle(viewer.player).display).to.equal('none');\n        viewer.play();\n        expect(viewer.played).to.be.true;\n        expect(window.getComputedStyle(viewer.player).display).to.not.equal('none');\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should play the images in inline mode', (done) => {\n    const imageList = window.createImageList();\n    const viewer = new Viewer(imageList, {\n      inline: true,\n\n      viewed() {\n        expect(viewer.played).to.be.false;\n        expect(window.getComputedStyle(viewer.player).display).to.equal('none');\n        viewer.play();\n        expect(viewer.played).to.be.true;\n        expect(window.getComputedStyle(viewer.player).display).to.not.equal('none');\n        done();\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/methods/prev.spec.js",
    "content": "describe('prev (method)', () => {\n  it('should view the previous image', (done) => {\n    const imageList = window.createImageList();\n    const viewer = new Viewer(imageList, {\n      inline: true,\n\n      viewed(event) {\n        switch (event.detail.index) {\n          case 0:\n            done();\n            break;\n\n          case 1:\n            viewer.prev();\n            break;\n\n          default:\n        }\n      },\n    });\n\n    viewer.view(1);\n  });\n\n  it('should not work when the current image already is the first one', (done) => {\n    const imageList = window.createImageList();\n    const viewer = new Viewer(imageList, {\n      viewed(event) {\n        switch (event.detail.index) {\n          case 0:\n            viewer.prev();\n            setTimeout(() => {\n              viewer.hide(true);\n              done();\n            }, 500);\n            break;\n\n          case 3:\n            expect.fail(1, 0);\n            break;\n\n          default:\n        }\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should work when the current image is the first one and the first argument is set to `true`', (done) => {\n    const imageList = window.createImageList();\n    const viewer = new Viewer(imageList, {\n      viewed(event) {\n        switch (event.detail.index) {\n          case 0:\n            viewer.prev(true);\n            break;\n\n          case 4:\n            viewer.hide(true);\n            done();\n            break;\n\n          default:\n        }\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should work in inline mode', (done) => {\n    const imageList = window.createImageList();\n    const viewer = new Viewer(imageList, {\n      inline: true,\n\n      viewed(event) {\n        switch (event.detail.index) {\n          case 0:\n            viewer.view(2);\n            break;\n\n          case 1:\n            done();\n            break;\n\n          case 2:\n            viewer.prev();\n            break;\n\n          default:\n        }\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/methods/reset.spec.js",
    "content": "describe('reset (method)', () => {\n  it('should reset the image to its initial state', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      viewed() {\n        viewer.move(10, 10).reset();\n        expect(viewer.imageData).to.deep.equal(viewer.initialImageData);\n        done();\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/methods/rotate.spec.js",
    "content": "describe('rotate (method)', () => {\n  it('should rotate to the expected degrees', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      viewed() {\n        const { imageData } = viewer;\n\n        expect(imageData.rotate).to.equal(0);\n        viewer.rotate(90);\n        expect(imageData.rotate).to.equal(90);\n        viewer.rotate(90);\n        expect(imageData.rotate).to.equal(180);\n        viewer.rotate(-180);\n        expect(imageData.rotate).to.equal(0);\n        done();\n      },\n    });\n  });\n\n  it('should not work when it is not rotatable', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n      rotatable: false,\n\n      viewed() {\n        const { imageData } = viewer;\n\n        expect(imageData.rotate).to.be.undefined;\n        viewer.rotate(90);\n        expect(imageData.rotate).to.be.undefined;\n        done();\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/methods/rotateTo.spec.js",
    "content": "describe('rotateTo (method)', () => {\n  it('should rotate to the given degrees', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      viewed() {\n        const { imageData } = viewer;\n\n        expect(imageData.rotate).to.equal(0);\n        viewer.rotateTo(360);\n        expect(imageData.rotate).to.equal(360);\n        viewer.rotateTo(-180);\n        expect(imageData.rotate).to.equal(-180);\n        viewer.rotateTo(0);\n        expect(imageData.rotate).to.equal(0);\n        done();\n      },\n    });\n  });\n\n  it('should not work when it is not rotatable', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n      rotatable: false,\n\n      viewed() {\n        const { imageData } = viewer;\n\n        expect(imageData.rotate).to.be.undefined;\n        viewer.rotateTo(90);\n        expect(imageData.rotate).to.be.undefined;\n        done();\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/methods/scale.spec.js",
    "content": "describe('scale (method)', () => {\n  it('should scale to the given values', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      viewed() {\n        const { imageData } = viewer;\n\n        expect(imageData.scaleX).to.equal(1);\n        expect(imageData.scaleY).to.equal(1);\n        viewer.scale(-1);\n        expect(imageData.scaleX).to.equal(-1);\n        expect(imageData.scaleY).to.equal(-1);\n        viewer.scale(1, -1);\n        expect(imageData.scaleX).to.equal(1);\n        expect(imageData.scaleY).to.equal(-1);\n        done();\n      },\n    });\n  });\n\n  it('should not work when it is not scalable', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n      scalable: false,\n\n      viewed() {\n        const { imageData } = viewer;\n\n        expect(imageData.scaleX).to.be.undefined;\n        expect(imageData.scaleY).to.be.undefined;\n        viewer.scale(-1, -1);\n        expect(imageData.scaleX).to.be.undefined;\n        expect(imageData.scaleY).to.be.undefined;\n        done();\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/methods/scaleX.spec.js",
    "content": "describe('scaleX (method)', () => {\n  it('should scale to the given value in x-axis', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      viewed() {\n        const { imageData } = viewer;\n\n        expect(imageData.scaleX).to.equal(1);\n        viewer.scaleX(-1);\n        expect(imageData.scaleX).to.equal(-1);\n        viewer.scaleX(1);\n        expect(imageData.scaleX).to.equal(1);\n        done();\n      },\n    });\n  });\n\n  it('should not work when it is not scalable', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n      scalable: false,\n\n      viewed() {\n        const { imageData } = viewer;\n\n        expect(imageData.scaleX).to.be.undefined;\n        viewer.scaleX(-1);\n        expect(imageData.scaleX).to.be.undefined;\n        done();\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/methods/scaleY.spec.js",
    "content": "describe('scaleY (method)', () => {\n  it('should scale to the given value in y-axis', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      viewed() {\n        const { imageData } = viewer;\n\n        expect(imageData.scaleY).to.equal(1);\n        viewer.scaleY(-1);\n        expect(imageData.scaleY).to.equal(-1);\n        viewer.scaleY(1);\n        expect(imageData.scaleY).to.equal(1);\n        done();\n      },\n    });\n  });\n\n  it('should not work when it is not scalable', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n      scalable: false,\n\n      viewed() {\n        const { imageData } = viewer;\n\n        expect(imageData.scaleY).to.be.undefined;\n        viewer.scaleY(-1);\n        expect(imageData.scaleY).to.be.undefined;\n        done();\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/methods/setDefaults.spec.js",
    "content": "describe('setDefaults', () => {\n  it('should be a static method', () => {\n    expect(Viewer.setDefaults).to.be.a('function');\n  });\n\n  it('should change the global default options', (done) => {\n    Viewer.setDefaults({\n      backdrop: false,\n    });\n\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      shown() {\n        expect(viewer.viewer.className).to.not.include('viewer-backdrop');\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    expect(viewer.options.backdrop).to.be.false;\n    viewer.show();\n\n    // Reverts it for the rest test suites\n    Viewer.setDefaults({\n      backdrop: true,\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/methods/show.spec.js",
    "content": "describe('show (method)', () => {\n  it('should show the viewer in modal mode', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      shown() {\n        expect(viewer.isShown).to.be.true;\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    expect(viewer.isShown).to.be.false;\n    viewer.show();\n  });\n\n  it('should view the first image after shown', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      viewed(event) {\n        expect(event.detail.index).to.equal(0);\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n});\n"
  },
  {
    "path": "test/specs/methods/stop.spec.js",
    "content": "describe('stop (method)', () => {\n  it('should stop to play the images', (done) => {\n    const imageList = window.createImageList();\n    const viewer = new Viewer(imageList, {\n      viewed() {\n        viewer.play();\n        expect(viewer.played).to.be.true;\n        expect(window.getComputedStyle(viewer.player).display).to.not.equal('none');\n        viewer.stop();\n        expect(viewer.played).to.be.false;\n        expect(window.getComputedStyle(viewer.player).display).to.equal('none');\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should stop to play the images in inline mode', (done) => {\n    const imageList = window.createImageList();\n    const viewer = new Viewer(imageList, {\n      inline: true,\n\n      viewed() {\n        viewer.play();\n        expect(viewer.played).to.be.true;\n        expect(window.getComputedStyle(viewer.player).display).to.not.equal('none');\n        viewer.stop();\n        expect(viewer.played).to.be.false;\n        expect(window.getComputedStyle(viewer.player).display).to.equal('none');\n        done();\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/methods/toggle.spec.js",
    "content": "describe('toggle (method)', () => {\n  it('should toggle the scale of the current viewing image between its current ratio and 1', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      viewed() {\n        const { imageData } = viewer;\n        const { ratio } = imageData;\n\n        viewer.toggle();\n        expect(imageData.ratio).to.equal(1);\n        viewer.toggle();\n        expect(imageData.ratio).to.equal(ratio);\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should not work when it is not zoomable', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      zoomable: false,\n\n      viewed() {\n        const { imageData } = viewer;\n        const { ratio } = imageData;\n\n        viewer.toggle();\n        expect(imageData.ratio).to.equal(ratio);\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n});\n"
  },
  {
    "path": "test/specs/methods/tooltip.spec.js",
    "content": "describe('tooltip (method)', () => {\n  it('should show tooltip by default', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      viewed() {\n        expect(window.getComputedStyle(viewer.tooltipBox).display).to.equal('none');\n        viewer.tooltip();\n        expect(window.getComputedStyle(viewer.tooltipBox).display).to.not.equal('none');\n        done();\n      },\n    });\n  });\n\n  it('should not show tooltip when the `tooltip` option is set to `false`', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n      tooltip: false,\n\n      viewed() {\n        expect(window.getComputedStyle(viewer.tooltipBox).display).to.equal('none');\n        viewer.tooltip();\n        expect(window.getComputedStyle(viewer.tooltipBox).display).to.equal('none');\n        done();\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/methods/update.spec.js",
    "content": "describe('update (method)', () => {\n  it('should update the image list', (done) => {\n    const imageList = window.createImageList();\n    let viewed;\n    const viewer = new Viewer(imageList, {\n      inline: true,\n\n      viewed() {\n        if (viewed) {\n          return;\n        }\n\n        viewed = true;\n        const items = imageList.getElementsByTagName('li');\n        const itemsLength = viewer.items.length;\n\n        imageList.removeChild(items.item(0));\n        viewer.update();\n        expect(viewer.items.length).to.equal(itemsLength - 1);\n        viewer.hide(true);\n        done();\n      },\n    });\n  });\n\n  it('should view the new image', (done) => {\n    const container = window.createContainer();\n    const image = window.createImage();\n    const viewer = new Viewer(container, {\n      viewed(event) {\n        expect(event.detail.originalImage).to.equal(image);\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    container.appendChild(image);\n    viewer.update();\n    image.click();\n  });\n\n  it('should view the new image in inline mode', (done) => {\n    const container = window.createContainer();\n    const image = window.createImage();\n    const viewer = new Viewer(container, {\n      inline: true,\n      viewed(event) {\n        expect(event.detail.originalImage).to.equal(image);\n        done();\n      },\n    });\n\n    container.appendChild(image);\n    viewer.update();\n  });\n});\n"
  },
  {
    "path": "test/specs/methods/view.spec.js",
    "content": "describe('view (method)', () => {\n  it('should view the image of the given index', (done) => {\n    const imageList = window.createImageList();\n    const viewer = new Viewer(imageList, {\n      viewed(event) {\n        expect(event.detail.index).to.equal(1);\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.view(1);\n  });\n\n  it('should not work when the given index is less than 0', (done) => {\n    const imageList = window.createImageList();\n    const viewer = new Viewer(imageList, {\n      viewed() {\n        expect.fail(1, 0);\n      },\n    });\n\n    viewer.view(-1);\n    setTimeout(() => {\n      viewer.hide(true);\n      done();\n    }, 500);\n  });\n\n  it('should not work when the given index is greater than or equal to the length of the images', (done) => {\n    const imageList = window.createImageList();\n    const viewer = new Viewer(imageList, {\n      viewed() {\n        expect.fail(1, 0);\n      },\n    });\n\n    viewer.view(imageList.childElementCount);\n    setTimeout(() => {\n      viewer.hide(true);\n      done();\n    }, 500);\n  });\n});\n"
  },
  {
    "path": "test/specs/methods/zoom.spec.js",
    "content": "describe('zoom (method)', () => {\n  it('should zoom with the given offset ratio', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      viewed() {\n        const { imageData } = viewer;\n        const { width } = imageData;\n\n        viewer.zoom(0.1);\n        expect(imageData.width).to.be.above(width);\n        viewer.zoom(-0.2);\n        expect(imageData.width).to.be.below(width);\n        viewer.hide(true);\n        done();\n      },\n    });\n  });\n\n  it('should not work when it is not zoomable', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n      zoomable: false,\n\n      viewed() {\n        const { imageData } = viewer;\n        const { width } = imageData;\n\n        viewer.zoom(0.1);\n        expect(imageData.width).to.equal(width);\n        done();\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/methods/zoomTo.spec.js",
    "content": "describe('zoomTo (method)', () => {\n  it('should zoom to the given ratio', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      viewed() {\n        const { imageData } = viewer;\n        const { ratio } = imageData;\n\n        viewer.zoomTo(1);\n        expect(imageData.ratio).to.equal(1);\n        viewer.zoomTo(ratio);\n        expect(imageData.ratio).to.equal(ratio);\n        viewer.hide(true);\n        done();\n      },\n    });\n  });\n\n  it('should not work when it is not zoomable', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n      zoomable: false,\n\n      viewed() {\n        const { imageData } = viewer;\n        const { width } = imageData;\n\n        viewer.zoomTo(1);\n        expect(imageData.width).to.equal(width);\n        done();\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/options/backdrop.spec.js",
    "content": "describe('backdrop (option)', () => {\n  it('should show backdrop and hide the viewer on click the backdrop by default', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      shown() {\n        expect(viewer.viewer.className).to.include('viewer-backdrop');\n        viewer.canvas.click();\n      },\n\n      hidden() {\n        done();\n      },\n    });\n\n    expect(viewer.options.backdrop).to.be.true;\n    viewer.show();\n  });\n\n  it('should not show backdrop and hide the viewer on click the backdrop', (done) => {\n    const image = window.createImage();\n    let hidable = false;\n    const viewer = new Viewer(image, {\n      backdrop: false,\n\n      shown() {\n        expect(viewer.viewer.className).to.not.include('viewer-backdrop');\n        viewer.canvas.click();\n        hidable = true;\n        viewer.hide(true);\n        done();\n      },\n\n      hide() {\n        if (!hidable) {\n          expect.fail(1, 0);\n        }\n      },\n    });\n\n    expect(viewer.options.backdrop).to.be.false;\n    viewer.show();\n  });\n\n  it('should show backdrop but not hide the viewer on click the backdrop', (done) => {\n    const image = window.createImage();\n    let hidable = false;\n    const viewer = new Viewer(image, {\n      backdrop: 'static',\n\n      shown() {\n        expect(viewer.viewer.className).to.include('viewer-backdrop');\n        viewer.canvas.click();\n        hidable = true;\n        viewer.hide(true);\n        done();\n      },\n\n      hide() {\n        if (!hidable) {\n          expect.fail(1, 0);\n        }\n      },\n    });\n\n    expect(viewer.options.backdrop).to.equal('static');\n    viewer.show();\n  });\n});\n"
  },
  {
    "path": "test/specs/options/button.spec.js",
    "content": "describe('button (option)', () => {\n  it('should show a close button and hide the viewer on click the button by default', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      shown() {\n        const { button } = viewer;\n\n        expect(window.getComputedStyle(button).display).to.not.equal('none');\n        expect(button.className).to.include('viewer-close');\n        button.click();\n      },\n\n      hidden() {\n        done();\n      },\n    });\n\n    expect(viewer.options.button).to.be.true;\n    viewer.show();\n  });\n\n  it('should hide the button', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      button: false,\n\n      shown() {\n        expect(window.getComputedStyle(viewer.button).display).to.equal('none');\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    expect(viewer.options.button).to.be.false;\n    viewer.show();\n  });\n\n  it('should show a fullscreen button and enter modal mode on click the button in inline mode', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      ready() {\n        const { button } = viewer;\n\n        expect(window.getComputedStyle(button).display).to.not.equal('none');\n        expect(button.className).to.include('viewer-fullscreen');\n        expect(button.className).to.not.include('viewer-fullscreen-exit');\n        expect(viewer.fulled).to.be.false;\n        viewer.button.click();\n        expect(button.className).to.include('viewer-fullscreen-exit');\n        expect(viewer.fulled).to.be.true;\n        viewer.exit();\n        done();\n      },\n    });\n  });\n\n  it('should show a exit fullscreen button and exit modal mode on click the button when fulled in inline mode', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      ready() {\n        const { button } = viewer;\n\n        expect(window.getComputedStyle(button).display).to.not.equal('none');\n        viewer.button.click();\n        expect(button.className).to.include('viewer-fullscreen-exit');\n        expect(viewer.fulled).to.be.true;\n        viewer.button.click();\n        expect(button.className).to.not.include('viewer-fullscreen-exit');\n        expect(viewer.fulled).to.be.false;\n        done();\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/options/className.spec.js",
    "content": "describe('className (option)', () => {\n  it('should be empty string by default', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image);\n\n    expect(viewer.options.className).to.equal('');\n  });\n\n  it('should add the given class name to the viewer', (done) => {\n    const image = window.createImage();\n    const className = 'custom-viewer';\n    const viewer = new Viewer(image, {\n      className,\n      ready(event) {\n        expect(viewer.viewer.className).to.include(className);\n        event.preventDefault();\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should support multiple class names', (done) => {\n    const image = window.createImage();\n    const className = 'custom-viewer another-custom-viewer';\n    const viewer = new Viewer(image, {\n      className,\n      ready(event) {\n        expect(viewer.viewer.className).to.include(className);\n        event.preventDefault();\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n});\n"
  },
  {
    "path": "test/specs/options/container.spec.js",
    "content": "describe('container (option)', () => {\n  it('should be \"body\" by default', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      ready(event) {\n        expect(viewer.viewer.parentElement).to.equal(image.ownerDocument.body);\n        event.preventDefault();\n        done();\n      },\n    });\n\n    expect(viewer.options.container).to.equal('body');\n    viewer.show();\n  });\n\n  it('should support selector', (done) => {\n    const image = window.createImage();\n    const container = window.createContainer();\n\n    container.id = 'container';\n\n    const viewer = new Viewer(image, {\n      container: '#container',\n\n      ready(event) {\n        expect(container.firstElementChild).to.equal(viewer.viewer);\n        event.preventDefault();\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should support element', (done) => {\n    const image = window.createImage();\n    const container = window.createContainer();\n    const viewer = new Viewer(image, {\n      container,\n\n      ready(event) {\n        expect(container.firstElementChild).to.equal(viewer.viewer);\n        event.preventDefault();\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n});\n"
  },
  {
    "path": "test/specs/options/filter.spec.js",
    "content": "describe('filter (option)', () => {\n  it('should be null be default', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image);\n\n    expect(viewer.options.filter).to.be.null;\n  });\n\n  it('should match the filtered image', (done) => {\n    const imageList = window.createImageList();\n    const viewer = new Viewer(imageList, {\n      inline: true,\n\n      filter(image) {\n        expect(this).to.be.an.instanceof(Viewer);\n\n        return /1\\.jpg$/.test(image.src);\n      },\n\n      ready() {\n        expect(viewer.list.childElementCount).to.equal(1);\n        expect(viewer.list.querySelector('img').src).to.match(/1\\.jpg$/);\n        done();\n      },\n    });\n  });\n\n  it('should not be ready when ignore all images', () => {\n    const imageList = window.createImageList();\n\n    new Viewer(imageList, {\n      inline: true,\n\n      filter() {\n        return false;\n      },\n\n      ready() {\n        expect.fail(1, 0);\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/options/focus.spec.js",
    "content": "describe('focus (option)', () => {\n  it('should be enabled by default', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      // XXX: The `focus` option is disabled globally in the `helpers.js`.\n      focus: true,\n\n      viewed() {\n        expect(document.activeElement).to.equal(viewer.items[viewer.index]);\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    expect(viewer.options.focus).to.be.true;\n    viewer.show();\n  });\n\n  it('should be disabled', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      focus: false,\n\n      viewed() {\n        expect(document.activeElement).to.not.equal(viewer.items[viewer.index]);\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    expect(viewer.options.focus).to.be.false;\n    viewer.show();\n  });\n\n  it('should be disabled when the `keyboard` option to set to `false`', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      keyboard: false,\n      focus: true,\n    });\n\n    expect(viewer.options.focus).to.be.false;\n  });\n});\n"
  },
  {
    "path": "test/specs/options/fullscreen.spec.js",
    "content": "describe('fullscreen (option)', () => {\n  it('should be enabled by default', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image);\n\n    expect(viewer.options.fullscreen).to.be.true;\n  });\n\n  it('should be disabled', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      fullscreen: false,\n    });\n\n    expect(viewer.options.fullscreen).to.be.false;\n  });\n\n  it('should support FullscreenOptions', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      fullscreen: {\n        navigationUI: 'hide',\n      },\n    });\n\n    expect(viewer.options.fullscreen).to.be.an('object').that.has.all.keys('navigationUI');\n  });\n});\n"
  },
  {
    "path": "test/specs/options/hidden.spec.js",
    "content": "describe('hidden (option)', () => {\n  it('should be null be default', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image);\n\n    expect(viewer.options.hidden).to.be.null;\n  });\n\n  it('should execute the `hidden` hook function', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      shown() {\n        viewer.hide(true);\n      },\n\n      hidden(event) {\n        expect(event.type).to.equal('hidden');\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should not execute the `hidden` hook function in inline mode', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      ready() {\n        viewer.hide(true);\n        done();\n      },\n\n      hidden() {\n        expect.fail(1, 0);\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/options/hide.spec.js",
    "content": "describe('hide (option)', () => {\n  it('should be null be default', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image);\n\n    expect(viewer.options.hide).to.be.null;\n  });\n\n  it('should execute the `hide` hook function', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      shown() {\n        viewer.hide(true);\n      },\n\n      hide(event) {\n        expect(event.type).to.equal('hide');\n      },\n\n      hidden() {\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should not trigger the `hidden` event when default prevented', (done) => {\n    const image = window.createImage();\n    let hidable = false;\n    const viewer = new Viewer(image, {\n      shown() {\n        viewer.hide(true);\n      },\n\n      hide(event) {\n        if (!hidable) {\n          event.preventDefault();\n          setTimeout(() => {\n            hidable = true;\n            viewer.hide(true);\n          }, 350);\n        }\n      },\n\n      hidden() {\n        if (!hidable) {\n          expect.fail(1, 0);\n        } else {\n          done();\n        }\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should not execute the `hide` hook function in inline mode', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      viewed() {\n        viewer.hide(true);\n        done();\n      },\n\n      hide() {\n        expect.fail(1, 0);\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/options/inheritedAttributes.spec.js",
    "content": "describe('inheritedAttributes (option)', () => {\n  it('should inherit the `crossOrigin` and `referrerPolicy` attributes', (done) => {\n    const image = window.createImage({\n      crossOrigin: '',\n      referrerPolicy: 'no-referrer',\n    });\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      viewed(event) {\n        expect(event.detail.image.getAttribute('crossOrigin')).to.equal('');\n        expect(event.detail.image.crossOrigin).to.equal('anonymous');\n        expect(event.detail.image.getAttribute('referrerPolicy')).to.equal('no-referrer');\n        expect(event.detail.image.referrerPolicy).to.equal('no-referrer');\n        done();\n      },\n    });\n\n    expect(viewer.options.inheritedAttributes).to.include('crossOrigin');\n    expect(viewer.options.inheritedAttributes).to.include('referrerPolicy');\n  });\n\n  it('should not inherit the `crossOrigin` and `referrerPolicy` attributes', (done) => {\n    const image = window.createImage({\n      crossOrigin: '',\n      referrerPolicy: 'no-referrer',\n    });\n    const viewer = new Viewer(image, {\n      inheritedAttributes: [],\n      inline: true,\n\n      viewed(event) {\n        expect(event.detail.image.getAttribute('crossOrigin')).to.be.null;\n        expect(event.detail.image.crossOrigin).to.be.null;\n        expect(event.detail.image.getAttribute('referrerPolicy')).to.be.null;\n        expect(event.detail.image.referrerPolicy).to.equal('');\n        done();\n      },\n    });\n\n    expect(viewer.options.inheritedAttributes).to.be.empty;\n  });\n});\n"
  },
  {
    "path": "test/specs/options/initialCoverage.spec.js",
    "content": "describe('initialCoverage (option)', () => {\n  it('should be \"0.9\" by default', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image);\n\n    expect(viewer.options.initialCoverage).to.equal(0.9);\n  });\n});\n"
  },
  {
    "path": "test/specs/options/initialViewIndex.spec.js",
    "content": "describe('initialViewIndex (option)', () => {\n  it('should be \"0\" by default', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image);\n\n    expect(viewer.options.initialViewIndex).to.equal(0);\n  });\n\n  it('should match the given index', (done) => {\n    const imageList = window.createImageList();\n    const viewer = new Viewer(imageList, {\n      initialViewIndex: 1,\n\n      viewed(event) {\n        expect(event.detail.index).to.equal(1);\n        done();\n      },\n    });\n\n    expect(viewer.options.initialViewIndex).to.equal(1);\n    viewer.show();\n  });\n\n  it('should work in inline mode', (done) => {\n    const imageList = window.createImageList();\n    const viewer = new Viewer(imageList, {\n      initialViewIndex: 1,\n      inline: true,\n\n      viewed(event) {\n        expect(event.detail.index).to.equal(1);\n        done();\n      },\n    });\n\n    expect(viewer.options.initialViewIndex).to.equal(1);\n  });\n});\n"
  },
  {
    "path": "test/specs/options/inline.spec.js",
    "content": "describe('inline (option)', () => {\n  it('should not be inline by default', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      shown() {\n        expect(viewer.fulled).to.be.true;\n        expect(viewer.viewer.className).to.include('viewer-fixed');\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    expect(viewer.options.inline).to.be.false;\n    viewer.show();\n  });\n\n  it('should be inline', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      ready() {\n        expect(viewer.fulled).to.be.false;\n        expect(viewer.viewer.className).to.not.include('viewer-fixed');\n        done();\n      },\n    });\n\n    expect(viewer.options.inline).to.be.true;\n  });\n\n  it('should trigger the `ready`, `view` and `viewed` events', (done) => {\n    const image = window.createImage();\n\n    new Viewer(image, {\n      inline: true,\n\n      ready(event) {\n        expect(event.type).to.equal('ready');\n      },\n\n      view(event) {\n        expect(event.type).to.equal('view');\n      },\n\n      viewed(event) {\n        expect(event.type).to.equal('viewed');\n        done();\n      },\n    });\n  });\n\n  it('should not trigger the `show`, `shown`, `hide` and `hidden` events', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      ready() {\n        done();\n      },\n\n      show() {\n        expect.fail(1, 0);\n      },\n\n      shown() {\n        viewer.hide(true);\n        expect.fail(1, 0);\n      },\n\n      hide() {\n        expect.fail(1, 0);\n      },\n\n      hidden() {\n        expect.fail(1, 0);\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/options/interval.spec.js",
    "content": "describe('interval (option)', () => {\n  it('should be `5000` by default', () => {\n    const imageList = window.createImageList();\n    const viewer = new Viewer(imageList);\n\n    expect(viewer.options.interval).to.equal(5000);\n  });\n\n  it('should show the next image after the delay time', (done) => {\n    const imageList = window.createImageList();\n    const interval = 1000;\n    const viewer = new Viewer(imageList, {\n      interval,\n      inline: true,\n\n      viewed() {\n        viewer.play();\n\n        const { children } = viewer.player;\n\n        expect(children[0].className).to.include('viewer-in');\n        expect(children[1].className).to.not.include('viewer-in');\n        expect(children[2].className).to.not.include('viewer-in');\n        setTimeout(() => {\n          expect(children[0].className).to.not.include('viewer-in');\n          expect(children[1].className).to.include('viewer-in');\n          expect(children[2].className).to.not.include('viewer-in');\n          viewer.stop();\n          done();\n        }, interval + 10);\n      },\n    });\n\n    expect(viewer.options.interval).to.equal(interval);\n  });\n});\n"
  },
  {
    "path": "test/specs/options/keyboard.spec.js",
    "content": "describe('keyboard (option)', () => {\n  it('should be enabled by default', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image);\n\n    expect(viewer.options.keyboard).to.be.true;\n  });\n\n  it('should be disabled', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      keyboard: false,\n\n      viewed() {\n        expect(viewer.isShown).to.be.true;\n        image.dispatchEvent(window.createEvent('keydown', {\n          key: 'Escape',\n          keyCode: 27,\n        }));\n        expect(viewer.isShown).to.be.true;\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    expect(viewer.options.keyboard).to.be.false;\n    viewer.show();\n  });\n\n  it('should hide the viewer when press the \"Escape\" key in modal mode', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      viewed() {\n        image.dispatchEvent(window.createEvent('keydown', {\n          key: 'Escape',\n          keyCode: 27,\n        }));\n      },\n\n      hide() {\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should exit modal mode when press the \"Escape\" key in inline mode', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      viewed() {\n        viewer.full();\n        expect(viewer.fulled).to.be.true;\n        image.dispatchEvent(window.createEvent('keydown', {\n          key: 'Escape',\n          keyCode: 27,\n        }));\n        expect(viewer.fulled).to.be.false;\n        done();\n      },\n    });\n  });\n\n  it('should stop playing when press the \"Escape\" key in modal mode', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      viewed() {\n        viewer.play();\n        expect(viewer.played).to.be.true;\n        image.dispatchEvent(window.createEvent('keydown', {\n          key: 'Escape',\n          keyCode: 27,\n        }));\n        expect(viewer.played).to.be.false;\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should stop playing when press the \"Space bar\" key in modal mode', (done) => {\n    const imageList = window.createImageList();\n    const viewer = new Viewer(imageList, {\n      viewed() {\n        viewer.play();\n        expect(viewer.played).to.be.true;\n        imageList.dispatchEvent(window.createEvent('keydown', {\n          key: ' ',\n          keyCode: 32,\n        }));\n        expect(viewer.played).to.be.false;\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should view the previous image when press the \"ArrowLeft\" key in modal mode', (done) => {\n    const imageList = window.createImageList();\n    const viewer = new Viewer(imageList, {\n      viewed(event) {\n        switch (event.detail.index) {\n          case 0:\n            viewer.hide(true);\n            done();\n            break;\n\n          case 1: {\n            imageList.dispatchEvent(window.createEvent('keydown', {\n              key: 'ArrowLeft',\n              keyCode: 37,\n            }));\n            break;\n          }\n\n          default:\n        }\n      },\n    });\n\n    viewer.view(1);\n  });\n\n  it('should view the next image when press the \"ArrowRight\" key in modal mode', (done) => {\n    const imageList = window.createImageList();\n    const viewer = new Viewer(imageList, {\n      viewed(event) {\n        switch (event.detail.index) {\n          case 0: {\n            imageList.dispatchEvent(window.createEvent('keydown', {\n              key: 'ArrowRight',\n              keyCode: 39,\n            }));\n            break;\n          }\n\n          case 1:\n            viewer.hide(true);\n            done();\n            break;\n\n          default:\n        }\n      },\n    });\n\n    viewer.view(0);\n  });\n\n  it('should zoom in when press the \"ArrowUp\" key in modal mode', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      viewed() {\n        const { ratio } = viewer.imageData;\n\n        image.dispatchEvent(window.createEvent('keydown', {\n          key: 'ArrowUp',\n          keyCode: 38,\n        }));\n        expect(viewer.imageData.ratio).to.be.above(ratio);\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should zoom out when press the \"ArrowDown\" key in modal mode', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      viewed() {\n        const { ratio } = viewer.imageData;\n\n        image.dispatchEvent(window.createEvent('keydown', {\n          key: 'ArrowDown',\n          keyCode: 40,\n        }));\n        expect(viewer.imageData.ratio).to.be.below(ratio);\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should zoom out to initial size when press `Ctrl + 0` in modal mode', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      viewed() {\n        const { imageData } = viewer;\n        const { ratio } = imageData;\n\n        viewer.zoomTo(1);\n        expect(imageData.ratio).to.not.equal(ratio);\n        image.dispatchEvent(window.createEvent('keydown', {\n          ctrlKey: true,\n          key: '0',\n          keyCode: 48,\n        }));\n        expect(imageData.ratio).to.equal(ratio);\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should zoom out in to natural size when press `Ctrl + 1` in modal mode', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      viewed() {\n        const { imageData } = viewer;\n\n        expect(imageData.ratio).to.not.equal(1);\n        image.dispatchEvent(window.createEvent('keydown', {\n          ctrlKey: true,\n          key: '1',\n          keyCode: 49,\n        }));\n        expect(imageData.ratio).to.equal(1);\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n});\n"
  },
  {
    "path": "test/specs/options/loading.spec.js",
    "content": "describe('loading (option)', () => {\n  it('should show a loading spinner when load image by default', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      view() {\n        setTimeout(() => {\n          if (!viewer.image.complete) {\n            expect(viewer.canvas.className).to.include('viewer-loading');\n          }\n\n          done();\n        }, 0);\n      },\n    });\n\n    expect(viewer.options.loading).to.be.true;\n  });\n\n  it('should not show a loading spinner when load image', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n      loading: false,\n\n      view() {\n        setTimeout(() => {\n          expect(viewer.canvas.className).to.not.include('viewer-loading');\n          done();\n        }, 0);\n      },\n    });\n\n    expect(viewer.options.loading).to.be.false;\n  });\n});\n"
  },
  {
    "path": "test/specs/options/loop.spec.js",
    "content": "describe('loop (option)', () => {\n  it('should be loop by default', (done) => {\n    const imageList = window.createImageList();\n    const viewer = new Viewer(imageList, {\n      inline: true,\n\n      viewed(event) {\n        switch (event.detail.index) {\n          case 0:\n            viewer.toolbar.querySelector('.viewer-prev').click();\n            break;\n\n          case 4:\n            done();\n            break;\n\n          default:\n        }\n      },\n    });\n\n    expect(viewer.options.loop).to.be.true;\n  });\n\n  it('should not be loop', (done) => {\n    const imageList = window.createImageList();\n    const viewer = new Viewer(imageList, {\n      inline: true,\n      loop: false,\n\n      viewed(event) {\n        switch (event.detail.index) {\n          case 0:\n            viewer.toolbar.querySelector('.viewer-prev').click();\n            done();\n            break;\n\n          case 4:\n            expect.fail(1, 0);\n            break;\n\n          default:\n        }\n      },\n    });\n\n    expect(viewer.options.loop).to.be.false;\n  });\n});\n"
  },
  {
    "path": "test/specs/options/maxZoomRatio.spec.js",
    "content": "describe('maxZoomRatio (option)', () => {\n  it('should be `100` by default', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image);\n\n    expect(viewer.options.maxZoomRatio).to.equal(100);\n  });\n\n  it('should not be greater than the given maximum zoom ratio', (done) => {\n    const image = window.createImage();\n    const maxZoomRatio = 10;\n    const viewer = new Viewer(image, {\n      inline: true,\n      maxZoomRatio,\n\n      viewed() {\n        viewer.zoomTo(11);\n        expect(viewer.imageData.ratio).to.equal(maxZoomRatio);\n        done();\n      },\n    });\n\n    expect(viewer.options.maxZoomRatio).to.equal(maxZoomRatio);\n  });\n});\n"
  },
  {
    "path": "test/specs/options/minHeight.spec.js",
    "content": "describe('minHeight (option)', () => {\n  it('should be `100` by default', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n    });\n\n    expect(viewer.options.minHeight).to.equal(100);\n  });\n\n  it('should not less than the given minimum height', (done) => {\n    const image = window.createImage();\n    const minHeight = 180;\n\n    image.parentElement.style.height = '90px';\n\n    const viewer = new Viewer(image, {\n      minHeight,\n      inline: true,\n\n      viewed() {\n        expect(parseFloat(window.getComputedStyle(viewer.viewer).height)).to.equal(minHeight);\n        done();\n      },\n    });\n\n    expect(viewer.options.minHeight).to.equal(minHeight);\n  });\n});\n"
  },
  {
    "path": "test/specs/options/minWidth.spec.js",
    "content": "describe('minWidth (option)', () => {\n  it('should be `200` by default', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n    });\n\n    expect(viewer.options.minWidth).to.equal(200);\n  });\n\n  it('should not less than the given minimum width', (done) => {\n    const image = window.createImage();\n    const minWidth = 320;\n\n    image.parentElement.style.width = '160px';\n\n    const viewer = new Viewer(image, {\n      minWidth,\n      inline: true,\n\n      viewed() {\n        expect(parseFloat(window.getComputedStyle(viewer.viewer).width)).to.equal(minWidth);\n        done();\n      },\n    });\n\n    expect(viewer.options.minWidth).to.equal(minWidth);\n  });\n});\n"
  },
  {
    "path": "test/specs/options/minZoomRatio.spec.js",
    "content": "describe('minZoomRatio (option)', () => {\n  it('should be `0.01` by default', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image);\n\n    expect(viewer.options.minZoomRatio).to.equal(0.01);\n  });\n\n  it('should not be less than the given minimum zoom ratio', (done) => {\n    const image = window.createImage();\n    const minZoomRatio = 0.1;\n    const viewer = new Viewer(image, {\n      minZoomRatio,\n      inline: true,\n\n      viewed() {\n        viewer.zoomTo(0.01);\n        expect(viewer.imageData.ratio).to.equal(minZoomRatio);\n        done();\n      },\n    });\n\n    expect(viewer.options.minZoomRatio).to.equal(minZoomRatio);\n  });\n});\n"
  },
  {
    "path": "test/specs/options/movable.spec.js",
    "content": "describe('movable (option)', () => {\n  it('should be movable by default', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      viewed() {\n        viewer.moveTo(0, 0);\n\n        const { imageData } = viewer;\n\n        expect(imageData.left).to.equal(0);\n        expect(imageData.top).to.equal(0);\n        done();\n      },\n    });\n\n    expect(viewer.options.movable).to.be.true;\n  });\n\n  it('should not be movable', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n      movable: false,\n\n      viewed() {\n        viewer.moveTo(0, 0);\n\n        const { imageData } = viewer;\n\n        expect(imageData.left).to.not.equal(0);\n        expect(imageData.top).to.not.equal(0);\n        done();\n      },\n    });\n\n    expect(viewer.options.movable).to.be.false;\n  });\n});\n"
  },
  {
    "path": "test/specs/options/move.spec.js",
    "content": "describe('move (option)', () => {\n  it('should be null be default', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image);\n\n    expect(viewer.options.move).to.be.null;\n  });\n\n  it('should execute the `move` hook function', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      viewed() {\n        viewer.moveTo(0);\n      },\n\n      move(event) {\n        expect(event.type).to.equal('move');\n        event.preventDefault();\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should have expected properties in `event.detail`', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      viewed() {\n        viewer.moveTo(0);\n      },\n\n      move(event) {\n        const { detail } = event;\n\n        expect(detail).to.be.an('object').that.has.all.keys('x', 'y', 'oldX', 'oldY', 'originalEvent');\n        expect(detail.x).to.be.a('number');\n        expect(detail.y).to.be.a('number');\n        expect(detail.oldX).to.be.a('number');\n        expect(detail.oldY).to.be.a('number');\n        expect(detail.originalEvent).to.be.null;\n        event.preventDefault();\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should not execute the `moved` hook function when default prevented', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      viewed() {\n        viewer.moveTo(0);\n      },\n\n      move(event) {\n        event.preventDefault();\n        setTimeout(() => {\n          viewer.hide(true);\n          done();\n        }, 350);\n      },\n\n      moved() {\n        expect.fail(1, 0);\n      },\n    });\n  });\n\n  it('should execute the `move` hook function in inline mode', (done) => {\n    const image = window.createImage();\n\n    new Viewer(image, {\n      inline: true,\n\n      viewed() {\n        this.viewer.moveTo(0);\n      },\n\n      move(event) {\n        expect(event.type).to.equal('move');\n        done();\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/options/moved.spec.js",
    "content": "describe('moved (option)', () => {\n  it('should be null be default', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image);\n\n    expect(viewer.options.moved).to.be.null;\n  });\n\n  it('should execute the `moved` hook function', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      viewed() {\n        viewer.moveTo(0);\n      },\n\n      moved(event) {\n        expect(event.type).to.equal('moved');\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should have expected properties in `event.detail`', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      viewed() {\n        viewer.moveTo(0);\n      },\n\n      moved(event) {\n        const { detail } = event;\n\n        expect(detail).to.be.an('object').that.has.all.keys('x', 'y', 'oldX', 'oldY', 'originalEvent');\n        expect(detail.x).to.be.a('number');\n        expect(detail.y).to.be.a('number');\n        expect(detail.oldX).to.be.a('number');\n        expect(detail.oldY).to.be.a('number');\n        expect(detail.originalEvent).to.be.null;\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should execute the `moved` hook function in inline mode', (done) => {\n    const image = window.createImage();\n\n    new Viewer(image, {\n      inline: true,\n\n      viewed() {\n        this.viewer.moveTo(0);\n      },\n\n      moved(event) {\n        expect(event.type).to.equal('moved');\n        done();\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/options/navbar.spec.js",
    "content": "describe('navbar (option)', () => {\n  it('should show navbar by default', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      ready() {\n        expect(window.getComputedStyle(viewer.navbar).display).to.not.equal('none');\n        done();\n      },\n    });\n\n    expect(viewer.options.navbar).to.be.true;\n  });\n\n  it('should not show navbar', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n      navbar: false,\n\n      ready() {\n        expect(window.getComputedStyle(viewer.navbar).display).to.equal('none');\n        done();\n      },\n    });\n\n    expect(viewer.options.navbar).to.be.false;\n  });\n});\n"
  },
  {
    "path": "test/specs/options/play.spec.js",
    "content": "describe('play (option)', () => {\n  it('should be null be default', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image);\n\n    expect(viewer.options.play).to.be.null;\n  });\n\n  it('should execute the `play` hook function', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      viewed() {\n        expect(viewer.played).to.false;\n        viewer.play();\n        expect(viewer.played).to.true;\n        done();\n      },\n\n      play(event) {\n        expect(event.type).to.equal('play');\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should not execute the `play` hook function when default prevented', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      viewed() {\n        expect(viewer.played).to.false;\n        viewer.play();\n        expect(viewer.played).to.false;\n        done();\n      },\n\n      play(event) {\n        expect(event.type).to.equal('play');\n        event.preventDefault();\n      },\n    });\n\n    viewer.show();\n  });\n});\n"
  },
  {
    "path": "test/specs/options/ready.spec.js",
    "content": "describe('ready (option)', () => {\n  it('should be null be default', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image);\n\n    expect(viewer.options.ready).to.be.null;\n  });\n\n  it('should execute the `ready` hook function', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      ready(event) {\n        expect(event.type).to.equal('ready');\n        event.preventDefault();\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should execute the `ready` hook function in inline mode', (done) => {\n    const image = window.createImage();\n\n    new Viewer(image, {\n      inline: true,\n\n      ready(event) {\n        expect(event.type).to.equal('ready');\n        done();\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/options/rotatable.spec.js",
    "content": "describe('rotatable (option)', () => {\n  it('should be rotatable by default', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      viewed() {\n        viewer.rotateTo(90);\n        expect(viewer.imageData.rotate).to.equal(90);\n        done();\n      },\n    });\n\n    expect(viewer.options.rotatable).to.be.true;\n  });\n\n  it('should not be rotatable', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n      rotatable: false,\n\n      viewed() {\n        viewer.rotateTo(90);\n        expect(viewer.imageData.rotate).to.not.equal(90);\n        done();\n      },\n    });\n\n    expect(viewer.options.rotatable).to.be.false;\n  });\n});\n"
  },
  {
    "path": "test/specs/options/rotate.spec.js",
    "content": "describe('rotate (option)', () => {\n  it('should be null be default', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image);\n\n    expect(viewer.options.rotate).to.be.null;\n  });\n\n  it('should execute the `rotate` hook function', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      viewed() {\n        viewer.rotateTo(45);\n      },\n\n      rotate(event) {\n        expect(event.type).to.equal('rotate');\n        event.preventDefault();\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should have expected properties in `event.detail`', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      viewed() {\n        viewer.rotateTo(45);\n      },\n\n      rotate(event) {\n        const { detail } = event;\n\n        expect(detail).to.be.an('object').that.has.all.keys('degree', 'oldDegree');\n        expect(detail.degree).to.be.a('number');\n        expect(detail.oldDegree).to.be.a('number');\n        event.preventDefault();\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should not execute the `rotated` hook function when default prevented', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      viewed() {\n        viewer.rotateTo(45);\n      },\n\n      rotate(event) {\n        event.preventDefault();\n        setTimeout(() => {\n          viewer.hide(true);\n          done();\n        }, 350);\n      },\n\n      rotated() {\n        expect.fail(1, 0);\n      },\n    });\n  });\n\n  it('should execute the `rotate` hook function in inline mode', (done) => {\n    const image = window.createImage();\n\n    new Viewer(image, {\n      inline: true,\n\n      viewed() {\n        this.viewer.rotateTo(45);\n      },\n\n      rotate(event) {\n        expect(event.type).to.equal('rotate');\n        done();\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/options/rotated.spec.js",
    "content": "describe('rotated (option)', () => {\n  it('should be null be default', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image);\n\n    expect(viewer.options.rotated).to.be.null;\n  });\n\n  it('should execute the `rotated` hook function', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      viewed() {\n        viewer.rotateTo(45);\n      },\n\n      rotated(event) {\n        expect(event.type).to.equal('rotated');\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should have expected properties in `event.detail`', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      viewed() {\n        viewer.rotateTo(45);\n      },\n\n      rotated(event) {\n        const { detail } = event;\n\n        expect(detail).to.be.an('object').that.has.all.keys('degree', 'oldDegree');\n        expect(detail.degree).to.be.a('number');\n        expect(detail.oldDegree).to.be.a('number');\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should execute the `rotated` hook function in inline mode', (done) => {\n    const image = window.createImage();\n\n    new Viewer(image, {\n      inline: true,\n\n      viewed() {\n        this.viewer.rotateTo(45);\n      },\n\n      rotated(event) {\n        expect(event.type).to.equal('rotated');\n        done();\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/options/scalable.spec.js",
    "content": "describe('scalable (option)', () => {\n  it('should be scalable by default', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      viewed() {\n        viewer.scale(-1, -1);\n\n        const { imageData } = viewer;\n\n        expect(imageData.scaleX).to.equal(-1);\n        expect(imageData.scaleY).to.equal(-1);\n        done();\n      },\n    });\n\n    expect(viewer.options.scalable).to.be.true;\n  });\n\n  it('should not be scalable', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n      scalable: false,\n\n      viewed() {\n        viewer.scale(-1, -1);\n\n        const { imageData } = viewer;\n\n        expect(imageData.scaleX).to.not.equal(-1);\n        expect(imageData.scaleY).to.not.equal(-1);\n        done();\n      },\n    });\n\n    expect(viewer.options.scalable).to.be.false;\n  });\n});\n"
  },
  {
    "path": "test/specs/options/scale.spec.js",
    "content": "describe('scale (option)', () => {\n  it('should be null be default', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image);\n\n    expect(viewer.options.scale).to.be.null;\n  });\n\n  it('should execute the `scale` hook function', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      viewed() {\n        viewer.scale(-1);\n      },\n\n      scale(event) {\n        expect(event.type).to.equal('scale');\n        event.preventDefault();\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should have expected properties in `event.detail`', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      viewed() {\n        viewer.scale(-1);\n      },\n\n      scale(event) {\n        const { detail } = event;\n\n        expect(detail).to.be.an('object').that.has.all.keys('scaleX', 'scaleY', 'oldScaleX', 'oldScaleY');\n        expect(detail.scaleX).to.be.a('number');\n        expect(detail.scaleY).to.be.a('number');\n        expect(detail.oldScaleX).to.be.a('number');\n        expect(detail.oldScaleY).to.be.a('number');\n        event.preventDefault();\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should not execute the `scaled` hook function when default prevented', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      viewed() {\n        viewer.scale(-1);\n      },\n\n      scale(event) {\n        event.preventDefault();\n        setTimeout(() => {\n          viewer.hide(true);\n          done();\n        }, 350);\n      },\n\n      scaled() {\n        expect.fail(1, 0);\n      },\n    });\n  });\n\n  it('should execute the `scale` hook function in inline mode', (done) => {\n    const image = window.createImage();\n\n    new Viewer(image, {\n      inline: true,\n\n      viewed() {\n        this.viewer.scale(-1);\n      },\n\n      scale(event) {\n        expect(event.type).to.equal('scale');\n        done();\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/options/scaled.spec.js",
    "content": "describe('scaled (option)', () => {\n  it('should be null be default', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image);\n\n    expect(viewer.options.scaled).to.be.null;\n  });\n\n  it('should execute the `scaled` hook function', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      viewed() {\n        viewer.scale(-1);\n      },\n\n      scaled(event) {\n        expect(event.type).to.equal('scaled');\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should have expected properties in `event.detail`', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      viewed() {\n        viewer.scale(-1);\n      },\n\n      scaled(event) {\n        const { detail } = event;\n\n        expect(detail).to.be.an('object').that.has.all.keys('scaleX', 'scaleY', 'oldScaleX', 'oldScaleY');\n        expect(detail.scaleX).to.be.a('number');\n        expect(detail.scaleY).to.be.a('number');\n        expect(detail.oldScaleX).to.be.a('number');\n        expect(detail.oldScaleY).to.be.a('number');\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should execute the `scaled` hook function in inline mode', (done) => {\n    const image = window.createImage();\n\n    new Viewer(image, {\n      inline: true,\n\n      viewed() {\n        this.viewer.scale(-1);\n      },\n\n      scaled(event) {\n        expect(event.type).to.equal('scaled');\n        done();\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/options/show.spec.js",
    "content": "describe('show (option)', () => {\n  it('should be null be default', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image);\n\n    expect(viewer.options.show).to.be.null;\n  });\n\n  it('should execute the `show` hook function', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      show(event) {\n        expect(event.type).to.equal('show');\n      },\n\n      shown() {\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should not execute the `shown` hook function when default prevented', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      show(event) {\n        event.preventDefault();\n        done();\n      },\n\n      shown() {\n        expect.fail(1, 0);\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should not execute the `show` hook function in inline mode', (done) => {\n    const image = window.createImage();\n\n    new Viewer(image, {\n      inline: true,\n\n      ready() {\n        done();\n      },\n\n      show() {\n        expect.fail(1, 0);\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/options/shown.spec.js",
    "content": "describe('shown (option)', () => {\n  it('should be null be default', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image);\n\n    expect(viewer.options.shown).to.be.null;\n  });\n\n  it('should execute the `shown` hook function', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      shown(event) {\n        expect(event.type).to.equal('shown');\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should not execute the `shown` hook function in inline mode', (done) => {\n    const image = window.createImage();\n\n    new Viewer(image, {\n      inline: true,\n\n      ready() {\n        done();\n      },\n\n      shown() {\n        expect.fail(1, 0);\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/options/slideOnTouch.spec.js",
    "content": "describe('slideOnTouch (option)', () => {\n  it('should be `true` by default', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image);\n\n    expect(viewer.options.slideOnTouch).to.be.true;\n  });\n});\n"
  },
  {
    "path": "test/specs/options/stop.spec.js",
    "content": "describe('stop (option)', () => {\n  it('should be null be default', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image);\n\n    expect(viewer.options.stop).to.be.null;\n  });\n\n  it('should execute the `stop` hook function', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      viewed() {\n        viewer.play();\n        setTimeout(() => {\n          expect(viewer.played).to.true;\n          viewer.stop();\n          expect(viewer.played).to.false;\n          done();\n        }, 500);\n      },\n\n      stop(event) {\n        expect(event.type).to.equal('stop');\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should not execute the `stop` hook function when default prevented', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      viewed() {\n        viewer.play();\n        setTimeout(() => {\n          expect(viewer.played).to.true;\n          viewer.stop();\n          expect(viewer.played).to.true;\n          done();\n        }, 500);\n      },\n\n      stop(event) {\n        expect(event.type).to.equal('stop');\n        event.preventDefault();\n      },\n    });\n\n    viewer.show();\n  });\n});\n"
  },
  {
    "path": "test/specs/options/title.spec.js",
    "content": "describe('title (option)', () => {\n  it('should show title by default', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      ready() {\n        expect(window.getComputedStyle(viewer.title).display).to.not.equal('none');\n        done();\n      },\n    });\n\n    expect(viewer.options.title).to.be.true;\n  });\n\n  it('should not show title', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n      title: false,\n\n      ready() {\n        expect(window.getComputedStyle(viewer.title).display).to.equal('none');\n        done();\n      },\n    });\n\n    expect(viewer.options.title).to.be.false;\n  });\n\n  it('should support function', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      title(img) {\n        return img.alt;\n      },\n\n      ready() {\n        expect(viewer.title.innerHTML).to.equal(image.alt);\n        done();\n      },\n    });\n\n    expect(viewer.options.title).to.be.a('function');\n  });\n\n  it('should support array', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n      title: [3, (img) => img.alt],\n\n      ready() {\n        expect(viewer.title.className).to.include('viewer-hide-sm-down');\n        expect(viewer.title.innerHTML).to.equal(image.alt);\n        done();\n      },\n    });\n\n    expect(viewer.options.title).to.be.an('array');\n  });\n});\n"
  },
  {
    "path": "test/specs/options/toggleOnDblclick.spec.js",
    "content": "describe('toggleOnDblclick (option)', () => {\n  it('should be true by default', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      viewed() {\n        viewer.canvas.firstElementChild.dispatchEvent(window.createEvent('dblclick'));\n        setTimeout(() => {\n          expect(viewer.imageData.ratio).to.equal(1);\n          done();\n        }, 300);\n      },\n    });\n\n    expect(viewer.options.toggleOnDblclick).to.be.true;\n  });\n\n  it('should be false', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n      toggleOnDblclick: false,\n\n      viewed() {\n        viewer.canvas.firstElementChild.dispatchEvent(window.createEvent('dblclick'));\n        setTimeout(() => {\n          expect(viewer.imageData.ratio).to.not.equal(1);\n          done();\n        }, 300);\n      },\n    });\n\n    expect(viewer.options.toggleOnDblclick).to.be.false;\n  });\n});\n"
  },
  {
    "path": "test/specs/options/toolbar.spec.js",
    "content": "describe('toolbar (option)', () => {\n  it('should show toolbar by default', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      ready() {\n        expect(window.getComputedStyle(viewer.toolbar).display).to.not.equal('none');\n        done();\n      },\n    });\n\n    expect(viewer.options.toolbar).to.be.true;\n  });\n\n  it('should not show toolbar', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n      toolbar: false,\n\n      ready() {\n        expect(window.getComputedStyle(viewer.toolbar).display).to.equal('none');\n        done();\n      },\n    });\n\n    expect(viewer.options.toolbar).to.be.false;\n  });\n});\n"
  },
  {
    "path": "test/specs/options/tooltip.spec.js",
    "content": "describe('tooltip (option)', () => {\n  it('should show tooltip when zoom by default', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      viewed() {\n        viewer.zoomTo(1, true);\n        expect(window.getComputedStyle(viewer.tooltipBox).display).to.not.equal('none');\n        done();\n      },\n    });\n\n    expect(viewer.options.tooltip).to.be.true;\n  });\n\n  it('should not show tooltip', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n      tooltip: false,\n\n      viewed() {\n        viewer.zoomTo(1, true);\n        expect(window.getComputedStyle(viewer.tooltipBox).display).to.equal('none');\n        done();\n      },\n    });\n\n    expect(viewer.options.tooltip).to.be.false;\n  });\n});\n"
  },
  {
    "path": "test/specs/options/transition.spec.js",
    "content": "describe('transition (option)', () => {\n  it('should be enabled by default', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      viewed() {\n        expect(viewer.viewer.className).to.include('viewer-transition');\n        expect(viewer.list.className).to.include('viewer-transition');\n        expect(viewer.image.className).to.include('viewer-transition');\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    expect(viewer.options.transition).to.be.true;\n    viewer.show();\n  });\n\n  it('should be disabled', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      transition: false,\n\n      viewed() {\n        expect(viewer.viewer.className).to.not.include('viewer-transition');\n        expect(viewer.list.className).to.not.include('viewer-transition');\n        expect(viewer.image.className).to.not.include('viewer-transition');\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    expect(viewer.options.transition).to.be.false;\n    viewer.show();\n  });\n});\n"
  },
  {
    "path": "test/specs/options/url.spec.js",
    "content": "describe('url (option)', () => {\n  it('should be \"src\" by default', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image);\n\n    expect(viewer.options.url).to.equal('src');\n  });\n\n  it('should support string', (done) => {\n    const image = window.createImage();\n\n    image.dataset.src = image.src;\n\n    new Viewer(image, {\n      inline: true,\n      url: 'data-src',\n\n      viewed() {\n        done();\n      },\n    });\n  });\n\n  it('should support function', (done) => {\n    const image = window.createImage();\n\n    image.dataset.src = image.src;\n\n    new Viewer(image, {\n      inline: true,\n\n      url(img) {\n        expect(this).to.be.an.instanceof(Viewer);\n\n        return img.dataset.src;\n      },\n\n      viewed() {\n        done();\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/options/view.spec.js",
    "content": "describe('view (option)', () => {\n  it('should be null be default', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image);\n\n    expect(viewer.options.view).to.be.null;\n  });\n\n  it('should execute the `view` hook function', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      view(event) {\n        expect(event.type).to.equal('view');\n      },\n\n      viewed() {\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should have expected properties in `event.detail`', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      view(event) {\n        expect(event.detail).to.be.an('object').that.has.all.keys('image', 'index', 'originalImage');\n        expect(event.detail.image.src).to.equal(image.src);\n        expect(event.detail.index).to.equal(0);\n        expect(event.detail.originalImage).to.equal(image);\n      },\n\n      viewed() {\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should not execute the `viewed` hook function when default prevented', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      view(event) {\n        event.preventDefault();\n        setTimeout(() => {\n          viewer.hide(true);\n          done();\n        }, 350);\n      },\n\n      viewed() {\n        expect.fail(1, 0);\n      },\n    });\n  });\n\n  it('should execute the `view` hook function in inline mode', (done) => {\n    const image = window.createImage();\n\n    new Viewer(image, {\n      inline: true,\n\n      view(event) {\n        expect(event.type).to.equal('view');\n        done();\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/options/viewed.spec.js",
    "content": "describe('viewed (option)', () => {\n  it('should be null be default', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image);\n\n    expect(viewer.options.viewed).to.be.null;\n  });\n\n  it('should execute the `viewed` hook function', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      viewed(event) {\n        expect(event.type).to.equal('viewed');\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should have expected properties in `event.detail`', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      viewed(event) {\n        expect(event.detail).to.be.an('object').that.has.all.keys('image', 'index', 'originalImage');\n        expect(event.detail.image.src).to.equal(image.src);\n        expect(event.detail.index).to.equal(0);\n        expect(event.detail.originalImage).to.equal(image);\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should execute the `viewed` hook function in inline mode', (done) => {\n    const image = window.createImage();\n\n    new Viewer(image, {\n      inline: true,\n\n      viewed(event) {\n        expect(event.type).to.equal('viewed');\n        done();\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/options/zIndex.spec.js",
    "content": "describe('zIndex (option)', () => {\n  it('should be `2015` by default', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image);\n\n    expect(viewer.options.zIndex).to.equal(2015);\n  });\n\n  it('should match the given z-index', (done) => {\n    const image = window.createImage();\n    const zIndex = 2016;\n    const viewer = new Viewer(image, {\n      zIndex,\n\n      viewed() {\n        expect(Number(window.getComputedStyle(viewer.viewer).zIndex)).to.equal(zIndex);\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    expect(viewer.options.zIndex).to.equal(zIndex);\n    viewer.show();\n  });\n\n  it('should not work in inline mode', (done) => {\n    const image = window.createImage();\n    const zIndex = 2016;\n    const viewer = new Viewer(image, {\n      zIndex,\n      inline: true,\n\n      viewed() {\n        expect(Number(window.getComputedStyle(viewer.viewer).zIndex)).to.not.equal(zIndex);\n        done();\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/options/zIndexInline.spec.js",
    "content": "describe('zIndexInline (option)', () => {\n  it('should be 0 by default', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n    });\n\n    expect(viewer.options.zIndexInline).to.equal(0);\n  });\n\n  it('should match the given value', (done) => {\n    const image = window.createImage();\n    const zIndexInline = 1;\n    const viewer = new Viewer(image, {\n      zIndexInline,\n      inline: true,\n\n      viewed() {\n        expect(Number(window.getComputedStyle(viewer.viewer).zIndex)).to.equal(zIndexInline);\n        done();\n      },\n    });\n\n    expect(viewer.options.zIndexInline).to.equal(zIndexInline);\n  });\n\n  it('should not work when it is not inline', (done) => {\n    const image = window.createImage();\n    const zIndexInline = 1;\n    const viewer = new Viewer(image, {\n      zIndexInline,\n\n      viewed() {\n        expect(Number(window.getComputedStyle(viewer.viewer).zIndex)).to.not.equal(zIndexInline);\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n});\n"
  },
  {
    "path": "test/specs/options/zoom.spec.js",
    "content": "describe('zoom (option)', () => {\n  it('should be null be default', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image);\n\n    expect(viewer.options.zoom).to.be.null;\n  });\n\n  it('should execute the `zoom` hook function', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      viewed() {\n        viewer.zoomTo(1);\n      },\n\n      zoom(event) {\n        expect(event.type).to.equal('zoom');\n        event.preventDefault();\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should have expected properties in `event.detail`', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      viewed() {\n        viewer.zoomTo(1);\n      },\n\n      zoom(event) {\n        const { detail } = event;\n\n        expect(detail).to.be.an('object').that.has.all.keys('ratio', 'oldRatio', 'originalEvent');\n        expect(detail.ratio).to.be.a('number');\n        expect(detail.oldRatio).to.be.a('number');\n        expect(detail.originalEvent).to.be.null;\n        event.preventDefault();\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should not execute the `zoomed` hook function when default prevented', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n\n      viewed() {\n        viewer.zoomTo(1);\n      },\n\n      zoom(event) {\n        event.preventDefault();\n        setTimeout(() => {\n          viewer.hide(true);\n          done();\n        }, 350);\n      },\n\n      zoomed() {\n        expect.fail(1, 0);\n      },\n    });\n  });\n\n  it('should execute the `zoom` hook function in inline mode', (done) => {\n    const image = window.createImage();\n\n    new Viewer(image, {\n      inline: true,\n\n      viewed() {\n        this.viewer.zoomTo(1);\n      },\n\n      zoom(event) {\n        expect(event.type).to.equal('zoom');\n        done();\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/options/zoomOnTouch.spec.js",
    "content": "describe('zoomOnTouch (option)', () => {\n  it('should be `true` by default', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image);\n\n    expect(viewer.options.zoomOnTouch).to.be.true;\n  });\n});\n"
  },
  {
    "path": "test/specs/options/zoomOnWheel.spec.js",
    "content": "describe('zoomOnWheel (option)', () => {\n  it('should be `true` by default', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image);\n\n    expect(viewer.options.zoomOnWheel).to.be.true;\n  });\n});\n"
  },
  {
    "path": "test/specs/options/zoomRatio.spec.js",
    "content": "describe('zoomRatio (option)', () => {\n  it('should be 0.1 by default', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image);\n\n    expect(viewer.options.zoomRatio).to.equal(0.1);\n  });\n\n  it('should match the given zoom ratio', (done) => {\n    const image = window.createImage();\n    const zoomRatio = 0.2;\n    const viewer = new Viewer(image, {\n      zoomRatio,\n\n      viewed() {\n        const { imageData } = viewer;\n        const { width } = imageData;\n\n        viewer.viewer.dispatchEvent(window.createEvent('wheel', {\n          deltaY: -1,\n        }));\n        expect(imageData.ratio).to.equal((width * (1 + zoomRatio)) / imageData.naturalWidth);\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    expect(viewer.options.zoomRatio).to.equal(zoomRatio);\n    viewer.show();\n  });\n});\n"
  },
  {
    "path": "test/specs/options/zoomable.spec.js",
    "content": "describe('zoomable (option)', () => {\n  it('should be zoomable by default', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image);\n\n    expect(viewer.options.zoomable).to.be.true;\n  });\n\n  it('should not be zoomable', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      inline: true,\n      zoomable: false,\n\n      viewed() {\n        viewer.zoomTo(1);\n        expect(viewer.imageData.ratio).to.not.equal(1);\n        done();\n      },\n    });\n\n    expect(viewer.options.zoomable).to.be.false;\n  });\n});\n"
  },
  {
    "path": "test/specs/options/zoomed.spec.js",
    "content": "describe('zoomed (option)', () => {\n  it('should be null be default', () => {\n    const image = window.createImage();\n    const viewer = new Viewer(image);\n\n    expect(viewer.options.zoomed).to.be.null;\n  });\n\n  it('should execute the `zoomed` hook function', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      viewed() {\n        viewer.zoomTo(1);\n      },\n\n      zoomed(event) {\n        expect(event.type).to.equal('zoomed');\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should have expected properties in `event.detail`', (done) => {\n    const image = window.createImage();\n    const viewer = new Viewer(image, {\n      viewed() {\n        viewer.zoomTo(1);\n      },\n\n      zoomed(event) {\n        const { detail } = event;\n\n        expect(detail).to.be.an('object').that.has.all.keys('ratio', 'oldRatio', 'originalEvent');\n        expect(detail.ratio).to.be.a('number');\n        expect(detail.oldRatio).to.be.a('number');\n        expect(detail.originalEvent).to.be.null;\n        viewer.hide(true);\n        done();\n      },\n    });\n\n    viewer.show();\n  });\n\n  it('should execute the `zoomed` hook function in inline mode', (done) => {\n    const image = window.createImage();\n\n    new Viewer(image, {\n      inline: true,\n\n      viewed() {\n        this.viewer.zoomTo(1);\n      },\n\n      zoomed(event) {\n        expect(event.type).to.equal('zoomed');\n        done();\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "types/index.d.ts",
    "content": "declare namespace Viewer {\n  export type Visibility = 0 | 1 | 2 | 3 | 4;\n  export type ToolbarButtonSize = 'small' | 'medium' | 'large';\n  export type ToolbarOption = boolean | Visibility | ToolbarButtonSize | Function | ToolbarButtonOptions | undefined;\n\n  export interface ToolbarButtonOptions {\n    click?: Function,\n    show?: boolean | Visibility;\n    size?: ToolbarButtonSize,\n  }\n\n  export interface ToolbarOptions {\n    flipHorizontal?: ToolbarOption;\n    flipVertical?: ToolbarOption;\n    next?: ToolbarOption;\n    oneToOne?: ToolbarOption;\n    play?: ToolbarOption;\n    prev?: ToolbarOption;\n    reset?: ToolbarOption;\n    rotateLeft?: ToolbarOption;\n    rotateRight?: ToolbarOption;\n    zoomIn?: ToolbarOption;\n    zoomOut?: ToolbarOption;\n    [x: string]: ToolbarOption;\n  }\n\n  export interface Pivot {\n    x: Number;\n    y: Number;\n  }\n\n  export interface MoveEventData {\n    x: number;\n    y: number;\n    oldX: number;\n    oldY: number;\n    originalEvent: PointerEvent | TouchEvent | MouseEvent | null;\n  }\n\n  export interface MoveEvent extends CustomEvent {\n    detail: MoveEventData;\n  }\n\n  export interface MovedEvent extends CustomEvent {\n    detail: MoveEventData;\n  }\n\n  export interface RotateEventData {\n    degree: number;\n    oldDegree: number;\n  }\n\n  export interface RotateEvent extends CustomEvent {\n    detail: RotateEventData;\n  }\n\n  export interface RotatedEvent extends CustomEvent {\n    detail: RotateEventData;\n  }\n\n  export interface ScaleEventData {\n    scaleX: number;\n    scaleY: number;\n    oldScaleX: number;\n    oldScaleY: number;\n  }\n\n  export interface ScaleEvent extends CustomEvent {\n    detail: ScaleEventData;\n  }\n\n  export interface ScaledEvent extends CustomEvent {\n    detail: ScaleEventData;\n  }\n\n  export interface ZoomEventData {\n    ratio: number;\n    oldRatio: number;\n    originalEvent: WheelEvent | PointerEvent | TouchEvent | MouseEvent | null;\n  }\n\n  export interface ZoomEvent extends CustomEvent {\n    detail: ZoomEventData;\n  }\n\n  export interface ZoomedEvent extends CustomEvent {\n    detail: ZoomEventData;\n  }\n\n  export interface Options {\n    backdrop?: boolean | string;\n    button?: boolean;\n    className?: string;\n    container?: string | HTMLElement;\n    filter?: Function;\n    fullscreen?: boolean | FullscreenOptions;\n    focus?: boolean;\n    hidden?(event: CustomEvent): void;\n    hide?(event: CustomEvent): void;\n    inheritedAttributes?: string[];\n    initialCoverage?: number;\n    initialViewIndex?: number;\n    inline?: boolean;\n    interval?: number;\n    keyboard?: boolean;\n    loading?: boolean;\n    loop?: boolean;\n    maxZoomRatio?: number;\n    minHeight?: number;\n    minWidth?: number;\n    minZoomRatio?: number;\n    movable?: boolean;\n    move?(event: MoveEvent): void;\n    moved?(event: MovedEvent): void;\n    navbar?: boolean | Visibility;\n    play?(event: CustomEvent): void;\n    ready?(event: CustomEvent): void;\n    rotatable?: boolean;\n    rotate?(event: RotateEvent): void;\n    rotated?(event: RotatedEvent): void;\n    scalable?: boolean;\n    scale?(event: ScaleEvent): void;\n    scaled?(event: ScaledEvent): void;\n    show?(event: CustomEvent): void;\n    shown?(event: CustomEvent): void;\n    slideOnTouch?: boolean;\n    stop?(event: CustomEvent): void;\n    title?: boolean | Visibility | Function | [Visibility, Function];\n    toggleOnDblclick?: boolean;\n    toolbar?: boolean | Visibility | ToolbarOptions;\n    tooltip?: boolean;\n    transition?: boolean;\n    url?: string | Function;\n    view?(event: CustomEvent): void;\n    viewed?(event: CustomEvent): void;\n    zIndex?: number;\n    zIndexInline?: number;\n    zoom?(event: ZoomEvent): void;\n    zoomOnTouch?: boolean;\n    zoomOnWheel?: boolean;\n    zoomRatio?: number;\n    zoomable?: boolean;\n    zoomed?(event: ZoomedEvent): void;\n  }\n}\n\ndeclare class Viewer {\n  constructor(element: HTMLElement, options?: Viewer.Options);\n  destroy(): Viewer;\n  exit(): Viewer;\n  full(): Viewer;\n  hide(immediate?: boolean): Viewer;\n  move(offsetX: number, offsetY?: number): Viewer;\n  moveTo(x: number, y?: number): Viewer;\n  next(loop?: boolean): Viewer;\n  play(fullscreen?: boolean | FullscreenOptions): Viewer;\n  prev(loop?: boolean): Viewer;\n  reset(): Viewer;\n  rotate(degree: number): Viewer;\n  rotateTo(degree: number): Viewer;\n  scale(scaleX: number, scaleY?: number): Viewer;\n  scaleX(scaleX: number): Viewer;\n  scaleY(scaleY: number): Viewer;\n  show(immediate?: boolean): Viewer;\n  stop(): Viewer;\n  toggle(): Viewer;\n  tooltip(): Viewer;\n  update(): Viewer;\n  view(index?: number): Viewer;\n  zoom(ratio: number, hasTooltip?: boolean, pivot?: Viewer.Pivot): Viewer;\n  zoomTo(ratio: number, hasTooltip?: boolean, pivot?: Viewer.Pivot): Viewer;\n  static noConflict(): Viewer;\n  static setDefaults(options: Viewer.Options): void;\n}\n\ndeclare module 'viewerjs' {\n  export default Viewer;\n}\n"
  }
]