Full Code of fengyuanchen/viewerjs for AI

main 94eb0ce2d9c6 cached
154 files
746.1 KB
192.7k tokens
584 symbols
1 requests
Download .txt
Showing preview only (790K chars total). Download the full file or copy to clipboard to get everything.
Repository: fengyuanchen/viewerjs
Branch: main
Commit: 94eb0ce2d9c6
Files: 154
Total size: 746.1 KB

Directory structure:
gitextract_1jewh7np/

├── .babelrc
├── .browserslistrc
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitattributes
├── .github/
│   ├── CODE_OF_CONDUCT.md
│   ├── CONTRIBUTING.md
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   └── feature_request.md
│   ├── PULL_REQUEST_TEMPLATE.md
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── .husky/
│   ├── commit-msg
│   └── pre-commit
├── .stylelintignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── commitlint.config.js
├── dist/
│   ├── viewer.common.js
│   ├── viewer.css
│   ├── viewer.esm.js
│   └── viewer.js
├── docs/
│   ├── css/
│   │   ├── main.css
│   │   └── viewer.css
│   ├── examples/
│   │   ├── custom-title.html
│   │   ├── custom-toolbar.html
│   │   ├── dynamic-viewer.html
│   │   ├── moving-range-limit.html
│   │   └── viewer-in-modal.html
│   ├── index.html
│   └── js/
│       ├── main.js
│       └── viewer.js
├── karma.conf.js
├── lint-staged.config.js
├── package.json
├── postcss.config.js
├── rollup.config.js
├── src/
│   ├── css/
│   │   ├── viewer.css
│   │   └── viewer.scss
│   ├── index.css
│   ├── index.js
│   ├── index.scss
│   └── js/
│       ├── constants.js
│       ├── defaults.js
│       ├── events.js
│       ├── handlers.js
│       ├── methods.js
│       ├── others.js
│       ├── render.js
│       ├── template.js
│       ├── utilities.js
│       └── viewer.js
├── stylelint.config.js
├── test/
│   ├── helpers.js
│   └── specs/
│       ├── Viewer.spec.js
│       ├── events/
│       │   ├── hidden.spec.js
│       │   ├── hide.spec.js
│       │   ├── move.spec.js
│       │   ├── moved.spec.js
│       │   ├── play.spec.js
│       │   ├── ready.spec.js
│       │   ├── rotate.spec.js
│       │   ├── rotated.spec.js
│       │   ├── scale.spec.js
│       │   ├── scaled.spec.js
│       │   ├── show.spec.js
│       │   ├── shown.spec.js
│       │   ├── stop.spec.js
│       │   ├── view.spec.js
│       │   ├── viewed.spec.js
│       │   ├── zoom.spec.js
│       │   └── zoomed.spec.js
│       ├── methods/
│       │   ├── destroy.spec.js
│       │   ├── exit.spec.js
│       │   ├── full.spec.js
│       │   ├── hide.spec.js
│       │   ├── move.spec.js
│       │   ├── moveTo.spec.js
│       │   ├── next.spec.js
│       │   ├── noConflict.spec.js
│       │   ├── play.spec.js
│       │   ├── prev.spec.js
│       │   ├── reset.spec.js
│       │   ├── rotate.spec.js
│       │   ├── rotateTo.spec.js
│       │   ├── scale.spec.js
│       │   ├── scaleX.spec.js
│       │   ├── scaleY.spec.js
│       │   ├── setDefaults.spec.js
│       │   ├── show.spec.js
│       │   ├── stop.spec.js
│       │   ├── toggle.spec.js
│       │   ├── tooltip.spec.js
│       │   ├── update.spec.js
│       │   ├── view.spec.js
│       │   ├── zoom.spec.js
│       │   └── zoomTo.spec.js
│       └── options/
│           ├── backdrop.spec.js
│           ├── button.spec.js
│           ├── className.spec.js
│           ├── container.spec.js
│           ├── filter.spec.js
│           ├── focus.spec.js
│           ├── fullscreen.spec.js
│           ├── hidden.spec.js
│           ├── hide.spec.js
│           ├── inheritedAttributes.spec.js
│           ├── initialCoverage.spec.js
│           ├── initialViewIndex.spec.js
│           ├── inline.spec.js
│           ├── interval.spec.js
│           ├── keyboard.spec.js
│           ├── loading.spec.js
│           ├── loop.spec.js
│           ├── maxZoomRatio.spec.js
│           ├── minHeight.spec.js
│           ├── minWidth.spec.js
│           ├── minZoomRatio.spec.js
│           ├── movable.spec.js
│           ├── move.spec.js
│           ├── moved.spec.js
│           ├── navbar.spec.js
│           ├── play.spec.js
│           ├── ready.spec.js
│           ├── rotatable.spec.js
│           ├── rotate.spec.js
│           ├── rotated.spec.js
│           ├── scalable.spec.js
│           ├── scale.spec.js
│           ├── scaled.spec.js
│           ├── show.spec.js
│           ├── shown.spec.js
│           ├── slideOnTouch.spec.js
│           ├── stop.spec.js
│           ├── title.spec.js
│           ├── toggleOnDblclick.spec.js
│           ├── toolbar.spec.js
│           ├── tooltip.spec.js
│           ├── transition.spec.js
│           ├── url.spec.js
│           ├── view.spec.js
│           ├── viewed.spec.js
│           ├── zIndex.spec.js
│           ├── zIndexInline.spec.js
│           ├── zoom.spec.js
│           ├── zoomOnTouch.spec.js
│           ├── zoomOnWheel.spec.js
│           ├── zoomRatio.spec.js
│           ├── zoomable.spec.js
│           └── zoomed.spec.js
└── types/
    └── index.d.ts

================================================
FILE CONTENTS
================================================

================================================
FILE: .babelrc
================================================
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "modules": false
      }
    ]
  ],
  "env": {
    "test": {
      "plugins": [
        "istanbul"
      ]
    }
  }
}


================================================
FILE: .browserslistrc
================================================
defaults
ie >= 9


================================================
FILE: .editorconfig
================================================
root = true

[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true


================================================
FILE: .eslintignore
================================================
*.local*
.husky
coverage
dist
docs
node_modules


================================================
FILE: .eslintrc.js
================================================
module.exports = {
  root: true,
  extends: 'airbnb-base',
  env: {
    browser: true,
  },
  plugins: [
    'import',
  ],
  rules: {
    'import/no-extraneous-dependencies': 'off',
    'no-param-reassign': 'off',
    'no-restricted-properties': 'off',
    'valid-jsdoc': ['error', {
      requireReturn: false,
    }],
  },
  overrides: [
    {
      files: 'test/**/*.spec.js',
      env: {
        mocha: true,
      },
      globals: {
        Viewer: true,
        expect: true,
      },
      rules: {
        'no-new': 'off',
        'no-unused-expressions': 'off',
      },
    },
  ],
};


================================================
FILE: .gitattributes
================================================
# Auto detect text files and perform LF normalization
* text=auto


================================================
FILE: .github/CODE_OF_CONDUCT.md
================================================
# Contributor Code of Conduct

As 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.

We 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.

Examples 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.

Project 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.

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.

This 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


================================================
FILE: .github/CONTRIBUTING.md
================================================
# Contributing to Viewer.js

> Based on [Angular's contributing guidelines](https://github.com/angular/angular/blob/master/CONTRIBUTING.md).

We 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:

- [Contributing to Viewer.js](#contributing-to-viewerjs)
  - [Code of Conduct](#code-of-conduct)
  - [Question or Problem](#question-or-problem)
  - [Issues and Bugs](#issues-and-bugs)
  - [Feature Requests](#feature-requests)
  - [Submission Guidelines](#submission-guidelines)
    - [Submitting an Issue](#submitting-an-issue)
    - [Submitting a Pull Request (PR)](#submitting-a-pull-request-pr)
      - [After your pull request is merged](#after-your-pull-request-is-merged)
  - [Coding Rules](#coding-rules)
  - [Commit Message Guidelines](#commit-message-guidelines)
    - [Commit Message Format](#commit-message-format)
    - [Revert](#revert)
    - [Type](#type)
    - [Scope](#scope)
    - [Subject](#subject)
    - [Body](#body)
    - [Footer](#footer)

## Code of Conduct

Help us keep Viewer.js open and inclusive. Please read and follow our [Code of Conduct](CODE_OF_CONDUCT.md).

## Question or Problem

Do 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`.

Stack Overflow is a much better place to ask questions since:

- There are thousands of people willing to help on Stack Overflow.
- Questions and answers stay available for public viewing so your question/answer might help someone else.
- Stack Overflow's voting system assures that the best answers are prominently visible.

To save you and our time, we will systematically close all issues that are requests for general support and redirect people to Stack Overflow.

## Issues and Bugs

If 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.

## Feature Requests

You 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.

Please consider what kind of change it is:

- 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.
- **Small Features** can be crafted and directly [submitted as a Pull Request](#submitting-a-pull-request-pr).

## Submission Guidelines

### Submitting an Issue

Before 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.

We 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:

- version of Viewer.js used
- 3rd-party libraries and their versions
- and most importantly - a use-case that fails

A 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.

We 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.

Unfortunately, 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.

You can file new issues by filling out our [new issue form](https://github.com/fengyuanchen/viewerjs/issues/new).

### Submitting a Pull Request (PR)

Before you submit your Pull Request (PR) consider the following guidelines:

1. 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.
1. Fork the **fengyuanchen/viewerjs** repo.
1. Make your changes in a new git branch:

    ```shell
    git checkout -b my-fix-branch main
    ```

1. Create your patch, **including appropriate test cases**.
1. Follow our [Coding Rules](#coding-rules).
1. Run the full Viewer.js test suite, and ensure that all tests pass.
1. 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.

    ```shell
    git commit -a
    ```

    Note: the optional commit `-a` command-line option will automatically "add" and "rm" edited files.
1. Push your branch to GitHub:

    ```shell
    git push origin my-fix-branch
    ```

1. In GitHub, send a pull request to `viewerjs:main`.
1. If we suggest changes then:
    - Make the required updates.
    - Re-run the Viewer.js test suites to ensure tests are still passing.
    - Rebase your branch and force push to your GitHub repository (this will update your Pull Request):

    ```shell
    git rebase main -i
    git push -f
    ```

That's it! Thank you for your contribution!

#### After your pull request is merged

After your pull request is merged, you can safely delete your branch and pull the changes from the main (upstream) repository:

1. Delete the remote branch on GitHub either through the GitHub web UI or your local shell as follows:

    ```shell
    git push origin --delete my-fix-branch
    ```

1. Check out the main branch:

    ```shell
    git checkout main -f
    ```

1. Delete the local branch:

    ```shell
    git branch -D my-fix-branch
    ```

1. Update your main with the latest upstream version:

    ```shell
    git pull --ff upstream main
    ```

## Coding Rules

To ensure consistency throughout the source code, keep these rules in mind as you are working:

- All features or bug fixes **must be tested** by one or more specs (unit-tests).
- All public API methods **must be documented**.
- We follow [Airbnb's JavaScript Style Guide](https://github.com/airbnb/javascript).

## Commit Message Guidelines

### Commit Message Format

A commit message consists of a **header**, **body** and **footer**. The header has a **type**, **scope** and **subject**:

```
<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
```

The **header** is mandatory and the **scope** of the header is optional.

Any 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.

The footer should contain a [closing reference to an issue](https://help.github.com/articles/closing-issues-via-commit-messages/) if any.

Here are some [samples](https://github.com/fengyuanchen/viewerjs/commits/main).

### Revert

If 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.

### Type

Must be one of the following:

- **build**: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
- **ci**: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
- **docs**: Documentation only changes
- **feat**: A new feature
- **fix**: A bug fix
- **perf**: A code change that improves performance
- **refactor**: A code change that neither fixes a bug nor adds a feature
- **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
- **test**: Adding missing tests or correcting existing tests

### Scope

The scope could be anything specifying the place of the commit change. For example `show`, `view`, `play`, etc...

### Subject

The subject contains a succinct description of the change:

- Use the imperative, present tense: "change" not "changed" nor "changes".
- Don't capitalize the first letter.
- No dot (.) at the end.

### Body

Just 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.

### Footer

The footer should contain any information about **Breaking Changes** and is also the place to reference GitHub issues that this commit **Closes**.

**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.


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: "\U0001F41E Bug report"
description: Create a report to help us improve
body:
  - type: markdown
    attributes:
      value: |
        **Before You Start...**

        This form is only for submitting bug reports. If you have a usage question
        or are unsure if this is really a bug, make sure to:

        - Read the [docs](https://fengyuanchen.github.io/viewerjs)
        - Ask on [GitHub Discussions](https://github.com/fengyuanchen/viewerjs/discussions)
        - Look for / ask questions on [Stack Overflow](https://stackoverflow.com/questions/ask?tags=viewerjs)

        Also try to search for your issue - it may have already been answered or even fixed in the development branch.
        However, if you find that an old, closed issue still persists in the latest version,
        you should open a new issue using the form below instead of commenting on the old issue.
  - type: input
    id: version
    attributes:
      label: Viewer version
    validations:
      required: true
  - type: input
    id: reproduction-link
    attributes:
      label: Link to minimal reproduction
      description: |
        The easiest way to provide a reproduction is by showing the bug in the [Playground](https://fengyuanchen.github.io/viewerjs).
        If it cannot be reproduced in the playground and requires a proper build setup, try [StackBlitz](https://stackblitz.com/?starters=frontend).
        If neither of these are suitable, you can always provide a GitHub repository.

        The reproduction should be **minimal** - i.e. it should contain only the bare minimum amount of code needed
        to show the bug.

        Please do not just fill in a random link. The issue will be closed if no valid reproduction is provided.
      placeholder: Reproduction Link
    validations:
      required: true
  - type: textarea
    id: steps-to-reproduce
    attributes:
      label: Steps to reproduce
      description: |
        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.
      placeholder: Steps to reproduce
    validations:
      required: true
  - type: textarea
    id: expected
    attributes:
      label: What is expected?
    validations:
      required: true
  - type: textarea
    id: actually-happening
    attributes:
      label: What is actually happening?
    validations:
      required: true
  - type: textarea
    id: system-info
    attributes:
      label: System Info
      description: Output of `npx envinfo --system --npmPackages viewerjs --binaries --browsers`
      render: shell
      placeholder: System, Binaries, Browsers
  - type: textarea
    id: additional-comments
    attributes:
      label: Any additional comments?
      description: e.g. some background/context of how you ran into this bug.


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
  - name: Questions & Discussions
    url: https://github.com/fengyuanchen/viewerjs/discussions
    about: Use GitHub discussions for message-board style questions and discussions.


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''

---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.


================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
<!-- Please don't delete this template -->

<!-- PULL REQUEST TEMPLATE -->
<!-- (Update "[ ]" to "[x]" to check a box) -->

**Summary**

**What kind of change does this PR introduce?** (check at least one)

- [ ] Bugfix
- [ ] Feature
- [ ] Code style update
- [ ] Refactor
- [ ] Docs
- [ ] Build-related changes
- [ ] Other, please describe:

If changing the UI of the default theme, please provide the **before/after** screenshot:

**Does this PR introduce a breaking change?** (check one)

- [ ] Yes
- [ ] No

If yes, please describe the impact and migration path for existing applications:

**The PR fulfills these requirements:**

- [ ] When resolving a specific issue, it's referenced in the PR's title (e.g. `fix #xxx[,#xxx]`, where "xxx" is the issue number)

You have tested in the following browsers: (Providing a detailed version will be better.)

- [ ] Chrome
- [ ] Firefox
- [ ] Safari
- [ ] Edge
- [ ] IE

If adding a **new feature**, the PR's description includes:

- [ ] A convincing reason for adding this feature
- [ ] Related documents have been updated
- [ ] Related tests have been updated

To avoid wasting your time, it's best to open a **feature request issue** first and wait for approval before working on it.

**Other information:**


================================================
FILE: .github/workflows/ci.yml
================================================
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 18
      - run: npm install
      - run: npm run lint
      - run: npm run build
      - run: npm test
      - uses: codecov/codecov-action@v5
        env:
          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}


================================================
FILE: .gitignore
================================================
*.local*
*.log
*.map
.DS_Store
coverage
node_modules


================================================
FILE: .husky/commit-msg
================================================
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx commitlint --edit $1


================================================
FILE: .husky/pre-commit
================================================
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx lint-staged


================================================
FILE: .stylelintignore
================================================
*.local*
.husky
coverage
dist
docs/css/viewer.css
node_modules


================================================
FILE: CHANGELOG.md
================================================
# Changelog

## 1.11.7 (Nov 24, 2024)

- Use SVG icons for better visual effects (#637).

## 1.11.6 (Sep 17, 2023)

- Fix an issue where some CSS styles were incompatible with old browsers (#611).

## 1.11.5 (Aug 26, 2023)

- Fix the issue of title blinking when opening the same image again (#609).

## 1.11.4 (Jul 23, 2023)

- Fix the incorrect RegExp for Safari browser detection (#606).

## 1.11.3 (Mar 5, 2023)

- Not actually moving when the `offsetX/Y` is `0` (#585, #588).

## 1.11.2 (Jan 1, 2023)

- Do not close the viewer when dragging the image on the backdrop (#577).

## 1.11.1 (Nov 6, 2022)

- Add missing type definitions for `initialCoverage` option and `zoom` and `zoomTo` methods (#571).

## 1.11.0 (Oct 16, 2022)

- Add a new option: `initialCoverage` (#314, #526).
- Don't load images in the list when hide the navbar (#451).
- Support for providing pivot pointer coordinates to `zoom` and `zoomTo` methods (#202).
- Don't override the padding right of the body when the width of the scrollbar is zero (#197).
- Add keyboard support to playing view (#90).

## 1.10.5 (Apr 5, 2022)

- Continue to initialize even if some images fail to load in inline mode.
- Avoid conflicts with nested modals (#540).

## 1.10.4 (Feb 13, 2022)

- Use legacy color function notation for better compatibility (#529).

## 1.10.3 (Feb 2, 2022)

- Get the pageX/Y properties from the original event when it is an emulated double click in touch devices (#527).
- Improve the zoom experience on the touch screen (#510).

## 1.10.2 (Oct 22, 2021)

- Increase title height for avoiding truncation (#509).
- Fix a `TypeError` when there are not any images (#504).
- Remove loading class on image load error (#502).

## 1.10.1 (Aug 1, 2021)

- Check if the active item exists to avoid TypeError (#491).
- Compute nav item gutter dynamically (#487).

## 1.10.0 (Jun 12, 2021)

- Enhance `fullscreen` option and `play` method to support [`FullscreenOptions`](https://developer.mozilla.org/en-US/docs/Web/API/FullscreenOptions) (#482, #483).
- Refactor the `toggle` method for toggling image between current/natural ratio (#477).
- Improve the `toggle` method for zooming by double-click (#422).

## 1.9.2 (May 29, 2021)

- Avoid conflicts with other modals (#474).
- Allow `ToolbarOption` to be undefined (#473).

## 1.9.1 (May 22, 2021)

- Fix the missing declaration for the `focus` option (#470).

## 1.9.0 (Dec 6, 2020)

- Add 6 new events: `move`, `moved`, `rotate`, `rotated`, `scale`, and `scaled`.
- Add an example for limiting the moving range with the help of the `move` event.

## 1.8.0 (Nov 8, 2020)

- Add a new option: `focus` .
- Add ARIA attributes for better accessibility.
- Add the `Tab` and `Enter` keys to the keyboard support.
- Check if the `pointer` object is defined or not for better compatibility (#421).

## 1.7.1 (Sep 29, 2020)

- Fix an issue in the `types/index.d.ts` file (#414).

## 1.7.0 (Sep 26, 2020)

- Add 2 new events: `play` and `stop` (#411).
- Let the `viewed`, `zoomed`, and `hidden` events not be canceled.
- Improve the TypeScript declarations in the `types/index.d.ts` file.

## 1.6.2 (Aug 30, 2020)

- Improve the `hide` method for some edge cases (#407).
- Improve the wheel zoom behavior (#396).
- Fix wrong usage about `this` in ES6+ (#395).

## 1.6.1 (Jun 14, 2020)

- Improve image filtering.

## 1.6.0 (Jun 6, 2020)

- Add a new options: `inheritedAttributes`.
- Remove unnecessary `padding-right: 0px` from the `body` element when close the viewer modal (#394).
- Reset the `padding-right` of the `body` element when resizing (#379).
- Improve the `hide` method for unexpected calling (#367).
- Ignore images without the `src` attribute (#326).

## 1.5.0 (Nov 23, 2019)

- Force reflow element in a new way to avoid side-effect (#343).
- Add a new option: `slideOnTouch` (#340).
- Detect if the queried image is existing when updating the image list (#333).

## 1.4.0 (Oct 26, 2019)

- Add two new options: `zoomOnTouch` and `zoomOnWheel` (#329).

## 1.3.7 (Oct 2, 2019)

- Improve event type determining for iOS 13+ (#321).
- Ignore invalid `element` parameter on the class utility functions (#317).
- Do nothing if the `index` value is invalid when calling the `view` method (#312).

## 1.3.6 (Jul 4, 2019)

- Avoid escaping URLs (#298, #301).
- Avoid using the `innerHTML` property for security (#269).

## 1.3.5 (Jun 29, 2019)

- Improve the escaping function to avoid escaping HTML entities repeatedly.

## 1.3.4 (Jun 1, 2019)

- Decode image name when it comes from URL (#282).
- Fix the missing fade-out transition when hiding the viewer (#275).
- Escape all strings that use in HTML for better security (#269).

## 1.3.3 (Apr 6, 2019)

- Fix unexpected modal exiting behavior when the mouse is pressed (#255).
- Abort image downloading when cancel viewing for better performance.

## 1.3.2 (Jan 24, 2019)

- Fix `Document not active` error when calling the `exit` method.
- Improve wheel event listening for better performance (#102).

## 1.3.1 (Dec 9, 2018)

- Ignore pointer events when not the primary button was pressed (#221).
- Emulate click (single tap) and double click (double tap) in touch devices to support backdrop and image zooming (#210).

## 1.3.0 (Oct 25, 2018)

- Fix wrong click action when target image is ignored by the `filter` option (#211)
- Add a new option: `className` (#209).

## 1.2.1 (Oct 20, 2018)

- Improve viewer instance storage to avoid side-effect.
- Fix parameter error of `Object.assign` in iOS devices.

## 1.2.0 (Jul 15, 2018)

- Enhance the `title` option to support to customize title content (#54, #185).
- Add 2 new options: `toggleOnDblclick` (#173) and `initialViewIndex` (#183).

## 1.1.0 (May 27, 2018)

- Make the touch zooming smoother (#162).
- Add 2 new events: `zoom` and `zoomed` (#144).

## 1.0.1 (May 20, 2018)

- Add a namespace to data attribute names (from `data-*` to `data-viewer-*`) to avoid side-effect.
- Make sure the image data is a non-null object to avoid unexpected errors.
- Fix broken zoom feature in iOS browsers (#167).

## 1.0.0 (Apr 1, 2018)

- Add in browser checking to support import in Node.js.
- Cancel update when there are no images when calling the `update` method.

## 1.0.0-rc.1 (Mar 13, 2018)

- Fix the wrong image switching behavior in iOS browsers.
- Fix a `TypeError` in strict mode (#149).
- Fix type definitions issue of the `show` and `hide` methods.

## 1.0.0-rc (Mar 10, 2018)

- Add a new option: `loading`.
- Add type definitions file for TypeScript.
- Enhance the `show`, `hide`, and `play` methods.
- Change the default value of the `loop` option from `false` to `true`.

## 1.0.0-beta.2 (Feb 13, 2018)

- Add a new option: `container`.
- Recover the missing default value of the `interval` option (#133).

## 1.0.0-beta.1 (Dec 23, 2017)

- Add a new option: `backdrop`.

## 1.0.0-beta (Dec 12, 2017)

- Add `style` field to `package.json`.
- Fall back to `document.documentElement` if `document.body` is not existing (#120).
- Fix the issue of NodeList deconstructing (#118).

## 0.10.0 (Nov 5, 2017)

- Add a new option: `loop`.
- Enhance toolbar customization.

## 0.9.0 (Nov 4, 2017)

- Add a new option: `filter`.
- Support to customize the layout of the toolbar (#79).
- Enhance the `prev` and `next` methods (#47).
- Disallow to show again if it had shown.

## 0.8.0 (Oct 8, 2017)

- Refactor - separate constants, simplify utilities, and so on.
- Stop play after exited fullscreen.
- Improve JSDoc.

## 0.7.2 (Aug 19, 2017)

- Fixed multiple active items in the navbar (#75).
- Ignore the mouse down event when the viewer is hiding (#70).

## 0.7.1 (May 14, 2017)

- Support to use the viewer in a modal (#39).

## 0.7.0 (Apr 30, 2017)

- Changed the `main` field value from `dist/viewer.js` (UMD) to `dist/viewer.common.js` (CommonJS).
- Added `module` and `browser` fields to `package.json`.
- Fixed an issue of touch zoom.

## 0.6.2 (Mar 4, 2017)

- Fixed the issue of touch and move problem (#63).

## 0.6.1 (Feb 18, 2017)

- Prevented the default behavior of drag action (#63).

## 0.6.0 (Jan 24, 2017)

- Ported JavaScript code to ECMAScript 6.
- Ported CSS code to CSSNext.

## 0.5.1 (Jan 2, 2017)

- Improve event handler for Pointer Events.

## 0.5.0 (July 22, 2016)

- Improve modal opening and closing.
- Remove the `build` event.
- Rename `built` event to `ready`.
- Fixed a bug of `data-*` attributes setting and getting (#33).

## 0.4.0 (Mar 20, 2016)

- Added some properties to `event.detail` of the "view" and "viewed" events.

## 0.3.3 (Mar 19, 2016)

- Fix the issue of hiding the wrong element in the "view" method (#19).

## 0.3.2 (Mar 11, 2016)

- Fix the error of the parameters on the `url` option when it is a function.

## 0.3.1 (Feb 2, 2016)

- Added tests.
- Ignored the invalid class name.
- Re-render image only when viewed.

## 0.3.0 (Jan 21, 2016)

- Add more available values to the "title", "toolbar" and "navbar" options.
- Support to toggle the visibility of title, toolbar, and navbar between different screen widths.
- Exit fullscreen when stop playing.
- Fixed title not generated bug.

## 0.2.0 (Jan 1, 2016)

- Added "update" method for update image dynamically.
- Hides title and toolbar on small screen (width < 768px).

## 0.1.1 (Dec 28, 2015)

- Supports to zoom from event triggering point.
- Optimized "toggle" method.
- Fixed a bug about the index of the viewing image.

## 0.1.0 (Dec 24, 2015)

- Supports 2 modes: "modal" (default), "inline"
- 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"
- 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"
- Supports 8 events: "build", "built", "show", "shown", "hide", "hidden", "view", "viewed"


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright 2015-present Chen Fengyuan

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


================================================
FILE: README.md
================================================
# Viewer.js

[![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)

> JavaScript image viewer.

- [Website](https://fengyuanchen.github.io/viewerjs)
- [jquery-viewer](https://github.com/fengyuanchen/jquery-viewer) - A jQuery plugin wrapper for Viewer.js.

## Table of contents

- [Features](#features)
- [Main Files](#main-files)
- [Getting started](#getting-started)
- [Keyboard support](#keyboard-support)
- [Options](#options)
- [Methods](#methods)
- [Events](#events)
- [No conflict](#no-conflict)
- [Browser support](#browser-support)
- [Contributing](#contributing)
- [Versioning](#versioning)
- [License](#license)

## Features

- Supports 53 [options](#options)
- Supports 23 [methods](#methods)
- Supports 17 [events](#events)
- Supports modal and inline modes
- Supports touch
- Supports move
- Supports zoom
- Supports rotation
- Supports scale (flip)
- Supports keyboard
- Cross-browser support

## Main files

```text
dist/
├── viewer.css
├── viewer.min.css   (compressed)
├── viewer.js        (UMD)
├── viewer.min.js    (UMD, compressed)
├── viewer.common.js (CommonJS, default)
└── viewer.esm.js    (ES Module)
```

## Getting started

### Installation

```shell
npm install viewerjs
```

In browser:

```html
<link  href="/path/to/viewer.css" rel="stylesheet">
<script src="/path/to/viewer.js"></script>
```

The [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).

### Usage

#### Syntax

```js
new Viewer(element[, options])
```

- **element**
  - Type: `HTMLElement`
  - The target image or container of images for viewing.

- **options** (optional)
  - Type: `Object`
  - The options for viewing. Check out the available [options](#options).

#### Example

```html
<!-- a block container is required -->
<div>
  <img id="image" src="picture.jpg" alt="Picture">
</div>

<div>
  <ul id="images">
    <li><img src="picture-1.jpg" alt="Picture 1"></li>
    <li><img src="picture-2.jpg" alt="Picture 2"></li>
    <li><img src="picture-3.jpg" alt="Picture 3"></li>
  </ul>
</div>
```

```js
// You should import the CSS file.
// import 'viewerjs/dist/viewer.css';
import Viewer from 'viewerjs';

// View an image.
const viewer = new Viewer(document.getElementById('image'), {
  inline: true,
  viewed() {
    viewer.zoomTo(1);
  },
});
// Then, show the image by clicking it, or call `viewer.show()`.

// View a list of images.
// Note: All images within the container will be found by calling `element.querySelectorAll('img')`.
const gallery = new Viewer(document.getElementById('images'));
// Then, show one image by click it, or call `gallery.show()`.
```

## Keyboard support

> Only available in modal mode.

- `Esc`: Exit full screen or close the viewer or exit modal mode or stop play.
- `Space`: Stop play.
- `Tab`: Switch the focus state on the buttons in the viewer.
- `Enter`: Trigger the click event handler on the button.
- `←`: View the previous image.
- `→`: View the next image.
- `↑`: Zoom in the image.
- `↓`: Zoom out the image.
- `Ctrl + 0`: Zoom out to initial size.
- `Ctrl + 1`: Zoom in to natural size.

[⬆ back to top](#table-of-contents)

## Options

You may set viewer options with `new Viewer(image, options)`.
If you want to change the global default options, You may use `Viewer.setDefaults(options)`.

### backdrop

- Type: `Boolean` or `String`
- Default: `true`

Enable the modal backdrop, specify `static` for the backdrop that will not close the modal on click.

### button

- Type: `Boolean`
- Default: `true`

Show the button on the top-right of the viewer.

### navbar

- Type: `Boolean` or `Number`
- Default: `true`
- Options:
  - `0` or `false`: hide the navbar
  - `1` or `true`: show the navbar
  - `2`: show the navbar only when the screen width is greater than 768 pixels
  - `3`: show the navbar only when the screen width is greater than 992 pixels
  - `4`: show the navbar only when the screen width is greater than 1200 pixels

Specify the visibility of the navbar.

### title

- Type: `Boolean` or `Number` or `Function` or `Array`
- Default: `true`
- Options:
  - `0` or `false`: hide the title
  - `1` or `true` or `Function` or `Array`: show the title
  - `2`: show the title only when the screen width is greater than 768 pixels
  - `3`: show the title only when the screen width is greater than 992 pixels
  - `4`: show the title only when the screen width is greater than 1200 pixels
  - `Function`: customize the title content
  - `[Number, Function]`: the first element indicate the visibility, the second element customize the title content

Specify the visibility and the content of the title.

> The name comes from the `alt` attribute of an image element or the image name parsed from its URL.

For example, `title: 4` equals to:

```js
new Viewer(image, {
  title: [4, (image, imageData) => `${image.alt} (${imageData.naturalWidth} × ${imageData.naturalHeight})`]
});
```

### toolbar

- Type: `Boolean` or `Number` or `Object`
- Default: `true`
- Options:
  - `0` or `false`: hide the toolbar.
  - `1` or `true`: show the toolbar.
  - `2`: show the toolbar only when the screen width is greater than 768 pixels.
  - `3`: show the toolbar only when the screen width is greater than 992 pixels.
  - `4`: show the toolbar only when the screen width is greater than 1200 pixels.
  - `{ key: Boolean | Number }`: show or hide the toolbar.
  - `{ key: String }`: customize the size of the button.
  - `{ key: Function }`: customize the click handler of the button.
  - `{ key: { show: Boolean | Number, size: String, click: Function }`: customize each property of the button.
  - Available built-in keys: "zoomIn", "zoomOut", "oneToOne", "reset", "prev", "play", "next", "rotateLeft", "rotateRight", "flipHorizontal", "flipVertical".
  - Available built-in sizes: "small", "medium" (default) and "large".

Specify the visibility and layout of the toolbar its buttons.

For example, `toolbar: 4` equals to:

```js
new Viewer(image, {
  toolbar: {
    zoomIn: 4,
    zoomOut: 4,
    oneToOne: 4,
    reset: 4,
    prev: 4,
    play: {
      show: 4,
      size: 'large',
    },
    next: 4,
    rotateLeft: 4,
    rotateRight: 4,
    flipHorizontal: 4,
    flipVertical: 4,
  },
});
```

> see more for [custom toolbar](docs/examples/custom-toolbar.html).

### className

- Type: `String`
- Default: `''`

Custom class name(s) to add to the viewer's root element.

### container

- Type: `Element` or `String`
- Default: `'body'`
- An element or a valid selector for [Document.querySelector](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)

Container to place the viewer in the modal mode.

> Only available when the `inline` option is set to `false`.

### filter

- Type: `Function`
- Default: `null`

Filter the images for viewing (should return `true` if the image is viewable, return `false` to ignore the image).

For example:

```js
new Viewer(image, {
  filter(image) {
    return image.complete;
  },
});
```

> Note that images without the `src` attribute set will be ignored by default.

### fullscreen

- Type: `Boolean` or [`FullscreenOptions`](https://developer.mozilla.org/en-US/docs/Web/API/FullscreenOptions)
- Default: `true`

Enable to request full screen when play.

> Requires the browser supports [Fullscreen API](https://caniuse.com/fullscreen).

### inheritedAttributes

- Type: `Array`
- Default: `['crossOrigin', 'decoding', 'isMap', 'loading', 'referrerPolicy', 'sizes', 'srcset', 'useMap']`

Define the extra attributes to inherit from the original image.

> Note that the basic attributes `src` and `alt` will always inherit from the original image.

### initialCoverage

- Type: `Number`
- Default: `0.9`

Define the initial coverage of the viewing image. It must a positive number between 0 (0%) and 1 (100%).

### initialViewIndex

- Type: `Number`
- Default: `0`

Define the initial index of the image for viewing.

> Also used as the default parameter value of the `view` method.

### inline

- Type: `Boolean`
- Default: `false`

Enable inline mode.

### interval

- Type: `Number`
- Default: `5000`

The amount of time to delay between automatically cycling an image when playing.

### keyboard

- Type: `Boolean`
- Default: `true`

Enable keyboard support.

### focus

- Type: `Boolean`
- Default: `true`

Focus the active item in the navbar when initialized.

> Requires the `keyboard` option set to `true`.

### loading

- Type: `Boolean`
- Default: `true`

Indicate if showing a loading spinner when loading the image or not.

### loop

- Type: `Boolean`
- Default: `true`

Indicate if enabling loop viewing or not.

> If the current image is the last one, then the next one to view is the first one, and vice versa.

### minWidth

- Type: `Number`
- Default: 200

Define the minimum width of the viewer.

> Only available in inline mode (set the `inline` option to `true`).

### minHeight

- Type: `Number`
- Default: 100

Define the minimum height of the viewer.

> Only available in inline mode (set the `inline` option to `true`).

### movable

- Type: `Boolean`
- Default: `true`

Enable to move the image.

### rotatable

- Type: `Boolean`
- Default: `true`

Enable to rotate the image.

### scalable

- Type: `Boolean`
- Default: `true`

Enable to scale the image.

### zoomable

- Type: `Boolean`
- Default: `true`

Enable to zoom the image.

### zoomOnTouch

- Type: `Boolean`
- Default: `true`

Enable to zoom the current image by dragging on the touch screen.

### zoomOnWheel

- Type: `Boolean`
- Default: `true`

Enable to zoom the image by wheeling the mouse.

### slideOnTouch

- Type: `Boolean`
- Default: `true`

Enable to slide to the next or previous image by swiping on the touch screen.

### toggleOnDblclick

- Type: `Boolean`
- Default: `true`

Indicate if toggle the image size between its natural size and initial size when double click on the image or not.

In other words, call the [`toggle`](#toggle) method automatically when double click on the image.

> Requires [`dblclick`](https://developer.mozilla.org/en-US/docs/Web/Events/dblclick) event support.

### tooltip

- Type: `Boolean`
- Default: `true`

Show the tooltip with image ratio (percentage) when zooming in or zooming out.

### transition

- Type: `Boolean`
- Default: `true`

Enable CSS3 Transition for some special elements.

### zIndex

- Type: `Number`
- Default: `2015`

Define the CSS `z-index` value of the viewer in modal mode.

### zIndexInline

- Type: `Number`
- Default: `0`

Define the CSS `z-index` value of the viewer in inline mode.

### zoomRatio

- Type: `Number`
- Default: `0.1`

Define the ratio when zooming the image by wheeling the mouse.

### minZoomRatio

- Type: `Number`
- Default: `0.01`

Define the min ratio of the image when zooming out.

### maxZoomRatio

- Type: `Number`
- Default: `100`

Define the max ratio of the image when zooming in.

### url

- Type: `String` or `Function`
- Default: `'src'`

Define where to get the original image URL for viewing.

> If it is a string, it should be one of the attributes of each image element.
> If it is a function, it should return a valid image URL.

For example:

```html
<img src="picture.jpg?size=160">
```

```js
new Viewer(image, {
  url(image) {
    return image.src.replace('?size=160', '');
  },
});
```

### ready

- Type: `Function`
- Default: `null`

Shortcut of the `ready` event.

### show

- Type: `Function`
- Default: `null`

Shortcut of the `show` event.

### shown

- Type: `Function`
- Default: `null`

Shortcut of the `shown` event.

### hide

- Type: `Function`
- Default: `null`

Shortcut of the `hide` event.

### hidden

- Type: `Function`
- Default: `null`

Shortcut of the `hidden` event.

### view

- Type: `Function`
- Default: `null`

Shortcut of the `view` event.

### viewed

- Type: `Function`
- Default: `null`

Shortcut of the `viewed` event.

### move

- Type: `Function`
- Default: `null`

Shortcut of the `move` event.

### moved

- Type: `Function`
- Default: `null`

Shortcut of the `moved` event.

### rotate

- Type: `Function`
- Default: `null`

Shortcut of the `rotate` event.

### rotated

- Type: `Function`
- Default: `null`

Shortcut of the `rotated` event.

### scale

- Type: `Function`
- Default: `null`

Shortcut of the `scale` event.

### scaled

- Type: `Function`
- Default: `null`

Shortcut of the `scaled` event.

### zoom

- Type: `Function`
- Default: `null`

Shortcut of the `zoom` event.

### zoomed

- Type: `Function`
- Default: `null`

Shortcut of the `zoomed` event.

### play

- Type: `Function`
- Default: `null`

Shortcut of the `play` event.

### stop

- Type: `Function`
- Default: `null`

Shortcut of the `stop` event.

[⬆ back to top](#table-of-contents)

## Methods

All methods allow chain composition.

As there are some **asynchronous** processes when start the viewer, you should call a method only when it is available, see the following **lifecycle**:

```js
new Viewer(image, {
  ready() {
    // 2 methods are available here: "show" and "destroy".
  },
  shown() {
    // 9 methods are available here: "hide", "view", "prev", "next", "play", "stop", "full", "exit" and "destroy".
  },
  viewed() {
    // All methods are available here except "show".
    this.viewer.zoomTo(1).rotateTo(180);
  }
});
```

### show([immediate])

- **immediate** (optional):
  - Type: `Boolean`
  - Default: `false`
  - Indicates if show the viewer immediately or not.

Show the viewer.

> Only available in modal mode.

### hide([immediate])

- **immediate** (optional):
  - Type: `Boolean`
  - Default: `false`
  - Indicates if hide the viewer immediately or not.

Hide the viewer.

> Only available in modal mode.

### view([index])

- **index** (optional):
  - Type: `Number`
  - Default: `0` (inherits from the `initialViewIndex` option)
  - The index of the image for viewing

View one of the images with the image index. If the viewer is hidden, it will be shown first.

```js
viewer.view(1); // View the second image
```

### prev([loop=false])

- **loop** (optional):
  - Type: `Boolean`
  - Default: `false`
  - Indicate if turn to view the last one when it is the first one at present.

View the previous image.

### next([loop=false])

- **loop** (optional):
  - Type: `Boolean`
  - Default: `false`
  - Indicate if turn to view the first one  when it is the last one at present.

View the next image.

### move(x[, y = x])

- **x**:
  - Type: `Number`
  - The moving distance in the horizontal direction.

- **y** (optional):
  - Type: `Number`
  - The moving distance in the vertical direction.
  - If not present, its default value is `x`

Move the image with relative offsets.

```js
viewer.move(1);
viewer.move(-1, 0); // Move left
viewer.move(1, 0);  // Move right
viewer.move(0, -1); // Move up
viewer.move(0, 1);  // Move down
```

### moveTo(x[, y = x])

- **x**:
  - Type: `Number`
  - The new position in the horizontal direction.

- **y** (optional):
  - Type: `Number`
  - The new position in the vertical direction.
  - If not present, its default value is `x`.

Move the image to an absolute point.

### rotate(degree)

- **degree**:
  - Type: `Number`
  - Rotate right: requires a positive number (degree > 0)
  - Rotate left: requires a negative number (degree < 0)

Rotate the image with a relative degree.

```js
viewer.rotate(90);
viewer.rotate(-90);
```

### rotateTo(degree)

- **degree**:
  - Type: `Number`

Rotate the image to an absolute degree.

```js
viewer.rotateTo(0); // Reset to zero degree
viewer.rotateTo(360); // Rotate a full round
```

### scale(scaleX[, scaleY])

- **scaleX**:
  - Type: `Number`
  - Default: `1`
  - The scaling factor to apply on the abscissa of the image
  - When equal to `1` it does nothing.

- **scaleY** (optional):
  - Type: `Number`
  - The scaling factor to apply on the ordinate of the image
  - If not present, its default value is `scaleX`.

Scale the image.

```js
viewer.scale(-1); // Flip both horizontal and vertical
viewer.scale(-1, 1); // Flip horizontal
viewer.scale(1, -1); // Flip vertical
```

### scaleX(scaleX)

- **scaleX**:
  - Type: `Number`
  - Default: `1`
  - The scaling factor to apply on the abscissa of the image
  - When equal to `1` it does nothing

Scale the abscissa of the image.

```js
viewer.scaleX(-1); // Flip horizontal
```

### scaleY(scaleY)

- **scaleY**:
  - Type: `Number`
  - Default: `1`
  - The scaling factor to apply on the ordinate of the image
  - When equal to `1` it does nothing

Scale the ordinate of the image.

```js
viewer.scaleY(-1); // Flip vertical
```

### zoom(ratio[, showTooltip[, pivot]])

- **ratio**:
  - Type: `Number`
  - Zoom in: requires a positive number (ratio > 0)
  - Zoom out: requires a negative number (ratio < 0)

- **showTooltip** (optional):
  - Type: `Boolean`
  - Default: `false`
  - Indicates whether to show the tooltip.

- **pivot** (optional):
  - Type: `Object`
  - Default: `null`
  - Schema: `{ x: Number, y: Number }`
  - The pivot point coordinate for zooming.

Zoom the image with a relative ratio

```js
viewer.zoom(0.1);
viewer.zoom(-0.1);
```

### zoomTo(ratio[, showTooltip[, pivot]])

- **ratio**:
  - Type: `Number`
  - Requires a positive number (ratio > 0)

- **showTooltip** (optional):
  - Type: `Boolean`
  - Default: `false`
  - Indicates whether to show the tooltip.

- **pivot** (optional):
  - Type: `Object`
  - Default: `null`
  - Schema: `{ x: Number, y: Number }`
  - The pivot point coordinate for zooming.

Zoom the image to an absolute ratio.

```js
viewer.zoomTo(0); // Zoom to zero size (0%)
viewer.zoomTo(1); // Zoom to natural size (100%)

// Zoom to 50% from the center of the window.
viewer.zoomTo(.5, {
  x: window.innerWidth / 2,
  y: viewer.innerHeight / 2,
});
```

### play([fullscreen])

- **fullscreen** (optional):
  - Type: `Boolean` or [`FullscreenOptions`](https://developer.mozilla.org/en-US/docs/Web/API/FullscreenOptions)
  - Default: `false`
  - Indicate if request fullscreen or not.

Play the images.

### stop()

Stop play.

### full()

Enter the modal mode.

> Only available in inline mode.

### exit()

Exit the modal mode.

> Only available in inline mode.

### tooltip()

Show the current ratio of the image by percentage.

> Requires the `tooltip` option set to `true`.

### toggle()

Toggle the image size between its current size and natural size.

> Used by the [`toggleOnDblclick`](#toggleOnDblclick) option.

### reset()

Reset the image to its initial state.

### update()

Update the viewer instance when the source images changed (added, removed, or sorted).

> If you load images dynamically (with XMLHTTPRequest), you can use this method to add the new images to the viewer instance.

### destroy()

Destroy the viewer and remove the instance.

[⬆ back to top](#table-of-contents)

## Events

All events can access the viewer instance with `this.viewer` in its handler.

> Be careful to use these events with other components which have the same event names, e.g.: [Bootstrap](https://getbootstrap.com/)'s modal.

```js
let viewer;

image.addEventListener('viewed', function () {
  console.log(this.viewer === viewer);
  // > true
});

viewer = new Viewer(image);
```

### ready

- **event.bubbles**: `true`
- **event.cancelable**: `true`
- **event.detail**: `null`

This event fires when a viewer instance is ready for viewing.

> In modal mode, this event will not be triggered until you click on one of the images.

### show

- **event.bubbles**: `true`
- **event.cancelable**: `true`
- **event.detail**: `null`

This event fires when the viewer modal starts to show.

> Only available in modal mode.

### shown

- **event.bubbles**: `true`
- **event.cancelable**: `true`
- **event.detail**: `null`

This event fires when the viewer modal has shown.

> Only available in modal mode.

### hide

- **event.bubbles**: `true`
- **event.cancelable**: `true`
- **event.detail**: `null`

This event fires when the viewer modal starts to hide.

> Only available in modal mode.

### hidden

- **event.bubbles**: `true`
- **event.cancelable**: `false`
- **event.detail**: `null`

This event fires when the viewer modal has hidden.

> Only available in modal mode.

### view

- **event.bubbles**: `true`
- **event.cancelable**: `true`
- **event.detail.index**:
  - Type: `Number`
  - The index of the original image.
- **event.detail.image**:
  - Type: `HTMLImageElement`
  - The current image (a clone of the original image).
- **event.detail.originalImage**:
  - Type: `HTMLImageElement`
  - The original image.

This event fires when a viewer starts to show (view) an image.

### viewed

- **event.bubbles**: `true`
- **event.cancelable**: `false`
- **event.detail**: the same as the `view` event.

This event fires when a viewer has shown (viewed) an image.

### move

- **event.bubbles**: `true`
- **event.cancelable**: `true`
- **event.detail.x**:
  - Type: `Number`
  - The new position in the horizontal direction.
- **event.detail.y**:
  - Type: `Number`
  - The new position in the vertical direction.
- **event.detail.oldX**:
  - Type: `Number`
  - The old position in the horizontal direction.
- **event.detail.oldY**:
  - Type: `Number`
  - The old position in the vertical direction.
- **event.detail.originalEvent**:
  - Type: `Event` or `null`
  - Options: `pointermove`, `touchmove`, and `mousemove`.

This event fires when a viewer starts to move an image.

### moved

- **event.bubbles**: `true`
- **event.cancelable**: `false`
- **event.detail**: the same as the `move` event.

This event fires when a viewer has moved an image.

### rotate

- **event.bubbles**: `true`
- **event.cancelable**: `true`
- **event.detail.degree**:
  - Type: `Number`
  - The new rotation degrees.
- **event.detail.oldDegree**:
  - Type: `Number`
  - The old rotation degrees.

This event fires when a viewer starts to rotate an image.

### rotated

- **event.bubbles**: `true`
- **event.cancelable**: `false`
- **event.detail**: the same as the `rotate` event.

This event fires when a viewer has rotated an image.

### scale

- **event.bubbles**: `true`
- **event.cancelable**: `true`
- **event.detail.scaleX**:
  - Type: `Number`
  - The new scaling factor in the horizontal direction.
- **event.detail.scaleY**:
  - Type: `Number`
  - The new scaling factor in the vertical direction.
- **event.detail.oldScaleX**:
  - Type: `Number`
  - The old scaling factor in the horizontal direction.
- **event.detail.oldScaleY**:
  - Type: `Number`
  - The old scaling factor in the vertical direction.

This event fires when a viewer starts to scale an image.

### scaled

- **event.bubbles**: `true`
- **event.cancelable**: `false`
- **event.detail**: the same as the `scale` event.

This event fires when a viewer has scaled an image.

### zoom

- **event.bubbles**: `true`
- **event.cancelable**: `true`
- **event.detail.ratio**:
  - Type: `Number`
  - The new (next) ratio of the image (`imageData.width / imageData.naturalWidth`).
- **event.detail.oldRatio**:
  - Type: `Number`
  - The old (current) ratio of the image.
- **event.detail.originalEvent**:
  - Type: `Event` or `null`
  - Options: `wheel`, `pointermove`, `touchmove`, and `mousemove`.

This event fires when a viewer starts to zoom (in or out) an image.

### zoomed

- **event.bubbles**: `true`
- **event.cancelable**: `false`
- **event.detail**: the same as the `zoom` event.

This event fires when a viewer has zoomed (in or out) an image.

### play

- **event.bubbles**: `true`
- **event.cancelable**: `true`
- **event.detail**: `null`

This event fires when the viewer starts to play.

> You can abort the playing process by calling `event.preventDefault()`.

### stop

- **event.bubbles**: `true`
- **event.cancelable**: `true`
- **event.detail**: `null`

This event fires when the viewer starts to stop.

> You can abort the stopping process by calling `event.preventDefault()`.

[⬆ back to top](#table-of-contents)

## No conflict

If you have to use another viewer with the same namespace, call the `Viewer.noConflict` static method to revert to it.

```html
<script src="other-viewer.js"></script>
<script src="viewer.js"></script>
<script>
  Viewer.noConflict();
  // Code that uses other `Viewer` can follow here.
</script>
```

## Browser support

- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Opera (latest)
- Edge (latest)
- Internet Explorer 9+

## Contributing

Please read through our [contributing guidelines](.github/CONTRIBUTING.md).

## Versioning

Maintained under the [Semantic Versioning guidelines](https://semver.org/).

## License

[MIT](https://opensource.org/licenses/MIT) © [Chen Fengyuan](https://chenfengyuan.com/)

[⬆ back to top](#table-of-contents)


================================================
FILE: commitlint.config.js
================================================
module.exports = {
  extends: [
    '@commitlint/config-conventional',
  ],
};


================================================
FILE: dist/viewer.common.js
================================================
/*!
 * Viewer.js v1.11.7
 * https://fengyuanchen.github.io/viewerjs
 *
 * Copyright 2015-present Chen Fengyuan
 * Released under the MIT license
 *
 * Date: 2024-11-24T04:32:19.116Z
 */

'use strict';

function _classCallCheck(a, n) {
  if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function");
}
function _defineProperties(e, r) {
  for (var t = 0; t < r.length; t++) {
    var o = r[t];
    o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o);
  }
}
function _createClass(e, r, t) {
  return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", {
    writable: !1
  }), e;
}
function _defineProperty(e, r, t) {
  return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
    value: t,
    enumerable: !0,
    configurable: !0,
    writable: !0
  }) : e[r] = t, e;
}
function ownKeys(e, r) {
  var t = Object.keys(e);
  if (Object.getOwnPropertySymbols) {
    var o = Object.getOwnPropertySymbols(e);
    r && (o = o.filter(function (r) {
      return Object.getOwnPropertyDescriptor(e, r).enumerable;
    })), t.push.apply(t, o);
  }
  return t;
}
function _objectSpread2(e) {
  for (var r = 1; r < arguments.length; r++) {
    var t = null != arguments[r] ? arguments[r] : {};
    r % 2 ? ownKeys(Object(t), !0).forEach(function (r) {
      _defineProperty(e, r, t[r]);
    }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {
      Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
    });
  }
  return e;
}
function _toPrimitive(t, r) {
  if ("object" != typeof t || !t) return t;
  var e = t[Symbol.toPrimitive];
  if (void 0 !== e) {
    var i = e.call(t, r || "default");
    if ("object" != typeof i) return i;
    throw new TypeError("@@toPrimitive must return a primitive value.");
  }
  return ("string" === r ? String : Number)(t);
}
function _toPropertyKey(t) {
  var i = _toPrimitive(t, "string");
  return "symbol" == typeof i ? i : i + "";
}
function _typeof(o) {
  "@babel/helpers - typeof";

  return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
    return typeof o;
  } : function (o) {
    return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
  }, _typeof(o);
}

var DEFAULTS = {
  /**
   * Enable a modal backdrop, specify `static` for a backdrop
   * which doesn't close the modal on click.
   * @type {boolean}
   */
  backdrop: true,
  /**
   * Show the button on the top-right of the viewer.
   * @type {boolean}
   */
  button: true,
  /**
   * Show the navbar.
   * @type {boolean | number}
   */
  navbar: true,
  /**
   * Specify the visibility and the content of the title.
   * @type {boolean | number | Function | Array}
   */
  title: true,
  /**
   * Show the toolbar.
   * @type {boolean | number | Object}
   */
  toolbar: true,
  /**
   * Custom class name(s) to add to the viewer's root element.
   * @type {string}
   */
  className: '',
  /**
   * Define where to put the viewer in modal mode.
   * @type {string | Element}
   */
  container: 'body',
  /**
   * Filter the images for viewing. Return true if the image is viewable.
   * @type {Function}
   */
  filter: null,
  /**
   * Enable to request fullscreen when play.
   * {@link https://developer.mozilla.org/en-US/docs/Web/API/FullscreenOptions}
   * @type {boolean|FullscreenOptions}
   */
  fullscreen: true,
  /**
   * Define the extra attributes to inherit from the original image.
   * @type {Array}
   */
  inheritedAttributes: ['crossOrigin', 'decoding', 'isMap', 'loading', 'referrerPolicy', 'sizes', 'srcset', 'useMap'],
  /**
   * Define the initial coverage of the viewing image.
   * @type {number}
   */
  initialCoverage: 0.9,
  /**
   * Define the initial index of the image for viewing.
   * @type {number}
   */
  initialViewIndex: 0,
  /**
   * Enable inline mode.
   * @type {boolean}
   */
  inline: false,
  /**
   * The amount of time to delay between automatically cycling an image when playing.
   * @type {number}
   */
  interval: 5000,
  /**
   * Enable keyboard support.
   * @type {boolean}
   */
  keyboard: true,
  /**
   * Focus the viewer when initialized.
   * @type {boolean}
   */
  focus: true,
  /**
   * Indicate if show a loading spinner when load image or not.
   * @type {boolean}
   */
  loading: true,
  /**
   * Indicate if enable loop viewing or not.
   * @type {boolean}
   */
  loop: true,
  /**
   * Min width of the viewer in inline mode.
   * @type {number}
   */
  minWidth: 200,
  /**
   * Min height of the viewer in inline mode.
   * @type {number}
   */
  minHeight: 100,
  /**
   * Enable to move the image.
   * @type {boolean}
   */
  movable: true,
  /**
   * Enable to rotate the image.
   * @type {boolean}
   */
  rotatable: true,
  /**
   * Enable to scale the image.
   * @type {boolean}
   */
  scalable: true,
  /**
   * Enable to zoom the image.
   * @type {boolean}
   */
  zoomable: true,
  /**
   * Enable to zoom the current image by dragging on the touch screen.
   * @type {boolean}
   */
  zoomOnTouch: true,
  /**
   * Enable to zoom the image by wheeling mouse.
   * @type {boolean}
   */
  zoomOnWheel: true,
  /**
   * Enable to slide to the next or previous image by swiping on the touch screen.
   * @type {boolean}
   */
  slideOnTouch: true,
  /**
   * Indicate if toggle the image size between its natural size
   * and initial size when double click on the image or not.
   * @type {boolean}
   */
  toggleOnDblclick: true,
  /**
   * Show the tooltip with image ratio (percentage) when zoom in or zoom out.
   * @type {boolean}
   */
  tooltip: true,
  /**
   * Enable CSS3 Transition for some special elements.
   * @type {boolean}
   */
  transition: true,
  /**
   * Define the CSS `z-index` value of viewer in modal mode.
   * @type {number}
   */
  zIndex: 2015,
  /**
   * Define the CSS `z-index` value of viewer in inline mode.
   * @type {number}
   */
  zIndexInline: 0,
  /**
   * Define the ratio when zoom the image by wheeling mouse.
   * @type {number}
   */
  zoomRatio: 0.1,
  /**
   * Define the min ratio of the image when zoom out.
   * @type {number}
   */
  minZoomRatio: 0.01,
  /**
   * Define the max ratio of the image when zoom in.
   * @type {number}
   */
  maxZoomRatio: 100,
  /**
   * Define where to get the original image URL for viewing.
   * @type {string | Function}
   */
  url: 'src',
  /**
   * Event shortcuts.
   * @type {Function}
   */
  ready: null,
  show: null,
  shown: null,
  hide: null,
  hidden: null,
  view: null,
  viewed: null,
  move: null,
  moved: null,
  rotate: null,
  rotated: null,
  scale: null,
  scaled: null,
  zoom: null,
  zoomed: null,
  play: null,
  stop: null
};

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>';

var IS_BROWSER = typeof window !== 'undefined' && typeof window.document !== 'undefined';
var WINDOW = IS_BROWSER ? window : {};
var IS_TOUCH_DEVICE = IS_BROWSER && WINDOW.document.documentElement ? 'ontouchstart' in WINDOW.document.documentElement : false;
var HAS_POINTER_EVENT = IS_BROWSER ? 'PointerEvent' in WINDOW : false;
var NAMESPACE = 'viewer';

// Actions
var ACTION_MOVE = 'move';
var ACTION_SWITCH = 'switch';
var ACTION_ZOOM = 'zoom';

// Classes
var CLASS_ACTIVE = "".concat(NAMESPACE, "-active");
var CLASS_CLOSE = "".concat(NAMESPACE, "-close");
var CLASS_FADE = "".concat(NAMESPACE, "-fade");
var CLASS_FIXED = "".concat(NAMESPACE, "-fixed");
var CLASS_FULLSCREEN = "".concat(NAMESPACE, "-fullscreen");
var CLASS_FULLSCREEN_EXIT = "".concat(NAMESPACE, "-fullscreen-exit");
var CLASS_HIDE = "".concat(NAMESPACE, "-hide");
var CLASS_HIDE_MD_DOWN = "".concat(NAMESPACE, "-hide-md-down");
var CLASS_HIDE_SM_DOWN = "".concat(NAMESPACE, "-hide-sm-down");
var CLASS_HIDE_XS_DOWN = "".concat(NAMESPACE, "-hide-xs-down");
var CLASS_IN = "".concat(NAMESPACE, "-in");
var CLASS_INVISIBLE = "".concat(NAMESPACE, "-invisible");
var CLASS_LOADING = "".concat(NAMESPACE, "-loading");
var CLASS_MOVE = "".concat(NAMESPACE, "-move");
var CLASS_OPEN = "".concat(NAMESPACE, "-open");
var CLASS_SHOW = "".concat(NAMESPACE, "-show");
var CLASS_TRANSITION = "".concat(NAMESPACE, "-transition");

// Native events
var EVENT_CLICK = 'click';
var EVENT_DBLCLICK = 'dblclick';
var EVENT_DRAG_START = 'dragstart';
var EVENT_FOCUSIN = 'focusin';
var EVENT_KEY_DOWN = 'keydown';
var EVENT_LOAD = 'load';
var EVENT_ERROR = 'error';
var EVENT_TOUCH_END = IS_TOUCH_DEVICE ? 'touchend touchcancel' : 'mouseup';
var EVENT_TOUCH_MOVE = IS_TOUCH_DEVICE ? 'touchmove' : 'mousemove';
var EVENT_TOUCH_START = IS_TOUCH_DEVICE ? 'touchstart' : 'mousedown';
var EVENT_POINTER_DOWN = HAS_POINTER_EVENT ? 'pointerdown' : EVENT_TOUCH_START;
var EVENT_POINTER_MOVE = HAS_POINTER_EVENT ? 'pointermove' : EVENT_TOUCH_MOVE;
var EVENT_POINTER_UP = HAS_POINTER_EVENT ? 'pointerup pointercancel' : EVENT_TOUCH_END;
var EVENT_RESIZE = 'resize';
var EVENT_TRANSITION_END = 'transitionend';
var EVENT_WHEEL = 'wheel';

// Custom events
var EVENT_READY = 'ready';
var EVENT_SHOW = 'show';
var EVENT_SHOWN = 'shown';
var EVENT_HIDE = 'hide';
var EVENT_HIDDEN = 'hidden';
var EVENT_VIEW = 'view';
var EVENT_VIEWED = 'viewed';
var EVENT_MOVE = 'move';
var EVENT_MOVED = 'moved';
var EVENT_ROTATE = 'rotate';
var EVENT_ROTATED = 'rotated';
var EVENT_SCALE = 'scale';
var EVENT_SCALED = 'scaled';
var EVENT_ZOOM = 'zoom';
var EVENT_ZOOMED = 'zoomed';
var EVENT_PLAY = 'play';
var EVENT_STOP = 'stop';

// Data keys
var DATA_ACTION = "".concat(NAMESPACE, "Action");

// RegExps
var REGEXP_SPACES = /\s\s*/;

// Misc
var BUTTONS = ['zoom-in', 'zoom-out', 'one-to-one', 'reset', 'prev', 'play', 'next', 'rotate-left', 'rotate-right', 'flip-horizontal', 'flip-vertical'];

/**
 * Check if the given value is a string.
 * @param {*} value - The value to check.
 * @returns {boolean} Returns `true` if the given value is a string, else `false`.
 */
function isString(value) {
  return typeof value === 'string';
}

/**
 * Check if the given value is not a number.
 */
var isNaN = Number.isNaN || WINDOW.isNaN;

/**
 * Check if the given value is a number.
 * @param {*} value - The value to check.
 * @returns {boolean} Returns `true` if the given value is a number, else `false`.
 */
function isNumber(value) {
  return typeof value === 'number' && !isNaN(value);
}

/**
 * Check if the given value is undefined.
 * @param {*} value - The value to check.
 * @returns {boolean} Returns `true` if the given value is undefined, else `false`.
 */
function isUndefined(value) {
  return typeof value === 'undefined';
}

/**
 * Check if the given value is an object.
 * @param {*} value - The value to check.
 * @returns {boolean} Returns `true` if the given value is an object, else `false`.
 */
function isObject(value) {
  return _typeof(value) === 'object' && value !== null;
}
var hasOwnProperty = Object.prototype.hasOwnProperty;

/**
 * Check if the given value is a plain object.
 * @param {*} value - The value to check.
 * @returns {boolean} Returns `true` if the given value is a plain object, else `false`.
 */
function isPlainObject(value) {
  if (!isObject(value)) {
    return false;
  }
  try {
    var _constructor = value.constructor;
    var prototype = _constructor.prototype;
    return _constructor && prototype && hasOwnProperty.call(prototype, 'isPrototypeOf');
  } catch (error) {
    return false;
  }
}

/**
 * Check if the given value is a function.
 * @param {*} value - The value to check.
 * @returns {boolean} Returns `true` if the given value is a function, else `false`.
 */
function isFunction(value) {
  return typeof value === 'function';
}

/**
 * Iterate the given data.
 * @param {*} data - The data to iterate.
 * @param {Function} callback - The process function for each element.
 * @returns {*} The original data.
 */
function forEach(data, callback) {
  if (data && isFunction(callback)) {
    if (Array.isArray(data) || isNumber(data.length) /* array-like */) {
      var length = data.length;
      var i;
      for (i = 0; i < length; i += 1) {
        if (callback.call(data, data[i], i, data) === false) {
          break;
        }
      }
    } else if (isObject(data)) {
      Object.keys(data).forEach(function (key) {
        callback.call(data, data[key], key, data);
      });
    }
  }
  return data;
}

/**
 * Extend the given object.
 * @param {*} obj - The object to be extended.
 * @param {*} args - The rest objects which will be merged to the first object.
 * @returns {Object} The extended object.
 */
var assign = Object.assign || function assign(obj) {
  for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
    args[_key - 1] = arguments[_key];
  }
  if (isObject(obj) && args.length > 0) {
    args.forEach(function (arg) {
      if (isObject(arg)) {
        Object.keys(arg).forEach(function (key) {
          obj[key] = arg[key];
        });
      }
    });
  }
  return obj;
};
var REGEXP_SUFFIX = /^(?:width|height|left|top|marginLeft|marginTop)$/;

/**
 * Apply styles to the given element.
 * @param {Element} element - The target element.
 * @param {Object} styles - The styles for applying.
 */
function setStyle(element, styles) {
  var style = element.style;
  forEach(styles, function (value, property) {
    if (REGEXP_SUFFIX.test(property) && isNumber(value)) {
      value += 'px';
    }
    style[property] = value;
  });
}

/**
 * Escape a string for using in HTML.
 * @param {String} value - The string to escape.
 * @returns {String} Returns the escaped string.
 */
function escapeHTMLEntities(value) {
  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;
}

/**
 * Check if the given element has a special class.
 * @param {Element} element - The element to check.
 * @param {string} value - The class to search.
 * @returns {boolean} Returns `true` if the special class was found.
 */
function hasClass(element, value) {
  if (!element || !value) {
    return false;
  }
  return element.classList ? element.classList.contains(value) : element.className.indexOf(value) > -1;
}

/**
 * Add classes to the given element.
 * @param {Element} element - The target element.
 * @param {string} value - The classes to be added.
 */
function addClass(element, value) {
  if (!element || !value) {
    return;
  }
  if (isNumber(element.length)) {
    forEach(element, function (elem) {
      addClass(elem, value);
    });
    return;
  }
  if (element.classList) {
    element.classList.add(value);
    return;
  }
  var className = element.className.trim();
  if (!className) {
    element.className = value;
  } else if (className.indexOf(value) < 0) {
    element.className = "".concat(className, " ").concat(value);
  }
}

/**
 * Remove classes from the given element.
 * @param {Element} element - The target element.
 * @param {string} value - The classes to be removed.
 */
function removeClass(element, value) {
  if (!element || !value) {
    return;
  }
  if (isNumber(element.length)) {
    forEach(element, function (elem) {
      removeClass(elem, value);
    });
    return;
  }
  if (element.classList) {
    element.classList.remove(value);
    return;
  }
  if (element.className.indexOf(value) >= 0) {
    element.className = element.className.replace(value, '');
  }
}

/**
 * Add or remove classes from the given element.
 * @param {Element} element - The target element.
 * @param {string} value - The classes to be toggled.
 * @param {boolean} added - Add only.
 */
function toggleClass(element, value, added) {
  if (!value) {
    return;
  }
  if (isNumber(element.length)) {
    forEach(element, function (elem) {
      toggleClass(elem, value, added);
    });
    return;
  }

  // IE10-11 doesn't support the second parameter of `classList.toggle`
  if (added) {
    addClass(element, value);
  } else {
    removeClass(element, value);
  }
}
var REGEXP_HYPHENATE = /([a-z\d])([A-Z])/g;

/**
 * Transform the given string from camelCase to kebab-case
 * @param {string} value - The value to transform.
 * @returns {string} The transformed value.
 */
function hyphenate(value) {
  return value.replace(REGEXP_HYPHENATE, '$1-$2').toLowerCase();
}

/**
 * Get data from the given element.
 * @param {Element} element - The target element.
 * @param {string} name - The data key to get.
 * @returns {string} The data value.
 */
function getData(element, name) {
  if (isObject(element[name])) {
    return element[name];
  }
  if (element.dataset) {
    return element.dataset[name];
  }
  return element.getAttribute("data-".concat(hyphenate(name)));
}

/**
 * Set data to the given element.
 * @param {Element} element - The target element.
 * @param {string} name - The data key to set.
 * @param {string} data - The data value.
 */
function setData(element, name, data) {
  if (isObject(data)) {
    element[name] = data;
  } else if (element.dataset) {
    element.dataset[name] = data;
  } else {
    element.setAttribute("data-".concat(hyphenate(name)), data);
  }
}
var onceSupported = function () {
  var supported = false;
  if (IS_BROWSER) {
    var once = false;
    var listener = function listener() {};
    var options = Object.defineProperty({}, 'once', {
      get: function get() {
        supported = true;
        return once;
      },
      /**
       * This setter can fix a `TypeError` in strict mode
       * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Getter_only}
       * @param {boolean} value - The value to set
       */
      set: function set(value) {
        once = value;
      }
    });
    WINDOW.addEventListener('test', listener, options);
    WINDOW.removeEventListener('test', listener, options);
  }
  return supported;
}();

/**
 * Remove event listener from the target element.
 * @param {Element} element - The event target.
 * @param {string} type - The event type(s).
 * @param {Function} listener - The event listener.
 * @param {Object} options - The event options.
 */
function removeListener(element, type, listener) {
  var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
  var handler = listener;
  type.trim().split(REGEXP_SPACES).forEach(function (event) {
    if (!onceSupported) {
      var listeners = element.listeners;
      if (listeners && listeners[event] && listeners[event][listener]) {
        handler = listeners[event][listener];
        delete listeners[event][listener];
        if (Object.keys(listeners[event]).length === 0) {
          delete listeners[event];
        }
        if (Object.keys(listeners).length === 0) {
          delete element.listeners;
        }
      }
    }
    element.removeEventListener(event, handler, options);
  });
}

/**
 * Add event listener to the target element.
 * @param {Element} element - The event target.
 * @param {string} type - The event type(s).
 * @param {Function} listener - The event listener.
 * @param {Object} options - The event options.
 */
function addListener(element, type, listener) {
  var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
  var _handler = listener;
  type.trim().split(REGEXP_SPACES).forEach(function (event) {
    if (options.once && !onceSupported) {
      var _element$listeners = element.listeners,
        listeners = _element$listeners === void 0 ? {} : _element$listeners;
      _handler = function handler() {
        delete listeners[event][listener];
        element.removeEventListener(event, _handler, options);
        for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
          args[_key2] = arguments[_key2];
        }
        listener.apply(element, args);
      };
      if (!listeners[event]) {
        listeners[event] = {};
      }
      if (listeners[event][listener]) {
        element.removeEventListener(event, listeners[event][listener], options);
      }
      listeners[event][listener] = _handler;
      element.listeners = listeners;
    }
    element.addEventListener(event, _handler, options);
  });
}

/**
 * Dispatch event on the target element.
 * @param {Element} element - The event target.
 * @param {string} type - The event type(s).
 * @param {Object} data - The additional event data.
 * @param {Object} options - The additional event options.
 * @returns {boolean} Indicate if the event is default prevented or not.
 */
function dispatchEvent(element, type, data, options) {
  var event;

  // Event and CustomEvent on IE9-11 are global objects, not constructors
  if (isFunction(Event) && isFunction(CustomEvent)) {
    event = new CustomEvent(type, _objectSpread2({
      bubbles: true,
      cancelable: true,
      detail: data
    }, options));
  } else {
    event = document.createEvent('CustomEvent');
    event.initCustomEvent(type, true, true, data);
  }
  return element.dispatchEvent(event);
}

/**
 * Get the offset base on the document.
 * @param {Element} element - The target element.
 * @returns {Object} The offset data.
 */
function getOffset(element) {
  var box = element.getBoundingClientRect();
  return {
    left: box.left + (window.pageXOffset - document.documentElement.clientLeft),
    top: box.top + (window.pageYOffset - document.documentElement.clientTop)
  };
}

/**
 * Get transforms base on the given object.
 * @param {Object} obj - The target object.
 * @returns {string} A string contains transform values.
 */
function getTransforms(_ref) {
  var rotate = _ref.rotate,
    scaleX = _ref.scaleX,
    scaleY = _ref.scaleY,
    translateX = _ref.translateX,
    translateY = _ref.translateY;
  var values = [];
  if (isNumber(translateX) && translateX !== 0) {
    values.push("translateX(".concat(translateX, "px)"));
  }
  if (isNumber(translateY) && translateY !== 0) {
    values.push("translateY(".concat(translateY, "px)"));
  }

  // Rotate should come first before scale to match orientation transform
  if (isNumber(rotate) && rotate !== 0) {
    values.push("rotate(".concat(rotate, "deg)"));
  }
  if (isNumber(scaleX) && scaleX !== 1) {
    values.push("scaleX(".concat(scaleX, ")"));
  }
  if (isNumber(scaleY) && scaleY !== 1) {
    values.push("scaleY(".concat(scaleY, ")"));
  }
  var transform = values.length ? values.join(' ') : 'none';
  return {
    WebkitTransform: transform,
    msTransform: transform,
    transform: transform
  };
}

/**
 * Get an image name from an image url.
 * @param {string} url - The target url.
 * @example
 * // picture.jpg
 * getImageNameFromURL('https://domain.com/path/to/picture.jpg?size=1280×960')
 * @returns {string} A string contains the image name.
 */
function getImageNameFromURL(url) {
  return isString(url) ? decodeURIComponent(url.replace(/^.*\//, '').replace(/[?&#].*$/, '')) : '';
}
var IS_SAFARI = WINDOW.navigator && /Version\/\d+(\.\d+)+?\s+Safari/i.test(WINDOW.navigator.userAgent);

/**
 * Get an image's natural sizes.
 * @param {string} image - The target image.
 * @param {Object} options - The viewer options.
 * @param {Function} callback - The callback function.
 * @returns {HTMLImageElement} The new image.
 */
function getImageNaturalSizes(image, options, callback) {
  var newImage = document.createElement('img');

  // Modern browsers (except Safari)
  if (image.naturalWidth && !IS_SAFARI) {
    callback(image.naturalWidth, image.naturalHeight);
    return newImage;
  }
  var body = document.body || document.documentElement;
  newImage.onload = function () {
    callback(newImage.width, newImage.height);
    if (!IS_SAFARI) {
      body.removeChild(newImage);
    }
  };
  forEach(options.inheritedAttributes, function (name) {
    var value = image.getAttribute(name);
    if (value !== null) {
      newImage.setAttribute(name, value);
    }
  });
  newImage.src = image.src;

  // iOS Safari will convert the image automatically
  // with its orientation once append it into DOM
  if (!IS_SAFARI) {
    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;';
    body.appendChild(newImage);
  }
  return newImage;
}

/**
 * Get the related class name of a responsive type number.
 * @param {string} type - The responsive type.
 * @returns {string} The related class name.
 */
function getResponsiveClass(type) {
  switch (type) {
    case 2:
      return CLASS_HIDE_XS_DOWN;
    case 3:
      return CLASS_HIDE_SM_DOWN;
    case 4:
      return CLASS_HIDE_MD_DOWN;
    default:
      return '';
  }
}

/**
 * Get the max ratio of a group of pointers.
 * @param {string} pointers - The target pointers.
 * @returns {number} The result ratio.
 */
function getMaxZoomRatio(pointers) {
  var pointers2 = _objectSpread2({}, pointers);
  var ratios = [];
  forEach(pointers, function (pointer, pointerId) {
    delete pointers2[pointerId];
    forEach(pointers2, function (pointer2) {
      var x1 = Math.abs(pointer.startX - pointer2.startX);
      var y1 = Math.abs(pointer.startY - pointer2.startY);
      var x2 = Math.abs(pointer.endX - pointer2.endX);
      var y2 = Math.abs(pointer.endY - pointer2.endY);
      var z1 = Math.sqrt(x1 * x1 + y1 * y1);
      var z2 = Math.sqrt(x2 * x2 + y2 * y2);
      var ratio = (z2 - z1) / z1;
      ratios.push(ratio);
    });
  });
  ratios.sort(function (a, b) {
    return Math.abs(a) < Math.abs(b);
  });
  return ratios[0];
}

/**
 * Get a pointer from an event object.
 * @param {Object} event - The target event object.
 * @param {boolean} endOnly - Indicates if only returns the end point coordinate or not.
 * @returns {Object} The result pointer contains start and/or end point coordinates.
 */
function getPointer(_ref2, endOnly) {
  var pageX = _ref2.pageX,
    pageY = _ref2.pageY;
  var end = {
    endX: pageX,
    endY: pageY
  };
  return endOnly ? end : _objectSpread2({
    timeStamp: Date.now(),
    startX: pageX,
    startY: pageY
  }, end);
}

/**
 * Get the center point coordinate of a group of pointers.
 * @param {Object} pointers - The target pointers.
 * @returns {Object} The center point coordinate.
 */
function getPointersCenter(pointers) {
  var pageX = 0;
  var pageY = 0;
  var count = 0;
  forEach(pointers, function (_ref3) {
    var startX = _ref3.startX,
      startY = _ref3.startY;
    pageX += startX;
    pageY += startY;
    count += 1;
  });
  pageX /= count;
  pageY /= count;
  return {
    pageX: pageX,
    pageY: pageY
  };
}

var render = {
  render: function render() {
    this.initContainer();
    this.initViewer();
    this.initList();
    this.renderViewer();
  },
  initBody: function initBody() {
    var ownerDocument = this.element.ownerDocument;
    var body = ownerDocument.body || ownerDocument.documentElement;
    this.body = body;
    this.scrollbarWidth = window.innerWidth - ownerDocument.documentElement.clientWidth;
    this.initialBodyPaddingRight = body.style.paddingRight;
    this.initialBodyComputedPaddingRight = window.getComputedStyle(body).paddingRight;
  },
  initContainer: function initContainer() {
    this.containerData = {
      width: window.innerWidth,
      height: window.innerHeight
    };
  },
  initViewer: function initViewer() {
    var options = this.options,
      parent = this.parent;
    var viewerData;
    if (options.inline) {
      viewerData = {
        width: Math.max(parent.offsetWidth, options.minWidth),
        height: Math.max(parent.offsetHeight, options.minHeight)
      };
      this.parentData = viewerData;
    }
    if (this.fulled || !viewerData) {
      viewerData = this.containerData;
    }
    this.viewerData = assign({}, viewerData);
  },
  renderViewer: function renderViewer() {
    if (this.options.inline && !this.fulled) {
      setStyle(this.viewer, this.viewerData);
    }
  },
  initList: function initList() {
    var _this = this;
    var element = this.element,
      options = this.options,
      list = this.list;
    var items = [];

    // initList may be called in this.update, so should keep idempotent
    list.innerHTML = '';
    forEach(this.images, function (image, index) {
      var src = image.src;
      var alt = image.alt || getImageNameFromURL(src);
      var url = _this.getImageURL(image);
      if (src || url) {
        var item = document.createElement('li');
        var img = document.createElement('img');
        forEach(options.inheritedAttributes, function (name) {
          var value = image.getAttribute(name);
          if (value !== null) {
            img.setAttribute(name, value);
          }
        });
        if (options.navbar) {
          img.src = src || url;
        }
        img.alt = alt;
        img.setAttribute('data-original-url', url || src);
        item.setAttribute('data-index', index);
        item.setAttribute('data-viewer-action', 'view');
        item.setAttribute('role', 'button');
        if (options.keyboard) {
          item.setAttribute('tabindex', 0);
        }
        item.appendChild(img);
        list.appendChild(item);
        items.push(item);
      }
    });
    this.items = items;
    forEach(items, function (item) {
      var image = item.firstElementChild;
      var onLoad;
      var onError;
      setData(image, 'filled', true);
      if (options.loading) {
        addClass(item, CLASS_LOADING);
      }
      addListener(image, EVENT_LOAD, onLoad = function onLoad(event) {
        removeListener(image, EVENT_ERROR, onError);
        if (options.loading) {
          removeClass(item, CLASS_LOADING);
        }
        _this.loadImage(event);
      }, {
        once: true
      });
      addListener(image, EVENT_ERROR, onError = function onError() {
        removeListener(image, EVENT_LOAD, onLoad);
        if (options.loading) {
          removeClass(item, CLASS_LOADING);
        }
      }, {
        once: true
      });
    });
    if (options.transition) {
      addListener(element, EVENT_VIEWED, function () {
        addClass(list, CLASS_TRANSITION);
      }, {
        once: true
      });
    }
  },
  renderList: function renderList() {
    var index = this.index;
    var item = this.items[index];
    if (!item) {
      return;
    }
    var next = item.nextElementSibling;
    var gutter = parseInt(window.getComputedStyle(next || item).marginLeft, 10);
    var offsetWidth = item.offsetWidth;
    var outerWidth = offsetWidth + gutter;

    // Place the active item in the center of the screen
    setStyle(this.list, assign({
      width: outerWidth * this.length - gutter
    }, getTransforms({
      translateX: (this.viewerData.width - offsetWidth) / 2 - outerWidth * index
    })));
  },
  resetList: function resetList() {
    var list = this.list;
    list.innerHTML = '';
    removeClass(list, CLASS_TRANSITION);
    setStyle(list, getTransforms({
      translateX: 0
    }));
  },
  initImage: function initImage(done) {
    var _this2 = this;
    var options = this.options,
      image = this.image,
      viewerData = this.viewerData;
    var footerHeight = this.footer.offsetHeight;
    var viewerWidth = viewerData.width;
    var viewerHeight = Math.max(viewerData.height - footerHeight, footerHeight);
    var oldImageData = this.imageData || {};
    var sizingImage;
    this.imageInitializing = {
      abort: function abort() {
        sizingImage.onload = null;
      }
    };
    sizingImage = getImageNaturalSizes(image, options, function (naturalWidth, naturalHeight) {
      var aspectRatio = naturalWidth / naturalHeight;
      var initialCoverage = Math.max(0, Math.min(1, options.initialCoverage));
      var width = viewerWidth;
      var height = viewerHeight;
      _this2.imageInitializing = false;
      if (viewerHeight * aspectRatio > viewerWidth) {
        height = viewerWidth / aspectRatio;
      } else {
        width = viewerHeight * aspectRatio;
      }
      initialCoverage = isNumber(initialCoverage) ? initialCoverage : 0.9;
      width = Math.min(width * initialCoverage, naturalWidth);
      height = Math.min(height * initialCoverage, naturalHeight);
      var left = (viewerWidth - width) / 2;
      var top = (viewerHeight - height) / 2;
      var imageData = {
        left: left,
        top: top,
        x: left,
        y: top,
        width: width,
        height: height,
        oldRatio: 1,
        ratio: width / naturalWidth,
        aspectRatio: aspectRatio,
        naturalWidth: naturalWidth,
        naturalHeight: naturalHeight
      };
      var initialImageData = assign({}, imageData);
      if (options.rotatable) {
        imageData.rotate = oldImageData.rotate || 0;
        initialImageData.rotate = 0;
      }
      if (options.scalable) {
        imageData.scaleX = oldImageData.scaleX || 1;
        imageData.scaleY = oldImageData.scaleY || 1;
        initialImageData.scaleX = 1;
        initialImageData.scaleY = 1;
      }
      _this2.imageData = imageData;
      _this2.initialImageData = initialImageData;
      if (done) {
        done();
      }
    });
  },
  renderImage: function renderImage(done) {
    var _this3 = this;
    var image = this.image,
      imageData = this.imageData;
    setStyle(image, assign({
      width: imageData.width,
      height: imageData.height,
      // XXX: Not to use translateX/Y to avoid image shaking when zooming
      marginLeft: imageData.x,
      marginTop: imageData.y
    }, getTransforms(imageData)));
    if (done) {
      if ((this.viewing || this.moving || this.rotating || this.scaling || this.zooming) && this.options.transition && hasClass(image, CLASS_TRANSITION)) {
        var onTransitionEnd = function onTransitionEnd() {
          _this3.imageRendering = false;
          done();
        };
        this.imageRendering = {
          abort: function abort() {
            removeListener(image, EVENT_TRANSITION_END, onTransitionEnd);
          }
        };
        addListener(image, EVENT_TRANSITION_END, onTransitionEnd, {
          once: true
        });
      } else {
        done();
      }
    }
  },
  resetImage: function resetImage() {
    var image = this.image;
    if (image) {
      if (this.viewing) {
        this.viewing.abort();
      }
      image.parentNode.removeChild(image);
      this.image = null;
      this.title.innerHTML = '';
    }
  }
};

var events = {
  bind: function bind() {
    var options = this.options,
      viewer = this.viewer,
      canvas = this.canvas;
    var document = this.element.ownerDocument;
    addListener(viewer, EVENT_CLICK, this.onClick = this.click.bind(this));
    addListener(viewer, EVENT_DRAG_START, this.onDragStart = this.dragstart.bind(this));
    addListener(canvas, EVENT_POINTER_DOWN, this.onPointerDown = this.pointerdown.bind(this));
    addListener(document, EVENT_POINTER_MOVE, this.onPointerMove = this.pointermove.bind(this));
    addListener(document, EVENT_POINTER_UP, this.onPointerUp = this.pointerup.bind(this));
    addListener(document, EVENT_KEY_DOWN, this.onKeyDown = this.keydown.bind(this));
    addListener(window, EVENT_RESIZE, this.onResize = this.resize.bind(this));
    if (options.zoomable && options.zoomOnWheel) {
      addListener(viewer, EVENT_WHEEL, this.onWheel = this.wheel.bind(this), {
        passive: false,
        capture: true
      });
    }
    if (options.toggleOnDblclick) {
      addListener(canvas, EVENT_DBLCLICK, this.onDblclick = this.dblclick.bind(this));
    }
  },
  unbind: function unbind() {
    var options = this.options,
      viewer = this.viewer,
      canvas = this.canvas;
    var document = this.element.ownerDocument;
    removeListener(viewer, EVENT_CLICK, this.onClick);
    removeListener(viewer, EVENT_DRAG_START, this.onDragStart);
    removeListener(canvas, EVENT_POINTER_DOWN, this.onPointerDown);
    removeListener(document, EVENT_POINTER_MOVE, this.onPointerMove);
    removeListener(document, EVENT_POINTER_UP, this.onPointerUp);
    removeListener(document, EVENT_KEY_DOWN, this.onKeyDown);
    removeListener(window, EVENT_RESIZE, this.onResize);
    if (options.zoomable && options.zoomOnWheel) {
      removeListener(viewer, EVENT_WHEEL, this.onWheel, {
        passive: false,
        capture: true
      });
    }
    if (options.toggleOnDblclick) {
      removeListener(canvas, EVENT_DBLCLICK, this.onDblclick);
    }
  }
};

var handlers = {
  click: function click(event) {
    var options = this.options,
      imageData = this.imageData;
    var target = event.target;
    var action = getData(target, DATA_ACTION);
    if (!action && target.localName === 'img' && target.parentElement.localName === 'li') {
      target = target.parentElement;
      action = getData(target, DATA_ACTION);
    }

    // Cancel the emulated click when the native click event was triggered.
    if (IS_TOUCH_DEVICE && event.isTrusted && target === this.canvas) {
      clearTimeout(this.clickCanvasTimeout);
    }
    switch (action) {
      case 'mix':
        if (this.played) {
          this.stop();
        } else if (options.inline) {
          if (this.fulled) {
            this.exit();
          } else {
            this.full();
          }
        } else {
          this.hide();
        }
        break;
      case 'hide':
        if (!this.pointerMoved) {
          this.hide();
        }
        break;
      case 'view':
        this.view(getData(target, 'index'));
        break;
      case 'zoom-in':
        this.zoom(0.1, true);
        break;
      case 'zoom-out':
        this.zoom(-0.1, true);
        break;
      case 'one-to-one':
        this.toggle();
        break;
      case 'reset':
        this.reset();
        break;
      case 'prev':
        this.prev(options.loop);
        break;
      case 'play':
        this.play(options.fullscreen);
        break;
      case 'next':
        this.next(options.loop);
        break;
      case 'rotate-left':
        this.rotate(-90);
        break;
      case 'rotate-right':
        this.rotate(90);
        break;
      case 'flip-horizontal':
        this.scaleX(-imageData.scaleX || -1);
        break;
      case 'flip-vertical':
        this.scaleY(-imageData.scaleY || -1);
        break;
      default:
        if (this.played) {
          this.stop();
        }
    }
  },
  dblclick: function dblclick(event) {
    event.preventDefault();
    if (this.viewed && event.target === this.image) {
      // Cancel the emulated double click when the native dblclick event was triggered.
      if (IS_TOUCH_DEVICE && event.isTrusted) {
        clearTimeout(this.doubleClickImageTimeout);
      }

      // XXX: No pageX/Y properties in custom event, fallback to the original event.
      this.toggle(event.isTrusted ? event : event.detail && event.detail.originalEvent);
    }
  },
  load: function load() {
    var _this = this;
    if (this.timeout) {
      clearTimeout(this.timeout);
      this.timeout = false;
    }
    var element = this.element,
      options = this.options,
      image = this.image,
      index = this.index,
      viewerData = this.viewerData;
    removeClass(image, CLASS_INVISIBLE);
    if (options.loading) {
      removeClass(this.canvas, CLASS_LOADING);
    }
    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;';
    this.initImage(function () {
      toggleClass(image, CLASS_MOVE, options.movable);
      toggleClass(image, CLASS_TRANSITION, options.transition);
      _this.renderImage(function () {
        _this.viewed = true;
        _this.viewing = false;
        if (isFunction(options.viewed)) {
          addListener(element, EVENT_VIEWED, options.viewed, {
            once: true
          });
        }
        dispatchEvent(element, EVENT_VIEWED, {
          originalImage: _this.images[index],
          index: index,
          image: image
        }, {
          cancelable: false
        });
      });
    });
  },
  loadImage: function loadImage(event) {
    var image = event.target;
    var parent = image.parentNode;
    var parentWidth = parent.offsetWidth || 30;
    var parentHeight = parent.offsetHeight || 50;
    var filled = !!getData(image, 'filled');
    getImageNaturalSizes(image, this.options, function (naturalWidth, naturalHeight) {
      var aspectRatio = naturalWidth / naturalHeight;
      var width = parentWidth;
      var height = parentHeight;
      if (parentHeight * aspectRatio > parentWidth) {
        if (filled) {
          width = parentHeight * aspectRatio;
        } else {
          height = parentWidth / aspectRatio;
        }
      } else if (filled) {
        height = parentWidth / aspectRatio;
      } else {
        width = parentHeight * aspectRatio;
      }
      setStyle(image, assign({
        width: width,
        height: height
      }, getTransforms({
        translateX: (parentWidth - width) / 2,
        translateY: (parentHeight - height) / 2
      })));
    });
  },
  keydown: function keydown(event) {
    var options = this.options;
    if (!options.keyboard) {
      return;
    }
    var keyCode = event.keyCode || event.which || event.charCode;
    switch (keyCode) {
      // Enter
      case 13:
        if (this.viewer.contains(event.target)) {
          this.click(event);
        }
        break;
    }
    if (!this.fulled) {
      return;
    }
    switch (keyCode) {
      // Escape
      case 27:
        if (this.played) {
          this.stop();
        } else if (options.inline) {
          if (this.fulled) {
            this.exit();
          }
        } else {
          this.hide();
        }
        break;

      // Space
      case 32:
        if (this.played) {
          this.stop();
        }
        break;

      // ArrowLeft
      case 37:
        if (this.played && this.playing) {
          this.playing.prev();
        } else {
          this.prev(options.loop);
        }
        break;

      // ArrowUp
      case 38:
        // Prevent scroll on Firefox
        event.preventDefault();

        // Zoom in
        this.zoom(options.zoomRatio, true);
        break;

      // ArrowRight
      case 39:
        if (this.played && this.playing) {
          this.playing.next();
        } else {
          this.next(options.loop);
        }
        break;

      // ArrowDown
      case 40:
        // Prevent scroll on Firefox
        event.preventDefault();

        // Zoom out
        this.zoom(-options.zoomRatio, true);
        break;

      // Ctrl + 0
      case 48:
      // Fall through

      // Ctrl + 1
      // eslint-disable-next-line no-fallthrough
      case 49:
        if (event.ctrlKey) {
          event.preventDefault();
          this.toggle();
        }
        break;
    }
  },
  dragstart: function dragstart(event) {
    if (event.target.localName === 'img') {
      event.preventDefault();
    }
  },
  pointerdown: function pointerdown(event) {
    var options = this.options,
      pointers = this.pointers;
    var buttons = event.buttons,
      button = event.button;
    this.pointerMoved = false;
    if (!this.viewed || this.showing || this.viewing || this.hiding

    // Handle mouse event and pointer event and ignore touch event
    || (event.type === 'mousedown' || event.type === 'pointerdown' && event.pointerType === 'mouse') && (
    // No primary button (Usually the left button)
    isNumber(buttons) && buttons !== 1 || isNumber(button) && button !== 0

    // Open context menu
    || event.ctrlKey)) {
      return;
    }

    // Prevent default behaviours as page zooming in touch devices.
    event.preventDefault();
    if (event.changedTouches) {
      forEach(event.changedTouches, function (touch) {
        pointers[touch.identifier] = getPointer(touch);
      });
    } else {
      pointers[event.pointerId || 0] = getPointer(event);
    }
    var action = options.movable ? ACTION_MOVE : false;
    if (options.zoomOnTouch && options.zoomable && Object.keys(pointers).length > 1) {
      action = ACTION_ZOOM;
    } else if (options.slideOnTouch && (event.pointerType === 'touch' || event.type === 'touchstart') && this.isSwitchable()) {
      action = ACTION_SWITCH;
    }
    if (options.transition && (action === ACTION_MOVE || action === ACTION_ZOOM)) {
      removeClass(this.image, CLASS_TRANSITION);
    }
    this.action = action;
  },
  pointermove: function pointermove(event) {
    var pointers = this.pointers,
      action = this.action;
    if (!this.viewed || !action) {
      return;
    }
    event.preventDefault();
    if (event.changedTouches) {
      forEach(event.changedTouches, function (touch) {
        assign(pointers[touch.identifier] || {}, getPointer(touch, true));
      });
    } else {
      assign(pointers[event.pointerId || 0] || {}, getPointer(event, true));
    }
    this.change(event);
  },
  pointerup: function pointerup(event) {
    var _this2 = this;
    var options = this.options,
      action = this.action,
      pointers = this.pointers;
    var pointer;
    if (event.changedTouches) {
      forEach(event.changedTouches, function (touch) {
        pointer = pointers[touch.identifier];
        delete pointers[touch.identifier];
      });
    } else {
      pointer = pointers[event.pointerId || 0];
      delete pointers[event.pointerId || 0];
    }
    if (!action) {
      return;
    }
    event.preventDefault();
    if (options.transition && (action === ACTION_MOVE || action === ACTION_ZOOM)) {
      addClass(this.image, CLASS_TRANSITION);
    }
    this.action = false;

    // Emulate click and double click in touch devices to support backdrop and image zooming (#210).
    if (IS_TOUCH_DEVICE && action !== ACTION_ZOOM && pointer && Date.now() - pointer.timeStamp < 500) {
      clearTimeout(this.clickCanvasTimeout);
      clearTimeout(this.doubleClickImageTimeout);
      if (options.toggleOnDblclick && this.viewed && event.target === this.image) {
        if (this.imageClicked) {
          this.imageClicked = false;

          // This timeout will be cleared later when a native dblclick event is triggering
          this.doubleClickImageTimeout = setTimeout(function () {
            dispatchEvent(_this2.image, EVENT_DBLCLICK, {
              originalEvent: event
            });
          }, 50);
        } else {
          this.imageClicked = true;

          // The default timing of a double click in Windows is 500 ms
          this.doubleClickImageTimeout = setTimeout(function () {
            _this2.imageClicked = false;
          }, 500);
        }
      } else {
        this.imageClicked = false;
        if (options.backdrop && options.backdrop !== 'static' && event.target === this.canvas) {
          // This timeout will be cleared later when a native click event is triggering
          this.clickCanvasTimeout = setTimeout(function () {
            dispatchEvent(_this2.canvas, EVENT_CLICK, {
              originalEvent: event
            });
          }, 50);
        }
      }
    }
  },
  resize: function resize() {
    var _this3 = this;
    if (!this.isShown || this.hiding) {
      return;
    }
    if (this.fulled) {
      this.close();
      this.initBody();
      this.open();
    }
    this.initContainer();
    this.initViewer();
    this.renderViewer();
    this.renderList();
    if (this.viewed) {
      this.initImage(function () {
        _this3.renderImage();
      });
    }
    if (this.played) {
      if (this.options.fullscreen && this.fulled && !(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement)) {
        this.stop();
        return;
      }
      forEach(this.player.getElementsByTagName('img'), function (image) {
        addListener(image, EVENT_LOAD, _this3.loadImage.bind(_this3), {
          once: true
        });
        dispatchEvent(image, EVENT_LOAD);
      });
    }
  },
  wheel: function wheel(event) {
    var _this4 = this;
    if (!this.viewed) {
      return;
    }
    event.preventDefault();

    // Limit wheel speed to prevent zoom too fast
    if (this.wheeling) {
      return;
    }
    this.wheeling = true;
    setTimeout(function () {
      _this4.wheeling = false;
    }, 50);
    var ratio = Number(this.options.zoomRatio) || 0.1;
    var delta = 1;
    if (event.deltaY) {
      delta = event.deltaY > 0 ? 1 : -1;
    } else if (event.wheelDelta) {
      delta = -event.wheelDelta / 120;
    } else if (event.detail) {
      delta = event.detail > 0 ? 1 : -1;
    }
    this.zoom(-delta * ratio, true, null, event);
  }
};

var methods = {
  /** Show the viewer (only available in modal mode)
   * @param {boolean} [immediate=false] - Indicates if show the viewer immediately or not.
   * @returns {Viewer} this
   */
  show: function show() {
    var immediate = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
    var element = this.element,
      options = this.options;
    if (options.inline || this.showing || this.isShown || this.showing) {
      return this;
    }
    if (!this.ready) {
      this.build();
      if (this.ready) {
        this.show(immediate);
      }
      return this;
    }
    if (isFunction(options.show)) {
      addListener(element, EVENT_SHOW, options.show, {
        once: true
      });
    }
    if (dispatchEvent(element, EVENT_SHOW) === false || !this.ready) {
      return this;
    }
    if (this.hiding) {
      this.transitioning.abort();
    }
    this.showing = true;
    this.open();
    var viewer = this.viewer;
    removeClass(viewer, CLASS_HIDE);
    viewer.setAttribute('role', 'dialog');
    viewer.setAttribute('aria-labelledby', this.title.id);
    viewer.setAttribute('aria-modal', true);
    viewer.removeAttribute('aria-hidden');
    if (options.transition && !immediate) {
      var shown = this.shown.bind(this);
      this.transitioning = {
        abort: function abort() {
          removeListener(viewer, EVENT_TRANSITION_END, shown);
          removeClass(viewer, CLASS_IN);
        }
      };
      addClass(viewer, CLASS_TRANSITION);

      // Force reflow to enable CSS3 transition
      viewer.initialOffsetWidth = viewer.offsetWidth;
      addListener(viewer, EVENT_TRANSITION_END, shown, {
        once: true
      });
      addClass(viewer, CLASS_IN);
    } else {
      addClass(viewer, CLASS_IN);
      this.shown();
    }
    return this;
  },
  /**
   * Hide the viewer (only available in modal mode)
   * @param {boolean} [immediate=false] - Indicates if hide the viewer immediately or not.
   * @returns {Viewer} this
   */
  hide: function hide() {
    var _this = this;
    var immediate = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
    var element = this.element,
      options = this.options;
    if (options.inline || this.hiding || !(this.isShown || this.showing)) {
      return this;
    }
    if (isFunction(options.hide)) {
      addListener(element, EVENT_HIDE, options.hide, {
        once: true
      });
    }
    if (dispatchEvent(element, EVENT_HIDE) === false) {
      return this;
    }
    if (this.showing) {
      this.transitioning.abort();
    }
    this.hiding = true;
    if (this.played) {
      this.stop();
    } else if (this.viewing) {
      this.viewing.abort();
    }
    var viewer = this.viewer,
      image = this.image;
    var hideImmediately = function hideImmediately() {
      removeClass(viewer, CLASS_IN);
      _this.hidden();
    };
    if (options.transition && !immediate) {
      var _onViewerTransitionEnd = function onViewerTransitionEnd(event) {
        // Ignore all propagating `transitionend` events (#275).
        if (event && event.target === viewer) {
          removeListener(viewer, EVENT_TRANSITION_END, _onViewerTransitionEnd);
          _this.hidden();
        }
      };
      var onImageTransitionEnd = function onImageTransitionEnd() {
        // In case of show the viewer by `viewer.show(true)` previously (#407).
        if (hasClass(viewer, CLASS_TRANSITION)) {
          addListener(viewer, EVENT_TRANSITION_END, _onViewerTransitionEnd);
          removeClass(viewer, CLASS_IN);
        } else {
          hideImmediately();
        }
      };
      this.transitioning = {
        abort: function abort() {
          if (_this.viewed && hasClass(image, CLASS_TRANSITION)) {
            removeListener(image, EVENT_TRANSITION_END, onImageTransitionEnd);
          } else if (hasClass(viewer, CLASS_TRANSITION)) {
            removeListener(viewer, EVENT_TRANSITION_END, _onViewerTransitionEnd);
          }
        }
      };

      // In case of hiding the viewer when holding on the image (#255),
      // note that the `CLASS_TRANSITION` class will be removed on pointer down.
      if (this.viewed && hasClass(image, CLASS_TRANSITION)) {
        addListener(image, EVENT_TRANSITION_END, onImageTransitionEnd, {
          once: true
        });
        this.zoomTo(0, false, null, null, true);
      } else {
        onImageTransitionEnd();
      }
    } else {
      hideImmediately();
    }
    return this;
  },
  /**
   * View one of the images with image's index
   * @param {number} index - The index of the image to view.
   * @returns {Viewer} this
   */
  view: function view() {
    var _this2 = this;
    var index = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.options.initialViewIndex;
    index = Number(index) || 0;
    if (this.hiding || this.played || index < 0 || index >= this.length || this.viewed && index === this.index) {
      return this;
    }
    if (!this.isShown) {
      this.index = index;
      return this.show();
    }
    if (this.viewing) {
      this.viewing.abort();
    }
    var element = this.element,
      options = this.options,
      title = this.title,
      canvas = this.canvas;
    var item = this.items[index];
    var img = item.querySelector('img');
    var url = getData(img, 'originalUrl');
    var alt = img.getAttribute('alt');
    var image = document.createElement('img');
    forEach(options.inheritedAttributes, function (name) {
      var value = img.getAttribute(name);
      if (value !== null) {
        image.setAttribute(name, value);
      }
    });
    image.src = url;
    image.alt = alt;
    if (isFunction(options.view)) {
      addListener(element, EVENT_VIEW, options.view, {
        once: true
      });
    }
    if (dispatchEvent(element, EVENT_VIEW, {
      originalImage: this.images[index],
      index: index,
      image: image
    }) === false || !this.isShown || this.hiding || this.played) {
      return this;
    }
    var activeItem = this.items[this.index];
    if (activeItem) {
      removeClass(activeItem, CLASS_ACTIVE);
      activeItem.removeAttribute('aria-selected');
    }
    addClass(item, CLASS_ACTIVE);
    item.setAttribute('aria-selected', true);
    if (options.focus) {
      item.focus();
    }
    this.image = image;
    this.viewed = false;
    this.index = index;
    this.imageData = {};
    addClass(image, CLASS_INVISIBLE);
    if (options.loading) {
      addClass(canvas, CLASS_LOADING);
    }
    canvas.innerHTML = '';
    canvas.appendChild(image);

    // Center current item
    this.renderList();

    // Clear title
    title.innerHTML = '';

    // Generate title after viewed
    var onViewed = function onViewed() {
      var imageData = _this2.imageData;
      var render = Array.isArray(options.title) ? options.title[1] : options.title;
      title.innerHTML = escapeHTMLEntities(isFunction(render) ? render.call(_this2, image, imageData) : "".concat(alt, " (").concat(imageData.naturalWidth, " \xD7 ").concat(imageData.naturalHeight, ")"));
    };
    var onLoad;
    var onError;
    addListener(element, EVENT_VIEWED, onViewed, {
      once: true
    });
    this.viewing = {
      abort: function abort() {
        removeListener(element, EVENT_VIEWED, onViewed);
        if (image.complete) {
          if (_this2.imageRendering) {
            _this2.imageRendering.abort();
          } else if (_this2.imageInitializing) {
            _this2.imageInitializing.abort();
          }
        } else {
          // Cancel download to save bandwidth.
          image.src = '';
          removeListener(image, EVENT_LOAD, onLoad);
          if (_this2.timeout) {
            clearTimeout(_this2.timeout);
          }
        }
      }
    };
    if (image.complete) {
      this.load();
    } else {
      addListener(image, EVENT_LOAD, onLoad = function onLoad() {
        removeListener(image, EVENT_ERROR, onError);
        _this2.load();
      }, {
        once: true
      });
      addListener(image, EVENT_ERROR, onError = function onError() {
        removeListener(image, EVENT_LOAD, onLoad);
        if (_this2.timeout) {
          clearTimeout(_this2.timeout);
          _this2.timeout = false;
        }
        removeClass(image, CLASS_INVISIBLE);
        if (options.loading) {
          removeClass(_this2.canvas, CLASS_LOADING);
        }
      }, {
        once: true
      });
      if (this.timeout) {
        clearTimeout(this.timeout);
      }

      // Make the image visible if it fails to load within 1s
      this.timeout = setTimeout(function () {
        removeClass(image, CLASS_INVISIBLE);
        _this2.timeout = false;
      }, 1000);
    }
    return this;
  },
  /**
   * View the previous image
   * @param {boolean} [loop=false] - Indicate if view the last one
   * when it is the first one at present.
   * @returns {Viewer} this
   */
  prev: function prev() {
    var loop = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
    var index = this.index - 1;
    if (index < 0) {
      index = loop ? this.length - 1 : 0;
    }
    this.view(index);
    return this;
  },
  /**
   * View the next image
   * @param {boolean} [loop=false] - Indicate if view the first one
   * when it is the last one at present.
   * @returns {Viewer} this
   */
  next: function next() {
    var loop = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
    var maxIndex = this.length - 1;
    var index = this.index + 1;
    if (index > maxIndex) {
      index = loop ? 0 : maxIndex;
    }
    this.view(index);
    return this;
  },
  /**
   * Move the image with relative offsets.
   * @param {number} x - The moving distance in the horizontal direction.
   * @param {number} [y=x] The moving distance in the vertical direction.
   * @returns {Viewer} this
   */
  move: function move(x) {
    var y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : x;
    var imageData = this.imageData;
    this.moveTo(isUndefined(x) ? x : imageData.x + Number(x), isUndefined(y) ? y : imageData.y + Number(y));
    return this;
  },
  /**
   * Move the image to an absolute point.
   * @param {number} x - The new position in the horizontal direction.
   * @param {number} [y=x] - The new position in the vertical direction.
   * @param {Event} [_originalEvent=null] - The original event if any.
   * @returns {Viewer} this
   */
  moveTo: function moveTo(x) {
    var _this3 = this;
    var y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : x;
    var _originalEvent = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
    var element = this.element,
      options = this.options,
      imageData = this.imageData;
    x = Number(x);
    y = Number(y);
    if (this.viewed && !this.played && options.movable) {
      var oldX = imageData.x;
      var oldY = imageData.y;
      var changed = false;
      if (isNumber(x)) {
        changed = true;
      } else {
        x = oldX;
      }
      if (isNumber(y)) {
        changed = true;
      } else {
        y = oldY;
      }
      if (changed) {
        if (isFunction(options.move)) {
          addListener(element, EVENT_MOVE, options.move, {
            once: true
          });
        }
        if (dispatchEvent(element, EVENT_MOVE, {
          x: x,
          y: y,
          oldX: oldX,
          oldY: oldY,
          originalEvent: _originalEvent
        }) === false) {
          return this;
        }
        imageData.x = x;
        imageData.y = y;
        imageData.left = x;
        imageData.top = y;
        this.moving = true;
        this.renderImage(function () {
          _this3.moving = false;
          if (isFunction(options.moved)) {
            addListener(element, EVENT_MOVED, options.moved, {
              once: true
            });
          }
          dispatchEvent(element, EVENT_MOVED, {
            x: x,
            y: y,
            oldX: oldX,
            oldY: oldY,
            originalEvent: _originalEvent
          }, {
            cancelable: false
          });
        });
      }
    }
    return this;
  },
  /**
   * Rotate the image with a relative degree.
   * @param {number} degree - The rotate degree.
   * @returns {Viewer} this
   */
  rotate: function rotate(degree) {
    this.rotateTo((this.imageData.rotate || 0) + Number(degree));
    return this;
  },
  /**
   * Rotate the image to an absolute degree.
   * @param {number} degree - The rotate degree.
   * @returns {Viewer} this
   */
  rotateTo: function rotateTo(degree) {
    var _this4 = this;
    var element = this.element,
      options = this.options,
      imageData = this.imageData;
    degree = Number(degree);
    if (isNumber(degree) && this.viewed && !this.played && options.rotatable) {
      var oldDegree = imageData.rotate;
      if (isFunction(options.rotate)) {
        addListener(element, EVENT_ROTATE, options.rotate, {
          once: true
        });
      }
      if (dispatchEvent(element, EVENT_ROTATE, {
        degree: degree,
        oldDegree: oldDegree
      }) === false) {
        return this;
      }
      imageData.rotate = degree;
      this.rotating = true;
      this.renderImage(function () {
        _this4.rotating = false;
        if (isFunction(options.rotated)) {
          addListener(element, EVENT_ROTATED, options.rotated, {
            once: true
          });
        }
        dispatchEvent(element, EVENT_ROTATED, {
          degree: degree,
          oldDegree: oldDegree
        }, {
          cancelable: false
        });
      });
    }
    return this;
  },
  /**
   * Scale the image on the x-axis.
   * @param {number} scaleX - The scale ratio on the x-axis.
   * @returns {Viewer} this
   */
  scaleX: function scaleX(_scaleX) {
    this.scale(_scaleX, this.imageData.scaleY);
    return this;
  },
  /**
   * Scale the image on the y-axis.
   * @param {number} scaleY - The scale ratio on the y-axis.
   * @returns {Viewer} this
   */
  scaleY: function scaleY(_scaleY) {
    this.scale(this.imageData.scaleX, _scaleY);
    return this;
  },
  /**
   * Scale the image.
   * @param {number} scaleX - The scale ratio on the x-axis.
   * @param {number} [scaleY=scaleX] - The scale ratio on the y-axis.
   * @returns {Viewer} this
   */
  scale: function scale(scaleX) {
    var _this5 = this;
    var scaleY = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : scaleX;
    var element = this.element,
      options = this.options,
      imageData = this.imageData;
    scaleX = Number(scaleX);
    scaleY = Number(scaleY);
    if (this.viewed && !this.played && options.scalable) {
      var oldScaleX = imageData.scaleX;
      var oldScaleY = imageData.scaleY;
      var changed = false;
      if (isNumber(scaleX)) {
        changed = true;
      } else {
        scaleX = oldScaleX;
      }
      if (isNumber(scaleY)) {
        changed = true;
      } else {
        scaleY = oldScaleY;
      }
      if (changed) {
        if (isFunction(options.scale)) {
          addListener(element, EVENT_SCALE, options.scale, {
            once: true
          });
        }
        if (dispatchEvent(element, EVENT_SCALE, {
          scaleX: scaleX,
          scaleY: scaleY,
          oldScaleX: oldScaleX,
          oldScaleY: oldScaleY
        }) === false) {
          return this;
        }
        imageData.scaleX = scaleX;
        imageData.scaleY = scaleY;
        this.scaling = true;
        this.renderImage(function () {
          _this5.scaling = false;
          if (isFunction(options.scaled)) {
            addListener(element, EVENT_SCALED, options.scaled, {
              once: true
            });
          }
          dispatchEvent(element, EVENT_SCALED, {
            scaleX: scaleX,
            scaleY: scaleY,
            oldScaleX: oldScaleX,
            oldScaleY: oldScaleY
          }, {
            cancelable: false
          });
        });
      }
    }
    return this;
  },
  /**
   * Zoom the image with a relative ratio.
   * @param {number} ratio - The target ratio.
   * @param {boolean} [showTooltip=false] - Indicates whether to show the tooltip.
   * @param {Object} [pivot] - The pivot point coordinate for zooming.
   * @param {Event} [_originalEvent=null] - The original event if any.
   * @returns {Viewer} this
   */
  zoom: function zoom(ratio) {
    var showTooltip = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
    var pivot = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
    var _originalEvent = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
    var imageData = this.imageData;
    ratio = Number(ratio);
    if (ratio < 0) {
      ratio = 1 / (1 - ratio);
    } else {
      ratio = 1 + ratio;
    }
    this.zoomTo(imageData.width * ratio / imageData.naturalWidth, showTooltip, pivot, _originalEvent);
    return this;
  },
  /**
   * Zoom the image to an absolute ratio.
   * @param {number} ratio - The target ratio.
   * @param {boolean} [showTooltip] - Indicates whether to show the tooltip.
   * @param {Object} [pivot] - The pivot point coordinate for zooming.
   * @param {Event} [_originalEvent=null] - The original event if any.
   * @param {Event} [_zoomable=false] - Indicates if the current zoom is available or not.
   * @returns {Viewer} this
   */
  zoomTo: function zoomTo(ratio) {
    var _this6 = this;
    var showTooltip = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
    var pivot = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
    var _originalEvent = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
    var _zoomable = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
    var element = this.element,
      options = this.options,
      pointers = this.pointers,
      imageData = this.imageData;
    var x = imageData.x,
      y = imageData.y,
      width = imageData.width,
      height = imageData.height,
      naturalWidth = imageData.naturalWidth,
      naturalHeight = imageData.naturalHeight;
    ratio = Math.max(0, ratio);
    if (isNumber(ratio) && this.viewed && !this.played && (_zoomable || options.zoomable)) {
      if (!_zoomable) {
        var minZoomRatio = Math.max(0.01, options.minZoomRatio);
        var maxZoomRatio = Math.min(100, options.maxZoomRatio);
        ratio = Math.min(Math.max(ratio, minZoomRatio), maxZoomRatio);
      }
      if (_originalEvent) {
        switch (_originalEvent.type) {
          case 'wheel':
            if (options.zoomRatio >= 0.055 && ratio > 0.95 && ratio < 1.05) {
              ratio = 1;
            }
            break;
          case 'pointermove':
          case 'touchmove':
          case 'mousemove':
            if (ratio > 0.99 && ratio < 1.01) {
              ratio = 1;
            }
            break;
        }
      }
      var newWidth = naturalWidth * ratio;
      var newHeight = naturalHeight * ratio;
      var offsetWidth = newWidth - width;
      var offsetHeight = newHeight - height;
      var oldRatio = imageData.ratio;
      if (isFunction(options.zoom)) {
        addListener(element, EVENT_ZOOM, options.zoom, {
          once: true
        });
      }
      if (dispatchEvent(element, EVENT_ZOOM, {
        ratio: ratio,
        oldRatio: oldRatio,
        originalEvent: _originalEvent
      }) === false) {
        return this;
      }
      this.zooming = true;
      if (_originalEvent) {
        var offset = getOffset(this.viewer);
        var center = pointers && Object.keys(pointers).length > 0 ? getPointersCenter(pointers) : {
          pageX: _originalEvent.pageX,
          pageY: _originalEvent.pageY
        };

        // Zoom from the triggering point of the event
        imageData.x -= offsetWidth * ((center.pageX - offset.left - x) / width);
        imageData.y -= offsetHeight * ((center.pageY - offset.top - y) / height);
      } else if (isPlainObject(pivot) && isNumber(pivot.x) && isNumber(pivot.y)) {
        imageData.x -= offsetWidth * ((pivot.x - x) / width);
        imageData.y -= offsetHeight * ((pivot.y - y) / height);
      } else {
        // Zoom from the center of the image
        imageData.x -= offsetWidth / 2;
        imageData.y -= offsetHeight / 2;
      }
      imageData.left = imageData.x;
      imageData.top = imageData.y;
      imageData.width = newWidth;
      imageData.height = newHeight;
      imageData.oldRatio = oldRatio;
      imageData.ratio = ratio;
      this.renderImage(function () {
        _this6.zooming = false;
        if (isFunction(options.zoomed)) {
          addListener(element, EVENT_ZOOMED, options.zoomed, {
            once: true
          });
        }
        dispatchEvent(element, EVENT_ZOOMED, {
          ratio: ratio,
          oldRatio: oldRatio,
          originalEvent: _originalEvent
        }, {
          cancelable: false
        });
      });
      if (showTooltip) {
        this.tooltip();
      }
    }
    return this;
  },
  /**
   * Play the images
   * @param {boolean|FullscreenOptions} [fullscreen=false] - Indicate if request fullscreen or not.
   * @returns {Viewer} this
   */
  play: function play() {
    var _this7 = this;
    var fullscreen = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
    if (!this.isShown || this.played) {
      return this;
    }
    var element = this.element,
      options = this.options;
    if (isFunction(options.play)) {
      addListener(element, EVENT_PLAY, options.play, {
        once: true
      });
    }
    if (dispatchEvent(element, EVENT_PLAY) === false) {
      return this;
    }
    var player = this.player;
    var onLoad = this.loadImage.bind(this);
    var list = [];
    var total = 0;
    var index = 0;
    this.played = true;
    this.onLoadWhenPlay = onLoad;
    if (fullscreen) {
      this.requestFullscreen(fullscreen);
    }
    addClass(player, CLASS_SHOW);
    forEach(this.items, function (item, i) {
      var img = item.querySelector('img');
      var image = document.createElement('img');
      image.src = getData(img, 'originalUrl');
      image.alt = img.getAttribute('alt');
      image.referrerPolicy = img.referrerPolicy;
      total += 1;
      addClass(image, CLASS_FADE);
      toggleClass(image, CLASS_TRANSITION, options.transition);
      if (hasClass(item, CLASS_ACTIVE)) {
        addClass(image, CLASS_IN);
        index = i;
      }
      list.push(image);
      addListener(image, EVENT_LOAD, onLoad, {
        once: true
      });
      player.appendChild(image);
    });
    if (isNumber(options.interval) && options.interval > 0) {
      var _prev = function prev() {
        clearTimeout(_this7.playing.timeout);
        removeClass(list[index], CLASS_IN);
        index -= 1;
        index = index >= 0 ? index : total - 1;
        addClass(list[index], CLASS_IN);
        _this7.playing.timeout = setTimeout(_prev, options.interval);
      };
      var _next = function next() {
        clearTimeout(_this7.playing.timeout);
        removeClass(list[index], CLASS_IN);
        index += 1;
        index = index < total ? index : 0;
        addClass(list[index], CLASS_IN);
        _this7.playing.timeout = setTimeout(_next, options.interval);
      };
      if (total > 1) {
        this.playing = {
          prev: _prev,
          next: _next,
          timeout: setTimeout(_next, options.interval)
        };
      }
    }
    return this;
  },
  // Stop play
  stop: function stop() {
    var _this8 = this;
    if (!this.played) {
      return this;
    }
    var element = this.element,
      options = this.options;
    if (isFunction(options.stop)) {
      addListener(element, EVENT_STOP, options.stop, {
        once: true
      });
    }
    if (dispatchEvent(element, EVENT_STOP) === false) {
      return this;
    }
    var player = this.player;
    clearTimeout(this.playing.timeout);
    this.playing = false;
    this.played = false;
    forEach(player.getElementsByTagName('img'), function (image) {
      removeListener(image, EVENT_LOAD, _this8.onLoadWhenPlay);
    });
    removeClass(player, CLASS_SHOW);
    player.innerHTML = '';
    this.exitFullscreen();
    return this;
  },
  // Enter modal mode (only available in inline mode)
  full: function full() {
    var _this9 = this;
    var options = this.options,
      viewer = this.viewer,
      image = this.image,
      list = this.list;
    if (!this.isShown || this.played || this.fulled || !options.inline) {
      return this;
    }
    this.fulled = true;
    this.open();
    addClass(this.button, CLASS_FULLSCREEN_EXIT);
    if (options.transition) {
      removeClass(list, CLASS_TRANSITION);
      if (this.viewed) {
        removeClass(image, CLASS_TRANSITION);
      }
    }
    addClass(viewer, CLASS_FIXED);
    viewer.setAttribute('role', 'dialog');
    viewer.setAttribute('aria-labelledby', this.title.id);
    viewer.setAttribute('aria-modal', true);
    viewer.removeAttribute('style');
    setStyle(viewer, {
      zIndex: options.zIndex
    });
    if (options.focus) {
      this.enforceFocus();
    }
    this.initContainer();
    this.viewerData = assign({}, this.containerData);
    this.renderList();
    if (this.viewed) {
      this.initImage(function () {
        _this9.renderImage(function () {
          if (options.transition) {
            setTimeout(function () {
              addClass(image, CLASS_TRANSITION);
              addClass(list, CLASS_TRANSITION);
            }, 0);
          }
        });
      });
    }
    return this;
  },
  // Exit modal mode (only available in inline mode)
  exit: function exit() {
    var _this10 = this;
    var options = this.options,
      viewer = this.viewer,
      image = this.image,
      list = this.list;
    if (!this.isShown || this.played || !this.fulled || !options.inline) {
      return this;
    }
    this.fulled = false;
    this.close();
    removeClass(this.button, CLASS_FULLSCREEN_EXIT);
    if (options.transition) {
      removeClass(list, CLASS_TRANSITION);
      if (this.viewed) {
        removeClass(image, CLASS_TRANSITION);
      }
    }
    if (options.focus) {
      this.clearEnforceFocus();
    }
    viewer.removeAttribute('role');
    viewer.removeAttribute('aria-labelledby');
    viewer.removeAttribute('aria-modal');
    removeClass(viewer, CLASS_FIXED);
    setStyle(viewer, {
      zIndex: options.zIndexInline
    });
    this.viewerData = assign({}, this.parentData);
    this.renderViewer();
    this.renderList();
    if (this.viewed) {
      this.initImage(function () {
        _this10.renderImage(function () {
          if (options.transition) {
            setTimeout(function () {
              addClass(image, CLASS_TRANSITION);
              addClass(list, CLASS_TRANSITION);
            }, 0);
          }
        });
      });
    }
    return this;
  },
  // Show the current ratio of the image with percentage
  tooltip: function tooltip() {
    var _this11 = this;
    var options = this.options,
      tooltipBox = this.tooltipBox,
      imageData = this.imageData;
    if (!this.viewed || this.played || !options.tooltip) {
      return this;
    }
    tooltipBox.textContent = "".concat(Math.round(imageData.ratio * 100), "%");
    if (!this.tooltipping) {
      if (options.transition) {
        if (this.fading) {
          dispatchEvent(tooltipBox, EVENT_TRANSITION_END);
        }
        addClass(tooltipBox, CLASS_SHOW);
        addClass(tooltipBox, CLASS_FADE);
        addClass(tooltipBox, CLASS_TRANSITION);
        tooltipBox.removeAttribute('aria-hidden');

        // Force reflow to enable CSS3 transition
        tooltipBox.initialOffsetWidth = tooltipBox.offsetWidth;
        addClass(tooltipBox, CLASS_IN);
      } else {
        addClass(tooltipBox, CLASS_SHOW);
        tooltipBox.removeAttribute('aria-hidden');
      }
    } else {
      clearTimeout(this.tooltipping);
    }
    this.tooltipping = setTimeout(function () {
      if (options.transition) {
        addListener(tooltipBox, EVENT_TRANSITION_END, function () {
          removeClass(tooltipBox, CLASS_SHOW);
          removeClass(tooltipBox, CLASS_FADE);
          removeClass(tooltipBox, CLASS_TRANSITION);
          tooltipBox.setAttribute('aria-hidden', true);
          _this11.fading = false;
        }, {
          once: true
        });
        removeClass(tooltipBox, CLASS_IN);
        _this11.fading = true;
      } else {
        removeClass(tooltipBox, CLASS_SHOW);
        tooltipBox.setAttribute('aria-hidden', true);
      }
      _this11.tooltipping = false;
    }, 1000);
    return this;
  },
  /**
   * Toggle the image size between its current size and natural size
   * @param {Event} [_originalEvent=null] - The original event if any.
   * @returns {Viewer} this
   */
  toggle: function toggle() {
    var _originalEvent = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
    if (this.imageData.ratio === 1) {
      this.zoomTo(this.imageData.oldRatio, true, null, _originalEvent);
    } else {
      this.zoomTo(1, true, null, _originalEvent);
    }
    return this;
  },
  // Reset the image to its initial state
  reset: function reset() {
    if (this.viewed && !this.played) {
      this.imageData = assign({}, this.initialImageData);
      this.renderImage();
    }
    return this;
  },
  // Update viewer when images changed
  update: function update() {
    var _this12 = this;
    var element = this.element,
      options = this.options,
      isImg = this.isImg;

    // Destroy viewer if the target image was deleted
    if (isImg && !element.parentNode) {
      return this.destroy();
    }
    var images = [];
    forEach(isImg ? [element] : element.querySelectorAll('img'), function (image) {
      if (isFunction(options.filter)) {
        if (options.filter.call(_this12, image)) {
          images.push(image);
        }
      } else if (_this12.getImageURL(image)) {
        images.push(image);
      }
    });
    if (!images.length) {
      return this;
    }
    this.images = images;
    this.length = images.length;
    if (this.ready) {
      var changedIndexes = [];
      forEach(this.items, function (item, i) {
        var img = item.querySelector('img');
        var image = images[i];
        if (image && img) {
          if (image.src !== img.src

          // Title changed (#408)
          || image.alt !== img.alt) {
            changedIndexes.push(i);
          }
        } else {
          changedIndexes.push(i);
        }
      });
      setStyle(this.list, {
        width: 'auto'
      });
      this.initList();
      if (this.isShown) {
        if (this.length) {
          if (this.viewed) {
            var changedIndex = changedIndexes.indexOf(this.index);
            if (changedIndex >= 0) {
              this.viewed = false;
              this.view(Math.max(Math.min(this.index - changedIndex, this.length - 1), 0));
            } else {
              var activeItem = this.items[this.index];

              // Reactivate the current viewing item after reset the list.
              addClass(activeItem, CLASS_ACTIVE);
              activeItem.setAttribute('aria-selected', true);
            }
          }
        } else {
          this.image = null;
          this.viewed = false;
          this.index = 0;
          this.imageData = {};
          this.canvas.innerHTML = '';
          this.title.innerHTML = '';
        }
      }
    } else {
      this.build();
    }
    return this;
  },
  // Destroy the viewer
  destroy: function destroy() {
    var element = this.element,
      options = this.options;
    if (!element[NAMESPACE]) {
      return this;
    }
    this.destroyed = true;
    if (this.ready) {
      if (this.played) {
        this.stop();
      }
      if (options.inline) {
        if (this.fulled) {
          this.exit();
        }
        this.unbind();
      } else if (this.isShown) {
        if (this.viewing) {
          if (this.imageRendering) {
            this.imageRendering.abort();
          } else if (this.imageInitializing) {
            this.imageInitializing.abort();
          }
        }
        if (this.hiding) {
          this.transitioning.abort();
        }
        this.hidden();
      } else if (this.showing) {
        this.transitioning.abort();
        this.hidden();
      }
      this.ready = false;
      this.viewer.parentNode.removeChild(this.viewer);
    } else if (options.inline) {
      if (this.delaying) {
        this.delaying.abort();
      } else if (this.initializing) {
        this.initializing.abort();
      }
    }
    if (!options.inline) {
      removeListener(element, EVENT_CLICK, this.onStart);
    }
    element[NAMESPACE] = undefined;
    return this;
  }
};

var others = {
  getImageURL: function getImageURL(image) {
    var url = this.options.url;
    if (isString(url)) {
      url = image.getAttribute(url);
    } else if (isFunction(url)) {
      url = url.call(this, image);
    } else {
      url = '';
    }
    return url;
  },
  enforceFocus: function enforceFocus() {
    var _this = this;
    this.clearEnforceFocus();
    addListener(document, EVENT_FOCUSIN, this.onFocusin = function (event) {
      var viewer = _this.viewer;
      var target = event.target;
      if (target === document || target === viewer || viewer.contains(target)) {
        return;
      }
      while (target) {
        // Avoid conflicts with other modals (#474, #540)
        if (target.getAttribute('tabindex') !== null || target.getAttribute('aria-modal') === 'true') {
          return;
        }
        target = target.parentElement;
      }
      viewer.focus();
    });
  },
  clearEnforceFocus: function clearEnforceFocus() {
    if (this.onFocusin) {
      removeListener(document, EVENT_FOCUSIN, this.onFocusin);
      this.onFocusin = null;
    }
  },
  open: function open() {
    var body = this.body;
    addClass(body, CLASS_OPEN);
    if (this.scrollbarWidth > 0) {
      body.style.paddingRight = "".concat(this.scrollbarWidth + (parseFloat(this.initialBodyComputedPaddingRight) || 0), "px");
    }
  },
  close: function close() {
    var body = this.body;
    removeClass(body, CLASS_OPEN);
    if (this.scrollbarWidth > 0) {
      body.style.paddingRight = this.initialBodyPaddingRight;
    }
  },
  shown: function shown() {
    var element = this.element,
      options = this.options,
      viewer = this.viewer;
    this.fulled = true;
    this.isShown = true;
    this.render();
    this.bind();
    this.showing = false;
    if (options.focus) {
      viewer.focus();
      this.enforceFocus();
    }
    if (isFunction(options.shown)) {
      addListener(element, EVENT_SHOWN, options.shown, {
        once: true
      });
    }
    if (dispatchEvent(element, EVENT_SHOWN) === false) {
      return;
    }
    if (this.ready && this.isShown && !this.hiding) {
      this.view(this.index);
    }
  },
  hidden: function hidden() {
    var element = this.element,
      options = this.options,
      viewer = this.viewer;
    if (options.fucus) {
      this.clearEnforceFocus();
    }
    this.close();
    this.unbind();
    addClass(viewer, CLASS_HIDE);
    viewer.removeAttribute('role');
    viewer.removeAttribute('aria-labelledby');
    viewer.removeAttribute('aria-modal');
    viewer.setAttribute('aria-hidden', true);
    this.resetList();
    this.resetImage();
    this.fulled = false;
    this.viewed = false;
    this.isShown = false;
    this.hiding = false;
    if (!this.destroyed) {
      if (isFunction(options.hidden)) {
        addListener(element, EVENT_HIDDEN, options.hidden, {
          once: true
        });
      }
      dispatchEvent(element, EVENT_HIDDEN, null, {
        cancelable: false
      });
    }
  },
  requestFullscreen: function requestFullscreen(options) {
    var document = this.element.ownerDocument;
    if (this.fulled && !(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement)) {
      var documentElement = document.documentElement;

      // Element.requestFullscreen()
      if (documentElement.requestFullscreen) {
        // Avoid TypeError when convert `options` to dictionary
        if (isPlainObject(options)) {
          documentElement.requestFullscreen(options);
        } else {
          documentElement.requestFullscreen();
        }
      } else if (documentElement.webkitRequestFullscreen) {
        documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
      } else if (documentElement.mozRequestFullScreen) {
        documentElement.mozRequestFullScreen();
      } else if (documentElement.msRequestFullscreen) {
        documentElement.msRequestFullscreen();
      }
    }
  },
  exitFullscreen: function exitFullscreen() {
    var document = this.element.ownerDocument;
    if (this.fulled && (document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement)) {
      // Document.exitFullscreen()
      if (document.exitFullscreen) {
        document.exitFullscreen();
      } else if (document.webkitExitFullscreen) {
        document.webkitExitFullscreen();
      } else if (document.mozCancelFullScreen) {
        document.mozCancelFullScreen();
      } else if (document.msExitFullscreen) {
        document.msExitFullscreen();
      }
    }
  },
  change: function change(event) {
    var options = this.options,
      pointers = this.pointers;
    var pointer = pointers[Object.keys(pointers)[0]];

    // In the case of the `pointers` object is empty (#421)
    if (!pointer) {
      return;
    }
    var offsetX = pointer.endX - pointer.startX;
    var offsetY = pointer.endY - pointer.startY;
    switch (this.action) {
      // Move the current image
      case ACTION_MOVE:
        if (offsetX !== 0 || offsetY !== 0) {
          this.pointerMoved = true;
          this.move(offsetX, offsetY, event);
        }
        break;

      // Zoom the current image
      case ACTION_ZOOM:
        this.zoom(getMaxZoomRatio(pointers), false, null, event);
        break;
      case ACTION_SWITCH:
        {
          this.action = 'switched';
          var absoluteOffsetX = Math.abs(offsetX);
          if (absoluteOffsetX > 1 && absoluteOffsetX > Math.abs(offsetY)) {
            // Empty `pointers` as `touchend` event will not be fired after swiped in iOS browsers.
            this.pointers = {};
            if (offsetX > 1) {
              this.prev(options.loop);
            } else if (offsetX < -1) {
              this.next(options.loop);
            }
          }
          break;
        }
    }

    // Override
    forEach(pointers, function (p) {
      p.startX = p.endX;
      p.startY = p.endY;
    });
  },
  isSwitchable: function isSwitchable() {
    var imageData = this.imageData,
      viewerData = this.viewerData;
    return this.length > 1 && imageData.x >= 0 && imageData.y >= 0 && imageData.width <= viewerData.width && imageData.height <= viewerData.height;
  }
};

var AnotherViewer = WINDOW.Viewer;
var getUniqueID = function (id) {
  return function () {
    id += 1;
    return id;
  };
}(-1);
var Viewer = /*#__PURE__*/function () {
  /**
   * Create a new Viewer.
   * @param {Element} element - The target element for viewing.
   * @param {Object} [options={}] - The configuration options.
   */
  function Viewer(element) {
    var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
    _classCallCheck(this, Viewer);
    if (!element || element.nodeType !== 1) {
      throw new Error('The first argument is required and must be an element.');
    }
    this.element = element;
    this.options = assign({}, DEFAULTS, isPlainObject(options) && options);
    this.action = false;
    this.fading = false;
    this.fulled = false;
    this.hiding = false;
    this.imageClicked = false;
    this.imageData = {};
    this.index = this.options.initialViewIndex;
    this.isImg = false;
    this.isShown = false;
    this.length = 0;
    this.moving = false;
    this.played = false;
    this.playing = false;
    this.pointers = {};
    this.ready = false;
    this.rotating = false;
    this.scaling = false;
    this.showing = false;
    this.timeout = false;
    this.tooltipping = false;
    this.viewed = false;
    this.viewing = false;
    this.wheeling = false;
    this.zooming = false;
    this.pointerMoved = false;
    this.id = getUniqueID();
    this.init();
  }
  return _createClass(Viewer, [{
    key: "init",
    value: function init() {
      var _this = this;
      var element = this.element,
        options = this.options;
      if (element[NAMESPACE]) {
        return;
      }
      element[NAMESPACE] = this;

      // The `focus` option requires the `keyboard` option set to `true`.
      if (options.focus && !options.keyboard) {
        options.focus = false;
      }
      var isImg = element.localName === 'img';
      var images = [];
      forEach(isImg ? [element] : element.querySelectorAll('img'), function (image) {
        if (isFunction(options.filter)) {
          if (options.filter.call(_this, image)) {
            images.push(image);
          }
        } else if (_this.getImageURL(image)) {
          images.push(image);
        }
      });
      this.isImg = isImg;
      this.length = images.length;
      this.images = images;
      this.initBody();

      // Override `transition` option if it is not supported
      if (isUndefined(document.createElement(NAMESPACE).style.transition)) {
        options.transition = false;
      }
      if (options.inline) {
        var count = 0;
        var progress = function progress() {
          count += 1;
          if (count === _this.length) {
            var timeout;
            _this.initializing = false;
            _this.delaying = {
              abort: function abort() {
                clearTimeout(timeout);
              }
            };

            // build asynchronously to keep `this.viewer` is accessible in `ready` event handler.
            timeout = setTimeout(function () {
              _this.delaying = false;
              _this.build();
            }, 0);
          }
        };
        this.initializing = {
          abort: function abort() {
            forEach(images, function (image) {
              if (!image.complete) {
                removeListener(image, EVENT_LOAD, progress);
                removeListener(image, EVENT_ERROR, progress);
              }
            });
          }
        };
        forEach(images, function (image) {
          if (image.complete) {
            progress();
          } else {
            var onLoad;
            var onError;
            addListener(image, EVENT_LOAD, onLoad = function onLoad() {
              removeListener(image, EVENT_ERROR, onError);
              progress();
            }, {
              once: true
            });
            addListener(image, EVENT_ERROR, onError = function onError() {
              removeListener(image, EVENT_LOAD, onLoad);
              progress();
            }, {
              once: true
            });
          }
        });
      } else {
        addListener(element, EVENT_CLICK, this.onStart = function (_ref) {
          var target = _ref.target;
          if (target.localName === 'img' && (!isFunction(options.filter) || options.filter.call(_this, target))) {
            _this.view(_this.images.indexOf(target));
          }
        });
      }
    }
  }, {
    key: "build",
    value: function build() {
      if (this.ready) {
        return;
      }
      var element = this.element,
        options = this.options;
      var parent = element.parentNode;
      var template = document.createElement('div');
      template.innerHTML = TEMPLATE;
      var viewer = template.querySelector(".".concat(NAMESPACE, "-container"));
      var title = viewer.querySelector(".".concat(NAMESPACE, "-title"));
      var toolbar = viewer.querySelector(".".concat(NAMESPACE, "-toolbar"));
      var navbar = viewer.querySelector(".".concat(NAMESPACE, "-navbar"));
      var button = viewer.querySelector(".".concat(NAMESPACE, "-button"));
      var canvas = viewer.querySelector(".".concat(NAMESPACE, "-canvas"));
      this.parent = parent;
      this.viewer = viewer;
      this.title = title;
      this.toolbar = toolbar;
      this.navbar = navbar;
      this.button = button;
      this.canvas = canvas;
      this.footer = viewer.querySelector(".".concat(NAMESPACE, "-footer"));
      this.tooltipBox = viewer.querySelector(".".concat(NAMESPACE, "-tooltip"));
      this.player = viewer.querySelector(".".concat(NAMESPACE, "-player"));
      this.list = viewer.querySelector(".".concat(NAMESPACE, "-list"));
      viewer.id = "".concat(NAMESPACE).concat(this.id);
      title.id = "".concat(NAMESPACE, "Title").concat(this.id);
      addClass(title, !options.title ? CLASS_HIDE : getResponsiveClass(Array.isArray(options.title) ? options.title[0] : options.title));
      addClass(navbar, !options.navbar ? CLASS_HIDE : getResponsiveClass(options.navbar));
      toggleClass(button, CLASS_HIDE, !options.button);
      if (options.keyboard) {
        button.setAttribute('tabindex', 0);
      }
      if (options.backdrop) {
        addClass(viewer, "".concat(NAMESPACE, "-backdrop"));
        if (!options.inline && options.backdrop !== 'static') {
          setData(canvas, DATA_ACTION, 'hide');
        }
      }
      if (isString(options.className) && options.className) {
        // In case there are multiple class names
        options.className.split(REGEXP_SPACES).forEach(function (className) {
          addClass(viewer, className);
        });
      }
      if (options.toolbar) {
        var list = document.createElement('ul');
        var custom = isPlainObject(options.toolbar);
        var zoomButtons = BUTTONS.slice(0, 3);
        var rotateButtons = BUTTONS.slice(7, 9);
        var scaleButtons = BUTTONS.slice(9);
        if (!custom) {
          addClass(toolbar, getResponsiveClass(options.toolbar));
        }
        forEach(custom ? options.toolbar : BUTTONS, function (value, index) {
          var deep = custom && isPlainObject(value);
          var name = custom ? hyphenate(index) : value;
          var show = deep && !isUndefined(value.show) ? value.show : value;
          if (!show || !options.zoomable && zoomButtons.indexOf(name) !== -1 || !options.rotatable && rotateButtons.indexOf(name) !== -1 || !options.scalable && scaleButtons.indexOf(name) !== -1) {
            return;
          }
          var size = deep && !isUndefined(value.size) ? value.size : value;
          var click = deep && !isUndefined(value.click) ? value.click : value;
          var item = document.createElement('li');
          if (options.keyboard) {
            item.setAttribute('tabindex', 0);
          }
          item.setAttribute('role', 'button');
          addClass(item, "".concat(NAMESPACE, "-").concat(name));
          if (!isFunction(click)) {
            setData(item, DATA_ACTION, name);
          }
          if (isNumber(show)) {
            addClass(item, getResponsiveClass(show));
          }
          if (['small', 'large'].indexOf(size) !== -1) {
            addClass(item, "".concat(NAMESPACE, "-").concat(size));
          } else if (name === 'play') {
            addClass(item, "".concat(NAMESPACE, "-large"));
          }
          if (isFunction(click)) {
            addListener(item, EVENT_CLICK, click);
          }
          list.appendChild(item);
        });
        toolbar.appendChild(list);
      } else {
        addClass(toolbar, CLASS_HIDE);
      }
      if (!options.rotatable) {
        var rotates = toolbar.querySelectorAll('li[class*="rotate"]');
        addClass(rotates, CLASS_INVISIBLE);
        forEach(rotates, function (rotate) {
          toolbar.appendChild(rotate);
        });
      }
      if (options.inline) {
        addClass(button, CLASS_FULLSCREEN);
        setStyle(viewer, {
          zIndex: options.zIndexInline
        });
        if (window.getComputedStyle(parent).position === 'static') {
          setStyle(parent, {
            position: 'relative'
          });
        }
        parent.insertBefore(viewer, element.nextSibling);
      } else {
        addClass(button, CLASS_CLOSE);
        addClass(viewer, CLASS_FIXED);
        addClass(viewer, CLASS_FADE);
        addClass(viewer, CLASS_HIDE);
        setStyle(viewer, {
          zIndex: options.zIndex
        });
        var container = options.container;
        if (isString(container)) {
          container = element.ownerDocument.querySelector(container);
        }
        if (!container) {
          container = this.body;
        }
        container.appendChild(viewer);
      }
      if (options.inline) {
        this.render();
        this.bind();
        this.isShown = true;
      }
      this.ready = true;
      if (isFunction(options.ready)) {
        addListener(element, EVENT_READY, options.ready, {
          once: true
        });
      }
      if (dispatchEvent(element, EVENT_READY) === false) {
        this.ready = false;
        return;
      }
      if (this.ready && options.inline) {
        this.view(this.index);
      }
    }

    /**
     * Get the no conflict viewer class.
     * @returns {Viewer} The viewer class.
     */
  }], [{
    key: "noConflict",
    value: function noConflict() {
      window.Viewer = AnotherViewer;
      return Viewer;
    }

    /**
     * Change the default options.
     * @param {Object} options - The new default options.
     */
  }, {
    key: "setDefaults",
    value: function setDefaults(options) {
      assign(DEFAULTS, isPlainObject(options) && options);
    }
  }]);
}();
assign(Viewer.prototype, render, events, handlers, methods, others);

module.exports = Viewer;


================================================
FILE: dist/viewer.css
================================================
/*!
 * Viewer.js v1.11.7
 * https://fengyuanchen.github.io/viewerjs
 *
 * Copyright 2015-present Chen Fengyuan
 * Released under the MIT license
 *
 * Date: 2024-11-24T04:32:14.526Z
 */

.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 {
    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");
    background-repeat: no-repeat;
    background-size: 280px;
    color: transparent;
    display: block;
    font-size: 0;
    height: 20px;
    line-height: 0;
    width: 20px;
  }

.viewer-zoom-in::before {
  background-position: 0 0;
  content: 'Zoom In';
}

.viewer-zoom-out::before {
  background-position: -20px 0;
  content: 'Zoom Out';
}

.viewer-one-to-one::before {
  background-position: -40px 0;
  content: 'One to One';
}

.viewer-reset::before {
  background-position: -60px 0;
  content: 'Reset';
}

.viewer-prev::before {
  background-position: -80px 0;
  content: 'Previous';
}

.viewer-play::before {
  background-position: -100px 0;
  content: 'Play';
}

.viewer-next::before {
  background-position: -120px 0;
  content: 'Next';
}

.viewer-rotate-left::before {
  background-position: -140px 0;
  content: 'Rotate Left';
}

.viewer-rotate-right::before {
  background-position: -160px 0;
  content: 'Rotate Right';
}

.viewer-flip-horizontal::before {
  background-position: -180px 0;
  content: 'Flip Horizontal';
}

.viewer-flip-vertical::before {
  background-position: -200px 0;
  content: 'Flip Vertical';
}

.viewer-fullscreen::before {
  background-position: -220px 0;
  content: 'Enter Full Screen';
}

.viewer-fullscreen-exit::before {
  background-position: -240px 0;
  content: 'Exit Full Screen';
}

.viewer-close::before {
  background-position: -260px 0;
  content: 'Close';
}

.viewer-container {
  bottom: 0;
  direction: ltr;
  font-size: 0;
  left: 0;
  line-height: 0;
  overflow: hidden;
  position: absolute;
  right: 0;
  -webkit-tap-highlight-color: transparent;
  top: 0;
  -ms-touch-action: none;
      touch-action: none;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
     -moz-user-select: none;
      -ms-user-select: none;
          user-select: none;
}

.viewer-container::-moz-selection, .viewer-container *::-moz-selection {
    background-color: transparent;
  }

.viewer-container::selection,
  .viewer-container *::selection {
    background-color: transparent;
  }

.viewer-container:focus {
    outline: 0;
  }

.viewer-container img {
    display: block;
    height: auto;
    max-height: none !important;
    max-width: none !important;
    min-height: 0 !important;
    min-width: 0 !important;
    width: 100%;
  }

.viewer-canvas {
  bottom: 0;
  left: 0;
  overflow: hidden;
  position: absolute;
  right: 0;
  top: 0;
}

.viewer-canvas > img {
    height: auto;
    margin: 15px auto;
    max-width: 90% !important;
    width: auto;
  }

.viewer-footer {
  bottom: 0;
  left: 0;
  overflow: hidden;
  position: absolute;
  right: 0;
  text-align: center;
}

.viewer-navbar {
  background-color: rgba(0, 0, 0, 0.5);
  overflow: hidden;
}

.viewer-list {
  box-sizing: content-box;
  height: 50px;
  margin: 0;
  overflow: hidden;
  padding: 1px 0;
}

.viewer-list > li {
    color: transparent;
    cursor: pointer;
    float: left;
    font-size: 0;
    height: 50px;
    line-height: 0;
    opacity: 0.5;
    overflow: hidden;
    transition: opacity 0.15s;
    width: 30px;
  }

.viewer-list > li:focus,
    .viewer-list > li:hover {
      opacity: 0.75;
    }

.viewer-list > li:focus {
      outline: 0;
    }

.viewer-list > li + li {
      margin-left: 1px;
    }

.viewer-list > .viewer-loading {
    position: relative;
  }

.viewer-list > .viewer-loading::after {
      border-width: 2px;
      height: 20px;
      margin-left: -10px;
      margin-top: -10px;
      width: 20px;
    }

.viewer-list > .viewer-active,
  .viewer-list > .viewer-active:focus,
  .viewer-list > .viewer-active:hover {
    opacity: 1;
  }

.viewer-player {
  background-color: #000;
  bottom: 0;
  cursor: none;
  display: none;
  left: 0;
  position: absolute;
  right: 0;
  top: 0;
  z-index: 1;
}

.viewer-player > img {
    left: 0;
    position: absolute;
    top: 0;
  }

.viewer-toolbar > ul {
    display: inline-block;
    margin: 0 auto 5px;
    overflow: hidden;
    padding: 6px 3px;
  }

.viewer-toolbar > ul > li {
      background-color: rgba(0, 0, 0, 0.5);
      border-radius: 50%;
      cursor: pointer;
      float: left;
      height: 24px;
      overflow: hidden;
      transition: background-color 0.15s;
      width: 24px;
    }

.viewer-toolbar > ul > li:focus,
      .viewer-toolbar > ul > li:hover {
        background-color: rgba(0, 0, 0, 0.8);
      }

.viewer-toolbar > ul > li:focus {
        box-shadow: 0 0 3px #fff;
        outline: 0;
        position: relative;
        z-index: 1;
      }

.viewer-toolbar > ul > li::before {
        margin: 2px;
      }

.viewer-toolbar > ul > li + li {
        margin-left: 1px;
      }

.viewer-toolbar > ul > .viewer-small {
      height: 18px;
      margin-bottom: 3px;
      margin-top: 3px;
      width: 18px;
    }

.viewer-toolbar > ul > .viewer-small::before {
        margin: -1px;
      }

.viewer-toolbar > ul > .viewer-large {
      height: 30px;
      margin-bottom: -3px;
      margin-top: -3px;
      width: 30px;
    }

.viewer-toolbar > ul > .viewer-large::before {
        margin: 5px;
      }

.viewer-tooltip {
  background-color: rgba(0, 0, 0, 0.8);
  border-radius: 10px;
  color: #fff;
  display: none;
  font-size: 12px;
  height: 20px;
  left: 50%;
  line-height: 20px;
  margin-left: -25px;
  margin-top: -10px;
  position: absolute;
  text-align: center;
  top: 50%;
  width: 50px;
}

.viewer-title {
  color: #ccc;
  display: inline-block;
  font-size: 12px;
  line-height: 1.2;
  margin: 5px 5%;
  max-width: 90%;
  min-height: 14px;
  opacity: 0.8;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: opacity 0.15s;
  white-space: nowrap;
}

.viewer-title:hover {
    opacity: 1;
  }

.viewer-button {
  -webkit-app-region: no-drag;
  background-color: rgba(0, 0, 0, 0.5);
  border-radius: 50%;
  cursor: pointer;
  height: 80px;
  overflow: hidden;
  position: absolute;
  right: -40px;
  top: -40px;
  transition: background-color 0.15s;
  width: 80px;
}

.viewer-button:focus,
  .viewer-button:hover {
    background-color: rgba(0, 0, 0, 0.8);
  }

.viewer-button:focus {
    box-shadow: 0 0 3px #fff;
    outline: 0;
  }

.viewer-button::before {
    bottom: 15px;
    left: 15px;
    position: absolute;
  }

.viewer-fixed {
  position: fixed;
}

.viewer-open {
  overflow: hidden;
}

.viewer-show {
  display: block;
}

.viewer-hide {
  display: none;
}

.viewer-backdrop {
  background-color: rgba(0, 0, 0, 0.5);
}

.viewer-invisible {
  visibility: hidden;
}

.viewer-move {
  cursor: move;
  cursor: grab;
}

.viewer-fade {
  opacity: 0;
}

.viewer-in {
  opacity: 1;
}

.viewer-transition {
  transition: all 0.3s;
}

@keyframes viewer-spinner {
  0% {
    transform: rotate(0deg);
  }

  100% {
    transform: rotate(360deg);
  }
}

.viewer-loading::after {
    animation: viewer-spinner 1s linear infinite;
    border: 4px solid rgba(255, 255, 255, 0.1);
    border-left-color: rgba(255, 255, 255, 0.5);
    border-radius: 50%;
    content: '';
    display: inline-block;
    height: 40px;
    left: 50%;
    margin-left: -20px;
    margin-top: -20px;
    position: absolute;
    top: 50%;
    width: 40px;
    z-index: 1;
  }

@media (max-width: 767px) {
  .viewer-hide-xs-down {
    display: none;
  }
}

@media (max-width: 991px) {
  .viewer-hide-sm-down {
    display: none;
  }
}

@media (max-width: 1199px) {
  .viewer-hide-md-down {
    display: none;
  }
}


================================================
FILE: dist/viewer.esm.js
================================================
/*!
 * Viewer.js v1.11.7
 * https://fengyuanchen.github.io/viewerjs
 *
 * Copyright 2015-present Chen Fengyuan
 * Released under the MIT license
 *
 * Date: 2024-11-24T04:32:19.116Z
 */

function _classCallCheck(a, n) {
  if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function");
}
function _defineProperties(e, r) {
  for (var t = 0; t < r.length; t++) {
    var o = r[t];
    o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o);
  }
}
function _createClass(e, r, t) {
  return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", {
    writable: !1
  }), e;
}
function _defineProperty(e, r, t) {
  return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
    value: t,
    enumerable: !0,
    configurable: !0,
    writable: !0
  }) : e[r] = t, e;
}
function ownKeys(e, r) {
  var t = Object.keys(e);
  if (Object.getOwnPropertySymbols) {
    var o = Object.getOwnPropertySymbols(e);
    r && (o = o.filter(function (r) {
      return Object.getOwnPropertyDescriptor(e, r).enumerable;
    })), t.push.apply(t, o);
  }
  return t;
}
function _objectSpread2(e) {
  for (var r = 1; r < arguments.length; r++) {
    var t = null != arguments[r] ? arguments[r] : {};
    r % 2 ? ownKeys(Object(t), !0).forEach(function (r) {
      _defineProperty(e, r, t[r]);
    }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {
      Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
    });
  }
  return e;
}
function _toPrimitive(t, r) {
  if ("object" != typeof t || !t) return t;
  var e = t[Symbol.toPrimitive];
  if (void 0 !== e) {
    var i = e.call(t, r || "default");
    if ("object" != typeof i) return i;
    throw new TypeError("@@toPrimitive must return a primitive value.");
  }
  return ("string" === r ? String : Number)(t);
}
function _toPropertyKey(t) {
  var i = _toPrimitive(t, "string");
  return "symbol" == typeof i ? i : i + "";
}
function _typeof(o) {
  "@babel/helpers - typeof";

  return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
    return typeof o;
  } : function (o) {
    return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
  }, _typeof(o);
}

var DEFAULTS = {
  /**
   * Enable a modal backdrop, specify `static` for a backdrop
   * which doesn't close the modal on click.
   * @type {boolean}
   */
  backdrop: true,
  /**
   * Show the button on the top-right of the viewer.
   * @type {boolean}
   */
  button: true,
  /**
   * Show the navbar.
   * @type {boolean | number}
   */
  navbar: true,
  /**
   * Specify the visibility and the content of the title.
   * @type {boolean | number | Function | Array}
   */
  title: true,
  /**
   * Show the toolbar.
   * @type {boolean | number | Object}
   */
  toolbar: true,
  /**
   * Custom class name(s) to add to the viewer's root element.
   * @type {string}
   */
  className: '',
  /**
   * Define where to put the viewer in modal mode.
   * @type {string | Element}
   */
  container: 'body',
  /**
   * Filter the images for viewing. Return true if the image is viewable.
   * @type {Function}
   */
  filter: null,
  /**
   * Enable to request fullscreen when play.
   * {@link https://developer.mozilla.org/en-US/docs/Web/API/FullscreenOptions}
   * @type {boolean|FullscreenOptions}
   */
  fullscreen: true,
  /**
   * Define the extra attributes to inherit from the original image.
   * @type {Array}
   */
  inheritedAttributes: ['crossOrigin', 'decoding', 'isMap', 'loading', 'referrerPolicy', 'sizes', 'srcset', 'useMap'],
  /**
   * Define the initial coverage of the viewing image.
   * @type {number}
   */
  initialCoverage: 0.9,
  /**
   * Define the initial index of the image for viewing.
   * @type {number}
   */
  initialViewIndex: 0,
  /**
   * Enable inline mode.
   * @type {boolean}
   */
  inline: false,
  /**
   * The amount of time to delay between automatically cycling an image when playing.
   * @type {number}
   */
  interval: 5000,
  /**
   * Enable keyboard support.
   * @type {boolean}
   */
  keyboard: true,
  /**
   * Focus the viewer when initialized.
   * @type {boolean}
   */
  focus: true,
  /**
   * Indicate if show a loading spinner when load image or not.
   * @type {boolean}
   */
  loading: true,
  /**
   * Indicate if enable loop viewing or not.
   * @type {boolean}
   */
  loop: true,
  /**
   * Min width of the viewer in inline mode.
   * @type {number}
   */
  minWidth: 200,
  /**
   * Min height of the viewer in inline mode.
   * @type {number}
   */
  minHeight: 100,
  /**
   * Enable to move the image.
   * @type {boolean}
   */
  movable: true,
  /**
   * Enable to rotate the image.
   * @type {boolean}
   */
  rotatable: true,
  /**
   * Enable to scale the image.
   * @type {boolean}
   */
  scalable: true,
  /**
   * Enable to zoom the image.
   * @type {boolean}
   */
  zoomable: true,
  /**
   * Enable to zoom the current image by dragging on the touch screen.
   * @type {boolean}
   */
  zoomOnTouch: true,
  /**
   * Enable to zoom the image by wheeling mouse.
   * @type {boolean}
   */
  zoomOnWheel: true,
  /**
   * Enable to slide to the next or previous image by swiping on the touch screen.
   * @type {boolean}
   */
  slideOnTouch: true,
  /**
   * Indicate if toggle the image size between its natural size
   * and initial size when double click on the image or not.
   * @type {boolean}
   */
  toggleOnDblclick: true,
  /**
   * Show the tooltip with image ratio (percentage) when zoom in or zoom out.
   * @type {boolean}
   */
  tooltip: true,
  /**
   * Enable CSS3 Transition for some special elements.
   * @type {boolean}
   */
  transition: true,
  /**
   * Define the CSS `z-index` value of viewer in modal mode.
   * @type {number}
   */
  zIndex: 2015,
  /**
   * Define the CSS `z-index` value of viewer in inline mode.
   * @type {number}
   */
  zIndexInline: 0,
  /**
   * Define the ratio when zoom the image by wheeling mouse.
   * @type {number}
   */
  zoomRatio: 0.1,
  /**
   * Define the min ratio of the image when zoom out.
   * @type {number}
   */
  minZoomRatio: 0.01,
  /**
   * Define the max ratio of the image when zoom in.
   * @type {number}
   */
  maxZoomRatio: 100,
  /**
   * Define where to get the original image URL for viewing.
   * @type {string | Function}
   */
  url: 'src',
  /**
   * Event shortcuts.
   * @type {Function}
   */
  ready: null,
  show: null,
  shown: null,
  hide: null,
  hidden: null,
  view: null,
  viewed: null,
  move: null,
  moved: null,
  rotate: null,
  rotated: null,
  scale: null,
  scaled: null,
  zoom: null,
  zoomed: null,
  play: null,
  stop: null
};

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>';

var IS_BROWSER = typeof window !== 'undefined' && typeof window.document !== 'undefined';
var WINDOW = IS_BROWSER ? window : {};
var IS_TOUCH_DEVICE = IS_BROWSER && WINDOW.document.documentElement ? 'ontouchstart' in WINDOW.document.documentElement : false;
var HAS_POINTER_EVENT = IS_BROWSER ? 'PointerEvent' in WINDOW : false;
var NAMESPACE = 'viewer';

// Actions
var ACTION_MOVE = 'move';
var ACTION_SWITCH = 'switch';
var ACTION_ZOOM = 'zoom';

// Classes
var CLASS_ACTIVE = "".concat(NAMESPACE, "-active");
var CLASS_CLOSE = "".concat(NAMESPACE, "-close");
var CLASS_FADE = "".concat(NAMESPACE, "-fade");
var CLASS_FIXED = "".concat(NAMESPACE, "-fixed");
var CLASS_FULLSCREEN = "".concat(NAMESPACE, "-fullscreen");
var CLASS_FULLSCREEN_EXIT = "".concat(NAMESPACE, "-fullscreen-exit");
var CLASS_HIDE = "".concat(NAMESPACE, "-hide");
var CLASS_HIDE_MD_DOWN = "".concat(NAMESPACE, "-hide-md-down");
var CLASS_HIDE_SM_DOWN = "".concat(NAMESPACE, "-hide-sm-down");
var CLASS_HIDE_XS_DOWN = "".concat(NAMESPACE, "-hide-xs-down");
var CLASS_IN = "".concat(NAMESPACE, "-in");
var CLASS_INVISIBLE = "".concat(NAMESPACE, "-invisible");
var CLASS_LOADING = "".concat(NAMESPACE, "-loading");
var CLASS_MOVE = "".concat(NAMESPACE, "-move");
var CLASS_OPEN = "".concat(NAMESPACE, "-open");
var CLASS_SHOW = "".concat(NAMESPACE, "-show");
var CLASS_TRANSITION = "".concat(NAMESPACE, "-transition");

// Native events
var EVENT_CLICK = 'click';
var EVENT_DBLCLICK = 'dblclick';
var EVENT_DRAG_START = 'dragstart';
var EVENT_FOCUSIN = 'focusin';
var EVENT_KEY_DOWN = 'keydown';
var EVENT_LOAD = 'load';
var EVENT_ERROR = 'error';
var EVENT_TOUCH_END = IS_TOUCH_DEVICE ? 'touchend touchcancel' : 'mouseup';
var EVENT_TOUCH_MOVE = IS_TOUCH_DEVICE ? 'touchmove' : 'mousemove';
var EVENT_TOUCH_START = IS_TOUCH_DEVICE ? 'touchstart' : 'mousedown';
var EVENT_POINTER_DOWN = HAS_POINTER_EVENT ? 'pointerdown' : EVENT_TOUCH_START;
var EVENT_POINTER_MOVE = HAS_POINTER_EVENT ? 'pointermove' : EVENT_TOUCH_MOVE;
var EVENT_POINTER_UP = HAS_POINTER_EVENT ? 'pointerup pointercancel' : EVENT_TOUCH_END;
var EVENT_RESIZE = 'resize';
var EVENT_TRANSITION_END = 'transitionend';
var EVENT_WHEEL = 'wheel';

// Custom events
var EVENT_READY = 'ready';
var EVENT_SHOW = 'show';
var EVENT_SHOWN = 'shown';
var EVENT_HIDE = 'hide';
var EVENT_HIDDEN = 'hidden';
var EVENT_VIEW = 'view';
var EVENT_VIEWED = 'viewed';
var EVENT_MOVE = 'move';
var EVENT_MOVED = 'moved';
var EVENT_ROTATE = 'rotate';
var EVENT_ROTATED = 'rotated';
var EVENT_SCALE = 'scale';
var EVENT_SCALED = 'scaled';
var EVENT_ZOOM = 'zoom';
var EVENT_ZOOMED = 'zoomed';
var EVENT_PLAY = 'play';
var EVENT_STOP = 'stop';

// Data keys
var DATA_ACTION = "".concat(NAMESPACE, "Action");

// RegExps
var REGEXP_SPACES = /\s\s*/;

// Misc
var BUTTONS = ['zoom-in', 'zoom-out', 'one-to-one', 'reset', 'prev', 'play', 'next', 'rotate-left', 'rotate-right', 'flip-horizontal', 'flip-vertical'];

/**
 * Check if the given value is a string.
 * @param {*} value - The value to check.
 * @returns {boolean} Returns `true` if the given value is a string, else `false`.
 */
function isString(value) {
  return typeof value === 'string';
}

/**
 * Check if the given value is not a number.
 */
var isNaN = Number.isNaN || WINDOW.isNaN;

/**
 * Check if the given value is a number.
 * @param {*} value - The value to check.
 * @returns {boolean} Returns `true` if the given value is a number, else `false`.
 */
function isNumber(value) {
  return typeof value === 'number' && !isNaN(value);
}

/**
 * Check if the given value is undefined.
 * @param {*} value - The value to check.
 * @returns {boolean} Returns `true` if the given value is undefined, else `false`.
 */
function isUndefined(value) {
  return typeof value === 'undefined';
}

/**
 * Check if the given value is an object.
 * @param {*} value - The value to check.
 * @returns {boolean} Returns `true` if the given value is an object, else `false`.
 */
function isObject(value) {
  return _typeof(value) === 'object' && value !== null;
}
var hasOwnProperty = Object.prototype.hasOwnProperty;

/**
 * Check if the given value is a plain object.
 * @param {*} value - The value to check.
 * @returns {boolean} Returns `true` if the given value is a plain object, else `false`.
 */
function isPlainObject(value) {
  if (!isObject(value)) {
    return false;
  }
  try {
    var _constructor = value.constructor;
    var prototype = _constructor.prototype;
    return _constructor && prototype && hasOwnProperty.call(prototype, 'isPrototypeOf');
  } catch (error) {
    return false;
  }
}

/**
 * Check if the given value is a function.
 * @param {*} value - The value to check.
 * @returns {boolean} Returns `true` if the given value is a function, else `false`.
 */
function isFunction(value) {
  return typeof value === 'function';
}

/**
 * Iterate the given data.
 * @param {*} data - The data to iterate.
 * @param {Function} callback - The process function for each element.
 * @returns {*} The original data.
 */
function forEach(data, callback) {
  if (data && isFunction(callback)) {
    if (Array.isArray(data) || isNumber(data.length) /* array-like */) {
      var length = data.length;
      var i;
      for (i = 0; i < length; i += 1) {
        if (callback.call(data, data[i], i, data) === false) {
          break;
        }
      }
    } else if (isObject(data)) {
      Object.keys(data).forEach(function (key) {
        callback.call(data, data[key], key, data);
      });
    }
  }
  return data;
}

/**
 * Extend the given object.
 * @param {*} obj - The object to be extended.
 * @param {*} args - The rest objects which will be merged to the first object.
 * @returns {Object} The extended object.
 */
var assign = Object.assign || function assign(obj) {
  for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
    args[_key - 1] = arguments[_key];
  }
  if (isObject(obj) && args.length > 0) {
    args.forEach(function (arg) {
      if (isObject(arg)) {
        Object.keys(arg).forEach(function (key) {
          obj[key] = arg[key];
        });
      }
    });
  }
  return obj;
};
var REGEXP_SUFFIX = /^(?:width|height|left|top|marginLeft|marginTop)$/;

/**
 * Apply styles to the given element.
 * @param {Element} element - The target element.
 * @param {Object} styles - The styles for applying.
 */
function setStyle(element, styles) {
  var style = element.style;
  forEach(styles, function (value, property) {
    if (REGEXP_SUFFIX.test(property) && isNumber(value)) {
      value += 'px';
    }
    style[property] = value;
  });
}

/**
 * Escape a string for using in HTML.
 * @param {String} value - The string to escape.
 * @returns {String} Returns the escaped string.
 */
function escapeHTMLEntities(value) {
  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;
}

/**
 * Check if the given element has a special class.
 * @param {Element} element - The element to check.
 * @param {string} value - The class to search.
 * @returns {boolean} Returns `true` if the special class was found.
 */
function hasClass(element, value) {
  if (!element || !value) {
    return false;
  }
  return element.classList ? element.classList.contains(value) : element.className.indexOf(value) > -1;
}

/**
 * Add classes to the given element.
 * @param {Element} element - The target element.
 * @param {string} value - The classes to be added.
 */
function addClass(element, value) {
  if (!element || !value) {
    return;
  }
  if (isNumber(element.length)) {
    forEach(element, function (elem) {
      addClass(elem, value);
    });
    return;
  }
  if (element.classList) {
    element.classList.add(value);
    return;
  }
  var className = element.className.trim();
  if (!className) {
    element.className = value;
  } else if (className.indexOf(value) < 0) {
    element.className = "".concat(className, " ").concat(value);
  }
}

/**
 * Remove classes from the given element.
 * @param {Element} element - The target element.
 * @param {string} value - The classes to be removed.
 */
function removeClass(element, value) {
  if (!element || !value) {
    return;
  }
  if (isNumber(element.length)) {
    forEach(element, function (elem) {
      removeClass(elem, value);
    });
    return;
  }
  if (element.classList) {
    element.classList.remove(value);
    return;
  }
  if (element.className.indexOf(value) >= 0) {
    element.className = element.className.replace(value, '');
  }
}

/**
 * Add or remove classes from the given element.
 * @param {Element} element - The target element.
 * @param {string} value - The classes to be toggled.
 * @param {boolean} added - Add only.
 */
function toggleClass(element, value, added) {
  if (!value) {
    return;
  }
  if (isNumber(element.length)) {
    forEach(element, function (elem) {
      toggleClass(elem, value, added);
    });
    return;
  }

  // IE10-11 doesn't support the second parameter of `classList.toggle`
  if (added) {
    addClass(element, value);
  } else {
    removeClass(element, value);
  }
}
var REGEXP_HYPHENATE = /([a-z\d])([A-Z])/g;

/**
 * Transform the given string from camelCase to kebab-case
 * @param {string} value - The value to transform.
 * @returns {string} The transformed value.
 */
function hyphenate(value) {
  return value.replace(REGEXP_HYPHENATE, '$1-$2').toLowerCase();
}

/**
 * Get data from the given element.
 * @param {Element} element - The target element.
 * @param {string} name - The data key to get.
 * @returns {string} The data value.
 */
function getData(element, name) {
  if (isObject(element[name])) {
    return element[name];
  }
  if (element.dataset) {
    return element.dataset[name];
  }
  return element.getAttribute("data-".concat(hyphenate(name)));
}

/**
 * Set data to the given element.
 * @param {Element} element - The target element.
 * @param {string} name - The data key to set.
 * @param {string} data - The data value.
 */
function setData(element, name, data) {
  if (isObject(data)) {
    element[name] = data;
  } else if (element.dataset) {
    element.dataset[name] = data;
  } else {
    element.setAttribute("data-".concat(hyphenate(name)), data);
  }
}
var onceSupported = function () {
  var supported = false;
  if (IS_BROWSER) {
    var once = false;
    var listener = function listener() {};
    var options = Object.defineProperty({}, 'once', {
      get: function get() {
        supported = true;
        return once;
      },
      /**
       * This setter can fix a `TypeError` in strict mode
       * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Getter_only}
       * @param {boolean} value - The value to set
       */
      set: function set(value) {
        once = value;
      }
    });
    WINDOW.addEventListener('test', listener, options);
    WINDOW.removeEventListener('test', listener, options);
  }
  return supported;
}();

/**
 * Remove event listener from the target element.
 * @param {Element} element - The event target.
 * @param {string} type - The event type(s).
 * @param {Function} listener - The event listener.
 * @param {Object} options - The event options.
 */
function removeListener(element, type, listener) {
  var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
  var handler = listener;
  type.trim().split(REGEXP_SPACES).forEach(function (event) {
    if (!onceSupported) {
      var listeners = element.listeners;
      if (listeners && listeners[event] && listeners[event][listener]) {
        handler = listeners[event][listener];
        delete listeners[event][listener];
        if (Object.keys(listeners[event]).length === 0) {
          delete listeners[event];
        }
        if (Object.keys(listeners).length === 0) {
          delete element.listeners;
        }
      }
    }
    element.removeEventListener(event, handler, options);
  });
}

/**
 * Add event listener to the target element.
 * @param {Element} element - The event target.
 * @param {string} type - The event type(s).
 * @param {Function} listener - The event listener.
 * @param {Object} options - The event options.
 */
function addListener(element, type, listener) {
  var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
  var _handler = listener;
  type.trim().split(REGEXP_SPACES).forEach(function (event) {
    if (options.once && !onceSupported) {
      var _element$listeners = element.listeners,
        listeners = _element$listeners === void 0 ? {} : _element$listeners;
      _handler = function handler() {
        delete listeners[event][listener];
        element.removeEventListener(event, _handler, options);
        for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
          args[_key2] = arguments[_key2];
        }
        listener.apply(element, args);
      };
      if (!listeners[event]) {
        listeners[event] = {};
      }
      if (listeners[event][listener]) {
        element.removeEventListener(event, listeners[event][listener], options);
      }
      listeners[event][listener] = _handler;
      element.listeners = listeners;
    }
    element.addEventListener(event, _handler, options);
  });
}

/**
 * Dispatch event on the target element.
 * @param {Element} element - The event target.
 * @param {string} type - The event type(s).
 * @param {Object} data - The additional event data.
 * @param {Object} options - The additional event options.
 * @returns {boolean} Indicate if the event is default prevented or not.
 */
function dispatchEvent(element, type, data, options) {
  var event;

  // Event and CustomEvent on IE9-11 are global objects, not constructors
  if (isFunction(Event) && isFunction(CustomEvent)) {
    event = new CustomEvent(type, _objectSpread2({
      bubbles: true,
      cancelable: true,
      detail: data
    }, options));
  } else {
    event = document.createEvent('CustomEvent');
    event.initCustomEvent(type, true, true, data);
  }
  return element.dispatchEvent(event);
}

/**
 * Get the offset base on the document.
 * @param {Element} element - The target element.
 * @returns {Object} The offset data.
 */
function getOffset(element) {
  var box = element.getBoundingClientRect();
  return {
    left: box.left + (window.pageXOffset - document.documentElement.clientLeft),
    top: box.top + (window.pageYOffset - document.documentElement.clientTop)
  };
}

/**
 * Get transforms base on the given object.
 * @param {Object} obj - The target object.
 * @returns {string} A string contains transform values.
 */
function getTransforms(_ref) {
  var rotate = _ref.rotate,
    scaleX = _ref.scaleX,
    scaleY = _ref.scaleY,
    translateX = _ref.translateX,
    translateY = _ref.translateY;
  var values = [];
  if (isNumber(translateX) && translateX !== 0) {
    values.push("translateX(".concat(translateX, "px)"));
  }
  if (isNumber(translateY) && translateY !== 0) {
    values.push("translateY(".concat(translateY, "px)"));
  }

  // Rotate should come first before scale to match orientation transform
  if (isNumber(rotate) && rotate !== 0) {
    values.push("rotate(".concat(rotate, "deg)"));
  }
  if (isNumber(scaleX) && scaleX !== 1) {
    values.push("scaleX(".concat(scaleX, ")"));
  }
  if (isNumber(scaleY) && scaleY !== 1) {
    values.push("scaleY(".concat(scaleY, ")"));
  }
  var transform = values.length ? values.join(' ') : 'none';
  return {
    WebkitTransform: transform,
    msTransform: transform,
    transform: transform
  };
}

/**
 * Get an image name from an image url.
 * @param {string} url - The target url.
 * @example
 * // picture.jpg
 * getImageNameFromURL('https://domain.com/path/to/picture.jpg?size=1280×960')
 * @returns {string} A string contains the image name.
 */
function getImageNameFromURL(url) {
  return isString(url) ? decodeURIComponent(url.replace(/^.*\//, '').replace(/[?&#].*$/, '')) : '';
}
var IS_SAFARI = WINDOW.navigator && /Version\/\d+(\.\d+)+?\s+Safari/i.test(WINDOW.navigator.userAgent);

/**
 * Get an image's natural sizes.
 * @param {string} image - The target image.
 * @param {Object} options - The viewer options.
 * @param {Function} callback - The callback function.
 * @returns {HTMLImageElement} The new image.
 */
function getImageNaturalSizes(image, options, callback) {
  var newImage = document.createElement('img');

  // Modern browsers (except Safari)
  if (image.naturalWidth && !IS_SAFARI) {
    callback(image.naturalWidth, image.naturalHeight);
    return newImage;
  }
  var body = document.body || document.documentElement;
  newImage.onload = function () {
    callback(newImage.width, newImage.height);
    if (!IS_SAFARI) {
      body.removeChild(newImage);
    }
  };
  forEach(options.inheritedAttributes, function (name) {
    var value = image.getAttribute(name);
    if (value !== null) {
      newImage.setAttribute(name, value);
    }
  });
  newImage.src = image.src;

  // iOS Safari will convert the image automatically
  // with its orientation once append it into DOM
  if (!IS_SAFARI) {
    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;';
    body.appendChild(newImage);
  }
  return newImage;
}

/**
 * Get the related class name of a responsive type number.
 * @param {string} type - The responsive type.
 * @returns {string} The related class name.
 */
function getResponsiveClass(type) {
  switch (type) {
    case 2:
      return CLASS_HIDE_XS_DOWN;
    case 3:
      return CLASS_HIDE_SM_DOWN;
    case 4:
      return CLASS_HIDE_MD_DOWN;
    default:
      return '';
  }
}

/**
 * Get the max ratio of a group of pointers.
 * @param {string} pointers - The target pointers.
 * @returns {number} The result ratio.
 */
function getMaxZoomRatio(pointers) {
  var pointers2 = _objectSpread2({}, pointers);
  var ratios = [];
  forEach(pointers, function (pointer, pointerId) {
    delete pointers2[pointerId];
    forEach(pointers2, function (pointer2) {
      var x1 = Math.abs(pointer.startX - pointer2.startX);
      var y1 = Math.abs(pointer.startY - pointer2.startY);
      var x2 = Math.abs(pointer.endX - pointer2.endX);
      var y2 = Math.abs(pointer.endY - pointer2.endY);
      var z1 = Math.sqrt(x1 * x1 + y1 * y1);
      var z2 = Math.sqrt(x2 * x2 + y2 * y2);
      var ratio = (z2 - z1) / z1;
      ratios.push(ratio);
    });
  });
  ratios.sort(function (a, b) {
    return Math.abs(a) < Math.abs(b);
  });
  return ratios[0];
}

/**
 * Get a pointer from an event object.
 * @param {Object} event - The target event object.
 * @param {boolean} endOnly - Indicates if only returns the end point coordinate or not.
 * @returns {Object} The result pointer contains start and/or end point coordinates.
 */
function getPointer(_ref2, endOnly) {
  var pageX = _ref2.pageX,
    pageY = _ref2.pageY;
  var end = {
    endX: pageX,
    endY: pageY
  };
  return endOnly ? end : _objectSpread2({
    timeStamp: Date.now(),
    startX: pageX,
    startY: pageY
  }, end);
}

/**
 * Get the center point coordinate of a group of pointers.
 * @param {Object} pointers - The target pointers.
 * @returns {Object} The center point coordinate.
 */
function getPointersCenter(pointers) {
  var pageX = 0;
  var pageY = 0;
  var count = 0;
  forEach(pointers, function (_ref3) {
    var startX = _ref3.startX,
      startY = _ref3.startY;
    pageX += startX;
    pageY += startY;
    count += 1;
  });
  pageX /= count;
  pageY /= count;
  return {
    pageX: pageX,
    pageY: pageY
  };
}

var render = {
  render: function render() {
    this.initContainer();
    this.initViewer();
    this.initList();
    this.renderViewer();
  },
  initBody: function initBody() {
    var ownerDocument = this.element.ownerDocument;
    var body = ownerDocument.body || ownerDocument.documentElement;
    this.body = body;
    this.scrollbarWidth = window.innerWidth - ownerDocument.documentElement.clientWidth;
    this.initialBodyPaddingRight = body.style.paddingRight;
    this.initialBodyComputedPaddingRight = window.getComputedStyle(body).paddingRight;
  },
  initContainer: function initContainer() {
    this.containerData = {
      width: window.innerWidth,
      height: window.innerHeight
    };
  },
  initViewer: function initViewer() {
    var options = this.options,
      parent = this.parent;
    var viewerData;
    if (options.inline) {
      viewerData = {
        width: Math.max(parent.offsetWidth, options.minWidth),
        height: Math.max(parent.offsetHeight, options.minHeight)
      };
      this.parentData = viewerData;
    }
    if (this.fulled || !viewerData) {
      viewerData = this.containerData;
    }
    this.viewerData = assign({}, viewerData);
  },
  renderViewer: function renderViewer() {
    if (this.options.inline && !this.fulled) {
      setStyle(this.viewer, this.viewerData);
    }
  },
  initList: function initList() {
    var _this = this;
    var element = this.element,
      options = this.options,
      list = this.list;
    var items = [];

    // initList may be called in this.update, so should keep idempotent
    list.innerHTML = '';
    forEach(this.images, function (image, index) {
      var src = image.src;
      var alt = image.alt || getImageNameFromURL(src);
      var url = _this.getImageURL(image);
      if (src || url) {
        var item = document.createElement('li');
        var img = document.createElement('img');
        forEach(options.inheritedAttributes, function (name) {
          var value = image.getAttribute(name);
          if (value !== null) {
            img.setAttribute(name, value);
          }
        });
        if (options.navbar) {
          img.src = src || url;
        }
        img.alt = alt;
        img.setAttribute('data-original-url', url || src);
        item.setAttribute('data-index', index);
        item.setAttribute('data-viewer-action', 'view');
        item.setAttribute('role', 'button');
        if (options.keyboard) {
          item.setAttribute('tabindex', 0);
        }
        item.appendChild(img);
        list.appendChild(item);
        items.push(item);
      }
    });
    this.items = items;
    forEach(items, function (item) {
      var image = item.firstElementChild;
      var onLoad;
      var onError;
      setData(image, 'filled', true);
      if (options.loading) {
        addClass(item, CLASS_LOADING);
      }
      addListener(image, EVENT_LOAD, onLoad = function onLoad(event) {
        removeListener(image, EVENT_ERROR, onError);
        if (options.loading) {
          removeClass(item, CLASS_LOADING);
        }
        _this.loadImage(event);
      }, {
        once: true
      });
      addListener(image, EVENT_ERROR, onError = function onError() {
        removeListener(image, EVENT_LOAD, onLoad);
        if (options.loading) {
          removeClass(item, CLASS_LOADING);
        }
      }, {
        once: true
      });
    });
    if (options.transition) {
      addListener(element, EVENT_VIEWED, function () {
        addClass(list, CLASS_TRANSITION);
      }, {
        once: true
      });
    }
  },
  renderList: function renderList() {
    var index = this.index;
    var item = this.items[index];
    if (!item) {
      return;
    }
    var next = item.nextElementSibling;
    var gutter = parseInt(window.getComputedStyle(next || item).marginLeft, 10);
    var offsetWidth = item.offsetWidth;
    var outerWidth = offsetWidth + gutter;

    // Place the active item in the center of the screen
    setStyle(this.list, assign({
      width: outerWidth * this.length - gutter
    }, getTransforms({
      translateX: (this.viewerData.width - offsetWidth) / 2 - outerWidth * index
    })));
  },
  resetList: function resetList() {
    var list = this.list;
    list.innerHTML = '';
    removeClass(list, CLASS_TRANSITION);
    setStyle(list, getTransforms({
      translateX: 0
    }));
  },
  initImage: function initImage(done) {
    var _this2 = this;
    var options = this.options,
      image = this.image,
      viewerData = this.viewerData;
    var footerHeight = this.footer.offsetHeight;
    var viewerWidth = viewerData.width;
    var viewerHeight = Math.max(viewerData.height - footerHeight, footerHeight);
    var oldImageData = this.imageData || {};
    var sizingImage;
    this.imageInitializing = {
      abort: function abort() {
        sizingImage.onload = null;
      }
    };
    sizingImage = getImageNaturalSizes(image, options, function (naturalWidth, naturalHeight) {
      var aspectRatio = naturalWidth / naturalHeight;
      var initialCoverage = Math.max(0, Math.min(1, options.initialCoverage));
      var width = viewerWidth;
      var height = viewerHeight;
      _this2.imageInitializing = false;
      if (viewerHeight * aspectRatio > viewerWidth) {
        height = viewerWidth / aspectRatio;
      } else {
        width = viewerHeight * aspectRatio;
      }
      initialCoverage = isNumber(initialCoverage) ? initialCoverage : 0.9;
      width = Math.min(width * initialCoverage, naturalWidth);
      height = Math.min(height * initialCoverage, naturalHeight);
      var left = (viewerWidth - width) / 2;
      var top = (viewerHeight - height) / 2;
      var imageData = {
        left: left,
        top: top,
        x: left,
        y: top,
        width: width,
        height: height,
        oldRatio: 1,
        ratio: width / naturalWidth,
        aspectRatio: aspectRatio,
        naturalWidth: naturalWidth,
        naturalHeight: naturalHeight
      };
      var initialImageData = assign({}, imageData);
      if (options.rotatable) {
        imageData.rotate = oldImageData.rotate || 0;
        initialImageData.rotate = 0;
      }
      if (options.scalable) {
        imageData.scaleX = oldImageData.scaleX || 1;
        imageData.scaleY = oldImageData.scaleY || 1;
        initialImageData.scaleX = 1;
        initialImageData.scaleY = 1;
      }
      _this2.imageData = imageData;
      _this2.initialImageData = initialImageData;
      if (done) {
        done();
      }
    });
  },
  renderImage: function renderImage(done) {
    var _this3 = this;
    var image = this.image,
      imageData = this.imageData;
    setStyle(image, assign({
      width: imageData.width,
      height: imageData.height,
      // XXX: Not to use translateX/Y to avoid image shaking when zooming
      marginLeft: imageData.x,
      mar
Download .txt
gitextract_1jewh7np/

├── .babelrc
├── .browserslistrc
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitattributes
├── .github/
│   ├── CODE_OF_CONDUCT.md
│   ├── CONTRIBUTING.md
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   └── feature_request.md
│   ├── PULL_REQUEST_TEMPLATE.md
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── .husky/
│   ├── commit-msg
│   └── pre-commit
├── .stylelintignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── commitlint.config.js
├── dist/
│   ├── viewer.common.js
│   ├── viewer.css
│   ├── viewer.esm.js
│   └── viewer.js
├── docs/
│   ├── css/
│   │   ├── main.css
│   │   └── viewer.css
│   ├── examples/
│   │   ├── custom-title.html
│   │   ├── custom-toolbar.html
│   │   ├── dynamic-viewer.html
│   │   ├── moving-range-limit.html
│   │   └── viewer-in-modal.html
│   ├── index.html
│   └── js/
│       ├── main.js
│       └── viewer.js
├── karma.conf.js
├── lint-staged.config.js
├── package.json
├── postcss.config.js
├── rollup.config.js
├── src/
│   ├── css/
│   │   ├── viewer.css
│   │   └── viewer.scss
│   ├── index.css
│   ├── index.js
│   ├── index.scss
│   └── js/
│       ├── constants.js
│       ├── defaults.js
│       ├── events.js
│       ├── handlers.js
│       ├── methods.js
│       ├── others.js
│       ├── render.js
│       ├── template.js
│       ├── utilities.js
│       └── viewer.js
├── stylelint.config.js
├── test/
│   ├── helpers.js
│   └── specs/
│       ├── Viewer.spec.js
│       ├── events/
│       │   ├── hidden.spec.js
│       │   ├── hide.spec.js
│       │   ├── move.spec.js
│       │   ├── moved.spec.js
│       │   ├── play.spec.js
│       │   ├── ready.spec.js
│       │   ├── rotate.spec.js
│       │   ├── rotated.spec.js
│       │   ├── scale.spec.js
│       │   ├── scaled.spec.js
│       │   ├── show.spec.js
│       │   ├── shown.spec.js
│       │   ├── stop.spec.js
│       │   ├── view.spec.js
│       │   ├── viewed.spec.js
│       │   ├── zoom.spec.js
│       │   └── zoomed.spec.js
│       ├── methods/
│       │   ├── destroy.spec.js
│       │   ├── exit.spec.js
│       │   ├── full.spec.js
│       │   ├── hide.spec.js
│       │   ├── move.spec.js
│       │   ├── moveTo.spec.js
│       │   ├── next.spec.js
│       │   ├── noConflict.spec.js
│       │   ├── play.spec.js
│       │   ├── prev.spec.js
│       │   ├── reset.spec.js
│       │   ├── rotate.spec.js
│       │   ├── rotateTo.spec.js
│       │   ├── scale.spec.js
│       │   ├── scaleX.spec.js
│       │   ├── scaleY.spec.js
│       │   ├── setDefaults.spec.js
│       │   ├── show.spec.js
│       │   ├── stop.spec.js
│       │   ├── toggle.spec.js
│       │   ├── tooltip.spec.js
│       │   ├── update.spec.js
│       │   ├── view.spec.js
│       │   ├── zoom.spec.js
│       │   └── zoomTo.spec.js
│       └── options/
│           ├── backdrop.spec.js
│           ├── button.spec.js
│           ├── className.spec.js
│           ├── container.spec.js
│           ├── filter.spec.js
│           ├── focus.spec.js
│           ├── fullscreen.spec.js
│           ├── hidden.spec.js
│           ├── hide.spec.js
│           ├── inheritedAttributes.spec.js
│           ├── initialCoverage.spec.js
│           ├── initialViewIndex.spec.js
│           ├── inline.spec.js
│           ├── interval.spec.js
│           ├── keyboard.spec.js
│           ├── loading.spec.js
│           ├── loop.spec.js
│           ├── maxZoomRatio.spec.js
│           ├── minHeight.spec.js
│           ├── minWidth.spec.js
│           ├── minZoomRatio.spec.js
│           ├── movable.spec.js
│           ├── move.spec.js
│           ├── moved.spec.js
│           ├── navbar.spec.js
│           ├── play.spec.js
│           ├── ready.spec.js
│           ├── rotatable.spec.js
│           ├── rotate.spec.js
│           ├── rotated.spec.js
│           ├── scalable.spec.js
│           ├── scale.spec.js
│           ├── scaled.spec.js
│           ├── show.spec.js
│           ├── shown.spec.js
│           ├── slideOnTouch.spec.js
│           ├── stop.spec.js
│           ├── title.spec.js
│           ├── toggleOnDblclick.spec.js
│           ├── toolbar.spec.js
│           ├── tooltip.spec.js
│           ├── transition.spec.js
│           ├── url.spec.js
│           ├── view.spec.js
│           ├── viewed.spec.js
│           ├── zIndex.spec.js
│           ├── zIndexInline.spec.js
│           ├── zoom.spec.js
│           ├── zoomOnTouch.spec.js
│           ├── zoomOnWheel.spec.js
│           ├── zoomRatio.spec.js
│           ├── zoomable.spec.js
│           └── zoomed.spec.js
└── types/
    └── index.d.ts
Download .txt
SYMBOL INDEX (584 symbols across 88 files)

FILE: dist/viewer.common.js
  function _classCallCheck (line 13) | function _classCallCheck(a, n) {
  function _defineProperties (line 16) | function _defineProperties(e, r) {
  function _createClass (line 22) | function _createClass(e, r, t) {
  function _defineProperty (line 27) | function _defineProperty(e, r, t) {
  function ownKeys (line 35) | function ownKeys(e, r) {
  function _objectSpread2 (line 45) | function _objectSpread2(e) {
  function _toPrimitive (line 56) | function _toPrimitive(t, r) {
  function _toPropertyKey (line 66) | function _toPropertyKey(t) {
  function _typeof (line 70) | function _typeof(o) {
  function isString (line 370) | function isString(value) {
  function isNumber (line 384) | function isNumber(value) {
  function isUndefined (line 393) | function isUndefined(value) {
  function isObject (line 402) | function isObject(value) {
  function isPlainObject (line 412) | function isPlainObject(value) {
  function isFunction (line 430) | function isFunction(value) {
  function forEach (line 440) | function forEach(data, callback) {
  function setStyle (line 487) | function setStyle(element, styles) {
  function escapeHTMLEntities (line 502) | function escapeHTMLEntities(value) {
  function hasClass (line 512) | function hasClass(element, value) {
  function addClass (line 524) | function addClass(element, value) {
  function removeClass (line 551) | function removeClass(element, value) {
  function toggleClass (line 576) | function toggleClass(element, value, added) {
  function hyphenate (line 601) | function hyphenate(value) {
  function getData (line 611) | function getData(element, name) {
  function setData (line 627) | function setData(element, name, data) {
  function removeListener (line 668) | function removeListener(element, type, listener) {
  function addListener (line 696) | function addListener(element, type, listener) {
  function dispatchEvent (line 732) | function dispatchEvent(element, type, data, options) {
  function getOffset (line 754) | function getOffset(element) {
  function getTransforms (line 767) | function getTransforms(_ref) {
  function getImageNameFromURL (line 807) | function getImageNameFromURL(url) {
  function getImageNaturalSizes (line 819) | function getImageNaturalSizes(image, options, callback) {
  function getResponsiveClass (line 856) | function getResponsiveClass(type) {
  function getMaxZoomRatio (line 874) | function getMaxZoomRatio(pointers) {
  function getPointer (line 902) | function getPointer(_ref2, endOnly) {
  function getPointersCenter (line 921) | function getPointersCenter(pointers) {
  function Viewer (line 2921) | function Viewer(element) {

FILE: dist/viewer.esm.js
  function _classCallCheck (line 11) | function _classCallCheck(a, n) {
  function _defineProperties (line 14) | function _defineProperties(e, r) {
  function _createClass (line 20) | function _createClass(e, r, t) {
  function _defineProperty (line 25) | function _defineProperty(e, r, t) {
  function ownKeys (line 33) | function ownKeys(e, r) {
  function _objectSpread2 (line 43) | function _objectSpread2(e) {
  function _toPrimitive (line 54) | function _toPrimitive(t, r) {
  function _toPropertyKey (line 64) | function _toPropertyKey(t) {
  function _typeof (line 68) | function _typeof(o) {
  function isString (line 368) | function isString(value) {
  function isNumber (line 382) | function isNumber(value) {
  function isUndefined (line 391) | function isUndefined(value) {
  function isObject (line 400) | function isObject(value) {
  function isPlainObject (line 410) | function isPlainObject(value) {
  function isFunction (line 428) | function isFunction(value) {
  function forEach (line 438) | function forEach(data, callback) {
  function setStyle (line 485) | function setStyle(element, styles) {
  function escapeHTMLEntities (line 500) | function escapeHTMLEntities(value) {
  function hasClass (line 510) | function hasClass(element, value) {
  function addClass (line 522) | function addClass(element, value) {
  function removeClass (line 549) | function removeClass(element, value) {
  function toggleClass (line 574) | function toggleClass(element, value, added) {
  function hyphenate (line 599) | function hyphenate(value) {
  function getData (line 609) | function getData(element, name) {
  function setData (line 625) | function setData(element, name, data) {
  function removeListener (line 666) | function removeListener(element, type, listener) {
  function addListener (line 694) | function addListener(element, type, listener) {
  function dispatchEvent (line 730) | function dispatchEvent(element, type, data, options) {
  function getOffset (line 752) | function getOffset(element) {
  function getTransforms (line 765) | function getTransforms(_ref) {
  function getImageNameFromURL (line 805) | function getImageNameFromURL(url) {
  function getImageNaturalSizes (line 817) | function getImageNaturalSizes(image, options, callback) {
  function getResponsiveClass (line 854) | function getResponsiveClass(type) {
  function getMaxZoomRatio (line 872) | function getMaxZoomRatio(pointers) {
  function getPointer (line 900) | function getPointer(_ref2, endOnly) {
  function getPointersCenter (line 919) | function getPointersCenter(pointers) {
  function Viewer (line 2919) | function Viewer(element) {

FILE: dist/viewer.js
  function _classCallCheck (line 17) | function _classCallCheck(a, n) {
  function _defineProperties (line 20) | function _defineProperties(e, r) {
  function _createClass (line 26) | function _createClass(e, r, t) {
  function _defineProperty (line 31) | function _defineProperty(e, r, t) {
  function ownKeys (line 39) | function ownKeys(e, r) {
  function _objectSpread2 (line 49) | function _objectSpread2(e) {
  function _toPrimitive (line 60) | function _toPrimitive(t, r) {
  function _toPropertyKey (line 70) | function _toPropertyKey(t) {
  function _typeof (line 74) | function _typeof(o) {
  function isString (line 374) | function isString(value) {
  function isNumber (line 388) | function isNumber(value) {
  function isUndefined (line 397) | function isUndefined(value) {
  function isObject (line 406) | function isObject(value) {
  function isPlainObject (line 416) | function isPlainObject(value) {
  function isFunction (line 434) | function isFunction(value) {
  function forEach (line 444) | function forEach(data, callback) {
  function setStyle (line 491) | function setStyle(element, styles) {
  function escapeHTMLEntities (line 506) | function escapeHTMLEntities(value) {
  function hasClass (line 516) | function hasClass(element, value) {
  function addClass (line 528) | function addClass(element, value) {
  function removeClass (line 555) | function removeClass(element, value) {
  function toggleClass (line 580) | function toggleClass(element, value, added) {
  function hyphenate (line 605) | function hyphenate(value) {
  function getData (line 615) | function getData(element, name) {
  function setData (line 631) | function setData(element, name, data) {
  function removeListener (line 672) | function removeListener(element, type, listener) {
  function addListener (line 700) | function addListener(element, type, listener) {
  function dispatchEvent (line 736) | function dispatchEvent(element, type, data, options) {
  function getOffset (line 758) | function getOffset(element) {
  function getTransforms (line 771) | function getTransforms(_ref) {
  function getImageNameFromURL (line 811) | function getImageNameFromURL(url) {
  function getImageNaturalSizes (line 823) | function getImageNaturalSizes(image, options, callback) {
  function getResponsiveClass (line 860) | function getResponsiveClass(type) {
  function getMaxZoomRatio (line 878) | function getMaxZoomRatio(pointers) {
  function getPointer (line 906) | function getPointer(_ref2, endOnly) {
  function getPointersCenter (line 925) | function getPointersCenter(pointers) {
  function Viewer (line 2925) | function Viewer(element) {

FILE: docs/js/main.js
  function toggleButtons (line 66) | function toggleButtons(mode) {
  function addEventListener (line 86) | function addEventListener(element, type, handler) {

FILE: docs/js/viewer.js
  function _classCallCheck (line 17) | function _classCallCheck(a, n) {
  function _defineProperties (line 20) | function _defineProperties(e, r) {
  function _createClass (line 26) | function _createClass(e, r, t) {
  function _defineProperty (line 31) | function _defineProperty(e, r, t) {
  function ownKeys (line 39) | function ownKeys(e, r) {
  function _objectSpread2 (line 49) | function _objectSpread2(e) {
  function _toPrimitive (line 60) | function _toPrimitive(t, r) {
  function _toPropertyKey (line 70) | function _toPropertyKey(t) {
  function _typeof (line 74) | function _typeof(o) {
  function isString (line 374) | function isString(value) {
  function isNumber (line 388) | function isNumber(value) {
  function isUndefined (line 397) | function isUndefined(value) {
  function isObject (line 406) | function isObject(value) {
  function isPlainObject (line 416) | function isPlainObject(value) {
  function isFunction (line 434) | function isFunction(value) {
  function forEach (line 444) | function forEach(data, callback) {
  function setStyle (line 491) | function setStyle(element, styles) {
  function escapeHTMLEntities (line 506) | function escapeHTMLEntities(value) {
  function hasClass (line 516) | function hasClass(element, value) {
  function addClass (line 528) | function addClass(element, value) {
  function removeClass (line 555) | function removeClass(element, value) {
  function toggleClass (line 580) | function toggleClass(element, value, added) {
  function hyphenate (line 605) | function hyphenate(value) {
  function getData (line 615) | function getData(element, name) {
  function setData (line 631) | function setData(element, name, data) {
  function removeListener (line 672) | function removeListener(element, type, listener) {
  function addListener (line 700) | function addListener(element, type, listener) {
  function dispatchEvent (line 736) | function dispatchEvent(element, type, data, options) {
  function getOffset (line 758) | function getOffset(element) {
  function getTransforms (line 771) | function getTransforms(_ref) {
  function getImageNameFromURL (line 811) | function getImageNameFromURL(url) {
  function getImageNaturalSizes (line 823) | function getImageNaturalSizes(image, options, callback) {
  function getResponsiveClass (line 860) | function getResponsiveClass(type) {
  function getMaxZoomRatio (line 878) | function getMaxZoomRatio(pointers) {
  function getPointer (line 906) | function getPointer(_ref2, endOnly) {
  function getPointersCenter (line 925) | function getPointersCenter(pointers) {
  function Viewer (line 2925) | function Viewer(element) {

FILE: src/js/constants.js
  constant IS_BROWSER (line 1) | const IS_BROWSER = typeof window !== 'undefined' && typeof window.docume...
  constant WINDOW (line 2) | const WINDOW = IS_BROWSER ? window : {};
  constant IS_TOUCH_DEVICE (line 3) | const IS_TOUCH_DEVICE = IS_BROWSER && WINDOW.document.documentElement ? ...
  constant HAS_POINTER_EVENT (line 4) | const HAS_POINTER_EVENT = IS_BROWSER ? 'PointerEvent' in WINDOW : false;
  constant NAMESPACE (line 5) | const NAMESPACE = 'viewer';
  constant ACTION_MOVE (line 8) | const ACTION_MOVE = 'move';
  constant ACTION_SWITCH (line 9) | const ACTION_SWITCH = 'switch';
  constant ACTION_ZOOM (line 10) | const ACTION_ZOOM = 'zoom';
  constant CLASS_ACTIVE (line 13) | const CLASS_ACTIVE = `${NAMESPACE}-active`;
  constant CLASS_CLOSE (line 14) | const CLASS_CLOSE = `${NAMESPACE}-close`;
  constant CLASS_FADE (line 15) | const CLASS_FADE = `${NAMESPACE}-fade`;
  constant CLASS_FIXED (line 16) | const CLASS_FIXED = `${NAMESPACE}-fixed`;
  constant CLASS_FULLSCREEN (line 17) | const CLASS_FULLSCREEN = `${NAMESPACE}-fullscreen`;
  constant CLASS_FULLSCREEN_EXIT (line 18) | const CLASS_FULLSCREEN_EXIT = `${NAMESPACE}-fullscreen-exit`;
  constant CLASS_HIDE (line 19) | const CLASS_HIDE = `${NAMESPACE}-hide`;
  constant CLASS_HIDE_MD_DOWN (line 20) | const CLASS_HIDE_MD_DOWN = `${NAMESPACE}-hide-md-down`;
  constant CLASS_HIDE_SM_DOWN (line 21) | const CLASS_HIDE_SM_DOWN = `${NAMESPACE}-hide-sm-down`;
  constant CLASS_HIDE_XS_DOWN (line 22) | const CLASS_HIDE_XS_DOWN = `${NAMESPACE}-hide-xs-down`;
  constant CLASS_IN (line 23) | const CLASS_IN = `${NAMESPACE}-in`;
  constant CLASS_INVISIBLE (line 24) | const CLASS_INVISIBLE = `${NAMESPACE}-invisible`;
  constant CLASS_LOADING (line 25) | const CLASS_LOADING = `${NAMESPACE}-loading`;
  constant CLASS_MOVE (line 26) | const CLASS_MOVE = `${NAMESPACE}-move`;
  constant CLASS_OPEN (line 27) | const CLASS_OPEN = `${NAMESPACE}-open`;
  constant CLASS_SHOW (line 28) | const CLASS_SHOW = `${NAMESPACE}-show`;
  constant CLASS_TRANSITION (line 29) | const CLASS_TRANSITION = `${NAMESPACE}-transition`;
  constant EVENT_CLICK (line 32) | const EVENT_CLICK = 'click';
  constant EVENT_DBLCLICK (line 33) | const EVENT_DBLCLICK = 'dblclick';
  constant EVENT_DRAG_START (line 34) | const EVENT_DRAG_START = 'dragstart';
  constant EVENT_FOCUSIN (line 35) | const EVENT_FOCUSIN = 'focusin';
  constant EVENT_KEY_DOWN (line 36) | const EVENT_KEY_DOWN = 'keydown';
  constant EVENT_LOAD (line 37) | const EVENT_LOAD = 'load';
  constant EVENT_ERROR (line 38) | const EVENT_ERROR = 'error';
  constant EVENT_TOUCH_END (line 39) | const EVENT_TOUCH_END = IS_TOUCH_DEVICE ? 'touchend touchcancel' : 'mous...
  constant EVENT_TOUCH_MOVE (line 40) | const EVENT_TOUCH_MOVE = IS_TOUCH_DEVICE ? 'touchmove' : 'mousemove';
  constant EVENT_TOUCH_START (line 41) | const EVENT_TOUCH_START = IS_TOUCH_DEVICE ? 'touchstart' : 'mousedown';
  constant EVENT_POINTER_DOWN (line 42) | const EVENT_POINTER_DOWN = HAS_POINTER_EVENT ? 'pointerdown' : EVENT_TOU...
  constant EVENT_POINTER_MOVE (line 43) | const EVENT_POINTER_MOVE = HAS_POINTER_EVENT ? 'pointermove' : EVENT_TOU...
  constant EVENT_POINTER_UP (line 44) | const EVENT_POINTER_UP = HAS_POINTER_EVENT ? 'pointerup pointercancel' :...
  constant EVENT_RESIZE (line 45) | const EVENT_RESIZE = 'resize';
  constant EVENT_TRANSITION_END (line 46) | const EVENT_TRANSITION_END = 'transitionend';
  constant EVENT_WHEEL (line 47) | const EVENT_WHEEL = 'wheel';
  constant EVENT_READY (line 50) | const EVENT_READY = 'ready';
  constant EVENT_SHOW (line 51) | const EVENT_SHOW = 'show';
  constant EVENT_SHOWN (line 52) | const EVENT_SHOWN = 'shown';
  constant EVENT_HIDE (line 53) | const EVENT_HIDE = 'hide';
  constant EVENT_HIDDEN (line 54) | const EVENT_HIDDEN = 'hidden';
  constant EVENT_VIEW (line 55) | const EVENT_VIEW = 'view';
  constant EVENT_VIEWED (line 56) | const EVENT_VIEWED = 'viewed';
  constant EVENT_MOVE (line 57) | const EVENT_MOVE = 'move';
  constant EVENT_MOVED (line 58) | const EVENT_MOVED = 'moved';
  constant EVENT_ROTATE (line 59) | const EVENT_ROTATE = 'rotate';
  constant EVENT_ROTATED (line 60) | const EVENT_ROTATED = 'rotated';
  constant EVENT_SCALE (line 61) | const EVENT_SCALE = 'scale';
  constant EVENT_SCALED (line 62) | const EVENT_SCALED = 'scaled';
  constant EVENT_ZOOM (line 63) | const EVENT_ZOOM = 'zoom';
  constant EVENT_ZOOMED (line 64) | const EVENT_ZOOMED = 'zoomed';
  constant EVENT_PLAY (line 65) | const EVENT_PLAY = 'play';
  constant EVENT_STOP (line 66) | const EVENT_STOP = 'stop';
  constant DATA_ACTION (line 69) | const DATA_ACTION = `${NAMESPACE}Action`;
  constant REGEXP_SPACES (line 72) | const REGEXP_SPACES = /\s\s*/;
  constant BUTTONS (line 75) | const BUTTONS = [

FILE: src/js/events.js
  method bind (line 18) | bind() {
  method unbind (line 42) | unbind() {

FILE: src/js/handlers.js
  method click (line 34) | click(event) {
  method dblclick (line 126) | dblclick(event) {
  method load (line 140) | load() {
  method loadImage (line 194) | loadImage(event) {
  method keydown (line 228) | keydown(event) {
  method dragstart (line 330) | dragstart(event) {
  method pointerdown (line 336) | pointerdown(event) {
  method pointermove (line 390) | pointermove(event) {
  method pointerup (line 410) | pointerup(event) {
  method resize (line 479) | resize() {
  method wheel (line 521) | wheel(event) {

FILE: src/js/methods.js
  method show (line 58) | show(immediate = false) {
  method hide (line 131) | hide(immediate = false) {
  method view (line 216) | view(index = this.options.initialViewIndex) {
  method prev (line 387) | prev(loop = false) {
  method next (line 404) | next(loop = false) {
  method move (line 422) | move(x, y = x) {
  method moveTo (line 440) | moveTo(x, y = x, _originalEvent = null) {
  method rotate (line 515) | rotate(degree) {
  method rotateTo (line 526) | rotateTo(degree) {
  method scaleX (line 575) | scaleX(scaleX) {
  method scaleY (line 586) | scaleY(scaleY) {
  method scale (line 598) | scale(scaleX, scaleY = scaleX) {
  method zoom (line 672) | zoom(ratio, showTooltip = false, pivot = null, _originalEvent = null) {
  method zoomTo (line 702) | zoomTo(ratio, showTooltip = false, pivot = null, _originalEvent = null, ...
  method play (line 828) | play(fullscreen = false) {
  method stop (line 913) | stop() {
  method full (line 946) | full() {
  method exit (line 1004) | exit() {
  method tooltip (line 1061) | tooltip() {
  method toggle (line 1122) | toggle(_originalEvent = null) {
  method reset (line 1133) | reset() {
  method update (line 1143) | update() {
  method destroy (line 1230) | destroy() {

FILE: src/js/others.js
  method getImageURL (line 25) | getImageURL(image) {
  method enforceFocus (line 39) | enforceFocus() {
  method clearEnforceFocus (line 62) | clearEnforceFocus() {
  method open (line 69) | open() {
  method close (line 79) | close() {
  method shown (line 89) | shown() {
  method hidden (line 118) | hidden() {
  method requestFullscreen (line 152) | requestFullscreen(options) {
  method exitFullscreen (line 181) | exitFullscreen() {
  method change (line 203) | change(event) {
  method isSwitchable (line 258) | isSwitchable() {

FILE: src/js/render.js
  method render (line 26) | render() {
  method initBody (line 33) | initBody() {
  method initContainer (line 43) | initContainer() {
  method initViewer (line 50) | initViewer() {
  method renderViewer (line 70) | renderViewer() {
  method initList (line 76) | initList() {
  method renderList (line 164) | renderList() {
  method resetList (line 185) | resetList() {
  method initImage (line 197) | initImage(done) {
  method renderImage (line 268) | renderImage(done) {
  method resetImage (line 304) | resetImage() {

FILE: src/js/utilities.js
  function isString (line 15) | function isString(value) {
  function isNumber (line 29) | function isNumber(value) {
  function isUndefined (line 38) | function isUndefined(value) {
  function isObject (line 47) | function isObject(value) {
  function isPlainObject (line 58) | function isPlainObject(value) {
  function isFunction (line 78) | function isFunction(value) {
  function forEach (line 88) | function forEach(data, callback) {
  constant REGEXP_SUFFIX (line 129) | const REGEXP_SUFFIX = /^(?:width|height|left|top|marginLeft|marginTop)$/;
  function setStyle (line 136) | function setStyle(element, styles) {
  function escapeHTMLEntities (line 153) | function escapeHTMLEntities(value) {
  function hasClass (line 168) | function hasClass(element, value) {
  function addClass (line 183) | function addClass(element, value) {
  function removeClass (line 214) | function removeClass(element, value) {
  function toggleClass (line 242) | function toggleClass(element, value, added) {
  constant REGEXP_HYPHENATE (line 262) | const REGEXP_HYPHENATE = /([a-z\d])([A-Z])/g;
  function hyphenate (line 269) | function hyphenate(value) {
  function getData (line 279) | function getData(element, name) {
  function setData (line 297) | function setData(element, name, data) {
  function removeData (line 312) | function removeData(element, name) {
  method get (line 338) | get() {
  method set (line 348) | set(value) {
  function removeListener (line 367) | function removeListener(element, type, listener, options = {}) {
  function addListener (line 399) | function addListener(element, type, listener, options = {}) {
  function dispatchEvent (line 436) | function dispatchEvent(element, type, data, options) {
  function getOffset (line 460) | function getOffset(element) {
  function getTransforms (line 474) | function getTransforms({
  function getImageNameFromURL (line 521) | function getImageNameFromURL(url) {
  constant IS_SAFARI (line 525) | const IS_SAFARI = WINDOW.navigator && /Version\/\d+(\.\d+)+?\s+Safari/i....
  function getImageNaturalSizes (line 534) | function getImageNaturalSizes(image, options, callback) {
  function getResponsiveClass (line 588) | function getResponsiveClass(type) {
  function getMaxZoomRatio (line 609) | function getMaxZoomRatio(pointers) {
  function getPointer (line 640) | function getPointer({ pageX, pageY }, endOnly) {
  function getPointersCenter (line 659) | function getPointersCenter(pointers) {

FILE: src/js/viewer.js
  class Viewer (line 50) | class Viewer {
    method constructor (line 56) | constructor(element, options = {}) {
    method init (line 92) | init() {
    method build (line 193) | build() {
    method noConflict (line 385) | static noConflict() {
    method setDefaults (line 394) | static setDefaults(options) {

FILE: test/specs/events/hidden.spec.js
  method shown (line 11) | shown() {
  method ready (line 29) | ready() {
  method shown (line 47) | shown() {

FILE: test/specs/events/hide.spec.js
  method shown (line 10) | shown() {
  method shown (line 41) | shown() {
  method viewed (line 59) | viewed() {

FILE: test/specs/methods/destroy.spec.js
  method ready (line 46) | ready() {
  method ready (line 61) | ready() {
  method show (line 73) | show() {
  method shown (line 86) | shown() {
  method shown (line 100) | shown() {
  method hide (line 104) | hide() {
  method shown (line 118) | shown() {
  method hidden (line 122) | hidden() {
  method view (line 136) | view() {
  method viewed (line 150) | viewed() {

FILE: test/specs/methods/exit.spec.js
  method ready (line 7) | ready() {
  method shown (line 27) | shown() {

FILE: test/specs/methods/full.spec.js
  method ready (line 7) | ready() {
  method shown (line 24) | shown() {

FILE: test/specs/methods/hide.spec.js
  method shown (line 5) | shown() {
  method hidden (line 10) | hidden() {
  method shown (line 22) | shown() {
  method ready (line 38) | ready() {

FILE: test/specs/methods/move.spec.js
  method viewed (line 7) | viewed() {
  method viewed (line 28) | viewed() {

FILE: test/specs/methods/moveTo.spec.js
  method viewed (line 7) | viewed() {
  method viewed (line 27) | viewed() {

FILE: test/specs/methods/next.spec.js
  method viewed (line 5) | viewed(event) {
  method viewed (line 27) | viewed(event) {
  method viewed (line 52) | viewed(event) {
  method viewed (line 76) | viewed(event) {

FILE: test/specs/methods/play.spec.js
  method viewed (line 5) | viewed() {
  method viewed (line 24) | viewed() {

FILE: test/specs/methods/prev.spec.js
  method viewed (line 7) | viewed(event) {
  method viewed (line 28) | viewed(event) {
  method viewed (line 53) | viewed(event) {
  method viewed (line 77) | viewed(event) {

FILE: test/specs/methods/reset.spec.js
  method viewed (line 7) | viewed() {

FILE: test/specs/methods/rotate.spec.js
  method viewed (line 7) | viewed() {
  method viewed (line 28) | viewed() {

FILE: test/specs/methods/rotateTo.spec.js
  method viewed (line 7) | viewed() {
  method viewed (line 28) | viewed() {

FILE: test/specs/methods/scale.spec.js
  method viewed (line 7) | viewed() {
  method viewed (line 29) | viewed() {

FILE: test/specs/methods/scaleX.spec.js
  method viewed (line 7) | viewed() {
  method viewed (line 26) | viewed() {

FILE: test/specs/methods/scaleY.spec.js
  method viewed (line 7) | viewed() {
  method viewed (line 26) | viewed() {

FILE: test/specs/methods/setDefaults.spec.js
  method shown (line 13) | shown() {

FILE: test/specs/methods/show.spec.js
  method shown (line 5) | shown() {
  method viewed (line 19) | viewed(event) {

FILE: test/specs/methods/stop.spec.js
  method viewed (line 5) | viewed() {
  method viewed (line 25) | viewed() {

FILE: test/specs/methods/toggle.spec.js
  method viewed (line 5) | viewed() {
  method viewed (line 26) | viewed() {

FILE: test/specs/methods/tooltip.spec.js
  method viewed (line 7) | viewed() {
  method viewed (line 22) | viewed() {

FILE: test/specs/methods/update.spec.js
  method viewed (line 8) | viewed() {
  method viewed (line 30) | viewed(event) {
  method viewed (line 47) | viewed(event) {

FILE: test/specs/methods/view.spec.js
  method viewed (line 5) | viewed(event) {
  method viewed (line 18) | viewed() {
  method viewed (line 33) | viewed() {

FILE: test/specs/methods/zoom.spec.js
  method viewed (line 7) | viewed() {
  method viewed (line 27) | viewed() {

FILE: test/specs/methods/zoomTo.spec.js
  method viewed (line 7) | viewed() {
  method viewed (line 27) | viewed() {

FILE: test/specs/options/backdrop.spec.js
  method shown (line 5) | shown() {
  method hidden (line 10) | hidden() {
  method shown (line 25) | shown() {
  method hide (line 33) | hide() {
  method shown (line 50) | shown() {
  method hide (line 58) | hide() {

FILE: test/specs/options/button.spec.js
  method shown (line 5) | shown() {
  method hidden (line 13) | hidden() {
  method shown (line 27) | shown() {
  method ready (line 43) | ready() {
  method ready (line 64) | ready() {

FILE: test/specs/options/className.spec.js
  method ready (line 14) | ready(event) {
  method ready (line 29) | ready(event) {

FILE: test/specs/options/container.spec.js
  method ready (line 5) | ready(event) {
  method ready (line 25) | ready(event) {
  method ready (line 41) | ready(event) {

FILE: test/specs/options/filter.spec.js
  method filter (line 14) | filter(image) {
  method ready (line 20) | ready() {
  method filter (line 34) | filter() {
  method ready (line 38) | ready() {

FILE: test/specs/options/focus.spec.js
  method viewed (line 8) | viewed() {
  method viewed (line 24) | viewed() {

FILE: test/specs/options/hidden.spec.js
  method shown (line 12) | shown() {
  method hidden (line 16) | hidden(event) {
  method ready (line 30) | ready() {
  method hidden (line 35) | hidden() {

FILE: test/specs/options/hide.spec.js
  method shown (line 12) | shown() {
  method hide (line 16) | hide(event) {
  method hidden (line 20) | hidden() {
  method shown (line 32) | shown() {
  method hide (line 36) | hide(event) {
  method hidden (line 46) | hidden() {
  method viewed (line 63) | viewed() {
  method hide (line 68) | hide() {

FILE: test/specs/options/inheritedAttributes.spec.js
  method viewed (line 10) | viewed(event) {
  method viewed (line 32) | viewed(event) {

FILE: test/specs/options/initialViewIndex.spec.js
  method viewed (line 14) | viewed(event) {
  method viewed (line 30) | viewed(event) {

FILE: test/specs/options/inline.spec.js
  method shown (line 5) | shown() {
  method ready (line 22) | ready() {
  method ready (line 38) | ready(event) {
  method view (line 42) | view(event) {
  method viewed (line 46) | viewed(event) {
  method ready (line 58) | ready() {
  method show (line 62) | show() {
  method shown (line 66) | shown() {
  method hide (line 71) | hide() {
  method hidden (line 75) | hidden() {

FILE: test/specs/options/interval.spec.js
  method viewed (line 16) | viewed() {

FILE: test/specs/options/keyboard.spec.js
  method viewed (line 14) | viewed() {
  method viewed (line 33) | viewed() {
  method hide (line 40) | hide() {
  method viewed (line 53) | viewed() {
  method viewed (line 69) | viewed() {
  method viewed (line 88) | viewed() {
  method viewed (line 107) | viewed(event) {
  method viewed (line 133) | viewed(event) {
  method viewed (line 159) | viewed() {
  method viewed (line 178) | viewed() {
  method viewed (line 197) | viewed() {
  method viewed (line 220) | viewed() {

FILE: test/specs/options/loading.spec.js
  method view (line 7) | view() {
  method view (line 27) | view() {

FILE: test/specs/options/loop.spec.js
  method viewed (line 7) | viewed(event) {
  method viewed (line 31) | viewed(event) {

FILE: test/specs/options/maxZoomRatio.spec.js
  method viewed (line 16) | viewed() {

FILE: test/specs/options/minHeight.spec.js
  method viewed (line 21) | viewed() {

FILE: test/specs/options/minWidth.spec.js
  method viewed (line 21) | viewed() {

FILE: test/specs/options/minZoomRatio.spec.js
  method viewed (line 16) | viewed() {

FILE: test/specs/options/movable.spec.js
  method viewed (line 7) | viewed() {
  method viewed (line 27) | viewed() {

FILE: test/specs/options/move.spec.js
  method viewed (line 12) | viewed() {
  method move (line 16) | move(event) {
  method viewed (line 30) | viewed() {
  method move (line 34) | move(event) {
  method viewed (line 57) | viewed() {
  method move (line 61) | move(event) {
  method moved (line 69) | moved() {
  method viewed (line 81) | viewed() {
  method move (line 85) | move(event) {

FILE: test/specs/options/moved.spec.js
  method viewed (line 12) | viewed() {
  method moved (line 16) | moved(event) {
  method viewed (line 29) | viewed() {
  method moved (line 33) | moved(event) {
  method viewed (line 56) | viewed() {
  method moved (line 60) | moved(event) {

FILE: test/specs/options/navbar.spec.js
  method ready (line 7) | ready() {
  method ready (line 22) | ready() {

FILE: test/specs/options/play.spec.js
  method viewed (line 12) | viewed() {
  method play (line 19) | play(event) {
  method viewed (line 30) | viewed() {
  method play (line 37) | play(event) {

FILE: test/specs/options/ready.spec.js
  method ready (line 12) | ready(event) {
  method ready (line 28) | ready(event) {

FILE: test/specs/options/rotatable.spec.js
  method viewed (line 7) | viewed() {
  method viewed (line 23) | viewed() {

FILE: test/specs/options/rotate.spec.js
  method viewed (line 12) | viewed() {
  method rotate (line 16) | rotate(event) {
  method viewed (line 30) | viewed() {
  method rotate (line 34) | rotate(event) {
  method viewed (line 54) | viewed() {
  method rotate (line 58) | rotate(event) {
  method rotated (line 66) | rotated() {
  method viewed (line 78) | viewed() {
  method rotate (line 82) | rotate(event) {

FILE: test/specs/options/rotated.spec.js
  method viewed (line 12) | viewed() {
  method rotated (line 16) | rotated(event) {
  method viewed (line 29) | viewed() {
  method rotated (line 33) | rotated(event) {
  method viewed (line 53) | viewed() {
  method rotated (line 57) | rotated(event) {

FILE: test/specs/options/scalable.spec.js
  method viewed (line 7) | viewed() {
  method viewed (line 27) | viewed() {

FILE: test/specs/options/scale.spec.js
  method viewed (line 12) | viewed() {
  method scale (line 16) | scale(event) {
  method viewed (line 30) | viewed() {
  method scale (line 34) | scale(event) {
  method viewed (line 56) | viewed() {
  method scale (line 60) | scale(event) {
  method scaled (line 68) | scaled() {
  method viewed (line 80) | viewed() {
  method scale (line 84) | scale(event) {

FILE: test/specs/options/scaled.spec.js
  method viewed (line 12) | viewed() {
  method scaled (line 16) | scaled(event) {
  method viewed (line 29) | viewed() {
  method scaled (line 33) | scaled(event) {
  method viewed (line 55) | viewed() {
  method scaled (line 59) | scaled(event) {

FILE: test/specs/options/show.spec.js
  method show (line 12) | show(event) {
  method shown (line 16) | shown() {
  method show (line 28) | show(event) {
  method shown (line 33) | shown() {
  method ready (line 47) | ready() {
  method show (line 51) | show() {

FILE: test/specs/options/shown.spec.js
  method shown (line 12) | shown(event) {
  method ready (line 28) | ready() {
  method shown (line 32) | shown() {

FILE: test/specs/options/stop.spec.js
  method viewed (line 12) | viewed() {
  method stop (line 22) | stop(event) {
  method viewed (line 33) | viewed() {
  method stop (line 43) | stop(event) {

FILE: test/specs/options/title.spec.js
  method ready (line 7) | ready() {
  method ready (line 22) | ready() {
  method title (line 36) | title(img) {
  method ready (line 40) | ready() {
  method ready (line 55) | ready() {

FILE: test/specs/options/toggleOnDblclick.spec.js
  method viewed (line 7) | viewed() {
  method viewed (line 25) | viewed() {

FILE: test/specs/options/toolbar.spec.js
  method ready (line 7) | ready() {
  method ready (line 22) | ready() {

FILE: test/specs/options/tooltip.spec.js
  method viewed (line 7) | viewed() {
  method viewed (line 23) | viewed() {

FILE: test/specs/options/transition.spec.js
  method viewed (line 5) | viewed() {
  method viewed (line 23) | viewed() {

FILE: test/specs/options/url.spec.js
  method viewed (line 18) | viewed() {
  method url (line 32) | url(img) {
  method viewed (line 38) | viewed() {

FILE: test/specs/options/view.spec.js
  method view (line 12) | view(event) {
  method viewed (line 16) | viewed() {
  method view (line 28) | view(event) {
  method viewed (line 35) | viewed() {
  method view (line 49) | view(event) {
  method viewed (line 57) | viewed() {
  method view (line 69) | view(event) {

FILE: test/specs/options/viewed.spec.js
  method viewed (line 12) | viewed(event) {
  method viewed (line 25) | viewed(event) {
  method viewed (line 44) | viewed(event) {

FILE: test/specs/options/zIndex.spec.js
  method viewed (line 15) | viewed() {
  method viewed (line 33) | viewed() {

FILE: test/specs/options/zIndexInline.spec.js
  method viewed (line 18) | viewed() {
  method viewed (line 33) | viewed() {

FILE: test/specs/options/zoom.spec.js
  method viewed (line 12) | viewed() {
  method zoom (line 16) | zoom(event) {
  method viewed (line 30) | viewed() {
  method zoom (line 34) | zoom(event) {
  method viewed (line 55) | viewed() {
  method zoom (line 59) | zoom(event) {
  method zoomed (line 67) | zoomed() {
  method viewed (line 79) | viewed() {
  method zoom (line 83) | zoom(event) {

FILE: test/specs/options/zoomRatio.spec.js
  method viewed (line 15) | viewed() {

FILE: test/specs/options/zoomable.spec.js
  method viewed (line 15) | viewed() {

FILE: test/specs/options/zoomed.spec.js
  method viewed (line 12) | viewed() {
  method zoomed (line 16) | zoomed(event) {
  method viewed (line 29) | viewed() {
  method zoomed (line 33) | zoomed(event) {
  method viewed (line 54) | viewed() {
  method zoomed (line 58) | zoomed(event) {

FILE: types/index.d.ts
  type Visibility (line 2) | type Visibility = 0 | 1 | 2 | 3 | 4;
  type ToolbarButtonSize (line 3) | type ToolbarButtonSize = 'small' | 'medium' | 'large';
  type ToolbarOption (line 4) | type ToolbarOption = boolean | Visibility | ToolbarButtonSize | Function...
  type ToolbarButtonOptions (line 6) | interface ToolbarButtonOptions {
  type ToolbarOptions (line 12) | interface ToolbarOptions {
  type Pivot (line 27) | interface Pivot {
  type MoveEventData (line 32) | interface MoveEventData {
  type MoveEvent (line 40) | interface MoveEvent extends CustomEvent {
  type MovedEvent (line 44) | interface MovedEvent extends CustomEvent {
  type RotateEventData (line 48) | interface RotateEventData {
  type RotateEvent (line 53) | interface RotateEvent extends CustomEvent {
  type RotatedEvent (line 57) | interface RotatedEvent extends CustomEvent {
  type ScaleEventData (line 61) | interface ScaleEventData {
  type ScaleEvent (line 68) | interface ScaleEvent extends CustomEvent {
  type ScaledEvent (line 72) | interface ScaledEvent extends CustomEvent {
  type ZoomEventData (line 76) | interface ZoomEventData {
  type ZoomEvent (line 82) | interface ZoomEvent extends CustomEvent {
  type ZoomedEvent (line 86) | interface ZoomedEvent extends CustomEvent {
  type Options (line 90) | interface Options {
  class Viewer (line 147) | class Viewer {
Condensed preview — 154 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (803K chars).
[
  {
    "path": ".babelrc",
    "chars": 185,
    "preview": "{\n  \"presets\": [\n    [\n      \"@babel/preset-env\",\n      {\n        \"modules\": false\n      }\n    ]\n  ],\n  \"env\": {\n    \"te"
  },
  {
    "path": ".browserslistrc",
    "chars": 17,
    "preview": "defaults\nie >= 9\n"
  },
  {
    "path": ".editorconfig",
    "chars": 147,
    "preview": "root = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\nindent_size = 2\nindent_style = space\ninsert_final_newline = true\ntrim_"
  },
  {
    "path": ".eslintignore",
    "chars": 48,
    "preview": "*.local*\n.husky\ncoverage\ndist\ndocs\nnode_modules\n"
  },
  {
    "path": ".eslintrc.js",
    "chars": 598,
    "preview": "module.exports = {\n  root: true,\n  extends: 'airbnb-base',\n  env: {\n    browser: true,\n  },\n  plugins: [\n    'import',\n "
  },
  {
    "path": ".gitattributes",
    "chars": 66,
    "preview": "# Auto detect text files and perform LF normalization\n* text=auto\n"
  },
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "chars": 1412,
    "preview": "# Contributor Code of Conduct\n\nAs contributors and maintainers of this project, we pledge to respect all people who cont"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "chars": 9874,
    "preview": "# Contributing to Viewer.js\n\n> Based on [Angular's contributing guidelines](https://github.com/angular/angular/blob/mast"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "chars": 3021,
    "preview": "name: \"\\U0001F41E Bug report\"\ndescription: Create a report to help us improve\nbody:\n  - type: markdown\n    attributes:\n "
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 224,
    "preview": "blank_issues_enabled: false\ncontact_links:\n  - name: Questions & Discussions\n    url: https://github.com/fengyuanchen/vi"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 595,
    "preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your fea"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "chars": 1259,
    "preview": "<!-- Please don't delete this template -->\n\n<!-- PULL REQUEST TEMPLATE -->\n<!-- (Update \"[ ]\" to \"[x]\" to check a box) -"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 453,
    "preview": "name: CI\n\non:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n\njobs:\n  build:\n    runs-on: ubuntu-late"
  },
  {
    "path": ".gitignore",
    "chars": 53,
    "preview": "*.local*\n*.log\n*.map\n.DS_Store\ncoverage\nnode_modules\n"
  },
  {
    "path": ".husky/commit-msg",
    "chars": 67,
    "preview": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\nnpx commitlint --edit $1\n"
  },
  {
    "path": ".husky/pre-commit",
    "chars": 58,
    "preview": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\nnpx lint-staged\n"
  },
  {
    "path": ".stylelintignore",
    "chars": 63,
    "preview": "*.local*\n.husky\ncoverage\ndist\ndocs/css/viewer.css\nnode_modules\n"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 10123,
    "preview": "# 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- Fi"
  },
  {
    "path": "LICENSE",
    "chars": 1084,
    "preview": "The MIT License (MIT)\n\nCopyright 2015-present Chen Fengyuan\n\nPermission is hereby granted, free of charge, to any person"
  },
  {
    "path": "README.md",
    "chars": 24925,
    "preview": "# Viewer.js\n\n[![Downloads](https://img.shields.io/npm/dm/viewerjs.svg)](https://www.npmjs.com/package/viewerjs) [![Versi"
  },
  {
    "path": "commitlint.config.js",
    "chars": 79,
    "preview": "module.exports = {\n  extends: [\n    '@commitlint/config-conventional',\n  ],\n};\n"
  },
  {
    "path": "dist/viewer.common.js",
    "chars": 99207,
    "preview": "/*!\n * Viewer.js v1.11.7\n * https://fengyuanchen.github.io/viewerjs\n *\n * Copyright 2015-present Chen Fengyuan\n * Releas"
  },
  {
    "path": "dist/viewer.css",
    "chars": 9351,
    "preview": "/*!\n * Viewer.js v1.11.7\n * https://fengyuanchen.github.io/viewerjs\n *\n * Copyright 2015-present Chen Fengyuan\n * Releas"
  },
  {
    "path": "dist/viewer.esm.js",
    "chars": 99197,
    "preview": "/*!\n * Viewer.js v1.11.7\n * https://fengyuanchen.github.io/viewerjs\n *\n * Copyright 2015-present Chen Fengyuan\n * Releas"
  },
  {
    "path": "dist/viewer.js",
    "chars": 105757,
    "preview": "/*!\n * Viewer.js v1.11.7\n * https://fengyuanchen.github.io/viewerjs\n *\n * Copyright 2015-present Chen Fengyuan\n * Releas"
  },
  {
    "path": "docs/css/main.css",
    "chars": 1884,
    "preview": ".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 "
  },
  {
    "path": "docs/css/viewer.css",
    "chars": 9351,
    "preview": "/*!\n * Viewer.js v1.11.7\n * https://fengyuanchen.github.io/viewerjs\n *\n * Copyright 2015-present Chen Fengyuan\n * Releas"
  },
  {
    "path": "docs/examples/custom-title.html",
    "chars": 2660,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n  <meta http-equiv=\"x-ua-compatible\" content=\"ie=edge\">"
  },
  {
    "path": "docs/examples/custom-toolbar.html",
    "chars": 3414,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n  <meta http-equiv=\"x-ua-compatible\" content=\"ie=edge\">"
  },
  {
    "path": "docs/examples/dynamic-viewer.html",
    "chars": 1321,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n  <meta http-equiv=\"x-ua-compatible\" content=\"ie=edge\">"
  },
  {
    "path": "docs/examples/moving-range-limit.html",
    "chars": 4909,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n  <meta http-equiv=\"x-ua-compatible\" content=\"ie=edge\">"
  },
  {
    "path": "docs/examples/viewer-in-modal.html",
    "chars": 3901,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n  <meta http-equiv=\"x-ua-compatible\" content=\"ie=edge\">"
  },
  {
    "path": "docs/index.html",
    "chars": 19140,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n  <meta http-equiv=\"x-ua-compatible\" content=\"ie=edge\">"
  },
  {
    "path": "docs/js/main.js",
    "chars": 3398,
    "preview": "window.onload = function () {\n  'use strict';\n\n  var Viewer = window.Viewer;\n  var console = window.console || { log: fu"
  },
  {
    "path": "docs/js/viewer.js",
    "chars": 105757,
    "preview": "/*!\n * Viewer.js v1.11.7\n * https://fengyuanchen.github.io/viewerjs\n *\n * Copyright 2015-present Chen Fengyuan\n * Releas"
  },
  {
    "path": "karma.conf.js",
    "chars": 1067,
    "preview": "const puppeteer = require('puppeteer');\nconst rollupConfig = require('./rollup.config');\n\nprocess.env.CHROME_BIN = puppe"
  },
  {
    "path": "lint-staged.config.js",
    "chars": 85,
    "preview": "module.exports = {\n  '*.js': 'eslint --fix',\n  '*.{css,scss}': 'stylelint --fix',\n};\n"
  },
  {
    "path": "package.json",
    "chars": 3032,
    "preview": "{\n  \"name\": \"viewerjs\",\n  \"description\": \"JavaScript image viewer.\",\n  \"version\": \"1.11.7\",\n  \"main\": \"dist/viewer.commo"
  },
  {
    "path": "postcss.config.js",
    "chars": 391,
    "preview": "const rollupConfig = require('./rollup.config');\n\nmodule.exports = {\n  plugins: {\n    'postcss-import': {},\n    'postcss"
  },
  {
    "path": "rollup.config.js",
    "chars": 893,
    "preview": "const { babel } = require('@rollup/plugin-babel');\nconst changeCase = require('change-case');\nconst createBanner = requi"
  },
  {
    "path": "src/css/viewer.css",
    "chars": 7000,
    "preview": ".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-"
  },
  {
    "path": "src/css/viewer.scss",
    "chars": 7454,
    "preview": ".viewer {\n  &-zoom-in,\n  &-zoom-out,\n  &-one-to-one,\n  &-reset,\n  &-prev,\n  &-play,\n  &-next,\n  &-rotate-left,\n  &-rotat"
  },
  {
    "path": "src/index.css",
    "chars": 28,
    "preview": "@import \"./css/viewer.css\";\n"
  },
  {
    "path": "src/index.js",
    "chars": 58,
    "preview": "import Viewer from './js/viewer';\n\nexport default Viewer;\n"
  },
  {
    "path": "src/index.scss",
    "chars": 24,
    "preview": "@import \"./css/viewer\";\n"
  },
  {
    "path": "src/js/constants.js",
    "chars": 3319,
    "preview": "export const IS_BROWSER = typeof window !== 'undefined' && typeof window.document !== 'undefined';\nexport const WINDOW ="
  },
  {
    "path": "src/js/defaults.js",
    "chars": 4514,
    "preview": "export default {\n  /**\n   * Enable a modal backdrop, specify `static` for a backdrop\n   * which doesn't close the modal "
  },
  {
    "path": "src/js/events.js",
    "chars": 2184,
    "preview": "import {\n  EVENT_CLICK,\n  EVENT_DBLCLICK,\n  EVENT_DRAG_START,\n  EVENT_KEY_DOWN,\n  EVENT_POINTER_DOWN,\n  EVENT_POINTER_MO"
  },
  {
    "path": "src/js/handlers.js",
    "chars": 12446,
    "preview": "import {\n  ACTION_MOVE,\n  ACTION_SWITCH,\n  ACTION_ZOOM,\n  CLASS_INVISIBLE,\n  CLASS_LOADING,\n  CLASS_MOVE,\n  CLASS_TRANSI"
  },
  {
    "path": "src/js/methods.js",
    "chars": 30864,
    "preview": "import {\n  CLASS_ACTIVE,\n  CLASS_FADE,\n  CLASS_FIXED,\n  CLASS_FULLSCREEN_EXIT,\n  CLASS_HIDE,\n  CLASS_IN,\n  CLASS_INVISIB"
  },
  {
    "path": "src/js/others.js",
    "chars": 6311,
    "preview": "import {\n  ACTION_MOVE,\n  ACTION_SWITCH,\n  ACTION_ZOOM,\n  CLASS_HIDE,\n  CLASS_OPEN,\n  EVENT_FOCUSIN,\n  EVENT_HIDDEN,\n  E"
  },
  {
    "path": "src/js/render.js",
    "chars": 7688,
    "preview": "import {\n  CLASS_LOADING,\n  CLASS_TRANSITION,\n  EVENT_ERROR,\n  EVENT_LOAD,\n  EVENT_TRANSITION_END,\n  EVENT_VIEWED,\n} fro"
  },
  {
    "path": "src/js/template.js",
    "chars": 596,
    "preview": "export default (\n  '<div class=\"viewer-container\" tabindex=\"-1\" touch-action=\"none\">'\n    + '<div class=\"viewer-canvas\">"
  },
  {
    "path": "src/js/utilities.js",
    "chars": 16985,
    "preview": "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} fr"
  },
  {
    "path": "src/js/viewer.js",
    "chars": 10455,
    "preview": "import DEFAULTS from './defaults';\nimport TEMPLATE from './template';\nimport render from './render';\nimport events from "
  },
  {
    "path": "stylelint.config.js",
    "chars": 207,
    "preview": "module.exports = {\n  extends: 'stylelint-config-standard',\n  plugins: [\n    'stylelint-order',\n  ],\n  rules: {\n    'no-d"
  },
  {
    "path": "test/helpers.js",
    "chars": 1931,
    "preview": "// XXX: Disable the `focus` option globally to avoid side effects.\nwindow.Viewer.setDefaults({\n  focus: false,\n});\n\nwind"
  },
  {
    "path": "test/specs/Viewer.spec.js",
    "chars": 328,
    "preview": "describe('Viewer', () => {\n  it('should be a class (function)', () => {\n    expect(Viewer).to.be.a('function');\n  });\n\n "
  },
  {
    "path": "test/specs/events/hidden.spec.js",
    "chars": 1134,
    "preview": "describe('hidden (event)', () => {\n  it('should trigger the `hidden` event', (done) => {\n    const image = window.create"
  },
  {
    "path": "test/specs/events/hide.spec.js",
    "chars": 1279,
    "preview": "describe('hide (event)', () => {\n  it('should trigger the `hide` event', (done) => {\n    const image = window.createImag"
  },
  {
    "path": "test/specs/events/move.spec.js",
    "chars": 2056,
    "preview": "describe('move (event)', () => {\n  it('should trigger the `move` event', (done) => {\n    const image = window.createImag"
  },
  {
    "path": "test/specs/events/moved.spec.js",
    "chars": 1997,
    "preview": "describe('moved (event)', () => {\n  it('should trigger the `moved` event', (done) => {\n    const image = window.createIm"
  },
  {
    "path": "test/specs/events/play.spec.js",
    "chars": 936,
    "preview": "describe('play (event)', () => {\n  it('should trigger the `play` event', (done) => {\n    const image = window.createImag"
  },
  {
    "path": "test/specs/events/ready.spec.js",
    "chars": 872,
    "preview": "describe('ready (event)', () => {\n  it('should not trigger the `ready` event by default', () => {\n    const image = wind"
  },
  {
    "path": "test/specs/events/rotate.spec.js",
    "chars": 1946,
    "preview": "describe('rotate (event)', () => {\n  it('should trigger the `rotate` event', (done) => {\n    const image = window.create"
  },
  {
    "path": "test/specs/events/rotated.spec.js",
    "chars": 1885,
    "preview": "describe('rotated (event)', () => {\n  it('should trigger the `rotated` event', (done) => {\n    const image = window.crea"
  },
  {
    "path": "test/specs/events/scale.spec.js",
    "chars": 2043,
    "preview": "describe('scale (event)', () => {\n  it('should trigger the `scale` event', (done) => {\n    const image = window.createIm"
  },
  {
    "path": "test/specs/events/scaled.spec.js",
    "chars": 1983,
    "preview": "describe('scaled (event)', () => {\n  it('should trigger the `scaled` event', (done) => {\n    const image = window.create"
  },
  {
    "path": "test/specs/events/show.spec.js",
    "chars": 947,
    "preview": "describe('show (event)', () => {\n  it('should trigger the `show` event', (done) => {\n    const image = window.createImag"
  },
  {
    "path": "test/specs/events/shown.spec.js",
    "chars": 622,
    "preview": "describe('shown (event)', () => {\n  it('should trigger the `shown` event', (done) => {\n    const image = window.createIm"
  },
  {
    "path": "test/specs/events/stop.spec.js",
    "chars": 1072,
    "preview": "describe('stop (event)', () => {\n  it('should trigger the `stop` event', (done) => {\n    const image = window.createImag"
  },
  {
    "path": "test/specs/events/view.spec.js",
    "chars": 1636,
    "preview": "describe('view (event)', () => {\n  it('should trigger the `view` event', (done) => {\n    const image = window.createImag"
  },
  {
    "path": "test/specs/events/viewed.spec.js",
    "chars": 1585,
    "preview": "describe('viewed (event)', () => {\n  it('should trigger the `viewed` event', (done) => {\n    const image = window.create"
  },
  {
    "path": "test/specs/events/zoom.spec.js",
    "chars": 1974,
    "preview": "describe('zoom (event)', () => {\n  it('should trigger the `zoom` event', (done) => {\n    const image = window.createImag"
  },
  {
    "path": "test/specs/events/zoomed.spec.js",
    "chars": 1923,
    "preview": "describe('zoomed (event)', () => {\n  it('should trigger the `zoomed` event', (done) => {\n    const image = window.create"
  },
  {
    "path": "test/specs/methods/destroy.spec.js",
    "chars": 4039,
    "preview": "describe('destroy (method)', () => {\n  it('should destroy successfully when there are not any images', () => {\n    const"
  },
  {
    "path": "test/specs/methods/exit.spec.js",
    "chars": 1354,
    "preview": "describe('exit (method)', () => {\n  it('should exit modal mode in inline mode', (done) => {\n    const image = window.cre"
  },
  {
    "path": "test/specs/methods/full.spec.js",
    "chars": 1322,
    "preview": "describe('full (method)', () => {\n  it('should enter modal mode in inline mode', (done) => {\n    const image = window.cr"
  },
  {
    "path": "test/specs/methods/hide.spec.js",
    "chars": 1044,
    "preview": "describe('hide (method)', () => {\n  it('should hide the viewer in modal mode', (done) => {\n    const image = window.crea"
  },
  {
    "path": "test/specs/methods/move.spec.js",
    "chars": 1022,
    "preview": "describe('move (method)', () => {\n  it('should move with the given offsets', (done) => {\n    const image = window.create"
  },
  {
    "path": "test/specs/methods/moveTo.spec.js",
    "chars": 971,
    "preview": "describe('moveTo (method)', () => {\n  it('should move to the expected position', (done) => {\n    const image = window.cr"
  },
  {
    "path": "test/specs/methods/next.spec.js",
    "chars": 1900,
    "preview": "describe('next (method)', () => {\n  it('should view the next image', (done) => {\n    const imageList = window.createImag"
  },
  {
    "path": "test/specs/methods/noConflict.spec.js",
    "chars": 434,
    "preview": "describe('noConflict', () => {\n  it('should be a static method', () => {\n    expect(Viewer.noConflict).to.be.a('function"
  },
  {
    "path": "test/specs/methods/play.spec.js",
    "chars": 1050,
    "preview": "describe('play (method)', () => {\n  it('should play the images', (done) => {\n    const imageList = window.createImageLis"
  },
  {
    "path": "test/specs/methods/prev.spec.js",
    "chars": 1961,
    "preview": "describe('prev (method)', () => {\n  it('should view the previous image', (done) => {\n    const imageList = window.create"
  },
  {
    "path": "test/specs/methods/reset.spec.js",
    "chars": 369,
    "preview": "describe('reset (method)', () => {\n  it('should reset the image to its initial state', (done) => {\n    const image = win"
  },
  {
    "path": "test/specs/methods/rotate.spec.js",
    "chars": 971,
    "preview": "describe('rotate (method)', () => {\n  it('should rotate to the expected degrees', (done) => {\n    const image = window.c"
  },
  {
    "path": "test/specs/methods/rotateTo.spec.js",
    "chars": 980,
    "preview": "describe('rotateTo (method)', () => {\n  it('should rotate to the given degrees', (done) => {\n    const image = window.cr"
  },
  {
    "path": "test/specs/methods/scale.spec.js",
    "chars": 1130,
    "preview": "describe('scale (method)', () => {\n  it('should scale to the given values', (done) => {\n    const image = window.createI"
  },
  {
    "path": "test/specs/methods/scaleX.spec.js",
    "chars": 895,
    "preview": "describe('scaleX (method)', () => {\n  it('should scale to the given value in x-axis', (done) => {\n    const image = wind"
  },
  {
    "path": "test/specs/methods/scaleY.spec.js",
    "chars": 895,
    "preview": "describe('scaleY (method)', () => {\n  it('should scale to the given value in y-axis', (done) => {\n    const image = wind"
  },
  {
    "path": "test/specs/methods/setDefaults.spec.js",
    "chars": 659,
    "preview": "describe('setDefaults', () => {\n  it('should be a static method', () => {\n    expect(Viewer.setDefaults).to.be.a('functi"
  },
  {
    "path": "test/specs/methods/show.spec.js",
    "chars": 655,
    "preview": "describe('show (method)', () => {\n  it('should show the viewer in modal mode', (done) => {\n    const image = window.crea"
  },
  {
    "path": "test/specs/methods/stop.spec.js",
    "chars": 1112,
    "preview": "describe('stop (method)', () => {\n  it('should stop to play the images', (done) => {\n    const imageList = window.create"
  },
  {
    "path": "test/specs/methods/toggle.spec.js",
    "chars": 964,
    "preview": "describe('toggle (method)', () => {\n  it('should toggle the scale of the current viewing image between its current ratio"
  },
  {
    "path": "test/specs/methods/tooltip.spec.js",
    "chars": 911,
    "preview": "describe('tooltip (method)', () => {\n  it('should show tooltip by default', (done) => {\n    const image = window.createI"
  },
  {
    "path": "test/specs/methods/update.spec.js",
    "chars": 1409,
    "preview": "describe('update (method)', () => {\n  it('should update the image list', (done) => {\n    const imageList = window.create"
  },
  {
    "path": "test/specs/methods/view.spec.js",
    "chars": 1066,
    "preview": "describe('view (method)', () => {\n  it('should view the image of the given index', (done) => {\n    const imageList = win"
  },
  {
    "path": "test/specs/methods/zoom.spec.js",
    "chars": 905,
    "preview": "describe('zoom (method)', () => {\n  it('should zoom with the given offset ratio', (done) => {\n    const image = window.c"
  },
  {
    "path": "test/specs/methods/zoomTo.spec.js",
    "chars": 891,
    "preview": "describe('zoomTo (method)', () => {\n  it('should zoom to the given ratio', (done) => {\n    const image = window.createIm"
  },
  {
    "path": "test/specs/options/backdrop.spec.js",
    "chars": 1643,
    "preview": "describe('backdrop (option)', () => {\n  it('should show backdrop and hide the viewer on click the backdrop by default', "
  },
  {
    "path": "test/specs/options/button.spec.js",
    "chars": 2355,
    "preview": "describe('button (option)', () => {\n  it('should show a close button and hide the viewer on click the button by default'"
  },
  {
    "path": "test/specs/options/className.spec.js",
    "chars": 995,
    "preview": "describe('className (option)', () => {\n  it('should be empty string by default', () => {\n    const image = window.create"
  },
  {
    "path": "test/specs/options/container.spec.js",
    "chars": 1206,
    "preview": "describe('container (option)', () => {\n  it('should be \"body\" by default', (done) => {\n    const image = window.createIm"
  },
  {
    "path": "test/specs/options/filter.spec.js",
    "chars": 964,
    "preview": "describe('filter (option)', () => {\n  it('should be null be default', () => {\n    const image = window.createImage();\n  "
  },
  {
    "path": "test/specs/options/focus.spec.js",
    "chars": 1120,
    "preview": "describe('focus (option)', () => {\n  it('should be enabled by default', (done) => {\n    const image = window.createImage"
  },
  {
    "path": "test/specs/options/fullscreen.spec.js",
    "chars": 725,
    "preview": "describe('fullscreen (option)', () => {\n  it('should be enabled by default', () => {\n    const image = window.createImag"
  },
  {
    "path": "test/specs/options/hidden.spec.js",
    "chars": 854,
    "preview": "describe('hidden (option)', () => {\n  it('should be null be default', () => {\n    const image = window.createImage();\n  "
  },
  {
    "path": "test/specs/options/hide.spec.js",
    "chars": 1470,
    "preview": "describe('hide (option)', () => {\n  it('should be null be default', () => {\n    const image = window.createImage();\n    "
  },
  {
    "path": "test/specs/options/inheritedAttributes.spec.js",
    "chars": 1517,
    "preview": "describe('inheritedAttributes (option)', () => {\n  it('should inherit the `crossOrigin` and `referrerPolicy` attributes'"
  },
  {
    "path": "test/specs/options/initialCoverage.spec.js",
    "chars": 235,
    "preview": "describe('initialCoverage (option)', () => {\n  it('should be \"0.9\" by default', () => {\n    const image = window.createI"
  },
  {
    "path": "test/specs/options/initialViewIndex.spec.js",
    "chars": 942,
    "preview": "describe('initialViewIndex (option)', () => {\n  it('should be \"0\" by default', () => {\n    const image = window.createIm"
  },
  {
    "path": "test/specs/options/inline.spec.js",
    "chars": 1688,
    "preview": "describe('inline (option)', () => {\n  it('should not be inline by default', (done) => {\n    const image = window.createI"
  },
  {
    "path": "test/specs/options/interval.spec.js",
    "chars": 1122,
    "preview": "describe('interval (option)', () => {\n  it('should be `5000` by default', () => {\n    const imageList = window.createIma"
  },
  {
    "path": "test/specs/options/keyboard.spec.js",
    "chars": 5936,
    "preview": "describe('keyboard (option)', () => {\n  it('should be enabled by default', () => {\n    const image = window.createImage("
  },
  {
    "path": "test/specs/options/loading.spec.js",
    "chars": 907,
    "preview": "describe('loading (option)', () => {\n  it('should show a loading spinner when load image by default', (done) => {\n    co"
  },
  {
    "path": "test/specs/options/loop.spec.js",
    "chars": 1037,
    "preview": "describe('loop (option)', () => {\n  it('should be loop by default', (done) => {\n    const imageList = window.createImage"
  },
  {
    "path": "test/specs/options/maxZoomRatio.spec.js",
    "chars": 666,
    "preview": "describe('maxZoomRatio (option)', () => {\n  it('should be `100` by default', () => {\n    const image = window.createImag"
  },
  {
    "path": "test/specs/options/minHeight.spec.js",
    "chars": 722,
    "preview": "describe('minHeight (option)', () => {\n  it('should be `100` by default', () => {\n    const image = window.createImage()"
  },
  {
    "path": "test/specs/options/minWidth.spec.js",
    "chars": 713,
    "preview": "describe('minWidth (option)', () => {\n  it('should be `200` by default', () => {\n    const image = window.createImage();"
  },
  {
    "path": "test/specs/options/minZoomRatio.spec.js",
    "chars": 668,
    "preview": "describe('minZoomRatio (option)', () => {\n  it('should be `0.01` by default', () => {\n    const image = window.createIma"
  },
  {
    "path": "test/specs/options/movable.spec.js",
    "chars": 884,
    "preview": "describe('movable (option)', () => {\n  it('should be movable by default', (done) => {\n    const image = window.createIma"
  },
  {
    "path": "test/specs/options/move.spec.js",
    "chars": 2081,
    "preview": "describe('move (option)', () => {\n  it('should be null be default', () => {\n    const image = window.createImage();\n    "
  },
  {
    "path": "test/specs/options/moved.spec.js",
    "chars": 1565,
    "preview": "describe('moved (option)', () => {\n  it('should be null be default', () => {\n    const image = window.createImage();\n   "
  },
  {
    "path": "test/specs/options/navbar.spec.js",
    "chars": 726,
    "preview": "describe('navbar (option)', () => {\n  it('should show navbar by default', (done) => {\n    const image = window.createIma"
  },
  {
    "path": "test/specs/options/play.spec.js",
    "chars": 1055,
    "preview": "describe('play (option)', () => {\n  it('should be null be default', () => {\n    const image = window.createImage();\n    "
  },
  {
    "path": "test/specs/options/ready.spec.js",
    "chars": 779,
    "preview": "describe('ready (option)', () => {\n  it('should be null be default', () => {\n    const image = window.createImage();\n   "
  },
  {
    "path": "test/specs/options/rotatable.spec.js",
    "chars": 746,
    "preview": "describe('rotatable (option)', () => {\n  it('should be rotatable by default', (done) => {\n    const image = window.creat"
  },
  {
    "path": "test/specs/options/rotate.spec.js",
    "chars": 1967,
    "preview": "describe('rotate (option)', () => {\n  it('should be null be default', () => {\n    const image = window.createImage();\n  "
  },
  {
    "path": "test/specs/options/rotated.spec.js",
    "chars": 1442,
    "preview": "describe('rotated (option)', () => {\n  it('should be null be default', () => {\n    const image = window.createImage();\n "
  },
  {
    "path": "test/specs/options/scalable.spec.js",
    "chars": 906,
    "preview": "describe('scalable (option)', () => {\n  it('should be scalable by default', (done) => {\n    const image = window.createI"
  },
  {
    "path": "test/specs/options/scale.spec.js",
    "chars": 2067,
    "preview": "describe('scale (option)', () => {\n  it('should be null be default', () => {\n    const image = window.createImage();\n   "
  },
  {
    "path": "test/specs/options/scaled.spec.js",
    "chars": 1548,
    "preview": "describe('scaled (option)', () => {\n  it('should be null be default', () => {\n    const image = window.createImage();\n  "
  },
  {
    "path": "test/specs/options/show.spec.js",
    "chars": 1131,
    "preview": "describe('show (option)', () => {\n  it('should be null be default', () => {\n    const image = window.createImage();\n    "
  },
  {
    "path": "test/specs/options/shown.spec.js",
    "chars": 780,
    "preview": "describe('shown (option)', () => {\n  it('should be null be default', () => {\n    const image = window.createImage();\n   "
  },
  {
    "path": "test/specs/options/slideOnTouch.spec.js",
    "chars": 227,
    "preview": "describe('slideOnTouch (option)', () => {\n  it('should be `true` by default', () => {\n    const image = window.createIma"
  },
  {
    "path": "test/specs/options/stop.spec.js",
    "chars": 1203,
    "preview": "describe('stop (option)', () => {\n  it('should be null be default', () => {\n    const image = window.createImage();\n    "
  },
  {
    "path": "test/specs/options/title.spec.js",
    "chars": 1506,
    "preview": "describe('title (option)', () => {\n  it('should show title by default', (done) => {\n    const image = window.createImage"
  },
  {
    "path": "test/specs/options/toggleOnDblclick.spec.js",
    "chars": 969,
    "preview": "describe('toggleOnDblclick (option)', () => {\n  it('should be true by default', (done) => {\n    const image = window.cre"
  },
  {
    "path": "test/specs/options/toolbar.spec.js",
    "chars": 734,
    "preview": "describe('toolbar (option)', () => {\n  it('should show toolbar by default', (done) => {\n    const image = window.createI"
  },
  {
    "path": "test/specs/options/tooltip.spec.js",
    "chars": 816,
    "preview": "describe('tooltip (option)', () => {\n  it('should show tooltip when zoom by default', (done) => {\n    const image = wind"
  },
  {
    "path": "test/specs/options/transition.spec.js",
    "chars": 1068,
    "preview": "describe('transition (option)', () => {\n  it('should be enabled by default', (done) => {\n    const image = window.create"
  },
  {
    "path": "test/specs/options/url.spec.js",
    "chars": 790,
    "preview": "describe('url (option)', () => {\n  it('should be \"src\" by default', () => {\n    const image = window.createImage();\n    "
  },
  {
    "path": "test/specs/options/view.spec.js",
    "chars": 1756,
    "preview": "describe('view (option)', () => {\n  it('should be null be default', () => {\n    const image = window.createImage();\n    "
  },
  {
    "path": "test/specs/options/viewed.spec.js",
    "chars": 1310,
    "preview": "describe('viewed (option)', () => {\n  it('should be null be default', () => {\n    const image = window.createImage();\n  "
  },
  {
    "path": "test/specs/options/zIndex.spec.js",
    "chars": 972,
    "preview": "describe('zIndex (option)', () => {\n  it('should be `2015` by default', () => {\n    const image = window.createImage();\n"
  },
  {
    "path": "test/specs/options/zIndexInline.spec.js",
    "chars": 1053,
    "preview": "describe('zIndexInline (option)', () => {\n  it('should be 0 by default', () => {\n    const image = window.createImage();"
  },
  {
    "path": "test/specs/options/zoom.spec.js",
    "chars": 1995,
    "preview": "describe('zoom (option)', () => {\n  it('should be null be default', () => {\n    const image = window.createImage();\n    "
  },
  {
    "path": "test/specs/options/zoomOnTouch.spec.js",
    "chars": 225,
    "preview": "describe('zoomOnTouch (option)', () => {\n  it('should be `true` by default', () => {\n    const image = window.createImag"
  },
  {
    "path": "test/specs/options/zoomOnWheel.spec.js",
    "chars": 225,
    "preview": "describe('zoomOnWheel (option)', () => {\n  it('should be `true` by default', () => {\n    const image = window.createImag"
  },
  {
    "path": "test/specs/options/zoomRatio.spec.js",
    "chars": 832,
    "preview": "describe('zoomRatio (option)', () => {\n  it('should be 0.1 by default', () => {\n    const image = window.createImage();\n"
  },
  {
    "path": "test/specs/options/zoomable.spec.js",
    "chars": 576,
    "preview": "describe('zoomable (option)', () => {\n  it('should be zoomable by default', () => {\n    const image = window.createImage"
  },
  {
    "path": "test/specs/options/zoomed.spec.js",
    "chars": 1486,
    "preview": "describe('zoomed (option)', () => {\n  it('should be null be default', () => {\n    const image = window.createImage();\n  "
  },
  {
    "path": "types/index.d.ts",
    "chars": 4830,
    "preview": "declare namespace Viewer {\n  export type Visibility = 0 | 1 | 2 | 3 | 4;\n  export type ToolbarButtonSize = 'small' | 'me"
  }
]

About this extraction

This page contains the full source code of the fengyuanchen/viewerjs GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 154 files (746.1 KB), approximately 192.7k tokens, and a symbol index with 584 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!