[
  {
    "path": ".browserslistrc",
    "content": "defaults\nnot IE 11\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\nindent_size = 2\nindent_style = space\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n"
  },
  {
    "path": ".eslintignore",
    "content": "*.local*\n.cache\n.husky\n.temp\ncache\ncoverage\ndist\ndocs/public\nnode_modules\n"
  },
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n  env: {\n    browser: true,\n    node: true,\n  },\n  extends: [\n    'airbnb-base',\n    'airbnb-typescript/base',\n    'plugin:@typescript-eslint/recommended',\n    'plugin:jsdoc/recommended',\n    'plugin:vue/vue3-recommended',\n  ],\n  parser: 'vue-eslint-parser',\n  parserOptions: {\n    parser: '@typescript-eslint/parser',\n    project: 'tsconfig.json',\n    sourceType: 'module',\n    extraFileExtensions: ['.vue'],\n  },\n  plugins: [\n    '@typescript-eslint',\n    'import',\n    'jsdoc',\n    'vue',\n  ],\n  rules: {\n    '@typescript-eslint/no-explicit-any': 'off',\n    'import/no-extraneous-dependencies': 'off',\n    'max-len': ['error', 100, 2, {\n      ignorePattern: '\\\\S+=\"[^\"]*\"',\n      ignoreComments: true,\n      ignoreUrls: true,\n      ignoreStrings: true,\n      ignoreTemplateLiterals: true,\n      ignoreRegExpLiterals: true,\n    }],\n    'no-param-reassign': 'off',\n  },\n  overrides: [\n    {\n      files: 'packages/**/tests/**/*.ts',\n      env: {\n        jest: true,\n      },\n      rules: {\n        'no-new': 'off',\n      },\n    },\n    {\n      files: 'docs/.vitepress/**/*.vue',\n      rules: {\n        'import/no-unresolved': 'off',\n        'no-new': 'off',\n      },\n    },\n  ],\n};\n"
  },
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "content": "# Contributor Code of Conduct\n\nAs contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.\n\nWe are committed to making participation in this project a harassment-free experience for everyone, regardless of the level of experience, gender, gender identity, and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.\n\nExamples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.\n\nProject maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.\n\nThis Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.0.0, available at https://www.contributor-covenant.org/version/1/0/0/code-of-conduct.html\n"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "# Contributing to Cropper.js\n\n> Based on [Angular's contributing guidelines](https://github.com/angular/angular/blob/master/CONTRIBUTING.md).\n\nWe would love for you to contribute to Cropper.js and help make it even better than it is today! As a contributor, here are the guidelines we would like you to follow:\n\n- [Contributing to Cropper.js](#contributing-to-cropperjs)\n  - [Code of Conduct](#code-of-conduct)\n  - [Question or Problem](#question-or-problem)\n  - [Issues and Bugs](#issues-and-bugs)\n  - [Feature Requests](#feature-requests)\n  - [Submission Guidelines](#submission-guidelines)\n    - [Submitting an Issue](#submitting-an-issue)\n    - [Submitting a Pull Request (PR)](#submitting-a-pull-request-pr)\n      - [After your pull request is merged](#after-your-pull-request-is-merged)\n  - [Coding Rules](#coding-rules)\n  - [Commit Message Guidelines](#commit-message-guidelines)\n    - [Commit Message Format](#commit-message-format)\n    - [Revert](#revert)\n    - [Type](#type)\n    - [Scope](#scope)\n    - [Subject](#subject)\n    - [Body](#body)\n    - [Footer](#footer)\n\n## Code of Conduct\n\nHelp us keep Cropper.js open and inclusive. Please read and follow our [Code of Conduct](CODE_OF_CONDUCT.md).\n\n## Question or Problem\n\nDo not open issues for general support questions as we want to keep GitHub issues for bug reports and feature requests. You've got much better chances of getting your question answered on [Stack Overflow](https://stackoverflow.com/questions/tagged/cropperjs) where the questions should be tagged with tag `cropperjs`.\n\nStack Overflow is a much better place to ask questions since:\n\n- There are thousands of people willing to help on Stack Overflow.\n- Questions and answers stay available for public viewing so your question/answer might help someone else.\n- Stack Overflow's voting system assures that the best answers are prominently visible.\n\nTo save you and our time, we will systematically close all issues that are requests for general support and redirect people to Stack Overflow.\n\n## Issues and Bugs\n\nIf you find a bug in the source code, you can help us by [submitting an issue](#submitting-an-issue) to our [GitHub Repository](https://github.com/fengyuanchen/cropperjs). Even better, you can [submit a Pull Request](#submitting-a-pull-request-pr) with a fix.\n\n## Feature Requests\n\nYou can *request* a new feature by [submitting an issue](#submitting-an-issue) to our [GitHub Repository](https://github.com/fengyuanchen/cropperjs). If you would like to *implement* a new feature, please submit an issue with a proposal for your work first, to be sure that we can use it.\n\nPlease consider what kind of change it is:\n\n- For a **Major Feature**, first, open an issue and outline your proposal so that it can be discussed. This will also allow us to better coordinate our efforts, prevent duplication of work, and help you to craft the change so that it is successfully accepted into the project.\n- **Small Features** can be crafted and directly [submitted as a Pull Request](#submitting-a-pull-request-pr).\n\n## Submission Guidelines\n\n### Submitting an Issue\n\nBefore you submit an issue, please search the [issue tracker](https://github.com/fengyuanchen/cropperjs/issues), which may be an issue for your problem already exists and the discussion might inform you of workarounds readily available.\n\nWe want to fix all the issues as soon as possible, but before fixing a bug we need to reproduce and confirm it. To reproduce bugs, we will systematically ask you to provide a minimal reproduction scenario using [CodePen](https://codepen.io/pen). Having a live, reproducible scenario gives us a wealth of important information without going back & forth to you with additional questions like:\n\n- version of Cropper.js used\n- 3rd-party libraries and their versions\n- and most importantly - a use-case that fails\n\nA minimal reproduction scenario using [CodePen](https://codepen.io/pen) allows us to quickly confirm a bug (or point out a coding problem) as well as confirm that we are fixing the right problem. If [CodePen](https://codepen.io/pen) is not a suitable way to demonstrate the problem (for example for issues related to our npm packaging), please create a standalone git repository demonstrating the problem.\n\nWe will be insisting on a minimal reproduction scenario to save maintainers time and ultimately be able to fix more bugs. Interestingly, from our experience users often find coding problems themselves while preparing a minimal reproduction scenario. We understand that sometimes it might be hard to extract essentials bits of code from a larger code-base but we need to isolate the problem before we can fix it.\n\nUnfortunately, we are not able to investigate/fix bugs without a minimal reproduction scenario, so if we don't hear back from you we are going to close an issue that doesn't have enough info to be reproduced.\n\nYou can file new issues by filling out our [new issue form](https://github.com/fengyuanchen/cropperjs/issues/new).\n\n### Submitting a Pull Request (PR)\n\nBefore you submit your Pull Request (PR) consider the following guidelines:\n\n1. Search [GitHub](https://github.com/fengyuanchen/cropperjs/pulls) for an open or closed PR that relates to your submission. You don't want to duplicate effort.\n1. Fork the **fengyuanchen/cropperjs** repo.\n1. Make your changes in a new git branch:\n\n    ```shell\n    git checkout -b my-fix-branch main\n    ```\n\n1. Create your patch, **including appropriate test cases**.\n1. Follow our [Coding Rules](#coding-rules).\n1. Run the full Cropper.js test suite, and ensure that all tests pass.\n1. Commit your changes using a descriptive commit message that follows our [Commit Message Guidelines](#commit-message-guidelines). Adherence to these guidelines is necessary because release notes are automatically generated from these messages.\n\n    ```shell\n    git commit -a\n    ```\n\n    Note: the optional commit `-a` command-line option will automatically \"add\" and \"rm\" edited files.\n1. Push your branch to GitHub:\n\n    ```shell\n    git push origin my-fix-branch\n    ```\n\n1. In GitHub, send a pull request to `cropperjs:main`.\n1. If we suggest changes then:\n    - Make the required updates.\n    - Re-run the Cropper.js test suites to ensure tests are still passing.\n    - Rebase your branch and force push to your GitHub repository (this will update your Pull Request):\n\n    ```shell\n    git rebase main -i\n    git push -f\n    ```\n\nThat's it! Thank you for your contribution!\n\n#### After your pull request is merged\n\nAfter your pull request is merged, you can safely delete your branch and pull the changes from the main (upstream) repository:\n\n1. Delete the remote branch on GitHub either through the GitHub web UI or your local shell as follows:\n\n    ```shell\n    git push origin --delete my-fix-branch\n    ```\n\n1. Check out the main branch:\n\n    ```shell\n    git checkout main -f\n    ```\n\n1. Delete the local branch:\n\n    ```shell\n    git branch -D my-fix-branch\n    ```\n\n1. Update your main with the latest upstream version:\n\n    ```shell\n    git pull --ff upstream main\n    ```\n\n## Coding Rules\n\nTo ensure consistency throughout the source code, keep these rules in mind as you are working:\n\n- All features or bug fixes **must be tested** by one or more specs (unit-tests).\n- All public API methods **must be documented**.\n- We follow [Airbnb's JavaScript Style Guide](https://github.com/airbnb/javascript).\n\n## Commit Message Guidelines\n\n### Commit Message Format\n\nA commit message consists of a **header**, **body** and **footer**. The header has a **type**, **scope** and **subject**:\n\n```\n<type>(<scope>): <subject>\n<BLANK LINE>\n<body>\n<BLANK LINE>\n<footer>\n```\n\nThe **header** is mandatory and the **scope** of the header is optional.\n\nAny line of the commit message cannot be longer than 100 characters! This allows the message to be easier to read on GitHub as well as in various git tools.\n\nThe footer should contain a [closing reference to an issue](https://help.github.com/articles/closing-issues-via-commit-messages/) if any.\n\nHere are some [samples](https://github.com/fengyuanchen/cropperjs/commits/main).\n\n### Revert\n\nIf the commit reverts a previous commit, it should begin with `revert: `, followed by the header of the reverted commit. In the body, it should say: `This reverts commit <hash>.`, where the hash is the SHA of the commit being reverted.\n\n### Type\n\nMust be one of the following:\n\n- **build**: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)\n- **ci**: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)\n- **docs**: Documentation only changes\n- **feat**: A new feature\n- **fix**: A bug fix\n- **perf**: A code change that improves performance\n- **refactor**: A code change that neither fixes a bug nor adds a feature\n- **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)\n- **test**: Adding missing tests or correcting existing tests\n\n### Scope\n\nThe scope could be anything specifying the place of the commit change. For example `move`, `zoom`, `rotate`, etc...\n\n### Subject\n\nThe subject contains a succinct description of the change:\n\n- Use the imperative, present tense: \"change\" not \"changed\" nor \"changes\".\n- Don't capitalize the first letter.\n- No dot (.) at the end.\n\n### Body\n\nJust as in the **subject**, use the imperative, present tense: \"change\" not \"changed\" nor \"changes\". The body should include the motivation for the change and contrast this with previous behavior.\n\n### Footer\n\nThe footer should contain any information about **Breaking Changes** and is also the place to reference GitHub issues that this commit **Closes**.\n\n**Breaking Changes** should start with the word `BREAKING CHANGE:` with a space or two newlines. The rest of the commit message is then used for this.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: \"\\U0001F41E Bug report\"\ndescription: Create a report to help us improve\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        **Before You Start...**\n\n        This form is only for submitting bug reports. If you have a usage question\n        or are unsure if this is really a bug, make sure to:\n\n        - Read the [docs](https://fengyuanchen.github.io/cropperjs)\n        - Ask on [GitHub Discussions](https://github.com/fengyuanchen/cropperjs/discussions)\n        - Look for / ask questions on [Stack Overflow](https://stackoverflow.com/questions/ask?tags=cropperjs)\n\n        Also try to search for your issue - it may have already been answered or even fixed in the development branch.\n        However, if you find that an old, closed issue still persists in the latest version,\n        you should open a new issue using the form below instead of commenting on the old issue.\n  - type: input\n    id: version\n    attributes:\n      label: Cropper version\n    validations:\n      required: true\n  - type: input\n    id: reproduction-link\n    attributes:\n      label: Link to minimal reproduction\n      description: |\n        The easiest way to provide a reproduction is by showing the bug in the [Playground](https://fengyuanchen.github.io/cropperjs/playground.html).\n        If it cannot be reproduced in the playground and requires a proper build setup, try [StackBlitz](https://stackblitz.com/?starters=frontend).\n        If neither of these are suitable, you can always provide a GitHub repository.\n\n        The reproduction should be **minimal** - i.e. it should contain only the bare minimum amount of code needed\n        to show the bug.\n\n        Please do not just fill in a random link. The issue will be closed if no valid reproduction is provided.\n      placeholder: Reproduction Link\n    validations:\n      required: true\n  - type: textarea\n    id: steps-to-reproduce\n    attributes:\n      label: Steps to reproduce\n      description: |\n        What do we need to do after opening your repro in order to make the bug happen? Clear and concise reproduction instructions are important for us to be able to triage your issue in a timely manner. Note that you can use [Markdown](https://guides.github.com/features/mastering-markdown/) to format lists and code.\n      placeholder: Steps to reproduce\n    validations:\n      required: true\n  - type: textarea\n    id: expected\n    attributes:\n      label: What is expected?\n    validations:\n      required: true\n  - type: textarea\n    id: actually-happening\n    attributes:\n      label: What is actually happening?\n    validations:\n      required: true\n  - type: textarea\n    id: system-info\n    attributes:\n      label: System Info\n      description: Output of `npx envinfo --system --npmPackages cropperjs --binaries --browsers`\n      render: shell\n      placeholder: System, Binaries, Browsers\n  - type: textarea\n    id: additional-comments\n    attributes:\n      label: Any additional comments?\n      description: e.g. some background/context of how you ran into this bug.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: Questions & Discussions\n    url: https://github.com/fengyuanchen/cropperjs/discussions\n    about: Use GitHub discussions for message-board style questions and discussions.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!-- Please don't delete this template -->\n\n<!-- PULL REQUEST TEMPLATE -->\n<!-- (Update \"[ ]\" to \"[x]\" to check a box) -->\n\n**Summary**\n\n**What kind of change does this PR introduce?** (check at least one)\n\n- [ ] Bugfix\n- [ ] Feature\n- [ ] Code style update\n- [ ] Refactor\n- [ ] Docs\n- [ ] Build-related changes\n- [ ] Other, please describe:\n\nIf changing the UI of the default theme, please provide the **before/after** screenshot:\n\n**Does this PR introduce a breaking change?** (check one)\n\n- [ ] Yes\n- [ ] No\n\nIf yes, please describe the impact and migration path for existing applications:\n\n**The PR fulfills these requirements:**\n\n- [ ] When resolving a specific issue, it's referenced in the PR's title (e.g. `fix #xxx[,#xxx]`, where \"xxx\" is the issue number)\n\nYou have tested in the following browsers: (Providing a detailed version will be better.)\n\n- [ ] Chrome\n- [ ] Firefox\n- [ ] Safari\n- [ ] Edge\n- [ ] IE\n\nIf adding a **new feature**, the PR's description includes:\n\n- [ ] A convincing reason for adding this feature\n- [ ] Related documents have been updated\n- [ ] Related tests have been updated\n\nTo avoid wasting your time, it's best to open a **feature request issue** first and wait for approval before working on it.\n\n**Other information:**\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 22\n      - run: npm install\n      - run: npm run lint\n      - run: npm run build\n      - run: npm test\n"
  },
  {
    "path": ".github/workflows/deploy.yml",
    "content": "name: Deploy\n\non:\n  push:\n    tags:\n      - v2.*\n      - '!v2.0.0-alpha*'\n  workflow_dispatch:\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 22\n      - run: npm install\n      - run: npm run build\n      - run: cp -r docs/.vitepress/dist .temp\n      - uses: actions/checkout@v4\n        with:\n          ref: gh-pages\n          clean: false\n      - run: cp -rf .temp/* .\n      - run: rm -r .temp\n      - run: git config user.name \"${{ github.actor }}\"\n      - run: git config user.email \"${{ github.actor }}@users.noreply.github.com\"\n      - run: git add .\n      - run: git commit --message \"${{ github.ref_name }}\"\n      - run: git push\n"
  },
  {
    "path": ".gitignore",
    "content": "*.local*\n*.log\n*.map\n.DS_Store\n.cache\n.temp\ncache\ncoverage\ndist\nnode_modules\n"
  },
  {
    "path": ".husky/commit-msg",
    "content": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\nnpx commitlint --edit $1\n"
  },
  {
    "path": ".husky/pre-commit",
    "content": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\nnpx lint-staged\n"
  },
  {
    "path": ".stylelintignore",
    "content": "*.local*\n.cache\n.husky\n.temp\ncoverage\ndist\ndocs/public\nnode_modules\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "## 2.1.0 (2025-10-19)\n\n* docs: fix value of free aspect ratio ([5b4e96f](https://github.com/fengyuanchen/cropperjs/commit/5b4e96f)), closes [#1274](https://github.com/fengyuanchen/cropperjs/issues/1274)\n* refactor(element-shade): use selection itself when default prevented ([0673f15](https://github.com/fengyuanchen/cropperjs/commit/0673f15))\n* fix(element-image): ensure the image is fully rendered ([5afd8f3](https://github.com/fengyuanchen/cropperjs/commit/5afd8f3)), closes [#1168](https://github.com/fengyuanchen/cropperjs/issues/1168)\n* fix(element-selection): get correct event target in shadow DOM (#1276) ([e64d6c3](https://github.com/fengyuanchen/cropperjs/commit/e64d6c3)), closes [#1276](https://github.com/fengyuanchen/cropperjs/issues/1276)\n* fix(element-shade): fix shade size when selection changes ([98c101c](https://github.com/fengyuanchen/cropperjs/commit/98c101c))\n* fix(element): call `attachShadow` only if `shadowRoot` does not exist ([75cabcf](https://github.com/fengyuanchen/cropperjs/commit/75cabcf)), closes [#1217](https://github.com/fengyuanchen/cropperjs/issues/1217)\n* feat(cropperjs): add `destroy` method ([6a933e3](https://github.com/fengyuanchen/cropperjs/commit/6a933e3)), closes [#1271](https://github.com/fengyuanchen/cropperjs/issues/1271)\n\n\n\n## <small>2.0.1 (2025-07-25)</small>\n\n* build: add missing `/dist` paths ([42dba8b](https://github.com/fengyuanchen/cropperjs/commit/42dba8b))\n* build: release v2.0.1 ([6b8e5ff](https://github.com/fengyuanchen/cropperjs/commit/6b8e5ff))\n* fix: correct require.node.default export and explicitly export package.json (#1259) ([97f8787](https://github.com/fengyuanchen/cropperjs/commit/97f8787)), closes [#1259](https://github.com/fengyuanchen/cropperjs/issues/1259)\n* fix: set crossorigin attribute on cropper image element (#1253) ([f11026c](https://github.com/fengyuanchen/cropperjs/commit/f11026c)), closes [#1253](https://github.com/fengyuanchen/cropperjs/issues/1253)\n* fix(cropper-selection): improve selection movement logic for better user experience ([e7e3510](https://github.com/fengyuanchen/cropperjs/commit/e7e3510))\n* fix(cropperjs): fix container query issue when in a custom element ([4fea6fd](https://github.com/fengyuanchen/cropperjs/commit/4fea6fd))\n* fix(cropperjs): inherit additional attributes from HTMLImageElement ([cb5a341](https://github.com/fengyuanchen/cropperjs/commit/cb5a341))\n* fix(element-image): add missing attributes (type declarations) ([2e46715](https://github.com/fengyuanchen/cropperjs/commit/2e46715)), closes [#1233](https://github.com/fengyuanchen/cropperjs/issues/1233)\n* fix(element-shade): get data from `event.detail` when the selection is  dynamic ([f6b2847](https://github.com/fengyuanchen/cropperjs/commit/f6b2847))\n* fix(element-shade): get data from `event.detail` when there are multiple selections ([09fd9ca](https://github.com/fengyuanchen/cropperjs/commit/09fd9ca))\n* fix(element-shade): prevent shade from \"glitching\" when pulling selection too far (#1242) ([2dab6ff](https://github.com/fengyuanchen/cropperjs/commit/2dab6ff)), closes [#1242](https://github.com/fengyuanchen/cropperjs/issues/1242) [#1078](https://github.com/fengyuanchen/cropperjs/issues/1078)\n* fix(element-viewer): fix selection query issue when in a custom element ([aecee79](https://github.com/fengyuanchen/cropperjs/commit/aecee79)), closes [#1245](https://github.com/fengyuanchen/cropperjs/issues/1245)\n* fix(element-viewer): transform the image by the selection offset after the next DOM update cycle ([04a2c8b](https://github.com/fengyuanchen/cropperjs/commit/04a2c8b)), closes [#1258](https://github.com/fengyuanchen/cropperjs/issues/1258)\n* test(cropper): remove support for HTMLCanvasElement and improve container handling in tests ([91d5ab3](https://github.com/fengyuanchen/cropperjs/commit/91d5ab3))\n* test(cropperjs): add container option to `getCropperSelections` test ([69a0ab6](https://github.com/fengyuanchen/cropperjs/commit/69a0ab6))\n* refactor(element-shade): rename selection event handler for better readability ([c960217](https://github.com/fengyuanchen/cropperjs/commit/c960217))\n* docs: explain that jQuery Cropper only available for Cropper.js 1.0 now ([3df9b20](https://github.com/fengyuanchen/cropperjs/commit/3df9b20))\n\n\n\n## 2.0.0 (2025-03-01)\n\n* build: config issue template ([649b2fa](https://github.com/fengyuanchen/cropperjs/commit/649b2fa))\n* build: prepare v2 stable version ([ae55bdf](https://github.com/fengyuanchen/cropperjs/commit/ae55bdf))\n* build: release v2.0.0 ([08a3d16](https://github.com/fengyuanchen/cropperjs/commit/08a3d16))\n* build: update dependencies ([56cef09](https://github.com/fengyuanchen/cropperjs/commit/56cef09))\n* build: update issue template for bug reports ([dc09c90](https://github.com/fengyuanchen/cropperjs/commit/dc09c90))\n* build: update repo urls ([2bee699](https://github.com/fengyuanchen/cropperjs/commit/2bee699))\n* ci: fix file path ([c13c162](https://github.com/fengyuanchen/cropperjs/commit/c13c162))\n* ci: update action version ([0bb45db](https://github.com/fengyuanchen/cropperjs/commit/0bb45db))\n* docs(element-canvas): except the `\"scale\"` option for the `event.detail.action` of actionstart/move/ ([c778cb6](https://github.com/fengyuanchen/cropperjs/commit/c778cb6))\n\n\n\n## 2.0.0-rc.2 (2024-08-18)\n\n* build: release v2.0.0-rc.2 ([396f2c7](https://github.com/fengyuanchen/cropperjs/commit/396f2c7))\n* fix(element-selection): disable keyboard control when input something ([af97972](https://github.com/fengyuanchen/cropperjs/commit/af97972)), closes [#1192](https://github.com/fengyuanchen/cropperjs/issues/1192)\n\n\n\n## 2.0.0-rc.1 (2024-07-06)\n\n* build: ignore `docs/public/service-worker.js` file ([d9cff71](https://github.com/fengyuanchen/cropperjs/commit/d9cff71))\n* build: release 2.0.0-rc.1 ([1a290d5](https://github.com/fengyuanchen/cropperjs/commit/1a290d5))\n* docs: add version info and changelog link ([4281c7f](https://github.com/fengyuanchen/cropperjs/commit/4281c7f))\n* docs: unregister service worker ([236d155](https://github.com/fengyuanchen/cropperjs/commit/236d155))\n* docs(element-selection): add dynamic changes example ([3b853fb](https://github.com/fengyuanchen/cropperjs/commit/3b853fb))\n* fix(element-image): zoom when the selection is both zoomable and dynamic ([0193036](https://github.com/fengyuanchen/cropperjs/commit/0193036))\n* fix(element-selection): backward compatible with `linked=\"true\"` only ([64e2714](https://github.com/fengyuanchen/cropperjs/commit/64e2714))\n* fix(element-selection): fix wrong action end listener ([5f764a1](https://github.com/fengyuanchen/cropperjs/commit/5f764a1))\n* refactor(cropperjs): disable zoomable by default ([f63e55d](https://github.com/fengyuanchen/cropperjs/commit/f63e55d))\n* feat(element-selection): pass zoom events down to image when selection is not zoomable ([3aa7ff4](https://github.com/fengyuanchen/cropperjs/commit/3aa7ff4)), closes [#1182](https://github.com/fengyuanchen/cropperjs/issues/1182)\n\n\n\n## 2.0.0-rc.0 (2024-06-22)\n\n* build: release 2.0.0-rc.0 ([08368f1](https://github.com/fengyuanchen/cropperjs/commit/08368f1))\n* build: update dependencies ([b9719fe](https://github.com/fengyuanchen/cropperjs/commit/b9719fe))\n* refactor(element-selection): backward compatible with 2.0.0-rc for the `linked` property ([0a931ed](https://github.com/fengyuanchen/cropperjs/commit/0a931ed))\n* refactor(element-selection): simplify the `$handleAction` function ([388c71a](https://github.com/fengyuanchen/cropperjs/commit/388c71a))\n* fix(element-selection): fix select action interaction ([a0ad269](https://github.com/fengyuanchen/cropperjs/commit/a0ad269)), closes [#1176](https://github.com/fengyuanchen/cropperjs/issues/1176)\n* fix(element-selection): fix wrong event to read `shiftKey` ([206d2b4](https://github.com/fengyuanchen/cropperjs/commit/206d2b4))\n* fix(element-selection): reset the `initialCoverage` property to its initial value when creating new  ([57e2688](https://github.com/fengyuanchen/cropperjs/commit/57e2688))\n* perf(element-selection): Avoid repeatedly calling the `$change` method ([a99a5bd](https://github.com/fengyuanchen/cropperjs/commit/a99a5bd))\n* refactor(element-selection)!: rename the `linked` property to `dynamic` and disable it by default ([8a005ab](https://github.com/fengyuanchen/cropperjs/commit/8a005ab)), closes [#1175](https://github.com/fengyuanchen/cropperjs/issues/1175)\n* docs(element-handle): add example for toggling action on dblclick ([1829c23](https://github.com/fengyuanchen/cropperjs/commit/1829c23))\n\n\n\n## 2.0.0-rc (2024-05-26)\n\n* build: add `--host` argument to `vitepress` script ([d8bd0dd](https://github.com/fengyuanchen/cropperjs/commit/d8bd0dd))\n* build: add publish script ([23bfd85](https://github.com/fengyuanchen/cropperjs/commit/23bfd85))\n* build: release 2.0.0-rc ([7027554](https://github.com/fengyuanchen/cropperjs/commit/7027554))\n* build: update dependencies ([8819476](https://github.com/fengyuanchen/cropperjs/commit/8819476))\n* test: update test cases ([b58ecd6](https://github.com/fengyuanchen/cropperjs/commit/b58ecd6))\n* fix: disable callout in webkit-based browsers ([851bd70](https://github.com/fengyuanchen/cropperjs/commit/851bd70)), closes [#1108](https://github.com/fengyuanchen/cropperjs/issues/1108)\n* fix(element-selection): observe the `linked` property ([7075bfe](https://github.com/fengyuanchen/cropperjs/commit/7075bfe))\n* fix(element-selection): resize when initial coverage change ([2c75cd9](https://github.com/fengyuanchen/cropperjs/commit/2c75cd9))\n* docs: fix TypeError when cropperCanvas is null ([6799b61](https://github.com/fengyuanchen/cropperjs/commit/6799b61))\n* docs: outline level 3 titles ([9dbc0d8](https://github.com/fengyuanchen/cropperjs/commit/9dbc0d8))\n* docs: outline level 3 titles for zh lang ([f4f1ec9](https://github.com/fengyuanchen/cropperjs/commit/f4f1ec9))\n* docs: replace `withBase` with `BASE_URL` ([b8c8a51](https://github.com/fengyuanchen/cropperjs/commit/b8c8a51))\n* docs: update migration for the `viewMode` option ([e3c7ab7](https://github.com/fengyuanchen/cropperjs/commit/e3c7ab7))\n* docs(cropperjs): improve basic example ([464d6ab](https://github.com/fengyuanchen/cropperjs/commit/464d6ab))\n* docs(element-image): add limiting boundaries example ([f022d68](https://github.com/fengyuanchen/cropperjs/commit/f022d68))\n* docs(element-selection): add example for limiting boundaries ([d5068b8](https://github.com/fengyuanchen/cropperjs/commit/d5068b8))\n* docs(element-selection): improve limiting boundaries example ([4e09be6](https://github.com/fengyuanchen/cropperjs/commit/4e09be6))\n* docs(element-shade): change the `x` property values in the demo ([1c004a3](https://github.com/fengyuanchen/cropperjs/commit/1c004a3))\n* feat(element-image): add new `initial-center-size` property ([daccbfb](https://github.com/fengyuanchen/cropperjs/commit/daccbfb)), closes [#1105](https://github.com/fengyuanchen/cropperjs/issues/1105)\n* feat(element-selection): add new `linked` property ([1851f25](https://github.com/fengyuanchen/cropperjs/commit/1851f25)), closes [#1133](https://github.com/fengyuanchen/cropperjs/issues/1133)\n* feat(element-selection): make the `x`, `y`, `width`, `height`, `aspect-ratio`, `initial-aspect-ratio ([ce3c6d3](https://github.com/fengyuanchen/cropperjs/commit/ce3c6d3)), closes [#1124](https://github.com/fengyuanchen/cropperjs/issues/1124) [#1163](https://github.com/fengyuanchen/cropperjs/issues/1163) [#1164](https://github.com/fengyuanchen/cropperjs/issues/1164) [#1165](https://github.com/fengyuanchen/cropperjs/issues/1165)\n* refactor: drop unnecessary type definitions ([7fc90e7](https://github.com/fengyuanchen/cropperjs/commit/7fc90e7))\n* refactor(element-grid): render grid in next tick ([aad0bde](https://github.com/fengyuanchen/cropperjs/commit/aad0bde))\n* refactor(element-image): change the default value of the `rotatable`, `scalable`, `skewable`, `trans ([1b5992e](https://github.com/fengyuanchen/cropperjs/commit/1b5992e))\n* refactor(element-image): use a copy of the internal matrix for avoiding side effects ([9761125](https://github.com/fengyuanchen/cropperjs/commit/9761125))\n\n\n\n## 2.0.0-beta.5 (2024-04-21)\n\n* build: release 2.0.0-beta.5 ([8e1b201](https://github.com/fengyuanchen/cropperjs/commit/8e1b201))\n* build: update dependencies ([739ceb1](https://github.com/fengyuanchen/cropperjs/commit/739ceb1))\n* build: upgrade to vitepress 1.1.3 ([37e48d2](https://github.com/fengyuanchen/cropperjs/commit/37e48d2))\n* docs: add reset all feature ([7a204ae](https://github.com/fengyuanchen/cropperjs/commit/7a204ae))\n* docs: drop useless images ([7b69e39](https://github.com/fengyuanchen/cropperjs/commit/7b69e39))\n* docs: update theme variables ([3a6e2f4](https://github.com/fengyuanchen/cropperjs/commit/3a6e2f4))\n* docs(cropperjs): add exported modules section ([5b6ed96](https://github.com/fengyuanchen/cropperjs/commit/5b6ed96))\n* feat: export `DEFAULT_TEMPLATE` (#1156) ([2208e73](https://github.com/fengyuanchen/cropperjs/commit/2208e73)), closes [#1156](https://github.com/fengyuanchen/cropperjs/issues/1156)\n* feat(cropper-selection): add `$clear` method and improve  `$reset` method (#1157) ([8e6dbb0](https://github.com/fengyuanchen/cropperjs/commit/8e6dbb0)), closes [#1157](https://github.com/fengyuanchen/cropperjs/issues/1157)\n* refactor: change  the translateY calculation to avoid side effects ([4d2665e](https://github.com/fengyuanchen/cropperjs/commit/4d2665e)), closes [#1152](https://github.com/fengyuanchen/cropperjs/issues/1152)\n* refactor(cropper-viewer): fallback to canvas if the selection is invalid ([4e4ce43](https://github.com/fengyuanchen/cropperjs/commit/4e4ce43))\n* refactor(cropperjs): add missing semicolon and move position ([003ffa0](https://github.com/fengyuanchen/cropperjs/commit/003ffa0))\n* fix(cropper-viewer): calculate translateY correctly when rotated 90deg (#1152) ([2dea551](https://github.com/fengyuanchen/cropperjs/commit/2dea551)), closes [#1152](https://github.com/fengyuanchen/cropperjs/issues/1152) [#1031](https://github.com/fengyuanchen/cropperjs/issues/1031)\n\n\n\n## 2.0.0-beta.4 (2023-08-20)\n\n* build: release 2.0.0-beta.4 ([163b419](https://github.com/fengyuanchen/cropperjs/commit/163b419))\n* build: update dependencies ([76f9874](https://github.com/fengyuanchen/cropperjs/commit/76f9874))\n* refactor(cropper-selection): improve selection switching UX ([e440638](https://github.com/fengyuanchen/cropperjs/commit/e440638))\n* docs: drop the `manifest.webmanifest.json` file ([684bbe4](https://github.com/fengyuanchen/cropperjs/commit/684bbe4))\n* docs(cropper-selection): improve multiple selections demo ([8460a97](https://github.com/fengyuanchen/cropperjs/commit/8460a97))\n* ci: remove v2 directory before copy files ([25d64bf](https://github.com/fengyuanchen/cropperjs/commit/25d64bf))\n* ci: use node 18 ([6f27efa](https://github.com/fengyuanchen/cropperjs/commit/6f27efa))\n* fix(cropper-image): make it a block element to avoid side effects ([c3d94fc](https://github.com/fengyuanchen/cropperjs/commit/c3d94fc)), closes [#1074](https://github.com/fengyuanchen/cropperjs/issues/1074)\n* fix(cropper-selection): change inactive selections synchronously when scaling ([2e8d67f](https://github.com/fengyuanchen/cropperjs/commit/2e8d67f))\n* fix(cropper-selection): improve  interaction between multiple selections ([f2bd525](https://github.com/fengyuanchen/cropperjs/commit/f2bd525)), closes [#1044](https://github.com/fengyuanchen/cropperjs/issues/1044) [#1061](https://github.com/fengyuanchen/cropperjs/issues/1061)\n\n\n\n## 2.0.0-beta.3 (2023-06-18)\n\n* build: release 2.0.0-beta.3 ([3bbcba4](https://github.com/fengyuanchen/cropperjs/commit/3bbcba4))\n* build: update dependencies ([c015559](https://github.com/fengyuanchen/cropperjs/commit/c015559))\n* build: update dependencies ([f69579c](https://github.com/fengyuanchen/cropperjs/commit/f69579c))\n* style: fix jsdoc comments ([16447cf](https://github.com/fengyuanchen/cropperjs/commit/16447cf))\n* style: sort order by stylelint ([3698074](https://github.com/fengyuanchen/cropperjs/commit/3698074))\n* docs: add missing page title ([b9bc75c](https://github.com/fengyuanchen/cropperjs/commit/b9bc75c))\n* docs: migrate Vuepress to Vitepress ([cb1425d](https://github.com/fengyuanchen/cropperjs/commit/cb1425d))\n* fix: blur the removing selection to avoid side-effects ([0a334d9](https://github.com/fengyuanchen/cropperjs/commit/0a334d9)), closes [#1022](https://github.com/fengyuanchen/cropperjs/issues/1022)\n\n\n\n## 2.0.0-beta.2 (2022-12-04)\n\n* build: add missing config for eslint ([0dbf411](https://github.com/fengyuanchen/cropperjs/commit/0dbf411))\n* build: release 2.0.0-beta.2 ([78de561](https://github.com/fengyuanchen/cropperjs/commit/78de561))\n* build: update config files ([5a71ee5](https://github.com/fengyuanchen/cropperjs/commit/5a71ee5))\n* build: update dependencies ([2ca9013](https://github.com/fengyuanchen/cropperjs/commit/2ca9013))\n* refactor: extract matrices multiplying as a utility function ([d21e093](https://github.com/fengyuanchen/cropperjs/commit/d21e093))\n* refactor: fix an eslint error ([c7c786a](https://github.com/fengyuanchen/cropperjs/commit/c7c786a))\n* refactor: improve the `$toCanvas` method for better image quality ([6365ab7](https://github.com/fengyuanchen/cropperjs/commit/6365ab7))\n* refactor(element-select): improve move interaction ([91907d8](https://github.com/fengyuanchen/cropperjs/commit/91907d8))\n* refactor(element-selection): improve code style ([0164109](https://github.com/fengyuanchen/cropperjs/commit/0164109))\n* test: add test cases for `multiplyMatrices` function ([8b327f5](https://github.com/fengyuanchen/cropperjs/commit/8b327f5))\n* fix(element-image): ignore selection move action ([c2aa7d9](https://github.com/fengyuanchen/cropperjs/commit/c2aa7d9)), closes [#985](https://github.com/fengyuanchen/cropperjs/issues/985)\n* docs: add missing slash ([888f001](https://github.com/fengyuanchen/cropperjs/commit/888f001))\n* docs: add zh locale ([fb227b3](https://github.com/fengyuanchen/cropperjs/commit/fb227b3))\n* docs: improve colors in drak mode ([b0f6f9e](https://github.com/fengyuanchen/cropperjs/commit/b0f6f9e))\n\n\n\n## 2.0.0-beta.1 (2022-06-19)\n\n* build: add entry file for unpkg and jsdelivr ([7303dad](https://github.com/fengyuanchen/cropperjs/commit/7303dad))\n* build: release 2.0.0-beta.1 ([f9b7e9f](https://github.com/fengyuanchen/cropperjs/commit/f9b7e9f))\n* fix: refresh viewer when the source image changed ([ab866e4](https://github.com/fengyuanchen/cropperjs/commit/ab866e4)), closes [#940](https://github.com/fengyuanchen/cropperjs/issues/940)\n* fix(element-canvas): correct the opposite scale direction ([9616427](https://github.com/fengyuanchen/cropperjs/commit/9616427))\n* fix(element-selection): correct canvas drawing position ([2604d15](https://github.com/fengyuanchen/cropperjs/commit/2604d15)), closes [#939](https://github.com/fengyuanchen/cropperjs/issues/939)\n* docs: drop unbubbled `load` events ([e2cc34c](https://github.com/fengyuanchen/cropperjs/commit/e2cc34c))\n\n\n\n## 2.0.0-beta (2022-05-01)\n\n* build: 简化 husky 命令 ([eaf868a](https://github.com/fengyuanchen/cropperjs/commit/eaf868a))\n* build: add jsdoc plugin for eslint ([5dfd4fc](https://github.com/fengyuanchen/cropperjs/commit/5dfd4fc))\n* build: clean up files for next version ([ae5733a](https://github.com/fengyuanchen/cropperjs/commit/ae5733a))\n* build: collect elements and helpers ([d38171d](https://github.com/fengyuanchen/cropperjs/commit/d38171d))\n* build: correct repo name ([1c89d5e](https://github.com/fengyuanchen/cropperjs/commit/1c89d5e))\n* build: improve bundled files ([851afff](https://github.com/fengyuanchen/cropperjs/commit/851afff))\n* build: improve config ([a4de3d4](https://github.com/fengyuanchen/cropperjs/commit/a4de3d4))\n* build: init ([0e279f7](https://github.com/fengyuanchen/cropperjs/commit/0e279f7))\n* build: migrate VuePress from v1 to v2 ([25d4437](https://github.com/fengyuanchen/cropperjs/commit/25d4437))\n* build: move `isCustomElement` into compilerOptions ([213151e](https://github.com/fengyuanchen/cropperjs/commit/213151e))\n* build: move up the `version` field ([4c55115](https://github.com/fengyuanchen/cropperjs/commit/4c55115))\n* build: process inline css with postcss ([67156c1](https://github.com/fengyuanchen/cropperjs/commit/67156c1))\n* build: release 2.0.0-beta ([f13e2bd](https://github.com/fengyuanchen/cropperjs/commit/f13e2bd))\n* build: separate config into files ([3dd7146](https://github.com/fengyuanchen/cropperjs/commit/3dd7146))\n* build: simplify config ([9a7f8c9](https://github.com/fengyuanchen/cropperjs/commit/9a7f8c9))\n* build: simplify configurations ([134558c](https://github.com/fengyuanchen/cropperjs/commit/134558c))\n* build: update ([83bd87f](https://github.com/fengyuanchen/cropperjs/commit/83bd87f))\n* build: update ([609a28e](https://github.com/fengyuanchen/cropperjs/commit/609a28e))\n* build: update ([b9ff6fd](https://github.com/fengyuanchen/cropperjs/commit/b9ff6fd))\n* build: update ([6f9bcb3](https://github.com/fengyuanchen/cropperjs/commit/6f9bcb3))\n* build: update ([7118faa](https://github.com/fengyuanchen/cropperjs/commit/7118faa))\n* build: update ([406a552](https://github.com/fengyuanchen/cropperjs/commit/406a552))\n* build: update ([c70464b](https://github.com/fengyuanchen/cropperjs/commit/c70464b))\n* build: update ([3caf1cb](https://github.com/fengyuanchen/cropperjs/commit/3caf1cb))\n* build: update ([97b08c2](https://github.com/fengyuanchen/cropperjs/commit/97b08c2))\n* build: update ([d436531](https://github.com/fengyuanchen/cropperjs/commit/d436531))\n* build: update ([d924b38](https://github.com/fengyuanchen/cropperjs/commit/d924b38))\n* build: update ([cf2b11d](https://github.com/fengyuanchen/cropperjs/commit/cf2b11d))\n* build: update ([c9bb962](https://github.com/fengyuanchen/cropperjs/commit/c9bb962))\n* build: update ([77aac17](https://github.com/fengyuanchen/cropperjs/commit/77aac17))\n* build: update building temp folder ([cef7b20](https://github.com/fengyuanchen/cropperjs/commit/cef7b20))\n* build: update dependencies ([a76bb77](https://github.com/fengyuanchen/cropperjs/commit/a76bb77))\n* build: update dependencies ([f7a60c5](https://github.com/fengyuanchen/cropperjs/commit/f7a60c5))\n* build: update dependencies ([9c0d861](https://github.com/fengyuanchen/cropperjs/commit/9c0d861))\n* build: update dependencies ([dcc47ca](https://github.com/fengyuanchen/cropperjs/commit/dcc47ca))\n* build: update dependencies ([f4c2cfc](https://github.com/fengyuanchen/cropperjs/commit/f4c2cfc))\n* build: update dependencies ([b66aeaa](https://github.com/fengyuanchen/cropperjs/commit/b66aeaa))\n* build: update dependencies ([3dc8045](https://github.com/fengyuanchen/cropperjs/commit/3dc8045))\n* build: update dependencies ([b4315fe](https://github.com/fengyuanchen/cropperjs/commit/b4315fe))\n* build: update dependencies ([4e2e5e0](https://github.com/fengyuanchen/cropperjs/commit/4e2e5e0))\n* build: update dependencies ([e073100](https://github.com/fengyuanchen/cropperjs/commit/e073100))\n* build: update dependencies ([69f28ab](https://github.com/fengyuanchen/cropperjs/commit/69f28ab))\n* build: update dev dependencies ([3cc1bbc](https://github.com/fengyuanchen/cropperjs/commit/3cc1bbc))\n* build: update huksy commands ([c9db707](https://github.com/fengyuanchen/cropperjs/commit/c9db707))\n* build: upgrade husky from v4 to v6 ([c597d90](https://github.com/fengyuanchen/cropperjs/commit/c597d90))\n* build: upgrade jest to v27 ([b9d9e84](https://github.com/fengyuanchen/cropperjs/commit/b9d9e84))\n* build: upgrade vuepress ([526b3fa](https://github.com/fengyuanchen/cropperjs/commit/526b3fa))\n* build: upgrade vuepress to v2.0.0-beta.18 ([15b9e8a](https://github.com/fengyuanchen/cropperjs/commit/15b9e8a))\n* build: use jsdom as test environment ([4f5e8b8](https://github.com/fengyuanchen/cropperjs/commit/4f5e8b8))\n* build(docs): use webpack as bundler ([5b836b6](https://github.com/fengyuanchen/cropperjs/commit/5b836b6))\n* fix: import type only ([590b83e](https://github.com/fengyuanchen/cropperjs/commit/590b83e))\n* fix: improve size adjusting ([abbdb54](https://github.com/fengyuanchen/cropperjs/commit/abbdb54))\n* fix: make the focusing in selection actionable ([a20c30b](https://github.com/fengyuanchen/cropperjs/commit/a20c30b))\n* fix: remove event listeners when disconnected ([d712947](https://github.com/fengyuanchen/cropperjs/commit/d712947))\n* fix(cropperjs): add missing initial attribute ([81c6d49](https://github.com/fengyuanchen/cropperjs/commit/81c6d49))\n* fix(element-canvas): add missing attribute to observe ([87fed15](https://github.com/fengyuanchen/cropperjs/commit/87fed15))\n* fix(element-canvas): fix type and add missing doc ([af831eb](https://github.com/fengyuanchen/cropperjs/commit/af831eb))\n* fix(element-selection): drop incorrect code ([039700f](https://github.com/fengyuanchen/cropperjs/commit/039700f))\n* fix(element-selection): update `active` when `multiple`  changed ([c36b026](https://github.com/fengyuanchen/cropperjs/commit/c36b026))\n* fix(element-shade): toggle only when the action is select ([34775ff](https://github.com/fengyuanchen/cropperjs/commit/34775ff))\n* fix(element): drop `$name`'s value to avoid side effects ([97c0c60](https://github.com/fengyuanchen/cropperjs/commit/97c0c60))\n* docs: add base URL ([8f47524](https://github.com/fengyuanchen/cropperjs/commit/8f47524))\n* docs: add logo, icons, and PWA support ([3759090](https://github.com/fengyuanchen/cropperjs/commit/3759090))\n* docs: add specifications for implementing `CropperElement` ([cd9376a](https://github.com/fengyuanchen/cropperjs/commit/cd9376a))\n* docs: change the status of the `cropper` library ([d5ec64e](https://github.com/fengyuanchen/cropperjs/commit/d5ec64e))\n* docs: drop build status badge ([cac5340](https://github.com/fengyuanchen/cropperjs/commit/cac5340))\n* docs: drop examples ([c3552bf](https://github.com/fengyuanchen/cropperjs/commit/c3552bf))\n* docs: drop examples links ([129be1a](https://github.com/fengyuanchen/cropperjs/commit/129be1a))\n* docs: fix brand colors ([17553de](https://github.com/fengyuanchen/cropperjs/commit/17553de))\n* docs: fix paths of the images ([618dcfd](https://github.com/fengyuanchen/cropperjs/commit/618dcfd))\n* docs: fix site link and add branch hint ([95db2de](https://github.com/fengyuanchen/cropperjs/commit/95db2de))\n* docs: fix typo ([5052de9](https://github.com/fengyuanchen/cropperjs/commit/5052de9))\n* docs: fix typos ([bc50442](https://github.com/fengyuanchen/cropperjs/commit/bc50442))\n* docs: fix typos ([cbf4691](https://github.com/fengyuanchen/cropperjs/commit/cbf4691))\n* docs: improve ([062c65a](https://github.com/fengyuanchen/cropperjs/commit/062c65a))\n* docs: improve components and cofigurations ([6cf6573](https://github.com/fengyuanchen/cropperjs/commit/6cf6573))\n* docs: improve content ([d5d1d57](https://github.com/fengyuanchen/cropperjs/commit/d5d1d57))\n* docs: improve demo code ([8a2f374](https://github.com/fengyuanchen/cropperjs/commit/8a2f374))\n* docs: improve distribution files title ([9a67f46](https://github.com/fengyuanchen/cropperjs/commit/9a67f46))\n* docs: improve playground ([662acfc](https://github.com/fengyuanchen/cropperjs/commit/662acfc))\n* docs: integrate Google Analytics ([62128b4](https://github.com/fengyuanchen/cropperjs/commit/62128b4))\n* docs: keep the attributes the same as the template's ([1b5e5e9](https://github.com/fengyuanchen/cropperjs/commit/1b5e5e9))\n* docs: resolve console error messages ([a8c1177](https://github.com/fengyuanchen/cropperjs/commit/a8c1177))\n* docs: simplify folders and content ([a82f30c](https://github.com/fengyuanchen/cropperjs/commit/a82f30c))\n* docs: update repo settings ([5dfa12d](https://github.com/fengyuanchen/cropperjs/commit/5dfa12d))\n* feat: add `$getTagNameOf` method to get real tag name ([cfc3a41](https://github.com/fengyuanchen/cropperjs/commit/cfc3a41))\n* feat: add some methods to Cropper interface ([1428e8c](https://github.com/fengyuanchen/cropperjs/commit/1428e8c))\n* feat: allow to zoom image and selection together ([a8dcbe7](https://github.com/fengyuanchen/cropperjs/commit/a8dcbe7))\n* feat: support two-finger touch rotation ([0b3f262](https://github.com/fengyuanchen/cropperjs/commit/0b3f262))\n* feat(cropper-image): support to rotate by offset ([1993e2e](https://github.com/fengyuanchen/cropperjs/commit/1993e2e))\n* feat(element-canvas): add 2 options to `$toCanvas` ([8f9e21e](https://github.com/fengyuanchen/cropperjs/commit/8f9e21e))\n* feat(element-selection): add 2 options to $toCanvas ([e3cdfd9](https://github.com/fengyuanchen/cropperjs/commit/e3cdfd9))\n* feat(types): add `Selection` interface ([51855c4](https://github.com/fengyuanchen/cropperjs/commit/51855c4))\n* ci: add deploy.yml ([05762c1](https://github.com/fengyuanchen/cropperjs/commit/05762c1))\n* ci: add v2 dir only ([c7b6b1c](https://github.com/fengyuanchen/cropperjs/commit/c7b6b1c))\n* ci: exclude v2.0.0-alpha* ([33ed5de](https://github.com/fengyuanchen/cropperjs/commit/33ed5de))\n* ci: improve ci workflow ([9755862](https://github.com/fengyuanchen/cropperjs/commit/9755862))\n* style: add note comment ([7af366f](https://github.com/fengyuanchen/cropperjs/commit/7af366f))\n* style: drop useless parameter ([7f6a2eb](https://github.com/fengyuanchen/cropperjs/commit/7f6a2eb))\n* style: fix eslint errors ([44b867c](https://github.com/fengyuanchen/cropperjs/commit/44b867c))\n* style: fix eslint errors ([cd04646](https://github.com/fengyuanchen/cropperjs/commit/cd04646))\n* style: fix stylelint errors ([0730dec](https://github.com/fengyuanchen/cropperjs/commit/0730dec))\n*  ci: create directory first if it does not exist ([52871bb](https://github.com/fengyuanchen/cropperjs/commit/52871bb))\n* refactor!: rename CropperCanvas's `scale` property to `scaleStep` ([bafa533](https://github.com/fengyuanchen/cropperjs/commit/bafa533))\n* refactor: drop the `$setTransformByOffset` method ([241640a](https://github.com/fengyuanchen/cropperjs/commit/241640a))\n* refactor: improve ([55a5905](https://github.com/fengyuanchen/cropperjs/commit/55a5905))\n* refactor: improve code, docs, and playground ([d243243](https://github.com/fengyuanchen/cropperjs/commit/d243243))\n* refactor: improve Cropper ([17d07ef](https://github.com/fengyuanchen/cropperjs/commit/17d07ef))\n* refactor: improve dependents ([f3cdadc](https://github.com/fengyuanchen/cropperjs/commit/f3cdadc))\n* refactor: improve elements and documentation ([5c370c7](https://github.com/fengyuanchen/cropperjs/commit/5c370c7))\n* refactor: improve events binding ([aa6be90](https://github.com/fengyuanchen/cropperjs/commit/aa6be90))\n* refactor: improve properties observing ([018c0ef](https://github.com/fengyuanchen/cropperjs/commit/018c0ef))\n* refactor: improve selection operating ([fa88586](https://github.com/fengyuanchen/cropperjs/commit/fa88586))\n* refactor: improve the `nextTick` utility function ([903e7a9](https://github.com/fengyuanchen/cropperjs/commit/903e7a9))\n* refactor: improve the move action behaviour ([bdd43ab](https://github.com/fengyuanchen/cropperjs/commit/bdd43ab))\n* refactor: merge `autoSelect` and `autoSelectArea` properties into `initialCoverage` property ([2582aed](https://github.com/fengyuanchen/cropperjs/commit/2582aed))\n* refactor: merge all helpers in the utils package ([f54228a](https://github.com/fengyuanchen/cropperjs/commit/f54228a))\n* refactor: rename `precision` to `precise` ([b654108](https://github.com/fengyuanchen/cropperjs/commit/b654108))\n* test: fix style property ([da86c43](https://github.com/fengyuanchen/cropperjs/commit/da86c43))\n* test(cropperjs): improve test case ([52c2a80](https://github.com/fengyuanchen/cropperjs/commit/52c2a80))\n* refacror: improve the `$center` and `$fit` methods ([21039d7](https://github.com/fengyuanchen/cropperjs/commit/21039d7))\n* perf: use `transform` for better performance ([23ab36b](https://github.com/fengyuanchen/cropperjs/commit/23ab36b))\n* chore: sort element packages ([c1b8af7](https://github.com/fengyuanchen/cropperjs/commit/c1b8af7))\n\n\n\n## 2.0.0-alpha.2 (2021-12-25)\n\n* build: add postcss dependency ([ca45f09](https://github.com/fengyuanchen/cropperjs/commit/ca45f09))\n* build: exports default module for CommonJS ([5836c46](https://github.com/fengyuanchen/cropperjs/commit/5836c46))\n* build: fix wrong file match pattern ([515bc81](https://github.com/fengyuanchen/cropperjs/commit/515bc81))\n* build: improve config ([edde79c](https://github.com/fengyuanchen/cropperjs/commit/edde79c))\n* build: release 2.0.0-alpha.2 ([a4378d5](https://github.com/fengyuanchen/cropperjs/commit/a4378d5))\n* build: simplify husky config ([20f70de](https://github.com/fengyuanchen/cropperjs/commit/20f70de))\n* build: update dependencies ([b2bd7ba](https://github.com/fengyuanchen/cropperjs/commit/b2bd7ba))\n* build: update dependencies ([2f1e322](https://github.com/fengyuanchen/cropperjs/commit/2f1e322))\n* build: update dependencies ([9ed8dc5](https://github.com/fengyuanchen/cropperjs/commit/9ed8dc5))\n* build: upgrade to husky 6 from 4 ([f0dab99](https://github.com/fengyuanchen/cropperjs/commit/f0dab99))\n* style: drop .scss extension ([b4481f4](https://github.com/fengyuanchen/cropperjs/commit/b4481f4))\n* style: fix eslint errors ([6466797](https://github.com/fengyuanchen/cropperjs/commit/6466797))\n* style: fix stylelint errors ([0830355](https://github.com/fengyuanchen/cropperjs/commit/0830355))\n* style: remove useless variable ([1f17255](https://github.com/fengyuanchen/cropperjs/commit/1f17255))\n* docs: add missing type of original event for the zoom event ([be89c55](https://github.com/fengyuanchen/cropperjs/commit/be89c55))\n* docs: add note about upload errors ([072220e](https://github.com/fengyuanchen/cropperjs/commit/072220e)), closes [#820](https://github.com/fengyuanchen/cropperjs/issues/820)\n* docs: add note for limiting max zoom ratio ([e3b8571](https://github.com/fengyuanchen/cropperjs/commit/e3b8571))\n* docs: disable zoomable to avoid side-effect ([d360b12](https://github.com/fengyuanchen/cropperjs/commit/d360b12)), closes [#837](https://github.com/fengyuanchen/cropperjs/issues/837)\n* docs: fix grammar errors ([852d743](https://github.com/fengyuanchen/cropperjs/commit/852d743))\n* docs: fix quotes ([b05b0e4](https://github.com/fengyuanchen/cropperjs/commit/b05b0e4))\n* docs: fix typos ([ef0447e](https://github.com/fengyuanchen/cropperjs/commit/ef0447e))\n* docs: fix wrong closing tag ([4604233](https://github.com/fengyuanchen/cropperjs/commit/4604233))\n* docs: improove notice for image styles ([b5e0ae8](https://github.com/fengyuanchen/cropperjs/commit/b5e0ae8))\n* docs: improve demo layout ([3bcc9ca](https://github.com/fengyuanchen/cropperjs/commit/3bcc9ca))\n* docs: improve event description ([ca6b79d](https://github.com/fengyuanchen/cropperjs/commit/ca6b79d))\n* docs: improve image importing ([72510a0](https://github.com/fengyuanchen/cropperjs/commit/72510a0)), closes [#724](https://github.com/fengyuanchen/cropperjs/issues/724)\n* docs: remove redundant HTML tags (#821) ([fccca8a](https://github.com/fengyuanchen/cropperjs/commit/fccca8a)), closes [#821](https://github.com/fengyuanchen/cropperjs/issues/821)\n* docs: rename `master` branch to `main` ([a773503](https://github.com/fengyuanchen/cropperjs/commit/a773503))\n* docs: replace \"ratio\" with \"scale\" for zooming feature ([9786c5f](https://github.com/fengyuanchen/cropperjs/commit/9786c5f))\n* docs: simplify changelog ([6bc40d6](https://github.com/fengyuanchen/cropperjs/commit/6bc40d6))\n* docs: update links of the external dependencies ([1605410](https://github.com/fengyuanchen/cropperjs/commit/1605410))\n* docs: update react-cropper repo url (#698) ([ecc5125](https://github.com/fengyuanchen/cropperjs/commit/ecc5125)), closes [#698](https://github.com/fengyuanchen/cropperjs/issues/698)\n* ci: move to GitHub Actions ([cc3154c](https://github.com/fengyuanchen/cropperjs/commit/cc3154c))\n* ci: use LTS version of Node.js ([104d914](https://github.com/fengyuanchen/cropperjs/commit/104d914))\n* fix: add `$cropper-image-path` variable into the SCSS file for Sass users (#498) ([086f260](https://github.com/fengyuanchen/cropperjs/commit/086f260)), closes [#498](https://github.com/fengyuanchen/cropperjs/issues/498)\n* fix: check if the preview image is ready ([38c3f67](https://github.com/fengyuanchen/cropperjs/commit/38c3f67)), closes [#654](https://github.com/fengyuanchen/cropperjs/issues/654)\n* fix: improve container resizing ([d9e3f76](https://github.com/fengyuanchen/cropperjs/commit/d9e3f76)), closes [#636](https://github.com/fengyuanchen/cropperjs/issues/636)\n* fix: improve ratio comparsion for negative value ([a165bf1](https://github.com/fengyuanchen/cropperjs/commit/a165bf1)), closes [#726](https://github.com/fengyuanchen/cropperjs/issues/726)\n* fix: IS_TOUCH_DEVICE (#614) ([2ebf9ec](https://github.com/fengyuanchen/cropperjs/commit/2ebf9ec)), closes [#614](https://github.com/fengyuanchen/cropperjs/issues/614)\n* fix: keep back compatibility (#799) ([2f939db](https://github.com/fengyuanchen/cropperjs/commit/2f939db)), closes [#799](https://github.com/fengyuanchen/cropperjs/issues/799)\n* fix: move initial image data storing to a reasonable place ([bbdc758](https://github.com/fengyuanchen/cropperjs/commit/bbdc758))\n* fix: set the request to be asynchronous explicitly ([9ac4738](https://github.com/fengyuanchen/cropperjs/commit/9ac4738)), closes [#682](https://github.com/fengyuanchen/cropperjs/issues/682)\n* fix: support to set the minContainerWidth/Height to 0 ([db8b851](https://github.com/fengyuanchen/cropperjs/commit/db8b851)), closes [#683](https://github.com/fengyuanchen/cropperjs/issues/683)\n* fix: typescript typing (#791) ([7db38ce](https://github.com/fengyuanchen/cropperjs/commit/7db38ce)), closes [#791](https://github.com/fengyuanchen/cropperjs/issues/791)\n* fix: use the max ratio of the X/Y-axis for resizing ([1c822ef](https://github.com/fengyuanchen/cropperjs/commit/1c822ef)), closes [#835](https://github.com/fengyuanchen/cropperjs/issues/835)\n* fix(types): add missing `MouseEvent` to the original event of the `zoom` event ([5018357](https://github.com/fengyuanchen/cropperjs/commit/5018357))\n* fix(types): improve the types for the `preview` option (#731) ([1f4f3f9](https://github.com/fengyuanchen/cropperjs/commit/1f4f3f9)), closes [#731](https://github.com/fengyuanchen/cropperjs/issues/731)\n* test: improve test cases for `wheelZoomRatio` ([f33734c](https://github.com/fengyuanchen/cropperjs/commit/f33734c))\n* test: make `wheelZoomRatio` test check less strict (round up to tenths) (#814) ([b5b9a4f](https://github.com/fengyuanchen/cropperjs/commit/b5b9a4f)), closes [#814](https://github.com/fengyuanchen/cropperjs/issues/814)\n* feat(types): add TypeScript declarations for the events ([68200c0](https://github.com/fengyuanchen/cropperjs/commit/68200c0))\n* perf: simplify max zoom ratio computing ([6039b12](https://github.com/fengyuanchen/cropperjs/commit/6039b12))\n* chore: add notes ([46d4d4f](https://github.com/fengyuanchen/cropperjs/commit/46d4d4f))\n* chore: update external links ([02dc521](https://github.com/fengyuanchen/cropperjs/commit/02dc521))\n* refactor: export additional types (#715) ([9fa2d00](https://github.com/fengyuanchen/cropperjs/commit/9fa2d00)), closes [#715](https://github.com/fengyuanchen/cropperjs/issues/715)\n\n\n\n## 2.0.0-alpha.1 (2019-11-09)\n\n* build: move to root ([5a0c068](https://github.com/fengyuanchen/cropperjs/commit/5a0c068))\n* build: release 2.0.0-alpha ([a334dd3](https://github.com/fengyuanchen/cropperjs/commit/a334dd3))\n* build: release 2.0.0-alpha.1 ([0ad96bb](https://github.com/fengyuanchen/cropperjs/commit/0ad96bb))\n* build: update dependencies ([cd3fc8e](https://github.com/fengyuanchen/cropperjs/commit/cd3fc8e))\n* build: update dependencies ([1999357](https://github.com/fengyuanchen/cropperjs/commit/1999357))\n* build(deps): bump lodash.template from 4.4.0 to 4.5.0 (#601) ([66da1fc](https://github.com/fengyuanchen/cropperjs/commit/66da1fc)), closes [#601](https://github.com/fengyuanchen/cropperjs/issues/601)\n* test: add test suties for new options ([259359e](https://github.com/fengyuanchen/cropperjs/commit/259359e))\n* test: update some suites ([2b4a528](https://github.com/fengyuanchen/cropperjs/commit/2b4a528))\n* fix: drop limited property ([d04b9c8](https://github.com/fengyuanchen/cropperjs/commit/d04b9c8))\n* fix: improve crop box changing ([ea86afb](https://github.com/fengyuanchen/cropperjs/commit/ea86afb))\n* fix: improve first cropping when there is aspect ratio ([99e6b54](https://github.com/fengyuanchen/cropperjs/commit/99e6b54))\n* refactor: improve crop experience ([a71b38e](https://github.com/fengyuanchen/cropperjs/commit/a71b38e))\n* refactor: simplify styles ([5de75a1](https://github.com/fengyuanchen/cropperjs/commit/5de75a1))\n* feat: add 4 new options for the canvas and crop box ([e2273fd](https://github.com/fengyuanchen/cropperjs/commit/e2273fd))\n* docs: add crossOrigin attribute to links ([48f653a](https://github.com/fengyuanchen/cropperjs/commit/48f653a))\n\n\n\n## <small>1.5.6 (2019-10-04)</small>\n\n* build: release 1.5.6 ([89f0b50](https://github.com/fengyuanchen/cropperjs/commit/89f0b50))\n* style: fix eslint errors ([70918dc](https://github.com/fengyuanchen/cropperjs/commit/70918dc))\n* fix: improve event type determining for iOS 13+ ([7f7422c](https://github.com/fengyuanchen/cropperjs/commit/7f7422c)), closes [#571](https://github.com/fengyuanchen/cropperjs/issues/571)\n* docs: improve comments ([8fa586f](https://github.com/fengyuanchen/cropperjs/commit/8fa586f))\n\n\n\n## <small>1.5.5 (2019-08-04)</small>\n\n* build: release 1.5.5 ([819bff4](https://github.com/fengyuanchen/cropperjs/commit/819bff4))\n* fix: clone the image's `crossOrigin` attribute always ([8d89392](https://github.com/fengyuanchen/cropperjs/commit/8d89392)), closes [#535](https://github.com/fengyuanchen/cropperjs/issues/535) [#552](https://github.com/fengyuanchen/cropperjs/issues/552)\n* fix: improve browser detecting (#554) ([54b30e5](https://github.com/fengyuanchen/cropperjs/commit/54b30e5)), closes [#554](https://github.com/fengyuanchen/cropperjs/issues/554)\n\n\n\n## <small>1.5.4 (2019-07-20)</small>\n\n* build: release 1.5.4 ([6f9218d](https://github.com/fengyuanchen/cropperjs/commit/6f9218d))\n* fix: add `alt` attribute to all internal images for better accessibility ([5036473](https://github.com/fengyuanchen/cropperjs/commit/5036473)), closes [#548](https://github.com/fengyuanchen/cropperjs/issues/548)\n* fix: avoid removing the events of the original image ([76d5949](https://github.com/fengyuanchen/cropperjs/commit/76d5949))\n* fix: avoid requesting Data URLs by XMLHttpRequest for better performance ([1eae10c](https://github.com/fengyuanchen/cropperjs/commit/1eae10c)), closes [#526](https://github.com/fengyuanchen/cropperjs/issues/526)\n* fix: transform the TS types for the drag mode, smoothing quality and view mode options (#550) ([f4e306f](https://github.com/fengyuanchen/cropperjs/commit/f4e306f)), closes [#550](https://github.com/fengyuanchen/cropperjs/issues/550)\n* fix: transform the ViewMode enum too ([153f9e3](https://github.com/fengyuanchen/cropperjs/commit/153f9e3))\n\n\n\n## <small>1.5.3 (2019-07-10)</small>\n\n* build: fix vulnerabilities ([64e9a9e](https://github.com/fengyuanchen/cropperjs/commit/64e9a9e))\n* build: release 1.5.3 ([abc0c2a](https://github.com/fengyuanchen/cropperjs/commit/abc0c2a))\n* style: improve comment ([a034294](https://github.com/fengyuanchen/cropperjs/commit/a034294))\n* chore: update ignoring rules ([ecfa89f](https://github.com/fengyuanchen/cropperjs/commit/ecfa89f))\n* fix: check all browsers use WebKit as the layout engine in iOS devices ([c9a83d3](https://github.com/fengyuanchen/cropperjs/commit/c9a83d3)), closes [#544](https://github.com/fengyuanchen/cropperjs/issues/544) [#545](https://github.com/fengyuanchen/cropperjs/issues/545)\n* docs: add lossy compression description ([921aaf5](https://github.com/fengyuanchen/cropperjs/commit/921aaf5))\n* docs: fix typos ([da5cc9c](https://github.com/fengyuanchen/cropperjs/commit/da5cc9c))\n\n\n\n## <small>1.5.2 (2019-06-30)</small>\n\n* build: release 1.5.2 ([006cc50](https://github.com/fengyuanchen/cropperjs/commit/006cc50))\n* docs: fix typos ([38e24ac](https://github.com/fengyuanchen/cropperjs/commit/38e24ac))\n* docs: update the version of the dependencies ([6101952](https://github.com/fengyuanchen/cropperjs/commit/6101952))\n* test: update test for cross origin image ([8c937ff](https://github.com/fengyuanchen/cropperjs/commit/8c937ff))\n* fix: alway add a timestamp to the url of a cross origin image ([f4b22b4](https://github.com/fengyuanchen/cropperjs/commit/f4b22b4)), closes [#519](https://github.com/fengyuanchen/cropperjs/issues/519)\n* Update Changelog (#493) ([4e3634e](https://github.com/fengyuanchen/cropperjs/commit/4e3634e)), closes [#493](https://github.com/fengyuanchen/cropperjs/issues/493)\n\n\n\n## <small>1.5.1 (2019-03-10)</small>\n\n* build: release 1.5.1 ([a45ff7b](https://github.com/fengyuanchen/cropperjs/commit/a45ff7b))\n* fix: revert the min container width and height ([3184327](https://github.com/fengyuanchen/cropperjs/commit/3184327))\n* docs: update travis badge url ([6b0bc56](https://github.com/fengyuanchen/cropperjs/commit/6b0bc56))\n\n\n\n## 1.5.0 (2019-03-10)\n\n* build: add stylelint ([0f7e746](https://github.com/fengyuanchen/cropperjs/commit/0f7e746))\n* build: merge config ([4ed2e5f](https://github.com/fengyuanchen/cropperjs/commit/4ed2e5f))\n* build: merge config into package.json ([12b1970](https://github.com/fengyuanchen/cropperjs/commit/12b1970))\n* build: release 1.5.0 ([2df11cc](https://github.com/fengyuanchen/cropperjs/commit/2df11cc))\n* build: update dependencies ([f5424ef](https://github.com/fengyuanchen/cropperjs/commit/f5424ef))\n* docs: add v2.x link ([ab1a2ba](https://github.com/fengyuanchen/cropperjs/commit/ab1a2ba))\n* docs: fix assets links ([24325ab](https://github.com/fengyuanchen/cropperjs/commit/24325ab))\n* docs: improve ([c78d938](https://github.com/fengyuanchen/cropperjs/commit/c78d938))\n* docs: improve demo code ([6759d59](https://github.com/fengyuanchen/cropperjs/commit/6759d59))\n* docs: improve GitHub templates ([e314fd6](https://github.com/fengyuanchen/cropperjs/commit/e314fd6))\n* docs: move examples into docs folder ([8eb84de](https://github.com/fengyuanchen/cropperjs/commit/8eb84de))\n* docs: update dependencies ([ee4adf5](https://github.com/fengyuanchen/cropperjs/commit/ee4adf5))\n* docs: update issue templates ([3f254cf](https://github.com/fengyuanchen/cropperjs/commit/3f254cf))\n* docs: update pull request template ([c5f07fb](https://github.com/fengyuanchen/cropperjs/commit/c5f07fb))\n* fix: avoid typed array spreading error in IE or Safari 9 ([fff0a8d](https://github.com/fengyuanchen/cropperjs/commit/fff0a8d))\n* fix: correct Safari detecting ([9db59b9](https://github.com/fengyuanchen/cropperjs/commit/9db59b9)), closes [#478](https://github.com/fengyuanchen/cropperjs/issues/478)\n* fix: ignore the pointer events are not triggered by the primary button ([a0e1e32](https://github.com/fengyuanchen/cropperjs/commit/a0e1e32))\n* perf: add options to wheel event for better performance ([4cbf6e4](https://github.com/fengyuanchen/cropperjs/commit/4cbf6e4))\n* perf: improve utilities ([61738ec](https://github.com/fengyuanchen/cropperjs/commit/61738ec))\n* perf: simplify url checking ([c8182db](https://github.com/fengyuanchen/cropperjs/commit/c8182db))\n* refactor: improve code ([b78625f](https://github.com/fengyuanchen/cropperjs/commit/b78625f))\n* refactor: sort properties ([746f98d](https://github.com/fengyuanchen/cropperjs/commit/746f98d))\n* test: add test suites for class and static methods ([04bd847](https://github.com/fengyuanchen/cropperjs/commit/04bd847))\n* test: improve helpers ([d537060](https://github.com/fengyuanchen/cropperjs/commit/d537060))\n* ci: cache dependencies ([1826ecc](https://github.com/fengyuanchen/cropperjs/commit/1826ecc))\n\n\n\n## <small>1.4.3 (2018-10-24)</small>\n\n* build: release 1.4.3 ([ed45be1](https://github.com/fengyuanchen/cropperjs/commit/ed45be1))\n* refactor: move `try...catch` into utility function ([3bb44c7](https://github.com/fengyuanchen/cropperjs/commit/3bb44c7))\n* fix: avoid range error ([5fb1774](https://github.com/fengyuanchen/cropperjs/commit/5fb1774))\n* fix: ignore range error when the image has incorrect Exif information ([2ca48d4](https://github.com/fengyuanchen/cropperjs/commit/2ca48d4))\n\n\n\n## <small>1.4.2 (2018-10-15)</small>\n\n* build: add test coverage and upgrde Babel to 7 ([7df905c](https://github.com/fengyuanchen/cropperjs/commit/7df905c))\n* build: release 1.4.2 ([b795bb9](https://github.com/fengyuanchen/cropperjs/commit/b795bb9))\n* fix: add missing static methods ([78a0ba5](https://github.com/fengyuanchen/cropperjs/commit/78a0ba5))\n* fix: fall back first parameter to empty object to avoid error ([dcfc79c](https://github.com/fengyuanchen/cropperjs/commit/dcfc79c)), closes [#432](https://github.com/fengyuanchen/cropperjs/issues/432)\n* fix: improve cropper instance storage to avoid side effect ([9f30bc7](https://github.com/fengyuanchen/cropperjs/commit/9f30bc7)), closes [#394](https://github.com/fengyuanchen/cropperjs/issues/394)\n* fix: remove useless modifier ([f218cb8](https://github.com/fengyuanchen/cropperjs/commit/f218cb8))\n* fix: reset abort event handler before abort ([bf938d1](https://github.com/fengyuanchen/cropperjs/commit/bf938d1))\n* test: simplify config ([1aa00b6](https://github.com/fengyuanchen/cropperjs/commit/1aa00b6))\n* chore: change request method to upper case ([7bbb8b5](https://github.com/fengyuanchen/cropperjs/commit/7bbb8b5))\n* docs: add donate on patron badge ([c1a98fd](https://github.com/fengyuanchen/cropperjs/commit/c1a98fd))\n* docs: add download note for outdated browsers ([313c56b](https://github.com/fengyuanchen/cropperjs/commit/313c56b))\n* docs: add filename to form data to aviod side effect ([47b7654](https://github.com/fengyuanchen/cropperjs/commit/47b7654)), closes [#425](https://github.com/fengyuanchen/cropperjs/issues/425)\n* docs: add minimum and maximum cropped dimensions example ([23b2e6c](https://github.com/fengyuanchen/cropperjs/commit/23b2e6c))\n* docs: add more explanation for `checkCrossOrigin` option ([d274c4e](https://github.com/fengyuanchen/cropperjs/commit/d274c4e)), closes [#413](https://github.com/fengyuanchen/cropperjs/issues/413)\n* docs: add reason for cache busting ([c5dcb87](https://github.com/fengyuanchen/cropperjs/commit/c5dcb87))\n* refactor: use more semantic function name ([bebd89d](https://github.com/fengyuanchen/cropperjs/commit/bebd89d)), closes [#422](https://github.com/fengyuanchen/cropperjs/issues/422)\n* perf: add characters in chunks in dataURLToArrayBuffer ([ac54c61](https://github.com/fengyuanchen/cropperjs/commit/ac54c61))\n* perf: check mime type in progress event insted of load event ([dc1da14](https://github.com/fengyuanchen/cropperjs/commit/dc1da14))\n* perf: don't recompute the DataUrl if it is already known (#422) ([af3a300](https://github.com/fengyuanchen/cropperjs/commit/af3a300)), closes [#422](https://github.com/fengyuanchen/cropperjs/issues/422)\n* perf: grow chunk size and reduce typed array ([d51a7c1](https://github.com/fengyuanchen/cropperjs/commit/d51a7c1))\n* perf: read orientation only when it is a JPEG image ([c644e0b](https://github.com/fengyuanchen/cropperjs/commit/c644e0b))\n* perf: simplify code ([aa0ea3d](https://github.com/fengyuanchen/cropperjs/commit/aa0ea3d))\n* revert: always generate a new Data URL to avoid side effect ([089f9b3](https://github.com/fengyuanchen/cropperjs/commit/089f9b3)), closes [#422](https://github.com/fengyuanchen/cropperjs/issues/422)\n\n\n\n## <small>1.4.1 (2018-07-15)</small>\n\n* build: release 1.4.1 ([f8a2da3](https://github.com/fengyuanchen/cropperjs/commit/f8a2da3))\n* build: update ([152dd88](https://github.com/fengyuanchen/cropperjs/commit/152dd88)), closes [#376](https://github.com/fengyuanchen/cropperjs/issues/376)\n* docs: add 1:1 crop box example ([9bc1716](https://github.com/fengyuanchen/cropperjs/commit/9bc1716))\n* docs: improve examples styles ([e57cb0b](https://github.com/fengyuanchen/cropperjs/commit/e57cb0b))\n* docs: only handle key down events of self ([93ab538](https://github.com/fengyuanchen/cropperjs/commit/93ab538))\n* docs: update ([aa78728](https://github.com/fengyuanchen/cropperjs/commit/aa78728))\n* fix: correct calc usage ([eee2153](https://github.com/fengyuanchen/cropperjs/commit/eee2153))\n* fix: crop box overflows in \"viewMode\" 1 and 2 (#381) ([44f50a3](https://github.com/fengyuanchen/cropperjs/commit/44f50a3)), closes [#381](https://github.com/fengyuanchen/cropperjs/issues/381)\n* fix: not to restrict the canvas position when it is not croppe ([e1b015b](https://github.com/fengyuanchen/cropperjs/commit/e1b015b))\n* fix: revert missing case of #381 ([21e14ea](https://github.com/fengyuanchen/cropperjs/commit/21e14ea)), closes [#381](https://github.com/fengyuanchen/cropperjs/issues/381)\n* perf: simplify code from #381 ([086a568](https://github.com/fengyuanchen/cropperjs/commit/086a568)), closes [#381](https://github.com/fengyuanchen/cropperjs/issues/381)\n* perf: use native `forEach` first for better performance ([e2ae655](https://github.com/fengyuanchen/cropperjs/commit/e2ae655))\n* Fix a typo in a classname (#383) ([d88d757](https://github.com/fengyuanchen/cropperjs/commit/d88d757)), closes [#383](https://github.com/fengyuanchen/cropperjs/issues/383)\n* refactor: simplify styles ([8251882](https://github.com/fengyuanchen/cropperjs/commit/8251882))\n* refactor: update code examples to es6 (#382) ([e436780](https://github.com/fengyuanchen/cropperjs/commit/e436780)), closes [#382](https://github.com/fengyuanchen/cropperjs/issues/382)\n* style: improve ([8ea8549](https://github.com/fengyuanchen/cropperjs/commit/8ea8549))\n* style: remove comment ([785f479](https://github.com/fengyuanchen/cropperjs/commit/785f479))\n* feat: add scss files ([37c998a](https://github.com/fengyuanchen/cropperjs/commit/37c998a)), closes [#360](https://github.com/fengyuanchen/cropperjs/issues/360)\n* test: fix wrong file name ([87e2f68](https://github.com/fengyuanchen/cropperjs/commit/87e2f68))\n* test: improve style ([ab096d6](https://github.com/fengyuanchen/cropperjs/commit/ab096d6))\n\n\n\n## 1.4.0 (2018-06-01)\n\n* build: release 1.4.0 ([26a1268](https://github.com/fengyuanchen/cropperjs/commit/26a1268))\n* fix: improve the smoothness of crop box resizing ([2b606c9](https://github.com/fengyuanchen/cropperjs/commit/2b606c9))\n* feat: add new option `initialAspectRatio` ([cc35974](https://github.com/fengyuanchen/cropperjs/commit/cc35974))\n* perf: unnecessary to compute ratio again ([5fde17e](https://github.com/fengyuanchen/cropperjs/commit/5fde17e))\n\n\n\n## <small>1.3.6 (2018-05-20)</small>\n\n* build: improve banner generating ([243056b](https://github.com/fengyuanchen/cropperjs/commit/243056b))\n* build: release 1.3.6 ([1b1a0c0](https://github.com/fengyuanchen/cropperjs/commit/1b1a0c0))\n* build: simplify config ([ebbb6dd](https://github.com/fengyuanchen/cropperjs/commit/ebbb6dd))\n* MIT ([665cc77](https://github.com/fengyuanchen/cropperjs/commit/665cc77))\n* fix: cancel enums exporting ([92331e8](https://github.com/fengyuanchen/cropperjs/commit/92331e8)), closes [#336](https://github.com/fengyuanchen/cropperjs/issues/336)\n* fix: check orientation only when it is rotatable and scalable ([b277a18](https://github.com/fengyuanchen/cropperjs/commit/b277a18))\n* fix: rounding off problem of getData ([1e19cd2](https://github.com/fengyuanchen/cropperjs/commit/1e19cd2)), closes [#343](https://github.com/fengyuanchen/cropperjs/issues/343)\n* fix: set checkOrientation to false when it is unnecessary ([d828952](https://github.com/fengyuanchen/cropperjs/commit/d828952))\n* test: improve ([2e8d15e](https://github.com/fengyuanchen/cropperjs/commit/2e8d15e))\n* test: refactor test suite for rounded data ([10fe559](https://github.com/fengyuanchen/cropperjs/commit/10fe559))\n* style: reference related issue in comment ([3abaa2f](https://github.com/fengyuanchen/cropperjs/commit/3abaa2f))\n\n\n\n## <small>1.3.5 (2018-04-15)</small>\n\n* build: release 1.3.5 ([a5f8f3f](https://github.com/fengyuanchen/cropperjs/commit/a5f8f3f))\n* build: set timeout value ([4ca670b](https://github.com/fengyuanchen/cropperjs/commit/4ca670b))\n* test: change the cross origin image url ([c1a9cfe](https://github.com/fengyuanchen/cropperjs/commit/c1a9cfe))\n* test: fix dataset property keys ([ead1fa7](https://github.com/fengyuanchen/cropperjs/commit/ead1fa7))\n* refactor: add namespace to data attribute names ([ad20faf](https://github.com/fengyuanchen/cropperjs/commit/ad20faf)), closes [#319](https://github.com/fengyuanchen/cropperjs/issues/319)\n* fix: ensure the cloned image loads completely before ready ([acfa14c](https://github.com/fengyuanchen/cropperjs/commit/acfa14c)), closes [#303](https://github.com/fengyuanchen/cropperjs/issues/303)\n\n\n\n## <small>1.3.4 (2018-03-31)</small>\n\n* build: release 1.3.4 ([bc201ff](https://github.com/fengyuanchen/cropperjs/commit/bc201ff))\n* style: improve ([34c24ea](https://github.com/fengyuanchen/cropperjs/commit/34c24ea))\n* Revert \"fix: export const enum for runtime\" ([1edfd73](https://github.com/fengyuanchen/cropperjs/commit/1edfd73))\n* fix: compute destination size with image's aspect ratio ([3dbbbf4](https://github.com/fengyuanchen/cropperjs/commit/3dbbbf4)), closes [#326](https://github.com/fengyuanchen/cropperjs/issues/326)\n\n\n\n## <small>1.3.3 (2018-03-18)</small>\n\n* build: release 1.3.3 ([08c517d](https://github.com/fengyuanchen/cropperjs/commit/08c517d))\n* fix: add browser env checking for using in node.js ([f2f1172](https://github.com/fengyuanchen/cropperjs/commit/f2f1172))\n* fix: add missing pivot to zoomTo (#320) ([de843e1](https://github.com/fengyuanchen/cropperjs/commit/de843e1)), closes [#320](https://github.com/fengyuanchen/cropperjs/issues/320)\n* fix: avoid a `TypeError` in strict mode ([a456d69](https://github.com/fengyuanchen/cropperjs/commit/a456d69))\n* fix: export const enum for runtime ([58e3c3c](https://github.com/fengyuanchen/cropperjs/commit/58e3c3c)), closes [#308](https://github.com/fengyuanchen/cropperjs/issues/308)\n* docs: update ([260a431](https://github.com/fengyuanchen/cropperjs/commit/260a431))\n* style: add comment for timeout ([5db0ebe](https://github.com/fengyuanchen/cropperjs/commit/5db0ebe))\n* perf: improve event utils ([8d005d0](https://github.com/fengyuanchen/cropperjs/commit/8d005d0))\n\n\n\n## <small>1.3.2 (2018-03-03)</small>\n\n* build: release 1.3.2 ([26a9287](https://github.com/fengyuanchen/cropperjs/commit/26a9287))\n* fix: draw with image's natural sizes ([2c13262](https://github.com/fengyuanchen/cropperjs/commit/2c13262)), closes [#313](https://github.com/fengyuanchen/cropperjs/issues/313)\n* docs: fix vars name of mask-an-image example: (#314) ([64d8a82](https://github.com/fengyuanchen/cropperjs/commit/64d8a82)), closes [#314](https://github.com/fengyuanchen/cropperjs/issues/314)\n\n\n\n## <small>1.3.1 (2018-02-28)</small>\n\n* build: lint code before commit ([88ee482](https://github.com/fengyuanchen/cropperjs/commit/88ee482))\n* build: release 1.3.1 ([204e19f](https://github.com/fengyuanchen/cropperjs/commit/204e19f))\n* test: fix typo ([8a3117e](https://github.com/fengyuanchen/cropperjs/commit/8a3117e))\n* docs: fix typos ([cbb3150](https://github.com/fengyuanchen/cropperjs/commit/cbb3150))\n* fix: add missing `width` and `height` definitions ([06f6b36](https://github.com/fengyuanchen/cropperjs/commit/06f6b36)), closes [#302](https://github.com/fengyuanchen/cropperjs/issues/302)\n* fix: correct view mode 2 ([38f418f](https://github.com/fengyuanchen/cropperjs/commit/38f418f)), closes [#304](https://github.com/fengyuanchen/cropperjs/issues/304)\n* fix: should only trigger start handler once ([0090b1e](https://github.com/fengyuanchen/cropperjs/commit/0090b1e)), closes [#306](https://github.com/fengyuanchen/cropperjs/issues/306)\n* fix(package): remove browser field (#307) ([d679bc3](https://github.com/fengyuanchen/cropperjs/commit/d679bc3)), closes [#307](https://github.com/fengyuanchen/cropperjs/issues/307)\n\n\n\n## 1.3.0 (2018-02-25)\n\n* build: add commit message linter ([83786bf](https://github.com/fengyuanchen/cropperjs/commit/83786bf))\n* build: add indexes files ([9b7b151](https://github.com/fengyuanchen/cropperjs/commit/9b7b151))\n* build: add the `root` field ([e3afb11](https://github.com/fengyuanchen/cropperjs/commit/e3afb11))\n* build: change enum properties to PascalCase ([49d7e88](https://github.com/fengyuanchen/cropperjs/commit/49d7e88))\n* build: release 1.3.0 ([ae88c17](https://github.com/fengyuanchen/cropperjs/commit/ae88c17))\n* build: simplify banner sharing ([23d059e](https://github.com/fengyuanchen/cropperjs/commit/23d059e))\n* refactor: change the second parameter name of `replace` method ([8f25400](https://github.com/fengyuanchen/cropperjs/commit/8f25400))\n* refactor: crop after ready ([4a0c638](https://github.com/fengyuanchen/cropperjs/commit/4a0c638))\n* refactor: not allow to crop again when it is already cropped ([56fc9d2](https://github.com/fengyuanchen/cropperjs/commit/56fc9d2))\n* refactor: optimize image sizing ([92f1f88](https://github.com/fengyuanchen/cropperjs/commit/92f1f88))\n* refactor: optimize the build process ([2d48734](https://github.com/fengyuanchen/cropperjs/commit/2d48734))\n* refactor: trigger `crop` event before `ready` event when auto crop ([63ed4b5](https://github.com/fengyuanchen/cropperjs/commit/63ed4b5))\n* style: add comment ([61fc266](https://github.com/fengyuanchen/cropperjs/commit/61fc266))\n* style: fix eslint errors ([bdd239f](https://github.com/fengyuanchen/cropperjs/commit/bdd239f))\n* test: fix typos ([2fd7fcc](https://github.com/fengyuanchen/cropperjs/commit/2fd7fcc))\n* test: refactor test suites ([ef8fd5e](https://github.com/fengyuanchen/cropperjs/commit/ef8fd5e))\n* perf: improve async process ([375cd51](https://github.com/fengyuanchen/cropperjs/commit/375cd51))\n* perf: improve some methods ([a89e0f5](https://github.com/fengyuanchen/cropperjs/commit/a89e0f5))\n* perf: improve utilities ([a0777bd](https://github.com/fengyuanchen/cropperjs/commit/a0777bd))\n* perf: improve XMLHTTPRequest abort ([4495b31](https://github.com/fengyuanchen/cropperjs/commit/4495b31))\n* docs: add description for timestamp ([8068a0e](https://github.com/fengyuanchen/cropperjs/commit/8068a0e))\n* docs: add example for uploading cropped image to server ([b8bf1a2](https://github.com/fengyuanchen/cropperjs/commit/b8bf1a2))\n* docs: add note for `data` option ([5ac334d](https://github.com/fengyuanchen/cropperjs/commit/5ac334d))\n* docs: add notes for `crop` event ([3daf2b0](https://github.com/fengyuanchen/cropperjs/commit/3daf2b0))\n* docs: change parameter name ([e0af88a](https://github.com/fengyuanchen/cropperjs/commit/e0af88a))\n* docs: detail cropper class arguments ([5a1eddf](https://github.com/fengyuanchen/cropperjs/commit/5a1eddf))\n* docs: keep the same image name when download ([0068a67](https://github.com/fengyuanchen/cropperjs/commit/0068a67))\n* docs: perfect contributing documentations ([0e9735f](https://github.com/fengyuanchen/cropperjs/commit/0e9735f))\n* docs: upgrade Bootstrap stable version ([a2a80ce](https://github.com/fengyuanchen/cropperjs/commit/a2a80ce))\n* docs: upgrade Bootstrap to v4.0.0-beta.3 ([7edf9cb](https://github.com/fengyuanchen/cropperjs/commit/7edf9cb))\n* feat: add type definitions file for TypeScript ([3a079ad](https://github.com/fengyuanchen/cropperjs/commit/3a079ad))\n* feat: enhance the `preview` option ([9f235ff](https://github.com/fengyuanchen/cropperjs/commit/9f235ff))\n* fix: correct cropped sizes when provide max/min sizes ([187fa61](https://github.com/fengyuanchen/cropperjs/commit/187fa61))\n* fix: the `setDragMode` method only available when it is ready ([2b70af4](https://github.com/fengyuanchen/cropperjs/commit/2b70af4))\n* pref: improve regexps ([0902077](https://github.com/fengyuanchen/cropperjs/commit/0902077))\n* Added touch-action attribute to template to accomodate pointer event polyfills (#299) ([7a4a705](https://github.com/fengyuanchen/cropperjs/commit/7a4a705)), closes [#299](https://github.com/fengyuanchen/cropperjs/issues/299)\n\n\n\n## <small>1.2.2 (2018-01-03)</small>\n\n* release: 1.2.2 ([583b763](https://github.com/fengyuanchen/cropperjs/commit/583b763))\n* build: upgrade rollup ([553fe5b](https://github.com/fengyuanchen/cropperjs/commit/553fe5b))\n* MIT ([cb4f3a9](https://github.com/fengyuanchen/cropperjs/commit/cb4f3a9))\n* fix: incorrect image natual sizes in iOS Safari ([5e3cdc4](https://github.com/fengyuanchen/cropperjs/commit/5e3cdc4)), closes [#279](https://github.com/fengyuanchen/cropperjs/issues/279)\n* fix: override `max-*` and `min-*` styles ([2ff231e](https://github.com/fengyuanchen/cropperjs/commit/2ff231e))\n* docs: add example for cross origin image ([6e298d6](https://github.com/fengyuanchen/cropperjs/commit/6e298d6))\n\n\n\n## <small>1.2.1 (2017-12-17)</small>\n\n* release: 1.2.1 ([b9de338](https://github.com/fengyuanchen/cropperjs/commit/b9de338))\n* feat: add style field to package.json ([98abdd2](https://github.com/fengyuanchen/cropperjs/commit/98abdd2))\n* fix: append image to dom to get correct size ([4ec5062](https://github.com/fengyuanchen/cropperjs/commit/4ec5062)), closes [#256](https://github.com/fengyuanchen/cropperjs/issues/256)\n* fix: fall back to documentElement if body is not existing ([fea4b72](https://github.com/fengyuanchen/cropperjs/commit/fea4b72))\n\n\n\n## 1.2.0 (2017-12-17)\n\n* chore: release 1.2.0 ([0d66566](https://github.com/fengyuanchen/cropperjs/commit/0d66566))\n* build: update scripts and dependencies ([0508d18](https://github.com/fengyuanchen/cropperjs/commit/0508d18))\n* build(rollup): add external helpers ([e33e595](https://github.com/fengyuanchen/cropperjs/commit/e33e595))\n* fix: correct rotated size computing ([572c0e2](https://github.com/fengyuanchen/cropperjs/commit/572c0e2)), closes [#260](https://github.com/fengyuanchen/cropperjs/issues/260)\n* fix: fallback for deleting property in element ([c84d3ce](https://github.com/fengyuanchen/cropperjs/commit/c84d3ce))\n* fix: improve event binding ([662c945](https://github.com/fengyuanchen/cropperjs/commit/662c945))\n* fix: simplify event binding ([6fdfcb4](https://github.com/fengyuanchen/cropperjs/commit/6fdfcb4))\n* Changed document to element.ownerDocument. This so cropper will work within an iframe (#201) ([1930e2b](https://github.com/fengyuanchen/cropperjs/commit/1930e2b)), closes [#201](https://github.com/fengyuanchen/cropperjs/issues/201)\n* Grammar correction (#271) ([6c64c31](https://github.com/fengyuanchen/cropperjs/commit/6c64c31)), closes [#271](https://github.com/fengyuanchen/cropperjs/issues/271)\n* use bidirectional resize cursor (#169) ([e1f8ddc](https://github.com/fengyuanchen/cropperjs/commit/e1f8ddc)), closes [#169](https://github.com/fengyuanchen/cropperjs/issues/169)\n* docs: add angular-cropperjs project ([1f6275f](https://github.com/fengyuanchen/cropperjs/commit/1f6275f)), closes [#230](https://github.com/fengyuanchen/cropperjs/issues/230)\n* docs: add some badges ([9c048cb](https://github.com/fengyuanchen/cropperjs/commit/9c048cb))\n* docs: improve demo ([c1cdd71](https://github.com/fengyuanchen/cropperjs/commit/c1cdd71))\n* feat: add pivot to zoomTo method ([2d99d2c](https://github.com/fengyuanchen/cropperjs/commit/2d99d2c)), closes [#144](https://github.com/fengyuanchen/cropperjs/issues/144)\n* style: fix eslint error ([565f4db](https://github.com/fengyuanchen/cropperjs/commit/565f4db))\n* style: remove redundant space ([382dc08](https://github.com/fengyuanchen/cropperjs/commit/382dc08))\n\n\n\n## <small>1.1.3 (2017-10-21)</small>\n\n* build: release 1.1.3 ([f6aff44](https://github.com/fengyuanchen/cropperjs/commit/f6aff44))\n* fix: error when disable one of rotatable and scalable options ([bdbc456](https://github.com/fengyuanchen/cropperjs/commit/bdbc456)), closes [#241](https://github.com/fengyuanchen/cropperjs/issues/241)\n* refactor: simplify window reference ([4da21d1](https://github.com/fengyuanchen/cropperjs/commit/4da21d1))\n\n\n\n## <small>1.1.2 (2017-10-18)</small>\n\n* build: add release script ([fe6ad77](https://github.com/fengyuanchen/cropperjs/commit/fe6ad77))\n* build: release 1.1.2 ([fa03501](https://github.com/fengyuanchen/cropperjs/commit/fa03501))\n* build: use files field instead of .npmignore file ([595d027](https://github.com/fengyuanchen/cropperjs/commit/595d027))\n* ci: add scripts ([adffe3e](https://github.com/fengyuanchen/cropperjs/commit/adffe3e))\n* fix: normalize decimal numbers when crop an image ([43df5f3](https://github.com/fengyuanchen/cropperjs/commit/43df5f3))\n\n\n\n## <small>1.1.1 (2017-10-11)</small>\n\n* build: release 1.1.1 ([d80c92d](https://github.com/fengyuanchen/cropperjs/commit/d80c92d))\n* docs: improve close button accessiblity ([91ba743](https://github.com/fengyuanchen/cropperjs/commit/91ba743))\n* docs: update author name ([4447625](https://github.com/fengyuanchen/cropperjs/commit/4447625))\n* fix: event listener should be a function ([0fe0c8f](https://github.com/fengyuanchen/cropperjs/commit/0fe0c8f)), closes [#238](https://github.com/fengyuanchen/cropperjs/issues/238)\n* fix: fix the originalUrl when src is not present (#234) ([2498140](https://github.com/fengyuanchen/cropperjs/commit/2498140)), closes [#234](https://github.com/fengyuanchen/cropperjs/issues/234)\n* fix: supports to load in node environment ([86a42c6](https://github.com/fengyuanchen/cropperjs/commit/86a42c6)), closes [#237](https://github.com/fengyuanchen/cropperjs/issues/237)\n\n\n\n## 1.1.0 (2017-10-08)\n\n* build: add banner with plugin ([39f0a90](https://github.com/fengyuanchen/cropperjs/commit/39f0a90))\n* build: format generated css ([e66bbdb](https://github.com/fengyuanchen/cropperjs/commit/e66bbdb)), closes [#231](https://github.com/fengyuanchen/cropperjs/issues/231)\n* build: release 1.1.0 ([779ffd8](https://github.com/fengyuanchen/cropperjs/commit/779ffd8))\n* build: remove bower.json file ([c7d7355](https://github.com/fengyuanchen/cropperjs/commit/c7d7355))\n* build: remove external helpers ([a6aba19](https://github.com/fengyuanchen/cropperjs/commit/a6aba19))\n* build: update config ([c386793](https://github.com/fengyuanchen/cropperjs/commit/c386793))\n* docs: add parameter to getCroppedCanvas ([24740bd](https://github.com/fengyuanchen/cropperjs/commit/24740bd))\n* docs: add steps to crop on server-side ([a146f74](https://github.com/fengyuanchen/cropperjs/commit/a146f74))\n* docs: improve layout and styles ([5ec5f80](https://github.com/fengyuanchen/cropperjs/commit/5ec5f80))\n* docs: improve rotate behaviour ([bde0d35](https://github.com/fengyuanchen/cropperjs/commit/bde0d35))\n* docs: improve style and layout ([50e6633](https://github.com/fengyuanchen/cropperjs/commit/50e6633))\n* docs: remove blank lines ([93bff67](https://github.com/fengyuanchen/cropperjs/commit/93bff67))\n* perf: remove window detecting as window is required ([5a86608](https://github.com/fengyuanchen/cropperjs/commit/5a86608))\n* perf: simplify element query ([c2a6bb5](https://github.com/fengyuanchen/cropperjs/commit/c2a6bb5))\n* test: enable orientation checking ([0fbec21](https://github.com/fengyuanchen/cropperjs/commit/0fbec21))\n* test: improve event dispatching ([f4afb4e](https://github.com/fengyuanchen/cropperjs/commit/f4afb4e))\n* built: add eslint rules ([618c373](https://github.com/fengyuanchen/cropperjs/commit/618c373))\n* refactor: separate contants and simplify utilities ([f17a7cb](https://github.com/fengyuanchen/cropperjs/commit/f17a7cb))\n* fix: improve action changing condition ([5a7c4f2](https://github.com/fengyuanchen/cropperjs/commit/5a7c4f2))\n* fix: improve event adding and removing ([720f3a9](https://github.com/fengyuanchen/cropperjs/commit/720f3a9))\n* fix: improve resizing condition ([ad379ce](https://github.com/fengyuanchen/cropperjs/commit/ad379ce)), closes [#229](https://github.com/fengyuanchen/cropperjs/issues/229)\n* fix: output clone to avoid side effect ([3d99679](https://github.com/fengyuanchen/cropperjs/commit/3d99679))\n* fix: set the default value of imageSmoothingEnabled ([2f3a5d4](https://github.com/fengyuanchen/cropperjs/commit/2f3a5d4)), closes [#232](https://github.com/fengyuanchen/cropperjs/issues/232)\n* fix: throw error is the first argument is invalid ([04cbf0a](https://github.com/fengyuanchen/cropperjs/commit/04cbf0a)), closes [#227](https://github.com/fengyuanchen/cropperjs/issues/227)\n* style: fix eslint errors ([3cb8677](https://github.com/fengyuanchen/cropperjs/commit/3cb8677))\n* style: remove unused variable ([eddb57e](https://github.com/fengyuanchen/cropperjs/commit/eddb57e))\n* feat: add 4 new options to `getCroppedCanvas` method ([2e8f7a5](https://github.com/fengyuanchen/cropperjs/commit/2e8f7a5))\n* feat: enhance scale ([cbfe15d](https://github.com/fengyuanchen/cropperjs/commit/cbfe15d))\n* Improved crop box resizing ([b15aa83](https://github.com/fengyuanchen/cropperjs/commit/b15aa83)), closes [#222](https://github.com/fengyuanchen/cropperjs/issues/222)\n\n\n\n## 1.0.0 (2017-09-03)\n\n* build: release 1.0.0 ([2365789](https://github.com/fengyuanchen/cropperjs/commit/2365789))\n* build: rename file and update config ([2479ef6](https://github.com/fengyuanchen/cropperjs/commit/2479ef6))\n* build: simplify configs ([ed396c0](https://github.com/fengyuanchen/cropperjs/commit/ed396c0))\n* build: update dependencies ([6d75a95](https://github.com/fengyuanchen/cropperjs/commit/6d75a95))\n* perl: simplify calculation ([85c508b](https://github.com/fengyuanchen/cropperjs/commit/85c508b))\n* fix: improve crop box resizing ([9257e06](https://github.com/fengyuanchen/cropperjs/commit/9257e06)), closes [#222](https://github.com/fengyuanchen/cropperjs/issues/222)\n* fix: zoom out bug after clear in view mode 1, 2, 3 ([7276d7b](https://github.com/fengyuanchen/cropperjs/commit/7276d7b)), closes [#209](https://github.com/fengyuanchen/cropperjs/issues/209)\n* test: upgrade QUnit to v2.4.0 ([4b4edcf](https://github.com/fengyuanchen/cropperjs/commit/4b4edcf))\n* style: fix eslint errors ([39befb9](https://github.com/fengyuanchen/cropperjs/commit/39befb9))\n* style: sort css properties ([67ee879](https://github.com/fengyuanchen/cropperjs/commit/67ee879))\n* style: sort css properties ([8ceb5af](https://github.com/fengyuanchen/cropperjs/commit/8ceb5af))\n* docs:  add notes to `getCroppedCanvas` method ([cab3f2a](https://github.com/fengyuanchen/cropperjs/commit/cab3f2a))\n* docs: upgrade to Bootstrap v4.0.0-beta ([1bc877f](https://github.com/fengyuanchen/cropperjs/commit/1bc877f))\n* Add ember-cropperjs in the related projects ([86901dd](https://github.com/fengyuanchen/cropperjs/commit/86901dd))\n\n\n\n## 1.0.0-rc.3 (2017-07-07)\n\n* add related - iron-coropper (web component) ([1969fa9](https://github.com/fengyuanchen/cropperjs/commit/1969fa9))\n* Added a new option to `getCroppedCanvas` method ([2e0eea0](https://github.com/fengyuanchen/cropperjs/commit/2e0eea0))\n* Added new options to `getCroppedCanvas` method ([fa409c3](https://github.com/fengyuanchen/cropperjs/commit/fa409c3))\n* Demo using cropper to mask an image ([4c8230c](https://github.com/fengyuanchen/cropperjs/commit/4c8230c))\n* Fix REGEXP_DATA_URL_JPEG using ([f38ba8a](https://github.com/fengyuanchen/cropperjs/commit/f38ba8a))\n* Fix typo ([00fd364](https://github.com/fengyuanchen/cropperjs/commit/00fd364))\n* Fixed boolean assigning ([b8cd3bb](https://github.com/fengyuanchen/cropperjs/commit/b8cd3bb))\n* Improved playground demo ([5dd1174](https://github.com/fengyuanchen/cropperjs/commit/5dd1174))\n* Release v1.0.0-rc.3 ([ddd2c2c](https://github.com/fengyuanchen/cropperjs/commit/ddd2c2c))\n* Rephrase documentation on viewMode in README ([1bd7118](https://github.com/fengyuanchen/cropperjs/commit/1bd7118))\n\n\n\n## 1.0.0-rc.2 (2017-05-30)\n\n* Improved canvas box initialization ([ddfdbda](https://github.com/fengyuanchen/cropperjs/commit/ddfdbda)), closes [#179](https://github.com/fengyuanchen/cropperjs/issues/179)\n* Release v1.0.0-rc.2 ([39a0659](https://github.com/fengyuanchen/cropperjs/commit/39a0659))\n* Sort properties ([9d01a09](https://github.com/fengyuanchen/cropperjs/commit/9d01a09))\n\n\n\n## 1.0.0-rc.1 (2017-04-30)\n\n* Release v1.0.0-rc.1 ([00dc42b](https://github.com/fengyuanchen/cropperjs/commit/00dc42b))\n\n\n\n## 1.0.0-rc (2017-03-25)\n\n* Change event name ([43fcf85](https://github.com/fengyuanchen/cropperjs/commit/43fcf85))\n* Fixed #161 ([be0ea20](https://github.com/fengyuanchen/cropperjs/commit/be0ea20)), closes [#161](https://github.com/fengyuanchen/cropperjs/issues/161)\n* Fixed #162 ([f950e71](https://github.com/fengyuanchen/cropperjs/commit/f950e71)), closes [#162](https://github.com/fengyuanchen/cropperjs/issues/162)\n* Ignores double click when dragMode is set to none ([19d0d53](https://github.com/fengyuanchen/cropperjs/commit/19d0d53))\n* Improve code ([4d279a3](https://github.com/fengyuanchen/cropperjs/commit/4d279a3))\n* Improve code style ([93dc2ca](https://github.com/fengyuanchen/cropperjs/commit/93dc2ca))\n* Release v1.0.0-rc ([5d2388f](https://github.com/fengyuanchen/cropperjs/commit/5d2388f))\n* Remove source map when publish ([1268465](https://github.com/fengyuanchen/cropperjs/commit/1268465)), closes [#159](https://github.com/fengyuanchen/cropperjs/issues/159)\n\n\n\n## 1.0.0-beta.2 (2017-02-25)\n\n* Changed variable name ([2c91371](https://github.com/fengyuanchen/cropperjs/commit/2c91371))\n* Fixed #155 ([314f275](https://github.com/fengyuanchen/cropperjs/commit/314f275)), closes [#155](https://github.com/fengyuanchen/cropperjs/issues/155)\n* Fixed #155 ([2d79294](https://github.com/fengyuanchen/cropperjs/commit/2d79294)), closes [#155](https://github.com/fengyuanchen/cropperjs/issues/155)\n* Fixed #156 ([d7749fe](https://github.com/fengyuanchen/cropperjs/commit/d7749fe)), closes [#156](https://github.com/fengyuanchen/cropperjs/issues/156)\n* Fixed wrong reference ([584db79](https://github.com/fengyuanchen/cropperjs/commit/584db79))\n* Release v1.0.0-beta.2 ([e803308](https://github.com/fengyuanchen/cropperjs/commit/e803308))\n* Simplify extend function ([c5113fd](https://github.com/fengyuanchen/cropperjs/commit/c5113fd))\n* Update ([fce963c](https://github.com/fengyuanchen/cropperjs/commit/fce963c))\n\n\n\n## 1.0.0-beta.1 (2017-01-21)\n\n* Added lost info ([c46479c](https://github.com/fengyuanchen/cropperjs/commit/c46479c))\n* Comment tests ([b2fe8be](https://github.com/fengyuanchen/cropperjs/commit/b2fe8be))\n* Improved code ([7cbf82d](https://github.com/fengyuanchen/cropperjs/commit/7cbf82d))\n* Improved examples ([35c6ce5](https://github.com/fengyuanchen/cropperjs/commit/35c6ce5))\n* Improved object assign ([78b0ee0](https://github.com/fengyuanchen/cropperjs/commit/78b0ee0))\n* Release v1.0.0-beta.1 ([73b08f4](https://github.com/fengyuanchen/cropperjs/commit/73b08f4))\n* Set `withCredentials` attribute if it is required ([b213574](https://github.com/fengyuanchen/cropperjs/commit/b213574)), closes [#141](https://github.com/fengyuanchen/cropperjs/issues/141)\n* Update README.md ([496fd38](https://github.com/fengyuanchen/cropperjs/commit/496fd38))\n* Use CSS3 2D Transforms for better performance ([0cfc109](https://github.com/fengyuanchen/cropperjs/commit/0cfc109)), closes [#138](https://github.com/fengyuanchen/cropperjs/issues/138)\n\n\n\n## 1.0.0-beta (2017-01-01)\n\n* Added ignored files ([a8eced3](https://github.com/fengyuanchen/cropperjs/commit/a8eced3))\n* Fixed #128 ([0e2a1a1](https://github.com/fengyuanchen/cropperjs/commit/0e2a1a1)), closes [#128](https://github.com/fengyuanchen/cropperjs/issues/128)\n* Handle Pointer Events ([e68dc2d](https://github.com/fengyuanchen/cropperjs/commit/e68dc2d)), closes [#127](https://github.com/fengyuanchen/cropperjs/issues/127)\n* Happy New Year ([4c7aa8f](https://github.com/fengyuanchen/cropperjs/commit/4c7aa8f))\n* Improved code ([7f57912](https://github.com/fengyuanchen/cropperjs/commit/7f57912))\n* Improved setCropBoxData method ([5c26499](https://github.com/fengyuanchen/cropperjs/commit/5c26499)), closes [#109](https://github.com/fengyuanchen/cropperjs/issues/109)\n* Ipmoved \"round image\" example. Added corners smoothing ([7811037](https://github.com/fengyuanchen/cropperjs/commit/7811037))\n* Release v1.0.0-beta ([b7c5f4e](https://github.com/fengyuanchen/cropperjs/commit/b7c5f4e))\n* Simplify code ([76ccb58](https://github.com/fengyuanchen/cropperjs/commit/76ccb58))\n* Specify image extensions ([b17e05e](https://github.com/fengyuanchen/cropperjs/commit/b17e05e))\n* Support to set a element for preview ([30ec6b3](https://github.com/fengyuanchen/cropperjs/commit/30ec6b3)), closes [#113](https://github.com/fengyuanchen/cropperjs/issues/113)\n* Update dependencies ([2697166](https://github.com/fengyuanchen/cropperjs/commit/2697166))\n\n\n\n## 1.0.0-alpha (2016-12-04)\n\n* Added .babelrc file ([7f74f48](https://github.com/fengyuanchen/cropperjs/commit/7f74f48))\n* Added a link to vue-cropperjs ([ae9b853](https://github.com/fengyuanchen/cropperjs/commit/ae9b853))\n* Added build script for add banner and copy files ([2b7124f](https://github.com/fengyuanchen/cropperjs/commit/2b7124f))\n* Added postcss.config.json file ([66fa255](https://github.com/fengyuanchen/cropperjs/commit/66fa255))\n* Added rollup.config.js ([cbd74f7](https://github.com/fengyuanchen/cropperjs/commit/cbd74f7))\n* Disable default touch action to avoid side effect ([9c9e9cc](https://github.com/fengyuanchen/cropperjs/commit/9c9e9cc)), closes [#101](https://github.com/fengyuanchen/cropperjs/issues/101)\n* Fix typos ([b37aa89](https://github.com/fengyuanchen/cropperjs/commit/b37aa89))\n* Fixed `replace()` without `autoCrop` crash ([0f3161e](https://github.com/fengyuanchen/cropperjs/commit/0f3161e))\n* Fixed class names ([e2f16f0](https://github.com/fengyuanchen/cropperjs/commit/e2f16f0))\n* Improved demo ([fb83bf2](https://github.com/fengyuanchen/cropperjs/commit/fb83bf2)), closes [#103](https://github.com/fengyuanchen/cropperjs/issues/103)\n* Improved doc ([83a3ebf](https://github.com/fengyuanchen/cropperjs/commit/83a3ebf))\n* Improved examples ([3b69c23](https://github.com/fengyuanchen/cropperjs/commit/3b69c23)), closes [#95](https://github.com/fengyuanchen/cropperjs/issues/95)\n* Merged test html files ([9a0edd4](https://github.com/fengyuanchen/cropperjs/commit/9a0edd4))\n* Move from Less to PostCSS ([a723642](https://github.com/fengyuanchen/cropperjs/commit/a723642))\n* Moved images to docs folder ([9b528a8](https://github.com/fengyuanchen/cropperjs/commit/9b528a8))\n* Release v1.0.0-alpha ([3d931f7](https://github.com/fengyuanchen/cropperjs/commit/3d931f7))\n* Removed assets ([aa113b8](https://github.com/fengyuanchen/cropperjs/commit/aa113b8))\n* Removed demo ([734ac50](https://github.com/fengyuanchen/cropperjs/commit/734ac50))\n* Removed docs LICENSE ([ea99713](https://github.com/fengyuanchen/cropperjs/commit/ea99713))\n* Removed gulpfile.js ([9ce981d](https://github.com/fengyuanchen/cropperjs/commit/9ce981d))\n* Revoke existing Blob URL ([d141660](https://github.com/fengyuanchen/cropperjs/commit/d141660))\n* Simplify code ([eb3ca54](https://github.com/fengyuanchen/cropperjs/commit/eb3ca54))\n* Update config files ([61ecee7](https://github.com/fengyuanchen/cropperjs/commit/61ecee7))\n* Updated related projects ([d512d07](https://github.com/fengyuanchen/cropperjs/commit/d512d07))\n\n\n\n## <small>0.8.1 (2016-09-03)</small>\n\n* Fixed #82 ([1da3fca](https://github.com/fengyuanchen/cropperjs/commit/1da3fca)), closes [#82](https://github.com/fengyuanchen/cropperjs/issues/82)\n* Fixed the bug of calling `ready` event twice ([f6d84b9](https://github.com/fengyuanchen/cropperjs/commit/f6d84b9)), closes [#81](https://github.com/fengyuanchen/cropperjs/issues/81)\n* Fixed the bug of cropping ([364f6ac](https://github.com/fengyuanchen/cropperjs/commit/364f6ac)), closes [#80](https://github.com/fengyuanchen/cropperjs/issues/80)\n* Improved demo and docs ([f68ef17](https://github.com/fengyuanchen/cropperjs/commit/f68ef17))\n* Improved dependencies ([05421c3](https://github.com/fengyuanchen/cropperjs/commit/05421c3))\n* Release v0.8.1 ([903925f](https://github.com/fengyuanchen/cropperjs/commit/903925f))\n* Update ([b415fdd](https://github.com/fengyuanchen/cropperjs/commit/b415fdd))\n* Updated TetherJS ([50cd87e](https://github.com/fengyuanchen/cropperjs/commit/50cd87e))\n\n\n\n## 0.8.0 (2016-08-18)\n\n* Added .eslintrc file ([9157cd6](https://github.com/fengyuanchen/cropperjs/commit/9157cd6))\n* Added ISSUE_TEMPLATE.md file ([4c23ab1](https://github.com/fengyuanchen/cropperjs/commit/4c23ab1))\n* Added note for getCroppedCanvas ([e1b2d52](https://github.com/fengyuanchen/cropperjs/commit/e1b2d52))\n* Deleted .htmlcombrc file ([e961b9f](https://github.com/fengyuanchen/cropperjs/commit/e961b9f))\n* Deleted useless files ([3385490](https://github.com/fengyuanchen/cropperjs/commit/3385490))\n* Fixed the error of orientation transform ([2d3cd63](https://github.com/fengyuanchen/cropperjs/commit/2d3cd63))\n* Fixed the error of orientation transform ([7eb2f16](https://github.com/fengyuanchen/cropperjs/commit/7eb2f16))\n* Improved RegExps ([f48421f](https://github.com/fengyuanchen/cropperjs/commit/f48421f))\n* Move from SASS to LESS ([81eec0a](https://github.com/fengyuanchen/cropperjs/commit/81eec0a))\n* Ported code to ES6 ([6b73030](https://github.com/fengyuanchen/cropperjs/commit/6b73030))\n* Release v0.8.0 ([59a9325](https://github.com/fengyuanchen/cropperjs/commit/59a9325))\n* Remove \"build\" event and rename \"built\" to ready ([1e0867f](https://github.com/fengyuanchen/cropperjs/commit/1e0867f))\n* Removed FAQ.md file ([bb1df8c](https://github.com/fengyuanchen/cropperjs/commit/bb1df8c))\n* Removed unnecessary escape characters ([29315dc](https://github.com/fengyuanchen/cropperjs/commit/29315dc))\n* Removed unnecessary rules ([b1411ed](https://github.com/fengyuanchen/cropperjs/commit/b1411ed))\n* Simplify class constants ([4d254c0](https://github.com/fengyuanchen/cropperjs/commit/4d254c0))\n* Update ([16ec8b1](https://github.com/fengyuanchen/cropperjs/commit/16ec8b1))\n* Update ([41fa0a6](https://github.com/fengyuanchen/cropperjs/commit/41fa0a6))\n* Update ([fda04c0](https://github.com/fengyuanchen/cropperjs/commit/fda04c0))\n* Update docs and demo ([2290a23](https://github.com/fengyuanchen/cropperjs/commit/2290a23))\n* Update README.md ([451ffd6](https://github.com/fengyuanchen/cropperjs/commit/451ffd6))\n* Updated assets ([5306016](https://github.com/fengyuanchen/cropperjs/commit/5306016))\n* Updated docs and demo ([aeb81dd](https://github.com/fengyuanchen/cropperjs/commit/aeb81dd))\n* Updated docs and demo ([c65a4ff](https://github.com/fengyuanchen/cropperjs/commit/c65a4ff))\n* Updated examples ([9b84e57](https://github.com/fengyuanchen/cropperjs/commit/9b84e57))\n* Updated links ([72d41bb](https://github.com/fengyuanchen/cropperjs/commit/72d41bb))\n* Updated paths and links ([5b5d650](https://github.com/fengyuanchen/cropperjs/commit/5b5d650))\n\n\n\n## <small>0.7.2 (2016-06-08)</small>\n\n* Console event type directly ([1e42c5c](https://github.com/fengyuanchen/cropperjs/commit/1e42c5c))\n* Convert data attribute name to hypen case ([61dd740](https://github.com/fengyuanchen/cropperjs/commit/61dd740))\n* Fixed the order of scale and rotate ([d0f185c](https://github.com/fengyuanchen/cropperjs/commit/d0f185c))\n* Improve ([073d5ec](https://github.com/fengyuanchen/cropperjs/commit/073d5ec))\n* Improve browser detection ([5ad736c](https://github.com/fengyuanchen/cropperjs/commit/5ad736c))\n* Release v0.7.2 ([59076d7](https://github.com/fengyuanchen/cropperjs/commit/59076d7))\n\n\n\n## <small>0.7.1 (2016-05-28)</small>\n\n* Add note for incorrect Orientation value ([ef6b110](https://github.com/fengyuanchen/cropperjs/commit/ef6b110))\n* Add note for using cropper in modal ([1551592](https://github.com/fengyuanchen/cropperjs/commit/1551592))\n* Added cdnjs link ([f272356](https://github.com/fengyuanchen/cropperjs/commit/f272356))\n* Added https ([54edc50](https://github.com/fengyuanchen/cropperjs/commit/54edc50))\n* Change link ([6295ebf](https://github.com/fengyuanchen/cropperjs/commit/6295ebf))\n* Change order to avoid wrong behavior ([8cc4e7d](https://github.com/fengyuanchen/cropperjs/commit/8cc4e7d))\n* Check cross origin setting when load image by xhr ([e9d3c9f](https://github.com/fengyuanchen/cropperjs/commit/e9d3c9f))\n* Improve some descriptions ([74d6179](https://github.com/fengyuanchen/cropperjs/commit/74d6179))\n* Release v0.7.1 ([af85df2](https://github.com/fengyuanchen/cropperjs/commit/af85df2))\n* Removed icons ([5db61ac](https://github.com/fengyuanchen/cropperjs/commit/5db61ac))\n* Replace dot with hash ([2e1f085](https://github.com/fengyuanchen/cropperjs/commit/2e1f085))\n* Return the whole canvas if not cropped ([2b969ad](https://github.com/fengyuanchen/cropperjs/commit/2b969ad))\n* Update assets ([1990fe6](https://github.com/fengyuanchen/cropperjs/commit/1990fe6))\n* Use https ([c32c271](https://github.com/fengyuanchen/cropperjs/commit/c32c271))\n\n\n\n## 0.7.0 (2016-03-20)\n\n* Added !default ([80c5186](https://github.com/fengyuanchen/cropperjs/commit/80c5186))\n* Added CSS settings for usage example ([212a8f9](https://github.com/fengyuanchen/cropperjs/commit/212a8f9))\n* Added custom events ([3c39b4b](https://github.com/fengyuanchen/cropperjs/commit/3c39b4b))\n* Added tests for events ([d3c59e1](https://github.com/fengyuanchen/cropperjs/commit/d3c59e1))\n* CC BY 4.0 ([1e3adad](https://github.com/fengyuanchen/cropperjs/commit/1e3adad))\n* Improve demo ([15e0f36](https://github.com/fengyuanchen/cropperjs/commit/15e0f36))\n* Improve docs ([de9a590](https://github.com/fengyuanchen/cropperjs/commit/de9a590))\n* Improve styles ([0e89821](https://github.com/fengyuanchen/cropperjs/commit/0e89821))\n* Optimize ([113780b](https://github.com/fengyuanchen/cropperjs/commit/113780b))\n* Release v0.7.0 ([4b56274](https://github.com/fengyuanchen/cropperjs/commit/4b56274))\n* Simplify ([c988ed1](https://github.com/fengyuanchen/cropperjs/commit/c988ed1))\n* Update data reference ([38cebe0](https://github.com/fengyuanchen/cropperjs/commit/38cebe0))\n* Update demo ([63bdd63](https://github.com/fengyuanchen/cropperjs/commit/63bdd63))\n* Update dependencies ([9d64e7c](https://github.com/fengyuanchen/cropperjs/commit/9d64e7c))\n* Update docs ([449337d](https://github.com/fengyuanchen/cropperjs/commit/449337d))\n* Update jQuery ([1938064](https://github.com/fengyuanchen/cropperjs/commit/1938064))\n* Update options and styles ([fb57160](https://github.com/fengyuanchen/cropperjs/commit/fb57160))\n* Update QUnit ([6bcc040](https://github.com/fengyuanchen/cropperjs/commit/6bcc040))\n\n\n\n## 0.6.0 (2016-02-22)\n\n* Add notes for minCropBoxWidth/Height ([3603289](https://github.com/fengyuanchen/cropperjs/commit/3603289))\n* Added a new parameter to the `replace` method ([f3bf720](https://github.com/fengyuanchen/cropperjs/commit/f3bf720))\n* Avoid to use the natualWidth/Height in Safari ([913d9ce](https://github.com/fengyuanchen/cropperjs/commit/913d9ce))\n* Cache the image in the view box ([3bc7f07](https://github.com/fengyuanchen/cropperjs/commit/3bc7f07))\n* Fix incorrect cropped canvas when scaleX/Y > 1 ([5f89114](https://github.com/fengyuanchen/cropperjs/commit/5f89114))\n* Fix incorrent size limitation of the crop box ([9e33cce](https://github.com/fengyuanchen/cropperjs/commit/9e33cce)), closes [#30](https://github.com/fengyuanchen/cropperjs/issues/30)\n* Fixed timestamp ([5383534](https://github.com/fengyuanchen/cropperjs/commit/5383534))\n* Improve replace method ([2297459](https://github.com/fengyuanchen/cropperjs/commit/2297459))\n* Not to override Orientation value to avoid side effect ([94328c9](https://github.com/fengyuanchen/cropperjs/commit/94328c9))\n* Override the Orientation value for Safari ([97c59f3](https://github.com/fengyuanchen/cropperjs/commit/97c59f3))\n* Release v0.6.0 ([712af0f](https://github.com/fengyuanchen/cropperjs/commit/712af0f))\n* Removed Gitter badge ([bccbb38](https://github.com/fengyuanchen/cropperjs/commit/bccbb38))\n* Typo ([d074ecd](https://github.com/fengyuanchen/cropperjs/commit/d074ecd))\n* Update jQuery ([fdd28dc](https://github.com/fengyuanchen/cropperjs/commit/fdd28dc))\n\n\n\n## <small>0.5.6 (2016-01-18)</small>\n\n* Added link of an advanced example ([455a659](https://github.com/fengyuanchen/cropperjs/commit/455a659))\n* Added links ([9cd66a4](https://github.com/fengyuanchen/cropperjs/commit/9cd66a4))\n* Defined crossOriginUrl when exists `crossOrigin` ([7ad2ff2](https://github.com/fengyuanchen/cropperjs/commit/7ad2ff2))\n* Fixed #24 ([3b61201](https://github.com/fengyuanchen/cropperjs/commit/3b61201)), closes [#24](https://github.com/fengyuanchen/cropperjs/issues/24)\n* Optimize description ([7c82a26](https://github.com/fengyuanchen/cropperjs/commit/7c82a26))\n* Optimized tests ([ba79c37](https://github.com/fengyuanchen/cropperjs/commit/ba79c37))\n* Release v0.5.6 ([0da5c74](https://github.com/fengyuanchen/cropperjs/commit/0da5c74))\n* Remove misdescription ([65dccb7](https://github.com/fengyuanchen/cropperjs/commit/65dccb7))\n\n\n\n## <small>0.5.5 (2016-01-01)</small>\n\n* Add related projects ([59b966b](https://github.com/fengyuanchen/cropperjs/commit/59b966b))\n* Added an example for cropping round image ([967a48d](https://github.com/fengyuanchen/cropperjs/commit/967a48d))\n* Floor canvas width/height instead of round ([e5dcf65](https://github.com/fengyuanchen/cropperjs/commit/e5dcf65))\n* Happy Year of the Monkey :monkey: ([3e2e8d0](https://github.com/fengyuanchen/cropperjs/commit/3e2e8d0))\n* Release v0.5.5 ([d5daeef](https://github.com/fengyuanchen/cropperjs/commit/d5daeef))\n* Update banner ([4476f4a](https://github.com/fengyuanchen/cropperjs/commit/4476f4a))\n* Updated \"getByTag/Class\" utilities ([9d30741](https://github.com/fengyuanchen/cropperjs/commit/9d30741))\n\n\n\n## <small>0.5.4 (2015-12-28)</small>\n\n* Optimize \"toArray\" utility ([c453071](https://github.com/fengyuanchen/cropperjs/commit/c453071))\n* Release v0.5.4 ([5c2a88c](https://github.com/fengyuanchen/cropperjs/commit/5c2a88c))\n* Supports to zoom from event triggering point ([35b8924](https://github.com/fengyuanchen/cropperjs/commit/35b8924))\n* Update ([81ac363](https://github.com/fengyuanchen/cropperjs/commit/81ac363))\n* Updated demo ([597b998](https://github.com/fengyuanchen/cropperjs/commit/597b998))\n* Updated docs ([787a47e](https://github.com/fengyuanchen/cropperjs/commit/787a47e))\n* Use prototype directly ([e49dfff](https://github.com/fengyuanchen/cropperjs/commit/e49dfff))\n\n\n\n## <small>0.5.3 (2015-12-24)</small>\n\n* Cancel to check property value ([533e708](https://github.com/fengyuanchen/cropperjs/commit/533e708)), closes [#22](https://github.com/fengyuanchen/cropperjs/issues/22)\n* Fix parameter for getCroppedCanvas ([1f9fe21](https://github.com/fengyuanchen/cropperjs/commit/1f9fe21))\n* Fixed #21: Limit wheel zoom speed ([7a28cef](https://github.com/fengyuanchen/cropperjs/commit/7a28cef)), closes [#21](https://github.com/fengyuanchen/cropperjs/issues/21)\n* Improve class utilities ([7230754](https://github.com/fengyuanchen/cropperjs/commit/7230754))\n* Improve utilities ([b3c6bed](https://github.com/fengyuanchen/cropperjs/commit/b3c6bed))\n* Inserts cropper after to target image ([1cbc7d7](https://github.com/fengyuanchen/cropperjs/commit/1cbc7d7))\n* Release v0.5.3 ([9383b78](https://github.com/fengyuanchen/cropperjs/commit/9383b78))\n* Rename \"wheeled\" to \"wheeling\" ([0264f59](https://github.com/fengyuanchen/cropperjs/commit/0264f59))\n* Simplify prototype assignment ([7510870](https://github.com/fengyuanchen/cropperjs/commit/7510870))\n\n\n\n## <small>0.5.2 (2015-12-15)</small>\n\n* Add csscomb task ([2cee121](https://github.com/fengyuanchen/cropperjs/commit/2cee121))\n* Add homepage ([5e54a0e](https://github.com/fengyuanchen/cropperjs/commit/5e54a0e))\n* Added .csscomb.json ([0d987e9](https://github.com/fengyuanchen/cropperjs/commit/0d987e9))\n* Fix event handlers ([8c9b2d9](https://github.com/fengyuanchen/cropperjs/commit/8c9b2d9))\n* Release v0.5.2 ([4a77ca1](https://github.com/fengyuanchen/cropperjs/commit/4a77ca1))\n\n\n\n## <small>0.5.1 (2015-12-12)</small>\n\n* Add ads ([bc22b6c](https://github.com/fengyuanchen/cropperjs/commit/bc22b6c))\n* Add dests ([b79ab6f](https://github.com/fengyuanchen/cropperjs/commit/b79ab6f))\n* Add QUnit for test ([dfad684](https://github.com/fengyuanchen/cropperjs/commit/dfad684))\n* Add reference of CORS ([b9c5c39](https://github.com/fengyuanchen/cropperjs/commit/b9c5c39))\n* Add styles by function ([4baeefd](https://github.com/fengyuanchen/cropperjs/commit/4baeefd))\n* Add trim ([923e368](https://github.com/fengyuanchen/cropperjs/commit/923e368))\n* Added .travi.yml ([e331abd](https://github.com/fengyuanchen/cropperjs/commit/e331abd))\n* Added Google Analytics tracking code ([2390187](https://github.com/fengyuanchen/cropperjs/commit/2390187))\n* Added hash links ([25c0c1e](https://github.com/fengyuanchen/cropperjs/commit/25c0c1e))\n* Added Travi CI badge ([ffc1559](https://github.com/fengyuanchen/cropperjs/commit/ffc1559))\n* Assign \"this\" to  \"_this\" ([0c4815c](https://github.com/fengyuanchen/cropperjs/commit/0c4815c))\n* Continue clone when ajax error ([f85585e](https://github.com/fengyuanchen/cropperjs/commit/f85585e))\n* Fix typo ([1d4d4c5](https://github.com/fengyuanchen/cropperjs/commit/1d4d4c5))\n* Fix typo ([40a3994](https://github.com/fengyuanchen/cropperjs/commit/40a3994))\n* Forward the calling of the \"build\" option ([973f289](https://github.com/fengyuanchen/cropperjs/commit/973f289))\n* Handle Data URL ([08ce34f](https://github.com/fengyuanchen/cropperjs/commit/08ce34f))\n* Improve \"trim\" function ([113cb0d](https://github.com/fengyuanchen/cropperjs/commit/113cb0d))\n* Improve utilities ([d88bd06](https://github.com/fengyuanchen/cropperjs/commit/d88bd06))\n* Improve utilities ([210934a](https://github.com/fengyuanchen/cropperjs/commit/210934a))\n* Improved RegExps ([085af83](https://github.com/fengyuanchen/cropperjs/commit/085af83))\n* Only handle 90-degree rotation when init canvas ([d334766](https://github.com/fengyuanchen/cropperjs/commit/d334766))\n* Only handle Orientation great than 1 ([8981630](https://github.com/fengyuanchen/cropperjs/commit/8981630))\n* Optimize code style ([46fdf87](https://github.com/fengyuanchen/cropperjs/commit/46fdf87))\n* Release v0.5.1 ([b0f7280](https://github.com/fengyuanchen/cropperjs/commit/b0f7280))\n* Remove unnecessary proxy ([53604ba](https://github.com/fengyuanchen/cropperjs/commit/53604ba))\n* Rename \"loaded\" to \"ready\" ([725d9a8](https://github.com/fengyuanchen/cropperjs/commit/725d9a8))\n* Reset \"isCompleted\" to \"false\" for re-rendering ([12d7d1d](https://github.com/fengyuanchen/cropperjs/commit/12d7d1d))\n* Rollback each function ([6299e44](https://github.com/fengyuanchen/cropperjs/commit/6299e44))\n* Simplify code ([9251b9c](https://github.com/fengyuanchen/cropperjs/commit/9251b9c))\n* Simplify description ([77160fa](https://github.com/fengyuanchen/cropperjs/commit/77160fa))\n* Simplify styles ([d2effbb](https://github.com/fengyuanchen/cropperjs/commit/d2effbb))\n* Simplify variables and properties ([48257ed](https://github.com/fengyuanchen/cropperjs/commit/48257ed))\n* Sort dev dependencies ([b651bf8](https://github.com/fengyuanchen/cropperjs/commit/b651bf8))\n* Split test files ([4d5d096](https://github.com/fengyuanchen/cropperjs/commit/4d5d096))\n* Update homepage link ([44837bc](https://github.com/fengyuanchen/cropperjs/commit/44837bc))\n* Update variables ([7f8d7d3](https://github.com/fengyuanchen/cropperjs/commit/7f8d7d3))\n* Use defined \"imageData\" ([a2ae59c](https://github.com/fengyuanchen/cropperjs/commit/a2ae59c))\n\n\n\n## 0.5.0 (2015-12-05)\n\n* Add a timestamp to preview image url ([f570a61](https://github.com/fengyuanchen/cropperjs/commit/f570a61))\n* Add http to url ([814124f](https://github.com/fengyuanchen/cropperjs/commit/814124f))\n* Add notes ([378c4a1](https://github.com/fengyuanchen/cropperjs/commit/378c4a1))\n* Added  a new option: \"checkOrientation\" ([4a350df](https://github.com/fengyuanchen/cropperjs/commit/4a350df))\n* Added a new image with the Exif Orientation ([87dda27](https://github.com/fengyuanchen/cropperjs/commit/87dda27))\n* Added Gitter badge ([96d4cd1](https://github.com/fengyuanchen/cropperjs/commit/96d4cd1))\n* Added html5-boilerplate ([c857014](https://github.com/fengyuanchen/cropperjs/commit/c857014))\n* Added table of contents for quick view ([393930e](https://github.com/fengyuanchen/cropperjs/commit/393930e))\n* Ignores undefined values ([87eb147](https://github.com/fengyuanchen/cropperjs/commit/87eb147))\n* Improved .gitattributes ([6ce22df](https://github.com/fengyuanchen/cropperjs/commit/6ce22df))\n* Only set defined values ([0ad6c54](https://github.com/fengyuanchen/cropperjs/commit/0ad6c54))\n* Release v0.5.0 ([a8a3019](https://github.com/fengyuanchen/cropperjs/commit/a8a3019))\n* Remove comment ([9bceced](https://github.com/fengyuanchen/cropperjs/commit/9bceced))\n* Removed .csscomb.json ([3e7ee3f](https://github.com/fengyuanchen/cropperjs/commit/3e7ee3f))\n* Update tasks ([dbcff1c](https://github.com/fengyuanchen/cropperjs/commit/dbcff1c))\n* Update versions ([83250aa](https://github.com/fengyuanchen/cropperjs/commit/83250aa))\n\n\n\n## 0.4.0 (2015-12-02)\n\n* Add \"restore\" option ([0fe878c](https://github.com/fengyuanchen/cropperjs/commit/0fe878c))\n* Added full crop box example ([46468a3](https://github.com/fengyuanchen/cropperjs/commit/46468a3))\n* Added new \"restore\" option ([70c74bd](https://github.com/fengyuanchen/cropperjs/commit/70c74bd))\n* Fix links ([397030b](https://github.com/fengyuanchen/cropperjs/commit/397030b))\n* Fixed #12: Added vendor prefixes ([cdb658d](https://github.com/fengyuanchen/cropperjs/commit/cdb658d)), closes [#12](https://github.com/fengyuanchen/cropperjs/issues/12)\n* Release v0.4.0 ([0c15da0](https://github.com/fengyuanchen/cropperjs/commit/0c15da0))\n* The image should be full width ([16b70cd](https://github.com/fengyuanchen/cropperjs/commit/16b70cd))\n* Update Bootstrap from v3.3.5 to v3.3.6 ([81a0f7c](https://github.com/fengyuanchen/cropperjs/commit/81a0f7c))\n* Update dev dependencies ([3f89838](https://github.com/fengyuanchen/cropperjs/commit/3f89838))\n* Update Font Awesome from v4.4.0 to v4.4.5 ([6bba180](https://github.com/fengyuanchen/cropperjs/commit/6bba180))\n* Update QUnit from v1.19.0 to v1.20.0 ([4ffee9e](https://github.com/fengyuanchen/cropperjs/commit/4ffee9e))\n\n\n\n## <small>0.3.3 (2015-11-30)</small>\n\n* Add \"cropper\" as keyword ([3829018](https://github.com/fengyuanchen/cropperjs/commit/3829018))\n* Change document title ([6d4db9c](https://github.com/fengyuanchen/cropperjs/commit/6d4db9c))\n* Change the position of .cropper-bg ([a73208d](https://github.com/fengyuanchen/cropperjs/commit/a73208d))\n* Floor the numerical parameters for `drawImage` ([ee056cf](https://github.com/fengyuanchen/cropperjs/commit/ee056cf))\n* Release v0.3.3 ([b65292d](https://github.com/fengyuanchen/cropperjs/commit/b65292d))\n* Remove optional extension ([d4267b1](https://github.com/fengyuanchen/cropperjs/commit/d4267b1))\n\n\n\n## <small>0.3.2 (2015-11-18)</small>\n\n* Fix version ([defb91f](https://github.com/fengyuanchen/cropperjs/commit/defb91f))\n* Improved ([ef4da9d](https://github.com/fengyuanchen/cropperjs/commit/ef4da9d))\n* Improved new crop box creating ([ef44621](https://github.com/fengyuanchen/cropperjs/commit/ef44621)), closes [#10](https://github.com/fengyuanchen/cropperjs/issues/10)\n* Release v0.3.2 ([bbba734](https://github.com/fengyuanchen/cropperjs/commit/bbba734))\n* Remove trailing spaces ([222328a](https://github.com/fengyuanchen/cropperjs/commit/222328a))\n* Update html head ([8febe78](https://github.com/fengyuanchen/cropperjs/commit/8febe78))\n\n\n\n## <small>0.3.1 (2015-11-11)</small>\n\n* Add `overflow: hidden` to preview element ([f2fae76](https://github.com/fengyuanchen/cropperjs/commit/f2fae76))\n* Add a wrap box for canvas ([0a2cafd](https://github.com/fengyuanchen/cropperjs/commit/0a2cafd))\n* Add links ([ee669b8](https://github.com/fengyuanchen/cropperjs/commit/ee669b8))\n* Add replace button ([96c28da](https://github.com/fengyuanchen/cropperjs/commit/96c28da))\n* Added end semicolons to css text ([591ae02](https://github.com/fengyuanchen/cropperjs/commit/591ae02))\n* Added example of aspect ratio range ([22e6e57](https://github.com/fengyuanchen/cropperjs/commit/22e6e57))\n* Added examples ([e5a46cd](https://github.com/fengyuanchen/cropperjs/commit/e5a46cd))\n* Added footer ([c3f2016](https://github.com/fengyuanchen/cropperjs/commit/c3f2016))\n* Added the `is` prefix  to all boolean variables ([916b039](https://github.com/fengyuanchen/cropperjs/commit/916b039))\n* Added the new `mode` option ([39c29d2](https://github.com/fengyuanchen/cropperjs/commit/39c29d2))\n* Changed ([496b029](https://github.com/fengyuanchen/cropperjs/commit/496b029))\n* Fix error comment ([984b8a1](https://github.com/fengyuanchen/cropperjs/commit/984b8a1))\n* Fix property ([8baa5f2](https://github.com/fengyuanchen/cropperjs/commit/8baa5f2))\n* Fix typo ([4b8ab46](https://github.com/fengyuanchen/cropperjs/commit/4b8ab46))\n* Fixed #7: Always set the \"crossOrigin\" value ([a757b3e](https://github.com/fengyuanchen/cropperjs/commit/a757b3e)), closes [#7](https://github.com/fengyuanchen/cropperjs/issues/7)\n* Improve \"typeOf\" utility function ([21eccdb](https://github.com/fengyuanchen/cropperjs/commit/21eccdb))\n* Improve test ([5dc4d68](https://github.com/fengyuanchen/cropperjs/commit/5dc4d68))\n* Improved ([6bd806a](https://github.com/fengyuanchen/cropperjs/commit/6bd806a))\n* Improved demo ([0388fb7](https://github.com/fengyuanchen/cropperjs/commit/0388fb7))\n* MIT ([9468b70](https://github.com/fengyuanchen/cropperjs/commit/9468b70))\n* Optimized docs ([c70cd41](https://github.com/fengyuanchen/cropperjs/commit/c70cd41))\n* Release v0.3.1 ([f217b0f](https://github.com/fengyuanchen/cropperjs/commit/f217b0f))\n* Released v0.3.0 ([bb0b212](https://github.com/fengyuanchen/cropperjs/commit/bb0b212))\n* Remove `overflow: hidden` from preview element ([e6eb119](https://github.com/fengyuanchen/cropperjs/commit/e6eb119))\n* Remove jQuery reference ([e3e8aee](https://github.com/fengyuanchen/cropperjs/commit/e3e8aee))\n* Remove jQuery reference from tests ([3d5e288](https://github.com/fengyuanchen/cropperjs/commit/3d5e288))\n* Remove jQuery usage ([4771cec](https://github.com/fengyuanchen/cropperjs/commit/4771cec))\n* Remove link ([1f0cfcf](https://github.com/fengyuanchen/cropperjs/commit/1f0cfcf))\n* Remove test ([175fa74](https://github.com/fengyuanchen/cropperjs/commit/175fa74))\n* Removed old \"dragCrop\" option ([18bb5d3](https://github.com/fengyuanchen/cropperjs/commit/18bb5d3))\n* Rename \"checkImageOrigin\" to \"checkCrossOrigin\" ([24ab029](https://github.com/fengyuanchen/cropperjs/commit/24ab029))\n* Rename \"doubleClickToggle\" ([d780b92](https://github.com/fengyuanchen/cropperjs/commit/d780b92))\n* Rename \"mode\" option to \"viewMode\" ([04e3856](https://github.com/fengyuanchen/cropperjs/commit/04e3856))\n* Rename \"mouseWheelZoom\" to \"zoomOnWheel\" ([dc7aadc](https://github.com/fengyuanchen/cropperjs/commit/dc7aadc))\n* Rename \"toggleDragModeOnDoubleClick\" ([81ad239](https://github.com/fengyuanchen/cropperjs/commit/81ad239))\n* Rename \"touchDragZoom\" to \"zoomOnTouch\" ([519f8df](https://github.com/fengyuanchen/cropperjs/commit/519f8df))\n* Rename `dragCrop` option to `dragMode` ([93d8a34](https://github.com/fengyuanchen/cropperjs/commit/93d8a34))\n* Renamed and improved `strict` option to `mode` ([60edabe](https://github.com/fengyuanchen/cropperjs/commit/60edabe))\n* Restore `preview` option ([c2b0724](https://github.com/fengyuanchen/cropperjs/commit/c2b0724))\n* Simplify ([2752d66](https://github.com/fengyuanchen/cropperjs/commit/2752d66))\n* Update changed options ([219291f](https://github.com/fengyuanchen/cropperjs/commit/219291f))\n* Update styles ([aa542ac](https://github.com/fengyuanchen/cropperjs/commit/aa542ac))\n* Updated changelog of v0.3.0 ([3ae0ad9](https://github.com/fengyuanchen/cropperjs/commit/3ae0ad9))\n* Updated demo code ([3512a56](https://github.com/fengyuanchen/cropperjs/commit/3512a56))\n\n\n\n## <small>0.2.1 (2015-10-28)</small>\n\n* Fix typo ([2a1f21f](https://github.com/fengyuanchen/cropperjs/commit/2a1f21f))\n* Released v0.2.1 ([0dbf95d](https://github.com/fengyuanchen/cropperjs/commit/0dbf95d))\n* remove jQuery reference in getCanvasData using existing each method ([ffde250](https://github.com/fengyuanchen/cropperjs/commit/ffde250))\n\n\n\n## 0.2.0 (2015-10-25)\n\n* Add tests for new methods ([83b19de](https://github.com/fengyuanchen/cropperjs/commit/83b19de))\n* Added `move/zoom/rotateTo` and `scaleX/Y` ([9bd6b4e](https://github.com/fengyuanchen/cropperjs/commit/9bd6b4e))\n* Added `naturalWidth/Height` to `canvasData` ([f5f94eb](https://github.com/fengyuanchen/cropperjs/commit/f5f94eb))\n* Added CSS files ([3367262](https://github.com/fengyuanchen/cropperjs/commit/3367262))\n* Export `naturalWidth/Height` with `getCanvasData` ([d6ae1d2](https://github.com/fengyuanchen/cropperjs/commit/d6ae1d2))\n* Fix variable mistake ([fb57e5a](https://github.com/fengyuanchen/cropperjs/commit/fb57e5a))\n* Improved the experience of cropping ([1dc67e4](https://github.com/fengyuanchen/cropperjs/commit/1dc67e4))\n* Optimized tests ([76bbdbc](https://github.com/fengyuanchen/cropperjs/commit/76bbdbc))\n* Parse numeric string by `Number` constructor ([bda377f](https://github.com/fengyuanchen/cropperjs/commit/bda377f))\n* Released v0.2.0 ([6d959df](https://github.com/fengyuanchen/cropperjs/commit/6d959df))\n* Round canvas size ([6a1aaed](https://github.com/fengyuanchen/cropperjs/commit/6a1aaed))\n* The `aspectRatio` should be a positive number ([3d97343](https://github.com/fengyuanchen/cropperjs/commit/3d97343))\n* Update ([c0cc90f](https://github.com/fengyuanchen/cropperjs/commit/c0cc90f))\n* Update demo ([a580e1e](https://github.com/fengyuanchen/cropperjs/commit/a580e1e))\n* Update tests with new changes ([e4a0893](https://github.com/fengyuanchen/cropperjs/commit/e4a0893))\n\n\n\n## <small>0.1.1 (2015-10-10)</small>\n\n* Add crossOrigin to preview images ([a94065d](https://github.com/fengyuanchen/cropperjs/commit/a94065d))\n* Fix code styles ([c3e4695](https://github.com/fengyuanchen/cropperjs/commit/c3e4695))\n* Fix date ([7d7e294](https://github.com/fengyuanchen/cropperjs/commit/7d7e294))\n* Fix image path ([cb7d9a4](https://github.com/fengyuanchen/cropperjs/commit/cb7d9a4))\n* Improve canvas limitation ([8a0bfc0](https://github.com/fengyuanchen/cropperjs/commit/8a0bfc0))\n* Improve crop box limitation ([be8844c](https://github.com/fengyuanchen/cropperjs/commit/be8844c))\n* Make crop box borders visible when full width ([2f042ed](https://github.com/fengyuanchen/cropperjs/commit/2f042ed))\n* Released v0.1.1 ([d425f3b](https://github.com/fengyuanchen/cropperjs/commit/d425f3b))\n* Restore `overflow: hidden` ([82dcfd0](https://github.com/fengyuanchen/cropperjs/commit/82dcfd0))\n\n\n\n## 0.1.0 (2015-09-25)\n\n* Added .gitattributes & .gitignore files ([3d5f69d](https://github.com/fengyuanchen/cropperjs/commit/3d5f69d))\n* Released v0.1.0 ([7d34674](https://github.com/fengyuanchen/cropperjs/commit/7d34674))\n\n\n\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright 2015-present Chen Fengyuan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Cropper.js\n\n[![Coverage Status](https://img.shields.io/codecov/c/github/fengyuanchen/cropperjs.svg)](https://codecov.io/gh/fengyuanchen/cropperjs) [![Downloads](https://img.shields.io/npm/dm/cropperjs.svg)](https://www.npmjs.com/package/cropperjs) [![Version](https://img.shields.io/npm/v/cropperjs.svg)](https://www.npmjs.com/package/cropperjs)\n\n> JavaScript image cropper. This is the branch for v2.x, for v1.x, check out the [`v1`](https://github.com/fengyuanchen/cropperjs/tree/v1) branch.\n\n- [Website](https://fengyuanchen.github.io/cropperjs/)\n\n## Versioning\n\nMaintained under the [Semantic Versioning guidelines](https://semver.org/).\n\n## Commit Message Guidelines\n\nGit commits message follows the [Conventional Commits guidelines](https://conventionalcommits.org/).\n\n## License\n\n[MIT](https://opensource.org/licenses/MIT) © [Chen Fengyuan](https://chenfengyuan.com/)\n\n## Related projects\n\n- [angular-cropperjs](https://github.com/matheusdavidson/angular-cropperjs) by [@matheusdavidson](https://github.com/matheusdavidson)\n- [blazor-cropperjs](https://github.com/CropperBlazor/Cropper.Blazor) by [@ColdForeign](https://github.com/ColdForeign), [@MaxymGorn](https://github.com/MaxymGorn)\n- [ember-cropperjs](https://github.com/danielthall/ember-cropperjs) by [@danielthall](https://github.com/danielthall)\n- [iron-cropper](https://github.com/safetychanger/iron-cropper) by [@safetychanger](https://github.com/safetychanger)\n- [react-cropper](https://github.com/react-cropper/react-cropper) by [@roadmanfong](https://github.com/roadmanfong)\n- [vue-cropperjs](https://github.com/Agontuk/vue-cropperjs) by [@Agontuk](https://github.com/Agontuk)\n"
  },
  {
    "path": "api-extractor.json",
    "content": "{\n  \"compiler\": {\n    \"overrideTsconfig\": {\n      \"compilerOptions\": {\n        \"paths\": {\n          \"@cropper/*\": [\"packages/*\"],\n          \"cropperjs\": [\"packages/cropperjs\"]\n        }\n      }\n    }\n  },\n  \"apiReport\": {\n    \"enabled\": false\n  },\n  \"docModel\": {\n    \"enabled\": false\n  },\n  \"dtsRollup\": {\n    \"enabled\": true\n  },\n  \"tsdocMetadata\": {\n    \"enabled\": false\n  },\n  \"messages\": {\n    \"tsdocMessageReporting\": {\n      \"default\": {\n        \"logLevel\": \"none\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "commitlint.config.js",
    "content": "module.exports = {\n  extends: [\n    '@commitlint/config-conventional',\n  ],\n};\n"
  },
  {
    "path": "docs/.vitepress/components/ColorInput.vue",
    "content": "<template>\n  <input\n    type=\"color\"\n    class=\"form-control form-control-color form-control-sm\"\n    :value=\"modelValueAsHex\"\n    @input=\"onInput\"\n  >\n</template>\n\n<script lang=\"ts\">\nimport rgbHex from 'rgb-hex';\n\nconst EVENT_UPDATE_MODEL_VALUE = 'update:modelValue';\n\nexport default {\n  name: 'ColorInput',\n  props: {\n    modelValue: {\n      type: String,\n      default: '',\n    },\n  },\n  emits: [EVENT_UPDATE_MODEL_VALUE],\n  computed: {\n    modelValueAsHex() {\n      const { modelValue } = this;\n\n      if (modelValue.includes('rgba')) {\n        return rgbHex(modelValue);\n      }\n\n      return modelValue;\n    },\n  },\n  methods: {\n    rgbHex,\n    onInput(event: any) {\n      this.$emit(EVENT_UPDATE_MODEL_VALUE, event.target.value, this.modelValue);\n    },\n  },\n};\n</script>\n"
  },
  {
    "path": "docs/.vitepress/components/CropperActionExample.vue",
    "content": "<template>\n  <div class=\"cropper-container\">\n    <cropper-canvas background>\n      <cropper-image\n        :src=\"src\"\n        alt=\"Picture\"\n        rotatable\n        scalable\n        skewable\n        translatable\n      />\n      <cropper-handle\n        action=\"select\"\n        plain\n        @dblclick=\"toggleActionOnDblclick\"\n      />\n      <cropper-selection\n        ref=\"cropperSelection\"\n        initial-coverage=\"0.5\"\n        movable\n        resizable\n        outlined\n      >\n        <cropper-grid\n          role=\"grid\"\n          covered\n        />\n        <cropper-crosshair centered />\n        <cropper-handle\n          action=\"move\"\n          theme-color=\"rgba(255, 255, 255, 0.35)\"\n          @dblclick=\"toggleActionOnDblclick\"\n        />\n        <cropper-handle action=\"n-resize\" />\n        <cropper-handle action=\"e-resize\" />\n        <cropper-handle action=\"s-resize\" />\n        <cropper-handle action=\"w-resize\" />\n        <cropper-handle action=\"ne-resize\" />\n        <cropper-handle action=\"nw-resize\" />\n        <cropper-handle action=\"se-resize\" />\n        <cropper-handle action=\"sw-resize\" />\n      </cropper-selection>\n    </cropper-canvas>\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport type CropperHandle from '@cropper/element-handle';\n\nconst { BASE_URL } = import.meta.env;\n\nexport default {\n  name: 'CropperActionExample',\n  data() {\n    return {\n      src: `${BASE_URL}picture.jpg`,\n    };\n  },\n  methods: {\n    toggleActionOnDblclick(event: any) {\n      const cropperHandle = event.target as CropperHandle;\n\n      cropperHandle.action = cropperHandle.action === 'move' ? 'select' : 'move';\n    },\n  },\n};\n</script>\n\n<style lang=\"scss\" scoped>\n.cropper-container {\n  border: 1px solid var(--vp-c-divider);\n  border-radius: 0.375rem;\n  margin-bottom: 1rem;\n  margin-top: 1rem;\n  padding: 1.25rem 1.5rem;\n\n  cropper-canvas {\n    height: 320px;\n  }\n}\n</style>\n"
  },
  {
    "path": "docs/.vitepress/components/CropperCanvasToNativeCanvas.vue",
    "content": "<template>\n  <div class=\"to-canvas-demo\">\n    <div>\n      <cropper-canvas\n        ref=\"source\"\n        background\n      >\n        <cropper-image\n          :src=\"src\"\n          alt=\"Picture\"\n          rotatable\n          scalable\n          skewable\n          translatable\n        />\n        <cropper-handle\n          action=\"move\"\n          plain\n        />\n      </cropper-canvas>\n    </div>\n    <button\n      type=\"button\"\n      @click=\"convertToCanvas\"\n    >\n      Convert to canvas &raquo;\n    </button>\n    <div ref=\"target\" />\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport type CropperCanvas from '@cropper/element-canvas';\n\nconst { BASE_URL } = import.meta.env;\n\nexport default {\n  name: 'CropperCanvasToNativeCanvas',\n  data() {\n    return {\n      src: `${BASE_URL}picture.jpg`,\n    };\n  },\n  methods: {\n    convertToCanvas() {\n      const source = this.$refs.source as CropperCanvas;\n      const target = this.$refs.target as HTMLElement;\n\n      target.innerHTML = '';\n      source.$toCanvas().then((canvas: HTMLCanvasElement) => {\n        target.appendChild(canvas);\n      });\n    },\n  },\n};\n</script>\n\n<style lang=\"scss\" scoped>\n.to-canvas-demo {\n  display: flex;\n\n  > :first-child,\n  > :last-child {\n    border: 1px solid var(--vp-c-divider);\n    flex: 1;\n    font-size: 0;\n  }\n\n  > :last-child {\n    align-items: center;\n    display: flex;\n    justify-content: center;\n\n    :deep(canvas) {\n      max-width: 100%;\n    }\n  }\n\n  > button {\n    align-self: center;\n    border: 1px solid var(--vp-c-brand);\n    border-radius: 0.25rem;\n    color: var(--vp-c-brand);\n    margin-left: 1rem;\n    margin-right: 1rem;\n    padding: 0.25rem 0.5rem;\n\n    &:focus,\n    &:hover {\n      background-color: var(--vp-c-brand);\n      color: var(--vp-c-bg);\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "docs/.vitepress/components/CropperExample.vue",
    "content": "<template>\n  <div class=\"cropper-container\" />\n</template>\n\n<script lang=\"ts\">\nimport Cropper from 'cropperjs';\n\nconst { BASE_URL } = import.meta.env;\n\nexport default {\n  name: 'CropperExample',\n  data() {\n    return {\n      src: `${BASE_URL}picture.jpg`,\n    };\n  },\n  mounted() {\n    const image = new Image();\n\n    image.src = this.src;\n    image.alt = 'Picture';\n\n    const cropper = new Cropper(image, {\n      container: '.cropper-container',\n    });\n\n    // eslint-disable-next-line no-console\n    console.log(cropper);\n  },\n};\n</script>\n\n<style lang=\"scss\" scoped>\n.cropper-container {\n  border: 1px solid var(--vp-c-divider);\n  border-radius: 0.375rem;\n  margin-bottom: 1rem;\n  margin-top: 1rem;\n  padding: 1.25rem 1.5rem;\n\n  :deep(cropper-canvas) {\n    height: 360px;\n  }\n}\n</style>\n"
  },
  {
    "path": "docs/.vitepress/components/CropperImageExample.vue",
    "content": "<template>\n  <div class=\"cropper-container\">\n    <form>\n      <fieldset>\n        <legend>Image Fit:</legend>\n        <input\n          id=\"inputImageFitContain\"\n          v-model=\"imageFit\"\n          type=\"radio\"\n          name=\"imageFit\"\n          value=\"contain\"\n        >\n        <label for=\"inputImageFitContain\">contain</label>\n        <input\n          id=\"inputImageFitCover\"\n          v-model=\"imageFit\"\n          type=\"radio\"\n          name=\"imageFit\"\n          value=\"cover\"\n        >\n        <label for=\"inputImageFitCover\">cover</label>\n        <input\n          id=\"inputImageFitNone\"\n          v-model=\"imageFit\"\n          type=\"radio\"\n          name=\"imageFit\"\n          value=\"none\"\n        >\n        <label for=\"inputImageFitNone\">none</label>\n      </fieldset>\n    </form>\n    <cropper-canvas\n      ref=\"cropperCanvas\"\n      :key=\"imageFit\"\n      background\n    >\n      <cropper-image\n        ref=\"cropperImage\"\n        :src=\"src\"\n        alt=\"Picture\"\n        rotatable\n        scalable\n        skewable\n        translatable\n        @transform=\"onCropperImageTransform\"\n      />\n      <cropper-handle\n        action=\"move\"\n        plain\n      />\n    </cropper-canvas>\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport type CropperCanvas from '@cropper/element-canvas';\nimport type CropperImage from '@cropper/element-image';\n\nconst { BASE_URL } = import.meta.env;\n\nexport default {\n  name: 'CropperImageExample',\n  data() {\n    return {\n      src: `${BASE_URL}picture.jpg`,\n      imageFit: 'contain',\n    };\n  },\n  methods: {\n    onCropperImageTransform(event: CustomEvent) {\n      const cropperCanvas = this.$refs.cropperCanvas as CropperCanvas;\n\n      if (!cropperCanvas || this.imageFit === 'none') {\n        return;\n      }\n\n      const cropperImage = this.$refs.cropperImage as CropperImage;\n      const cropperCanvasRect = cropperCanvas.getBoundingClientRect();\n\n      // 1. Clone the cropper image.\n      const cropperImageClone = cropperImage.cloneNode() as CropperImage;\n\n      // 2. Apply the new matrix to the cropper image clone.\n      cropperImageClone.style.transform = `matrix(${event.detail.matrix.join(', ')})`;\n\n      // 3. Make the cropper image clone invisible.\n      cropperImageClone.style.opacity = '0';\n\n      // 4. Append the cropper image clone to the cropper canvas.\n      cropperCanvas.appendChild(cropperImageClone);\n\n      // 5. Compute the boundaries of the cropper image clone.\n      const cropperImageRect = cropperImageClone.getBoundingClientRect();\n\n      // 6. Remove the cropper image clone.\n      cropperCanvas.removeChild(cropperImageClone);\n\n      if (\n        (this.imageFit === 'contain' && (\n          (\n            cropperImageRect.top > cropperCanvasRect.top\n            && cropperImageRect.right < cropperCanvasRect.right\n          )\n          || (\n            cropperImageRect.right < cropperCanvasRect.right\n            && cropperImageRect.bottom < cropperCanvasRect.bottom\n          )\n          || (\n            cropperImageRect.bottom < cropperCanvasRect.bottom\n            && cropperImageRect.left > cropperCanvasRect.left\n          )\n          || (\n            cropperImageRect.left > cropperCanvasRect.left\n            && cropperImageRect.top > cropperCanvasRect.top\n          )\n        ))\n        || (this.imageFit === 'cover' && (\n          cropperImageRect.top > cropperCanvasRect.top\n          || cropperImageRect.right < cropperCanvasRect.right\n          || cropperImageRect.bottom < cropperCanvasRect.bottom\n          || cropperImageRect.left > cropperCanvasRect.left\n        ))\n      ) {\n        event.preventDefault();\n      }\n    },\n  },\n};\n</script>\n\n<style lang=\"scss\" scoped>\n.cropper-container {\n  border: 1px solid var(--vp-c-divider);\n  border-radius: 0.375rem;\n  margin-bottom: 1rem;\n  margin-top: 1rem;\n  padding: 1.25rem 1.5rem;\n\n  fieldset {\n    border: 1px solid var(--vp-c-divider);\n    border-radius: 0.375rem;\n    margin-bottom: 1rem;\n    padding: 0.25rem 0.75rem 0.75rem 0.75rem;\n\n    > input {\n      margin: 0 0.25rem 0 0;\n      transform: translateY(-0.5px);\n      vertical-align: middle;\n    }\n\n    > label {\n      margin-right: 0.5rem;\n    }\n  }\n\n  cropper-canvas {\n    height: 320px;\n  }\n}\n</style>\n"
  },
  {
    "path": "docs/.vitepress/components/CropperPlayground.vue",
    "content": "<template>\n  <div\n    v-if=\"loading\"\n    class=\"loading\"\n  />\n  <div\n    v-else\n    class=\"playground\"\n  >\n    <aside>\n      <section class=\"d-grid\">\n        <button\n          type=\"button\"\n          class=\"btn btn-outline-primary btn-sm\"\n          @click=\"resetAll\"\n        >\n          Reset All\n        </button>\n      </section>\n      <section>\n        <h6>&lt;cropper-canvas&gt;</h6>\n        <ul>\n          <li>\n            <label for=\"canvasHidden\">hidden</label>\n            <input\n              id=\"canvasHidden\"\n              v-model=\"canvas.hidden\"\n              type=\"checkbox\"\n              name=\"hidden\"\n            >\n          </li>\n          <li>\n            <label for=\"canvasBackground\">background</label>\n            <input\n              id=\"canvasBackground\"\n              v-model=\"canvas.background\"\n              type=\"checkbox\"\n              name=\"background\"\n            >\n          </li>\n          <li>\n            <label for=\"canvasDisabled\">disabled</label>\n            <input\n              id=\"canvasDisabled\"\n              v-model=\"canvas.disabled\"\n              type=\"checkbox\"\n              name=\"disabled\"\n            >\n          </li>\n          <li>\n            <label for=\"canvasScaleStep\">scale-step</label>\n            <input\n              id=\"canvasScaleStep\"\n              v-model=\"canvas.scaleStep\"\n              type=\"number\"\n              name=\"scaleStep\"\n              min=\"0.1\"\n              max=\"1\"\n              step=\"0.1\"\n            >\n          </li>\n          <li>\n            <label for=\"canvasThemeColor\">theme-color</label>\n            <color-input\n              id=\"canvasThemeColor\"\n              v-model=\"canvas.themeColor\"\n              type=\"color\"\n              class=\"form-control form-control-color form-control-sm\"\n              name=\"themeColor\"\n            />\n          </li>\n          <li>\n            <button\n              type=\"button\"\n              class=\"btn btn-outline-primary btn-sm\"\n              data-bs-toggle=\"modal\"\n              data-bs-target=\"#canvasViewerModal\"\n              @click=\"cropperCanvasToCanvas\"\n            >\n              $toCanvas()\n            </button>\n          </li>\n        </ul>\n      </section>\n      <section>\n        <h6>&lt;cropper-image&gt;</h6>\n        <ul>\n          <li>\n            <label for=\"imageHidden\">hidden</label>\n            <input\n              id=\"imageHidden\"\n              v-model=\"image.hidden\"\n              type=\"checkbox\"\n              name=\"hidden\"\n            >\n          </li>\n          <li>\n            <label for=\"imageRotatable\">rotatable</label>\n            <input\n              id=\"imageRotatable\"\n              v-model=\"image.rotatable\"\n              type=\"checkbox\"\n              name=\"rotatable\"\n            >\n          </li>\n          <li>\n            <label for=\"imageScalable\">scalable</label>\n            <input\n              id=\"imageScalable\"\n              v-model=\"image.scalable\"\n              type=\"checkbox\"\n              name=\"scalable\"\n            >\n          </li>\n          <li>\n            <label for=\"imageSkewable\">skewable</label>\n            <input\n              id=\"imageSkewable\"\n              v-model=\"image.skewable\"\n              type=\"checkbox\"\n              name=\"skewable\"\n            >\n          </li>\n          <li>\n            <label for=\"imageTranslatable\">translatable</label>\n            <input\n              id=\"imageTranslatable\"\n              v-model=\"image.translatable\"\n              type=\"checkbox\"\n              name=\"translatable\"\n            >\n          </li>\n          <li>\n            <label for=\"imageInitialCenterSize\">initialCenterSize</label>\n            <select\n              id=\"imageInitialCenterSize\"\n              v-model=\"image.initialCenterSize\"\n              class=\"form-control form-control-sm\"\n              name=\"initialCenterSize\"\n            >\n              <option value=\"contain\">\n                contain\n              </option>\n              <option value=\"cover\">\n                cover\n              </option>\n            </select>\n          </li>\n          <li>\n            <label for=\"imageSrc\">src</label>\n            <input\n              id=\"imageSrc\"\n              v-model=\"image.src\"\n              type=\"text\"\n              class=\"form-control form-control-sm\"\n              name=\"src\"\n              autocomplete=\"off\"\n            >\n          </li>\n          <li>\n            <label for=\"imageAlt\">alt</label>\n            <input\n              id=\"imageAlt\"\n              v-model=\"image.alt\"\n              type=\"text\"\n              class=\"form-control form-control-sm\"\n              name=\"alt\"\n              autocomplete=\"off\"\n            >\n          </li>\n          <li>\n            <button\n              type=\"button\"\n              class=\"btn btn-outline-primary btn-sm\"\n              data-toggle=\"tooltip\"\n              data-placement=\"top\"\n              title=\"image.$getTransform()\"\n              @click=\"output.image.matrix = JSON.stringify($refs.cropperImage.$getTransform())\"\n            >\n              $getTransform()\n            </button>\n          </li>\n          <li>\n            <button\n              type=\"button\"\n              class=\"btn btn-outline-primary btn-sm\"\n              data-toggle=\"tooltip\"\n              data-placement=\"top\"\n              title=\"image.$setTransform()\"\n              @click=\"$refs.cropperImage.$setTransform(JSON.parse(output.image.matrix))\"\n            >\n              $setTransform()\n            </button>\n          </li>\n          <li>\n            <button\n              type=\"button\"\n              class=\"btn btn-outline-primary btn-sm\"\n              data-toggle=\"tooltip\"\n              data-placement=\"top\"\n              title=\"image.$resetTransform()\"\n              @click=\"output.image.matrix = JSON.stringify($refs.cropperImage.$resetTransform().$getTransform())\"\n            >\n              $resetTransform()\n            </button>\n          </li>\n          <li>\n            <button\n              type=\"button\"\n              class=\"btn btn-outline-primary btn-sm\"\n              data-toggle=\"tooltip\"\n              data-placement=\"top\"\n              title=\"image.$center()\"\n              @click=\"$refs.cropperImage.$center()\"\n            >\n              $center()\n            </button>\n            <button\n              type=\"button\"\n              class=\"btn btn-outline-primary btn-sm\"\n              data-toggle=\"tooltip\"\n              data-placement=\"top\"\n              title=\"image.$center('contain')\"\n              @click=\"$refs.cropperImage.$center('contain')\"\n            >\n              $center('contain')\n            </button>\n          </li>\n          <li>\n            <button\n              type=\"button\"\n              class=\"btn btn-outline-primary btn-sm\"\n              data-toggle=\"tooltip\"\n              data-placement=\"top\"\n              title=\"image.$center('cover')\"\n              @click=\"$refs.cropperImage.$center('cover')\"\n            >\n              $center('cover')\n            </button>\n            <button\n              type=\"button\"\n              class=\"btn btn-outline-primary btn-sm\"\n              data-toggle=\"tooltip\"\n              data-placement=\"top\"\n              title=\"image.$moveTo(0, 0)\"\n              @click=\"$refs.cropperImage.$moveTo(0, 0)\"\n            >\n              $moveTo(0, 0)\n            </button>\n          </li>\n          <li>\n            <div class=\"btn-groups\">\n              <div\n                class=\"btn-group\"\n                role=\"group\"\n                aria-label=\"Move the cropper image\"\n              >\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"image.$move(-1, -1)\"\n                  @click=\"$refs.cropperImage.$move(-1, -1)\"\n                >\n                  <i class=\"bi-arrow-up-left\" />\n                </button>\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"image.$move(0, -1)\"\n                  @click=\"$refs.cropperImage.$move(0, -1)\"\n                >\n                  <i class=\"bi-arrow-up\" />\n                </button>\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"image.$move(1, -1)\"\n                  @click=\"$refs.cropperImage.$move(1, -1)\"\n                >\n                  <i class=\"bi-arrow-up-right\" />\n                </button>\n              </div>\n              <div\n                class=\"btn-group\"\n                role=\"group\"\n                aria-label=\"Move the cropper image\"\n              >\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"image.$move(-1, 0)\"\n                  @click=\"$refs.cropperImage.$move(-1, 0)\"\n                >\n                  <i class=\"bi-arrow-left\" />\n                </button>\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  disabled\n                >\n                  <i class=\"bi-arrows-move\" />\n                </button>\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"image.$move(1, 0)\"\n                  @click=\"$refs.cropperImage.$move(1, 0)\"\n                >\n                  <i class=\"bi-arrow-right\" />\n                </button>\n              </div>\n              <div\n                class=\"btn-group\"\n                role=\"group\"\n                aria-label=\"Move the cropper image\"\n              >\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"image.$move(-1, 1)\"\n                  @click=\"$refs.cropperImage.$move(-1, 1)\"\n                >\n                  <i class=\"bi-arrow-down-left\" />\n                </button>\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"image.$move(0, 1)\"\n                  @click=\"$refs.cropperImage.$move(0, 1)\"\n                >\n                  <i class=\"bi-arrow-down\" />\n                </button>\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"image.$move(1, 1)\"\n                  @click=\"$refs.cropperImage.$move(1, 1)\"\n                >\n                  <i class=\"bi-arrow-down-right\" />\n                </button>\n              </div>\n            </div>\n            <div class=\"btn-groups\">\n              <div\n                class=\"btn-group\"\n                role=\"group\"\n                aria-label=\"Translate the cropper image\"\n              >\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"image.$translate(-1, -1)\"\n                  @click=\"$refs.cropperImage.$translate(-1, -1)\"\n                >\n                  <i class=\"bi-arrow-up-left\" />\n                </button>\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"image.$translate(0, -1)\"\n                  @click=\"$refs.cropperImage.$translate(0, -1)\"\n                >\n                  <i class=\"bi-arrow-up\" />\n                </button>\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"image.$translate(1, -1)\"\n                  @click=\"$refs.cropperImage.$translate(1, -1)\"\n                >\n                  <i class=\"bi-arrow-up-right\" />\n                </button>\n              </div>\n              <div\n                class=\"btn-group\"\n                role=\"group\"\n                aria-label=\"Translate the cropper image\"\n              >\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"image.$translate(-1, 0)\"\n                  @click=\"$refs.cropperImage.$translate(-1, 0)\"\n                >\n                  <i class=\"bi-arrow-left\" />\n                </button>\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  disabled\n                >\n                  <i class=\"bi-arrows-move\" />\n                </button>\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"image.$translate(1, 0)\"\n                  @click=\"$refs.cropperImage.$translate(1, 0)\"\n                >\n                  <i class=\"bi-arrow-right\" />\n                </button>\n              </div>\n              <div\n                class=\"btn-group\"\n                role=\"group\"\n                aria-label=\"Translate the cropper image\"\n              >\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"image.$translate(-1, 1)\"\n                  @click=\"$refs.cropperImage.$translate(-1, 1)\"\n                >\n                  <i class=\"bi-arrow-down-left\" />\n                </button>\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"image.$translate(0, 1)\"\n                  @click=\"$refs.cropperImage.$translate(0, 1)\"\n                >\n                  <i class=\"bi-arrow-down\" />\n                </button>\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"image.$translate(1, 1)\"\n                  @click=\"$refs.cropperImage.$translate(1, 1)\"\n                >\n                  <i class=\"bi-arrow-down-right\" />\n                </button>\n              </div>\n            </div>\n          </li>\n          <li>\n            <div\n              class=\"btn-group\"\n              role=\"group\"\n              aria-label=\"Scale the cropper image\"\n            >\n              <button\n                type=\"button\"\n                class=\"btn btn-outline-primary btn-sm\"\n                data-toggle=\"tooltip\"\n                data-placement=\"top\"\n                title=\"image.$zoom(0.1)\"\n                @click=\"$refs.cropperImage.$zoom(0.1)\"\n              >\n                <i class=\"bi-zoom-in\" />\n              </button>\n              <button\n                type=\"button\"\n                class=\"btn btn-outline-primary btn-sm\"\n                data-toggle=\"tooltip\"\n                data-placement=\"top\"\n                title=\"image.$zoom(-0.1)\"\n                @click=\"$refs.cropperImage.$zoom(-0.1)\"\n              >\n                <i class=\"bi-zoom-out\" />\n              </button>\n            </div>\n            <div\n              class=\"btn-group\"\n              role=\"group\"\n              aria-label=\"Scale the cropper image\"\n            >\n              <button\n                type=\"button\"\n                class=\"btn btn-outline-primary btn-sm\"\n                data-toggle=\"tooltip\"\n                data-placement=\"top\"\n                title=\"image.$zoom(0.1, 0, 0)\"\n                @click=\"$refs.cropperImage.$zoom(0.1, 0, 0)\"\n              >\n                <i class=\"bi-zoom-in\" />\n              </button>\n              <button\n                type=\"button\"\n                class=\"btn btn-outline-primary btn-sm\"\n                data-toggle=\"tooltip\"\n                data-placement=\"top\"\n                title=\"image.$zoom(-0.1, 0, 0)\"\n                @click=\"$refs.cropperImage.$zoom(-0.1, 0, 0)\"\n              >\n                <i class=\"bi-zoom-out\" />\n              </button>\n            </div>\n            <div\n              class=\"btn-group\"\n              role=\"group\"\n              aria-label=\"Rotate the cropper image\"\n            >\n              <button\n                type=\"button\"\n                class=\"btn btn-outline-primary btn-sm\"\n                data-toggle=\"tooltip\"\n                data-placement=\"top\"\n                title=\"image.$scale(-1, 1)\"\n                @click=\"$refs.cropperImage.$scale(-1, 1)\"\n              >\n                <i class=\"bi-arrow-left-right\" />\n              </button>\n              <button\n                type=\"button\"\n                class=\"btn btn-outline-primary btn-sm\"\n                data-toggle=\"tooltip\"\n                data-placement=\"top\"\n                title=\"image.$scale(1, -1)\"\n                @click=\"$refs.cropperImage.$scale(1, -1)\"\n              >\n                <i class=\"bi-arrow-down-up\" />\n              </button>\n            </div>\n          </li>\n          <li>\n            <div\n              class=\"btn-group\"\n              role=\"group\"\n              aria-label=\"Rotate the cropper image\"\n            >\n              <button\n                type=\"button\"\n                class=\"btn btn-outline-primary btn-sm\"\n                data-toggle=\"tooltip\"\n                data-placement=\"top\"\n                title=\"image.$rotate('-45deg')\"\n                @click=\"$refs.cropperImage.$rotate('-45deg')\"\n              >\n                <i class=\"bi-arrow-90deg-left\" />\n              </button>\n              <button\n                type=\"button\"\n                class=\"btn btn-outline-primary btn-sm\"\n                data-toggle=\"tooltip\"\n                data-placement=\"top\"\n                title=\"image.$rotate('45deg')\"\n                @click=\"$refs.cropperImage.$rotate('45deg')\"\n              >\n                <i class=\"bi-arrow-90deg-right\" />\n              </button>\n            </div>\n            <div\n              class=\"btn-group\"\n              role=\"group\"\n              aria-label=\"Rotate the cropper image\"\n            >\n              <button\n                type=\"button\"\n                class=\"btn btn-outline-primary btn-sm\"\n                data-toggle=\"tooltip\"\n                data-placement=\"top\"\n                title=\"image.$rotate('-45deg', 0, 0)\"\n                @click=\"$refs.cropperImage.$rotate('-45deg', 0, 0)\"\n              >\n                <i class=\"bi-arrow-90deg-left\" />\n              </button>\n              <button\n                type=\"button\"\n                class=\"btn btn-outline-primary btn-sm\"\n                data-toggle=\"tooltip\"\n                data-placement=\"top\"\n                title=\"image.$rotate('45deg', 0, 0)\"\n                @click=\"$refs.cropperImage.$rotate('45deg', 0, 0)\"\n              >\n                <i class=\"bi-arrow-90deg-right\" />\n              </button>\n            </div>\n            <div\n              class=\"btn-group\"\n              role=\"group\"\n              aria-label=\"Rotate the cropper image\"\n            >\n              <button\n                type=\"button\"\n                class=\"btn btn-outline-primary btn-sm\"\n                data-toggle=\"tooltip\"\n                data-placement=\"top\"\n                title=\"image.$skew('45deg')\"\n                @click=\"$refs.cropperImage.$skew('45deg')\"\n              >\n                <i class=\"bi-arrow-up-left-square\" />\n              </button>\n              <button\n                type=\"button\"\n                class=\"btn btn-outline-primary btn-sm\"\n                data-toggle=\"tooltip\"\n                data-placement=\"top\"\n                title=\"image.$skew('-45deg')\"\n                @click=\"$refs.cropperImage.$skew('-45deg')\"\n              >\n                <i class=\"bi-arrow-up-right-square\" />\n              </button>\n            </div>\n          </li>\n          <li>\n            <textarea\n              id=\"imageMatrix\"\n              v-model=\"output.image.matrix\"\n              class=\"form-control form-control-sm\"\n            />\n          </li>\n        </ul>\n      </section>\n      <section>\n        <h6>&lt;cropper-shade&gt;</h6>\n        <ul>\n          <li>\n            <label for=\"shadeHidden\">hidden</label>\n            <input\n              id=\"shadeHidden\"\n              v-model=\"shade.hidden\"\n              type=\"checkbox\"\n              name=\"hidden\"\n            >\n          </li>\n          <li>\n            <label for=\"shadeThemeColor\">theme-color</label>\n            <color-input\n              id=\"shadeThemeColor\"\n              v-model=\"shade.themeColor\"\n              type=\"color\"\n              class=\"form-control form-control-color form-control-sm\"\n              name=\"themeColor\"\n            />\n          </li>\n        </ul>\n      </section>\n      <section>\n        <h6>&lt;cropper-handle&gt;</h6>\n        <ul>\n          <li>\n            <label for=\"handleHidden\">hidden</label>\n            <input\n              id=\"handleHidden\"\n              v-model=\"handle.hidden\"\n              type=\"checkbox\"\n              name=\"hidden\"\n            >\n          </li>\n          <li>\n            <label for=\"handleAction\">action</label>\n            <select\n              id=\"handleAction\"\n              v-model=\"handle.action\"\n              class=\"form-control form-control-sm\"\n              name=\"action\"\n            >\n              <option value=\"select\">\n                select\n              </option>\n              <option value=\"move\">\n                move\n              </option>\n              <option\n                value=\"scale\"\n                disabled\n              >\n                scale\n              </option>\n              <option value=\"none\">\n                none\n              </option>\n              <option\n                value=\"n-resize\"\n                disabled\n              >\n                n-resize\n              </option>\n              <option\n                value=\"e-resize\"\n                disabled\n              >\n                e-resize\n              </option>\n              <option\n                value=\"s-resize\"\n                disabled\n              >\n                s-resize\n              </option>\n              <option\n                value=\"w-resize\"\n                disabled\n              >\n                w-resize\n              </option>\n              <option\n                value=\"ne-resize\"\n                disabled\n              >\n                ne-resize\n              </option>\n              <option\n                value=\"nw-resize\"\n                disabled\n              >\n                nw-resize\n              </option>\n              <option\n                value=\"se-resize\"\n                disabled\n              >\n                se-resize\n              </option>\n              <option\n                value=\"sw-resize\"\n                disabled\n              >\n                sw-resize\n              </option>\n            </select>\n          </li>\n          <li>\n            <label for=\"handlePlain\">plain</label>\n            <input\n              id=\"handlePlain\"\n              v-model=\"handle.plain\"\n              type=\"checkbox\"\n              name=\"plain\"\n            >\n          </li>\n          <li>\n            <label for=\"handleThemeColor\">theme-color</label>\n            <color-input\n              id=\"handleThemeColor\"\n              v-model=\"handle.themeColor\"\n              type=\"color\"\n              class=\"form-control form-control-color form-control-sm\"\n              name=\"themeColor\"\n            />\n          </li>\n        </ul>\n      </section>\n      <section>\n        <h6>&lt;cropper-selection&gt;</h6>\n        <ul>\n          <li>\n            <label for=\"selectionHidden\">hidden</label>\n            <input\n              id=\"selectionHidden\"\n              v-model=\"selection.hidden\"\n              type=\"checkbox\"\n              name=\"hidden\"\n            >\n          </li>\n          <li>\n            <label for=\"selectionX\">x</label>\n            <input\n              id=\"selectionX\"\n              v-model=\"selection.x\"\n              class=\"form-control form-control-sm\"\n              type=\"number\"\n              name=\"x\"\n              autocomplete=\"off\"\n            >\n          </li>\n          <li>\n            <label for=\"selectionY\">y</label>\n            <input\n              id=\"selectionY\"\n              v-model=\"selection.y\"\n              class=\"form-control form-control-sm\"\n              type=\"number\"\n              name=\"y\"\n              autocomplete=\"off\"\n            >\n          </li>\n          <li>\n            <label for=\"selectionWidth\">width</label>\n            <input\n              id=\"selectionWidth\"\n              v-model=\"selection.width\"\n              class=\"form-control form-control-sm\"\n              type=\"number\"\n              name=\"width\"\n              min=\"0\"\n              autocomplete=\"off\"\n            >\n          </li>\n          <li>\n            <label for=\"selectionHeight\">height</label>\n            <input\n              id=\"selectionHeight\"\n              v-model=\"selection.height\"\n              class=\"form-control form-control-sm\"\n              type=\"number\"\n              name=\"height\"\n              min=\"0\"\n              autocomplete=\"off\"\n            >\n          </li>\n          <li>\n            <label for=\"selectionAspectRatio\">aspect-ratio</label>\n            <select\n              id=\"selectionAspectRatio\"\n              v-model.number=\"selection.aspectRatio\"\n              class=\"form-control form-control-sm\"\n              name=\"aspectRatio\"\n            >\n              <option :value=\"undefined\">\n                Free\n              </option>\n              <option :value=\"16/9\">\n                16:9\n              </option>\n              <option :value=\"4/3\">\n                4:3\n              </option>\n              <option :value=\"1\">\n                1:1\n              </option>\n              <option :value=\"3/4\">\n                3:4\n              </option>\n              <option :value=\"9/16\">\n                9:16\n              </option>\n            </select>\n          </li>\n          <li>\n            <label for=\"selectionInitialAspectRatio\">initial-aspect-ratio</label>\n            <select\n              id=\"selectionInitialAspectRatio\"\n              v-model.number=\"selection.initialAspectRatio\"\n              class=\"form-control form-control-sm\"\n              name=\"initialAspectRatio\"\n            >\n              <option :value=\"undefined\">\n                Free\n              </option>\n              <option :value=\"16/9\">\n                16:9\n              </option>\n              <option :value=\"4/3\">\n                4:3\n              </option>\n              <option :value=\"1\">\n                1:1\n              </option>\n              <option :value=\"3/4\">\n                3:4\n              </option>\n              <option :value=\"9/16\">\n                9:16\n              </option>\n            </select>\n          </li>\n          <li>\n            <label for=\"selectionAutoSelectArea\">initial-coverage</label>\n            <input\n              id=\"selectionAutoSelectArea\"\n              v-model.number=\"selection.initialCoverage\"\n              type=\"range\"\n              class=\"form-range form-control-sm\"\n              name=\"initialCoverage\"\n              min=\"0\"\n              max=\"1\"\n              step=\"0.1\"\n            >\n          </li>\n          <li>\n            <label for=\"selectionLinked\">dynamic</label>\n            <input\n              id=\"selectionLinked\"\n              v-model=\"selection.dynamic\"\n              type=\"checkbox\"\n              name=\"dynamic\"\n            >\n          </li>\n          <li>\n            <label for=\"selectionMovable\">movable</label>\n            <input\n              id=\"selectionMovable\"\n              v-model=\"selection.movable\"\n              type=\"checkbox\"\n              name=\"movable\"\n            >\n          </li>\n          <li>\n            <label for=\"selectionResizable\">resizable</label>\n            <input\n              id=\"selectionResizable\"\n              v-model=\"selection.resizable\"\n              type=\"checkbox\"\n              name=\"resizable\"\n            >\n          </li>\n          <li>\n            <label for=\"selectionZoomable\">zoomable</label>\n            <input\n              id=\"selectionZoomable\"\n              v-model=\"selection.zoomable\"\n              type=\"checkbox\"\n              name=\"zoomable\"\n            >\n          </li>\n          <li>\n            <label for=\"selectionMultiple\">multiple</label>\n            <input\n              id=\"selectionMultiple\"\n              v-model=\"selection.multiple\"\n              type=\"checkbox\"\n              name=\"multiple\"\n            >\n          </li>\n          <li>\n            <label for=\"selectionKeyboard\">keyboard</label>\n            <input\n              id=\"selectionKeyboard\"\n              v-model=\"selection.keyboard\"\n              type=\"checkbox\"\n              name=\"keyboard\"\n            >\n          </li>\n          <li>\n            <label for=\"selectionOutlined\">outlined</label>\n            <input\n              id=\"selectionOutlined\"\n              v-model=\"selection.outlined\"\n              type=\"checkbox\"\n              name=\"outlined\"\n            >\n          </li>\n          <li>\n            <label for=\"selectionPrecise\">precise</label>\n            <input\n              id=\"selectionPrecise\"\n              v-model=\"selection.precise\"\n              type=\"checkbox\"\n              name=\"precise\"\n            >\n          </li>\n          <li>\n            <button\n              type=\"button\"\n              class=\"btn btn-outline-primary btn-sm\"\n              data-toggle=\"tooltip\"\n              data-placement=\"top\"\n              title=\"selection.$change(0, 0, 160, 90)\"\n              @click=\"$refs.cropperSelection.$change(0, 0, 160, 90)\"\n            >\n              $change(0, 0, 160, 90)\n            </button>\n          </li>\n          <li>\n            <button\n              type=\"button\"\n              class=\"btn btn-outline-primary btn-sm\"\n              data-toggle=\"tooltip\"\n              data-placement=\"top\"\n              title=\"selection.$center()\"\n              @click=\"$refs.cropperSelection.$center()\"\n            >\n              $center()\n            </button>\n            <button\n              type=\"button\"\n              class=\"btn btn-outline-primary btn-sm\"\n              data-toggle=\"tooltip\"\n              data-placement=\"top\"\n              title=\"selection.$reset()\"\n              @click=\"$refs.cropperSelection.$reset()\"\n            >\n              $reset()\n            </button>\n            <button\n              type=\"button\"\n              class=\"btn btn-outline-primary btn-sm\"\n              data-toggle=\"tooltip\"\n              data-placement=\"top\"\n              title=\"selection.$clear()\"\n              @click=\"$refs.cropperSelection.$clear()\"\n            >\n              $clear()\n            </button>\n          </li>\n          <li>\n            <button\n              type=\"button\"\n              class=\"btn btn-outline-primary btn-sm\"\n              data-toggle=\"tooltip\"\n              data-placement=\"top\"\n              title=\"selection.$moveTo(0, 0)\"\n              @click=\"$refs.cropperSelection.$moveTo(0, 0)\"\n            >\n              $moveTo(0, 0)\n            </button>\n            <button\n              type=\"button\"\n              class=\"btn btn-outline-primary btn-sm\"\n              data-bs-toggle=\"modal\"\n              data-bs-target=\"#canvasViewerModal\"\n              @click=\"cropperSelectionToCanvas\"\n            >\n              $toCanvas()\n            </button>\n          </li>\n          <li>\n            <div class=\"btn-groups\">\n              <div\n                class=\"btn-group\"\n                role=\"group\"\n                aria-label=\"Move the cropper selection\"\n              >\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"selection.$move(-1, -1)\"\n                  @click=\"$refs.cropperSelection.$move(-1, -1)\"\n                >\n                  <i class=\"bi-arrow-up-left\" />\n                </button>\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"selection.$move(0, -1)\"\n                  @click=\"$refs.cropperSelection.$move(0, -1)\"\n                >\n                  <i class=\"bi-arrow-up\" />\n                </button>\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"selection.$move(1, -1)\"\n                  @click=\"$refs.cropperSelection.$move(1, -1)\"\n                >\n                  <i class=\"bi-arrow-up-right\" />\n                </button>\n              </div>\n              <div\n                class=\"btn-group\"\n                role=\"group\"\n                aria-label=\"Move the cropper selection\"\n              >\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"selection.$move(-1, 0)\"\n                  @click=\"$refs.cropperSelection.$move(-1, 0)\"\n                >\n                  <i class=\"bi-arrow-left\" />\n                </button>\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  disabled\n                >\n                  <i class=\"bi-arrows-move\" />\n                </button>\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"selection.$move(1, 0)\"\n                  @click=\"$refs.cropperSelection.$move(1, 0)\"\n                >\n                  <i class=\"bi-arrow-right\" />\n                </button>\n              </div>\n              <div\n                class=\"btn-group\"\n                role=\"group\"\n                aria-label=\"Move the cropper selection\"\n              >\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"selection.$move(-1, 1)\"\n                  @click=\"$refs.cropperSelection.$move(-1, 1)\"\n                >\n                  <i class=\"bi-arrow-down-left\" />\n                </button>\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"selection.$move(0, 1)\"\n                  @click=\"$refs.cropperSelection.$move(0, 1)\"\n                >\n                  <i class=\"bi-arrow-down\" />\n                </button>\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"selection.$move(1, 1)\"\n                  @click=\"$refs.cropperSelection.$move(1, 1)\"\n                >\n                  <i class=\"bi-arrow-down-right\" />\n                </button>\n              </div>\n            </div>\n          </li>\n          <li>\n            <div class=\"btn-groups\">\n              <div\n                class=\"btn-group\"\n                role=\"group\"\n                aria-label=\"Resize the cropper selection\"\n              >\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"selection.$resize('nw-resize', -1, -1)\"\n                  @click=\"$refs.cropperSelection.$resize('nw-resize', -1, -1)\"\n                >\n                  <i class=\"bi-arrow-up-left-square\" />\n                </button>\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"selection.$resize('n-resize', 0, -1)\"\n                  @click=\"$refs.cropperSelection.$resize('n-resize', 0, -1)\"\n                >\n                  <i class=\"bi-arrow-up-square\" />\n                </button>\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"selection.$resize('ne-resize', 1, -1)\"\n                  @click=\"$refs.cropperSelection.$resize('ne-resize', 1, -1)\"\n                >\n                  <i class=\"bi-arrow-up-right-square\" />\n                </button>\n              </div>\n              <div\n                class=\"btn-group\"\n                role=\"group\"\n                aria-label=\"Resize the cropper selection\"\n              >\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"selection.$resize('w-resize', -1, 0)\"\n                  @click=\"$refs.cropperSelection.$resize('w-resize', -1, 0)\"\n                >\n                  <i class=\"bi-arrow-left-square\" />\n                </button>\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"selection.$zoom(0.1)\"\n                  @click=\"$refs.cropperSelection.$zoom(0.1)\"\n                >\n                  <i class=\"bi-zoom-in\" />\n                </button>\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"selection.$resize('e-resize', 1, 0)\"\n                  @click=\"$refs.cropperSelection.$resize('e-resize', 1, 0)\"\n                >\n                  <i class=\"bi-arrow-right-square\" />\n                </button>\n              </div>\n              <div\n                class=\"btn-group\"\n                role=\"group\"\n                aria-label=\"Resize the cropper selection\"\n              >\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"selection.$resize('sw-resize', -1, 1)\"\n                  @click=\"$refs.cropperSelection.$resize('sw-resize', -1, 1)\"\n                >\n                  <i class=\"bi-arrow-down-left-square\" />\n                </button>\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"selection.$resize('s-resize', 0, 1)\"\n                  @click=\"$refs.cropperSelection.$resize('s-resize', 0, 1)\"\n                >\n                  <i class=\"bi-arrow-down-square\" />\n                </button>\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"selection.$resize('se-resize', 1, 1)\"\n                  @click=\"$refs.cropperSelection.$resize('se-resize', 1, 1)\"\n                >\n                  <i class=\"bi-arrow-down-right-square\" />\n                </button>\n              </div>\n            </div>\n            <div class=\"btn-groups\">\n              <div\n                class=\"btn-group\"\n                role=\"group\"\n                aria-label=\"Resize the cropper selection\"\n              >\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"selection.$resize('nw-resize', 1, 1)\"\n                  @click=\"$refs.cropperSelection.$resize('nw-resize', 1, 1)\"\n                >\n                  <i class=\"bi-arrow-down-right-square\" />\n                </button>\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"selection.$resize('n-resize', 0, 1)\"\n                  @click=\"$refs.cropperSelection.$resize('n-resize', 0, 1)\"\n                >\n                  <i class=\"bi-arrow-down-square\" />\n                </button>\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"selection.$resize('ne-resize', -1, 1)\"\n                  @click=\"$refs.cropperSelection.$resize('ne-resize', -1, 1)\"\n                >\n                  <i class=\"bi-arrow-down-left-square\" />\n                </button>\n              </div>\n              <div\n                class=\"btn-group\"\n                role=\"group\"\n                aria-label=\"Resize the cropper selection\"\n              >\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"selection.$resize('w-resize', 1, 0)\"\n                  @click=\"$refs.cropperSelection.$resize('w-resize', 1, 0)\"\n                >\n                  <i class=\"bi-arrow-right-square\" />\n                </button>\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"selection.$zoom(-0.1)\"\n                  @click=\"$refs.cropperSelection.$zoom(-0.1)\"\n                >\n                  <i class=\"bi-zoom-out\" />\n                </button>\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"selection.$resize('e-resize', -1, 0)\"\n                  @click=\"$refs.cropperSelection.$resize('e-resize', -1, 0)\"\n                >\n                  <i class=\"bi-arrow-left-square\" />\n                </button>\n              </div>\n              <div\n                class=\"btn-group\"\n                role=\"group\"\n                aria-label=\"Resize the cropper selection\"\n              >\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"selection.$resize('sw-resize', 1, -1)\"\n                  @click=\"$refs.cropperSelection.$resize('sw-resize', 1, -1)\"\n                >\n                  <i class=\"bi-arrow-up-right-square\" />\n                </button>\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"selection.$resize('s-resize', 0, -1)\"\n                  @click=\"$refs.cropperSelection.$resize('s-resize', 0, -1)\"\n                >\n                  <i class=\"bi-arrow-up-square\" />\n                </button>\n                <button\n                  type=\"button\"\n                  class=\"btn btn-outline-primary btn-sm\"\n                  data-toggle=\"tooltip\"\n                  data-placement=\"top\"\n                  title=\"selection.$resize('se-resize', -1, -1)\"\n                  @click=\"$refs.cropperSelection.$resize('se-resize', -1, -1)\"\n                >\n                  <i class=\"bi-arrow-up-left-square\" />\n                </button>\n              </div>\n            </div>\n          </li>\n        </ul>\n      </section>\n      <section>\n        <h6>&lt;cropper-grid&gt;</h6>\n        <ul>\n          <li>\n            <label for=\"selectionGridHidden\">hidden</label>\n            <input\n              id=\"selectionGridHidden\"\n              v-model=\"grid.hidden\"\n              type=\"checkbox\"\n              name=\"hidden\"\n            >\n          </li>\n          <li>\n            <label for=\"selectionGridRows\">rows</label>\n            <input\n              id=\"selectionGridRows\"\n              v-model.number=\"grid.rows\"\n              type=\"number\"\n              class=\"form-control form-control-sm\"\n              name=\"rows\"\n              min=\"0\"\n              max=\"9\"\n            >\n          </li>\n          <li>\n            <label for=\"selectionGridColumns\">columns</label>\n            <input\n              id=\"selectionGridColumns\"\n              v-model.number=\"grid.columns\"\n              type=\"number\"\n              class=\"form-control form-control-sm\"\n              name=\"columns\"\n              min=\"0\"\n              max=\"9\"\n            >\n          </li>\n          <li>\n            <label for=\"selectionGridBordered\">bordered</label>\n            <input\n              id=\"selectionGridBordered\"\n              v-model=\"grid.bordered\"\n              type=\"checkbox\"\n              name=\"bordered\"\n            >\n          </li>\n          <li>\n            <label for=\"selectionGridCovered\">covered</label>\n            <input\n              id=\"selectionGridCovered\"\n              v-model=\"grid.covered\"\n              type=\"checkbox\"\n              name=\"covered\"\n            >\n          </li>\n          <li>\n            <label for=\"selectionGridThemeColor\">theme-color</label>\n            <color-input\n              id=\"selectionGridThemeColor\"\n              v-model=\"grid.themeColor\"\n              type=\"color\"\n              class=\"form-control form-control-color form-control-sm\"\n              name=\"themeColor\"\n            />\n          </li>\n        </ul>\n      </section>\n      <section>\n        <h6>&lt;cropper-crosshair&gt;</h6>\n        <ul>\n          <li>\n            <label for=\"selectionCrosshairHidden\">hidden</label>\n            <input\n              id=\"selectionCrosshairHidden\"\n              v-model=\"crosshair.hidden\"\n              type=\"checkbox\"\n              name=\"hidden\"\n            >\n          </li>\n          <li>\n            <label for=\"selectionCrosshairCentered\">centered</label>\n            <input\n              id=\"selectionCrosshairCentered\"\n              v-model=\"crosshair.centered\"\n              type=\"checkbox\"\n              name=\"centered\"\n            >\n          </li>\n          <li>\n            <label for=\"selectionCrosshairThemeColor\">theme-color</label>\n            <color-input\n              id=\"selectionCrosshairThemeColor\"\n              v-model=\"crosshair.themeColor\"\n              type=\"color\"\n              class=\"form-control form-control-color form-control-sm\"\n              name=\"themeColor\"\n            />\n          </li>\n        </ul>\n      </section>\n      <section\n        v-for=\"(subhandle, index) in handles\"\n        :key=\"subhandle.action\"\n      >\n        <h6>&lt;cropper-handle action=\"{{ subhandle.action }}\"&gt;</h6>\n        <ul>\n          <li>\n            <label :for=\"`selectionHandleHidden${index}`\">hidden</label>\n            <input\n              :id=\"`selectionHandleHidden${index}`\"\n              v-model=\"subhandle.hidden\"\n              type=\"checkbox\"\n              name=\"hidden\"\n            >\n          </li>\n          <li>\n            <label :for=\"`selectionHandleAction${index}`\">action</label>\n            <select\n              :id=\"`selectionHandleAction${index}`\"\n              v-model=\"subhandle.action\"\n              class=\"form-control form-control-sm\"\n              name=\"action\"\n              :disabled=\"index > 0\"\n            >\n              <option value=\"select\">\n                select\n              </option>\n              <option value=\"move\">\n                move\n              </option>\n              <option\n                value=\"scale\"\n                disabled\n              >\n                scale\n              </option>\n              <option value=\"none\">\n                none\n              </option>\n              <option\n                value=\"n-resize\"\n                disabled\n              >\n                n-resize\n              </option>\n              <option\n                value=\"e-resize\"\n                disabled\n              >\n                e-resize\n              </option>\n              <option\n                value=\"s-resize\"\n                disabled\n              >\n                s-resize\n              </option>\n              <option\n                value=\"w-resize\"\n                disabled\n              >\n                w-resize\n              </option>\n              <option\n                value=\"ne-resize\"\n                disabled\n              >\n                ne-resize\n              </option>\n              <option\n                value=\"nw-resize\"\n                disabled\n              >\n                nw-resize\n              </option>\n              <option\n                value=\"se-resize\"\n                disabled\n              >\n                se-resize\n              </option>\n              <option\n                value=\"sw-resize\"\n                disabled\n              >\n                sw-resize\n              </option>\n            </select>\n          </li>\n          <li>\n            <label :for=\"`selectionHandleThemeColor${index}`\">theme-color</label>\n            <color-input\n              :id=\"`selectionHandleThemeColor${index}`\"\n              v-model=\"subhandle.themeColor\"\n              type=\"color\"\n              class=\"form-control form-control-color form-control-sm\"\n              name=\"themeColor\"\n            />\n          </li>\n        </ul>\n      </section>\n    </aside>\n    <article>\n      <cropper-canvas\n        v-if=\"ready\"\n        ref=\"cropperCanvas\"\n        :background=\"canvas.background\"\n        :disabled=\"canvas.disabled\"\n        :hidden=\"canvas.hidden\"\n        :scale-step=\"canvas.scaleStep\"\n        :theme-color=\"canvas.themeColor\"\n      >\n        <cropper-image\n          ref=\"cropperImage\"\n          :hidden=\"image.hidden\"\n          :rotatable=\"image.rotatable\"\n          :scalable=\"image.scalable\"\n          :skewable=\"image.skewable\"\n          :translatable=\"image.translatable\"\n          :initial-center-size=\"image.initialCenterSize\"\n          :src=\"image.src\"\n          :alt=\"image.alt\"\n          @transform=\"onImageTransform\"\n        />\n        <cropper-shade\n          :hidden=\"shade.hidden\"\n          :theme-color=\"shade.themeColor\"\n        />\n        <cropper-handle\n          :action=\"handle.action\"\n          :hidden=\"handle.hidden\"\n          :plain=\"handle.plain\"\n          :theme-color=\"handle.themeColor\"\n        />\n        <cropper-selection\n          id=\"cropperSelection\"\n          ref=\"cropperSelection\"\n          :x=\"selection.x\"\n          :y=\"selection.y\"\n          :width=\"selection.width\"\n          :height=\"selection.height\"\n          :aspect-ratio=\"selection.aspectRatio\"\n          :initial-coverage=\"selection.initialCoverage\"\n          :hidden=\"selection.hidden\"\n          :initial-aspect-ratio=\"selection.initialAspectRatio\"\n          :movable=\"selection.movable\"\n          :resizable=\"selection.resizable\"\n          :zoomable=\"selection.zoomable\"\n          :multiple=\"selection.multiple\"\n          :keyboard=\"selection.keyboard\"\n          :outlined=\"selection.outlined\"\n          :precise=\"selection.precise\"\n          :dynamic=\"selection.dynamic\"\n          @change=\"onSelectionChange\"\n        >\n          <cropper-grid\n            :hidden=\"grid.hidden\"\n            :rows=\"grid.rows\"\n            :columns=\"grid.columns\"\n            :bordered=\"grid.bordered\"\n            :covered=\"grid.covered\"\n            :theme-color=\"grid.themeColor\"\n          />\n          <cropper-crosshair\n            :hidden=\"crosshair.hidden\"\n            :centered=\"crosshair.centered\"\n            :theme-color=\"crosshair.themeColor\"\n          />\n          <cropper-handle\n            v-for=\"subhandle in handles\"\n            :key=\"subhandle.action\"\n            :action=\"subhandle.action\"\n            :hidden=\"subhandle.hidden\"\n            :theme-color=\"subhandle.themeColor\"\n          />\n        </cropper-selection>\n      </cropper-canvas>\n    </article>\n    <aside>\n      <section class=\"carbonads\">\n        <VPDocAsideCarbonAds\n          :carbon-ads=\"{\n            code: 'CKYI55Q7',\n            placement: 'fengyuanchengithubio',\n          }\"\n        />\n      </section>\n      <section class=\"previews clearfix\">\n        <h6>Preview</h6>\n        <cropper-viewer\n          v-if=\"ready\"\n          class=\"preview preview-lg\"\n          selection=\"#cropperSelection\"\n        />\n        <cropper-viewer\n          v-if=\"ready\"\n          class=\"preview preview-md\"\n          selection=\"#cropperSelection\"\n        />\n        <cropper-viewer\n          v-if=\"ready\"\n          class=\"preview preview-sm\"\n          selection=\"#cropperSelection\"\n        />\n        <cropper-viewer\n          v-if=\"ready\"\n          class=\"preview preview-xs\"\n          selection=\"#cropperSelection\"\n        />\n      </section>\n      <section>\n        <h6>Image Data</h6>\n        <ul>\n          <li>\n            <span>scaleX</span>\n            <span>{{ imageData.matrix[0] }}</span>\n          </li>\n          <li>\n            <span>skewY</span>\n            <span>{{ imageData.matrix[1] }}</span>\n          </li>\n          <li>\n            <span>skewX</span>\n            <span>{{ imageData.matrix[2] }}</span>\n          </li>\n          <li>\n            <span>scaleY</span>\n            <span>{{ imageData.matrix[3] }}</span>\n          </li>\n          <li>\n            <span>translateX</span>\n            <span>{{ imageData.matrix[4] }}</span>\n          </li>\n          <li>\n            <span>translateY</span>\n            <span>{{ imageData.matrix[5] }}</span>\n          </li>\n        </ul>\n      </section>\n      <section>\n        <h6>Selection Data</h6>\n        <ul>\n          <li>\n            <span>x</span>\n            <span>{{ selectionData.x }}</span>\n          </li>\n          <li>\n            <span>y</span>\n            <span>{{ selectionData.y }}</span>\n          </li>\n          <li>\n            <span>width</span>\n            <span>{{ selectionData.width }}</span>\n          </li>\n          <li>\n            <span>height</span>\n            <span>{{ selectionData.height }}</span>\n          </li>\n        </ul>\n      </section>\n    </aside>\n\n    <div\n      id=\"canvasViewerModal\"\n      class=\"modal fade\"\n      tabindex=\"-1\"\n      aria-labelledby=\"canvasViewerModalLabel\"\n      aria-hidden=\"true\"\n    >\n      <div class=\"modal-dialog\">\n        <div class=\"modal-content\">\n          <div class=\"modal-header\">\n            <h5\n              id=\"canvasViewerModalLabel\"\n              class=\"modal-title\"\n            >\n              Canvas\n            </h5>\n            <button\n              type=\"button\"\n              class=\"btn-close\"\n              data-bs-dismiss=\"modal\"\n              aria-label=\"Close\"\n            />\n          </div>\n          <div\n            ref=\"canvasViewer\"\n            class=\"modal-body text-center\"\n          />\n          <div class=\"modal-footer\">\n            <button\n              type=\"button\"\n              class=\"btn btn-secondary\"\n              data-bs-dismiss=\"modal\"\n            >\n              Close\n            </button>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport type CropperCanvas from '@cropper/element-canvas';\nimport type CropperSelection from '@cropper/element-selection';\nimport ColorInput from './ColorInput.vue';\n\nconst { BASE_URL } = import.meta.env;\n\nexport default {\n  name: 'CropperPlayground',\n  components: {\n    ColorInput,\n  },\n  data(): Record<string, any> {\n    return {\n      loading: true,\n      ready: false,\n      initialCanvas: '',\n      canvas: {\n        hidden: false,\n        background: true,\n        disabled: false,\n        scaleStep: 0.1,\n        themeColor: '#3399ff',\n      },\n      image: {\n        hidden: false,\n        initialCenterSize: 'contain',\n        rotatable: true,\n        scalable: true,\n        skewable: true,\n        translatable: true,\n        src: `${BASE_URL}picture.jpg`,\n        alt: 'The image to crop',\n      },\n      shade: {\n        themeColor: 'rgba(0, 0, 0, 0.65)',\n      },\n      handle: {\n        hidden: false,\n        action: 'select',\n        plain: true,\n        themeColor: 'rgba(51, 153, 255, 0.5)',\n      },\n      selection: {\n        hidden: false,\n        x: undefined,\n        y: undefined,\n        width: undefined,\n        height: undefined,\n        aspectRatio: undefined,\n        initialAspectRatio: undefined,\n        initialCoverage: 0.5,\n        dynamic: false,\n        movable: true,\n        resizable: true,\n        zoomable: false,\n        multiple: false,\n        keyboard: false,\n        outlined: false,\n        precise: false,\n      },\n      grid: {\n        hidden: false,\n        rows: 3,\n        columns: 3,\n        bordered: true,\n        covered: true,\n        themeColor: 'rgba(238, 238, 238, 0.5)',\n      },\n      crosshair: {\n        hidden: false,\n        centered: true,\n        themeColor: 'rgba(238, 238, 238, 0.5)',\n      },\n      handles: [\n        {\n          hidden: false,\n          action: 'move',\n          themeColor: 'rgba(255, 255, 255, 0.35)',\n        },\n        {\n          hidden: false,\n          action: 'n-resize',\n          themeColor: '#3399ff',\n        },\n        {\n          hidden: false,\n          action: 'e-resize',\n          themeColor: '#3399ff',\n        },\n        {\n          hidden: false,\n          action: 's-resize',\n          themeColor: '#3399ff',\n        },\n        {\n          hidden: false,\n          action: 'w-resize',\n          themeColor: '#3399ff',\n        },\n        {\n          hidden: false,\n          action: 'ne-resize',\n          themeColor: '#3399ff',\n        },\n        {\n          hidden: false,\n          action: 'nw-resize',\n          themeColor: '#3399ff',\n        },\n        {\n          hidden: false,\n          action: 'se-resize',\n          themeColor: '#3399ff',\n        },\n        {\n          hidden: false,\n          action: 'sw-resize',\n          themeColor: '#3399ff',\n        },\n      ],\n      imageData: {\n        matrix: [1, 0, 0, 1, 0, 0],\n      },\n      selectionData: {\n        x: 0,\n        y: 0,\n        width: 0,\n        height: 0,\n      },\n      output: {\n        canvas: {},\n        image: {\n          matrix: '[1, 0, 0, 1, 0, 0]',\n        },\n      },\n    };\n  },\n  watch: {\n    'selection.initialCoverage': {\n      handler() {\n        Object.assign(this.selection, {\n          x: undefined,\n          y: undefined,\n          width: undefined,\n          height: undefined,\n        });\n      },\n    },\n  },\n  created() {\n    this.initialData = JSON.stringify(this.$data);\n  },\n  mounted() {\n    Promise.all([\n      new Promise((resolve, reject) => {\n        const link = document.createElement('link');\n\n        link.href = 'https://unpkg.com/bootstrap@5/dist/css/bootstrap.min.css';\n        link.rel = 'stylesheet';\n        link.crossOrigin = 'anonymous';\n        link.onload = resolve;\n        link.onerror = reject;\n        link.onabort = reject;\n        document.head.appendChild(link);\n      }),\n      new Promise((resolve, reject) => {\n        const link = document.createElement('link');\n\n        link.href = 'https://unpkg.com/bootstrap-icons@1/font/bootstrap-icons.css';\n        link.rel = 'stylesheet';\n        link.crossOrigin = 'anonymous';\n        link.onload = resolve;\n        link.onerror = reject;\n        link.onabort = reject;\n        document.head.appendChild(link);\n      }),\n      new Promise((resolve, reject) => {\n        const script = document.createElement('script');\n\n        script.src = 'https://unpkg.com/bootstrap@5/dist/js/bootstrap.bundle.min.js';\n        script.crossOrigin = 'anonymous';\n        script.onload = resolve;\n        script.onerror = reject;\n        script.onabort = reject;\n        document.head.appendChild(script);\n      }),\n    ]).then(() => {\n      this.loading = false;\n      this.$nextTick(() => {\n        this.ready = true;\n        Array.from(this.$el.querySelectorAll('[data-toggle=\"tooltip\"]')).forEach((element) => {\n          new (window as any).bootstrap.Tooltip(element);\n        });\n      });\n    });\n  },\n  beforeUnmount() {\n    Array.from(document.head.querySelectorAll('[href*=\"bootstrap\"],[src*=\"bootstrap\"]')).forEach((element) => {\n      document.head.removeChild(element);\n    });\n  },\n  methods: {\n    resetAll() {\n      this.ready = false;\n      this.$nextTick(() => {\n        const initialData = JSON.parse(this.initialData);\n\n        Object.keys(initialData).forEach((key) => {\n          const value = initialData[key];\n\n          if (typeof value === 'object') {\n            this.$data[key] = value;\n          }\n        });\n        this.ready = true;\n      });\n    },\n    onImageTransform(event: CustomEvent) {\n      this.imageData = event.detail;\n    },\n    onSelectionChange(event: CustomEvent) {\n      this.selectionData = event.detail;\n    },\n    cropperCanvasToCanvas() {\n      const cropperCanvas = this.$refs.cropperCanvas as CropperCanvas;\n      const canvasViewer = this.$refs.canvasViewer as HTMLElement;\n\n      canvasViewer.innerHTML = '';\n      cropperCanvas.$toCanvas().then((canvas: HTMLCanvasElement) => {\n        canvasViewer.appendChild(canvas);\n      });\n    },\n    cropperSelectionToCanvas() {\n      const cropperSelection = this.$refs.cropperSelection as CropperSelection;\n      const canvasViewer = this.$refs.canvasViewer as HTMLElement;\n\n      canvasViewer.innerHTML = '';\n      cropperSelection.$toCanvas().then((canvas: HTMLCanvasElement) => {\n        canvasViewer.appendChild(canvas);\n      });\n    },\n  },\n};\n</script>\n\n<style lang=\"scss\">\n.playground {\n  background-color: var(--vp-c-bg-soft-up);\n  color: var(--vp-c-text-1);\n\n  canvas {\n    display: block;\n    width: 100%;\n  }\n\n  textarea {\n    width: 100%;\n  }\n\n  > aside {\n    background-color: var(--vp-c-bg);\n    overflow-x: hidden;\n    overflow-y: auto;\n    padding: 1rem;\n\n    > section {\n      margin-bottom: 1rem;\n\n      > h6 {\n        font-size: 0.875rem;\n      }\n\n      > ul {\n        padding-left: 1rem;\n\n        > li {\n          align-items: center;\n          display: flex;\n          font-size: 0.875rem;\n          justify-content: space-between;\n          padding-bottom: 0.25rem;\n          padding-top: 0.25rem;\n\n          > label {\n            margin-right: 0.5rem;\n            white-space: nowrap;\n          }\n\n          > input,\n          > select {\n            max-width: 5rem;\n          }\n\n          > input[type=\"color\"] {\n            padding-left: .125rem;\n            padding-right: .125rem;\n          }\n        }\n      }\n    }\n  }\n\n  > article {\n    height: 20rem;\n\n    > cropper-canvas {\n      height: 100%;\n    }\n  }\n\n  .btn-groups {\n    display: inline-flex;\n    flex-direction: column;\n\n    > .btn-group {\n      &:first-child {\n        > .btn {\n          &:first-child {\n            border-bottom-left-radius: 0;\n          }\n\n          &:last-child {\n            border-bottom-right-radius: 0;\n          }\n        }\n      }\n\n      &:nth-child(2) {\n        margin-bottom: -1px;\n        margin-top: -1px;\n\n        > .btn {\n          &:first-child {\n            border-bottom-left-radius: 0;\n            border-top-left-radius: 0;\n          }\n\n          &:last-child {\n            border-bottom-right-radius: 0;\n            border-top-right-radius: 0;\n          }\n        }\n      }\n\n      &:last-child {\n        > .btn {\n          &:first-child {\n            border-top-left-radius: 0;\n          }\n\n          &:last-child {\n            border-top-right-radius: 0;\n          }\n        }\n      }\n    }\n  }\n\n  .previews {\n    margin-bottom: 0;\n    margin-right: -1rem;\n  }\n\n  .preview {\n    border: 1px solid var(--vp-c-divider);\n    float: left;\n    margin-bottom: 1rem;\n    margin-right: 1rem;\n  }\n\n  .preview-lg {\n    height: 9rem;\n    width: 16rem;\n  }\n\n  .preview-md {\n    height: 4.5rem;\n    width: 8rem;\n  }\n\n  .preview-sm {\n    height: 2.25rem;\n    width: 4rem;\n  }\n\n  .preview-xs {\n    height: 1.125rem;\n    width: 2rem;\n  }\n}\n\n@media (min-width: 992px) {\n  .playground {\n    display: flex;\n    height: 100vh;\n\n    > aside {\n      width: 18rem;\n    }\n\n    > article {\n      flex: 1;\n      height: 100%;\n    }\n  }\n}\n\n@keyframes spinner {\n  0% {\n    transform: rotate(0deg);\n  }\n\n  100% {\n    transform: rotate(360deg);\n  }\n}\n\n.loading {\n  background-color: var(--vp-c-bg);\n  display: flex;\n  height: 100vh;\n  position: relative;\n\n  &::after {\n    animation: spinner 1s linear infinite;\n    border: 4px solid rgba(51, 153, 255, 0.5);\n    border-left-color: rgba(255, 255, 255, 0.1);\n    border-radius: 50%;\n    box-sizing: border-box;\n    content: '';\n    display: inline-block;\n    height: 40px;\n    left: 50%;\n    margin-left: -20px;\n    margin-top: -20px;\n    position: absolute;\n    top: 50%;\n    width: 40px;\n    z-index: 1;\n  }\n}\n</style>\n"
  },
  {
    "path": "docs/.vitepress/components/CropperPlaygroundContainer.vue",
    "content": "<template>\n  <iframe\n    class=\"cropper-playground-container\"\n    :src=\"src\"\n  />\n</template>\n\n<script lang=\"ts\">\nexport default {\n  name: 'CropperPlaygroundContainer',\n  props: {\n    src: {\n      type: String,\n      default: undefined,\n    },\n  },\n  mounted() {\n    const footer = document.body.querySelector('.VPFooter') as any;\n\n    if (footer) {\n      footer.style.display = 'none';\n    }\n  },\n  beforeUnmount() {\n    const footer = document.body.querySelector('.VPFooter') as any;\n\n    if (footer) {\n      footer.style.display = '';\n    }\n  },\n};\n</script>\n\n<style lang=\"scss\">\n.cropper-playground-container {\n  border-color: var(--vp-c-divider);\n  border-style: solid;\n  border-width: 1px 0 0;\n  height: calc(100vh - var(--vp-nav-height));\n  width: 100%;\n}\n</style>\n"
  },
  {
    "path": "docs/.vitepress/components/CropperSelectionExample.vue",
    "content": "<template>\n  <div class=\"cropper-container\">\n    <form>\n      <fieldset>\n        <legend>Within:</legend>\n        <input\n          id=\"inputWithinCanvas\"\n          v-model=\"within\"\n          type=\"radio\"\n          name=\"within\"\n          value=\"canvas\"\n        >\n        <label for=\"inputWithinCanvas\">canvas</label>\n        <input\n          id=\"inputWithinImage\"\n          v-model=\"within\"\n          type=\"radio\"\n          name=\"within\"\n          value=\"image\"\n        >\n        <label for=\"inputWithinImage\">image</label>\n        <input\n          id=\"inputWithinNone\"\n          v-model=\"within\"\n          type=\"radio\"\n          name=\"within\"\n          value=\"none\"\n        >\n        <label for=\"inputWithinNone\">none</label>\n      </fieldset>\n    </form>\n    <cropper-canvas\n      ref=\"cropperCanvas\"\n      :key=\"within\"\n      background\n    >\n      <cropper-image\n        ref=\"cropperImage\"\n        :src=\"src\"\n        alt=\"Picture\"\n        rotatable\n        scalable\n        skewable\n        translatable\n        @transform=\"onCropperImageTransform\"\n      />\n      <cropper-handle\n        action=\"move\"\n        plain\n      />\n      <cropper-selection\n        ref=\"cropperSelection\"\n        initial-coverage=\"0.5\"\n        movable\n        resizable\n        outlined\n        @change=\"onCropperSelectionChange\"\n      >\n        <cropper-grid\n          role=\"grid\"\n          covered\n        />\n        <cropper-crosshair centered />\n        <cropper-handle\n          action=\"move\"\n          theme-color=\"rgba(255, 255, 255, 0.35)\"\n        />\n        <cropper-handle action=\"n-resize\" />\n        <cropper-handle action=\"e-resize\" />\n        <cropper-handle action=\"s-resize\" />\n        <cropper-handle action=\"w-resize\" />\n        <cropper-handle action=\"ne-resize\" />\n        <cropper-handle action=\"nw-resize\" />\n        <cropper-handle action=\"se-resize\" />\n        <cropper-handle action=\"sw-resize\" />\n      </cropper-selection>\n    </cropper-canvas>\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport type CropperCanvas from '@cropper/element-canvas';\nimport type CropperImage from '@cropper/element-image';\nimport type CropperSelection from '@cropper/element-selection';\nimport type { Selection } from '@cropper/element-selection';\n\nconst { BASE_URL } = import.meta.env;\n\nexport default {\n  name: 'CropperSelectionExample',\n  data() {\n    return {\n      src: `${BASE_URL}picture.jpg`,\n      within: 'canvas',\n    };\n  },\n  methods: {\n    inSelection(selection: Selection, maxSelection: Selection) {\n      return (\n        selection.x >= maxSelection.x\n        && selection.y >= maxSelection.y\n        && (selection.x + selection.width) <= (maxSelection.x + maxSelection.width)\n        && (selection.y + selection.height) <= (maxSelection.y + maxSelection.height)\n      );\n    },\n    onCropperImageTransform(event: CustomEvent) {\n      const cropperCanvas = this.$refs.cropperCanvas as CropperCanvas;\n\n      if (!cropperCanvas || this.within !== 'image') {\n        return;\n      }\n\n      const cropperImage = this.$refs.cropperImage as CropperImage;\n      const cropperSelection = this.$refs.cropperSelection as CropperSelection;\n      const cropperCanvasRect = cropperCanvas.getBoundingClientRect();\n\n      // 1. Clone the cropper image.\n      const cropperImageClone = cropperImage.cloneNode() as CropperImage;\n\n      // 2. Apply the new matrix to the cropper image clone.\n      cropperImageClone.style.transform = `matrix(${event.detail.matrix.join(', ')})`;\n\n      // 3. Make the cropper image clone invisible.\n      cropperImageClone.style.opacity = '0';\n\n      // 4. Append the cropper image clone to the cropper canvas.\n      cropperCanvas.appendChild(cropperImageClone);\n\n      // 5. Compute the boundaries of the cropper image clone.\n      const cropperImageRect = cropperImageClone.getBoundingClientRect();\n\n      // 6. Remove the cropper image clone.\n      cropperCanvas.removeChild(cropperImageClone);\n\n      const selection = cropperSelection as Selection;\n      const maxSelection: Selection = {\n        x: cropperImageRect.left - cropperCanvasRect.left,\n        y: cropperImageRect.top - cropperCanvasRect.top,\n        width: cropperImageRect.width,\n        height: cropperImageRect.height,\n      };\n\n      if (!this.inSelection(selection, maxSelection)) {\n        event.preventDefault();\n      }\n    },\n    onCropperSelectionChange(event: CustomEvent) {\n      const cropperCanvas = this.$refs.cropperCanvas as CropperCanvas;\n\n      if (!cropperCanvas || this.within === 'none') {\n        return;\n      }\n\n      const cropperCanvasRect = cropperCanvas.getBoundingClientRect();\n      const selection = event.detail as Selection;\n\n      switch (this.within) {\n        case 'canvas': {\n          const maxSelection: Selection = {\n            x: 0,\n            y: 0,\n            width: cropperCanvasRect.width,\n            height: cropperCanvasRect.height,\n          };\n\n          if (!this.inSelection(selection, maxSelection)) {\n            event.preventDefault();\n          }\n          break;\n        }\n\n        case 'image': {\n          const cropperImage = this.$refs.cropperImage as CropperImage;\n          const cropperImageRect = cropperImage.getBoundingClientRect();\n          const maxSelection: Selection = {\n            x: cropperImageRect.left - cropperCanvasRect.left,\n            y: cropperImageRect.top - cropperCanvasRect.top,\n            width: cropperImageRect.width,\n            height: cropperImageRect.height,\n          };\n\n          if (!this.inSelection(selection, maxSelection)) {\n            event.preventDefault();\n          }\n          break;\n        }\n\n        default:\n      }\n    },\n  },\n};\n</script>\n\n<style lang=\"scss\" scoped>\n.cropper-container {\n  border: 1px solid var(--vp-c-divider);\n  border-radius: 0.375rem;\n  margin-bottom: 1rem;\n  margin-top: 1rem;\n  padding: 1.25rem 1.5rem;\n\n  fieldset {\n    border: 1px solid var(--vp-c-divider);\n    border-radius: 0.375rem;\n    margin-bottom: 1rem;\n    padding: 0.25rem 0.75rem 0.75rem 0.75rem;\n\n    > input {\n      margin: 0 0.25rem 0 0;\n      transform: translateY(-0.5px);\n      vertical-align: middle;\n    }\n\n    > label {\n      margin-right: 0.5rem;\n    }\n  }\n\n  cropper-canvas {\n    height: 320px;\n  }\n}\n</style>\n"
  },
  {
    "path": "docs/.vitepress/components/CropperSelectionToNativeCanvas.vue",
    "content": "<template>\n  <div class=\"to-canvas-demo\">\n    <div>\n      <cropper-canvas background>\n        <cropper-image\n          :src=\"src\"\n          alt=\"Picture\"\n          rotatable\n          scalable\n          skewable\n          translatable\n        />\n        <cropper-shade hidden />\n        <cropper-handle\n          action=\"move\"\n          plain\n        />\n        <cropper-selection\n          ref=\"source\"\n          initial-coverage=\"0.5\"\n          movable\n        >\n          <cropper-handle\n            action=\"move\"\n            plain\n          />\n        </cropper-selection>\n      </cropper-canvas>\n    </div>\n    <button\n      type=\"button\"\n      @click=\"convertToCanvas\"\n    >\n      Convert to canvas &raquo;\n    </button>\n    <div ref=\"target\" />\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport type CropperCanvas from '@cropper/element-canvas';\n\nconst { BASE_URL } = import.meta.env;\n\nexport default {\n  name: 'CropperSelectionToNativeCanvas',\n  data() {\n    return {\n      src: `${BASE_URL}picture.jpg`,\n    };\n  },\n  methods: {\n    convertToCanvas() {\n      const source = this.$refs.source as CropperCanvas;\n      const target = this.$refs.target as HTMLElement;\n\n      target.innerHTML = '';\n      source.$toCanvas().then((canvas: HTMLCanvasElement) => {\n        target.appendChild(canvas);\n      });\n    },\n  },\n};\n</script>\n\n<style lang=\"scss\" scoped>\n.to-canvas-demo {\n  display: flex;\n\n  > :first-child,\n  > :last-child {\n    border: 1px solid var(--vp-c-divider);\n    flex: 1;\n    font-size: 0;\n  }\n\n  > :last-child {\n    align-items: center;\n    display: flex;\n    justify-content: center;\n\n    :deep(canvas) {\n      max-width: 100%;\n    }\n  }\n\n  > button {\n    align-self: center;\n    border: 1px solid var(--vp-c-brand);\n    border-radius: 0.25rem;\n    color: var(--vp-c-brand);\n    margin-left: 1rem;\n    margin-right: 1rem;\n    padding: 0.25rem 0.5rem;\n\n    &:focus,\n    &:hover {\n      background-color: var(--vp-c-brand);\n      color: var(--vp-c-bg);\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "docs/.vitepress/components/LiveDemo.vue",
    "content": "<template>\n  <div class=\"live-demo\">\n    <div class=\"live-demo__view\">\n      <div\n        ref=\"demo\"\n        class=\"live-demo__view-content\"\n      />\n    </div>\n    <div\n      ref=\"code\"\n      class=\"live-demo__code\"\n    >\n      <slot />\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\">\nexport default {\n  name: 'LiveDemo',\n  mounted() {\n    this.render();\n  },\n  updated() {\n    this.render();\n  },\n  methods: {\n    render() {\n      const demo = this.$refs.demo as HTMLElement;\n      const code = this.$refs.code as HTMLElement;\n\n      demo.innerHTML = code.querySelector('pre > code')?.textContent || '';\n    },\n  },\n};\n</script>\n\n<style lang=\"scss\" scoped>\n.live-demo {\n  margin-bottom: 1rem;\n  margin-top: 1rem;\n\n  &__view {\n    border: 1px solid var(--vp-c-divider);\n    border-top-left-radius: 0.5rem;\n    border-top-right-radius: 0.5rem;\n    padding: 1.25rem 1.5rem;\n\n    &-content {\n      overflow: hidden;\n      position: relative;\n    }\n  }\n\n  &__code {\n    > :first-child {\n      border-top-left-radius: 0;\n      border-top-right-radius: 0;\n      margin-bottom: 0;\n      margin-top: 0;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "docs/.vitepress/config.mts",
    "content": "import { readdirSync } from 'fs';\nimport { resolve } from 'path';\nimport markdownItContainer from 'markdown-it-container';\nimport { defineConfig } from 'vitepress';\nimport lerna from '../../lerna.json';\n\nconst packages = resolve(__dirname, '../../packages');\n\nexport default defineConfig({\n  base: '/cropperjs/',\n  head: [\n    [\n      'link',\n      {\n        rel: 'apple-touch-icon',\n        sizes: '180x180',\n        href: '/cropperjs/apple-touch-icon.png',\n      },\n    ],\n    [\n      'link',\n      {\n        rel: 'icon',\n        href: '/cropperjs/favicon.ico',\n      },\n    ],\n    [\n      'link',\n      {\n        rel: 'icon',\n        type: 'image/png',\n        size: '16x16',\n        href: '/cropperjs/favicon-16x16.png',\n      },\n    ],\n    [\n      'link',\n      {\n        rel: 'icon',\n        type: 'image/png',\n        size: '32x32',\n        href: '/cropperjs/favicon-32x32.png',\n      },\n    ],\n    [\n      'meta',\n      {\n        name: 'theme-color',\n        content: '#39f',\n      },\n    ],\n    [\n      'script',\n      {\n        src: 'https://fengyuanchen.github.io/shared/google-analytics.js',\n        crossOrigin: 'anonymous',\n      },\n    ],\n  ],\n\n  lastUpdated: true,\n  themeConfig: {\n    logo: '/logo.svg',\n    outline: {\n      level: [2, 3],\n    },\n    socialLinks: [\n      {\n        icon: 'github',\n        link: 'https://github.com/fengyuanchen/cropperjs',\n      },\n    ],\n    footer: {\n      message: 'Released under the MIT License.',\n      copyright: 'Copyright © 2015-present Chen Fengyuan',\n    },\n    search: {\n      provider: 'local',\n    },\n    carbonAds: {\n      code: 'CKYI55Q7',\n      placement: 'fengyuanchengithubio',\n    },\n  },\n  locales: {\n    root: {\n      label: 'English',\n      lang: 'en-US',\n      title: 'Cropper.js',\n      description: 'JavaScript image cropper.',\n      themeConfig: {\n        editLink: {\n          pattern: 'https://github.com/fengyuanchen/cropperjs/edit/main/docs/:path',\n          text: 'Edit this page on GitHub',\n        },\n        nav: [\n          {\n            text: 'Guide',\n            link: '/guide.html',\n          },\n          {\n            text: 'API',\n            link: '/api/',\n          },\n          {\n            text: 'Playground',\n            link: '/playground.html',\n          },\n          {\n            text: 'Migration',\n            link: '/migration.html',\n          },\n          {\n            text: lerna.version,\n            items: [\n              { text: 'Changelog', link: 'https://github.com/fengyuanchen/cropperjs/blob/main/CHANGELOG.md' },\n              { text: 'Contributing', link: 'https://github.com/fengyuanchen/cropperjs/blob/main/.github/CONTRIBUTING.md' },\n            ],\n          },\n          {\n            text: '1.x',\n            link: 'https://fengyuanchen.github.io/cropperjs/v1/',\n          },\n        ],\n        sidebar: {\n          '/api/': [\n            {\n              text: 'API',\n              items: [\n                {\n                  text: 'Cropper',\n                  link: '/api/',\n                },\n                {\n                  text: 'CropperElement',\n                  link: '/api/cropper-element.html',\n                },\n                {\n                  text: 'Elements',\n                  items: [\n                    {\n                      text: 'CropperCanvas',\n                      link: '/api/cropper-canvas.html',\n                    },\n                    {\n                      text: 'CropperImage',\n                      link: '/api/cropper-image.html',\n                    },\n                    {\n                      text: 'CropperShade',\n                      link: '/api/cropper-shade.html',\n                    },\n                    {\n                      text: 'CropperHandle',\n                      link: '/api/cropper-handle.html',\n                    },\n                    {\n                      text: 'CropperSelection',\n                      link: '/api/cropper-selection.html',\n                    },\n                    {\n                      text: 'CropperGrid',\n                      link: '/api/cropper-grid.html',\n                    },\n                    {\n                      text: 'CropperCrosshair',\n                      link: '/api/cropper-crosshair.html',\n                    },\n                    {\n                      text: 'CropperViewer',\n                      link: '/api/cropper-viewer.html',\n                    },\n                  ],\n                },\n              ],\n            },\n          ],\n        },\n      },\n    },\n    zh: {\n      label: '中文',\n      lang: 'zh-CN',\n      title: 'Cropper.js',\n      description: 'JavaScript 图片裁剪器。',\n      themeConfig: {\n        lastUpdatedText: '上次更新',\n        darkModeSwitchLabel: '外观',\n        sidebarMenuLabel: '菜单',\n        returnToTopLabel: '回到顶部',\n        langMenuLabel: '选择语言',\n        outline: {\n          level: [2, 3],\n          label: '在当前页面上',\n        },\n        editLink: {\n          pattern: 'https://github.com/fengyuanchen/cropperjs/edit/main/docs/:path',\n          text: '在 GitHub 上编辑此页',\n        },\n        docFooter: {\n          prev: '上一页',\n          next: '下一页',\n        },\n        nav: [\n          {\n            text: '指南',\n            link: '/zh/guide.html',\n          },\n          {\n            text: 'API',\n            link: '/zh/api/',\n          },\n          {\n            text: '演练场',\n            link: '/zh/playground.html',\n          },\n          {\n            text: '迁移',\n            link: '/zh/migration.html',\n          },\n          {\n            text: '1.x',\n            link: 'https://fengyuanchen.github.io/cropperjs/v1',\n          },\n        ],\n        sidebar: {\n          '/zh/api/': [\n            {\n              text: 'API',\n              items: [\n                {\n                  text: 'Cropper',\n                  link: '/zh/api/',\n                },\n                {\n                  text: 'CropperElement',\n                  link: '/zh/api/cropper-element.html',\n                },\n                {\n                  text: '元素',\n                  items: [\n                    {\n                      text: 'CropperCanvas',\n                      link: '/zh/api/cropper-canvas.html',\n                    },\n                    {\n                      text: 'CropperImage',\n                      link: '/zh/api/cropper-image.html',\n                    },\n                    {\n                      text: 'CropperShade',\n                      link: '/zh/api/cropper-shade.html',\n                    },\n                    {\n                      text: 'CropperHandle',\n                      link: '/zh/api/cropper-handle.html',\n                    },\n                    {\n                      text: 'CropperSelection',\n                      link: '/zh/api/cropper-selection.html',\n                    },\n                    {\n                      text: 'CropperGrid',\n                      link: '/zh/api/cropper-grid.html',\n                    },\n                    {\n                      text: 'CropperCrosshair',\n                      link: '/zh/api/cropper-crosshair.html',\n                    },\n                    {\n                      text: 'CropperViewer',\n                      link: '/zh/api/cropper-viewer.html',\n                    },\n                  ],\n                },\n              ],\n            },\n          ],\n        },\n      },\n    },\n  },\n  markdown: {\n    lineNumbers: false,\n    config: (md) => {\n      md.use(markdownItContainer, 'live-demo', {\n        render(tokens: any[], index: number) {\n          return tokens[index].nesting === 1 ? '<ClientOnly><LiveDemo>' : '</LiveDemo></ClientOnly>';\n        },\n      });\n    },\n  },\n  vite: {\n    resolve: {\n      alias: readdirSync(packages).reduce((alias: Record<string, string>, name) => {\n        alias[name === 'cropperjs' ? name : `@cropper/${name}`] = resolve(packages, name, 'src');\n        return alias;\n      }, {}),\n    },\n  },\n  vue: {\n    template: {\n      compilerOptions: {\n        isCustomElement: (tag: string) => tag.startsWith('cropper-'),\n      },\n    },\n  },\n});\n"
  },
  {
    "path": "docs/.vitepress/shims.d.ts",
    "content": "declare module 'markdown-it-container';\ndeclare module 'vue-color-input';\n"
  },
  {
    "path": "docs/.vitepress/theme/index.scss",
    "content": ":root {\n  --vp-c-brand-1: #39f;\n  --vp-c-brand-2: #47a3ff;\n  --vp-c-brand-3: #39f;\n}\n\n.playground-container {\n  .theme-default-content {\n    max-width: none;\n  }\n\n  .page-meta {\n    display: none;\n  }\n}\n\n.theme-default-content:not(.custom) td {\n  > a {\n    white-space: nowrap;\n\n    > img {\n      max-width: none;\n    }\n  }\n}\n"
  },
  {
    "path": "docs/.vitepress/theme/index.ts",
    "content": "import type { EnhanceAppContext } from 'vitepress';\nimport DefaultTheme from 'vitepress/theme';\nimport './index.scss';\n\nexport default {\n  extends: DefaultTheme,\n  async enhanceApp(ctx: EnhanceAppContext) {\n    if (import.meta.env.SSR) {\n      return;\n    }\n\n    await import('cropperjs');\n    await import('vitepress/dist/client/theme-default/components/VPDocAsideCarbonAds.vue').then((VPDocAsideCarbonAds) => {\n      ctx.app.component('VPDocAsideCarbonAds', VPDocAsideCarbonAds.default);\n    });\n    await Promise.all([\n      import('../components/ColorInput.vue'),\n      import('../components/CropperActionExample.vue'),\n      import('../components/CropperCanvasToNativeCanvas.vue'),\n      import('../components/CropperExample.vue'),\n      import('../components/CropperImageExample.vue'),\n      import('../components/CropperPlayground.vue'),\n      import('../components/CropperPlaygroundContainer.vue'),\n      import('../components/CropperSelectionExample.vue'),\n      import('../components/CropperSelectionToNativeCanvas.vue'),\n      import('../components/LiveDemo.vue'),\n    ]).then((components) => {\n      components.forEach((component: any) => {\n        ctx.app.component(component.default.name, component.default);\n      });\n    });\n  },\n};\n"
  },
  {
    "path": "docs/api/cropper-canvas.md",
    "content": "# CropperCanvas\n\nThe `CropperCanvas` interface provides properties and methods for manipulating the layout and presentation of `<cropper-canvas>` elements.\n\n## Examples\n\n### Basic\n\n:::live-demo\n\n```html\n<cropper-canvas></cropper-canvas>\n```\n\n:::\n\n:::tip\nThe default minimum width and minimum height of this element are `200px` and `100px`.\n:::\n\n### Background\n\n:::live-demo\n\n```html\n<cropper-canvas background></cropper-canvas>\n```\n\n:::\n\n### Interactive\n\n:::live-demo\n\n```html\n<cropper-canvas background>\n  <cropper-image src=\"/cropperjs/picture.jpg\" alt=\"Picture\" rotatable scalable skewable translatable></cropper-image>\n  <cropper-handle action=\"move\" plain></cropper-handle>\n</cropper-canvas>\n```\n\n:::\n\n### Disabled\n\nAll pointer events are disabled.\n\n:::live-demo\n\n```html\n<cropper-canvas background disabled>\n  <cropper-image src=\"/cropperjs/picture.jpg\" alt=\"Picture\" rotatable scalable skewable translatable></cropper-image>\n  <cropper-handle action=\"move\" plain></cropper-handle>\n</cropper-canvas>\n```\n\n:::\n\n## Properties\n\nInherits properties from its parent, [`CropperElement`](cropper-element.html), and implements the following properties:\n\n| Name | Type | Default | Options | Description |\n| --- | --- | --- | --- | --- |\n| background | `boolean` | `false` | - | Indicates whether this element has a grid background. |\n| disabled | `boolean` | `false` | - | Indicates whether this element is disabled. |\n| scaleStep | `number` | `0.1` | - | Indicates the stepping interval of the scaling factor when zooming in/out by wheeling, must a positive number. |\n| themeColor | `string` | `\"#39f\"` | - | Indicates the primary color of this element and its children. |\n\n## Methods\n\n### $setAction\n\n- **Syntax**: `$setAction(action)`\n- **Arguments**:\n  - `action`:\n    - Type: `string`\n    - The new action.\n- **Returns**:\n  - Type: `CropperCanvas`\n  - The element instance for chaining.\n\nChanges the current action to a new one.\n\n### $toCanvas\n\n- **Syntax**:\n  - `$toCanvas()`\n  - `$toCanvas(options)`\n- **Arguments**:\n  - `options`:\n    - Type: `Object`\n    - The available options.\n    - Properties:\n      - `width`:\n        - Type: `number`\n        - The width of the canvas.\n      - `height`:\n        - Type: `number`\n        - The height of the canvas.\n      - `beforeDraw`:\n        - Type: `Function`\n        - The function called before drawing the image onto the canvas.\n        - Syntax: `beforeDraw(context, canvas)`\n        - Arguments:\n          - `context`:\n            - Type: `CanvasRenderingContext2D`\n            - The 2D rendering context of the canvas.\n          - `canvas`:\n            - Type: `HTMLCanvasElement`\n            - The canvas element itself.\n        - Example: `function (context) { context.filter = 'grayscale(100%)'; }`\n- **Returns**:\n  - Type: `Promise`\n  - A promise that resolves to the generated canvas element.\n- **Example**:\n  <ClientOnly>\n    <CropperCanvasToNativeCanvas />\n  </ClientOnly>\n\nGenerates a real canvas element, with the image drawn into if there is one.\n\n## Events\n\n### action\n\nThe event is fired when a pointer changes on the canvas.\n\n- Event:\n  - **event.bubbles**: `true`\n  - **event.cancelable**: `false`\n  - **event.composed**: `true`\n  - **event.detail**:\n    - Type: `Object`\n    - The related data of the action.\n  - **event.detail.action**:\n    - Type: `string`\n    - Options: `\"select\"`, `\"move\"`, `\"scale\"`, `\"rotate\"`, `\"transform\"`, `\"n-resize\"`, `\"e-resize\"`, `\"s-resize\"`, `\"w-resize\"`, `\"ne-resize\"`, `\"nw-resize\"`, `\"se-resize\"`, and `\"sw-resize\"`.\n    - The action type.\n  - **event.detail.relatedEvent**:\n    - Type: `PointerEvent | TouchEvent | MouseEvent | WheelEvent`\n    - The related native event that triggered this event.\n  - **event.detail.scale**:\n    - Type: `number`\n    - The scaling factor, only available when the `action` is `\"scale\"` or `\"transform\"`.\n  - **event.detail.rotate**:\n    - Type: `number`\n    - The scaling factor, only available when the `action` is `\"rotate\"`or `\"transform\"`.\n  - **event.detail.startX**:\n    - Type: `number`\n    - The starting `pageX` value, only available when the `relatedEvent` is `PointerEvent`, `TouchEvent`, or `MouseEvent`.\n  - **event.detail.startY**:\n    - Type: `number`\n    - The starting `pageY` value, only available when the `relatedEvent` is `PointerEvent`, `TouchEvent`, or `MouseEvent`.\n  - **event.detail.endX**:\n    - Type: `number`\n    - The ending `pageX` value, only available when the `relatedEvent` is `PointerEvent`, `TouchEvent`, or `MouseEvent`.\n  - **event.detail.endY**:\n    - Type: `number`\n    - The ending `pageY` value, only available when the `relatedEvent` is `PointerEvent`, `TouchEvent`, or `MouseEvent`.\n- Example:\n\n```html\n<cropper-canvas id=\"canvas\"></cropper-canvas>\n\n<script>\ndocument.querySelector('#canvas').addEventListener('action', function (event) {\n  console.log(event);\n});\n</script>\n```\n\n### actionstart\n\nThe event is fired when a pointer becomes active.\n\n- Event:\n  - **event.bubbles**: `true`\n  - **event.cancelable**: `true`\n  - **event.composed**: `true`\n  - **event.detail**:\n    - Type: `Object`\n    - The related data of the action.\n  - **event.detail.action**:\n    - Type: `string`\n    - Options: same as the `action` event, except for the `\"scale\"` option.\n    - The action type.\n  - **event.detail.relatedEvent**:\n    - Type: `PointerEvent | TouchEvent | MouseEvent`\n    - The related native event that triggered this event.\n\n### actionmove\n\nThis event is fired when a pointer changes coordinates.\n\n- Event:\n  - **event.bubbles**: `true`\n  - **event.cancelable**: `true`\n  - **event.composed**: `true`\n  - **event.detail**:\n    - Type: `Object`\n    - The related data of the action.\n  - **event.detail.action**:\n    - Type: `string`\n    - Options: same as the `action` event, except for the `\"scale\"` option.\n    - The action type.\n  - **event.detail.relatedEvent**:\n    - Type: `PointerEvent | TouchEvent | MouseEvent`\n    - The related native event that triggered this event.\n\n### actionend\n\nThis event is fired when a pointer is no longer active.\n\n- Event:\n  - **event.bubbles**: `true`\n  - **event.cancelable**: `true`\n  - **event.composed**: `true`\n  - **event.detail**:\n    - Type: `Object`\n    - The related data of the action.\n  - **event.detail.action**:\n    - Type: `string`\n    - Options: same as the `action` event, except for the `\"scale\"` option.\n    - The action type.\n  - **event.detail.relatedEvent**:\n    - Type: `PointerEvent | TouchEvent | MouseEvent`\n    - The related native event that triggered this event.\n\n## Slots\n\nThere is only one default slot in this element.\n\n> You can disable it by setting the `slottable` property to `false`:\n>\n> ```html\n> <cropper-canvas slottable=\"false\"></cropper-canvas>\n> ```\n"
  },
  {
    "path": "docs/api/cropper-crosshair.md",
    "content": "# CropperCrosshair\n\nThe `CropperCrosshair` interface provides properties and methods for manipulating the layout and presentation of `<cropper-crosshair>` elements.\n\n## Examples\n\n### Basic\n\n:::live-demo\n\n```html\n<cropper-crosshair></cropper-crosshair>\n```\n\n:::\n\n### Custom color\n\n:::live-demo\n\n```html\n<cropper-crosshair theme-color=\"#39f\"></cropper-crosshair>\n```\n\n:::\n\n### Within CropperCanvas\n\n:::live-demo\n\n```html\n<cropper-canvas style=\"background-color: #39f;\">\n  <cropper-crosshair centered></cropper-crosshair>\n</cropper-canvas>\n```\n\n:::\n\n### Within CropperSelection\n\n:::live-demo\n\n```html\n<cropper-selection width=\"160\" height=\"90\" style=\"background-color: #39f;\">\n  <cropper-crosshair centered></cropper-crosshair>\n</cropper-selection>\n```\n\n:::\n\n## Properties\n\nInherits properties from its parent, [`CropperElement`](cropper-element.html), and implements the following properties:\n\n| Name | Type | Default | Options | Description |\n| --- | --- | --- | --- | --- |\n| centered | `boolean` | `false` | - | Indicates whether this element is centered. |\n| slottable | `boolean` | `false` | - | Indicates whether this element is slottable. |\n| themeColor | `string` | `\"rgba(238, 238, 238, 0.5)\"` | - | Indicates the color of the crosshair. |\n\n## Slots\n\nThere are no available slots in this element.\n\n> You can enable the default slot by setting the `slottable` property to `true`:\n>\n> ```html\n> <cropper-crosshair slottable></cropper-crosshair>\n> ```\n"
  },
  {
    "path": "docs/api/cropper-element.md",
    "content": "# CropperElement\n\nThe `CropperElement` interface represents any Cropper element, extends the [HTMLElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement) interface.\n\n## Specifications\n\n- The name of public properties should start with an alphabetic character.\n- The name of private properties should start with `$`.\n- The name of public/private custom methods should start with `$`.\n- The name of private custom listeners should start with `$on`.\n\n## Example\n\n```js\nimport { CropperElement } from 'cropperjs';\n// Or\n// import CropperElement from '@cropper/element';\n\nclass MyCropperElement extends CropperElement {\n  myStringProperty = '';\n  myNumberProperty = NaN;\n  myBooleanProperty = false;\n\n  static get observedAttributes() {\n    return super.observedAttributes.concat([\n      'my-boolean-property',\n      'my-number-property',\n      'my-string-property',\n    ]);\n  }\n\n  // ...\n}\n\nMyCropperElement.$define();\n```\n\n```html\n<my-cropper-element my-string-property=\"foo\" my-number-property=\"1\" my-boolean-property></my-cropper-element>\n```\n\n## Properties\n\nInherits properties from its parent, [`HTMLElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement), and implements the following properties:\n\n| Name | Type | Default | Options | Description |\n| --- | --- | --- | --- | --- |\n| shadowRootMode | `string` | `\"open\"` | `\"closed\" \\| \"open\"` | Indicates the encapsulation mode for the shadow DOM tree. |\n| slottable | `boolean` | `true` | - | Indicates whether this element contains a `<slot>` element. |\n| themeColor | `string` | - | - | Indicates the theme color of this element and its children. |\n\n## Methods\n\n### $getShadowRoot\n\n- **Syntax**: `$getShadowRoot()`\n- **Returns**:\n  - Type: `ShadowRoot`\n  - The shadow root.\n\nOutputs the shadow root of the element, even if its mode is `\"closed\"`.\n\n### $addStyles\n\n- **Syntax**: `$addStyles(styles)`\n- **Arguments**:\n  - `styles`:\n    - Type: `string`\n    - The styles to add.\n- **Returns**:\n  - Type: `CSSStyleSheet | HTMLStyleElement`\n  - The generated style sheet.\n- **Example**:\n\n  ```js\n  const canvas = new CropperCanvas();\n\n  canvas.$addStyles(`\n    :host {\n      border: 1px solid #39f;\n    }\n  `);\n  ```\n\nAdds styles to the shadow root.\n\n### $emit\n\n- **Syntax**:\n  - `$emit(type)`\n  - `$emit(type, detail)`\n  - `$emit(type, detail, options)`\n- **Arguments**:\n  - `type`:\n    - Type: `string`\n    - The name of the event.\n  - `detail`:\n    - Type: `*`\n    - Default: `undefined`\n    - The data passed when initializing the event.\n  - `options`:\n    - Type: `CustomEventInit`\n    - Default: `{ bubbles: true, cancelable: true, composed: true }`\n    - The other event options.\n- **Returns**:\n  - Type: `boolean`\n  - The result value.\n- **Example**:\n\n  ```js\n  const selection = new CropperSelection();\n\n  selection.$emit('change', {\n    x: 10,\n    y: 5,\n    width: 160,\n    height: 90,\n  });\n  ```\n\nDispatches an event at the current element.\n\n### $nextTick\n\n- **Syntax**:\n  - `$nextTick()`\n  - `$nextTick(callback)`\n- **Arguments**:\n  - `callback`:\n    - Type: `Function`\n    - The callback to execute after the next DOM update cycle.\n- **Returns**:\n  - Type: `Promise`\n  - A promise that resolves to nothing.\n\nDefers the callback to be executed after the next DOM update cycle.\n\n## Static Properties\n\n| Name | Type | Description |\n| --- | --- | --- |\n| $name | `string` | The name of the custom element. |\n| $version | `string` | The version of the package. |\n\n## Static methods\n\n### $define\n\nDefines the constructor as a new custom element. It is just a shortcut to call [`CustomElementRegistry.define()`](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define).\n\n- **Syntax**:\n  - `$define()`\n  - `$define(name)`\n  - `$define(options)`\n  - `$define(name, options)`\n- **Alternatives**:\n  - `customElements.define(name, constructor)`\n  - `customElements.define(name, constructor, options)`\n- **Arguments**:\n  - `name`:\n    - Type: `string`\n    - The element name. Defaults to the `$name` static property of the constructor.\n  - `options`:\n    - Type: `Object`\n    - The element definition options.\n- **Example**:\n\n  ```js\n  // Define as a autonomous custom element: `<my-cropper-element></my-cropper-element>`.\n  CropperElement.$define('my-cropper-element');\n  ```\n"
  },
  {
    "path": "docs/api/cropper-grid.md",
    "content": "# CropperGrid\n\nThe `CropperGrid` interface provides properties and methods for manipulating the layout and presentation of `<cropper-grid>` elements.\n\n## Examples\n\n### Basic\n\n:::live-demo\n\n```html\n<cropper-grid></cropper-grid>\n```\n\n:::\n\n:::tip\nThe default height of this element is `0`.\n:::\n\n### Custom rows and columns\n\n:::live-demo\n\n```html\n<cropper-grid rows=\"4\" columns=\"18\" theme-color=\"#39f\" style=\"height: 10rem;\"></cropper-grid>\n```\n\n:::\n\n### Within CropperCanvas\n\n:::live-demo\n\n```html\n<cropper-canvas style=\"background-color: #39f;\">\n  <cropper-grid bordered covered></cropper-grid>\n</cropper-canvas>\n```\n\n:::\n\n### Within CropperSelection\n\n:::live-demo\n\n```html\n<cropper-selection width=\"160\" height=\"90\" style=\"background-color: #39f;\">\n  <cropper-grid bordered covered></cropper-grid>\n</cropper-selection>\n```\n\n:::\n\n## Properties\n\nInherits properties from its parent, [`CropperElement`](cropper-element.html), and implements the following properties:\n\n| Name | Type | Default | Options | Description |\n| --- | --- | --- | --- | --- |\n| rows | `number` | `3` | - | Indicates the number of the rows. |\n| columns | `number` | `3` | - | Indicates the number of the columns. |\n| bordered | `boolean` | `false` | - | Indicates whether this element is bordered. |\n| covered | `boolean` | `false` | - | Indicates whether this element covers its parent element. |\n| slottable | `boolean` | `false` | - | Indicates whether this element is slottable. |\n| themeColor | `string` | `\"rgba(238, 238, 238, 0.5)\"` | - | Indicates the color of the element. |\n\n## Slots\n\nThere are no available slots in this element.\n\n> You can enable the default slot by setting the `slottable` property to `true`:\n>\n> ```html\n> <cropper-grid slottable></cropper-grid>\n> ```\n"
  },
  {
    "path": "docs/api/cropper-handle.md",
    "content": "# CropperHandle\n\nThe `CropperHandle` interface provides properties and methods for manipulating the layout and presentation of `<cropper-handle>` elements.\n\n## Examples\n\n### Basic\n\n:::live-demo\n\n```html\n<cropper-handle></cropper-handle>\n```\n\n:::\n\n:::tip\nThe default width and height of this element is `0`.\n:::\n\n### Within CropperCanvas\n\n:::live-demo\n\n```html\n<cropper-canvas background>\n  <cropper-image src=\"/cropperjs/picture.jpg\" alt=\"Picture\" rotatable scalable skewable translatable></cropper-image>\n  <cropper-handle action=\"move\"></cropper-handle>\n</cropper-canvas>\n```\n\n:::\n\n### Within CropperSelection\n\n:::live-demo\n\n```html\n<cropper-canvas background>\n  <cropper-image src=\"/cropperjs/picture.jpg\" alt=\"Picture\" rotatable scalable skewable translatable></cropper-image>\n  <cropper-selection width=\"100\" height=\"100\" movable>\n    <cropper-handle action=\"move\"></cropper-handle>\n  </cropper-selection>\n</cropper-canvas>\n```\n\n:::\n\n### Toggle Action on Dblclick\n\n<ClientOnly>\n  <CropperActionExample />\n</ClientOnly>\n\n::: details\n<<< @/.vitepress/components/CropperActionExample.vue\n:::\n\n## Properties\n\nInherits properties from its parent, [`CropperElement`](cropper-element.html), and implements the following properties:\n\n| Name | Type | Default | Options | Description |\n| --- | --- | --- | --- | --- |\n| action | `string` | `\"none\"` | `\"select\"`, `\"move\"`, `\"scale\"`, `\"n-resize\"`, `\"e-resize\"`, `\"s-resize\"`, `\"w-resize\"`, `\"ne-resize\"`, `\"nw-resize\"`, `\"se-resize\"`, `\"sw-resize\"`, `\"none\"` | Indicates the action type of the handle. |\n| plain | `boolean` | `false` | - | Indicates whether this element is plain (without background). |\n| slottable | `boolean` | `false` | - | Indicates whether this element is slottable. |\n| themeColor | `string` | `\"rgba(51, 153, 255, 0.5)\"` | - | Indicates the color of the handle. |\n\n## Slots\n\nThere are no available slots in this element.\n\n> You can enable the default slot by setting the `slottable` property to `true`:\n>\n> ```html\n> <cropper-handle slottable></cropper-handle>\n> ```\n"
  },
  {
    "path": "docs/api/cropper-image.md",
    "content": "# CropperImage\n\nThe `CropperImage` interface provides properties and methods for manipulating the layout and presentation of `<cropper-image>` elements.\n\n## Examples\n\n### Basic\n\n:::live-demo\n\n```html\n<cropper-image></cropper-image>\n```\n\n:::\n\n:::tip\nThe default width and height of this element is `0`.\n:::\n\n### With image source\n\n:::live-demo\n\n```html\n<cropper-image src=\"/cropperjs/picture.jpg\" alt=\"Picture\" style=\"width: 100%;\" rotatable scalable skewable translatable></cropper-image>\n```\n\n:::\n\n### Limit boundaries\n\n<ClientOnly>\n  <CropperImageExample />\n</ClientOnly>\n\n::: details\n<<< @/.vitepress/components/CropperImageExample.vue\n:::\n\n## Properties\n\nInherits properties from its parent, [`CropperElement`](cropper-element.html), and implements the following properties:\n\n| Name | Type | Default | Options | Description |\n| --- | --- | --- | --- | --- |\n| initial-center-size | `string` | `\"contain\"` | `\"contain\"`, `\"cover\"` | Indicates the initial size of the image when aligned with the center of its parent element. |\n| rotatable | `boolean` | `false` | - | Indicates whether this element is rotatable. |\n| scalable | `boolean` | `false` | - | Indicates whether this element is scalable. |\n| skewable | `boolean` | `false` | - | Indicates whether this element is skewable. |\n| slottable | `boolean` | `false` | - | Indicates whether this element is slottable. |\n| translatable | `boolean` | `false` | - | Indicates whether this element is translatable. |\n\nThe built-in `<img>` element will inherit the following attributes by default:\n\n- `alt`\n- `crossorigin`\n- `decoding`\n- `elementtiming`\n- `fetchpriority`\n- `loading`\n- `referrerpolicy`\n- `sizes`\n- `src`\n- `srcset`\n\n## Methods\n\n### $ready\n\n- **Syntax**:\n  - `$ready()`\n  - `$ready(callback)`\n- **Arguments**:\n  - `callback`:\n    - Type: `Function`\n    - The callback to execute after successfully loading the image.\n- **Returns**:\n  - Type: `Promise`\n  - A promise that resolves to the image element.\n- **Example**:\n\n  ```js\n  const cropperImage = new CropperImage();\n\n  cropperImage.$ready((image) => {\n    console.log(image.naturalWidth, image.naturalHeight);\n  });\n  cropperImage.src = '/cropperjs/picture.jpg';\n  ```\n\nDefers the callback to execute after successfully loading the image.\n\n### $center\n\n- **Syntax**:\n  - `$center()`\n  - `$center(size)`\n- **Arguments**:\n  - `size`:\n    - Type: `string`\n    - Options: `\"contain\"`, and `\"cover\"`.\n    - The size of the image.\n- **Returns**:\n  - Type: `CropperImage`\n  - The element instance for chaining.\n\nAligns the image to the center of its parent element.\n\n### $move\n\n- **Syntax**:\n  - `$move(x)`\n  - `$move(x, y)`\n- **Arguments**:\n  - `x`:\n    - Type: `number`\n    - The moving distance in the horizontal direction.\n  - `y`:\n    - Type: `number`\n    - Default: `x`\n    - The moving distance in the vertical direction.\n- **Returns**:\n  - Type: `CropperImage`\n  - The element instance for chaining.\n\nMoves the image.\n\n### $moveTo\n\n- **Syntax**:\n  - `$moveTo(x)`\n  - `$moveTo(x, y)`\n- **Arguments**:\n  - `x`:\n    - Type: `number`\n    - The new position in the horizontal direction.\n  - `y`:\n    - Type: `number`\n    - Default: `x`\n    - The new position in the vertical direction.\n- **Returns**:\n  - Type: `CropperImage`\n  - The element instance for chaining.\n\nMoves the image to a specific position.\n\n### $rotate\n\n- **Syntax**: `$rotate(angle)`\n- **Arguments**:\n  - `angle`:\n    - Type: `number | string`\n    - The rotation angle (in radians). The default unit is `rad`.\n  - `x`:\n    - Type: `number`\n    - Default: The center of the image in the horizontal.\n    - The rotation origin in the horizontal.\n  - `y`:\n    - Type: `number`\n    - Default: The center of the image in the vertical.\n    - The rotation origin in the vertical.\n- **Returns**:\n  - Type: `CropperImage`\n  - The element instance for chaining.\n- **Examples**:\n  - `$rotate(0.8)`\n  - `$rotate('0.8rad')`\n  - `$rotate('45deg')`\n  - `$rotate('50grad')`\n  - `$rotate('0.1turn')`\n  - `$rotate('90deg', 0, 0)`\n\nRotates the image. It is similar to CSS function [rotate()](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/rotate()) or [CanvasRenderingContext2D.rotate()](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/rotate).\n\n### $zoom\n\n- **Syntax**:\n  - `$zoom(scale)`\n  - `$zoom(scale, x, y)`\n- **Arguments**:\n  - `scale`:\n    - Type: `number`\n    - The zoom factor. Positive numbers for zooming in, and negative numbers for zooming out.\n  - `x`:\n    - Type: `number`\n    - Default: The center of the image in the horizontal.\n    - The zoom origin in the horizontal.\n  - `y`:\n    - Type: `number`\n    - Default: The center of the image in the vertical.\n    - The zoom origin in the vertical.\n- **Returns**:\n  - Type: `CropperImage`\n  - The element instance for chaining.\n- **Examples**:\n\n  ```js\n  cropperImage.$zoom(0.1); // Zoom in 10%\n  cropperImage.$zoom(-0.1); // Zoom out 10%\n  cropperImage.$zoom(0.1, 0, 0); // Zoom in from the top-left corner\n  cropperImage.$zoom(-0.1, 0, 0); // Zoom out from the top-left corner\n  ```\n\nZooms the image.\n\n### $scale\n\n- **Syntax**:\n  - `$scale(x)`\n  - `$scale(x, y)`\n- **Arguments**:\n  - `x`:\n    - Type: `number`\n    - The scaling factor in the horizontal direction.\n  - `y`:\n    - Type: `number`\n    - Default: `x`\n    - The scaling factor in the vertical direction.\n- **Returns**:\n  - Type: `CropperImage`\n  - The element instance for chaining.\n- **Examples**:\n\n  ```js\n  cropperImage.$scale(1.1); // Zoom in 10%\n  cropperImage.$scale(0.9); // Zoom out 10%\n  cropperImage.$scale(-1); // Flip both the horizontal and vertical directions\n  cropperImage.$scale(-1, 1); // Flip the horizontal direction\n  cropperImage.$scale(1, -1); // Flip the vertical direction\n  ```\n\nScales the image. It is similar to CSS function [scale()](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/scale()) or [CanvasRenderingContext2D.scale()](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/scale).\n\n### $skew\n\n- **Syntax**:\n  - `$skew(x)`\n  - `$skew(x, y)`\n- **Arguments**:\n  - `x`:\n    - Type: `number | string`\n    - The skewing angle in the horizontal direction. The default unit is `rad`.\n  - `y`:\n    - Type: `number | string`\n    - Default: `x`\n    - The skewing angle in the vertical direction. The default unit is `rad`.\n- **Returns**:\n  - Type: `CropperImage`\n  - The element instance for chaining.\n- **Examples**:\n  - `$skew(0.8)`\n  - `$skew('0.8rad')`\n  - `$skew('45deg')`\n  - `$skew('50grad')`\n  - `$skew('0.1turn')`\n  - `$skew(0, 0.8)`\n\nSkews the image. It is similar to CSS function [skew()](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/skew()).\n\n### $translate\n\n- **Syntax**:\n  - `$translate(x)`\n  - `$translate(x, y)`\n- **Arguments**:\n  - `x`:\n    - Type: `number`\n    - The translating distance in the horizontal direction.\n  - `y`:\n    - Type: `number`\n    - Default: `x`\n    - The translating distance in the vertical direction.\n- **Returns**:\n  - Type: `CropperImage`\n  - The element instance for chaining.\n\nTranslates the image. It is similar to CSS function [translate()](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/translate()) or [CanvasRenderingContext2D.translate()](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/translate).\n\n### $transform\n\n- **Syntax**: `$transform(a, b, c, d, e, f)`\n- **Arguments**:\n  - `a`:\n    - Type: `number`\n    - The scaling factor in the horizontal direction.\n  - `b`:\n    - Type: `number`\n    - The skewing angle in the vertical direction.\n  - `c`:\n    - Type: `number`\n    - The skewing angle in the horizontal direction.\n  - `d`:\n    - Type: `number`\n    - The scaling factor in the vertical direction.\n  - `e`:\n    - Type: `number`\n    - The translating distance in the horizontal direction.\n  - `f`:\n    - Type: `number`\n    - The translating distance in the vertical direction.\n- **Returns**:\n  - Type: `CropperImage`\n  - The element instance for chaining.\n\nTransforms the image. It is similar to CSS function [matrix()](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/matrix()) or [CanvasRenderingContext2D.transform()](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/transform).\n\n### $setTransform\n\n- **Syntax**:\n  - `$setTransform(a, b, c, d, e, f)`\n  - `$setTransform(a)`\n- **Arguments**:\n  - `a`:\n    - Type: `number | Array`\n    - The scaling factor in the horizontal direction, or the transformation matrix.\n  - `b`:\n    - Type: `number`\n    - The skewing angle in the vertical direction.\n  - `c`:\n    - Type: `number`\n    - The skewing angle in the horizontal direction.\n  - `d`:\n    - Type: `number`\n    - The scaling factor in the vertical direction.\n  - `e`:\n    - Type: `number`\n    - The translating distance in the horizontal direction.\n  - `f`:\n    - Type: `number`\n    - The translating distance in the vertical direction.\n- **Returns**:\n  - Type: `CropperImage`\n  - The element instance for chaining.\n\nResets (overrides) the current transform to the specific identity matrix, and then invokes a transform described by the arguments of this method. This lets you scale, rotate, translate (move), and skew the context. It is similar to [CanvasRenderingContext2D.setTransform()](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setTransform).\n\n### $getTransform\n\n- **Syntax**: `$getTransform()`\n- **Returns**:\n  - Type: `Array`\n  - The current transformation matrix of the element.\n\nRetrieves the current transformation matrix being applied to the element. It is similar to [CanvasRenderingContext2D.getTransform()](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/getTransform).\n\n### $resetTransform\n\n- **Syntax**:\n  - `$resetTransform()`\n- **Alternatives**:\n  - `$setTransform(1, 0, 0, 1, 0, 0)`\n  - `$setTransform([1, 0, 0, 1, 0, 0])`\n- **Returns**:\n  - Type: `CropperImage`\n  - The element instance for chaining.\n\nResets the current transform to the initial identity matrix. It is similar to [CanvasRenderingContext2D.resetTransform()](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/resetTransform).\n\n## Events\n\n### transform\n\n- Event:\n  - **event.bubbles**: `true`\n  - **event.cancelable**: `true`\n  - **event.composed**: `true`\n  - **event.detail**:\n    - Type: `Object`\n    - The transform information of the image.\n  - **event.detail.matrix**:\n    - Type: `Array`\n    - The new (next) matrix object.\n  - **event.detail.oldMatrix**:\n    - Type: `Array`\n    - The old (current) matrix object.\n\nThe event is fired when the [`transform`](https://developer.mozilla.org/en-US/docs/Web/CSS/transform) CSS property of the element is going to change.\n\n## Slots\n\nThere are no available slots in this element.\n\n> You can enable the default slot by setting the `slottable` property to `true`:\n>\n> ```html\n> <cropper-image slottable></cropper-image>\n> ```\n"
  },
  {
    "path": "docs/api/cropper-selection.md",
    "content": "# CropperSelection\n\nThe `CropperSelection` interface provides properties and methods for manipulating the layout and presentation of `<cropper-selection>` elements.\n\n## Examples\n\n### Basic\n\n:::live-demo\n\n```html\n<cropper-selection></cropper-selection>\n```\n\n:::\n\n:::tip\nThe default width and height of this element is `0`.\n:::\n\n### Customize initial selection coverage\n\n:::live-demo\n\n```html\n<cropper-canvas background>\n  <cropper-selection initial-coverage=\"0.5\" outlined></cropper-selection>\n</cropper-canvas>\n```\n\n:::\n\n### Customize position and size\n\n:::live-demo\n\n```html\n<cropper-canvas background>\n  <cropper-selection x=\"10\" y=\"5\" width=\"160\" height=\"90\" outlined></cropper-selection>\n</cropper-canvas>\n```\n\n:::\n\n### With handles\n\n:::live-demo\n\n```html\n<cropper-canvas background>\n  <cropper-selection initial-coverage=\"0.5\" movable resizable zoomable outlined>\n    <cropper-handle action=\"move\" theme-color=\"rgba(255, 255, 255, 0.35)\"></cropper-handle>\n    <cropper-handle action=\"n-resize\"></cropper-handle>\n    <cropper-handle action=\"e-resize\"></cropper-handle>\n    <cropper-handle action=\"s-resize\"></cropper-handle>\n    <cropper-handle action=\"w-resize\"></cropper-handle>\n    <cropper-handle action=\"ne-resize\"></cropper-handle>\n    <cropper-handle action=\"nw-resize\"></cropper-handle>\n    <cropper-handle action=\"se-resize\"></cropper-handle>\n    <cropper-handle action=\"sw-resize\"></cropper-handle>\n  </cropper-selection>\n</cropper-canvas>\n```\n\n:::\n\n### Dynamic\n\nSet the `dynamic` property to `true` to change as the image changes.\n\n:::live-demo\n\n```html\n<cropper-canvas style=\"height: 360px;\" background>\n  <cropper-image src=\"/cropperjs/picture.jpg\" alt=\"Picture\" rotatable scalable skewable translatable></cropper-image>\n  <cropper-shade hidden></cropper-shade>\n  <cropper-handle action=\"move\" plain></cropper-handle>\n  <cropper-selection initial-coverage=\"0.5\" dynamic movable resizable zoomable>\n    <cropper-grid role=\"grid\" covered></cropper-grid>\n    <cropper-crosshair centered></cropper-crosshair>\n    <cropper-handle action=\"move\" theme-color=\"rgba(255, 255, 255, 0.35)\"></cropper-handle>\n    <cropper-handle action=\"n-resize\"></cropper-handle>\n    <cropper-handle action=\"e-resize\"></cropper-handle>\n    <cropper-handle action=\"s-resize\"></cropper-handle>\n    <cropper-handle action=\"w-resize\"></cropper-handle>\n    <cropper-handle action=\"ne-resize\"></cropper-handle>\n    <cropper-handle action=\"nw-resize\"></cropper-handle>\n    <cropper-handle action=\"se-resize\"></cropper-handle>\n    <cropper-handle action=\"sw-resize\"></cropper-handle>\n  </cropper-selection>\n</cropper-canvas>\n```\n\n:::\n\n### Multiple\n\nSet the `multiple` property to `true` to support multiple selections on the same image.\n\n:::live-demo\n\n```html\n<cropper-canvas style=\"height: 360px;\" background>\n  <cropper-image src=\"/cropperjs/picture.jpg\" alt=\"Picture\" rotatable scalable skewable translatable></cropper-image>\n  <cropper-shade hidden></cropper-shade>\n  <cropper-handle action=\"select\" plain></cropper-handle>\n  <cropper-selection id=\"cropperSelection\" x=\"20\" y=\"20\" width=\"40\" height=\"40\" movable resizable multiple keyboard>\n    <cropper-grid role=\"grid\" covered></cropper-grid>\n    <cropper-crosshair centered></cropper-crosshair>\n    <cropper-handle action=\"move\" theme-color=\"rgba(255, 255, 255, 0.35)\"></cropper-handle>\n    <cropper-handle action=\"n-resize\"></cropper-handle>\n    <cropper-handle action=\"e-resize\"></cropper-handle>\n    <cropper-handle action=\"s-resize\"></cropper-handle>\n    <cropper-handle action=\"w-resize\"></cropper-handle>\n    <cropper-handle action=\"ne-resize\"></cropper-handle>\n    <cropper-handle action=\"nw-resize\"></cropper-handle>\n    <cropper-handle action=\"se-resize\"></cropper-handle>\n    <cropper-handle action=\"sw-resize\"></cropper-handle>\n  </cropper-selection>\n  <cropper-selection id=\"cropperSelection1\" x=\"60\" y=\"60\" width=\"80\" height=\"80\" movable resizable multiple keyboard>\n    <cropper-grid role=\"grid\" covered></cropper-grid>\n    <cropper-crosshair centered></cropper-crosshair>\n    <cropper-handle action=\"move\" theme-color=\"rgba(255, 255, 255, 0.35)\"></cropper-handle>\n    <cropper-handle action=\"n-resize\"></cropper-handle>\n    <cropper-handle action=\"e-resize\"></cropper-handle>\n    <cropper-handle action=\"s-resize\"></cropper-handle>\n    <cropper-handle action=\"w-resize\"></cropper-handle>\n    <cropper-handle action=\"ne-resize\"></cropper-handle>\n    <cropper-handle action=\"nw-resize\"></cropper-handle>\n    <cropper-handle action=\"se-resize\"></cropper-handle>\n    <cropper-handle action=\"sw-resize\"></cropper-handle>\n  </cropper-selection>\n  <cropper-selection id=\"cropperSelection2\" x=\"140\" y=\"140\" width=\"120\" height=\"120\" movable resizable multiple keyboard>\n    <cropper-grid role=\"grid\" covered></cropper-grid>\n    <cropper-crosshair centered></cropper-crosshair>\n    <cropper-handle action=\"move\" theme-color=\"rgba(255, 255, 255, 0.35)\"></cropper-handle>\n    <cropper-handle action=\"n-resize\"></cropper-handle>\n    <cropper-handle action=\"e-resize\"></cropper-handle>\n    <cropper-handle action=\"s-resize\"></cropper-handle>\n    <cropper-handle action=\"w-resize\"></cropper-handle>\n    <cropper-handle action=\"ne-resize\"></cropper-handle>\n    <cropper-handle action=\"nw-resize\"></cropper-handle>\n    <cropper-handle action=\"se-resize\"></cropper-handle>\n    <cropper-handle action=\"sw-resize\"></cropper-handle>\n  </cropper-selection>\n</cropper-canvas>\n```\n\n:::\n\n### Limit boundaries\n\n<ClientOnly>\n  <CropperSelectionExample />\n</ClientOnly>\n\n::: details\n<<< @/.vitepress/components/CropperSelectionExample.vue\n:::\n\n## Properties\n\nInherits properties from its parent, [`CropperElement`](cropper-element.html), and implements the following properties:\n\n| Name | Type | Default | Options | Description |\n| --- | --- | --- | --- | --- |\n| x | `number` | `0` | - | Indicates the x-axis coordinate of the selection. |\n| y | `number` | `0` | - | Indicates the y-axis coordinate of the selection. |\n| width | `number` | `0` | - | Indicates the width of the selection. |\n| height | `number` | `0` | - | Indicates the height of the selection. |\n| aspectRatio | `number` | `NaN` | - | Indicates the aspect ratio of the selection, must a positive number. |\n| initialAspectRatio | `number` | `NaN` | - | Indicates the initial aspect ratio of the selection, must a positive number. |\n| initialCoverage | `number` | `NaN` | - | Indicates the initial coverage of the selection, must a positive number between `0` (0%) and `1` (100%). |\n| dynamic | `boolean` | `false` | - | Indicates whether this selection is dynamic and changes as the image changes. |\n| movable | `boolean` | `false` | - | Indicates whether this element is movable. |\n| resizable | `boolean` | `false` | - | Indicates whether this element is resizable. |\n| zoomable | `boolean` | `false` | - | Indicates whether this element is zoomable. |\n| multiple | `boolean` | `false` | - | Indicates whether multiple selections is supported. |\n| keyboard | `boolean` | `false` | - | Indicates whether keyboard control is supported. |\n| outlined | `boolean` | `false` | - | Indicates whether show the outlined or not. |\n| precise | `boolean` | `false` | - | Indicates whether reserve the precise of the `x`, `y`, `width`, and `height` properties or not. |\n\nThe supported keyboard keys:\n\n- `Delete` or `Command + Backspace`: Removes the active selection.\n- `ArrowLeft`: Moves the active selection to the left by 1 pixel.\n- `ArrowRight`: Moves the active selection to the right by 1 pixel.\n- `ArrowUp`: Moves the active selection to the top by 1 pixel.\n- `ArrowDown`: Moves the active selection to the bottom by 1 pixel.\n- `+`: Zooms in the active selection by 10%.\n- `-`: Zooms out the active selection by 10%.\n\n## Methods\n\n### $center\n\n- **Syntax**: `$center()`\n- **Returns**:\n  - Type: `CropperSelection`\n  - The element instance for chaining.\n\nAligns the selection to the center of its parent element.\n\n### $move\n\n- **Syntax**:\n  - `$move(x)`\n  - `$move(x, y)`\n- **Alternatives**:\n  - `$moveTo(selection.x + x)`\n  - `$moveTo(selection.x + x, selection.y + y)`\n- **Arguments**:\n  - `x`:\n    - Type: `number`\n    - The moving distance in the horizontal direction.\n  - `y`:\n    - Type: `number`\n    - Default: `x`\n    - The moving distance in the vertical direction.\n- **Returns**:\n  - Type: `CropperSelection`\n  - The element instance for chaining.\n\nMoves the selection.\n\n### $moveTo\n\n- **Syntax**:\n  - `$moveTo(x)`\n  - `$moveTo(x, y)`\n- **Arguments**:\n  - `x`:\n    - Type: `number`\n    - The new position in the horizontal direction.\n  - `y`:\n    - Type: `number`\n    - Default: `x`\n    - The new position in the vertical direction.\n- **Returns**:\n  - Type: `CropperSelection`\n  - The element instance for chaining.\n\nMoves the selection to a specific position.\n\n### $resize\n\n- **Syntax**:\n  - `$resize(action)`\n  - `$resize(action, offsetX)`\n  - `$resize(action, offsetX, offsetY)`\n  - `$resize(action, offsetX, offsetY, aspectRatio)`\n- **Arguments**:\n  - `action`:\n    - Type: `string`\n    - Options: `\"n-resize\"`, `\"e-resize\"`, `\"s-resize\"`, `\"w-resize\"`, `\"ne-resize\"`, `\"nw-resize\"`, `\"se-resize\"`, and `\"sw-resize\"`.\n    - Indicates the side or corner to resize.\n  - `offsetX`:\n    - Type: `number`\n    - Default: `0`\n    - The horizontal offset of the specific side or corner.\n  - `offsetY`:\n    - Type: `number`\n    - Default: `0`\n    - The vertical offset of the specific side or corner.\n  - `aspectRatio`:\n    - Type: `number`\n    - Default: `this.aspectRatio`\n    - The aspect ratio for computing the new size if it is necessary.\n- **Returns**:\n  - Type: `CropperSelection`\n  - The element instance for chaining.\n\nAdjusts the size of the selection on a specific side or corner.\n\n### $zoom\n\n- **Syntax**:\n  - `$zoom(scale)`\n  - `$zoom(scale, x, y)`\n- **Arguments**:\n  - `scale`:\n    - Type: `number`\n    - The zoom factor. Positive numbers for zooming in, and negative numbers for zooming out.\n  - `x`:\n    - Type: `number`\n    - Default: The center of the selection in the horizontal.\n    - The zoom origin in the horizontal.\n  - `y`:\n    - Type: `number`\n    - Default: The center of the selection in the vertical.\n    - The zoom origin in the vertical.\n- **Returns**:\n  - Type: `CropperSelection`\n  - The element instance for chaining.\n- **Example**:\n\n  ```js\n  cropperSelection.$zoom(0.1); // Zoom in 10%\n  cropperSelection.$zoom(-0.1); // Zoom out 10%\n  ```\n\nZooms the selection. Changes the width and height of the selection in pixels directly at the same time.\n\n### $change\n\n- **Syntax**:\n  - `$change(x, y)`\n  - `$change(x, y, width, height)`\n  - `$change(x, y, width, height, aspectRatio)`\n- **Arguments**:\n  - `x`:\n    - Type: `number`\n    - The new position in the horizontal direction.\n  - `y`:\n    - Type: `number`\n    - The new position in the vertical direction.\n  - `width`:\n    - Type: `number`\n    - Default: `this.width`\n    - The new width.\n  - `height`:\n    - Type: `number`\n    - Default: `this.height`\n    - The new height.\n  - `aspectRatio`:\n    - Type: `number`\n    - Default: `this.aspectRatio`\n    - The new aspect ratio for this change only.\n- **Returns**:\n  - Type: `CropperSelection`\n  - The element instance for chaining.\n\nChanges the position and/or size of the selection.\n\n### $reset\n\n- **Syntax**: `$reset()`\n- **Returns**:\n  - Type: `CropperSelection`\n  - The element instance for chaining.\n\nResets the selection to its initial position and size.\n\n### $clear\n\n- **Syntax**: `$clear()`\n- **Returns**:\n  - Type: `CropperSelection`\n  - The element instance for chaining.\n\nClears the selection.\n\n### $render\n\n- **Syntax**: `$render()`\n- **Returns**:\n  - Type: `CropperSelection`\n  - The element instance for chaining.\n\nRefreshes the position or size of the selection.\n\n### $toCanvas\n\n- **Syntax**:\n  - `$toCanvas()`\n  - `$toCanvas(options)`\n- **Arguments**:\n  - `options`:\n    - Type: `Object`\n    - The available options.\n    - Properties:\n      - `width`:\n        - Type: `number`\n        - The width of the canvas.\n      - `height`:\n        - Type: `number`\n        - The height of the canvas.\n      - `beforeDraw`:\n        - Type: `Function`\n        - The function called before drawing the image onto the canvas.\n        - Syntax: `beforeDraw(context, canvas)`\n        - Arguments:\n          - `context`:\n            - Type: `CanvasRenderingContext2D`\n            - The 2D rendering context of the canvas.\n          - `canvas`:\n            - Type: `HTMLCanvasElement`\n            - The canvas element itself.\n        - Example: `function (context) { context.filter = 'grayscale(100%)'; }`\n- **Returns**:\n  - Type: `Promise`\n  - A promise that resolves to the generated canvas element.\n- **Example**:\n  <ClientOnly>\n    <CropperSelectionToNativeCanvas />\n  </ClientOnly>\n\nGenerates a real canvas element, with the image (selected area only) drawn into if there is one.\n\n## Events\n\n### change\n\nThe event is fired when the position or size of the selection is going to change.\n\n- Event:\n  - **event.bubbles**: `true`\n  - **event.cancelable**: `true`\n  - **event.composed**: `true`\n  - **event.detail**:\n    - Type: `Object`\n    - The position and size data of the selection.\n  - **event.detail.x**:\n    - Type: `number`\n    - The x-axis coordinate of the selection.\n  - **event.detail.y**:\n    - Type: `number`\n    - The y-axis coordinate of the selection.\n  - **event.detail.width**:\n    - Type: `number`\n    - The width of the selection.\n  - **event.detail.height**:\n    - Type: `number`\n    - The height of the selection.\n- Example:\n\n```html\n<cropper-selection id=\"selection\"></cropper-selection>\n\n<script>\ndocument.querySelector('#selection').addEventListener('change', function (event) {\n  console.log(event);\n});\n</script>\n```\n\n## Slots\n\nThere is only one default slot in this element.\n\n> You can disable it by setting the `slottable` property to `false`:\n>\n> ```html\n> <cropper-selection slottable=\"false\"></cropper-selection>\n> ```\n"
  },
  {
    "path": "docs/api/cropper-shade.md",
    "content": "# CropperShade\n\nThe `CropperShade` interface provides properties and methods for manipulating the layout and presentation of `<cropper-shade>` elements.\n\n## Examples\n\n### Basic\n\n:::live-demo\n\n```html\n<cropper-shade></cropper-shade>\n```\n\n:::\n\n:::tip\nThe default width and height of this element is `0`.\n:::\n\n### Specify position and size\n\n:::live-demo\n\n```html\n<cropper-canvas background>\n  <cropper-shade x=\"240\" y=\"5\" width=\"160\" height=\"90\"></cropper-shade>\n</cropper-canvas>\n```\n\n:::\n\n### Customize the color\n\n:::live-demo\n\n```html\n<cropper-canvas background>\n  <cropper-shade x=\"240\" y=\"5\" width=\"160\" height=\"90\" theme-color=\"rgba(0, 0, 0, 0.35)\"></cropper-shade>\n</cropper-canvas>\n```\n\n:::\n\n### Toggle visibility on pointer down/up dynamically\n\n:::live-demo\n\n```html\n<cropper-canvas background>\n  <cropper-image src=\"/cropperjs/picture.jpg\" alt=\"Picture\" rotatable scalable skewable translatable></cropper-image>\n  <cropper-shade hidden></cropper-shade>\n  <cropper-handle action=\"select\" plain></cropper-handle>\n  <cropper-selection movable resizable hidden>\n    <cropper-handle action=\"move\" plain></cropper-handle>\n  </cropper-selection>\n</cropper-canvas>\n```\n\n:::\n\n:::tip\nThe `<cropper-shade>` element will automatically synchronize the position and size of the currently active `<cropper-selection>` element.\n:::\n\n:::tip\nThe [`hidden`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/hidden) attribute is a native global attribute.\n:::\n\n## Properties\n\nInherits properties from its parent, [`CropperElement`](cropper-element.html), and implements the following properties:\n\n| Name | Type | Default | Options | Description |\n| --- | --- | --- | --- | --- |\n| x | `number` | `0` | - | Indicates the x-axis coordinate of the element. |\n| y | `number` | `0` | - | Indicates the y-axis coordinate of the element. |\n| width | `number` | `0` | - | Indicates the width of the element. |\n| height | `number` | `0` | - | Indicates the height of the element. |\n| slottable | `boolean` | `false` | - | Indicates whether this element is slottable. |\n| themeColor | `string` | `\"rgba(0, 0, 0, 0.65)\"` | - | Indicates the color of the shade. |\n\n## Methods\n\n### $change\n\n- **Syntax**:\n  - `$change(x, y)`\n  - `$change(x, y, width, height)`\n- **Arguments**:\n  - `x`:\n    - Type: `number`\n    - The new position in the horizontal direction.\n  - `y`:\n    - Type: `number`\n    - The new position in the vertical direction.\n  - `width`:\n    - Type: `number`\n    - Default: `this.width`\n    - The new width.\n  - `height`:\n    - Type: `number`\n    - Default: `this.height`\n    - The new height.\n- **Returns**:\n  - Type: `CropperShade`\n  - The element instance for chaining.\n\nChanges the position and/or size of the shade.\n\n### $reset\n\n- **Syntax**: `$reset()`\n- **Returns**:\n  - Type: `CropperShade`\n  - The element instance for chaining.\n\nResets the shade to its initial position and size.\n\n### $render\n\n- **Syntax**: `$render()`\n- **Returns**:\n  - Type: `CropperShade`\n  - The element instance for chaining.\n\nRefreshes the position or size of the shade.\n\n## Slots\n\nThere is only one default slot in this element.\n\n> You can disable it by setting the `slottable` property to `false`:\n>\n> ```html\n> <cropper-shade slottable=\"false\"></cropper-shade>\n> ```\n"
  },
  {
    "path": "docs/api/cropper-viewer.md",
    "content": "# CropperViewer\n\nThe `CropperViewer` interface provides properties and methods for manipulating the layout and presentation of `<cropper-viewer>` elements.\n\n## Examples\n\n### Basic\n\n:::live-demo\n\n```html\n<cropper-viewer></cropper-viewer>\n```\n\n:::\n\n:::tip\nThe default height of this element is `0`.\n:::\n\n### Connect to CropperSelection\n\n:::live-demo\n\n```html\n<cropper-canvas style=\"height: 240px;\" background>\n  <cropper-image src=\"/cropperjs/picture.jpg\" alt=\"Picture\" rotatable scalable skewable translatable></cropper-image>\n  <cropper-shade hidden></cropper-shade>\n  <cropper-handle action=\"select\" plain></cropper-handle>\n  <cropper-selection id=\"cropperSelection\" initial-aspect-ratio=\"1.5\" initial-coverage=\"0.5\" movable resizable>\n    <cropper-handle action=\"move\" theme-color=\"rgba(255, 255, 255, 0.35)\"></cropper-handle>\n    <cropper-handle action=\"n-resize\"></cropper-handle>\n    <cropper-handle action=\"e-resize\"></cropper-handle>\n    <cropper-handle action=\"s-resize\"></cropper-handle>\n    <cropper-handle action=\"w-resize\"></cropper-handle>\n    <cropper-handle action=\"ne-resize\"></cropper-handle>\n    <cropper-handle action=\"nw-resize\"></cropper-handle>\n    <cropper-handle action=\"se-resize\"></cropper-handle>\n    <cropper-handle action=\"sw-resize\"></cropper-handle>\n  </cropper-selection>\n</cropper-canvas>\n\n<div class=\"cropper-viewers\">\n  <cropper-viewer selection=\"#cropperSelection\" style=\"width: 320px;\"></cropper-viewer>\n  <cropper-viewer selection=\"#cropperSelection\" style=\"width: 160px;\"></cropper-viewer>\n  <cropper-viewer selection=\"#cropperSelection\" style=\"width: 80px;\"></cropper-viewer>\n  <cropper-viewer selection=\"#cropperSelection\" style=\"width: 40px;\"></cropper-viewer>\n</div>\n\n<style>\n.cropper-viewers {\n  margin-top: 0.5rem;\n}\n\n.cropper-viewers > cropper-viewer {\n  border: 1px solid var(--vp-c-divider);\n  display: inline-block;\n  margin-right: 0.25rem;\n}\n</style>\n```\n\n:::\n\n## Properties\n\nInherits properties from its parent, [`CropperElement`](cropper-element.html), and implements the following properties:\n\n| Name | Type | Default | Options | Description |\n| --- | --- | --- | --- | --- |\n| resize | `string` | `\"vertical\"` | `\"both\"`, `\"horizontal\"`, `\"vertical\"`, `\"none\"` | Indicates whether this element is resizable, and if so, in which directions. |\n| selection | `string` | `\"\"` | - | Indicates the source selection to view. It must be a valid selector for `document.querySelector`. |\n| slottable | `boolean` | `false` | - | Indicates whether this element is slottable. |\n\n## Slots\n\nThere are no available slots in this element.\n\n> You can enable the default slot by setting the `slottable` property to `true`:\n>\n> ```html\n> <cropper-viewer slottable></cropper-viewer>\n> ```\n"
  },
  {
    "path": "docs/api/index.md",
    "content": "# Cropper\n\nThe `Cropper` constructor creates a new Cropper instance.\n\n## Usage\n\n### Syntax\n\n```js\nnew Cropper(element[, options])\n```\n\n- **element**\n  - Type: `HTMLImageElement | HTMLCanvasElement | string`\n  - The target image or canvas element for cropping. If it is a string, will be passed into the [`document.querySelector`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector) to find the element.\n\n- **options** (optional)\n  - Type: `Object`\n  - The [options](#options) for cropping.\n\n### Example\n\n<ClientOnly>\n  <CropperExample />\n</ClientOnly>\n\n::: details\n<<< @/.vitepress/components/CropperExample.vue\n:::\n\n## Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| container | `Element \\| string` | Defaults to the parent element of the target element, or `document.body` if the parent element is null. | The Cropper container. If it is a string, it will be passed into the `document.querySelector` to find the element. |\n| template | `string` | Defaults to a built-in template, see below. | The Cropper template. |\n\nThe default template for the Cropper:\n\n```html\n<cropper-canvas background>\n  <cropper-image></cropper-image>\n  <cropper-shade hidden></cropper-shade>\n  <cropper-handle action=\"select\" plain></cropper-handle>\n  <cropper-selection initial-coverage=\"0.5\" movable resizable>\n    <cropper-grid role=\"grid\" bordered covered></cropper-grid>\n    <cropper-crosshair centered></cropper-crosshair>\n    <cropper-handle action=\"move\" theme-color=\"rgba(255, 255, 255, 0.35)\"></cropper-handle>\n    <cropper-handle action=\"n-resize\"></cropper-handle>\n    <cropper-handle action=\"e-resize\"></cropper-handle>\n    <cropper-handle action=\"s-resize\"></cropper-handle>\n    <cropper-handle action=\"w-resize\"></cropper-handle>\n    <cropper-handle action=\"ne-resize\"></cropper-handle>\n    <cropper-handle action=\"nw-resize\"></cropper-handle>\n    <cropper-handle action=\"se-resize\"></cropper-handle>\n    <cropper-handle action=\"sw-resize\"></cropper-handle>\n  </cropper-selection>\n</cropper-canvas>\n```\n\n## Instance Properties\n\n| Name | Type | Description |\n| --- | --- | --- |\n| element | `HTMLImageElement \\| HTMLCanvasElement` | The normalized Cropper element. |\n| options | `Object` | The normalized Cropper options. |\n| container | `Element` | The normalized Cropper container. |\n\n## Instance Methods\n\n### getCropperCanvas\n\n- **Syntax**: `getCropperCanvas()`\n- **Alternative**: `cropper.container.querySelector('cropper-canvas')`\n- **Returns**:\n  - Type: `CropperCanvas | null`\n  - The `<cropper-canvas>` element if any.\n\nGet the `<cropper-canvas>` element in the Cropper container.\n\n### getCropperImage\n\n- **Syntax**: `getCropperImage()`\n- **Alternative**: `cropper.container.querySelector('cropper-image')`\n- **Returns**:\n  - Type: `CropperImage | null`\n  - The `<cropper-image>` element if any.\n\nGet the `<cropper-image>` element in the Cropper container.\n\n### getCropperSelection\n\n- **Syntax**: `getCropperSelection()`\n- **Alternative**: `cropper.container.querySelector('cropper-selection')`\n- **Returns**:\n  - Type: `CropperSelection | null`\n  - The `<cropper-selection>` element if any.\n\nGet the `<cropper-selection>` element in the Cropper container.\n\n### getCropperSelections\n\n- **Syntax**: `getCropperSelections()`\n- **Alternative**: `cropper.container.querySelectorAll('cropper-selection')`\n- **Returns**:\n  - Type: `NodeListOf<CropperSelection> | null`\n  - The `<cropper-selection>` element if any.\n\nGet all the `<cropper-selection>` elements in the Cropper container when there are multiple selections.\n\n### destroy <Badge type=\"tip\" text=\"^2.1.0\" />\n\n- **Syntax**: `destroy()`\n\nDestroy the cropper instance.\n\n## Exported Modules\n\n```js\nimport {\n  // Constants\n  DEFAULT_TEMPLATE,\n\n  // Elements\n  CropperElement,\n  CropperCanvas,\n  CropperImage,\n  CropperShade,\n  CropperHandle,\n  CropperSelection,\n  CropperGrid,\n  CropperCrosshair,\n  CropperViewer,\n} from 'cropperjs';\n```\n"
  },
  {
    "path": "docs/cropper-playground.md",
    "content": "---\ntitle: Cropper Playground\nlayout: false\n---\n\n<ClientOnly>\n  <CropperPlayground />\n</ClientOnly>\n"
  },
  {
    "path": "docs/guide.md",
    "content": "---\nsidebar: auto\n---\n\n# Guide\n\n## Introduction\n\nCropper.js 2.0 is a series of web components for image cropping.\n\n- **What is the difference between Cropper.js 1.0 and Cropper.js 2.0?**\n\n| Type | Cropper.js 1.0 | Cropper.js 2.0 |\n| --- | --- | --- |\n| Created | 2015 | 2021 |\n| Status | Maintaining | Active |\n| Package Number | 1 | 12+ |\n| Architecture | All in one | Modular |\n| Browser Compatibility | Modern browsers / IE 9+ | Modern browsers |\n| Extensible | No | Yes |\n| Customizable | No | Yes |\n| CSS-in-JS | No | Yes |\n| Import on-demand | No | Yes |\n| Multiple selections | No | Yes |\n| Rotate image on touch | No | Yes |\n\n- **What is the difference between Cropper, Cropper.js, and jQuery Cropper?**\n\n| GitHub Project | npm Package | Dependencies | Created | Status | Description |\n| --- | --- | --- | --- | --- | --- |\n| [Cropper](https://github.com/fengyuanchen/cropper) | [`cropper`](https://www.npmjs.com/package/cropper) | [`jquery`](https://www.npmjs.com/package/jquery) | 2014 | Deprecated | A simple jQuery image cropping plugin. |\n| [Cropper.js](https://github.com/fengyuanchen/cropperjs) | [`cropperjs`](https://www.npmjs.com/package/cropperjs) | - | 2015 | Active | JavaScript image cropper. |\n| [jQuery Cropper](https://github.com/fengyuanchen/jquery-cropper) | [`jquery-cropper`](https://www.npmjs.com/package/jquery-cropper) | [`jquery`](https://www.npmjs.com/package/jquery) + [`cropperjs@1`](https://www.npmjs.com/package/cropperjs) | 2018 | Maintaining | A jQuery plugin wrapper for Cropper.js 1.0. |\n\n## Getting started\n\n### Installation\n\n#### npm\n\nnpm is the recommended installation method when building large-scale applications with Cropper.js.\n\n```sh\nnpm install cropperjs\n```\n\nFor a specific package:\n\n```sh\nnpm install @cropper/element-canvas\n```\n\n#### CDN\n\nFor prototyping or learning purposes, you can use the latest version with:\n\n```html\n<script src=\"https://unpkg.com/cropperjs\"></script>\n```\n\nFor production, we recommend linking to a specific version number and build to avoid unexpected breakage from newer versions.\n\n### Usage\n\n#### Use in JavaScript\n\nImport the Cropper class and constructing a new Cropper instance.\n\n```js\nimport Cropper from 'cropperjs';\n\nconst cropper = new Cropper('#image');\n```\n\n```html\n<img id=\"image\" src=\"/cropperjs/picture.jpg\" alt=\"Picture\">\n```\n\n#### Use in DOM\n\nImport all Cropper elements from the `cropperjs` package and [define](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define) them as custom elements automatically.\n\n```js\nimport 'cropperjs';\n```\n\n:::live-demo\n\n```html\n<cropper-canvas background>\n  <cropper-image src=\"/cropperjs/picture.jpg\" alt=\"Picture\" rotatable scalable skewable translatable></cropper-image>\n  <cropper-shade hidden></cropper-shade>\n  <cropper-handle action=\"select\" plain></cropper-handle>\n  <cropper-selection initial-coverage=\"0.5\" movable resizable>\n    <cropper-grid role=\"grid\" covered></cropper-grid>\n    <cropper-crosshair centered></cropper-crosshair>\n    <cropper-handle action=\"move\" theme-color=\"rgba(255, 255, 255, 0.35)\"></cropper-handle>\n    <cropper-handle action=\"n-resize\"></cropper-handle>\n    <cropper-handle action=\"e-resize\"></cropper-handle>\n    <cropper-handle action=\"s-resize\"></cropper-handle>\n    <cropper-handle action=\"w-resize\"></cropper-handle>\n    <cropper-handle action=\"ne-resize\"></cropper-handle>\n    <cropper-handle action=\"nw-resize\"></cropper-handle>\n    <cropper-handle action=\"se-resize\"></cropper-handle>\n    <cropper-handle action=\"sw-resize\"></cropper-handle>\n  </cropper-selection>\n</cropper-canvas>\n```\n\n:::\n\n#### Use on-demand\n\nImport the required Cropper elements only and [define](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define) them as custom elements manually.\n\n```js\nimport CropperCanvas from '@cropper/element-canvas';\nimport CropperImage from '@cropper/element-image';\nimport CropperHandle from '@cropper/element-handle';\n\nCropperCanvas.$define();\nCropperImage.$define();\nCropperHandle.$define();\n```\n\n:::live-demo\n\n```html\n<cropper-canvas background>\n  <cropper-image src=\"/cropperjs/picture.jpg\" alt=\"Picture\" rotatable scalable skewable translatable></cropper-image>\n  <cropper-handle action=\"move\" plain></cropper-handle>\n</cropper-canvas>\n```\n\n:::\n\n## Packages\n\nCropper.js contains a series of [npm](https://www.npmjs.com/) packages:\n\n| Package | Version | Description |\n| --- | --- | --- |\n| `cropperjs` | [![Version](https://img.shields.io/npm/v/cropperjs)](https://www.npmjs.com/package/cropperjs/v) | The all-in-one package. |\n| `@cropper/element` | [![Version](https://img.shields.io/npm/v/@cropper/element)](https://www.npmjs.com/package/@cropper/element) | An abstract class for constructing Cropper elements. |\n| `@cropper/element-canvas` | [![Version](https://img.shields.io/npm/v/@cropper/element-canvas)](https://www.npmjs.com/package/@cropper/element-canvas) | A custom canvas element for the Cropper. |\n| `@cropper/element-image` | [![Version](https://img.shields.io/npm/v/@cropper/element-image)](https://www.npmjs.com/package/@cropper/element-image) | A custom image element for the Cropper. |\n| `@cropper/element-shade` | [![Version](https://img.shields.io/npm/v/@cropper/element-shade)](https://www.npmjs.com/package/@cropper/element-shade) | A custom shade element for the Cropper. |\n| `@cropper/element-handle` | [![Version](https://img.shields.io/npm/v/@cropper/element-handle)](https://www.npmjs.com/package/@cropper/element-handle) | A custom handle element for the Cropper. |\n| `@cropper/element-selection` | [![Version](https://img.shields.io/npm/v/@cropper/element-selection)](https://www.npmjs.com/package/@cropper/element-selection) | A custom selection element for the Cropper. |\n| `@cropper/element-grid` | [![Version](https://img.shields.io/npm/v/@cropper/element-grid)](https://www.npmjs.com/package/@cropper/element-grid) | A custom grid element for the Cropper. |\n| `@cropper/element-crosshair` | [![Version](https://img.shields.io/npm/v/@cropper/element-crosshair)](https://www.npmjs.com/package/@cropper/element-crosshair) | A custom crosshair element for the Cropper. |\n| `@cropper/element-viewer` | [![Version](https://img.shields.io/npm/v/@cropper/element-viewer)](https://www.npmjs.com/package/@cropper/element-viewer) | A custom viewer element for the Cropper. |\n| `@cropper/elements` | [![Version](https://img.shields.io/npm/v/@cropper/elements)](https://www.npmjs.com/package/@cropper/elements) | A series of custom elements for the Cropper. |\n| `@cropper/utils` | [![Version](https://img.shields.io/npm/v/@cropper/utils)](https://www.npmjs.com/package/@cropper/utils) | A series of common constants and utility functions for Cropper. |\n\n## Interfaces\n\n| Interface | Inherits | Description |\n| --- | --- | --- |\n| `Cropper` | `Function` | The `Cropper` constructor creates a new Cropper instance. |\n| `CropperElement` | `HTMLElement` | The `CropperElement` interface represents any Cropper element, extends the [HTMLElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement) interface. |\n| `CropperCanvas` | `CropperElement` | The `CropperCanvas` interface provides properties and methods for manipulating the layout and presentation of `<cropper-canvas>` elements. |\n| `CropperImage` | `CropperElement` | The `CropperImage` interface provides properties and methods for manipulating the layout and presentation of `<cropper-image>` elements. |\n| `CropperShade` | `CropperElement` | The `CropperShade` interface provides properties and methods for manipulating the layout and presentation of `<cropper-shade>` elements. |\n| `CropperHandle` | `CropperElement` | The `CropperHandle` interface provides properties and methods for manipulating the layout and presentation of `<cropper-handle>` elements. |\n| `CropperSelection` | `CropperElement` | The `CropperSelection` interface provides properties and methods for manipulating the layout and presentation of `<cropper-selection>` elements. |\n| `CropperGrid` | `CropperElement` | The `CropperGrid` interface provides properties and methods for manipulating the layout and presentation of `<cropper-grid>` elements. |\n| `CropperCrosshair` | `CropperElement` | The `CropperCrosshair` interface provides properties and methods for manipulating the layout and presentation of `<cropper-crosshair>` elements. |\n| `CropperViewer` | `CropperElement` | The `CropperViewer` interface provides properties and methods for manipulating the layout and presentation of `<cropper-viewer>` elements. |\n\n## Browser support\n\n- Edge 79+\n- Firefox 63+\n- Chrome 54+\n- Safari 10.1+\n- Opera 41+\n- iOS Safari 10.3+\n- Android Browser 81+\n- Opera Mobile 46+\n- Chrome for Android 81+\n- Firefox for Android 68+\n- Samsung Internet 6.2+\n"
  },
  {
    "path": "docs/index.md",
    "content": "---\nlayout: home\ntitle: Home\nhero:\n  name: Cropper.js\n  text: JavaScript image cropper.\n  image:\n    src: /logo.svg\n    alt: Cropper.js\n  actions:\n    - theme: brand\n      text: Get Started\n      link: /guide.html\n    - theme: alt\n      text: Playground\n      link: /playground.html\nfeatures:\n  - title: Customizable\n    details: Customize your own cropper easily.\n  - title: Extensible\n    details: Extend the cropper if you need more functions.\n  - title: Import on-demand\n    details: Use the parts you need and let the rest go with the wind.\n---\n"
  },
  {
    "path": "docs/migration.md",
    "content": "---\nsidebar: auto\n---\n\n# Migration\n\n## Options\n\n| Cropper.js 1.0 | Cropper.js 2.0 |\n| --- | --- |\n| `viewMode` | Use the `<cropper-image>` element's `transform` event to [limit image boundaries](api/cropper-image.html#limit-boundaries), or use the `<cropper-selection>` element's  `change` event to [limit selection boundaries](api/cropper-selection.html#limit-boundaries). |\n| `dragMode` | Use the `<cropper-handle>` element's `action` property. |\n| `initialAspectRatio` | Use the `<cropper-selection>` element's `initialAspectRatio` property. |\n| `aspectRatio` | Use the `<cropper-selection>` element's `aspectRatio` property. |\n| `data` | Use the `<cropper-image>` element's `$setTransform` method, and the `<cropper-selection>` element's `x`, `y`, `width`, and `height` properties. |\n| `preview` | Use the `<cropper-viewer>` element. |\n| `responsive` | Deprecated. |\n| `restore` | Deprecated. |\n| `checkCrossOrigin` | Deprecated. |\n| `checkOrientation` | Deprecated for performance reasons. As an alternative, it is recommended to use a third-party library, such as [JavaScript-Load-Image](https://github.com/blueimp/JavaScript-Load-Image). |\n| `modal` | Use the `<cropper-shade>` element. |\n| `guides` | Use the `<cropper-grid>` element. |\n| `center` | Use the `<cropper-crosshair>` element. |\n| `highlight` | Use the `<cropper-action>` element. |\n| `background` | Use the `<cropper-canvas>` element's `background` property. |\n| `autoCrop` | Use the `<cropper-selection>` element's `initialCoverage` property. |\n| `autoCropArea` | Use the `<cropper-selection>` element's `initialCoverage` property. |\n| `movable` | Use the `<cropper-image>` element's `translatable` property. |\n| `rotatable` | Use the `<cropper-image>` element's `rotatable` property. |\n| `scalable` | Use the `<cropper-image>` element's `scalable` property. |\n| `zoomable` | Deprecated. |\n| `zoomOnTouch` | Deprecated. |\n| `zoomOnWheel` | Deprecated. |\n| `wheelZoomRatio` | Use the `<cropper-canvas>` element's `scaleStep` property. |\n| `cropBoxMovable` | Use the `<cropper-selection>` element's `movable` property. |\n| `cropBoxResizable` | Use the `<cropper-selection>` element's `resizable` property. |\n| `toggleDragModeOnDblclick` | Use the `<cropper-handle>` element's `dblclick` event to [toggle action on dblclick](api/cropper-handle.html#toggle-action-on-dblclick). |\n| `minContainerWidth` | Deprecated. |\n| `minContainerHeight` | Deprecated. |\n| `minCanvasWidth` | Use the `<cropper-canvas>` element's `min-width` CSS property. |\n| `minCanvasHeight` | Use the `<cropper-canvas>` element's `min-height` CSS property. |\n| `minCropBoxWidth` | Use the `<cropper-selection>` element's `min-width` CSS property. |\n| `minCropBoxHeight` | Use the `<cropper-selection>` element's `min-height` CSS property. |\n| `ready` | Use the `<cropper-image>` element's  `$ready` method. |\n| `cropstart` | Use the `<cropper-canvas>` element's `actionstart` event. |\n| `cropmove` | Use the `<cropper-canvas>` element's `actionmove` event. |\n| `cropend` | Use the `<cropper-canvas>` element's `actionend` event. |\n| `crop` | Use the `<cropper-canvas>` element's `action` event. |\n| `zoom` | Use the `<cropper-canvas>` element's `action` event. |\n\n## Methods\n\n| Cropper.js 1.0 | Cropper.js 2.0 |\n| --- | --- |\n| `crop` | Use the `<cropper-selection>` element's `$change` method. |\n| `reset` | Use the `<cropper-image>` element's `$resetTransform` method, and the `<cropper-selection>` element's `$reset` method. |\n| `clear` | Use the `<cropper-selection>` element's `$reset` method and `hidden` property. |\n| `replace` | Use the `<cropper-image>` element's `src` property. |\n| `enable` | Use the `<cropper-canvas>` element's `disabled` property. |\n| `disable` | Use the `<cropper-canvas>` element's `disabled` property. |\n| `destroy` | Deprecated. Drops all the Cropper elements from the DOM directly. |\n| `move` | Use the `<cropper-image>` element's `$move` method. |\n| `moveTo` | Use the `<cropper-image>` element's `$moveTo` method. |\n| `zoom` | Use the `<cropper-image>` element's `$scale` method. |\n| `zoomTo` | Use the `<cropper-image>` element's `$setTransform` method. |\n| `rotate` | Use the `<cropper-image>` element's `$rotate` method. |\n| `rotateTo` | Use the `<cropper-image>` element's `$setTransform` method. |\n| `scale` | Use the `<cropper-image>` element's `$scale` method. |\n| `scaleX` | Use the `<cropper-image>` element's `$scale` method. |\n| `scaleY` | Use the `<cropper-image>` element's `$scale` method. |\n| `getData` | Use the `<cropper-image>` element's `$getTransform` method, and the `<cropper-selection>` element's `x`, `y`, `width`, and `height` properties. |\n| `setData` | Use the `<cropper-image>` element's `$setTransform` method, and the `<cropper-selection>` element's `x`, `y`, `width`, and `height` properties. |\n| `getContainerData` | Deprecated. |\n| `getImageData` | Use the `<cropper-image>` element's `$getTransform` method. |\n| `getCanvasData` | Deprecated. |\n| `setCanvasData` | Deprecated. |\n| `getCropBoxData` | Use the `<cropper-selection>` element's `x`, `y`, `width`, and `height` properties. |\n| `setCropBoxData` | Use the `<cropper-selection>` element's `x`, `y`, `width`, and `height` properties. |\n| `getCroppedCanvas` | Use the `<cropper-selection>` element's `$toCanvas` method. |\n| `setAspectRatio` | Use the `<cropper-selection>` element's `aspectRatio` property. |\n| `setDragMode` | Use the `<cropper-handle>` element's `action` property. |\n\n## Events\n\n| Cropper.js 1.0 | Cropper.js 2.0 |\n| --- | --- |\n| `ready` | Use the `<cropper-image>` element's  `$ready` method. |\n| `cropstart` | Use the `<cropper-canvas>` element's `actionstart` event. |\n| `cropmove` | Use the `<cropper-canvas>` element's `actionmove` event. |\n| `cropend` | Use the `<cropper-canvas>` element's `actionend` event. |\n| `crop` | Use the `<cropper-canvas>` element's `action` event. |\n| `zoom` | Use the `<cropper-canvas>` element's `action` event. |\n"
  },
  {
    "path": "docs/playground.md",
    "content": "---\ntitle: Playground\nlayout: page\n---\n\n<ClientOnly>\n  <CropperPlaygroundContainer src=\"./cropper-playground.html\" />\n</ClientOnly>\n"
  },
  {
    "path": "docs/public/manifest.webmanifest",
    "content": "{\n  \"name\": \"Cropper.js\",\n  \"short_name\": \"Cropper.js\",\n  \"description\": \"JavaScript image cropper.\",\n  \"start_url\": \"/cropperjs/index.html\",\n  \"display\": \"standalone\",\n  \"background_color\": \"#fff\",\n  \"theme_color\": \"#39f\"\n}\n"
  },
  {
    "path": "docs/public/service-worker.js",
    "content": "if ('serviceWorker' in navigator) {\n  self.addEventListener('install', () => {\n    self.skipWaiting();\n  });\n  self.addEventListener('activate', (event) => {\n    event.waitUntil(\n      caches.keys()\n        .then((keys) => Promise.all(keys.map((key) => caches.delete(key))))\n        .then(() => navigator.serviceWorker.getRegistrations())\n        .then((registrations) => Promise.all(registrations.map((registration) => registration.unregister()))),\n    );\n  });\n}\n"
  },
  {
    "path": "docs/zh/api/cropper-canvas.md",
    "content": "# CropperCanvas\n\n`CropperCanvas` 接口提供了用于操作 `<cropper-canvas>` 元素的布局和表示的属性和方法。\n\n## 示例\n\n### 基本\n\n:::live-demo\n\n```html\n<cropper-canvas></cropper-canvas>\n```\n\n:::\n\n:::tip\n此元素的默认最小宽度和最小高度为 `200px` 和 `100px`。\n:::\n\n### 背景\n\n:::live-demo\n\n```html\n<cropper-canvas background></cropper-canvas>\n```\n\n:::\n\n### 交互\n\n:::live-demo\n\n```html\n<cropper-canvas background>\n  <cropper-image src=\"/cropperjs/picture.jpg\" alt=\"Picture\" rotatable scalable skewable translatable></cropper-image>\n  <cropper-handle action=\"move\" plain></cropper-handle>\n</cropper-canvas>\n```\n\n:::\n\n### 禁用\n\n所有指针事件均被禁用。\n\n:::live-demo\n\n```html\n<cropper-canvas background disabled>\n  <cropper-image src=\"/cropperjs/picture.jpg\" alt=\"Picture\" rotatable scalable skewable translatable></cropper-image>\n  <cropper-handle action=\"move\" plain></cropper-handle>\n</cropper-canvas>\n```\n\n:::\n\n## 属性\n\n从其父级 [`CropperElement`](cropper-element.html) 继承属性，并实现以下属性：\n\n| 名称 | 类型 | 默认值 | 可选值 | 描述 |\n| --- | --- | --- | --- | --- |\n| background | `boolean` | `false` | - | 指示此元素是否具有网格背景。 |\n| disabled | `boolean` | `false` | - | 指示此元素是否已禁用。 |\n| scaleStep | `number` | `0.1` | - | 指示滚轮放大/缩小时缩放系数的步进间隔，必须为正数。 |\n| themeColor | `string` | `\"#39f\"` | - | 指示此元素及其子元素的颜色。 |\n\n## 方法\n\n### $setAction\n\n- **语法**：`$setAction(action)`\n- **参数**：\n  - `action`：\n    - 类型：`string`\n    - 新的动作。\n- **返回值**：\n  - 类型：`CropperCanvas`\n  - 元素实例。\n\n将当前动作变更为新动作。\n\n### $toCanvas\n\n- **语法**：\n  - `$toCanvas()`\n  - `$toCanvas(options)`\n- **参数**：\n  - `options`：\n    - 类型：`Object`\n    - 可用选项。\n    - 属性：\n      - `width`：\n        - 类型：`number`\n        - 画布的宽度。\n      - `height`：\n        - 类型：`number`\n        - 画布的高度。\n      - `beforeDraw`：\n        - 类型：`Function`\n        - 在将图像绘制到画布上之前调用的函数。\n        - 语法：`beforeDraw(context, canvas)`\n        - 参数：\n          - `context`：\n            - 类型：`CanvasRenderingContext2D`\n            - 画布的 2D 渲染上下文。\n          - `canvas`：\n            - 类型：`HTMLCanvasElement`\n            - 画布元素本身。\n        - 示例：`function (context) { context.filter = 'grayscale(100%)'; }`\n- **返回值**：\n  - 类型：`Promise`\n  - 一个以生成的画布元素为给定值解析后的 Promise 对象。\n- **示例**：\n  <ClientOnly>\n    <CropperCanvasToNativeCanvas />\n  </ClientOnly>\n\n生成一个真实的画布元素，如果有图像，则将其绘制到其中。\n\n## 事件\n\n### action\n\n当指针在画布上变化时触发该事件。\n\n- 事件：\n  - **event.bubbles**：`true`\n  - **event.cancelable**：`false`\n  - **event.composed**：`true`\n  - **event.detail**：\n    - 类型：`Object`\n    - 动作的相关数据。\n  - **event.detail.action**：\n    - 类型：`string`\n    - 可选值：`\"select\"`, `\"move\"`, `\"scale\"`, `\"rotate\"`, `\"transform\"`, `\"n-resize\"`, `\"e-resize\"`, `\"s-resize\"`, `\"w-resize\"`, `\"ne-resize\"`, `\"nw-resize\"`, `\"se-resize\"`, and `\"sw-resize\"`.\n    - 动作类型。\n  - **event.detail.relatedEvent**：\n    - 类型：`PointerEvent | TouchEvent | MouseEvent | WheelEvent`\n    - 触发此事件的相关原生事件。\n  - **event.detail.scale**：\n    - 类型：`number`\n    - 缩放系数，仅当 `action` 为 `\"scale\"` 或 `\"transform\"` 时可用。\n  - **event.detail.rotate**：\n    - 类型：`number`\n    - 缩放系数，仅当 `action` 为 `\"rotate\"` 或 `\"transform\"` 时可用。\n  - **event.detail.startX**：\n    - 类型：`number`\n    - 起始 `pageX` 值，仅当 `relatedEvent` 为 `PointerEvent`、`TouchEvent` 或 `MouseEvent` 时可用。\n  - **event.detail.startY**：\n    - 类型：`number`\n    - 起始 `pageY` 值，仅在 `relatedEvent` 为 `PointerEvent`、`TouchEvent` 或 `MouseEvent` 时可用。\n  - **event.detail.endX**：\n    - 类型：`number`\n    - 结束 `pageX` 值，仅当 `relatedEvent` 为 `PointerEvent`、`TouchEvent` 或 `MouseEvent` 时可用。\n  - **event.detail.endY**：\n    - 类型：`number`\n    - 结束 `pageY` 值，仅当 `relatedEvent` 为 `PointerEvent`、`TouchEvent` 或 `MouseEvent` 时可用。\n- 示例：\n\n```html\n<cropper-canvas id=\"canvas\"></cropper-canvas>\n\n<script>\ndocument.querySelector('#canvas').addEventListener('action', function (event) {\n  console.log(event);\n});\n</script>\n```\n\n### actionstart\n\n当指针变为活动状态时触发该事件。\n\n- 事件：\n  - **event.bubbles**：`true`\n  - **event.cancelable**：`true`\n  - **event.composed**：`true`\n  - **event.detail**：\n    - 类型：`Object`\n    - 动作的相关数据。\n  - **event.detail.action**：\n    - 类型：`string`\n    - 可选值：与 `action` 事件相同，除了 `\"scale\"` 选项。\n    - 动作类型。\n  - **event.detail.relatedEvent**：\n    - 类型：`PointerEvent | TouchEvent | MouseEvent`\n    - 触发此事件的相关原生事件。\n\n### actionmove\n\n当指针改变坐标时触发此事件。\n\n- 事件：\n  - **event.bubbles**：`true`\n  - **event.cancelable**：`true`\n  - **event.composed**：`true`\n  - **event.detail**：\n    - 类型：`Object`\n    - 动作的相关数据。\n  - **event.detail.action**：\n    - 类型：`string`\n    - 可选值：与 `action` 事件相同，除了 `\"scale\"` 选项。\n    - 动作类型。\n  - **event.detail.relatedEvent**：\n    - 类型：`PointerEvent | TouchEvent | MouseEvent`\n    - 触发此事件的相关原生事件。\n\n### actionend\n\n当指针不再处于活动状态时会触发此事件。\n\n- 事件：\n  - **event.bubbles**：`true`\n  - **event.cancelable**：`true`\n  - **event.composed**：`true`\n  - **event.detail**：\n    - 类型：`Object`\n    - 动作的相关数据。\n  - **event.detail.action**：\n    - 类型：`string`\n    - 可选值：与 `action` 事件相同，除了 `\"scale\"` 选项。\n    - 动作类型。\n  - **event.detail.relatedEvent**：\n    - 类型：`PointerEvent | TouchEvent | MouseEvent`\n    - 触发此事件的相关原生事件。\n\n## 插槽\n\n此元素中只有一个默认插槽。\n\n> 你可以通过将 `slottable` 属性设置为 `false` 来禁用它：\n>\n> ```html\n> <cropper-canvas slottable=\"false\"></cropper-canvas>\n> ```\n"
  },
  {
    "path": "docs/zh/api/cropper-crosshair.md",
    "content": "# CropperCrosshair\n\n`CropperCrosshair` 接口提供了用于操作 `<cropper-crosshair>` 元素的布局和表示的属性和方法。\n\n## 示例\n\n### 基本\n\n:::live-demo\n\n```html\n<cropper-crosshair></cropper-crosshair>\n```\n\n:::\n\n### 自定义颜色\n\n:::live-demo\n\n```html\n<cropper-crosshair theme-color=\"#39f\"></cropper-crosshair>\n```\n\n:::\n\n### 在 CropperCanvas 中\n\n:::live-demo\n\n```html\n<cropper-canvas style=\"background-color: #39f;\">\n  <cropper-crosshair centered></cropper-crosshair>\n</cropper-canvas>\n```\n\n:::\n\n### 在 CropperSelection 中\n\n:::live-demo\n\n```html\n<cropper-selection width=\"160\" height=\"90\" style=\"background-color: #39f;\">\n  <cropper-crosshair centered></cropper-crosshair>\n</cropper-selection>\n```\n\n:::\n\n## 属性\n\n从其父级 [`CropperElement`](cropper-element.html) 继承属性，并实现以下属性：\n\n| 名称 | 类型 | 默认值 | 可选值 | 描述 |\n| --- | --- | --- | --- | --- |\n| centered | `boolean` | `false` | - | 指示此元素是否居中。 |\n| slottable | `boolean` | `false` | - | 指示此元素是否启用默认插槽。 |\n| themeColor | `string` | `\"rgba(238, 238, 238, 0.5)\"` | - | 指示十字准线的颜色。 |\n\n## 插槽\n\n此元素中没有可用的插槽。\n\n> 你可以通过将 `slottable` 属性设置为 `true` 来启用默认插槽：\n>\n> ```html\n> <cropper-crosshair slottable></cropper-crosshair>\n> ```\n"
  },
  {
    "path": "docs/zh/api/cropper-element.md",
    "content": "# CropperElement\n\n`CropperElement` 接口代表任何 Cropper 元素，扩展了 [HTMLElement](https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLElement) 接口。\n\n## 规格\n\n- 公共属性的名称应以字母字符开头。\n- 私有属性的名称应该以 `$` 开头。\n- 公共/私有自定义方法的名称应以 `$` 开头。\n- 私有自定义侦听器的名称应以 `$on` 开头。\n\n## 示例\n\n```js\nimport { CropperElement } from 'cropperjs';\n// Or\n// import CropperElement from '@cropper/element';\n\nclass MyCropperElement extends CropperElement {\n  myStringProperty = '';\n  myNumberProperty = NaN;\n  myBooleanProperty = false;\n\n  static get observedAttributes() {\n    return super.observedAttributes.concat([\n      'my-boolean-property',\n      'my-number-property',\n      'my-string-property',\n    ]);\n  }\n\n  // ...\n}\n\nMyCropperElement.$define();\n```\n\n```html\n<my-cropper-element my-string-property=\"foo\" my-number-property=\"1\" my-boolean-property></my-cropper-element>\n```\n\n## 属性\n\n从其父级 [`HTMLElement`](https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLElement) 继承属性，并实现以下属性：\n\n| 名称 | 类型 | 默认值 | 可选值 | 描述 |\n| --- | --- | --- | --- | --- |\n| shadowRootMode | `string` | `\"open\"` | `\"closed\" \\| \"open\"` | 指示 shadow DOM 树的封装模式。 |\n| slottable | `boolean` | `true` | - | 指示此元素是否启用默认插槽，即包含 `<slot>` 元素。 |\n| themeColor | `string` | - | - | 指示此元素及其子元素的颜色。 |\n\n## 方法\n\n### $getShadowRoot\n\n- **语法**：`$getShadowRoot()`\n- **返回值**：\n  - 类型：`ShadowRoot`\n  - shadow root。\n\n输出元素的 shadow root，即使它的模式是 `\"closed\"`。\n\n### $addStyles\n\n- **语法**：`$addStyles(styles)`\n- **参数**：\n  - `styles`：\n    - 类型：`string`\n    - 要添加的样式。\n- **返回值**：\n  - 类型：`CSSStyleSheet | HTMLStyleElement`\n  - 生成的样式表。\n- **示例**：\n\n  ```js\n  const canvas = new CropperCanvas();\n\n  canvas.$addStyles(`\n    :host {\n      border: 1px solid #39f;\n    }\n  `);\n  ```\n\n将样式添加到 shadow root。\n\n### $emit\n\n- **语法**：\n  - `$emit(type)`\n  - `$emit(type, detail)`\n  - `$emit(type, detail, options)`\n- **参数**：\n  - `type`：\n    - 类型：`string`\n    - 事件的名称。\n  - `detail`：\n    - 类型：`*`\n    - 默认值：`undefined`\n    - 初始化事件时传递的数据。\n  - `options`：\n    - 类型：`CustomEventInit`\n    - 默认值：`{ bubbles：true, cancelable: true, composed: true }`\n    - 其他事件选项。\n- **返回值**：\n  - 类型：`boolean`\n  - 结果值。\n- **示例**：\n\n  ```js\n  const selection = new CropperSelection();\n\n  selection.$emit('change', {\n    x: 10,\n    y: 5,\n    width: 160,\n    height: 90,\n  });\n  ```\n\n在当前元素上派发事件。\n\n### $nextTick\n\n- **语法**：\n  - `$nextTick()`\n  - `$nextTick(callback)`\n- **参数**：\n  - `callback`：\n    - 类型：`Function`\n    - 在下一个 DOM 更新周期后执行的回调。\n- **返回值**：\n  - 类型：`Promise`\n  - 一个以 `undefined` 为给定值解析后的 Promise 对象。\n\n推迟到下一个 DOM 更新周期后执行的回调。\n\n## 静态属性\n\n| 名称 | 类型 | 描述 |\n| --- | --- | --- |\n| $name | `string` | 自定义元素的名称。 |\n| $version | `string` | 安装包的版本。 |\n\n## 静态方法\n\n### $define\n\n将构造函数定义为新的自定义元素。这只是调用 [`CustomElementRegistry.define()`](https://developer.mozilla.org/zh-CN/docs/Web/API/CustomElementRegistry/define) 的快捷方式。\n\n- **语法**：\n  - `$define()`\n  - `$define(name)`\n  - `$define(options)`\n  - `$define(name, options)`\n- **等同于**：\n  - `customElements.define(name, constructor)`\n  - `customElements.define(name, constructor, options)`\n- **参数**：\n  - `name`：\n    - 类型：`string`\n    - 元素名称。默认为构造函数的 `$name` 静态属性。\n  - `options`：\n    - 类型：`Object`\n    - 元素定义选项。\n- **示例**：\n\n  ```js\n  // 定义为自主自定义元素：`<my-cropper-element></my-cropper-element>`.\n  CropperElement.$define('my-cropper-element');\n  ```\n"
  },
  {
    "path": "docs/zh/api/cropper-grid.md",
    "content": "# CropperGrid\n\n`CropperGrid` 接口提供了用于操作 `<cropper-grid>` 元素的布局和表示的属性和方法。\n\n## 示例\n\n### 基本\n\n:::live-demo\n\n```html\n<cropper-grid></cropper-grid>\n```\n\n:::\n\n:::tip\n此元素的默认高度为 `0`。\n:::\n\n### 自定义行和列\n\n:::live-demo\n\n```html\n<cropper-grid rows=\"4\" columns=\"18\" theme-color=\"#39f\" style=\"height: 10rem;\"></cropper-grid>\n```\n\n:::\n\n### 在 CropperCanvas 中\n\n:::live-demo\n\n```html\n<cropper-canvas style=\"background-color: #39f;\">\n  <cropper-grid bordered covered></cropper-grid>\n</cropper-canvas>\n```\n\n:::\n\n### 在 CropperSelection 中\n\n:::live-demo\n\n```html\n<cropper-selection width=\"160\" height=\"90\" style=\"background-color: #39f;\">\n  <cropper-grid bordered covered></cropper-grid>\n</cropper-selection>\n```\n\n:::\n\n## 属性\n\n从其父级 [`CropperElement`](cropper-element.html) 继承属性，并实现以下属性：\n\n| 名称 | 类型 | 默认值 | 可选值 | 描述 |\n| --- | --- | --- | --- | --- |\n| rows | `number` | `3` | - | 指示行的数量。 |\n| columns | `number` | `3` | - | 指示列的数量。 |\n| bordered | `boolean` | `false` | - | 指示此元素是否有边框。 |\n| covered | `boolean` | `false` | - | 指示此元素是否覆盖其父元素。 |\n| slottable | `boolean` | `false` | - | 指示此元素是否启用默认插槽。 |\n| themeColor | `string` | `\"rgba(238, 238, 238, 0.5)\"` | - | 指示元素的颜色。 |\n\n## 插槽\n\n此元素中没有可用的插槽。\n\n> 你可以通过将 `slottable` 属性设置为 `true` 来启用默认插槽：\n>\n> ```html\n> <cropper-grid slottable></cropper-grid>\n> ```\n"
  },
  {
    "path": "docs/zh/api/cropper-handle.md",
    "content": "# CropperHandle\n\n`CropperHandle` 接口提供了用于操作 `<cropper-handle>` 元素的布局和表示的属性和方法。\n\n## 示例\n\n### 基本\n\n:::live-demo\n\n```html\n<cropper-handle></cropper-handle>\n```\n\n:::\n\n:::tip\n此元素的默认宽度和高度为 `0`。\n:::\n\n### 在 CropperCanvas 中\n\n:::live-demo\n\n```html\n<cropper-canvas background>\n  <cropper-image src=\"/cropperjs/picture.jpg\" alt=\"Picture\" rotatable scalable skewable translatable></cropper-image>\n  <cropper-handle action=\"move\"></cropper-handle>\n</cropper-canvas>\n```\n\n:::\n\n### 在 CropperSelection 中\n\n:::live-demo\n\n```html\n<cropper-canvas background>\n  <cropper-image src=\"/cropperjs/picture.jpg\" alt=\"Picture\" rotatable scalable skewable translatable></cropper-image>\n  <cropper-selection width=\"100\" height=\"100\" movable>\n    <cropper-handle action=\"move\"></cropper-handle>\n  </cropper-selection>\n</cropper-canvas>\n```\n\n:::\n\n### 双击切换动作类型\n\n<ClientOnly>\n  <CropperActionExample />\n</ClientOnly>\n\n::: details\n<<< @/.vitepress/components/CropperActionExample.vue\n:::\n\n## 属性\n\n从其父级 [`CropperElement`](cropper-element.html) 继承属性，并实现以下属性：\n\n| 名称 | 类型 | 默认值 | 可选值 | 描述 |\n| --- | --- | --- | --- | --- |\n| action | `string` | `\"none\"` | `\"select\"`, `\"move\"`, `\"scale\"`, `\"n-resize\"`, `\"e-resize\"`, `\"s-resize\"`, `\"w-resize\"`, `\"ne-resize\"`, `\"nw-resize\"`, `\"se-resize\"`, `\"sw-resize\"`, `\"none\"` | 表示手柄的动作类型。 |\n| plain | `boolean` | `false` | - | 指示此元素是否为素色元素。 |\n| slottable | `boolean` | `false` | - | 指示此元素是否启用默认插槽。 |\n| themeColor | `string` | `\"rgba(51, 153, 255, 0.5)\"` | - | 指示手柄的颜色。 |\n\n## 插槽\n\n此元素中没有可用的插槽。\n\n> 你可以通过将 `slottable` 属性设置为 `true` 来启用默认插槽：\n>\n> ```html\n> <cropper-handle slottable></cropper-handle>\n> ```\n"
  },
  {
    "path": "docs/zh/api/cropper-image.md",
    "content": "# CropperImage\n\n`CropperImage` 接口提供了用于操作 `<cropper-image>` 元素的布局和表示的属性和方法。\n\n## 示例\n\n### 基本\n\n:::live-demo\n\n```html\n<cropper-image></cropper-image>\n```\n\n:::\n\n:::tip\n此元素的默认宽度和高度为 `0`。\n:::\n\n### 有图片源\n\n:::live-demo\n\n```html\n<cropper-image src=\"/cropperjs/picture.jpg\" alt=\"Picture\" style=\"width: 100%;\" rotatable scalable skewable translatable></cropper-image>\n```\n\n:::\n\n### 限制边界\n\n<ClientOnly>\n  <CropperImageExample />\n</ClientOnly>\n\n::: details\n<<< @/.vitepress/components/CropperImageExample.vue\n:::\n\n## 属性\n\n从其父级 [`CropperElement`](cropper-element.html) 继承属性，并实现以下属性：\n\n| 名称 | 类型 | 默认值 | 可选值 | 描述 |\n| --- | --- | --- | --- | --- |\n| initial-center-size | `string` | `\"contain\"` | `\"contain\"`, `\"cover\"` | 指示图像与其父元素的中心对齐时的初始大小。 |\n| rotatable | `boolean` | `false` | - | 指示此元素是否可旋转。 |\n| scalable | `boolean` | `false` | - | 指示此元素是否可缩放。 |\n| skewable | `boolean` | `false` | - | 指示此元素是否可倾斜。 |\n| slottable | `boolean` | `false` | - | 指示此元素是否启用默认插槽。 |\n| translatable | `boolean` | `false` | - | 指示此元素是否可移动。 |\n\n默认情况下，内置的 `<img>` 元素将继承以下属性：\n\n- `alt`\n- `crossorigin`\n- `decoding`\n- `elementtiming`\n- `fetchpriority`\n- `loading`\n- `referrerpolicy`\n- `sizes`\n- `src`\n- `srcset`\n\n## 方法\n\n### $ready\n\n- **语法**：\n  - `$ready()`\n  - `$ready(callback)`\n- **参数**：\n  - `callback`：\n    - 类型：`Function`\n    - 成功加载图片后执行的回调。\n- **返回值**：\n  - 类型：`Promise`\n  - 一个以图片元素为给定值解析后的 Promise。\n- **示例**：\n\n  ```js\n  const cropperImage = new CropperImage();\n\n  cropperImage.$ready((image) => {\n    console.log(image.naturalWidth, image.naturalHeight);\n  });\n  cropperImage.src = '/cropperjs/picture.jpg';\n  ```\n\n成功加载图像后延迟执行回调。\n\n### $center\n\n- **语法**：\n  - `$center()`\n  - `$center(size)`\n- **参数**：\n  - `size`：\n    - 类型：`string`\n    - 可选值：`\"contain\"`, and `\"cover\"`.\n    - 图像的尺寸模式。\n- **返回值**：\n  - 类型：`CropperImage`\n  - 元素实例。\n\n将图像与其父元素的中心对齐。\n\n### $move\n\n- **语法**：\n  - `$move(x)`\n  - `$move(x, y)`\n- **参数**：\n  - `x`：\n    - 类型：`number`\n    - 水平方向的移动距离。\n  - `y`：\n    - 类型：`number`\n    - 默认值：`x`\n    - 垂直方向的移动距离。\n- **返回值**：\n  - 类型：`CropperImage`\n  - 元素实例。\n\n移动图像。\n\n### $moveTo\n\n- **语法**：\n  - `$moveTo(x)`\n  - `$moveTo(x, y)`\n- **参数**：\n  - `x`：\n    - 类型：`number`\n    - 水平方向的新位置。\n  - `y`：\n    - 类型：`number`\n    - 默认值：`x`\n    - 垂直方向的新位置。\n- **返回值**：\n  - 类型：`CropperImage`\n  - 元素实例。\n\n移动图像到指定位置。\n\n### $rotate\n\n- **语法**：`$rotate(angle)`\n- **参数**：\n  - `angle`：\n    - 类型：`number | string`\n    - 旋转角度（以弧度为单位）。默认单位是 `rad`。\n  - `x`：\n    - 类型：`number`\n    - 默认值：图像在水平方向的中心。\n    - 水平方向的旋转原点。\n  - `y`：\n    - 类型：`number`\n    - 默认值：图像在垂直方向的中心。\n    - 垂直方向的旋转原点。\n- **返回值**：\n  - 类型：`CropperImage`\n  - 元素实例。\n- **示例**：\n  - `$rotate(0.8)`\n  - `$rotate('0.8rad')`\n  - `$rotate('45deg')`\n  - `$rotate('50grad')`\n  - `$rotate('0.1turn')`\n  - `$rotate('90deg', 0, 0)`\n\n旋转图像。它类似于 CSS 函数 [rotate()](https://developer.mozilla.org/zh-CN/docs/Web/CSS/transform-function/rotate()) 或 [CanvasRenderingContext2D.rotate()](https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/rotate)。\n\n### $zoom\n\n- **语法**：\n  - `$zoom(scale)`\n  - `$zoom(scale, x, y)`\n- **参数**：\n  - `scale`：\n    - 类型：`number`\n    - 缩放系数。正数表示放大，负数表示缩小。\n  - `x`：\n    - 类型：`number`\n    - 默认值：图像在水平方向的中心。\n    - 水平方向的缩放原点。\n  - `y`：\n    - 类型：`number`\n    - 默认值：图像在垂直方向的中心。\n    - 垂直方向的缩放原点。\n- **返回值**：\n  - 类型：`CropperImage`\n  - 元素实例。\n- **示例**：\n\n  ```js\n  cropperImage.$zoom(0.1); // 放大 10%\n  cropperImage.$zoom(-0.1); // 缩小 10%\n  cropperImage.$zoom(0.1, 0, 0); // 以图片左上角为原点放大 10%\n  cropperImage.$zoom(-0.1, 0, 0); // 以图片左上角为原点缩小 10%\n  ```\n\n缩放图像。\n\n### $scale\n\n- **语法**：\n  - `$scale(x)`\n  - `$scale(x, y)`\n- **参数**：\n  - `x`：\n    - 类型：`number`\n    - 水平方向的缩放系数。\n  - `y`：\n    - 类型：`number`\n    - 默认值：`x`\n    - 垂直方向的缩放系数。\n- **返回值**：\n  - 类型：`CropperImage`\n  - 元素实例。\n- **示例**：\n\n  ```js\n  cropperImage.$scale(1.1); // 放大 10%\n  cropperImage.$scale(0.9); // 缩小 10%\n  cropperImage.$scale(-1); // 翻转水平和垂直方向\n  cropperImage.$scale(-1, 1); // 翻转水平方向\n  cropperImage.$scale(1, -1); // 翻转垂直方向\n  ```\n\n缩放图像。它类似于 CSS 函数 [scale()](https://developer.mozilla.org/zh-CN/docs/Web/CSS/transform-function/scale()) 或 [CanvasRenderingContext2D.scale()](https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/scale)。\n\n### $skew\n\n- **语法**：\n  - `$skew(x)`\n  - `$skew(x, y)`\n- **参数**：\n  - `x`：\n    - 类型：`number | string`\n    - 水平方向的倾斜角度。默认单位是 `rad`。\n  - `y`：\n    - 类型：`number | string`\n    - 默认值：`x`\n    - 垂直方向的倾斜角度。默认单位是 `rad`。\n- **返回值**：\n  - 类型：`CropperImage`\n  - 元素实例。\n- **示例**：\n  - `$skew(0.8)`\n  - `$skew('0.8rad')`\n  - `$skew('45deg')`\n  - `$skew('50grad')`\n  - `$skew('0.1turn')`\n  - `$skew(0, 0.8)`\n\n倾斜图像。它类似于 CSS 函数 [skew()](https://developer.mozilla.org/zh-CN/docs/Web/CSS/transform-function/skew())。\n\n### $translate\n\n- **语法**：\n  - `$translate(x)`\n  - `$translate(x, y)`\n- **参数**：\n  - `x`：\n    - 类型：`number`\n    - 水平方向的平移距离。\n  - `y`：\n    - 类型：`number`\n    - 默认值：`x`\n    - 垂直方向的平移距离。\n- **返回值**：\n  - 类型：`CropperImage`\n  - 元素实例。\n\n平移图像。它类似于 CSS 函数 [translate()](https://developer.mozilla.org/zh-CN/docs/Web/CSS/transform-function/translate()) 或 [CanvasRenderingContext2D.translate()](https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/translate)。\n\n### $transform\n\n- **语法**：`$transform(a, b, c, d, e, f)`\n- **参数**：\n  - `a`：\n    - 类型：`number`\n    - 水平方向的缩放系数。\n  - `b`：\n    - 类型：`number`\n    - 垂直方向的倾斜角度。\n  - `c`：\n    - 类型：`number`\n    - 水平方向的倾斜角度。\n  - `d`：\n    - 类型：`number`\n    - 垂直方向的缩放系数。\n  - `e`：\n    - 类型：`number`\n    - 水平方向的平移距离。\n  - `f`：\n    - 类型：`number`\n    - 垂直方向的平移距离。\n- **返回值**：\n  - 类型：`CropperImage`\n  - 元素实例。\n\n变换图像。它类似于 CSS 函数 [matrix()](https://developer.mozilla.org/zh-CN/docs/Web/CSS/transform-function/matrix()) 或 [CanvasRenderingContext2D.transform()](https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/transform)。\n\n### $setTransform\n\n- **语法**：\n  - `$setTransform(a, b, c, d, e, f)`\n  - `$setTransform(a)`\n- **参数**：\n  - `a`：\n    - 类型：`number | Array`\n    - 水平方向的缩放系数，或变换矩阵。\n  - `b`：\n    - 类型：`number`\n    - 垂直方向的倾斜角度。\n  - `c`：\n    - 类型：`number`\n    - 水平方向的倾斜角度。\n  - `d`：\n    - 类型：`number`\n    - 垂直方向的缩放系数。\n  - `e`：\n    - 类型：`number`\n    - 水平方向的平移距离。\n  - `f`：\n    - 类型：`number`\n    - 垂直方向的平移距离。\n- **返回值**：\n  - 类型：`CropperImage`\n  - 元素实例。\n\n将当前变换重置（覆盖）为指定的单位矩阵，然后调用由该方法的参数描述的变换。这使你可以缩放、旋转、平移（移动）和倾斜上下文。它类似于 [CanvasRenderingContext2D.setTransform()](https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/setTransform)。\n\n### $getTransform\n\n- **语法**：`$getTransform()`\n- **返回值**：\n  - 类型：`Array`\n  - 元素的当前变换矩阵。\n\n检索应用于元素的当前变换矩阵。它类似于 [CanvasRenderingContext2D.getTransform()](https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/getTransform)。\n\n### $resetTransform\n\n- **语法**：\n  - `$resetTransform()`\n- **等同于**：\n  - `$setTransform(1, 0, 0, 1, 0, 0)`\n  - `$setTransform([1, 0, 0, 1, 0, 0])`\n- **返回值**：\n  - 类型：`CropperImage`\n  - 元素实例。\n\n将当前变换重置为初始单位矩阵。它类似于 [CanvasRenderingContext2D.resetTransform()](https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/resetTransform)。\n\n## 事件\n\n### transform\n\n- 事件：\n  - **event.bubbles**：`true`\n  - **event.cancelable**：`true`\n  - **event.composed**：`true`\n  - **event.detail**：\n    - 类型：`Object`\n    - 图像的变换信息。\n  - **event.detail.matrix**：\n    - 类型：`Array`\n    - 新的（下一个）矩阵对象。\n  - **event.detail.oldMatrix**：\n    - 类型：`Array`\n    - 旧的（当前）矩阵对象。\n\n当元素的 [`transform`](https://developer.mozilla.org/zh-CN/docs/Web/CSS/transform) CSS 属性将要变更时，将触发该事件。\n\n## 插槽\n\n此元素中没有可用的插槽。\n\n> 你可以通过将 `slottable` 属性设置为 `true` 来启用默认插槽：\n>\n> ```html\n> <cropper-image slottable></cropper-image>\n> ```\n"
  },
  {
    "path": "docs/zh/api/cropper-selection.md",
    "content": "# CropperSelection\n\n`CropperSelection` 接口提供了用于操作 `<cropper-selection>` 元素的布局和表示的属性和方法。\n\n## 示例\n\n### 基本\n\n:::live-demo\n\n```html\n<cropper-selection></cropper-selection>\n```\n\n:::\n\n:::tip\n此元素的默认宽度和高度为 `0`。\n:::\n\n### 自定义初始覆盖范围\n\n:::live-demo\n\n```html\n<cropper-canvas background>\n  <cropper-selection initial-coverage=\"0.5\" outlined></cropper-selection>\n</cropper-canvas>\n```\n\n:::\n\n### 自定义位置和大小\n\n:::live-demo\n\n```html\n<cropper-canvas background>\n  <cropper-selection x=\"10\" y=\"5\" width=\"160\" height=\"90\" outlined></cropper-selection>\n</cropper-canvas>\n```\n\n:::\n\n### 带有手柄\n\n:::live-demo\n\n```html\n<cropper-canvas background>\n  <cropper-selection initial-coverage=\"0.5\" movable resizable zoomable outlined>\n    <cropper-handle action=\"move\" theme-color=\"rgba(255, 255, 255, 0.35)\"></cropper-handle>\n    <cropper-handle action=\"n-resize\"></cropper-handle>\n    <cropper-handle action=\"e-resize\"></cropper-handle>\n    <cropper-handle action=\"s-resize\"></cropper-handle>\n    <cropper-handle action=\"w-resize\"></cropper-handle>\n    <cropper-handle action=\"ne-resize\"></cropper-handle>\n    <cropper-handle action=\"nw-resize\"></cropper-handle>\n    <cropper-handle action=\"se-resize\"></cropper-handle>\n    <cropper-handle action=\"sw-resize\"></cropper-handle>\n  </cropper-selection>\n</cropper-canvas>\n```\n\n:::\n\n### 动态变化\n\n将 `dynamic` 属性设置为 `true`，以随着图像的变化而变化.\n\n:::live-demo\n\n```html\n<cropper-canvas style=\"height: 360px;\" background>\n  <cropper-image src=\"/cropperjs/picture.jpg\" alt=\"Picture\" rotatable scalable skewable translatable></cropper-image>\n  <cropper-shade hidden></cropper-shade>\n  <cropper-handle action=\"move\" plain></cropper-handle>\n  <cropper-selection initial-coverage=\"0.5\" dynamic movable resizable zoomable>\n    <cropper-grid role=\"grid\" covered></cropper-grid>\n    <cropper-crosshair centered></cropper-crosshair>\n    <cropper-handle action=\"move\" theme-color=\"rgba(255, 255, 255, 0.35)\"></cropper-handle>\n    <cropper-handle action=\"n-resize\"></cropper-handle>\n    <cropper-handle action=\"e-resize\"></cropper-handle>\n    <cropper-handle action=\"s-resize\"></cropper-handle>\n    <cropper-handle action=\"w-resize\"></cropper-handle>\n    <cropper-handle action=\"ne-resize\"></cropper-handle>\n    <cropper-handle action=\"nw-resize\"></cropper-handle>\n    <cropper-handle action=\"se-resize\"></cropper-handle>\n    <cropper-handle action=\"sw-resize\"></cropper-handle>\n  </cropper-selection>\n</cropper-canvas>\n```\n\n:::\n\n### 多选区\n\n将 `multiple` 属性设置为 `true`，以支持在同一图像上的创建多个选区。\n\n:::live-demo\n\n```html\n<cropper-canvas style=\"height: 360px;\" background>\n  <cropper-image src=\"/cropperjs/picture.jpg\" alt=\"Picture\" rotatable scalable skewable translatable></cropper-image>\n  <cropper-shade hidden></cropper-shade>\n  <cropper-handle action=\"select\" plain></cropper-handle>\n  <cropper-selection id=\"cropperSelection\" x=\"20\" y=\"20\" width=\"40\" height=\"40\" movable resizable multiple keyboard>\n    <cropper-grid role=\"grid\" covered></cropper-grid>\n    <cropper-crosshair centered></cropper-crosshair>\n    <cropper-handle action=\"move\" theme-color=\"rgba(255, 255, 255, 0.35)\"></cropper-handle>\n    <cropper-handle action=\"n-resize\"></cropper-handle>\n    <cropper-handle action=\"e-resize\"></cropper-handle>\n    <cropper-handle action=\"s-resize\"></cropper-handle>\n    <cropper-handle action=\"w-resize\"></cropper-handle>\n    <cropper-handle action=\"ne-resize\"></cropper-handle>\n    <cropper-handle action=\"nw-resize\"></cropper-handle>\n    <cropper-handle action=\"se-resize\"></cropper-handle>\n    <cropper-handle action=\"sw-resize\"></cropper-handle>\n  </cropper-selection>\n  <cropper-selection id=\"cropperSelection1\" x=\"60\" y=\"60\" width=\"80\" height=\"80\" movable resizable multiple keyboard>\n    <cropper-grid role=\"grid\" covered></cropper-grid>\n    <cropper-crosshair centered></cropper-crosshair>\n    <cropper-handle action=\"move\" theme-color=\"rgba(255, 255, 255, 0.35)\"></cropper-handle>\n    <cropper-handle action=\"n-resize\"></cropper-handle>\n    <cropper-handle action=\"e-resize\"></cropper-handle>\n    <cropper-handle action=\"s-resize\"></cropper-handle>\n    <cropper-handle action=\"w-resize\"></cropper-handle>\n    <cropper-handle action=\"ne-resize\"></cropper-handle>\n    <cropper-handle action=\"nw-resize\"></cropper-handle>\n    <cropper-handle action=\"se-resize\"></cropper-handle>\n    <cropper-handle action=\"sw-resize\"></cropper-handle>\n  </cropper-selection>\n  <cropper-selection id=\"cropperSelection2\" x=\"140\" y=\"140\" width=\"120\" height=\"120\" movable resizable multiple keyboard>\n    <cropper-grid role=\"grid\" covered></cropper-grid>\n    <cropper-crosshair centered></cropper-crosshair>\n    <cropper-handle action=\"move\" theme-color=\"rgba(255, 255, 255, 0.35)\"></cropper-handle>\n    <cropper-handle action=\"n-resize\"></cropper-handle>\n    <cropper-handle action=\"e-resize\"></cropper-handle>\n    <cropper-handle action=\"s-resize\"></cropper-handle>\n    <cropper-handle action=\"w-resize\"></cropper-handle>\n    <cropper-handle action=\"ne-resize\"></cropper-handle>\n    <cropper-handle action=\"nw-resize\"></cropper-handle>\n    <cropper-handle action=\"se-resize\"></cropper-handle>\n    <cropper-handle action=\"sw-resize\"></cropper-handle>\n  </cropper-selection>\n</cropper-canvas>\n```\n\n:::\n\n### 限制边界\n\n<ClientOnly>\n  <CropperSelectionExample />\n</ClientOnly>\n\n::: details\n<<< @/.vitepress/components/CropperSelectionExample.vue\n:::\n\n## 属性\n\n从其父级 [`CropperElement`](cropper-element.html) 继承属性，并实现以下属性：\n\n| 名称 | 类型 | 默认值 | 可选值 | 描述 |\n| --- | --- | --- | --- | --- |\n| x | `number` | `0` | - | 指示选区的 x 轴坐标。 |\n| y | `number` | `0` | - | 指示选区的 y 轴坐标。 |\n| width | `number` | `0` | - | 指示选区的宽度。 |\n| height | `number` | `0` | - | 指示选区的高度。 |\n| aspectRatio | `number` | `NaN` | - | 指示选区的纵横比，必须是正数。 |\n| initialAspectRatio | `number` | `NaN` | - | 指示选区的初始长宽比，必须一个正数。 |\n| initialCoverage | `number` | `NaN` | - | 指示选区的初始覆盖范围，必须是在 `0` （0％）和 `1` （100%）之间的正数。 |\n| dynamic | `boolean` | `false` | - | 指示此选区是否是动态的，以及是否随着图像的变化而变化。 |\n| movable | `boolean` | `false` | - | 指示此元素是否可移动。 |\n| resizable | `boolean` | `false` | - | 指示此元素是否可调整大小。 |\n| zoomable | `boolean` | `false` | - | 指示此元素是否可缩放。 |\n| multiple | `boolean` | `false` | - | 指示是否支持多选区。 |\n| keyboard | `boolean` | `false` | - | 指示是否支持键盘控制。 |\n| outlined | `boolean` | `false` | - | 指示是否显示轮廓线。 |\n| precise | `boolean` | `false` | - | 指示是否保留 `x`、`y`、`width` 和 `height` 属性的精确值。 |\n\n支持的键盘键：\n\n- `Delete` 或 `Command + Backspace`：删除活动选区。\n- `ArrowLeft`：将活动选区向左移动 1 个像素。\n- `ArrowRight`：将活动选区向右移动 1 个像素。\n- `ArrowUp`：将活动选区向上移动 1 个像素。\n- `ArrowDown`：将活动选区向下移动 1 个像素。\n- `+`：将活动选区放大 10%。\n- `-`：将活动选区缩小 10%。\n\n## 方法\n\n### $center\n\n- **语法**：`$center()`\n- **返回值**：\n  - 类型：`CropperSelection`\n  - 元素实例。\n\n将选区与其父元素的中心对齐。\n\n### $move\n\n- **语法**：\n  - `$move(x)`\n  - `$move(x, y)`\n- **等同于**：\n  - `$moveTo(selection.x + x)`\n  - `$moveTo(selection.x + x, selection.y + y)`\n- **参数**：\n  - `x`：\n    - 类型：`number`\n    - 水平方向的移动距离。\n  - `y`：\n    - 类型：`number`\n    - 默认值：`x`\n    - 垂直方向的移动距离。\n- **返回值**：\n  - 类型：`CropperSelection`\n  - 元素实例。\n\n移动选区。\n\n### $moveTo\n\n- **语法**：\n  - `$moveTo(x)`\n  - `$moveTo(x, y)`\n- **参数**：\n  - `x`：\n    - 类型：`number`\n    - 水平方向的新位置。\n  - `y`：\n    - 类型：`number`\n    - 默认值：`x`\n    - 垂直方向的新位置。\n- **返回值**：\n  - 类型：`CropperSelection`\n  - 元素实例。\n\n移动选区到指定位置。\n\n### $resize\n\n- **语法**：\n  - `$resize(action)`\n  - `$resize(action, offsetX)`\n  - `$resize(action, offsetX, offsetY)`\n  - `$resize(action, offsetX, offsetY, aspectRatio)`\n- **参数**：\n  - `action`：\n    - 类型：`string`\n    - 可选值：`\"n-resize\"`, `\"e-resize\"`, `\"s-resize\"`, `\"w-resize\"`, `\"ne-resize\"`, `\"nw-resize\"`, `\"se-resize\"`, and `\"sw-resize\"`.\n    - 指示要调整大小的边或角。\n  - `offsetX`：\n    - 类型：`number`\n    - 默认值：`0`\n    - 指定边或角的水平偏移。\n  - `offsetY`：\n    - 类型：`number`\n    - 默认值：`0`\n    - 指定边或角的垂直偏移。\n  - `aspectRatio`：\n    - 类型：`number`\n    - 默认值：`this.aspectRatio`\n    - 必要时计算新尺寸的纵横比。\n- **返回值**：\n  - 类型：`CropperSelection`\n  - 元素实例。\n\n调整指定边或角上选区的大小。\n\n### $zoom\n\n- **语法**：\n  - `$zoom(scale)`\n  - `$zoom(scale, x, y)`\n- **参数**：\n  - `scale`：\n    - 类型：`number`\n    - 缩放系数。正数表示放大，负数表示缩小。\n  - `x`：\n    - 类型：`number`\n    - 默认值：选区在水平方向上的中心。\n    - 水平方向的缩放原点。\n  - `y`：\n    - 类型：`number`\n    - 默认值：选区在垂直方向上的中心。\n    - 垂直方向的缩放原点。\n- **返回值**：\n  - 类型：`CropperSelection`\n  - 元素实例。\n- **示例**：\n\n  ```js\n  cropperSelection.$zoom(0.1); // 放大 10%\n  cropperSelection.$zoom(-0.1); // 缩小 10%\n  ```\n\n缩放选区。同时直接变更选区的宽度和高度（以像素为单位）。\n\n### $change\n\n- **语法**：\n  - `$change(x, y)`\n  - `$change(x, y, width, height)`\n  - `$change(x, y, width, height, aspectRatio)`\n- **参数**：\n  - `x`：\n    - 类型：`number`\n    - 水平方向的新位置。\n  - `y`：\n    - 类型：`number`\n    - 垂直方向的新位置。\n  - `width`：\n    - 类型：`number`\n    - 默认值：`this.width`\n    - 新宽度。\n  - `height`：\n    - 类型：`number`\n    - 默认值：`this.height`\n    - 新高度。\n  - `aspectRatio`：\n    - 类型：`number`\n    - 默认值：`this.aspectRatio`\n    - 仅限当前变更的新纵横比。\n- **返回值**：\n  - 类型：`CropperSelection`\n  - 元素实例。\n\n变更选区的位置和/或大小。\n\n### $reset\n\n- **语法**：`$reset()`\n- **返回值**：\n  - 类型：`CropperSelection`\n  - 元素实例。\n\n将选区重置为其初始位置和大小。\n\n### $clear\n\n- **语法**：`$clear()`\n- **返回值**：\n  - 类型：`CropperSelection`\n  - 元素实例。\n\n清空选区。\n\n### $render\n\n- **语法**：`$render()`\n- **返回值**：\n  - 类型：`CropperSelection`\n  - 元素实例。\n\n刷新选区的位置或大小。\n\n### $toCanvas\n\n- **语法**：\n  - `$toCanvas()`\n  - `$toCanvas(options)`\n- **参数**：\n  - `options`：\n    - 类型：`Object`\n    - 可用选项。\n    - 属性：\n      - `width`：\n        - 类型：`number`\n        - 画布的宽度。\n      - `height`：\n        - 类型：`number`\n        - 画布的高度。\n      - `beforeDraw`：\n        - 类型：`Function`\n        - 在将图像绘制到画布上之前调用的函数。\n        - 语法：`beforeDraw(context, canvas)`\n        - 参数：\n          - `context`：\n            - 类型：`CanvasRenderingContext2D`\n            - 画布的 2D 渲染上下文。\n          - `canvas`：\n            - 类型：`HTMLCanvasElement`\n            - 画布元素本身。\n        - 示例：`function (context) { context.filter = 'grayscale(100%)'; }`\n- **返回值**：\n  - 类型：`Promise`\n  - 一个以生成的画布元素为给定值解析后的 Promise 对象。\n- **Example**：\n  <ClientOnly>\n    <CropperSelectionToNativeCanvas />\n  </ClientOnly>\n\n生成一个真实的画布元素，如果有图像（仅限选定区域），则将其绘制到其中。\n\n## 事件\n\n### change\n\n当选区的位置和尺寸即将发送变化时触发该事件。\n\n- 事件：\n  - **event.bubbles**：`true`\n  - **event.cancelable**：`true`\n  - **event.composed**：`true`\n  - **event.detail**：\n    - 类型：`Object`\n    - 选区的位置和大小数据。\n  - **event.detail.x**：\n    - 类型：`number`\n    - 选区的 x 轴坐标。\n  - **event.detail.y**：\n    - 类型：`number`\n    - 选区的 y 轴坐标。\n  - **event.detail.width**：\n    - 类型：`number`\n    - 选区的宽度。\n  - **event.detail.height**：\n    - 类型：`number`\n    - 选区的高度。\n- 示例：\n\n```html\n<cropper-selection id=\"selection\"></cropper-selection>\n\n<script>\ndocument.querySelector('#selection').addEventListener('change', function (event) {\n  console.log(event);\n});\n</script>\n```\n\n## 插槽\n\n此元素中只有一个默认插槽。\n\n> 你可以通过将 `slottable` 属性设置为 `false` 来禁用它：\n>\n> ```html\n> <cropper-selection slottable=\"false\"></cropper-selection>\n> ```\n"
  },
  {
    "path": "docs/zh/api/cropper-shade.md",
    "content": "# CropperShade\n\n`CropperShade` 接口提供了用于操作 `<cropper-shade>` 元素的布局和表示的属性和方法。\n\n## 示例\n\n### 基本\n\n:::live-demo\n\n```html\n<cropper-shade></cropper-shade>\n```\n\n:::\n\n:::tip\n此元素的默认宽度和高度为 `0`。\n:::\n\n### 指定位置和大小\n\n:::live-demo\n\n```html\n<cropper-canvas background>\n  <cropper-shade x=\"240\" y=\"5\" width=\"160\" height=\"90\"></cropper-shade>\n</cropper-canvas>\n```\n\n:::\n\n### 自定义颜色\n\n:::live-demo\n\n```html\n<cropper-canvas background>\n  <cropper-shade x=\"240\" y=\"5\" width=\"160\" height=\"90\" theme-color=\"rgba(0, 0, 0, 0.35)\"></cropper-shade>\n</cropper-canvas>\n```\n\n:::\n\n### 当指针按下/松开时动态切换可见性\n\n:::live-demo\n\n```html\n<cropper-canvas background>\n  <cropper-image src=\"/cropperjs/picture.jpg\" alt=\"Picture\" rotatable scalable skewable translatable></cropper-image>\n  <cropper-shade hidden></cropper-shade>\n  <cropper-handle action=\"select\" plain></cropper-handle>\n  <cropper-selection movable resizable hidden>\n    <cropper-handle action=\"move\" plain></cropper-handle>\n  </cropper-selection>\n</cropper-canvas>\n```\n\n:::\n\n:::tip\n`<cropper-shade>` 元素将自动同步当前活动的 `<cropper-selection>` 元素的位置和大小。\n:::\n\n:::tip\n[`hidden`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Global_attributes/hidden) 属性是原生全局属性。\n:::\n\n## 属性\n\n从其父级 [`CropperElement`](cropper-element.html) 继承属性，并实现以下属性：\n\n| 名称 | 类型 | 默认值 | 可选值 | 描述 |\n| --- | --- | --- | --- | --- |\n| x | `number` | `0` | - | 指示元素的 x 轴坐标。 |\n| y | `number` | `0` | - | 指示元素的 y 轴坐标。 |\n| width | `number` | `0` | - | 指示元素的宽度。 |\n| height | `number` | `0` | - | 指示元素的高度。 |\n| slottable | `boolean` | `false` | - | 指示此元素是否启用默认插槽。 |\n| themeColor | `string` | `\"rgba(0, 0, 0, 0.65)\"` | - | 指示此元素的颜色。 |\n\n## 方法\n\n### $change\n\n- **语法**：\n  - `$change(x, y)`\n  - `$change(x, y, width, height)`\n- **参数**：\n  - `x`：\n    - 类型：`number`\n    - 水平方向的新位置。\n  - `y`：\n    - 类型：`number`\n    - 垂直方向的新位置。\n  - `width`：\n    - 类型：`number`\n    - 默认值：`this.width`\n    - 新宽度。\n  - `height`：\n    - 类型：`number`\n    - 默认值：`this.height`\n    - 新高度。\n- **返回值**：\n  - 类型：`CropperShade`\n  - 用于链接的元素实例。\n\n变更阴影的位置和/或大小。\n\n### $reset\n\n- **语法**：`$reset()`\n- **返回值**：\n  - 类型：`CropperShade`\n  - 元素实例。\n\n将阴影重置为其初始位置和大小。\n\n### $render\n\n- **语法**：`$render()`\n- **返回值**：\n  - 类型：`CropperShade`\n  - 元素实例。\n\n刷新阴影的位置或大小。\n\n## 插槽\n\n此元素中只有一个默认插槽。\n\n> 你可以通过将 `slottable` 属性设置为 `false` 来禁用它：\n>\n> ```html\n> <cropper-shade slottable=\"false\"></cropper-shade>\n> ```\n"
  },
  {
    "path": "docs/zh/api/cropper-viewer.md",
    "content": "# CropperViewer\n\n`CropperViewer` 接口提供了用于操作 `<cropper-viewer>` 元素的布局和表示的属性和方法。\n\n## 示例\n\n### 基本\n\n:::live-demo\n\n```html\n<cropper-viewer></cropper-viewer>\n```\n\n:::\n\n:::tip\n此元素的默认高度为 `0`。\n:::\n\n### 连接到 CropperSelection\n\n:::live-demo\n\n```html\n<cropper-canvas style=\"height: 240px;\" background>\n  <cropper-image src=\"/cropperjs/picture.jpg\" alt=\"Picture\" rotatable scalable skewable translatable></cropper-image>\n  <cropper-shade hidden></cropper-shade>\n  <cropper-handle action=\"select\" plain></cropper-handle>\n  <cropper-selection id=\"cropperSelection\" initial-aspect-ratio=\"1.5\" initial-coverage=\"0.5\" movable resizable>\n    <cropper-handle action=\"move\" theme-color=\"rgba(255, 255, 255, 0.35)\"></cropper-handle>\n    <cropper-handle action=\"n-resize\"></cropper-handle>\n    <cropper-handle action=\"e-resize\"></cropper-handle>\n    <cropper-handle action=\"s-resize\"></cropper-handle>\n    <cropper-handle action=\"w-resize\"></cropper-handle>\n    <cropper-handle action=\"ne-resize\"></cropper-handle>\n    <cropper-handle action=\"nw-resize\"></cropper-handle>\n    <cropper-handle action=\"se-resize\"></cropper-handle>\n    <cropper-handle action=\"sw-resize\"></cropper-handle>\n  </cropper-selection>\n</cropper-canvas>\n\n<div class=\"cropper-viewers\">\n  <cropper-viewer selection=\"#cropperSelection\" style=\"width: 320px;\"></cropper-viewer>\n  <cropper-viewer selection=\"#cropperSelection\" style=\"width: 160px;\"></cropper-viewer>\n  <cropper-viewer selection=\"#cropperSelection\" style=\"width: 80px;\"></cropper-viewer>\n  <cropper-viewer selection=\"#cropperSelection\" style=\"width: 40px;\"></cropper-viewer>\n</div>\n\n<style>\n.cropper-viewers {\n  margin-top: 0.5rem;\n}\n\n.cropper-viewers > cropper-viewer {\n  border: 1px solid var(--vp-c-divider);\n  display: inline-block;\n  margin-right: 0.25rem;\n}\n</style>\n```\n\n:::\n\n## 属性\n\n从其父级 [`CropperElement`](cropper-element.html) 继承属性，并实现以下属性：\n\n| 名称 | 类型 | 默认值 | 可选值 | 描述 |\n| --- | --- | --- | --- | --- |\n| resize | `string` | `\"vertical\"` | `\"both\"`, `\"horizontal\"`, `\"vertical\"`, `\"none\"` | 指示此元素是否可调整大小，如果是，则在哪个方向。 |\n| selection | `string` | `\"\"` | - | 指示要查看的源选区。它必须是 `document.querySelector` 的有效选择器。 |\n| slottable | `boolean` | `false` | - | 指示此元素是否启用默认插槽。 |\n\n## 插槽\n\n此元素中没有可用的插槽。\n\n> 你可以通过将 `slottable` 属性设置为 `true` 来启用默认插槽：\n>\n> ```html\n> <cropper-viewer slottable></cropper-viewer>\n> ```\n"
  },
  {
    "path": "docs/zh/api/index.md",
    "content": "# Cropper\n\n`Cropper` 构造函数用于创建一个新的 Cropper 实例。\n\n## 使用\n\n### 语法\n\n```js\nnew Cropper(element[, options])\n```\n\n- **element**\n  - 类型：`HTMLImageElement | HTMLCanvasElement | string`\n  - 用于裁剪的目标图像或画布元素。如果是字符串，将传入 [`document.querySelector`](https://developer.mozilla.org/zh-CN/docs/Web/API/Document/querySelector) 中去查找元素。\n\n- **options** (可选)\n  - 类型：`Object`\n  - 用于裁剪的[选项](#options)。\n\n### 示例\n\n<ClientOnly>\n  <CropperExample />\n</ClientOnly>\n\n::: details\n<<< @/.vitepress/components/CropperExample.vue\n:::\n\n## 选项\n\n| 名称 | 类型 | 默认值 | 描述 |\n| --- | --- | --- | --- |\n| container | `Element \\| string` | 默认为目标元素的父元素，如果父元素为空，则为 `document.body`。 | Cropper 容器。如果是字符串，将传入 `document.querySelector` 中去查找元素。 |\n| template | `string` | 默认为内置模板，见下文。 | Cropper 模板。 |\n\nCropper 的默认模板：\n\n```html\n<cropper-canvas background>\n  <cropper-image></cropper-image>\n  <cropper-shade hidden></cropper-shade>\n  <cropper-handle action=\"select\" plain></cropper-handle>\n  <cropper-selection initial-coverage=\"0.5\" movable resizable>\n    <cropper-grid role=\"grid\" bordered covered></cropper-grid>\n    <cropper-crosshair centered></cropper-crosshair>\n    <cropper-handle action=\"move\" theme-color=\"rgba(255, 255, 255, 0.35)\"></cropper-handle>\n    <cropper-handle action=\"n-resize\"></cropper-handle>\n    <cropper-handle action=\"e-resize\"></cropper-handle>\n    <cropper-handle action=\"s-resize\"></cropper-handle>\n    <cropper-handle action=\"w-resize\"></cropper-handle>\n    <cropper-handle action=\"ne-resize\"></cropper-handle>\n    <cropper-handle action=\"nw-resize\"></cropper-handle>\n    <cropper-handle action=\"se-resize\"></cropper-handle>\n    <cropper-handle action=\"sw-resize\"></cropper-handle>\n  </cropper-selection>\n</cropper-canvas>\n```\n\n## 实例属性\n\n| 名称 | 类型 | 描述 |\n| --- | --- | --- |\n| element | `HTMLImageElement \\| HTMLCanvasElement` | 标准化的 Cropper 元素。 |\n| options | `Object` | 标准化的 Cropper 选项。 |\n| container | `Element` | 标准化的 Cropper 容器。 |\n\n## 实例方法\n\n### getCropperCanvas\n\n- **语法**：`getCropperCanvas()`\n- **等同于**：`cropper.container.querySelector('cropper-canvas')`\n- **返回值**：\n  - 类型：`CropperCanvas | null`\n  - `<cropper-canvas>` 元素（如果有）。\n\n获取 Cropper 容器中的 `<cropper-canvas>` 元素。\n\n### getCropperImage\n\n- **语法**：`getCropperImage()`\n- **等同于**：`cropper.container.querySelector('cropper-image')`\n- **返回值**：\n  - 类型：`CropperImage | null`\n  - `<cropper-image>` 元素（如果有）。\n\n获取 Cropper 容器中的 `<cropper-image>` 元素。\n\n### getCropperSelection\n\n- **语法**：`getCropperSelection()`\n- **等同于**：`cropper.container.querySelector('cropper-selection')`\n- **返回值**：\n  - 类型：`CropperSelection | null`\n  - `<cropper-selection>` 元素（如果有）。\n\n获取 Cropper 容器中的 `<cropper-selection>` 元素。\n\n### getCropperSelections\n\n- **语法**：`getCropperSelections()`\n- **等同于**：`cropper.container.querySelectorAll('cropper-selection')`\n- **返回值**：\n  - 类型：`NodeListOf<CropperSelection> | null`\n  - `<cropper-selection>` 元素（如果有）。\n\n当有多个选择时，获取 Cropper 容器中的所有 `<cropper-selection>` 元素。\n\n### destroy <Badge type=\"tip\" text=\"^2.1.0\" />\n\n- **语法**: `destroy()`\n\n注销 Cropper 实例。\n\n## 导出的模块\n\n```js\nimport {\n  // 常量\n  DEFAULT_TEMPLATE,\n\n  // 元素\n  CropperElement,\n  CropperCanvas,\n  CropperImage,\n  CropperShade,\n  CropperHandle,\n  CropperSelection,\n  CropperGrid,\n  CropperCrosshair,\n  CropperViewer,\n} from 'cropperjs';\n```\n"
  },
  {
    "path": "docs/zh/cropper-playground.md",
    "content": "---\ntitle: Cropper 演练场\nlayout: false\n---\n\n<ClientOnly>\n  <CropperPlayground />\n</ClientOnly>\n"
  },
  {
    "path": "docs/zh/guide.md",
    "content": "---\nsidebar: auto\n---\n\n# 指南\n\n## 介绍\n\nCropper.js 2.0 是一系列用于图像裁剪的 Web 组件。\n\n- **Cropper.js 1.0 和 Cropper.js 2.0 有什么区别？**\n\n| 类型 | Cropper.js 1.0 | Cropper.js 2.0 |\n| --- | --- | --- |\n| 创建于 | 2015 | 2021 |\n| 状态 | 维护中 | 活跃中 |\n| 包数量 | 1 | 12+ |\n| 架构 | 一体化 | 模块化 |\n| 浏览器兼容性 | 现代浏览器 / IE 9+ | 现代浏览器 |\n| 可扩展 | 否 | 是 |\n| 可定制 | 否 | 是 |\n| CSS-in-JS | 否 | 是 |\n| 按需引用 | 否 | 是 |\n| 多选区 | 否 | 是 |\n| 触摸旋转图片 | 否 | 是 |\n\n- **Cropper、Cropper.js 和 jQuery Cropper 之间有什么区别？**\n\n| GitHub 项目 | npm 包 | 依赖 | 创建于 | 状态 | 描述 |\n| --- | --- | --- | --- | --- | --- |\n| [Cropper](https://github.com/fengyuanchen/cropper) | [`cropper`](https://www.npmjs.com/package/cropper) | [`jquery`](https://www.npmjs.com/package/jquery) | 2014 | 已废弃 | 一个简单的 jQuery 图像裁剪插件。 |\n| [Cropper.js](https://github.com/fengyuanchen/cropperjs) | [`cropperjs`](https://www.npmjs.com/package/cropperjs) | - | 2015 | 活跃中 | JavaScript 图片裁剪器。 |\n| [jQuery Cropper](https://github.com/fengyuanchen/jquery-cropper) | [`jquery-cropper`](https://www.npmjs.com/package/jquery-cropper) | [`jquery`](https://www.npmjs.com/package/jquery) + [`cropperjs@1`](https://www.npmjs.com/package/cropperjs) | 2018 | 维护中 | Cropper.js 1.0 的 jQuery 插件包装器。 |\n\n## 入门\n\n### 安装\n\n#### npm\n\n在使用 Cropper.js 构建大型应用程序时，推荐使用 npm 安装方法。\n\n```sh\nnpm install cropperjs\n```\n\n对于特定的包：\n\n```sh\nnpm install @cropper/element-canvas\n```\n\n#### CDN\n\n出于原型设计或学习目的，你可以使用最新版本：\n\n```html\n<script src=\"https://unpkg.com/cropperjs\"></script>\n```\n\n对于生产，我们建议链接到特定版本号和文件名以避免新版本意外损坏。\n\n### 使用\n\n#### 在 JavaScript 中使用\n\n导入 Cropper 类并构造一个新的 Cropper 实例。\n\n```js\nimport Cropper from 'cropperjs';\n\nconst cropper = new Cropper('#image');\n```\n\n```html\n<img id=\"image\" src=\"/cropperjs/picture.jpg\" alt=\"Picture\">\n```\n\n#### 在 DOM 中使用\n\n从 `cropperjs` 包中导入所有 Cropper 元素，并自动将它们[定义](https://developer.mozilla.org/zh-CN/docs/Web/API/CustomElementRegistry/define)为自定义元素。\n\n```js\nimport 'cropperjs';\n```\n\n:::live-demo\n\n```html\n<cropper-canvas background>\n  <cropper-image src=\"/cropperjs/picture.jpg\" alt=\"Picture\" rotatable scalable skewable translatable></cropper-image>\n  <cropper-shade hidden></cropper-shade>\n  <cropper-handle action=\"select\" plain></cropper-handle>\n  <cropper-selection initial-coverage=\"0.5\" movable resizable>\n    <cropper-grid role=\"grid\" covered></cropper-grid>\n    <cropper-crosshair centered></cropper-crosshair>\n    <cropper-handle action=\"move\" theme-color=\"rgba(255, 255, 255, 0.35)\"></cropper-handle>\n    <cropper-handle action=\"n-resize\"></cropper-handle>\n    <cropper-handle action=\"e-resize\"></cropper-handle>\n    <cropper-handle action=\"s-resize\"></cropper-handle>\n    <cropper-handle action=\"w-resize\"></cropper-handle>\n    <cropper-handle action=\"ne-resize\"></cropper-handle>\n    <cropper-handle action=\"nw-resize\"></cropper-handle>\n    <cropper-handle action=\"se-resize\"></cropper-handle>\n    <cropper-handle action=\"sw-resize\"></cropper-handle>\n  </cropper-selection>\n</cropper-canvas>\n```\n\n:::\n\n#### 按需引用\n\n仅导入所需的 Cropper 元素，然后手动将它们[定义](https://developer.mozilla.org/zh-CN/docs/Web/API/CustomElementRegistry/define)为自定义元素。\n\n```js\nimport CropperCanvas from '@cropper/element-canvas';\nimport CropperImage from '@cropper/element-image';\nimport CropperHandle from '@cropper/element-handle';\n\nCropperCanvas.$define();\nCropperImage.$define();\nCropperHandle.$define();\n```\n\n:::live-demo\n\n```html\n<cropper-canvas background>\n  <cropper-image src=\"/cropperjs/picture.jpg\" alt=\"Picture\" rotatable scalable skewable translatable></cropper-image>\n  <cropper-handle action=\"move\" plain></cropper-handle>\n</cropper-canvas>\n```\n\n:::\n\n## 安装包\n\nCropper.js 包含一系列 [npm](https://www.npmjs.com/) 包：\n\n| 安装包名称 | 版本 | 描述 |\n| --- | --- | --- |\n| `cropperjs` | [![Version](https://img.shields.io/npm/v/cropperjs)](https://www.npmjs.com/package/cropperjs/v) | 完整包。 |\n| `@cropper/element` | [![Version](https://img.shields.io/npm/v/@cropper/element)](https://www.npmjs.com/package/@cropper/element) | 用于构造 Cropper 元素的抽象类。 |\n| `@cropper/element-canvas` | [![Version](https://img.shields.io/npm/v/@cropper/element-canvas)](https://www.npmjs.com/package/@cropper/element-canvas) | Cropper 的自定义画布元素。 |\n| `@cropper/element-image` | [![Version](https://img.shields.io/npm/v/@cropper/element-image)](https://www.npmjs.com/package/@cropper/element-image) | Cropper 的自定义图像元素。 |\n| `@cropper/element-shade` | [![Version](https://img.shields.io/npm/v/@cropper/element-shade)](https://www.npmjs.com/package/@cropper/element-shade) | Cropper 的自定义阴影元素。 |\n| `@cropper/element-handle` | [![Version](https://img.shields.io/npm/v/@cropper/element-handle)](https://www.npmjs.com/package/@cropper/element-handle) | Cropper 的自定义手柄元素。 |\n| `@cropper/element-selection` | [![Version](https://img.shields.io/npm/v/@cropper/element-selection)](https://www.npmjs.com/package/@cropper/element-selection) | Cropper 的自定义选区元素。 |\n| `@cropper/element-grid` | [![Version](https://img.shields.io/npm/v/@cropper/element-grid)](https://www.npmjs.com/package/@cropper/element-grid) | Cropper 的自定义网格元素。 |\n| `@cropper/element-crosshair` | [![Version](https://img.shields.io/npm/v/@cropper/element-crosshair)](https://www.npmjs.com/package/@cropper/element-crosshair) | Cropper 的自定义十字准线元素。 |\n| `@cropper/element-viewer` | [![Version](https://img.shields.io/npm/v/@cropper/element-viewer)](https://www.npmjs.com/package/@cropper/element-viewer) | Cropper 的自定义查看器元素。 |\n| `@cropper/elements` | [![Version](https://img.shields.io/npm/v/@cropper/elements)](https://www.npmjs.com/package/@cropper/elements) | Cropper 的一系列自定义元素。 |\n| `@cropper/utils` | [![Version](https://img.shields.io/npm/v/@cropper/utils)](https://www.npmjs.com/package/@cropper/utils) | Cropper 的一系列常用常量和实用函数。 |\n\n## 接口\n\n| 接口名称 | 继承自 | 描述 |\n| --- | --- | --- |\n| `Cropper` | `Function` | `Cropper` 构造函数用于创建一个新的 Cropper 实例。 |\n| `CropperElement` | `HTMLElement` | `CropperElement` 接口代表任何 Cropper 元素，扩展了 [HTMLElement](https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLElement) 接口。 |\n| `CropperCanvas` | `CropperElement` | `CropperCanvas` 接口提供了用于操作 `<cropper-canvas>` 元素的布局和表示的属性和方法。 |\n| `CropperImage` | `CropperElement` | `CropperImage` 接口提供了用于操作 `<cropper-image>` 元素的布局和表示的属性和方法。 |\n| `CropperShade` | `CropperElement` | `CropperShade` 接口提供了用于操作 `<cropper-shade>` 元素的布局和表示的属性和方法。 |\n| `CropperHandle` | `CropperElement` | `CropperHandle` 接口提供了用于操作 `<cropper-handle>` 元素的布局和表示的属性和方法。 |\n| `CropperSelection` | `CropperElement` | `CropperSelection` 接口提供了用于操作 `<cropper-selection>` 元素的布局和表示的属性和方法。 |\n| `CropperGrid` | `CropperElement` | `CropperGrid` 接口提供了用于操作 `<cropper-grid>` 元素的布局和表示的属性和方法。 |\n| `CropperCrosshair` | `CropperElement` | `CropperCrosshair` 接口提供了用于操作 `<cropper-crosshair>` 元素的布局和表示的属性和方法。 |\n| `CropperViewer` | `CropperElement` | `CropperViewer` 接口提供了用于操作 `<cropper-viewer>` 元素的布局和表示的属性和方法。 |\n\n## 浏览器支持\n\n- Edge 79+\n- Firefox 63+\n- Chrome 54+\n- Safari 10.1+\n- Opera 41+\n- iOS Safari 10.3+\n- Android Browser 81+\n- Opera Mobile 46+\n- Chrome for Android 81+\n- Firefox for Android 68+\n- Samsung Internet 6.2+\n"
  },
  {
    "path": "docs/zh/index.md",
    "content": "---\nlayout: home\ntitle: 首页\nhero:\n  name: Cropper.js\n  text: JavaScript 图片裁剪器。\n  image:\n    src: /logo.svg\n    alt: Cropper.js\n  actions:\n    - theme: brand\n      text: 入门\n      link: /guide.html\n    - theme: alt\n      text: 演练场\n      link: /playground.html\nfeatures:\n  - title: 可定制\n    details: 轻松定制你自己的裁剪器。\n  - title: 可扩展\n    details: 如需要更多功能，可扩展裁剪器。\n  - title: 按需引用\n    details: 使用你需要的部分，让其他部分随风而去。\n---\n"
  },
  {
    "path": "docs/zh/migration.md",
    "content": "---\nsidebar: auto\n---\n\n# 迁移\n\n## 选项\n\n| Cropper.js 1.0 | Cropper.js 2.0 |\n| --- | --- |\n| `viewMode` | 改用 `<cropper-image>` 元素的 `transform` 事件去[限制图片边界](api/cropper-image.html#限制边界), 或者改用 `<cropper-selection>` 元素的  `change` 事件去[限制选区边界](api/cropper-selection.html#限制边界). |\n| `dragMode` | 改用 `<cropper-handle>` 元素的 `action` 属性。 |\n| `initialAspectRatio` | 改用 `<cropper-selection>` 元素的 `initialAspectRatio` 属性。 |\n| `aspectRatio` | 改用 `<cropper-selection>` 元素的 `aspectRatio` 属性。 |\n| `data` | 改用 `<cropper-image>` 元素的 `$setTransform` 方法，以及 `<cropper-selection>` 元素的 `x`、`y`、`width`、`height` 属性。 |\n| `preview` | 改用 `<cropper-viewer>` 元素。 |\n| `responsive` | 已废弃。 |\n| `restore` | 已废弃。 |\n| `checkCrossOrigin` | 已废弃。 |\n| `checkOrientation` | 出于性能原因已废弃。作为替代方案，建议使用第三方库，例如 [JavaScript-Load-Image](https://github.com/blueimp/JavaScript-Load-Image)。 |\n| `modal` | 改用 `<cropper-shade>` 元素。 |\n| `guides` | 改用 `<cropper-grid>` 元素。 |\n| `center` | 改用 `<cropper-crosshair>` 元素。 |\n| `highlight` | 改用 `<cropper-action>` 元素。 |\n| `background` | 改用 `<cropper-canvas>` 元素的 `background` 属性。 |\n| `autoCrop` | 改用 `<cropper-selection>` 元素的 `initialCoverage` 属性。 |\n| `autoCropArea` | 改用 `<cropper-selection>` 元素的 `initialCoverage` 属性。 |\n| `movable` | 改用 `<cropper-image>` 元素的 `translatable` 属性。 |\n| `rotatable` | 改用 `<cropper-image>` 元素的 `rotatable` 属性。 |\n| `scalable` | 改用 `<cropper-image>` 元素的 `scalable` 属性。 |\n| `zoomable` | 已废弃。 |\n| `zoomOnTouch` | 已废弃。 |\n| `zoomOnWheel` | 已废弃。 |\n| `wheelZoomRatio` | 改用 `<cropper-canvas>` 元素的 `scaleStep` 属性。 |\n| `cropBoxMovable` | 改用 `<cropper-selection>` 元素的 `movable` 属性。 |\n| `cropBoxResizable` | 改用 `<cropper-selection>` 元素的 `resizable` 属性。 |\n| `toggleDragModeOnDblclick` | 改用 `<cropper-handle>` 元素的 `dblclick` 事件去[双击切换动作类型](api/cropper-handle.html#toggle-action-on-dblclick)。 |\n| `minContainerWidth` | 已废弃。 |\n| `minContainerHeight` | 已废弃。 |\n| `minCanvasWidth` | 改用 `<cropper-canvas>` 元素的 `min-width` CSS 属性。 |\n| `minCanvasHeight` | 改用 `<cropper-canvas>` 元素的 `min-height` CSS 属性。 |\n| `minCropBoxWidth` | 改用 `<cropper-selection>` 元素的 `min-width` CSS 属性。 |\n| `minCropBoxHeight` | 改用 `<cropper-selection>` 元素的 `min-height` CSS 属性。 |\n| `ready` | 改用 `<cropper-image>` 元素的  `$ready` 方法。 |\n| `cropstart` | 改用 `<cropper-canvas>` 元素的 `actionstart` 事件。 |\n| `cropmove` | 改用 `<cropper-canvas>` 元素的 `actionmove` 事件。 |\n| `cropend` | 改用 `<cropper-canvas>` 元素的 `actionend` 事件。 |\n| `crop` | 改用 `<cropper-canvas>` 元素的 `action` 事件。 |\n| `zoom` | 改用 `<cropper-canvas>` 元素的 `action` 事件。 |\n\n## 方法\n\n| Cropper.js 1.0 | Cropper.js 2.0 |\n| --- | --- |\n| `crop` | 改用 `<cropper-selection>` 元素的 `$change` 方法。 |\n| `reset` | 改用 `<cropper-image>` 元素的 `$resetTransform` 方法，以及 `<cropper-selection>` 元素的 `$reset` 方法。 |\n| `clear` | 改用 `<cropper-selection>` 元素的 `$reset` 方法和 `hidden` 属性。 |\n| `replace` | 改用 `<cropper-image>` 元素的 `src` 属性。 |\n| `enable` | 改用 `<cropper-canvas>` 元素的 `disabled` 属性。 |\n| `disable` | 改用 `<cropper-canvas>` 元素的 `disabled` 属性。 |\n| `destroy` | 已废弃。直接从 DOM 中删除所有 Cropper 元素。 |\n| `move` | 改用 `<cropper-image>` 元素的 `$move` 方法。 |\n| `moveTo` | 改用 `<cropper-image>` 元素的 `$moveTo` 方法。 |\n| `zoom` | 改用 `<cropper-image>` 元素的 `$scale` 方法。 |\n| `zoomTo` | 改用 `<cropper-image>` 元素的 `$setTransform` 方法。 |\n| `rotate` | 改用 `<cropper-image>` 元素的 `$rotate` 方法。 |\n| `rotateTo` | 改用 `<cropper-image>` 元素的 `$setTransform` 方法。 |\n| `scale` | 改用 `<cropper-image>` 元素的 `$scale` 方法。 |\n| `scaleX` | 改用 `<cropper-image>` 元素的 `$scale` 方法。 |\n| `scaleY` | 改用 `<cropper-image>` 元素的 `$scale` 方法。 |\n| `getData` | 改用 `<cropper-image>` 元素的 `$getTransform` 方法，以及 `<cropper-selection>` 元素的 `x`、`y`、`width`、`height` 属性。 |\n| `setData` | 改用 `<cropper-image>` 元素的 `$setTransform` 方法，以及 `<cropper-selection>` 元素的 `x`、`y`、`width`、`height` 属性。 |\n| `getContainerData` | 已废弃。 |\n| `getImageData` | 改用 `<cropper-image>` 元素的 `$getTransform` 方法。 |\n| `getCanvasData` | 已废弃。 |\n| `setCanvasData` | 已废弃。 |\n| `getCropBoxData` | 改用 `<cropper-selection>` 元素的 `x`、`y`、`width`、`height` 属性。 |\n| `setCropBoxData` | 改用 `<cropper-selection>` 元素的 `x`、`y`、`width`、`height` 属性。 |\n| `getCroppedCanvas` | 改用 `<cropper-selection>` 元素的 `$toCanvas` 方法。 |\n| `setAspectRatio` | 改用 `<cropper-selection>` 元素的 `aspectRatio` 属性。 |\n| `setDragMode` | 改用 `<cropper-handle>` 元素的 `action` 属性。 |\n\n## 事件\n\n| Cropper.js 1.0 | Cropper.js 2.0 |\n| --- | --- |\n| `ready` | 改用 `<cropper-image>` 元素的  `$ready` 方法。 |\n| `cropstart` | 改用 `<cropper-canvas>` 元素的 `actionstart` 事件。 |\n| `cropmove` | 改用 `<cropper-canvas>` 元素的 `actionmove` 事件。 |\n| `cropend` | 改用 `<cropper-canvas>` 元素的 `actionend` 事件。 |\n| `crop` | 改用 `<cropper-canvas>` 元素的 `action` 事件。 |\n| `zoom` | 改用 `<cropper-canvas>` 元素的 `action` 事件。 |\n"
  },
  {
    "path": "docs/zh/playground.md",
    "content": "---\ntitle: 演练场\nlayout: page\n---\n\n<ClientOnly>\n  <CropperPlaygroundContainer src=\"./cropper-playground.html\" />\n</ClientOnly>\n"
  },
  {
    "path": "jest.config.js",
    "content": "module.exports = {\n  preset: 'ts-jest',\n  collectCoverage: true,\n  coverageDirectory: 'coverage',\n  coverageReporters: ['html', 'lcov', 'text'],\n  moduleNameMapper: {\n    '^@cropper/(.*?)$': '<rootDir>/packages/$1/src',\n    cropperjs: '<rootDir>/packages/cropperjs/src',\n  },\n  testEnvironment: 'jsdom',\n  testEnvironmentOptions: {\n    resources: 'usable',\n  },\n  testMatch: ['**/packages/*/tests/**/*.spec.ts'],\n};\n"
  },
  {
    "path": "lerna.json",
    "content": "{\n  \"$schema\": \"node_modules/lerna/schemas/lerna-schema.json\",\n  \"version\": \"2.1.0\",\n  \"command\": {\n    \"version\": {\n      \"conventionalCommits\": true,\n      \"changelog\": false,\n      \"gitTagVersion\": false,\n      \"push\": false\n    }\n  }\n}"
  },
  {
    "path": "lint-staged.config.js",
    "content": "module.exports = {\n  '*.{js,ts,vue}': 'eslint --fix',\n  '*.{css,scss,vue}': 'stylelint --fix',\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"cropperjs\",\n  \"version\": \"2.1.0\",\n  \"private\": true,\n  \"workspaces\": [\n    \"packages/*\"\n  ],\n  \"scripts\": {\n    \"build\": \"vitepress build docs\",\n    \"changelog\": \"conventional-changelog -p angular -i CHANGELOG.md -s -r 0\",\n    \"dev\": \"vitepress dev docs --host\",\n    \"lint\": \"npm run lint:js && npm run lint:css\",\n    \"lint:css\": \"stylelint docs/.vitepress/**/*.{css,scss,vue} --fix\",\n    \"lint:js\": \"eslint . docs/.vitepress --fix\",\n    \"prepare\": \"husky install\",\n    \"publish\": \"lerna publish from-package\",\n    \"release\": \"lerna run release && npm run lint && npm test && npm run build && npm run changelog\",\n    \"start\": \"npm run dev\",\n    \"test\": \"jest\"\n  },\n  \"devDependencies\": {\n    \"@commitlint/cli\": \"^19.7.1\",\n    \"@commitlint/config-conventional\": \"^19.7.1\",\n    \"@microsoft/api-extractor\": \"^7.50.0\",\n    \"@rollup/plugin-commonjs\": \"^28.0.2\",\n    \"@rollup/plugin-node-resolve\": \"^16.0.0\",\n    \"@rollup/plugin-replace\": \"^6.0.2\",\n    \"@rollup/plugin-terser\": \"^0.4.4\",\n    \"@rollup/plugin-typescript\": \"^12.1.2\",\n    \"@types/estree\": \"^1.0.6\",\n    \"@types/jest\": \"^29.5.14\",\n    \"@typescript-eslint/eslint-plugin\": \"^7.18.0\",\n    \"@typescript-eslint/parser\": \"^7.18.0\",\n    \"autoprefixer\": \"^10.4.20\",\n    \"change-case\": \"^5.4.4\",\n    \"conventional-changelog-cli\": \"^5.0.0\",\n    \"create-banner\": \"^2.0.0\",\n    \"cssnano\": \"^7.0.6\",\n    \"del-cli\": \"^6.0.0\",\n    \"eslint\": \"^8.57.0\",\n    \"eslint-config-airbnb-base\": \"^15.0.0\",\n    \"eslint-config-airbnb-typescript\": \"^18.0.0\",\n    \"eslint-plugin-import\": \"^2.31.0\",\n    \"eslint-plugin-jsdoc\": \"^50.6.3\",\n    \"eslint-plugin-vue\": \"^9.32.0\",\n    \"husky\": \"^9.1.7\",\n    \"jest\": \"^29.7.0\",\n    \"jest-environment-jsdom\": \"^29.7.0\",\n    \"lerna\": \"^8.1.9\",\n    \"lint-staged\": \"^15.4.3\",\n    \"markdown-it-container\": \"^4.0.0\",\n    \"postcss\": \"^8.5.2\",\n    \"postcss-html\": \"^1.8.0\",\n    \"rgb-hex\": \"^4.1.0\",\n    \"rollup\": \"^2.79.2\",\n    \"rollup-plugin-inline-postcss\": \"^3.0.1\",\n    \"sass\": \"^1.85.0\",\n    \"stylelint\": \"^15.11.0\",\n    \"stylelint-config-recommended-scss\": \"^13.1.0\",\n    \"stylelint-config-recommended-vue\": \"^1.6.0\",\n    \"stylelint-order\": \"^6.0.4\",\n    \"ts-jest\": \"^29.2.5\",\n    \"tslib\": \"^2.8.1\",\n    \"typescript\": \"^5.5.4\",\n    \"vitepress\": \"^1.6.3\"\n  }\n}\n"
  },
  {
    "path": "packages/cropperjs/README.md",
    "content": "# Cropper.js\n\n> JavaScript image cropper.\n\n## Main npm package files\n\n```text\ndist/\n├── cropper.js         (UMD, bundled)\n├── cropper.min.js     (UMD, bundled, compressed)\n├── cropper.raw.js     (UMD, unbundled, default)\n├── cropper.esm.js     (ECMAScript Module, bundled)\n├── cropper.esm.min.js (ECMAScript Module, bundled, compressed)\n├── cropper.esm.raw.js (ECMAScript Module, unbundled)\n└── cropper.d.ts       (TypeScript Declaration File)\n```\n\n## Getting started\n\n### Installation\n\n```sh\nnpm install cropperjs\n```\n\n### Usage\n\n```js\nimport Cropper from 'cropperjs';\n\nconst image = new Image();\n\nimage.src = '/path/to/image.jpg';\n\nconst cropper = new Cropper(image);\n```\n\n## Versioning\n\nMaintained under the [Semantic Versioning guidelines](https://semver.org).\n\n## License\n\n[MIT](https://opensource.org/licenses/MIT)\n"
  },
  {
    "path": "packages/cropperjs/api-extractor.json",
    "content": "{\n  \"extends\": \"../../api-extractor.json\",\n  \"mainEntryPointFilePath\": \"./.temp/packages/<packageName>/src/index.d.ts\",\n  \"dtsRollup\": {\n    \"publicTrimmedFilePath\": \"./dist/cropper.d.ts\"\n  }\n}\n"
  },
  {
    "path": "packages/cropperjs/package.json",
    "content": "{\n  \"name\": \"cropperjs\",\n  \"version\": \"2.1.0\",\n  \"description\": \"JavaScript image cropper.\",\n  \"main\": \"dist/cropper.raw.js\",\n  \"module\": \"dist/cropper.esm.raw.js\",\n  \"types\": \"dist/cropper.d.ts\",\n  \"unpkg\": \"dist/cropper.js\",\n  \"jsdelivr\": \"dist/cropper.js\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"import\": {\n        \"types\": \"./dist/cropper.d.ts\",\n        \"node\": {\n          \"production\": \"./dist/cropper.esm.min.js\",\n          \"development\": \"./dist/cropper.esm.js\",\n          \"default\": \"./dist/cropper.esm.raw.js\"\n        },\n        \"default\": \"./dist/cropper.esm.raw.js\"\n      },\n      \"require\": {\n        \"types\": \"./dist/cropper.d.ts\",\n        \"node\": {\n          \"production\": \"./dist/cropper.min.js\",\n          \"development\": \"./dist/cropper.js\",\n          \"default\": \"./dist/cropper.raw.js\"\n        },\n        \"default\": \"./dist/cropper.raw.js\"\n      }\n    },\n    \"./dist/*\": \"./dist/*\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"scripts\": {\n    \"api-extractor\": \"api-extractor run --local --verbose\",\n    \"build\": \"npm run tsc && npm run api-extractor && npm run rollup\",\n    \"clean\": \"del-cli dist .temp\",\n    \"release\": \"npm run clean && npm run build\",\n    \"rollup\": \"rollup -c ../../rollup.config.js\",\n    \"tsc\": \"tsc --outDir ./.temp --declaration --emitDeclarationOnly\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/fengyuanchen/cropperjs.git\",\n    \"directory\": \"packages/cropperjs\"\n  },\n  \"keywords\": [\n    \"image\",\n    \"crop\",\n    \"move\",\n    \"zoom\",\n    \"rotate\",\n    \"scale\",\n    \"cropper\",\n    \"cropper.js\",\n    \"image-cropping\",\n    \"image-viewing\",\n    \"image-processing\",\n    \"cropper-element\",\n    \"custom-element\",\n    \"web-component\",\n    \"html\",\n    \"css\",\n    \"javascript\",\n    \"front-end\",\n    \"web\"\n  ],\n  \"author\": \"Chen Fengyuan (https://chenfengyuan.com/)\",\n  \"license\": \"MIT\",\n  \"bugs\": \"https://github.com/fengyuanchen/cropperjs/issues\",\n  \"homepage\": \"https://github.com/fengyuanchen/cropperjs/tree/main/packages/cropperjs/#readme\",\n  \"dependencies\": {\n    \"@cropper/elements\": \"^2.1.0\",\n    \"@cropper/utils\": \"^2.1.0\"\n  }\n}\n"
  },
  {
    "path": "packages/cropperjs/src/index.ts",
    "content": "import {\n  CROPPER_CANVAS,\n  CROPPER_IMAGE,\n  CROPPER_SELECTION,\n  getRootDocument,\n  isElement,\n  isString,\n} from '@cropper/utils';\nimport {\n  CropperCanvas,\n  CropperCrosshair,\n  CropperGrid,\n  CropperHandle,\n  CropperImage,\n  CropperSelection,\n  CropperShade,\n  CropperViewer,\n} from '@cropper/elements';\nimport DEFAULT_TEMPLATE from './template';\n\nexport interface CropperOptions {\n  container?: Element | string;\n  template?: string;\n}\n\nconst REGEXP_ALLOWED_ELEMENTS = /^img|canvas$/;\nconst REGEXP_BLOCKED_TAGS = /<(\\/?(?:script|style)[^>]*)>/gi;\nconst DEFAULT_OPTIONS: CropperOptions = {\n  template: DEFAULT_TEMPLATE,\n};\n\nCropperCanvas.$define();\nCropperCrosshair.$define();\nCropperGrid.$define();\nCropperHandle.$define();\nCropperImage.$define();\nCropperSelection.$define();\nCropperShade.$define();\nCropperViewer.$define();\n\nexport { DEFAULT_TEMPLATE };\nexport * from '@cropper/utils';\nexport * from '@cropper/elements';\nexport default class Cropper {\n  static version = '__VERSION__';\n\n  element: HTMLImageElement | HTMLCanvasElement;\n\n  options: CropperOptions = DEFAULT_OPTIONS;\n\n  container: Element;\n\n  constructor(\n    element: HTMLImageElement | HTMLCanvasElement | string,\n    options?: CropperOptions,\n  ) {\n    if (isString(element)) {\n      element = document.querySelector(element) as HTMLImageElement;\n    }\n\n    if (!isElement(element) || !REGEXP_ALLOWED_ELEMENTS.test(element.localName)) {\n      throw new Error('The first argument is required and must be an <img> or <canvas> element.');\n    }\n\n    this.element = element;\n    options = { ...DEFAULT_OPTIONS, ...options };\n    this.options = options;\n\n    let { container } = options;\n\n    if (container) {\n      if (isString(container)) {\n        container = getRootDocument(element)?.querySelector(container) as Element;\n      }\n\n      if (!isElement(container)) {\n        throw new Error('The `container` option must be an element or a valid selector.');\n      }\n    }\n\n    if (!isElement(container)) {\n      if (element.parentElement) {\n        container = element.parentElement;\n      } else {\n        container = element.ownerDocument.body;\n      }\n    }\n\n    this.container = container;\n\n    const tagName = element.localName;\n    let src = '';\n\n    if (tagName === 'img') {\n      ({ src } = element as HTMLImageElement);\n    } else if (tagName === 'canvas' && window.HTMLCanvasElement) {\n      src = (element as HTMLCanvasElement).toDataURL();\n    }\n\n    const { template } = options;\n\n    if (template && isString(template)) {\n      const templateElement = document.createElement('template');\n      const documentFragment = document.createDocumentFragment();\n\n      templateElement.innerHTML = template.replace(REGEXP_BLOCKED_TAGS, '&lt;$1&gt;');\n      documentFragment.appendChild(templateElement.content);\n\n      Array.from(documentFragment.querySelectorAll(CROPPER_IMAGE)).forEach((image) => {\n        image.setAttribute('src', src);\n        image.setAttribute('alt', (element as HTMLImageElement).alt || 'The image to crop');\n\n        // Inherit additional attributes from HTMLImageElement\n        if (tagName === 'img') {\n          [\n            'crossorigin',\n            'decoding',\n            'elementtiming',\n            'fetchpriority',\n            'loading',\n            'referrerpolicy',\n            'sizes',\n            'srcset',\n          ].forEach((attribute) => {\n            if ((element as HTMLImageElement).hasAttribute(attribute)) {\n              image.setAttribute(attribute, (element as HTMLImageElement).getAttribute(attribute) || '');\n            }\n          });\n        }\n      });\n\n      if (element.parentElement) {\n        element.style.display = 'none';\n        container.insertBefore(documentFragment, element.nextSibling);\n      } else {\n        container.appendChild(documentFragment);\n      }\n    }\n  }\n\n  getCropperCanvas(): CropperCanvas | null {\n    return this.container.querySelector(CROPPER_CANVAS);\n  }\n\n  getCropperImage(): CropperImage | null {\n    return this.container.querySelector(CROPPER_IMAGE);\n  }\n\n  getCropperSelection(): CropperSelection | null {\n    return this.container.querySelector(CROPPER_SELECTION);\n  }\n\n  getCropperSelections(): NodeListOf<CropperSelection> | null {\n    return this.container.querySelectorAll(CROPPER_SELECTION);\n  }\n\n  destroy(): void {\n    const cropperCanvas = this.getCropperCanvas();\n\n    if (cropperCanvas) {\n      cropperCanvas.parentElement?.removeChild(cropperCanvas);\n    }\n\n    if (this.element) {\n      this.element.style.display = '';\n    }\n  }\n}\n"
  },
  {
    "path": "packages/cropperjs/src/template.ts",
    "content": "export default (\n  '<cropper-canvas background>'\n    + '<cropper-image rotatable scalable skewable translatable></cropper-image>'\n    + '<cropper-shade hidden></cropper-shade>'\n    + '<cropper-handle action=\"select\" plain></cropper-handle>'\n    + '<cropper-selection initial-coverage=\"0.5\" movable resizable>'\n      + '<cropper-grid role=\"grid\" bordered covered></cropper-grid>'\n      + '<cropper-crosshair centered></cropper-crosshair>'\n      + '<cropper-handle action=\"move\" theme-color=\"rgba(255, 255, 255, 0.35)\"></cropper-handle>'\n      + '<cropper-handle action=\"n-resize\"></cropper-handle>'\n      + '<cropper-handle action=\"e-resize\"></cropper-handle>'\n      + '<cropper-handle action=\"s-resize\"></cropper-handle>'\n      + '<cropper-handle action=\"w-resize\"></cropper-handle>'\n      + '<cropper-handle action=\"ne-resize\"></cropper-handle>'\n      + '<cropper-handle action=\"nw-resize\"></cropper-handle>'\n      + '<cropper-handle action=\"se-resize\"></cropper-handle>'\n      + '<cropper-handle action=\"sw-resize\"></cropper-handle>'\n    + '</cropper-selection>'\n  + '</cropper-canvas>'\n);\n"
  },
  {
    "path": "packages/cropperjs/tests/index.spec.ts",
    "content": "import {\n  CROPPER_CANVAS,\n} from '@cropper/utils';\nimport {\n  CropperCanvas,\n  CropperImage,\n  CropperSelection,\n} from '@cropper/elements';\nimport Cropper from '../src';\n\ndescribe('Cropper', () => {\n  describe('element', () => {\n    it('should support `HTMLImageElement`', () => {\n      expect(() => {\n        new Cropper(document.createElement('img'));\n      }).not.toThrow();\n    });\n\n    it('should not support other elements', () => {\n      expect(() => {\n        new Cropper(document.createElement('div') as HTMLImageElement);\n      }).toThrow();\n    });\n  });\n\n  describe('options', () => {\n    describe('container', () => {\n      it('should be `undefined` by default', () => {\n        const image = new Image();\n        const cropper = new Cropper(image);\n\n        expect(cropper.options.container).toBeUndefined();\n      });\n\n      it('should be the parent element if it exists by default', () => {\n        const container = document.createElement('div');\n        const image = new Image();\n\n        container.appendChild(image);\n        document.body.appendChild(container);\n\n        const cropper = new Cropper(image);\n\n        expect(cropper.options.container).toBeUndefined();\n        expect(cropper.container).toBe(container);\n      });\n\n      it('should be the document body if the parent element does not exist by default', () => {\n        const image = new Image();\n        const cropper = new Cropper(image);\n\n        expect(cropper.options.container).toBeUndefined();\n        expect(document.body.contains(document.querySelector(CROPPER_CANVAS))).toBeTruthy();\n      });\n\n      it('should use the given element', () => {\n        const container = document.createElement('div');\n        const image = new Image();\n\n        container.appendChild(image);\n        document.body.appendChild(container);\n\n        const cropper = new Cropper(image, {\n          container,\n        });\n\n        expect(cropper.options.container).toBe(container);\n        expect(cropper.container).toBe(container);\n      });\n    });\n\n    describe('template', () => {\n      it('should be defined by default', () => {\n        const image = new Image();\n        const cropper = new Cropper(image);\n\n        expect(cropper.options.template).toBeDefined();\n      });\n\n      it('should use the given template', () => {\n        const template = '<cropper-canvas id=\"cropperCanvas\"><cropper-image></cropper-image></cropper-canvas>';\n        const image = new Image();\n        const cropper = new Cropper(image, {\n          template,\n        });\n\n        expect(cropper.options.template).toBe(template);\n        expect(document.querySelector('#cropperCanvas')).toBeTruthy();\n      });\n    });\n  });\n\n  describe('methods', () => {\n    describe('getCropperCanvas', () => {\n      it('should return the cropper canvas element', () => {\n        const image = new Image();\n        const cropper = new Cropper(image);\n\n        expect(cropper.getCropperCanvas()).toBeInstanceOf(CropperCanvas);\n      });\n    });\n\n    describe('getCropperImage', () => {\n      it('should return the cropper image element', () => {\n        const image = new Image();\n        const cropper = new Cropper(image);\n\n        expect(cropper.getCropperImage()).toBeInstanceOf(CropperImage);\n      });\n    });\n\n    describe('getCropperSelection', () => {\n      it('should return the cropper selection element', () => {\n        const image = new Image();\n        const cropper = new Cropper(image);\n\n        expect(cropper.getCropperSelection()).toBeInstanceOf(CropperSelection);\n      });\n    });\n\n    describe('getCropperSelections', () => {\n      it('should return all the cropper selection elements', () => {\n        const container = document.createElement('div');\n        const image = new Image();\n\n        container.appendChild(image);\n        document.body.appendChild(container);\n\n        const cropper = new Cropper(image, {\n          container,\n        });\n        const selections = cropper.getCropperSelections();\n\n        expect(selections).toHaveLength(1);\n\n        if (selections) {\n          expect(selections[0]).toBeInstanceOf(CropperSelection);\n        }\n      });\n    });\n\n    describe('destroy', () => {\n      it('should destroy the cropper instance', () => {\n        const container = document.createElement('div');\n        const image = new Image();\n\n        container.appendChild(image);\n        document.body.appendChild(container);\n\n        const cropper = new Cropper(image, {\n          container,\n        });\n\n        cropper.destroy();\n        expect(image.style.display).toBe('');\n        expect(container.childElementCount).toBe(1);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/element/README.md",
    "content": "# @cropper/element\n\n> An abstract class for constructing Cropper elements.\n\n## Main npm package files\n\n```text\ndist/\n├── element.js         (UMD, bundled)\n├── element.min.js     (UMD, bundled, compressed)\n├── element.raw.js     (UMD, unbundled, default)\n├── element.esm.js     (ECMAScript Module, bundled)\n├── element.esm.min.js (ECMAScript Module, bundled, compressed)\n├── element.esm.raw.js (ECMAScript Module, unbundled)\n└── element.d.ts       (TypeScript Declaration File)\n```\n\n## Getting started\n\n### Installation\n\n```sh\nnpm install @cropper/element\n```\n\n### Usage\n\n```js\nimport { CropperElement } from 'cropperjs';\n// Or\n// import CropperElement from '@cropper/element';\n\nclass MyCropperElement extends CropperElement {\n  myStringProperty = '';\n  myNumberProperty = NaN;\n  myBooleanProperty = false;\n\n  static get observedAttributes(): string[] {\n    return super.observedAttributes.concat([\n      'my-boolean-property',\n      'my-number-property',\n      'my-string-property',\n    ]);\n  }\n\n  // ...\n}\n\nMyCropperElement.$define();\n```\n\n```html\n<my-cropper-element my-string-property=\"foo\" my-number-property=\"1\" my-boolean-property></my-cropper-element>\n```\n\n## Versioning\n\nMaintained under the [Semantic Versioning guidelines](https://semver.org).\n\n## License\n\n[MIT](https://opensource.org/licenses/MIT)\n"
  },
  {
    "path": "packages/element/api-extractor.json",
    "content": "{\n  \"extends\": \"../../api-extractor.json\",\n  \"mainEntryPointFilePath\": \"./.temp/packages/<unscopedPackageName>/src/index.d.ts\",\n  \"dtsRollup\": {\n    \"publicTrimmedFilePath\": \"./dist/<unscopedPackageName>.d.ts\"\n  }\n}\n"
  },
  {
    "path": "packages/element/package.json",
    "content": "{\n  \"name\": \"@cropper/element\",\n  \"version\": \"2.1.0\",\n  \"description\": \"An abstract class for constructing Cropper elements.\",\n  \"main\": \"dist/element.raw.js\",\n  \"module\": \"dist/element.esm.raw.js\",\n  \"types\": \"dist/element.d.ts\",\n  \"unpkg\": \"dist/element.js\",\n  \"jsdelivr\": \"dist/element.js\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"import\": {\n        \"types\": \"./dist/element.d.ts\",\n        \"node\": {\n          \"production\": \"./dist/element.esm.min.js\",\n          \"development\": \"./dist/element.esm.js\",\n          \"default\": \"./dist/element.esm.raw.js\"\n        },\n        \"default\": \"./dist/element.esm.raw.js\"\n      },\n      \"require\": {\n        \"types\": \"./dist/element.d.ts\",\n        \"node\": {\n          \"production\": \"./dist/element.min.js\",\n          \"development\": \"./dist/element.js\",\n          \"default\": \"./dist/element.raw.js\"\n        },\n        \"default\": \"./dist/element.raw.js\"\n      }\n    },\n    \"./dist/*\": \"./dist/*\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"scripts\": {\n    \"api-extractor\": \"api-extractor run --local --verbose\",\n    \"build\": \"npm run tsc && npm run api-extractor && npm run rollup\",\n    \"clean\": \"del-cli dist .temp\",\n    \"release\": \"npm run clean && npm run build\",\n    \"rollup\": \"rollup -c ../../rollup.config.js\",\n    \"tsc\": \"tsc --outDir ./.temp --declaration --emitDeclarationOnly\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/fengyuanchen/cropperjs.git\",\n    \"directory\": \"packages/element\"\n  },\n  \"keywords\": [\n    \"element\",\n    \"cropper\",\n    \"cropper.js\",\n    \"cropper-element\",\n    \"custom-element\",\n    \"web-component\"\n  ],\n  \"author\": \"Chen Fengyuan (https://chenfengyuan.com/)\",\n  \"license\": \"MIT\",\n  \"bugs\": \"https://github.com/fengyuanchen/cropperjs/issues\",\n  \"homepage\": \"https://github.com/fengyuanchen/cropperjs/tree/main/packages/element/#readme\",\n  \"dependencies\": {\n    \"@cropper/utils\": \"^2.1.0\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  }\n}\n"
  },
  {
    "path": "packages/element/src/index.ts",
    "content": "import {\n  IS_BROWSER,\n  WINDOW,\n  emit,\n  isNaN,\n  isNumber,\n  isObject,\n  isUndefined,\n  nextTick,\n  toCamelCase,\n  toKebabCase,\n} from '@cropper/utils';\nimport style from './style';\n\nconst REGEXP_SUFFIX = /left|top|width|height/i;\nconst DEFAULT_SHADOW_ROOT_MODE = 'open';\nconst shadowRoots = new WeakMap();\nconst styleSheets = new WeakMap();\nconst tagNames: Map<string, string> = new Map();\nconst supportsAdoptedStyleSheets = WINDOW.document && Array.isArray(WINDOW.document.adoptedStyleSheets) && 'replaceSync' in WINDOW.CSSStyleSheet.prototype;\n\nexport default class CropperElement extends HTMLElement {\n  static $name: string;\n\n  static $version = '__VERSION__';\n\n  protected $style?: string;\n\n  protected $template?: string;\n\n  protected get $sharedStyle(): string {\n    return `${this.themeColor ? `:host{--theme-color: ${this.themeColor};}` : ''}${style}`;\n  }\n\n  shadowRootMode: ShadowRootMode = DEFAULT_SHADOW_ROOT_MODE;\n\n  slottable = true;\n\n  themeColor?: string;\n\n  constructor() {\n    super();\n\n    const name = Object.getPrototypeOf(this)?.constructor?.$name;\n\n    if (name) {\n      tagNames.set(name, this.tagName.toLowerCase());\n    }\n  }\n\n  protected static get observedAttributes(): string[] {\n    return [\n      'shadow-root-mode',\n      'slottable',\n      'theme-color',\n    ];\n  }\n\n  // Convert attribute to property\n  protected attributeChangedCallback(name: string, oldValue: string, newValue: string): void {\n    if (Object.is(newValue, oldValue)) {\n      return;\n    }\n\n    const propertyName = toCamelCase(name);\n    const oldPropertyValue = (this as any)[propertyName];\n    let newPropertyValue: any = newValue;\n\n    switch (typeof oldPropertyValue) {\n      case 'boolean':\n        newPropertyValue = newValue !== null && newValue !== 'false';\n        break;\n\n      case 'number':\n        newPropertyValue = Number(newValue);\n        break;\n\n      default:\n    }\n\n    (this as any)[propertyName] = newPropertyValue;\n\n    switch (name) {\n      case 'theme-color': {\n        const styleSheet = styleSheets.get(this);\n        const styles = this.$sharedStyle;\n\n        if (styleSheet && styles) {\n          if (supportsAdoptedStyleSheets) {\n            styleSheet.replaceSync(styles);\n          } else {\n            styleSheet.textContent = styles;\n          }\n        }\n\n        break;\n      }\n\n      default:\n    }\n  }\n\n  // Convert property to attribute\n  protected $propertyChangedCallback(name: string, oldValue: unknown, newValue: unknown): void {\n    if (Object.is(newValue, oldValue)) {\n      return;\n    }\n\n    name = toKebabCase(name);\n\n    switch (typeof newValue) {\n      case 'boolean':\n        if (newValue === true) {\n          if (!this.hasAttribute(name)) {\n            this.setAttribute(name, '');\n          }\n        } else {\n          this.removeAttribute(name);\n        }\n        break;\n\n      case 'number':\n        if (isNaN(newValue)) {\n          newValue = '';\n        } else {\n          newValue = String(newValue);\n        }\n        // Fall through\n\n        // case 'string':\n        // eslint-disable-next-line no-fallthrough\n      default:\n        if (newValue) {\n          if (this.getAttribute(name) !== newValue) {\n            this.setAttribute(name, newValue as string);\n          }\n        } else {\n          this.removeAttribute(name);\n        }\n    }\n  }\n\n  protected connectedCallback(): void {\n    // Observe properties after observed attributes\n    Object.getPrototypeOf(this).constructor.observedAttributes.forEach((attribute: string) => {\n      const property = toCamelCase(attribute);\n      let value = (this as any)[property];\n\n      if (!isUndefined(value)) {\n        this.$propertyChangedCallback(property, undefined, value);\n      }\n\n      Object.defineProperty(this, property, {\n        enumerable: true,\n        configurable: true,\n        get() {\n          return value;\n        },\n        set(newValue) {\n          const oldValue = value;\n\n          value = newValue;\n          this.$propertyChangedCallback(property, oldValue, newValue);\n        },\n      });\n    });\n\n    const shadow = this.shadowRoot || this.attachShadow({\n      mode: this.shadowRootMode || DEFAULT_SHADOW_ROOT_MODE,\n    });\n\n    shadowRoots.set(this, shadow);\n    styleSheets.set(this, this.$addStyles(this.$sharedStyle));\n\n    if (this.$style) {\n      this.$addStyles(this.$style);\n    }\n\n    if (this.$template) {\n      const template = document.createElement('template');\n\n      template.innerHTML = this.$template;\n      shadow.appendChild(template.content);\n    }\n\n    if (this.slottable) {\n      const slot = document.createElement('slot');\n\n      shadow.appendChild(slot);\n    }\n  }\n\n  protected disconnectedCallback(): void {\n    if (styleSheets.has(this)) {\n      styleSheets.delete(this);\n    }\n\n    if (shadowRoots.has(this)) {\n      shadowRoots.delete(this);\n    }\n  }\n\n  // eslint-disable-next-line class-methods-use-this\n  protected $getTagNameOf(name: string): string {\n    return tagNames.get(name) ?? name;\n  }\n\n  protected $setStyles(properties: Record<string, any>): this {\n    Object.keys(properties).forEach((property: any) => {\n      let value = properties[property];\n\n      if (isNumber(value)) {\n        if (value !== 0 && REGEXP_SUFFIX.test(property)) {\n          value = `${value}px`;\n        } else {\n          value = String(value);\n        }\n      }\n\n      this.style[property] = value;\n    });\n\n    return this;\n  }\n\n  /**\n   * Outputs the shadow root of the element.\n   * @returns {ShadowRoot} Returns the shadow root.\n   */\n  $getShadowRoot(): ShadowRoot {\n    return this.shadowRoot || shadowRoots.get(this);\n  }\n\n  /**\n   * Adds styles to the shadow root.\n   * @param {string} styles The styles to add.\n   * @returns {CSSStyleSheet|HTMLStyleElement} Returns the generated style sheet.\n   */\n  $addStyles(styles: string): CSSStyleSheet | HTMLStyleElement {\n    let styleSheet;\n\n    const shadow = this.$getShadowRoot();\n\n    if (supportsAdoptedStyleSheets) {\n      styleSheet = new CSSStyleSheet();\n      (styleSheet as any).replaceSync(styles);\n      (shadow as any).adoptedStyleSheets = (shadow as any).adoptedStyleSheets.concat(styleSheet);\n    } else {\n      styleSheet = document.createElement('style');\n      styleSheet.textContent = styles;\n      shadow.appendChild(styleSheet);\n    }\n\n    return styleSheet;\n  }\n\n  /**\n   * Dispatches an event at the element.\n   * @param {string} type The name of the event.\n   * @param {*} [detail] The data passed when initializing the event.\n   * @param {CustomEventInit} [options] The other event options.\n   * @returns {boolean} Returns the result value.\n   */\n  $emit(type: string, detail?: unknown, options?: CustomEventInit): boolean {\n    return emit(this, type, detail, options);\n  }\n\n  /**\n   * Defers the callback to be executed after the next DOM update cycle.\n   * @param {Function} [callback] The callback to execute after the next DOM update cycle.\n   * @returns {Promise} A promise that resolves to nothing.\n   */\n  $nextTick(callback?: () => void): Promise<void> {\n    return nextTick(this, callback);\n  }\n\n  /**\n   * Defines the constructor as a new custom element.\n   * {@link https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define}\n   * @param {string|object} [name] The element name.\n   * @param {object} [options] The element definition options.\n   */\n  static $define(\n    name?: string | ElementDefinitionOptions,\n    options?: ElementDefinitionOptions,\n  ): void {\n    if (isObject(name)) {\n      options = name;\n      name = '';\n    }\n\n    if (!name) {\n      name = this.$name || this.name;\n    }\n\n    name = toKebabCase(name as string);\n\n    if (IS_BROWSER && WINDOW.customElements && !WINDOW.customElements.get(name)) {\n      customElements.define(name, this, options);\n    }\n  }\n}\n"
  },
  {
    "path": "packages/element/src/style.ts",
    "content": "export default `\n:host([hidden]) {\n  display: none !important;\n}\n`;\n"
  },
  {
    "path": "packages/element/tests/index.spec.ts",
    "content": "import CropperElement from '../src';\n\nCropperElement.$define();\n\ndescribe('CropperElement', () => {\n  describe('properties', () => {\n    describe('shadowRootMode', () => {\n      it('should be `\"open\"` by default', () => {\n        const element = new CropperElement();\n\n        expect(element.shadowRootMode).toBe('open');\n      });\n\n      it('should be `\"closed\"` by default', () => {\n        const element = new CropperElement();\n\n        element.setAttribute('shadow-root-mode', 'closed');\n        expect(element.shadowRootMode).toBe('closed');\n      });\n    });\n\n    describe('slottable', () => {\n      it('should be `true` by default', () => {\n        const element = new CropperElement();\n\n        expect(element.slottable).toBe(true);\n      });\n\n      it('should be `false` by default', () => {\n        const element = new CropperElement();\n\n        element.setAttribute('slottable', 'false');\n        expect(element.slottable).toBe(false);\n      });\n    });\n\n    describe('themeColor', () => {\n      it('should be `undefined` by default', () => {\n        const element = new CropperElement();\n\n        expect(element.themeColor).toBeUndefined();\n      });\n\n      it('should be \"#000\"', () => {\n        const element = new CropperElement();\n\n        element.setAttribute('theme-color', '#000');\n        expect(element.themeColor).toBe('#000');\n      });\n    });\n  });\n\n  describe('methods', () => {\n    describe('$getShadowRoot', () => {\n      it('should return the shadow root of the element', () => {\n        const element = new CropperElement();\n\n        document.body.appendChild(element);\n        expect(element.$getShadowRoot()).toBe(element.shadowRoot);\n      });\n\n      it('should return the shadow root of the element even if its mode is \"closed\"', () => {\n        const element = new CropperElement();\n\n        element.setAttribute('shadow-root-mode', 'closed');\n        document.body.appendChild(element);\n        expect(element.$getShadowRoot()).not.toBeNull();\n      });\n    });\n\n    describe('$addStyles', () => {\n      it('should add styles to the shadow root', () => {\n        const element = new CropperElement();\n\n        document.body.appendChild(element);\n\n        const shadowRoot = element.$getShadowRoot();\n\n        element.$addStyles(':host{color:blue}');\n\n        if (shadowRoot) {\n          expect(shadowRoot.querySelectorAll('style')).toHaveLength(2);\n        }\n      });\n    });\n\n    describe('$emit', () => {\n      it('should dispatch event at the current element', () => {\n        const element = new CropperElement();\n\n        document.body.appendChild(element);\n\n        element.addEventListener('click', (event) => {\n          expect(event.type).toBe('click');\n        });\n        element.$emit('click');\n      });\n    });\n  });\n\n  describe('extends', () => {\n    class MyCropperElement extends CropperElement {\n      string = '';\n\n      number = 0;\n\n      boolean = false;\n\n      protected static get observedAttributes(): string[] {\n        return super.observedAttributes.concat([\n          'boolean',\n          'number',\n          'string',\n        ]);\n      }\n    }\n\n    MyCropperElement.$define();\n\n    it('should convert attributes to properties', () => {\n      const element = new MyCropperElement();\n\n      element.setAttribute('string', 'foo');\n      element.setAttribute('number', '1');\n      element.setAttribute('boolean', '');\n      expect(element.string).toBe('foo');\n      expect(element.number).toBe(1);\n      expect(element.boolean).toBe(true);\n    });\n\n    it('should convert properties to attributes', () => {\n      const element = new MyCropperElement();\n\n      element.string = 'foo';\n      element.number = 1;\n      element.boolean = true;\n      document.body.appendChild(element);\n      expect(element.getAttribute('string')).toBe('foo');\n      expect(element.getAttribute('number')).toBe('1');\n      expect(element.getAttribute('boolean')).toBe('');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/element-canvas/README.md",
    "content": "# @cropper/element-canvas\n\n> A custom canvas element for the Cropper.\n\n## Main npm package files\n\n```text\ndist/\n├── element-canvas.js         (UMD, bundled)\n├── element-canvas.min.js     (UMD, bundled, compressed)\n├── element-canvas.raw.js     (UMD, unbundled, default)\n├── element-canvas.esm.js     (ECMAScript Module, bundled)\n├── element-canvas.esm.min.js (ECMAScript Module, bundled, compressed)\n├── element-canvas.esm.raw.js (ECMAScript Module, unbundled)\n└── element-canvas.d.ts       (TypeScript Declaration File)\n```\n\n## Getting started\n\n### Installation\n\n```sh\nnpm install @cropper/element-canvas\n```\n\n### Usage\n\n```js\nimport CropperCanvas from '@cropper/element-canvas';\n\nCropperCanvas.$define();\n```\n\n```html\n<cropper-canvas></cropper-canvas>\n```\n\n## Versioning\n\nMaintained under the [Semantic Versioning guidelines](https://semver.org).\n\n## License\n\n[MIT](https://opensource.org/licenses/MIT)\n"
  },
  {
    "path": "packages/element-canvas/api-extractor.json",
    "content": "{\n  \"extends\": \"../../api-extractor.json\",\n  \"mainEntryPointFilePath\": \"./.temp/packages/<unscopedPackageName>/src/index.d.ts\",\n  \"dtsRollup\": {\n    \"publicTrimmedFilePath\": \"./dist/<unscopedPackageName>.d.ts\"\n  }\n}\n"
  },
  {
    "path": "packages/element-canvas/package.json",
    "content": "{\n  \"name\": \"@cropper/element-canvas\",\n  \"version\": \"2.1.0\",\n  \"description\": \"A custom canvas element for the Cropper.\",\n  \"main\": \"dist/element-canvas.raw.js\",\n  \"module\": \"dist/element-canvas.esm.raw.js\",\n  \"types\": \"dist/element-canvas.d.ts\",\n  \"unpkg\": \"dist/element-canvas.js\",\n  \"jsdelivr\": \"dist/element-canvas.js\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"import\": {\n        \"types\": \"./dist/element-canvas.d.ts\",\n        \"node\": {\n          \"production\": \"./dist/element-canvas.esm.min.js\",\n          \"development\": \"./dist/element-canvas.esm.js\",\n          \"default\": \"./dist/element-canvas.esm.raw.js\"\n        },\n        \"default\": \"./dist/element-canvas.esm.raw.js\"\n      },\n      \"require\": {\n        \"types\": \"./dist/element-canvas.d.ts\",\n        \"node\": {\n          \"production\": \"./dist/element-canvas.min.js\",\n          \"development\": \"./dist/element-canvas.js\",\n          \"default\": \"./dist/element-canvas.raw.js\"\n        },\n        \"default\": \"./dist/element-canvas.raw.js\"\n      }\n    },\n    \"./dist/*\": \"./dist/*\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"scripts\": {\n    \"api-extractor\": \"api-extractor run --local --verbose\",\n    \"build\": \"npm run tsc && npm run api-extractor && npm run rollup\",\n    \"clean\": \"del-cli dist .temp\",\n    \"release\": \"npm run clean && npm run build\",\n    \"rollup\": \"rollup -c ../../rollup.config.js\",\n    \"tsc\": \"tsc --outDir ./.temp --declaration --emitDeclarationOnly\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/fengyuanchen/cropperjs.git\",\n    \"directory\": \"packages/element-canvas\"\n  },\n  \"keywords\": [\n    \"canvas\",\n    \"cropper\",\n    \"cropper.js\",\n    \"cropper-element\",\n    \"custom-element\",\n    \"web-component\"\n  ],\n  \"author\": \"Chen Fengyuan (https://chenfengyuan.com/)\",\n  \"license\": \"MIT\",\n  \"bugs\": \"https://github.com/fengyuanchen/cropperjs/issues\",\n  \"homepage\": \"https://github.com/fengyuanchen/cropperjs/tree/main/packages/element-canvas/#readme\",\n  \"dependencies\": {\n    \"@cropper/element\": \"^2.1.0\",\n    \"@cropper/utils\": \"^2.1.0\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  }\n}\n"
  },
  {
    "path": "packages/element-canvas/src/index.ts",
    "content": "import CropperElement from '@cropper/element';\nimport {\n  ACTION_NONE,\n  ACTION_ROTATE,\n  ACTION_SCALE,\n  ACTION_TRANSFORM,\n  ATTRIBUTE_ACTION,\n  CROPPER_CANVAS,\n  CROPPER_IMAGE,\n  EVENT_ACTION,\n  EVENT_ACTION_END,\n  EVENT_ACTION_MOVE,\n  EVENT_ACTION_START,\n  EVENT_POINTER_DOWN,\n  EVENT_POINTER_MOVE,\n  EVENT_POINTER_UP,\n  EVENT_WHEEL,\n  getAdjustedSizes,\n  isElement,\n  isFunction,\n  isNumber,\n  isPlainObject,\n  isPositiveNumber,\n  isString,\n  off,\n  on,\n} from '@cropper/utils';\nimport style from './style';\n\ninterface ActionEventData {\n  action: string;\n  relatedEvent: Event;\n  scale?: number;\n  rotate?: number;\n  startX?: number;\n  startY?: number;\n  endX?: number;\n  endY?: number;\n  centerX?: number;\n  centerY?: number;\n}\n\nexport default class CropperCanvas extends CropperElement {\n  static $name = CROPPER_CANVAS;\n\n  static $version = '__VERSION__';\n\n  protected $onPointerDown: EventListener | null = null;\n\n  protected $onPointerMove: EventListener | null = null;\n\n  protected $onPointerUp: EventListener | null = null;\n\n  protected $onWheel: EventListener | null = null;\n\n  protected $wheeling = false;\n\n  protected readonly $pointers: Map<number, any> = new Map();\n\n  protected $style = style;\n\n  protected $action = ACTION_NONE;\n\n  background = false;\n\n  disabled = false;\n\n  scaleStep = 0.1;\n\n  themeColor = '#39f';\n\n  protected static get observedAttributes(): string[] {\n    return super.observedAttributes.concat([\n      'background',\n      'disabled',\n      'scale-step',\n    ]);\n  }\n\n  protected connectedCallback(): void {\n    super.connectedCallback();\n\n    if (!this.disabled) {\n      this.$bind();\n    }\n  }\n\n  protected disconnectedCallback(): void {\n    if (!this.disabled) {\n      this.$unbind();\n    }\n\n    super.disconnectedCallback();\n  }\n\n  protected $propertyChangedCallback(name: string, oldValue: unknown, newValue: unknown): void {\n    if (Object.is(newValue, oldValue)) {\n      return;\n    }\n\n    super.$propertyChangedCallback(name, oldValue, newValue);\n\n    switch (name) {\n      case 'disabled':\n        if (newValue) {\n          this.$unbind();\n        } else {\n          this.$bind();\n        }\n        break;\n\n      default:\n    }\n  }\n\n  protected $bind(): void {\n    if (!this.$onPointerDown) {\n      this.$onPointerDown = this.$handlePointerDown.bind(this);\n      on(this, EVENT_POINTER_DOWN, this.$onPointerDown);\n    }\n\n    if (!this.$onPointerMove) {\n      this.$onPointerMove = this.$handlePointerMove.bind(this);\n      on(this.ownerDocument, EVENT_POINTER_MOVE, this.$onPointerMove);\n    }\n\n    if (!this.$onPointerUp) {\n      this.$onPointerUp = this.$handlePointerUp.bind(this);\n      on(this.ownerDocument, EVENT_POINTER_UP, this.$onPointerUp);\n    }\n\n    if (!this.$onWheel) {\n      this.$onWheel = this.$handleWheel.bind(this);\n      on(this, EVENT_WHEEL, this.$onWheel, {\n        passive: false,\n        capture: true,\n      });\n    }\n  }\n\n  protected $unbind(): void {\n    if (this.$onPointerDown) {\n      off(this, EVENT_POINTER_DOWN, this.$onPointerDown);\n      this.$onPointerDown = null;\n    }\n\n    if (this.$onPointerMove) {\n      off(this.ownerDocument, EVENT_POINTER_MOVE, this.$onPointerMove);\n      this.$onPointerMove = null;\n    }\n\n    if (this.$onPointerUp) {\n      off(this.ownerDocument, EVENT_POINTER_UP, this.$onPointerUp);\n      this.$onPointerUp = null;\n    }\n\n    if (this.$onWheel) {\n      off(this, EVENT_WHEEL, this.$onWheel, {\n        capture: true,\n      });\n      this.$onWheel = null;\n    }\n  }\n\n  protected $handlePointerDown(event: Event): void {\n    const { buttons, button, type } = event as PointerEvent;\n\n    if (this.disabled || (\n      // Handle pointer or mouse event, and ignore touch event\n      ((type === 'pointerdown' && (event as PointerEvent).pointerType === 'mouse') || type === 'mousedown') && (\n        // No primary button (Usually the left button)\n        (isNumber(buttons) && buttons !== 1) || (isNumber(button) && button !== 0)\n\n        // Open context menu\n        || (event as PointerEvent).ctrlKey\n      ))\n    ) {\n      return;\n    }\n\n    const { $pointers } = this;\n    let action = '';\n\n    if ((event as TouchEvent).changedTouches) {\n      Array.from((event as TouchEvent).changedTouches).forEach(({\n        identifier,\n        pageX,\n        pageY,\n      }) => {\n        $pointers.set(identifier, {\n          startX: pageX,\n          startY: pageY,\n          endX: pageX,\n          endY: pageY,\n        });\n      });\n    } else {\n      const { pointerId = 0, pageX, pageY } = (event as PointerEvent);\n\n      $pointers.set(pointerId, {\n        startX: pageX,\n        startY: pageY,\n        endX: pageX,\n        endY: pageY,\n      });\n    }\n\n    if ($pointers.size > 1) {\n      action = ACTION_TRANSFORM;\n    } else if (isElement(event.target)) {\n      action = (event.target as any).action || event.target.getAttribute(ATTRIBUTE_ACTION) || '';\n    }\n\n    if (this.$emit(EVENT_ACTION_START, {\n      action,\n      relatedEvent: event,\n    }) === false) {\n      return;\n    }\n\n    // Prevent page zooming in the browsers for iOS.\n    event.preventDefault();\n    this.$action = action;\n    this.style.willChange = 'transform';\n  }\n\n  protected $handlePointerMove(event: Event): void {\n    const { $action, $pointers } = this;\n\n    if (this.disabled || $action === ACTION_NONE || $pointers.size === 0) {\n      return;\n    }\n\n    if (this.$emit(EVENT_ACTION_MOVE, {\n      action: $action,\n      relatedEvent: event,\n    }) === false) {\n      return;\n    }\n\n    // Prevent page scrolling.\n    event.preventDefault();\n\n    if ((event as TouchEvent).changedTouches) {\n      Array.from((event as TouchEvent).changedTouches).forEach(({\n        identifier,\n        pageX,\n        pageY,\n      }) => {\n        const pointer = $pointers.get(identifier);\n\n        if (pointer) {\n          Object.assign(pointer, {\n            endX: pageX,\n            endY: pageY,\n          });\n        }\n      });\n    } else {\n      const { pointerId = 0, pageX, pageY } = (event as PointerEvent);\n      const pointer = $pointers.get(pointerId);\n\n      if (pointer) {\n        Object.assign(pointer, {\n          endX: pageX,\n          endY: pageY,\n        });\n      }\n    }\n\n    const detail: ActionEventData = {\n      action: $action,\n      relatedEvent: event,\n    };\n\n    if ($action === ACTION_TRANSFORM) {\n      const pointers2 = new Map($pointers);\n      let maxRotateRate = 0;\n      let maxScaleRate = 0;\n      let rotate = 0;\n      let scale = 0;\n      let centerX = (event as PointerEvent).pageX;\n      let centerY = (event as PointerEvent).pageY;\n\n      $pointers.forEach((pointer, pointerId) => {\n        pointers2.delete(pointerId);\n        pointers2.forEach((pointer2) => {\n          let x1 = pointer2.startX - pointer.startX;\n          let y1 = pointer2.startY - pointer.startY;\n          let x2 = pointer2.endX - pointer.endX;\n          let y2 = pointer2.endY - pointer.endY;\n          let z1 = 0;\n          let z2 = 0;\n          let a1 = 0;\n          let a2 = 0;\n\n          if (x1 === 0) {\n            if (y1 < 0) {\n              a1 = Math.PI * 2;\n            } else if (y1 > 0) {\n              a1 = Math.PI;\n            }\n          } else if (x1 > 0) {\n            a1 = (Math.PI / 2) + Math.atan(y1 / x1);\n          } else if (x1 < 0) {\n            a1 = (Math.PI * 1.5) + Math.atan(y1 / x1);\n          }\n\n          if (x2 === 0) {\n            if (y2 < 0) {\n              a2 = Math.PI * 2;\n            } else if (y2 > 0) {\n              a2 = Math.PI;\n            }\n          } else if (x2 > 0) {\n            a2 = (Math.PI / 2) + Math.atan(y2 / x2);\n          } else if (x2 < 0) {\n            a2 = (Math.PI * 1.5) + Math.atan(y2 / x2);\n          }\n\n          if (a2 > 0 || a1 > 0) {\n            const rotateRate = a2 - a1;\n            const absRotateRate = Math.abs(rotateRate);\n\n            if (absRotateRate > maxRotateRate) {\n              maxRotateRate = absRotateRate;\n              rotate = rotateRate;\n              centerX = (pointer.startX + pointer2.startX) / 2;\n              centerY = (pointer.startY + pointer2.startY) / 2;\n            }\n          }\n\n          x1 = Math.abs(x1);\n          y1 = Math.abs(y1);\n          x2 = Math.abs(x2);\n          y2 = Math.abs(y2);\n\n          if (x1 > 0 && y1 > 0) {\n            z1 = Math.sqrt((x1 * x1) + (y1 * y1));\n          } else if (x1 > 0) {\n            z1 = x1;\n          } else if (y1 > 0) {\n            z1 = y1;\n          }\n\n          if (x2 > 0 && y2 > 0) {\n            z2 = Math.sqrt((x2 * x2) + (y2 * y2));\n          } else if (x2 > 0) {\n            z2 = x2;\n          } else if (y2 > 0) {\n            z2 = y2;\n          }\n\n          if (z1 > 0 && z2 > 0) {\n            const scaleRate = (z2 - z1) / z1;\n            const absScaleRate = Math.abs(scaleRate);\n\n            if (absScaleRate > maxScaleRate) {\n              maxScaleRate = absScaleRate;\n              scale = scaleRate;\n              centerX = (pointer.startX + pointer2.startX) / 2;\n              centerY = (pointer.startY + pointer2.startY) / 2;\n            }\n          }\n        });\n      });\n\n      const rotatable = maxRotateRate > 0;\n      const scalable = maxScaleRate > 0;\n\n      if (rotatable && scalable) {\n        detail.rotate = rotate;\n        detail.scale = scale;\n        detail.centerX = centerX;\n        detail.centerY = centerY;\n      } else if (rotatable) {\n        detail.action = ACTION_ROTATE;\n        detail.rotate = rotate;\n        detail.centerX = centerX;\n        detail.centerY = centerY;\n      } else if (scalable) {\n        detail.action = ACTION_SCALE;\n        detail.scale = scale;\n        detail.centerX = centerX;\n        detail.centerY = centerY;\n      } else {\n        detail.action = ACTION_NONE;\n      }\n    } else {\n      const [pointer] = Array.from($pointers.values());\n\n      Object.assign(detail, pointer);\n    }\n\n    // Override the starting coordinate\n    $pointers.forEach((pointer) => {\n      pointer.startX = pointer.endX;\n      pointer.startY = pointer.endY;\n    });\n\n    if (detail.action !== ACTION_NONE) {\n      this.$emit(EVENT_ACTION, detail, {\n        cancelable: false,\n      });\n    }\n  }\n\n  protected $handlePointerUp(event: Event): void {\n    const { $action, $pointers } = this;\n\n    if (this.disabled || $action === ACTION_NONE) {\n      return;\n    }\n\n    if (this.$emit(EVENT_ACTION_END, {\n      action: $action,\n      relatedEvent: event,\n    }) === false) {\n      return;\n    }\n\n    event.preventDefault();\n\n    if ((event as TouchEvent).changedTouches) {\n      Array.from((event as TouchEvent).changedTouches).forEach(({\n        identifier,\n      }) => {\n        $pointers.delete(identifier);\n      });\n    } else {\n      const { pointerId = 0 } = (event as PointerEvent);\n\n      $pointers.delete(pointerId);\n    }\n\n    if ($pointers.size === 0) {\n      this.style.willChange = '';\n      this.$action = ACTION_NONE;\n    }\n  }\n\n  protected $handleWheel(event: Event): void {\n    if (this.disabled) {\n      return;\n    }\n\n    event.preventDefault();\n\n    // Limit wheel speed to prevent zoom too fast (#21)\n    if (this.$wheeling) {\n      return;\n    }\n\n    this.$wheeling = true;\n\n    // Debounce by 50ms\n    setTimeout(() => {\n      this.$wheeling = false;\n    }, 50);\n\n    const delta = (event as WheelEvent).deltaY > 0 ? -1 : 1;\n    const scale = delta * this.scaleStep;\n\n    this.$emit(EVENT_ACTION, {\n      action: ACTION_SCALE,\n      scale,\n      relatedEvent: event,\n    }, {\n      cancelable: false,\n    });\n  }\n\n  /**\n   * Changes the current action to a new one.\n   * @param {string} action The new action.\n   * @returns {CropperCanvas} Returns `this` for chaining.\n   */\n  $setAction(action: string): this {\n    if (isString(action)) {\n      this.$action = action;\n    }\n\n    return this;\n  }\n\n  /**\n   * Generates a real canvas element, with the image draw into if there is one.\n   * @param {object} [options] The available options.\n   * @param {number} [options.width] The width of the canvas.\n   * @param {number} [options.height] The height of the canvas.\n   * @param {Function} [options.beforeDraw] The function called before drawing the image onto the canvas.\n   * @returns {Promise} Returns a promise that resolves to the generated canvas element.\n   */\n  $toCanvas(options?: {\n    width?: number;\n    height?: number;\n    beforeDraw?: (context: CanvasRenderingContext2D, canvas: HTMLCanvasElement) => void;\n  }): Promise<HTMLCanvasElement> {\n    return new Promise<HTMLCanvasElement>((resolve, reject) => {\n      if (!this.isConnected) {\n        reject(new Error('The current element is not connected to the DOM.'));\n        return;\n      }\n\n      const canvas = document.createElement('canvas');\n      let width = this.offsetWidth;\n      let height = this.offsetHeight;\n      let scale = 1;\n\n      if (isPlainObject(options)\n        && (isPositiveNumber(options.width) || isPositiveNumber(options.height))) {\n        ({ width, height } = getAdjustedSizes({\n          aspectRatio: width / height,\n          width: options.width as number,\n          height: options.height as number,\n        }));\n        scale = width / this.offsetWidth;\n      }\n\n      canvas.width = width;\n      canvas.height = height;\n\n      const cropperImage: any = this.querySelector(this.$getTagNameOf(CROPPER_IMAGE));\n\n      if (!cropperImage) {\n        resolve(canvas);\n        return;\n      }\n\n      cropperImage.$ready().then((image: HTMLImageElement) => {\n        const context = canvas.getContext('2d');\n\n        if (context) {\n          const [a, b, c, d, e, f] = cropperImage.$getTransform();\n          let newE = e;\n          let newF = f;\n          let destWidth = image.naturalWidth;\n          let destHeight = image.naturalHeight;\n\n          if (scale !== 1) {\n            newE *= scale;\n            newF *= scale;\n            destWidth *= scale;\n            destHeight *= scale;\n          }\n\n          const centerX = destWidth / 2;\n          const centerY = destHeight / 2;\n\n          context.fillStyle = 'transparent';\n          context.fillRect(0, 0, width, height);\n\n          if (isPlainObject(options) && isFunction(options.beforeDraw)) {\n            options.beforeDraw.call(this, context, canvas);\n          }\n\n          context.save();\n\n          // Move the transform origin to the center of the image.\n          // https://developer.mozilla.org/en-US/docs/Web/CSS/transform-origin\n          context.translate(centerX, centerY);\n          context.transform(a, b, c, d, newE, newF);\n\n          // Reset the transform origin to the top-left of the image.\n          context.translate(-centerX, -centerY);\n          context.drawImage(image, 0, 0, destWidth, destHeight);\n          context.restore();\n        }\n\n        resolve(canvas);\n      }).catch(reject);\n    });\n  }\n}\n"
  },
  {
    "path": "packages/element-canvas/src/style.ts",
    "content": "export default `\n:host {\n  display: block;\n  min-height: 100px;\n  min-width: 200px;\n  overflow: hidden;\n  position: relative;\n  touch-action: none;\n  -webkit-touch-callout: none;\n  user-select: none;\n}\n\n:host([background]) {\n  background-color: #fff;\n  background-image: repeating-linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc), repeating-linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc);\n  background-image: repeating-conic-gradient(#ccc 0 25%, #fff 0 50%);\n  background-position: 0 0, 0.5rem 0.5rem;\n  background-size: 1rem 1rem;\n}\n\n:host([disabled]) {\n  pointer-events: none;\n}\n\n:host([disabled])::after {\n  bottom: 0;\n  content: \"\";\n  cursor: not-allowed;\n  display: block;\n  left: 0;\n  pointer-events: none;\n  position: absolute;\n  right: 0;\n  top: 0;\n}\n`;\n"
  },
  {
    "path": "packages/element-canvas/tests/index.spec.ts",
    "content": "import {\n  ACTION_MOVE,\n  ACTION_SCALE,\n  ATTRIBUTE_ACTION,\n  EVENT_ACTION,\n  EVENT_ACTION_END,\n  EVENT_ACTION_MOVE,\n  EVENT_ACTION_START,\n  EVENT_POINTER_DOWN,\n  EVENT_POINTER_MOVE,\n  EVENT_POINTER_UP,\n  EVENT_WHEEL,\n  HAS_POINTER_EVENT,\n  IS_TOUCH_DEVICE,\n} from '@cropper/utils';\nimport CropperCanvas from '../src';\n\nCropperCanvas.$define();\n\ndescribe('CropperCanvas', () => {\n  describe('properties', () => {\n    describe('background', () => {\n      it('should be `false` by default', () => {\n        const element = new CropperCanvas();\n\n        expect(element.background).toBe(false);\n      });\n\n      it('should be `true`', () => {\n        const element = new CropperCanvas();\n\n        element.setAttribute('background', '');\n        expect(element.background).toBe(true);\n      });\n    });\n\n    describe('disabled', () => {\n      it('should be `false` by default', () => {\n        const element = new CropperCanvas();\n\n        expect(element.disabled).toBe(false);\n      });\n\n      it('should be `true`', () => {\n        const element = new CropperCanvas();\n\n        element.setAttribute('disabled', '');\n        expect(element.disabled).toBe(true);\n      });\n    });\n\n    describe('scaleStep', () => {\n      it('should be `0.1` by default', () => {\n        const element = new CropperCanvas();\n\n        expect(element.scaleStep).toBe(0.1);\n      });\n\n      it('should be `0.2`', () => {\n        const element = new CropperCanvas();\n\n        element.setAttribute('scale-step', '0.2');\n        expect(element.scaleStep).toBe(0.2);\n      });\n    });\n\n    describe('themeColor', () => {\n      it('should be `\"#39f\"` by default', () => {\n        const element = new CropperCanvas();\n\n        expect(element.themeColor).toBe('#39f');\n      });\n\n      it('should be \"#000\"', () => {\n        const element = new CropperCanvas();\n\n        element.setAttribute('theme-color', '#000');\n        expect(element.themeColor).toBe('#000');\n      });\n    });\n  });\n\n  describe('events', () => {\n    const TouchEvent = IS_TOUCH_DEVICE ? window.TouchEvent : window.MouseEvent;\n    const PointerEvent = HAS_POINTER_EVENT ? window.PointerEvent : TouchEvent;\n    const pointerEventOptions = {\n      bubbles: true,\n      cancelable: true,\n      composed: true,\n      button: 0,\n      buttons: 1,\n    };\n    const wheelEventOptions = {\n      deltaY: 1,\n    };\n\n    describe(EVENT_ACTION, () => {\n      it('should trigger the `action` event', (done) => {\n        const element = new CropperCanvas();\n\n        document.body.appendChild(element);\n        element.addEventListener(EVENT_ACTION, (event: Event) => {\n          expect(event.type).toBe(EVENT_ACTION);\n          done();\n        });\n        element.dispatchEvent(new WheelEvent(EVENT_WHEEL, wheelEventOptions));\n      });\n\n      it('should have expected properties in `event.detail`', (done) => {\n        const element = new CropperCanvas();\n\n        document.body.appendChild(element);\n        element.addEventListener(EVENT_ACTION, (event: Event) => {\n          const { detail } = event as CustomEvent;\n\n          expect(detail.action).toBe(ACTION_SCALE);\n          expect(detail.scale).toBeLessThan(0);\n          expect(detail.relatedEvent).toBeInstanceOf(WheelEvent);\n          done();\n        });\n        element.dispatchEvent(new WheelEvent(EVENT_WHEEL, wheelEventOptions));\n      });\n    });\n\n    describe(EVENT_ACTION_START, () => {\n      it('should trigger the `actionstart` event', (done) => {\n        const element = new CropperCanvas();\n\n        element.setAttribute(ATTRIBUTE_ACTION, ACTION_MOVE);\n        document.body.appendChild(element);\n        element.addEventListener(EVENT_ACTION_START, (event: Event) => {\n          expect(event.type).toBe(EVENT_ACTION_START);\n          done();\n        });\n        element.dispatchEvent(new PointerEvent(EVENT_POINTER_DOWN, pointerEventOptions));\n        element.dispatchEvent(new PointerEvent(EVENT_POINTER_UP, pointerEventOptions));\n      });\n\n      it('should have expected properties in `event.detail`', (done) => {\n        const element = new CropperCanvas();\n\n        element.setAttribute(ATTRIBUTE_ACTION, ACTION_MOVE);\n        document.body.appendChild(element);\n        element.addEventListener(EVENT_ACTION_START, (event: Event) => {\n          const { detail } = event as CustomEvent;\n\n          expect(detail.action).toBe(ACTION_MOVE);\n          expect(detail.relatedEvent).toBeInstanceOf(PointerEvent);\n          done();\n        });\n        element.dispatchEvent(new PointerEvent(EVENT_POINTER_DOWN, pointerEventOptions));\n        element.dispatchEvent(new PointerEvent(EVENT_POINTER_UP, pointerEventOptions));\n      });\n    });\n\n    describe(EVENT_ACTION_MOVE, () => {\n      it('should trigger the `actionmove` event', (done) => {\n        const element = new CropperCanvas();\n\n        element.setAttribute(ATTRIBUTE_ACTION, ACTION_MOVE);\n        document.body.appendChild(element);\n        element.addEventListener(EVENT_ACTION_MOVE, (event: Event) => {\n          expect(event.type).toBe(EVENT_ACTION_MOVE);\n          done();\n        });\n        element.dispatchEvent(new PointerEvent(EVENT_POINTER_DOWN, pointerEventOptions));\n        element.dispatchEvent(new PointerEvent(EVENT_POINTER_MOVE, pointerEventOptions));\n        element.dispatchEvent(new PointerEvent(EVENT_POINTER_UP, pointerEventOptions));\n      });\n\n      it('should have expected properties in `event.detail`', (done) => {\n        const element = new CropperCanvas();\n\n        element.setAttribute(ATTRIBUTE_ACTION, ACTION_MOVE);\n        document.body.appendChild(element);\n        element.addEventListener(EVENT_ACTION_MOVE, (event: Event) => {\n          const { detail } = event as CustomEvent;\n\n          expect(detail.action).toBe(ACTION_MOVE);\n          expect(detail.relatedEvent).toBeInstanceOf(PointerEvent);\n          done();\n        });\n        element.dispatchEvent(new PointerEvent(EVENT_POINTER_DOWN, pointerEventOptions));\n        element.dispatchEvent(new PointerEvent(EVENT_POINTER_MOVE, pointerEventOptions));\n        element.dispatchEvent(new PointerEvent(EVENT_POINTER_UP, pointerEventOptions));\n      });\n    });\n\n    describe(EVENT_ACTION_END, () => {\n      it('should trigger the `actionend` event', (done) => {\n        const element = new CropperCanvas();\n\n        element.setAttribute(ATTRIBUTE_ACTION, ACTION_MOVE);\n        document.body.appendChild(element);\n        element.addEventListener(EVENT_ACTION_END, (event: Event) => {\n          expect(event.type).toBe(EVENT_ACTION_END);\n          done();\n        });\n        element.dispatchEvent(new PointerEvent(EVENT_POINTER_DOWN, pointerEventOptions));\n        element.dispatchEvent(new PointerEvent(EVENT_POINTER_UP, pointerEventOptions));\n      });\n\n      it('should have expected properties in `event.detail`', (done) => {\n        const element = new CropperCanvas();\n\n        element.setAttribute(ATTRIBUTE_ACTION, ACTION_MOVE);\n        document.body.appendChild(element);\n        element.addEventListener(EVENT_ACTION_END, (event: Event) => {\n          const { detail } = event as CustomEvent;\n\n          expect(detail.action).toBe(ACTION_MOVE);\n          expect(detail.relatedEvent).toBeInstanceOf(PointerEvent);\n          done();\n        });\n        element.dispatchEvent(new PointerEvent(EVENT_POINTER_DOWN, pointerEventOptions));\n        element.dispatchEvent(new PointerEvent(EVENT_POINTER_UP, pointerEventOptions));\n      });\n    });\n  });\n\n  describe('methods', () => {\n    describe('$toCanvas', () => {\n      it('should return a promise that resolves the generated canvas element', (done) => {\n        const element = new CropperCanvas();\n\n        document.body.appendChild(element);\n\n        const promise = element.$toCanvas();\n\n        expect(promise).toBeInstanceOf(Promise);\n        promise.then((canvas) => {\n          expect(canvas).toBeInstanceOf(HTMLCanvasElement);\n          done();\n        });\n      });\n\n      it('should throw error when it is not connected to the DOM', (done) => {\n        const element = new CropperCanvas();\n\n        element.$toCanvas().catch((error) => {\n          expect(error.message).toBe('The current element is not connected to the DOM.');\n          done();\n        });\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/element-crosshair/README.md",
    "content": "# @cropper/element-crosshair\n\n> A custom crosshair element for the Cropper.\n\n## Main npm package files\n\n```text\ndist/\n├── element-crosshair.js         (UMD, bundled)\n├── element-crosshair.min.js     (UMD, bundled, compressed)\n├── element-crosshair.raw.js     (UMD, unbundled, default)\n├── element-crosshair.esm.js     (ECMAScript Module, bundled)\n├── element-crosshair.esm.min.js (ECMAScript Module, bundled, compressed)\n├── element-crosshair.esm.raw.js (ECMAScript Module, unbundled)\n└── element-crosshair.d.ts       (TypeScript Declaration File)\n```\n\n## Getting started\n\n### Installation\n\n```sh\nnpm install @cropper/element-crosshair\n```\n\n### Usage\n\n```js\nimport CropperCrosshair from '@cropper/element-crosshair';\n\nCropperCrosshair.$define();\n```\n\n```html\n<cropper-crosshair></cropper-crosshair>\n```\n\n## Versioning\n\nMaintained under the [Semantic Versioning guidelines](https://semver.org).\n\n## License\n\n[MIT](https://opensource.org/licenses/MIT)\n"
  },
  {
    "path": "packages/element-crosshair/api-extractor.json",
    "content": "{\n  \"extends\": \"../../api-extractor.json\",\n  \"mainEntryPointFilePath\": \"./.temp/packages/<unscopedPackageName>/src/index.d.ts\",\n  \"dtsRollup\": {\n    \"publicTrimmedFilePath\": \"./dist/<unscopedPackageName>.d.ts\"\n  }\n}\n"
  },
  {
    "path": "packages/element-crosshair/package.json",
    "content": "{\n  \"name\": \"@cropper/element-crosshair\",\n  \"version\": \"2.1.0\",\n  \"description\": \"A custom crosshair element for the Cropper.\",\n  \"main\": \"dist/element-crosshair.raw.js\",\n  \"module\": \"dist/element-crosshair.esm.raw.js\",\n  \"types\": \"dist/element-crosshair.d.ts\",\n  \"unpkg\": \"dist/element-crosshair.js\",\n  \"jsdelivr\": \"dist/element-crosshair.js\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"import\": {\n        \"types\": \"./dist/element-crosshair.d.ts\",\n        \"node\": {\n          \"production\": \"./dist/element-crosshair.esm.min.js\",\n          \"development\": \"./dist/element-crosshair.esm.js\",\n          \"default\": \"./dist/element-crosshair.esm.raw.js\"\n        },\n        \"default\": \"./dist/element-crosshair.esm.raw.js\"\n      },\n      \"require\": {\n        \"types\": \"./dist/element-crosshair.d.ts\",\n        \"node\": {\n          \"production\": \"./dist/element-crosshair.min.js\",\n          \"development\": \"./dist/element-crosshair.js\",\n          \"default\": \"./dist/element-crosshair.raw.js\"\n        },\n        \"default\": \"./dist/element-crosshair.raw.js\"\n      }\n    },\n    \"./dist/*\": \"./dist/*\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"scripts\": {\n    \"api-extractor\": \"api-extractor run --local --verbose\",\n    \"build\": \"npm run tsc && npm run api-extractor && npm run rollup\",\n    \"clean\": \"del-cli dist .temp\",\n    \"release\": \"npm run clean && npm run build\",\n    \"rollup\": \"rollup -c ../../rollup.config.js\",\n    \"tsc\": \"tsc --outDir ./.temp --declaration --emitDeclarationOnly\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/fengyuanchen/cropperjs.git\",\n    \"directory\": \"packages/element-crosshair\"\n  },\n  \"keywords\": [\n    \"crosshair\",\n    \"cropper\",\n    \"cropper.js\",\n    \"cropper-element\",\n    \"custom-element\",\n    \"web-component\"\n  ],\n  \"author\": \"Chen Fengyuan (https://chenfengyuan.com/)\",\n  \"license\": \"MIT\",\n  \"bugs\": \"https://github.com/fengyuanchen/cropperjs/issues\",\n  \"homepage\": \"https://github.com/fengyuanchen/cropperjs/tree/main/packages/element-crosshair/#readme\",\n  \"dependencies\": {\n    \"@cropper/element\": \"^2.1.0\",\n    \"@cropper/utils\": \"^2.1.0\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  }\n}\n"
  },
  {
    "path": "packages/element-crosshair/src/index.ts",
    "content": "import CropperElement from '@cropper/element';\nimport { CROPPER_CROSSHAIR } from '@cropper/utils';\nimport style from './style';\n\nexport default class CropperCrosshair extends CropperElement {\n  static $name = CROPPER_CROSSHAIR;\n\n  static $version = '__VERSION__';\n\n  protected $style = style;\n\n  centered = false;\n\n  slottable = false;\n\n  themeColor = 'rgba(238, 238, 238, 0.5)';\n\n  protected static get observedAttributes(): string[] {\n    return super.observedAttributes.concat([\n      'centered',\n    ]);\n  }\n}\n"
  },
  {
    "path": "packages/element-crosshair/src/style.ts",
    "content": "export default `\n:host {\n  display: inline-block;\n  height: 1em;\n  position: relative;\n  touch-action: none;\n  user-select: none;\n  vertical-align: middle;\n  width: 1em;\n}\n\n:host::before,\n:host::after {\n  background-color: var(--theme-color);\n  content: \"\";\n  display: block;\n  position: absolute;\n}\n\n:host::before {\n  height: 1px;\n  left: 0;\n  top: 50%;\n  transform: translateY(-50%);\n  width: 100%;\n}\n\n:host::after {\n  height: 100%;\n  left: 50%;\n  top: 0;\n  transform: translateX(-50%);\n  width: 1px;\n}\n\n:host([centered]) {\n  left: 50%;\n  position: absolute;\n  top: 50%;\n  transform: translate(-50%, -50%);\n}\n`;\n"
  },
  {
    "path": "packages/element-crosshair/tests/index.spec.ts",
    "content": "import CropperCrosshair from '../src';\n\nCropperCrosshair.$define();\n\ndescribe('CropperCrosshair', () => {\n  describe('properties', () => {\n    describe('centered', () => {\n      it('should be `false` by default', () => {\n        const element = new CropperCrosshair();\n\n        expect(element.centered).toBe(false);\n      });\n\n      it('should be `true`', () => {\n        const element = new CropperCrosshair();\n\n        element.setAttribute('centered', '');\n        expect(element.centered).toBe(true);\n      });\n    });\n\n    describe('slottable', () => {\n      it('should be `false` by default', () => {\n        const element = new CropperCrosshair();\n\n        expect(element.slottable).toBe(false);\n      });\n\n      it('should be `true`', () => {\n        const element = new CropperCrosshair();\n\n        element.setAttribute('slottable', '');\n        expect(element.slottable).toBe(true);\n      });\n    });\n\n    describe('themeColor', () => {\n      it('should be `\"rgba(238, 238, 238, 0.5)\"` by default', () => {\n        const element = new CropperCrosshair();\n\n        expect(element.themeColor).toBe('rgba(238, 238, 238, 0.5)');\n      });\n\n      it('should be \"#000\"', () => {\n        const element = new CropperCrosshair();\n\n        element.setAttribute('theme-color', '#000');\n        expect(element.themeColor).toBe('#000');\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/element-grid/README.md",
    "content": "# @cropper/element-gird\n\n> A custom gird element for the Cropper.\n\n## Main npm package files\n\n```text\ndist/\n├── element-gird.js         (UMD, bundled)\n├── element-gird.min.js     (UMD, bundled, compressed)\n├── element-gird.raw.js     (UMD, unbundled, default)\n├── element-gird.esm.js     (ECMAScript Module, bundled)\n├── element-gird.esm.min.js (ECMAScript Module, bundled, compressed)\n├── element-gird.esm.raw.js (ECMAScript Module, unbundled)\n└── element-gird.d.ts       (TypeScript Declaration File)\n```\n\n## Getting started\n\n### Installation\n\n```sh\nnpm install @cropper/element-gird\n```\n\n### Usage\n\n```js\nimport CropperGrid from '@cropper/element-gird';\n\nCropperGrid.$define();\n```\n\n```html\n<cropper-gird></cropper-gird>\n```\n\n## Versioning\n\nMaintained under the [Semantic Versioning guidelines](https://semver.org).\n\n## License\n\n[MIT](https://opensource.org/licenses/MIT)\n"
  },
  {
    "path": "packages/element-grid/api-extractor.json",
    "content": "{\n  \"extends\": \"../../api-extractor.json\",\n  \"mainEntryPointFilePath\": \"./.temp/packages/<unscopedPackageName>/src/index.d.ts\",\n  \"dtsRollup\": {\n    \"publicTrimmedFilePath\": \"./dist/<unscopedPackageName>.d.ts\"\n  }\n}\n"
  },
  {
    "path": "packages/element-grid/package.json",
    "content": "{\n  \"name\": \"@cropper/element-grid\",\n  \"version\": \"2.1.0\",\n  \"description\": \"A custom grid element for the Cropper.\",\n  \"main\": \"dist/element-grid.raw.js\",\n  \"module\": \"dist/element-grid.esm.raw.js\",\n  \"types\": \"dist/element-grid.d.ts\",\n  \"unpkg\": \"dist/element-grid.js\",\n  \"jsdelivr\": \"dist/element-grid.js\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"import\": {\n        \"types\": \"./dist/element-grid.d.ts\",\n        \"node\": {\n          \"production\": \"./dist/element-grid.esm.min.js\",\n          \"development\": \"./dist/element-grid.esm.js\",\n          \"default\": \"./dist/element-grid.esm.raw.js\"\n        },\n        \"default\": \"./dist/element-grid.esm.raw.js\"\n      },\n      \"require\": {\n        \"types\": \"./dist/element-grid.d.ts\",\n        \"node\": {\n          \"production\": \"./dist/element-grid.min.js\",\n          \"development\": \"./dist/element-grid.js\",\n          \"default\": \"./dist/element-grid.raw.js\"\n        },\n        \"default\": \"./dist/element-grid.raw.js\"\n      }\n    },\n    \"./dist/*\": \"./dist/*\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"scripts\": {\n    \"api-extractor\": \"api-extractor run --local --verbose\",\n    \"build\": \"npm run tsc && npm run api-extractor && npm run rollup\",\n    \"clean\": \"del-cli dist .temp\",\n    \"release\": \"npm run clean && npm run build\",\n    \"rollup\": \"rollup -c ../../rollup.config.js\",\n    \"tsc\": \"tsc --outDir ./.temp --declaration --emitDeclarationOnly\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/fengyuanchen/cropperjs.git\",\n    \"directory\": \"packages/element-grid\"\n  },\n  \"keywords\": [\n    \"grid\",\n    \"cropper\",\n    \"cropper.js\",\n    \"cropper-element\",\n    \"custom-element\",\n    \"web-component\"\n  ],\n  \"author\": \"Chen Fengyuan (https://chenfengyuan.com/)\",\n  \"license\": \"MIT\",\n  \"bugs\": \"https://github.com/fengyuanchen/cropperjs/issues\",\n  \"homepage\": \"https://github.com/fengyuanchen/cropperjs/tree/main/packages/element-grid/#readme\",\n  \"dependencies\": {\n    \"@cropper/element\": \"^2.1.0\",\n    \"@cropper/utils\": \"^2.1.0\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  }\n}\n"
  },
  {
    "path": "packages/element-grid/src/index.ts",
    "content": "import CropperElement from '@cropper/element';\nimport { CROPPER_GIRD } from '@cropper/utils';\nimport style from './style';\n\nexport default class CropperGrid extends CropperElement {\n  static $name = CROPPER_GIRD;\n\n  static $version = '__VERSION__';\n\n  protected $style = style;\n\n  bordered = false;\n\n  columns = 3;\n\n  covered = false;\n\n  rows = 3;\n\n  slottable = false;\n\n  themeColor = 'rgba(238, 238, 238, 0.5)';\n\n  protected static get observedAttributes(): string[] {\n    return super.observedAttributes.concat([\n      'bordered',\n      'columns',\n      'covered',\n      'rows',\n    ]);\n  }\n\n  protected $propertyChangedCallback(name: string, oldValue: unknown, newValue: unknown): void {\n    if (Object.is(newValue, oldValue)) {\n      return;\n    }\n\n    super.$propertyChangedCallback(name, oldValue, newValue);\n\n    if (name === 'rows' || name === 'columns') {\n      this.$nextTick(() => {\n        this.$render();\n      });\n    }\n  }\n\n  protected connectedCallback(): void {\n    super.connectedCallback();\n    this.$render();\n  }\n\n  protected $render(): void {\n    const shadow = this.$getShadowRoot();\n    const fragment = document.createDocumentFragment();\n\n    for (let i = 0; i < this.rows; i += 1) {\n      const row = document.createElement('span');\n\n      row.setAttribute('role', 'row');\n\n      for (let j = 0; j < this.columns; j += 1) {\n        const column = document.createElement('span');\n\n        column.setAttribute('role', 'gridcell');\n        row.appendChild(column);\n      }\n\n      fragment.appendChild(row);\n    }\n\n    if (shadow) {\n      shadow.innerHTML = '';\n      shadow.appendChild(fragment);\n    }\n  }\n}\n"
  },
  {
    "path": "packages/element-grid/src/style.ts",
    "content": "export default `\n:host {\n  display: flex;\n  flex-direction: column;\n  position: relative;\n  touch-action: none;\n  user-select: none;\n}\n\n:host([bordered]) {\n  border: 1px dashed var(--theme-color);\n}\n\n:host([covered]) {\n  bottom: 0;\n  left: 0;\n  position: absolute;\n  right: 0;\n  top: 0;\n}\n\n:host > span {\n  display: flex;\n  flex: 1;\n}\n\n:host > span + span {\n  border-top: 1px dashed var(--theme-color);\n}\n\n:host > span > span {\n  flex: 1;\n}\n\n:host > span > span + span {\n  border-left: 1px dashed var(--theme-color);\n}\n`;\n"
  },
  {
    "path": "packages/element-grid/tests/index.spec.ts",
    "content": "import CropperGrid from '../src';\n\nCropperGrid.$define();\n\ndescribe('CropperGrid', () => {\n  describe('properties', () => {\n    describe('bordered', () => {\n      it('should be `false` by default', () => {\n        const element = new CropperGrid();\n\n        expect(element.bordered).toBe(false);\n      });\n\n      it('should be `true`', () => {\n        const element = new CropperGrid();\n\n        element.setAttribute('bordered', '');\n        expect(element.bordered).toBe(true);\n      });\n    });\n\n    describe('columns', () => {\n      it('should be `3` by default', () => {\n        const element = new CropperGrid();\n\n        expect(element.columns).toBe(3);\n      });\n\n      it('should be `2`', () => {\n        const element = new CropperGrid();\n\n        element.setAttribute('columns', '2');\n        expect(element.columns).toBe(2);\n      });\n    });\n\n    describe('covered', () => {\n      it('should be `false` by default', () => {\n        const element = new CropperGrid();\n\n        expect(element.covered).toBe(false);\n      });\n\n      it('should be `true`', () => {\n        const element = new CropperGrid();\n\n        element.setAttribute('covered', '');\n        expect(element.covered).toBe(true);\n      });\n    });\n\n    describe('rows', () => {\n      it('should be `3` by default', () => {\n        const element = new CropperGrid();\n\n        expect(element.rows).toBe(3);\n      });\n\n      it('should be `2`', () => {\n        const element = new CropperGrid();\n\n        element.setAttribute('rows', '2');\n        expect(element.rows).toBe(2);\n      });\n    });\n\n    describe('slottable', () => {\n      it('should be `false` by default', () => {\n        const element = new CropperGrid();\n\n        expect(element.slottable).toBe(false);\n      });\n\n      it('should be `true`', () => {\n        const element = new CropperGrid();\n\n        element.setAttribute('slottable', '');\n        expect(element.slottable).toBe(true);\n      });\n    });\n\n    describe('themeColor', () => {\n      it('should be `\"rgba(238, 238, 238, 0.5)\"` by default', () => {\n        const element = new CropperGrid();\n\n        expect(element.themeColor).toBe('rgba(238, 238, 238, 0.5)');\n      });\n\n      it('should be \"#000\"', () => {\n        const element = new CropperGrid();\n\n        element.setAttribute('theme-color', '#000');\n        expect(element.themeColor).toBe('#000');\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/element-handle/README.md",
    "content": "# @cropper/element-handle\n\n> A custom handle element for the Cropper.\n\n## Main npm package files\n\n```text\ndist/\n├── element-handle.js         (UMD, bundled)\n├── element-handle.min.js     (UMD, bundled, compressed)\n├── element-handle.raw.js     (UMD, unbundled, default)\n├── element-handle.esm.js     (ECMAScript Module, bundled)\n├── element-handle.esm.min.js (ECMAScript Module, bundled, compressed)\n├── element-handle.esm.raw.js (ECMAScript Module, unbundled)\n└── element-handle.d.ts       (TypeScript Declaration File)\n```\n\n## Getting started\n\n### Installation\n\n```sh\nnpm install @cropper/element-handle\n```\n\n### Usage\n\n```js\nimport CropperHandle from '@cropper/element-handle';\n\nCropperHandle.$define();\n```\n\n```html\n<cropper-handle></cropper-handle>\n```\n\n## Versioning\n\nMaintained under the [Semantic Versioning guidelines](https://semver.org).\n\n## License\n\n[MIT](https://opensource.org/licenses/MIT)\n"
  },
  {
    "path": "packages/element-handle/api-extractor.json",
    "content": "{\n  \"extends\": \"../../api-extractor.json\",\n  \"mainEntryPointFilePath\": \"./.temp/packages/<unscopedPackageName>/src/index.d.ts\",\n  \"dtsRollup\": {\n    \"publicTrimmedFilePath\": \"./dist/<unscopedPackageName>.d.ts\"\n  }\n}\n"
  },
  {
    "path": "packages/element-handle/package.json",
    "content": "{\n  \"name\": \"@cropper/element-handle\",\n  \"version\": \"2.1.0\",\n  \"description\": \"A custom handle element for the Cropper.\",\n  \"main\": \"dist/element-handle.raw.js\",\n  \"module\": \"dist/element-handle.esm.raw.js\",\n  \"types\": \"dist/element-handle.d.ts\",\n  \"unpkg\": \"dist/element-handle.js\",\n  \"jsdelivr\": \"dist/element-handle.js\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"import\": {\n        \"types\": \"./dist/element-handle.d.ts\",\n        \"node\": {\n          \"production\": \"./dist/element-handle.esm.min.js\",\n          \"development\": \"./dist/element-handle.esm.js\",\n          \"default\": \"./dist/element-handle.esm.raw.js\"\n        },\n        \"default\": \"./dist/element-handle.esm.raw.js\"\n      },\n      \"require\": {\n        \"types\": \"./dist/element-handle.d.ts\",\n        \"node\": {\n          \"production\": \"./dist/element-handle.min.js\",\n          \"development\": \"./dist/element-handle.js\",\n          \"default\": \"./dist/element-handle.raw.js\"\n        },\n        \"default\": \"./dist/element-handle.raw.js\"\n      }\n    },\n    \"./dist/*\": \"./dist/*\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"scripts\": {\n    \"api-extractor\": \"api-extractor run --local --verbose\",\n    \"build\": \"npm run tsc && npm run api-extractor && npm run rollup\",\n    \"clean\": \"del-cli dist .temp\",\n    \"release\": \"npm run clean && npm run build\",\n    \"rollup\": \"rollup -c ../../rollup.config.js\",\n    \"tsc\": \"tsc --outDir ./.temp --declaration --emitDeclarationOnly\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/fengyuanchen/cropperjs.git\",\n    \"directory\": \"packages/element-handle\"\n  },\n  \"keywords\": [\n    \"handle\",\n    \"cropper\",\n    \"cropper.js\",\n    \"cropper-element\",\n    \"custom-element\",\n    \"web-component\"\n  ],\n  \"author\": \"Chen Fengyuan (https://chenfengyuan.com/)\",\n  \"license\": \"MIT\",\n  \"bugs\": \"https://github.com/fengyuanchen/cropperjs/issues\",\n  \"homepage\": \"https://github.com/fengyuanchen/cropperjs/tree/main/packages/element-handle/#readme\",\n  \"dependencies\": {\n    \"@cropper/element\": \"^2.1.0\",\n    \"@cropper/utils\": \"^2.1.0\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  }\n}\n"
  },
  {
    "path": "packages/element-handle/src/index.ts",
    "content": "import CropperElement from '@cropper/element';\nimport { ACTION_NONE, CROPPER_HANDLE } from '@cropper/utils';\nimport style from './style';\n\nexport default class CropperHandle extends CropperElement {\n  static $name = CROPPER_HANDLE;\n\n  static $version = '__VERSION__';\n\n  protected $onCanvasCropEnd: EventListener | null = null;\n\n  protected $onCanvasCropStart: EventListener | null = null;\n\n  protected $style = style;\n\n  action = ACTION_NONE;\n\n  plain = false;\n\n  slottable = false;\n\n  themeColor = 'rgba(51, 153, 255, 0.5)';\n\n  protected static get observedAttributes(): string[] {\n    return super.observedAttributes.concat([\n      'action',\n      'plain',\n    ]);\n  }\n}\n"
  },
  {
    "path": "packages/element-handle/src/style.ts",
    "content": "export default `\n:host {\n  background-color: var(--theme-color);\n  display: block;\n}\n\n:host([action=\"move\"]),\n:host([action=\"select\"]) {\n  height: 100%;\n  left: 0;\n  position: absolute;\n  top: 0;\n  width: 100%;\n}\n\n:host([action=\"move\"]) {\n  cursor: move;\n}\n\n:host([action=\"select\"]) {\n  cursor: crosshair;\n}\n\n:host([action$=\"-resize\"]) {\n  background-color: transparent;\n  height: 15px;\n  position: absolute;\n  width: 15px;\n}\n\n:host([action$=\"-resize\"])::after {\n  background-color: var(--theme-color);\n  content: \"\";\n  display: block;\n  height: 5px;\n  left: 50%;\n  top: 50%;\n  position: absolute;\n  width: 5px;\n  transform: translate(-50%, -50%);\n}\n\n:host([action=\"n-resize\"]),\n:host([action=\"s-resize\"]) {\n  cursor: ns-resize;\n  left: 50%;\n  transform: translateX(-50%);\n  width: 100%;\n}\n\n:host([action=\"n-resize\"]) {\n  top: -8px;\n}\n\n:host([action=\"s-resize\"]) {\n  bottom: -8px;\n}\n\n:host([action=\"e-resize\"]),\n:host([action=\"w-resize\"]) {\n  cursor: ew-resize;\n  height: 100%;\n  top: 50%;\n  transform: translateY(-50%);\n}\n\n:host([action=\"e-resize\"]) {\n  right: -8px;\n}\n\n:host([action=\"w-resize\"]) {\n  left: -8px;\n}\n\n:host([action=\"ne-resize\"]) {\n  cursor: nesw-resize;\n  right: -8px;\n  top: -8px;\n}\n\n:host([action=\"nw-resize\"]) {\n  cursor: nwse-resize;\n  left: -8px;\n  top: -8px;\n}\n\n:host([action=\"se-resize\"]) {\n  cursor: nwse-resize;\n  right: -8px;\n  bottom: -8px;\n}\n\n:host([action=\"se-resize\"])::after {\n  height: 15px;\n  width: 15px;\n}\n\n@media (pointer: coarse) {\n  :host([action=\"se-resize\"])::after {\n    height: 10px;\n    width: 10px;\n  }\n}\n\n@media (pointer: fine) {\n  :host([action=\"se-resize\"])::after {\n    height: 5px;\n    width: 5px;\n  }\n}\n\n:host([action=\"sw-resize\"]) {\n  cursor: nesw-resize;\n  left: -8px;\n  bottom: -8px;\n}\n\n:host([plain]) {\n  background-color: transparent;\n}\n`;\n"
  },
  {
    "path": "packages/element-handle/tests/index.spec.ts",
    "content": "import CropperHandle from '../src';\n\nCropperHandle.$define();\n\ndescribe('CropperHandle', () => {\n  describe('properties', () => {\n    describe('action', () => {\n      it('should be `\"none\"` by default', () => {\n        const element = new CropperHandle();\n\n        expect(element.action).toBe('none');\n      });\n\n      it('should be \"move\"', () => {\n        const element = new CropperHandle();\n\n        element.setAttribute('action', 'move');\n        expect(element.action).toBe('move');\n      });\n    });\n\n    describe('plain', () => {\n      it('should be `false` by default', () => {\n        const element = new CropperHandle();\n\n        expect(element.plain).toBe(false);\n      });\n\n      it('should be `true`', () => {\n        const element = new CropperHandle();\n\n        element.setAttribute('plain', '');\n        expect(element.plain).toBe(true);\n      });\n    });\n\n    describe('slottable', () => {\n      it('should be `false` by default', () => {\n        const element = new CropperHandle();\n\n        expect(element.slottable).toBe(false);\n      });\n\n      it('should be `true`', () => {\n        const element = new CropperHandle();\n\n        element.setAttribute('slottable', '');\n        expect(element.slottable).toBe(true);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/element-image/README.md",
    "content": "# @cropper/element-image\n\n> A custom image element for the Cropper.\n\n## Main npm package files\n\n```text\ndist/\n├── element-image.js         (UMD, bundled)\n├── element-image.min.js     (UMD, bundled, compressed)\n├── element-image.raw.js     (UMD, unbundled, default)\n├── element-image.esm.js     (ECMAScript Module, bundled)\n├── element-image.esm.min.js (ECMAScript Module, bundled, compressed)\n├── element-image.esm.raw.js (ECMAScript Module, unbundled)\n└── element-image.d.ts       (TypeScript Declaration File)\n```\n\n## Getting started\n\n### Installation\n\n```sh\nnpm install @cropper/element-image\n```\n\n### Usage\n\n```js\nimport CropperImage from '@cropper/element-image';\n\nCropperImage.$define();\n```\n\n```html\n<cropper-image></cropper-image>\n```\n\n## Versioning\n\nMaintained under the [Semantic Versioning guidelines](https://semver.org).\n\n## License\n\n[MIT](https://opensource.org/licenses/MIT)\n"
  },
  {
    "path": "packages/element-image/api-extractor.json",
    "content": "{\n  \"extends\": \"../../api-extractor.json\",\n  \"mainEntryPointFilePath\": \"./.temp/packages/<unscopedPackageName>/src/index.d.ts\",\n  \"dtsRollup\": {\n    \"publicTrimmedFilePath\": \"./dist/<unscopedPackageName>.d.ts\"\n  }\n}\n"
  },
  {
    "path": "packages/element-image/package.json",
    "content": "{\n  \"name\": \"@cropper/element-image\",\n  \"version\": \"2.1.0\",\n  \"description\": \"A custom image element for the Cropper.\",\n  \"main\": \"dist/element-image.raw.js\",\n  \"module\": \"dist/element-image.esm.raw.js\",\n  \"types\": \"dist/element-image.d.ts\",\n  \"unpkg\": \"dist/element-image.js\",\n  \"jsdelivr\": \"dist/element-image.js\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"import\": {\n        \"types\": \"./dist/element-image.d.ts\",\n        \"node\": {\n          \"production\": \"./dist/element-image.esm.min.js\",\n          \"development\": \"./dist/element-image.esm.js\",\n          \"default\": \"./dist/element-image.esm.raw.js\"\n        },\n        \"default\": \"./dist/element-image.esm.raw.js\"\n      },\n      \"require\": {\n        \"types\": \"./dist/element-image.d.ts\",\n        \"node\": {\n          \"production\": \"./dist/element-image.min.js\",\n          \"development\": \"./dist/element-image.js\",\n          \"default\": \"./dist/element-image.raw.js\"\n        },\n        \"default\": \"./dist/element-image.raw.js\"\n      }\n    },\n    \"./dist/*\": \"./dist/*\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"scripts\": {\n    \"api-extractor\": \"api-extractor run --local --verbose\",\n    \"build\": \"npm run tsc && npm run api-extractor && npm run rollup\",\n    \"clean\": \"del-cli dist .temp\",\n    \"release\": \"npm run clean && npm run build\",\n    \"rollup\": \"rollup -c ../../rollup.config.js\",\n    \"tsc\": \"tsc --outDir ./.temp --declaration --emitDeclarationOnly\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/fengyuanchen/cropperjs.git\",\n    \"directory\": \"packages/element-image\"\n  },\n  \"keywords\": [\n    \"image\",\n    \"cropper\",\n    \"cropper.js\",\n    \"cropper-element\",\n    \"custom-element\",\n    \"web-component\"\n  ],\n  \"author\": \"Chen Fengyuan (https://chenfengyuan.com/)\",\n  \"license\": \"MIT\",\n  \"bugs\": \"https://github.com/fengyuanchen/cropperjs/issues\",\n  \"homepage\": \"https://github.com/fengyuanchen/cropperjs/tree/main/packages/element-image/#readme\",\n  \"dependencies\": {\n    \"@cropper/element\": \"^2.1.0\",\n    \"@cropper/element-canvas\": \"^2.1.0\",\n    \"@cropper/utils\": \"^2.1.0\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  }\n}\n"
  },
  {
    "path": "packages/element-image/src/index.ts",
    "content": "import CropperElement from '@cropper/element';\nimport type CropperCanvas from '@cropper/element-canvas';\nimport type CropperSelection from '@cropper/element-selection';\nimport {\n  ACTION_MOVE,\n  ACTION_NONE,\n  ACTION_ROTATE,\n  ACTION_SCALE,\n  ACTION_TRANSFORM,\n  CROPPER_CANVAS,\n  CROPPER_IMAGE,\n  CROPPER_SELECTION,\n  EVENT_ACTION,\n  EVENT_ACTION_END,\n  EVENT_ACTION_START,\n  EVENT_ERROR,\n  EVENT_LOAD,\n  EVENT_TRANSFORM,\n  isFunction,\n  isNumber,\n  multiplyMatrices,\n  off,\n  on,\n  once,\n  toAngleInRadian,\n} from '@cropper/utils';\nimport style from './style';\n\nconst canvasCache = new WeakMap();\nconst NATIVE_ATTRIBUTES = [\n  'alt',\n  'crossorigin',\n  'decoding',\n  'elementtiming',\n  'fetchpriority',\n  'loading',\n  'referrerpolicy',\n  'sizes',\n  'src',\n  'srcset',\n];\n\nexport default class CropperImage extends CropperElement {\n  static $name = CROPPER_IMAGE;\n\n  static $version = '__VERSION__';\n\n  protected $matrix = [1, 0, 0, 1, 0, 0];\n\n  protected $onLoad: EventListener | null = null;\n\n  protected $onCanvasAction: EventListener | null = null;\n\n  protected $onCanvasActionEnd: EventListener | null = null;\n\n  protected $onCanvasActionStart: EventListener | null = null;\n\n  protected $actionStartTarget: EventTarget | null = null;\n\n  protected $style = style;\n\n  readonly $image = new Image();\n\n  initialCenterSize = 'contain';\n\n  rotatable = false;\n\n  scalable = false;\n\n  skewable = false;\n\n  slottable = false;\n\n  translatable = false;\n\n  // Native attributes\n  alt = '';\n\n  crossorigin = '';\n\n  decoding = '';\n\n  elementtiming = '';\n\n  fetchpriority = '';\n\n  loading = '';\n\n  referrerpolicy = '';\n\n  sizes = '';\n\n  src = '';\n\n  srcset = '';\n\n  protected set $canvas(element: CropperCanvas) {\n    canvasCache.set(this, element);\n  }\n\n  protected get $canvas(): CropperCanvas {\n    return canvasCache.get(this);\n  }\n\n  protected static get observedAttributes(): string[] {\n    return super.observedAttributes.concat(NATIVE_ATTRIBUTES, [\n      'initial-center-size',\n      'rotatable',\n      'scalable',\n      'skewable',\n      'translatable',\n    ]);\n  }\n\n  protected attributeChangedCallback(name: string, oldValue: string, newValue: string): void {\n    if (Object.is(newValue, oldValue)) {\n      return;\n    }\n\n    super.attributeChangedCallback(name, oldValue, newValue);\n\n    // Inherits the native attributes\n    if (NATIVE_ATTRIBUTES.includes(name)) {\n      this.$image.setAttribute(name, newValue);\n    }\n  }\n\n  protected $propertyChangedCallback(name: string, oldValue: unknown, newValue: unknown): void {\n    if (Object.is(newValue, oldValue)) {\n      return;\n    }\n\n    super.$propertyChangedCallback(name, oldValue, newValue);\n\n    switch (name) {\n      case 'initialCenterSize':\n        this.$nextTick(() => {\n          this.$center(newValue as string);\n        });\n        break;\n\n      default:\n    }\n  }\n\n  protected connectedCallback(): void {\n    super.connectedCallback();\n\n    const { $image } = this;\n    const $canvas: CropperCanvas | null = this.closest(this.$getTagNameOf(CROPPER_CANVAS));\n\n    if ($canvas) {\n      this.$canvas = $canvas;\n      this.$setStyles({\n        // Make it a block element to avoid side effects (#1074).\n        display: 'block',\n        position: 'absolute',\n      });\n\n      this.$onCanvasActionStart = (event: Event | CustomEvent) => {\n        this.$actionStartTarget = (event as CustomEvent).detail?.relatedEvent?.target;\n      };\n      this.$onCanvasActionEnd = () => {\n        this.$actionStartTarget = null;\n      };\n      this.$onCanvasAction = this.$handleAction.bind(this);\n      on($canvas, EVENT_ACTION_START, this.$onCanvasActionStart);\n      on($canvas, EVENT_ACTION_END, this.$onCanvasActionEnd);\n      on($canvas, EVENT_ACTION, this.$onCanvasAction);\n    }\n\n    this.$onLoad = this.$handleLoad.bind(this);\n    on($image, EVENT_LOAD, this.$onLoad);\n    this.$getShadowRoot().appendChild($image);\n  }\n\n  protected disconnectedCallback(): void {\n    const { $image, $canvas } = this;\n\n    if ($canvas) {\n      if (this.$onCanvasActionStart) {\n        off($canvas, EVENT_ACTION_START, this.$onCanvasActionStart);\n        this.$onCanvasActionStart = null;\n      }\n\n      if (this.$onCanvasActionEnd) {\n        off($canvas, EVENT_ACTION_END, this.$onCanvasActionEnd);\n        this.$onCanvasActionEnd = null;\n      }\n\n      if (this.$onCanvasAction) {\n        off($canvas, EVENT_ACTION, this.$onCanvasAction);\n        this.$onCanvasAction = null;\n      }\n    }\n\n    if ($image && this.$onLoad) {\n      off($image, EVENT_LOAD, this.$onLoad);\n      this.$onLoad = null;\n    }\n\n    this.$getShadowRoot().removeChild($image);\n    super.disconnectedCallback();\n  }\n\n  protected $handleLoad(): void {\n    const { $image } = this;\n\n    this.$setStyles({\n      width: $image.naturalWidth,\n      height: $image.naturalHeight,\n    });\n\n    if (this.$canvas) {\n      this.$center(this.initialCenterSize);\n    }\n  }\n\n  protected $handleAction(event: Event | CustomEvent): void {\n    if (this.hidden || !(this.rotatable || this.scalable || this.translatable)) {\n      return;\n    }\n\n    const { $canvas } = this;\n    const { detail } = event as CustomEvent;\n\n    if (detail) {\n      const { relatedEvent } = detail;\n      let { action } = detail;\n\n      if (action === ACTION_TRANSFORM && (!this.rotatable || !this.scalable)) {\n        if (this.rotatable) {\n          action = ACTION_ROTATE;\n        } else if (this.scalable) {\n          action = ACTION_SCALE;\n        } else {\n          action = ACTION_NONE;\n        }\n      }\n\n      switch (action) {\n        case ACTION_MOVE:\n          if (this.translatable) {\n            let $selection: CropperSelection | null = null;\n\n            if (relatedEvent) {\n              $selection = relatedEvent.target.closest(this.$getTagNameOf(CROPPER_SELECTION));\n            }\n\n            if (!$selection) {\n              $selection = $canvas.querySelector(this.$getTagNameOf(CROPPER_SELECTION));\n            }\n\n            if ($selection && $selection.multiple && !$selection.active) {\n              $selection = $canvas.querySelector(`${this.$getTagNameOf(CROPPER_SELECTION)}[active]`);\n            }\n\n            if (!$selection || $selection.hidden || !$selection.movable || $selection.dynamic\n              || !(this.$actionStartTarget && $selection.contains(this.$actionStartTarget as Node))\n            ) {\n              this.$move(detail.endX - detail.startX, detail.endY - detail.startY);\n            }\n          }\n          break;\n\n        case ACTION_ROTATE:\n          if (this.rotatable) {\n            if (relatedEvent) {\n              const { x, y } = this.getBoundingClientRect();\n\n              this.$rotate(\n                detail.rotate,\n                relatedEvent.clientX - x,\n                relatedEvent.clientY - y,\n              );\n            } else {\n              this.$rotate(detail.rotate);\n            }\n          }\n          break;\n\n        case ACTION_SCALE:\n          if (this.scalable) {\n            if (relatedEvent) {\n              const $selection: CropperSelection | null = relatedEvent.target.closest(\n                this.$getTagNameOf(CROPPER_SELECTION),\n              );\n\n              if (\n                !$selection\n                || !$selection.zoomable\n                || ($selection.zoomable && $selection.dynamic)\n              ) {\n                const { x, y } = this.getBoundingClientRect();\n\n                this.$zoom(\n                  detail.scale,\n                  relatedEvent.clientX - x,\n                  relatedEvent.clientY - y,\n                );\n              }\n            } else {\n              this.$zoom(detail.scale);\n            }\n          }\n          break;\n\n        case ACTION_TRANSFORM:\n          if (this.rotatable && this.scalable) {\n            const { rotate } = detail;\n            let { scale } = detail;\n\n            if (scale < 0) {\n              scale = 1 / (1 - scale);\n            } else {\n              scale += 1;\n            }\n\n            const cos = Math.cos(rotate);\n            const sin = Math.sin(rotate);\n            const [scaleX, skewY, skewX, scaleY] = [\n              cos * scale,\n              sin * scale,\n              -sin * scale,\n              cos * scale,\n            ];\n\n            if (relatedEvent) {\n              const clientRect = this.getBoundingClientRect();\n              const x = relatedEvent.clientX - clientRect.x;\n              const y = relatedEvent.clientY - clientRect.y;\n              const [a, b, c, d] = this.$matrix;\n              const originX = clientRect.width / 2;\n              const originY = clientRect.height / 2;\n              const moveX = x - originX;\n              const moveY = y - originY;\n              const translateX = ((moveX * d) - (c * moveY)) / ((a * d) - (c * b));\n              const translateY = ((moveY * a) - (b * moveX)) / ((a * d) - (c * b));\n\n              /**\n               * Equals to\n               * this.$rotate(rotate, x, y);\n               * this.$scale(scale, x, y);\n               */\n              this.$transform(\n                scaleX,\n                skewY,\n                skewX,\n                scaleY,\n                translateX * (1 - scaleX) + translateY * skewX,\n                translateY * (1 - scaleY) + translateX * skewY,\n              );\n            } else {\n              /**\n               * Equals to\n               * this.$rotate(rotate);\n               * this.$scale(scale);\n               */\n              this.$transform(scaleX, skewY, skewX, scaleY, 0, 0);\n            }\n          }\n          break;\n\n        default:\n      }\n    }\n  }\n\n  /**\n   * Defers the callback to execute after successfully loading the image.\n   * @param {Function} [callback] The callback to execute after successfully loading the image.\n   * @returns {Promise} Returns a promise that resolves to the image element.\n   */\n  $ready(callback?: (image: HTMLImageElement) => unknown): Promise<HTMLImageElement> {\n    const { $image } = this;\n    const promise = new Promise<HTMLImageElement>((resolve, reject) => {\n      const error = new Error('Failed to load the image source');\n\n      if ($image.complete) {\n        if ($image.naturalWidth > 0 && $image.naturalHeight > 0) {\n          resolve($image);\n        } else {\n          reject(error);\n        }\n      } else {\n        const onLoad = () => {\n          // eslint-disable-next-line @typescript-eslint/no-use-before-define\n          off($image, EVENT_ERROR, onError);\n\n          // Ensure the image is fully rendered.\n          setTimeout(() => {\n            resolve($image);\n          });\n        };\n        const onError = () => {\n          off($image, EVENT_LOAD, onLoad);\n          reject(error);\n        };\n\n        once($image, EVENT_LOAD, onLoad);\n        once($image, EVENT_ERROR, onError);\n      }\n    });\n\n    if (isFunction(callback)) {\n      promise.then((image) => {\n        callback(image);\n        return image;\n      });\n    }\n\n    return promise;\n  }\n\n  /**\n   * Aligns the image to the center of its parent element.\n   * @param {string} [size] The size of the image.\n   * @returns {CropperImage} Returns `this` for chaining.\n   */\n  $center(size?: string): this {\n    const { parentElement } = this;\n\n    if (!parentElement) {\n      return this;\n    }\n\n    const container = parentElement.getBoundingClientRect();\n    const containerWidth = container.width;\n    const containerHeight = container.height;\n    const {\n      x,\n      y,\n      width,\n      height,\n    } = this.getBoundingClientRect();\n    const startX = x + (width / 2);\n    const startY = y + (height / 2);\n    const endX = container.x + (containerWidth / 2);\n    const endY = container.y + (containerHeight / 2);\n\n    this.$move(endX - startX, endY - startY);\n\n    if (size && (width !== containerWidth || height !== containerHeight)) {\n      const scaleX = containerWidth / width;\n      const scaleY = containerHeight / height;\n\n      switch (size) {\n        case 'cover':\n          this.$scale(Math.max(scaleX, scaleY));\n          break;\n\n        case 'contain':\n          this.$scale(Math.min(scaleX, scaleY));\n          break;\n\n        default:\n      }\n    }\n\n    return this;\n  }\n\n  /**\n   * Moves the image.\n   * @param {number} x The moving distance in the horizontal direction.\n   * @param {number} [y] The moving distance in the vertical direction.\n   * @returns {CropperImage} Returns `this` for chaining.\n   */\n  $move(x: number, y: number = x): this {\n    if (this.translatable && isNumber(x) && isNumber(y)) {\n      const [a, b, c, d] = this.$matrix;\n      const e = ((x * d) - (c * y)) / ((a * d) - (c * b));\n      const f = ((y * a) - (b * x)) / ((a * d) - (c * b));\n\n      this.$translate(e, f);\n    }\n\n    return this;\n  }\n\n  /**\n   * Moves the image to a specific position.\n   * @param {number} x The new position in the horizontal direction.\n   * @param {number} [y] The new position in the vertical direction.\n   * @returns {CropperImage} Returns `this` for chaining.\n   */\n  $moveTo(x: number, y: number = x): this {\n    if (this.translatable && isNumber(x) && isNumber(y)) {\n      const [a, b, c, d] = this.$matrix;\n      const e = ((x * d) - (c * y)) / ((a * d) - (c * b));\n      const f = ((y * a) - (b * x)) / ((a * d) - (c * b));\n\n      this.$setTransform(a, b, c, d, e, f);\n    }\n\n    return this;\n  }\n\n  /**\n   * Rotates the image.\n   * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/rotate}\n   * {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/rotate}\n   * @param {number|string} angle The rotation angle (in radians).\n   * @param {number} [x] The rotation origin in the horizontal, defaults to the center of the image.\n   * @param {number} [y] The rotation origin in the vertical, defaults to the center of the image.\n   * @returns {CropperImage} Returns `this` for chaining.\n   */\n  $rotate(angle: number | string, x?: number, y?: number): this {\n    if (this.rotatable) {\n      const radian = toAngleInRadian(angle);\n      const cos = Math.cos(radian);\n      const sin = Math.sin(radian);\n      const [scaleX, skewY, skewX, scaleY] = [cos, sin, -sin, cos];\n\n      if (isNumber(x) && isNumber(y)) {\n        const [a, b, c, d] = this.$matrix;\n        const { width, height } = this.getBoundingClientRect();\n        const originX = width / 2;\n        const originY = height / 2;\n        const moveX = x - originX;\n        const moveY = y - originY;\n        const translateX = ((moveX * d) - (c * moveY)) / ((a * d) - (c * b));\n        const translateY = ((moveY * a) - (b * moveX)) / ((a * d) - (c * b));\n\n        /**\n         * Equals to\n         * this.$translate(translateX, translateX);\n         * this.$rotate(angle);\n         * this.$translate(-translateX, -translateX);\n         */\n        this.$transform(\n          scaleX,\n          skewY,\n          skewX,\n          scaleY,\n          translateX * (1 - scaleX) - translateY * skewX,\n          translateY * (1 - scaleY) - translateX * skewY,\n        );\n      } else {\n        this.$transform(scaleX, skewY, skewX, scaleY, 0, 0);\n      }\n    }\n\n    return this;\n  }\n\n  /**\n   * Zooms the image.\n   * @param {number} scale The zoom factor. Positive numbers for zooming in, and negative numbers for zooming out.\n   * @param {number} [x] The zoom origin in the horizontal, defaults to the center of the image.\n   * @param {number} [y] The zoom origin in the vertical, defaults to the center of the image.\n   * @returns {CropperImage} Returns `this` for chaining.\n   */\n  $zoom(scale: number, x?: number, y?: number): this {\n    if (!this.scalable || scale === 0) {\n      return this;\n    }\n\n    if (scale < 0) {\n      scale = 1 / (1 - scale);\n    } else {\n      scale += 1;\n    }\n\n    if (isNumber(x) && isNumber(y)) {\n      const [a, b, c, d] = this.$matrix;\n      const { width, height } = this.getBoundingClientRect();\n      const originX = width / 2;\n      const originY = height / 2;\n      const moveX = x - originX;\n      const moveY = y - originY;\n      const translateX = ((moveX * d) - (c * moveY)) / ((a * d) - (c * b));\n      const translateY = ((moveY * a) - (b * moveX)) / ((a * d) - (c * b));\n\n      /**\n       * Equals to\n       * this.$translate(translateX, translateX);\n       * this.$scale(scale);\n       * this.$translate(-translateX, -translateX);\n       */\n      this.$transform(scale, 0, 0, scale, translateX * (1 - scale), translateY * (1 - scale));\n    } else {\n      this.$scale(scale);\n    }\n\n    return this;\n  }\n\n  /**\n   * Scales the image.\n   * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/scale}\n   * {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/scale}\n   * @param {number} x The scaling factor in the horizontal direction.\n   * @param {number} [y] The scaling factor in the vertical direction.\n   * @returns {CropperImage} Returns `this` for chaining.\n   */\n  $scale(x: number, y: number = x): this {\n    if (this.scalable) {\n      this.$transform(x, 0, 0, y, 0, 0);\n    }\n\n    return this;\n  }\n\n  /**\n   * Skews the image.\n   * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/skew}\n   * {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/transform}\n   * @param {number|string} x The skewing angle in the horizontal direction.\n   * @param {number|string} [y] The skewing angle in the vertical direction.\n   * @returns {CropperImage} Returns `this` for chaining.\n   */\n  $skew(x: number | string, y: number | string = 0): this {\n    if (this.skewable) {\n      const radianX = toAngleInRadian(x);\n      const radianY = toAngleInRadian(y);\n\n      this.$transform(1, Math.tan(radianY), Math.tan(radianX), 1, 0, 0);\n    }\n\n    return this;\n  }\n\n  /**\n   * Translates the image.\n   * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/translate}\n   * {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/translate}\n   * @param {number} x The translating distance in the horizontal direction.\n   * @param {number} [y] The translating distance in the vertical direction.\n   * @returns {CropperImage} Returns `this` for chaining.\n   */\n  $translate(x: number, y: number = x): this {\n    if (this.translatable && isNumber(x) && isNumber(y)) {\n      this.$transform(1, 0, 0, 1, x, y);\n    }\n\n    return this;\n  }\n\n  /**\n   * Transforms the image.\n   * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/matrix}\n   * {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/transform}\n   * @param {number} a The scaling factor in the horizontal direction.\n   * @param {number} b The skewing angle in the vertical direction.\n   * @param {number} c The skewing angle in the horizontal direction.\n   * @param {number} d The scaling factor in the vertical direction.\n   * @param {number} e The translating distance in the horizontal direction.\n   * @param {number} f The translating distance in the vertical direction.\n   * @returns {CropperImage} Returns `this` for chaining.\n   */\n  $transform(a: number, b: number, c: number, d: number, e: number, f: number): this {\n    if (\n      isNumber(a)\n      && isNumber(b)\n      && isNumber(c)\n      && isNumber(d)\n      && isNumber(e)\n      && isNumber(f)\n    ) {\n      return this.$setTransform(multiplyMatrices(this.$matrix, [a, b, c, d, e, f]));\n    }\n\n    return this;\n  }\n\n  /**\n   * Resets (overrides) the current transform to the specific identity matrix.\n   * {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setTransform}\n   * @param {number|Array} a The scaling factor in the horizontal direction.\n   * @param {number} b The skewing angle in the vertical direction.\n   * @param {number} c The skewing angle in the horizontal direction.\n   * @param {number} d The scaling factor in the vertical direction.\n   * @param {number} e The translating distance in the horizontal direction.\n   * @param {number} f The translating distance in the vertical direction.\n   * @returns {CropperImage} Returns `this` for chaining.\n   */\n  $setTransform(\n    a: number | number[],\n    b?: number,\n    c?: number,\n    d?: number,\n    e?: number,\n    f?: number,\n  ): this {\n    if (this.rotatable || this.scalable || this.skewable || this.translatable) {\n      if (Array.isArray(a)) {\n        [a, b, c, d, e, f] = a;\n      }\n\n      if (\n        isNumber(a)\n        && isNumber(b)\n        && isNumber(c)\n        && isNumber(d)\n        && isNumber(e)\n        && isNumber(f)\n      ) {\n        const oldMatrix = [...this.$matrix];\n        const newMatrix = [a, b, c, d, e, f];\n\n        if (this.$emit(EVENT_TRANSFORM, {\n          matrix: newMatrix,\n          oldMatrix,\n        }) === false) {\n          return this;\n        }\n\n        this.$matrix = newMatrix;\n        this.style.transform = `matrix(${newMatrix.join(', ')})`;\n      }\n    }\n\n    return this;\n  }\n\n  /**\n   * Retrieves the current transformation matrix being applied to the element.\n   * {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/getTransform}\n   * @returns {Array} Returns the readonly transformation matrix.\n   */\n  $getTransform(): number[] {\n    return this.$matrix.slice();\n  }\n\n  /**\n   * Resets the current transform to the initial identity matrix.\n   * {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/resetTransform}\n   * @returns {CropperImage} Returns `this` for chaining.\n   */\n  $resetTransform(): this {\n    return this.$setTransform([1, 0, 0, 1, 0, 0]);\n  }\n}\n"
  },
  {
    "path": "packages/element-image/src/style.ts",
    "content": "export default `\n:host {\n  display: inline-block;\n}\n\nimg {\n  display: block;\n  height: 100%;\n  max-height: none !important;\n  max-width: none !important;\n  min-height: 0 !important;\n  min-width: 0 !important;\n  width: 100%;\n}\n`;\n"
  },
  {
    "path": "packages/element-image/tests/index.spec.ts",
    "content": "import { EVENT_TRANSFORM } from '@cropper/utils';\nimport CropperImage from '../src';\n\n// A 1×1 pixel PNG image.\nconst URL_EXAMPLE_IMAGE = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQYV2P4////fwAJ+wP9BUNFygAAAABJRU5ErkJggg==';\n\nCropperImage.$define();\n\ndescribe('CropperImage', () => {\n  describe('properties', () => {\n    describe('initial-center-size', () => {\n      it('should be `\"contain\"` by default', () => {\n        const element = new CropperImage();\n\n        expect(element.initialCenterSize).toBe('contain');\n      });\n\n      it('should be `\"cover\"`', () => {\n        const element = new CropperImage();\n\n        element.setAttribute('initial-center-size', 'cover');\n        expect(element.initialCenterSize).toBe('cover');\n      });\n    });\n\n    describe('rotatable', () => {\n      it('should be `false` by default', () => {\n        const element = new CropperImage();\n\n        expect(element.rotatable).toBe(false);\n      });\n\n      it('should be `true`', () => {\n        const element = new CropperImage();\n\n        element.setAttribute('rotatable', 'true');\n        expect(element.rotatable).toBe(true);\n      });\n    });\n\n    describe('scalable', () => {\n      it('should be `false` by default', () => {\n        const element = new CropperImage();\n\n        expect(element.scalable).toBe(false);\n      });\n\n      it('should be `true`', () => {\n        const element = new CropperImage();\n\n        element.setAttribute('scalable', 'true');\n        expect(element.scalable).toBe(true);\n      });\n    });\n\n    describe('skewable', () => {\n      it('should be `false` by default', () => {\n        const element = new CropperImage();\n\n        expect(element.skewable).toBe(false);\n      });\n\n      it('should be `true`', () => {\n        const element = new CropperImage();\n\n        element.setAttribute('skewable', 'true');\n        expect(element.skewable).toBe(true);\n      });\n    });\n\n    describe('translatable', () => {\n      it('should be `false` by default', () => {\n        const element = new CropperImage();\n\n        expect(element.translatable).toBe(false);\n      });\n\n      it('should be `true`', () => {\n        const element = new CropperImage();\n\n        element.setAttribute('translatable', 'true');\n        expect(element.translatable).toBe(true);\n      });\n    });\n\n    describe('slottable', () => {\n      it('should be `false` by default', () => {\n        const element = new CropperImage();\n\n        expect(element.slottable).toBe(false);\n      });\n\n      it('should be `true`', () => {\n        const element = new CropperImage();\n\n        element.setAttribute('slottable', '');\n        expect(element.slottable).toBe(true);\n      });\n    });\n  });\n\n  describe('methods', () => {\n    describe('$move', () => {\n      it('should move the image', () => {\n        const element = new CropperImage();\n\n        element.translatable = true;\n        element.$move(10, 10);\n\n        const matrix = element.$getTransform();\n\n        expect(matrix[4]).not.toBe(0);\n        expect(matrix[5]).not.toBe(0);\n      });\n\n      it('should default to the first parameter for the second parameter', () => {\n        const element = new CropperImage();\n\n        element.translatable = true;\n        element.$move(10);\n\n        const matrix = element.$getTransform();\n\n        expect(matrix[5]).toBe(matrix[4]);\n      });\n    });\n\n    describe('$moveTo', () => {\n      it('should move the image to a specific position', () => {\n        const element = new CropperImage();\n\n        element.translatable = true;\n        element.$moveTo(10, 10);\n\n        const matrix = element.$getTransform();\n\n        expect(matrix[4]).not.toBe(0);\n        expect(matrix[5]).not.toBe(0);\n      });\n\n      it('should default to the first parameter for the second parameter', () => {\n        const element = new CropperImage();\n\n        element.translatable = true;\n        element.$moveTo(10);\n\n        const matrix = element.$getTransform();\n\n        expect(matrix[5]).toBe(matrix[4]);\n      });\n    });\n\n    describe('$rotate', () => {\n      it('should rotate the image', () => {\n        const element = new CropperImage();\n\n        element.rotatable = true;\n        element.$rotate(45);\n\n        const [a, b, c, d] = element.$getTransform();\n\n        expect(a).not.toBe(1);\n        expect(b).not.toBe(0);\n        expect(c).not.toBe(0);\n        expect(d).not.toBe(1);\n      });\n\n      it('should support string parameter', () => {\n        const element = new CropperImage();\n\n        element.rotatable = true;\n        element.$rotate('45deg');\n\n        const [a, b, c, d] = element.$getTransform();\n\n        expect(a).not.toBe(1);\n        expect(b).not.toBe(0);\n        expect(c).not.toBe(0);\n        expect(d).not.toBe(1);\n      });\n    });\n\n    describe('$zoom', () => {\n      it('should zoom in the image', () => {\n        const element = new CropperImage();\n\n        element.scalable = true;\n        element.$zoom(0.1);\n\n        const matrix = element.$getTransform();\n\n        expect(matrix[0]).toBeGreaterThan(1);\n        expect(matrix[3]).toBeGreaterThan(1);\n      });\n\n      it('should zoom out the image', () => {\n        const element = new CropperImage();\n\n        element.scalable = true;\n        element.$zoom(-0.1);\n\n        const matrix = element.$getTransform();\n\n        expect(matrix[0]).toBeLessThan(1);\n        expect(matrix[3]).toBeLessThan(1);\n      });\n    });\n\n    describe('$scale', () => {\n      it('should scale the image', () => {\n        const element = new CropperImage();\n\n        element.scalable = true;\n        element.$scale(1.1, 1.2);\n\n        const matrix = element.$getTransform();\n\n        expect(matrix[0]).not.toBe(1);\n        expect(matrix[3]).not.toBe(1);\n      });\n\n      it('should default to the first parameter for the second parameter', () => {\n        const element = new CropperImage();\n\n        element.scalable = true;\n        element.$scale(1.1);\n\n        const matrix = element.$getTransform();\n\n        expect(matrix[2]).toBe(matrix[1]);\n      });\n    });\n\n    describe('$skew', () => {\n      it('should skew the image', () => {\n        const element = new CropperImage();\n\n        element.skewable = true;\n        element.$skew(0.1, 0.2);\n\n        const matrix = element.$getTransform();\n\n        expect(matrix[1]).not.toBe(0);\n        expect(matrix[2]).not.toBe(0);\n      });\n\n      it('should support string parameter', () => {\n        const element = new CropperImage();\n\n        element.skewable = true;\n        element.$skew('15deg', '30deg');\n\n        const matrix = element.$getTransform();\n\n        expect(matrix[1]).not.toBe(0);\n        expect(matrix[2]).not.toBe(0);\n      });\n\n      it('should default to 0 for the second parameter', () => {\n        const element = new CropperImage();\n\n        element.skewable = true;\n        element.$skew(0.1);\n\n        const matrix = element.$getTransform();\n\n        expect(matrix[1]).toBe(0);\n        expect(matrix[2]).not.toBe(0);\n      });\n    });\n\n    describe('$translate', () => {\n      it('should translate the image', () => {\n        const element = new CropperImage();\n\n        element.translatable = true;\n        element.$translate(10, 5);\n\n        const matrix = element.$getTransform();\n\n        expect(matrix[4]).toBe(10);\n        expect(matrix[5]).toBe(5);\n      });\n\n      it('should default to the first parameter for the second parameter', () => {\n        const element = new CropperImage();\n\n        element.translatable = true;\n        element.$translate(10);\n\n        const matrix = element.$getTransform();\n\n        expect(matrix[5]).toBe(matrix[4]);\n      });\n    });\n\n    describe('$transform', () => {\n      it('should transform the image', () => {\n        const element = new CropperImage();\n\n        element.rotatable = true;\n        element.scalable = true;\n        element.skewable = true;\n        element.translatable = true;\n        element.$transform(0.5, 0.5, 0.5, 0.5, 5, 5);\n        expect(element.$getTransform()).toEqual([0.5, 0.5, 0.5, 0.5, 5, 5]);\n      });\n    });\n\n    describe('$setTransform', () => {\n      it('should reset (override) the current transform to the specific identity matrix', () => {\n        const element = new CropperImage();\n\n        element.rotatable = true;\n        element.scalable = true;\n        element.skewable = true;\n        element.translatable = true;\n        element.$setTransform(0.5, 0.5, 0.5, 0.5, 5, 5);\n        expect(element.$getTransform()).toEqual([0.5, 0.5, 0.5, 0.5, 5, 5]);\n      });\n\n      it('should support array parameter', () => {\n        const element = new CropperImage();\n\n        element.rotatable = true;\n        element.scalable = true;\n        element.skewable = true;\n        element.translatable = true;\n        element.$setTransform([0.5, 0.5, 0.5, 0.5, 5, 5]);\n        expect(element.$getTransform()).toEqual([0.5, 0.5, 0.5, 0.5, 5, 5]);\n      });\n    });\n\n    describe('$getTransform', () => {\n      it('should retrieves the current transformation matrix being applied to the element', () => {\n        const element = new CropperImage();\n\n        expect(element.$getTransform()).toEqual([1, 0, 0, 1, 0, 0]);\n      });\n    });\n\n    describe('$resetTransform', () => {\n      it('should reset the current transform to the initial identity matrix', () => {\n        const element = new CropperImage();\n\n        element.rotatable = true;\n        element.scalable = true;\n        element.skewable = true;\n        element.translatable = true;\n        expect(element.$getTransform()).toEqual([1, 0, 0, 1, 0, 0]);\n        element.$setTransform([0.5, 0.5, 0.5, 0.5, 5, 5]);\n        expect(element.$getTransform()).toEqual([0.5, 0.5, 0.5, 0.5, 5, 5]);\n        element.$resetTransform();\n        expect(element.$getTransform()).toEqual([1, 0, 0, 1, 0, 0]);\n      });\n    });\n  });\n\n  describe('events', () => {\n    describe(EVENT_TRANSFORM, () => {\n      it('should trigger the `transform` event', (done) => {\n        const element = new CropperImage();\n\n        element.rotatable = true;\n        element.scalable = true;\n        element.skewable = true;\n        element.translatable = true;\n        element.addEventListener(EVENT_TRANSFORM, (event) => {\n          const { detail } = event as CustomEvent;\n\n          expect(detail.matrix).toHaveLength(6);\n          expect(detail.matrix).toBeInstanceOf(Array);\n          expect(detail.oldMatrix).toHaveLength(6);\n          expect(detail.oldMatrix).toBeInstanceOf(Array);\n          done();\n        });\n        element.$setTransform(0.5, 0.5, 0.5, 0.5, 5, 5);\n      });\n\n      it('should not transform when default prevented', () => {\n        const element = new CropperImage();\n\n        element.rotatable = true;\n        element.scalable = true;\n        element.skewable = true;\n        element.translatable = true;\n        element.addEventListener(EVENT_TRANSFORM, (event) => {\n          event.preventDefault();\n        });\n        expect(element.$getTransform()).toEqual([1, 0, 0, 1, 0, 0]);\n        element.$setTransform(0.5, 0.5, 0.5, 0.5, 5, 5);\n        expect(element.$getTransform()).toEqual([1, 0, 0, 1, 0, 0]);\n      });\n    });\n  });\n\n  describe('others', () => {\n    it('should inherit the native attributes', () => {\n      const element = new CropperImage();\n      const img = element.querySelector('img');\n\n      element.setAttribute('src', URL_EXAMPLE_IMAGE);\n      element.setAttribute('alt', 'A 1×1 pixel PNG image');\n      element.setAttribute('crossorigin', '');\n\n      if (img) {\n        expect(img.hasAttribute('src')).toBe(true);\n        expect(img.hasAttribute('alt')).toBe(true);\n        expect(img.hasAttribute('crossorigin')).toBe(true);\n      }\n    });\n  });\n});\n"
  },
  {
    "path": "packages/element-selection/README.md",
    "content": "# @cropper/element-selection\n\n> A custom selection element for the Cropper.\n\n## Main npm package files\n\n```text\ndist/\n├── element-selection.js         (UMD, bundled)\n├── element-selection.min.js     (UMD, bundled, compressed)\n├── element-selection.raw.js     (UMD, unbundled, default)\n├── element-selection.esm.js     (ECMAScript Module, bundled)\n├── element-selection.esm.min.js (ECMAScript Module, bundled, compressed)\n├── element-selection.esm.raw.js (ECMAScript Module, unbundled)\n└── element-selection.d.ts       (TypeScript Declaration File)\n```\n\n## Getting started\n\n### Installation\n\n```sh\nnpm install @cropper/element-selection\n```\n\n### Usage\n\n```js\nimport CropperSelection from '@cropper/element-selection';\n\nCropperSelection.$define();\n```\n\n```html\n<cropper-selection></cropper-selection>\n```\n\n## Versioning\n\nMaintained under the [Semantic Versioning guidelines](https://semver.org).\n\n## License\n\n[MIT](https://opensource.org/licenses/MIT)\n"
  },
  {
    "path": "packages/element-selection/api-extractor.json",
    "content": "{\n  \"extends\": \"../../api-extractor.json\",\n  \"mainEntryPointFilePath\": \"./.temp/packages/<unscopedPackageName>/src/index.d.ts\",\n  \"dtsRollup\": {\n    \"publicTrimmedFilePath\": \"./dist/<unscopedPackageName>.d.ts\"\n  }\n}\n"
  },
  {
    "path": "packages/element-selection/package.json",
    "content": "{\n  \"name\": \"@cropper/element-selection\",\n  \"version\": \"2.1.0\",\n  \"description\": \"A custom selection element for the Cropper.\",\n  \"main\": \"dist/element-selection.raw.js\",\n  \"module\": \"dist/element-selection.esm.raw.js\",\n  \"types\": \"dist/element-selection.d.ts\",\n  \"unpkg\": \"dist/element-selection.js\",\n  \"jsdelivr\": \"dist/element-selection.js\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"import\": {\n        \"types\": \"./dist/element-selection.d.ts\",\n        \"node\": {\n          \"production\": \"./dist/element-selection.esm.min.js\",\n          \"development\": \"./dist/element-selection.esm.js\",\n          \"default\": \"./dist/element-selection.esm.raw.js\"\n        },\n        \"default\": \"./dist/element-selection.esm.raw.js\"\n      },\n      \"require\": {\n        \"types\": \"./dist/element-selection.d.ts\",\n        \"node\": {\n          \"production\": \"./dist/element-selection.min.js\",\n          \"development\": \"./dist/element-selection.js\",\n          \"default\": \"./dist/element-selection.raw.js\"\n        },\n        \"default\": \"./dist/element-selection.raw.js\"\n      }\n    },\n    \"./dist/*\": \"./dist/*\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"scripts\": {\n    \"api-extractor\": \"api-extractor run --local --verbose\",\n    \"build\": \"npm run tsc && npm run api-extractor && npm run rollup\",\n    \"clean\": \"del-cli dist .temp\",\n    \"release\": \"npm run clean && npm run build\",\n    \"rollup\": \"rollup -c ../../rollup.config.js\",\n    \"tsc\": \"tsc --outDir ./.temp --declaration --emitDeclarationOnly\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/fengyuanchen/cropperjs.git\",\n    \"directory\": \"packages/element-selection\"\n  },\n  \"keywords\": [\n    \"selection\",\n    \"cropper\",\n    \"cropper.js\",\n    \"cropper-element\",\n    \"custom-element\",\n    \"web-component\"\n  ],\n  \"author\": \"Chen Fengyuan (https://chenfengyuan.com/)\",\n  \"license\": \"MIT\",\n  \"bugs\": \"https://github.com/fengyuanchen/cropperjs/issues\",\n  \"homepage\": \"https://github.com/fengyuanchen/cropperjs/tree/main/packages/element-selection/#readme\",\n  \"dependencies\": {\n    \"@cropper/element\": \"^2.1.0\",\n    \"@cropper/element-canvas\": \"^2.1.0\",\n    \"@cropper/element-image\": \"^2.1.0\",\n    \"@cropper/utils\": \"^2.1.0\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  }\n}\n"
  },
  {
    "path": "packages/element-selection/src/index.ts",
    "content": "import CropperElement from '@cropper/element';\nimport type CropperCanvas from '@cropper/element-canvas';\nimport type CropperImage from '@cropper/element-image';\nimport {\n  ACTION_MOVE,\n  ACTION_RESIZE_EAST,\n  ACTION_RESIZE_NORTH,\n  ACTION_RESIZE_NORTHEAST,\n  ACTION_RESIZE_NORTHWEST,\n  ACTION_RESIZE_SOUTH,\n  ACTION_RESIZE_SOUTHEAST,\n  ACTION_RESIZE_SOUTHWEST,\n  ACTION_RESIZE_WEST,\n  ACTION_SCALE,\n  ACTION_SELECT,\n  CROPPER_CANVAS,\n  CROPPER_IMAGE,\n  CROPPER_SELECTION,\n  EVENT_ACTION,\n  EVENT_ACTION_END,\n  EVENT_ACTION_START,\n  EVENT_CHANGE,\n  EVENT_KEYDOWN,\n  getAdjustedSizes,\n  getOffset,\n  isFunction,\n  isNumber,\n  isPlainObject,\n  isPositiveNumber,\n  getComposedPathTarget,\n  off,\n  on,\n} from '@cropper/utils';\nimport style from './style';\n\nconst canvasCache = new WeakMap();\n\nexport interface Selection {\n  x: number;\n  y: number;\n  width: number;\n  height: number;\n}\n\nexport default class CropperSelection extends CropperElement {\n  static $name = CROPPER_SELECTION;\n\n  static $version = '__VERSION__';\n\n  protected $onCanvasAction: EventListener | null = null;\n\n  protected $onCanvasActionStart: EventListener | null = null;\n\n  protected $onCanvasActionEnd: EventListener | null = null;\n\n  protected $onDocumentKeyDown: EventListener | null = null;\n\n  protected $action = '';\n\n  protected $actionStartTarget: EventTarget | null = null;\n\n  protected $changing = false;\n\n  protected $style = style;\n\n  private $initialSelection = {\n    x: 0,\n    y: 0,\n    width: 0,\n    height: 0,\n  };\n\n  x = 0;\n\n  y = 0;\n\n  width = 0;\n\n  height = 0;\n\n  aspectRatio = NaN;\n\n  initialAspectRatio = NaN;\n\n  initialCoverage = NaN;\n\n  active = false;\n\n  // Deprecated as of v2.0.0-rc.0, use `dynamic` instead.\n  linked = false;\n\n  dynamic = false;\n\n  movable = false;\n\n  resizable = false;\n\n  zoomable = false;\n\n  multiple = false;\n\n  keyboard = false;\n\n  outlined = false;\n\n  precise = false;\n\n  protected set $canvas(element: CropperCanvas) {\n    canvasCache.set(this, element);\n  }\n\n  protected get $canvas(): CropperCanvas {\n    return canvasCache.get(this);\n  }\n\n  protected static get observedAttributes(): string[] {\n    return super.observedAttributes.concat([\n      'active',\n      'aspect-ratio',\n      'dynamic',\n      'height',\n      'initial-aspect-ratio',\n      'initial-coverage',\n      'keyboard',\n      'linked',\n      'movable',\n      'multiple',\n      'outlined',\n      'precise',\n      'resizable',\n      'width',\n      'x',\n      'y',\n      'zoomable',\n    ]);\n  }\n\n  protected $propertyChangedCallback(name: string, oldValue: unknown, newValue: unknown): void {\n    if (Object.is(newValue, oldValue)) {\n      return;\n    }\n\n    super.$propertyChangedCallback(name, oldValue, newValue);\n\n    switch (name) {\n      case 'x':\n      case 'y':\n      case 'width':\n      case 'height':\n        if (!this.$changing) {\n          this.$nextTick(() => {\n            this.$change(this.x, this.y, this.width, this.height, this.aspectRatio, true);\n          });\n        }\n        break;\n\n      case 'aspectRatio':\n      case 'initialAspectRatio':\n        this.$nextTick(() => {\n          this.$initSelection();\n        });\n        break;\n\n      case 'initialCoverage':\n        this.$nextTick(() => {\n          if (isPositiveNumber(newValue) && newValue <= 1) {\n            this.$initSelection(true, true);\n          }\n        });\n        break;\n\n      case 'keyboard':\n        this.$nextTick(() => {\n          if (this.$canvas) {\n            if (newValue) {\n              if (!this.$onDocumentKeyDown) {\n                this.$onDocumentKeyDown = this.$handleKeyDown.bind(this);\n                on(this.ownerDocument, EVENT_KEYDOWN, this.$onDocumentKeyDown);\n              }\n            } else if (this.$onDocumentKeyDown) {\n              off(this.ownerDocument, EVENT_KEYDOWN, this.$onDocumentKeyDown);\n              this.$onDocumentKeyDown = null;\n            }\n          }\n        });\n        break;\n\n      case 'multiple':\n        this.$nextTick(() => {\n          if (this.$canvas) {\n            const selections = this.$getSelections();\n\n            if (newValue) {\n              selections.forEach((selection) => {\n                selection.active = false;\n              });\n              this.active = true;\n              this.$emit(EVENT_CHANGE, {\n                x: this.x,\n                y: this.y,\n                width: this.width,\n                height: this.height,\n              });\n            } else {\n              this.active = false;\n              selections.slice(1).forEach((selection) => {\n                this.$removeSelection(selection);\n              });\n            }\n          }\n        });\n        break;\n\n      case 'precise':\n        this.$nextTick(() => {\n          this.$change(this.x, this.y);\n        });\n        break;\n\n      // Backwards compatible with 2.0.0-rc\n      case 'linked':\n        if (newValue) {\n          this.dynamic = true;\n        }\n        break;\n\n      default:\n    }\n  }\n\n  protected connectedCallback(): void {\n    super.connectedCallback();\n\n    const $canvas: CropperCanvas | null = this.closest(this.$getTagNameOf(CROPPER_CANVAS));\n\n    if ($canvas) {\n      this.$canvas = $canvas;\n      this.$setStyles({\n        position: 'absolute',\n        transform: `translate(${this.x}px, ${this.y}px)`,\n      });\n\n      if (!this.hidden) {\n        this.$render();\n      }\n\n      this.$initSelection(true);\n      this.$onCanvasActionStart = this.$handleActionStart.bind(this);\n      this.$onCanvasActionEnd = this.$handleActionEnd.bind(this);\n      this.$onCanvasAction = this.$handleAction.bind(this);\n      on($canvas, EVENT_ACTION_START, this.$onCanvasActionStart);\n      on($canvas, EVENT_ACTION_END, this.$onCanvasActionEnd);\n      on($canvas, EVENT_ACTION, this.$onCanvasAction);\n    } else {\n      this.$render();\n    }\n  }\n\n  protected disconnectedCallback(): void {\n    const { $canvas } = this;\n\n    if ($canvas) {\n      if (this.$onCanvasActionStart) {\n        off($canvas, EVENT_ACTION_START, this.$onCanvasActionStart);\n        this.$onCanvasActionStart = null;\n      }\n\n      if (this.$onCanvasActionEnd) {\n        off($canvas, EVENT_ACTION_END, this.$onCanvasActionEnd);\n        this.$onCanvasActionEnd = null;\n      }\n\n      if (this.$onCanvasAction) {\n        off($canvas, EVENT_ACTION, this.$onCanvasAction);\n        this.$onCanvasAction = null;\n      }\n    }\n\n    super.disconnectedCallback();\n  }\n\n  protected $getSelections(): CropperSelection[] {\n    let selections: CropperSelection[] = [];\n\n    if (this.parentElement) {\n      selections = Array.from(this.parentElement.querySelectorAll(\n        this.$getTagNameOf(CROPPER_SELECTION),\n      ));\n    }\n\n    return selections;\n  }\n\n  protected $initSelection(center = false, resize = false) {\n    const { initialCoverage, parentElement } = this;\n\n    if (isPositiveNumber(initialCoverage) && parentElement) {\n      const aspectRatio = this.aspectRatio || this.initialAspectRatio;\n      let width = (resize ? 0 : this.width) || parentElement.offsetWidth * initialCoverage;\n      let height = (resize ? 0 : this.height) || parentElement.offsetHeight * initialCoverage;\n\n      if (isPositiveNumber(aspectRatio)) {\n        ({ width, height } = getAdjustedSizes({ aspectRatio, width, height }));\n      }\n\n      this.$change(this.x, this.y, width, height);\n\n      if (center) {\n        this.$center();\n      }\n\n      // Overrides the initial position and size\n      this.$initialSelection = {\n        x: this.x,\n        y: this.y,\n        width: this.width,\n        height: this.height,\n      };\n    }\n  }\n\n  protected $createSelection(): CropperSelection {\n    const newSelection = this.cloneNode(true) as CropperSelection;\n\n    if (this.hasAttribute('id')) {\n      newSelection.removeAttribute('id');\n    }\n\n    newSelection.initialCoverage = NaN;\n    this.active = false;\n\n    if (this.parentElement) {\n      this.parentElement.insertBefore(newSelection, this.nextSibling);\n    }\n\n    return newSelection;\n  }\n\n  protected $removeSelection(selection: CropperSelection = this): void {\n    if (this.parentElement) {\n      const selections = this.$getSelections();\n\n      if (selections.length > 1) {\n        const index = selections.indexOf(selection);\n        const activeSelection = selections[index + 1] || selections[index - 1];\n\n        if (activeSelection) {\n          selection.active = false;\n          this.parentElement.removeChild(selection);\n          activeSelection.active = true;\n          activeSelection.$emit(EVENT_CHANGE, {\n            x: activeSelection.x,\n            y: activeSelection.y,\n            width: activeSelection.width,\n            height: activeSelection.height,\n          });\n        }\n      } else {\n        this.$clear();\n      }\n    }\n  }\n\n  protected $handleActionStart(event: Event): void {\n    const relatedTarget = (event as CustomEvent).detail?.relatedEvent?.target;\n\n    this.$action = '';\n    this.$actionStartTarget = relatedTarget;\n\n    if (\n      !this.hidden\n      && this.multiple\n      && !this.active\n      && relatedTarget === this\n      && this.parentElement\n    ) {\n      this.$getSelections().forEach((selection) => {\n        (selection as CropperSelection).active = false;\n      });\n      this.active = true;\n      this.$emit(EVENT_CHANGE, {\n        x: this.x,\n        y: this.y,\n        width: this.width,\n        height: this.height,\n      });\n    }\n  }\n\n  protected $handleAction(event: Event): void {\n    const { currentTarget, detail } = event as CustomEvent;\n\n    if (!currentTarget || !detail) {\n      return;\n    }\n\n    const { relatedEvent } = detail;\n    let { action } = detail;\n    const relatedTarget = relatedEvent\n      ? getComposedPathTarget(relatedEvent)\n      : null;\n\n    // Switching to another selection\n    if (!action && this.multiple) {\n      // Get the `action` property from the focusing in selection\n      action = this.$action || (relatedTarget as any)?.action;\n      this.$action = action;\n    }\n\n    if (!action\n      || (this.hidden && action !== ACTION_SELECT)\n      || (this.multiple && !this.active && action !== ACTION_SCALE)) {\n      return;\n    }\n\n    const { width, height } = this;\n    let moveX = detail.endX - detail.startX;\n    let moveY = detail.endY - detail.startY;\n    let { aspectRatio } = this;\n\n    // Locking aspect ratio by holding shift key\n    if (!isPositiveNumber(aspectRatio) && relatedEvent.shiftKey) {\n      aspectRatio = isPositiveNumber(width) && isPositiveNumber(height) ? width / height : 1;\n    }\n\n    switch (action) {\n      case ACTION_SELECT:\n        if (moveX !== 0 || moveY !== 0) {\n          // Force to create a square selection for better user experience\n          if (moveX === 0) {\n            moveX = moveY;\n          } else if (moveY === 0) {\n            moveY = moveX;\n          }\n\n          const { $canvas } = this;\n          const offset = getOffset(currentTarget as Element);\n\n          (this.multiple && !this.hidden ? this.$createSelection() : this).$change(\n            detail.startX - offset.left,\n            detail.startY - offset.top,\n            Math.abs(moveX),\n            Math.abs(moveY),\n            aspectRatio,\n          );\n\n          if (moveX < 0) {\n            if (moveY < 0) {\n              // ↖️\n              action = ACTION_RESIZE_NORTHWEST;\n            } else if (moveY > 0) {\n              // ↙️\n              action = ACTION_RESIZE_SOUTHWEST;\n            }\n          } else if (moveX > 0) {\n            if (moveY < 0) {\n              // ↗️\n              action = ACTION_RESIZE_NORTHEAST;\n            } else if (moveY > 0) {\n              // ↘️\n              action = ACTION_RESIZE_SOUTHEAST;\n            }\n          }\n\n          if ($canvas) {\n            ($canvas as any).$action = action;\n          }\n        }\n        break;\n\n      case ACTION_MOVE:\n        if (this.movable && (\n          this.dynamic\n          || (this.$actionStartTarget && this.contains(this.$actionStartTarget as Node))\n        )) {\n          this.$move(moveX, moveY);\n        }\n        break;\n\n      case ACTION_SCALE:\n        if (relatedEvent && this.zoomable && (\n          this.dynamic\n          || this.contains(relatedEvent.target as Node)\n        )) {\n          const offset = getOffset(currentTarget as Element);\n\n          this.$zoom(\n            detail.scale,\n            relatedEvent.pageX - offset.left,\n            relatedEvent.pageY - offset.top,\n          );\n        }\n        break;\n\n      default:\n        this.$resize(action, moveX, moveY, aspectRatio);\n    }\n  }\n\n  protected $handleActionEnd(): void {\n    this.$action = '';\n    this.$actionStartTarget = null;\n  }\n\n  protected $handleKeyDown(event: Event): void {\n    if (\n      this.hidden\n      || !this.keyboard\n      || (this.multiple && !this.active)\n      || event.defaultPrevented\n    ) {\n      return;\n    }\n\n    const { activeElement } = document;\n\n    // Disable keyboard control when input something\n    if (activeElement && (\n      ['INPUT', 'TEXTAREA'].includes(activeElement.tagName)\n      || ['true', 'plaintext-only'].includes((activeElement as HTMLElement).contentEditable)\n    )) {\n      return;\n    }\n\n    switch ((event as KeyboardEvent).key) {\n      case 'Backspace':\n        if ((event as KeyboardEvent).metaKey) {\n          event.preventDefault();\n          this.$removeSelection();\n        }\n        break;\n\n      case 'Delete':\n        event.preventDefault();\n        this.$removeSelection();\n        break;\n\n      // Move to the left\n      case 'ArrowLeft':\n        event.preventDefault();\n        this.$move(-1, 0);\n        break;\n\n      // Move to the right\n      case 'ArrowRight':\n        event.preventDefault();\n        this.$move(1, 0);\n        break;\n\n      // Move to the top\n      case 'ArrowUp':\n        event.preventDefault();\n        this.$move(0, -1);\n        break;\n\n      // Move to the bottom\n      case 'ArrowDown':\n        event.preventDefault();\n        this.$move(0, 1);\n        break;\n\n      case '+':\n        event.preventDefault();\n        this.$zoom(0.1);\n        break;\n\n      case '-':\n        event.preventDefault();\n        this.$zoom(-0.1);\n        break;\n\n      default:\n    }\n  }\n\n  /**\n   * Aligns the selection to the center of its parent element.\n   * @returns {CropperSelection} Returns `this` for chaining.\n   */\n  $center(): this {\n    const { parentElement } = this;\n\n    if (!parentElement) {\n      return this;\n    }\n\n    const x = (parentElement.offsetWidth - this.width) / 2;\n    const y = (parentElement.offsetHeight - this.height) / 2;\n\n    return this.$change(x, y);\n  }\n\n  /**\n   * Moves the selection.\n   * @param {number} x The moving distance in the horizontal direction.\n   * @param {number} [y] The moving distance in the vertical direction.\n   * @returns {CropperSelection} Returns `this` for chaining.\n   */\n  $move(x: number, y: number = x): this {\n    return this.$moveTo(this.x + x, this.y + y);\n  }\n\n  /**\n   * Moves the selection to a specific position.\n   * @param {number} x The new position in the horizontal direction.\n   * @param {number} [y] The new position in the vertical direction.\n   * @returns {CropperSelection} Returns `this` for chaining.\n   */\n  $moveTo(x: number, y: number = x): this {\n    if (!this.movable) {\n      return this;\n    }\n\n    return this.$change(x, y);\n  }\n\n  /**\n   * Adjusts the size the selection on a specific side or corner.\n   * @param {string} action Indicates the side or corner to resize.\n   * @param {number} [offsetX] The horizontal offset of the specific side or corner.\n   * @param {number} [offsetY] The vertical offset of the specific side or corner.\n   * @param {number} [aspectRatio] The aspect ratio for computing the new size if it is necessary.\n   * @returns {CropperSelection} Returns `this` for chaining.\n   */\n  $resize(\n    action: string,\n    offsetX = 0,\n    offsetY = 0,\n    aspectRatio: number = this.aspectRatio,\n  ): this {\n    if (!this.resizable) {\n      return this;\n    }\n\n    const hasValidAspectRatio = isPositiveNumber(aspectRatio);\n    const { $canvas } = this;\n    let {\n      x,\n      y,\n      width,\n      height,\n    } = this;\n\n    switch (action) {\n      case ACTION_RESIZE_NORTH:\n        y += offsetY;\n        height -= offsetY;\n\n        if (height < 0) {\n          action = ACTION_RESIZE_SOUTH;\n          height = -height;\n          y -= height;\n        }\n\n        if (hasValidAspectRatio) {\n          offsetX = offsetY * aspectRatio;\n          x += offsetX / 2;\n          width -= offsetX;\n\n          if (width < 0) {\n            width = -width;\n            x -= width;\n          }\n        }\n\n        break;\n\n      case ACTION_RESIZE_EAST:\n        width += offsetX;\n\n        if (width < 0) {\n          action = ACTION_RESIZE_WEST;\n          width = -width;\n          x -= width;\n        }\n\n        if (hasValidAspectRatio) {\n          offsetY = offsetX / aspectRatio;\n          y -= offsetY / 2;\n          height += offsetY;\n\n          if (height < 0) {\n            height = -height;\n            y -= height;\n          }\n        }\n\n        break;\n\n      case ACTION_RESIZE_SOUTH:\n        height += offsetY;\n\n        if (height < 0) {\n          action = ACTION_RESIZE_NORTH;\n          height = -height;\n          y -= height;\n        }\n\n        if (hasValidAspectRatio) {\n          offsetX = offsetY * aspectRatio;\n          x -= offsetX / 2;\n          width += offsetX;\n\n          if (width < 0) {\n            width = -width;\n            x -= width;\n          }\n        }\n\n        break;\n\n      case ACTION_RESIZE_WEST:\n        x += offsetX;\n        width -= offsetX;\n\n        if (width < 0) {\n          action = ACTION_RESIZE_EAST;\n          width = -width;\n          x -= width;\n        }\n\n        if (hasValidAspectRatio) {\n          offsetY = offsetX / aspectRatio;\n          y += offsetY / 2;\n          height -= offsetY;\n\n          if (height < 0) {\n            height = -height;\n            y -= height;\n          }\n        }\n\n        break;\n\n      case ACTION_RESIZE_NORTHEAST:\n        if (hasValidAspectRatio) {\n          offsetY = -offsetX / aspectRatio;\n        }\n\n        y += offsetY;\n        height -= offsetY;\n        width += offsetX;\n\n        if (width < 0 && height < 0) {\n          action = ACTION_RESIZE_SOUTHWEST;\n          width = -width;\n          height = -height;\n          x -= width;\n          y -= height;\n        } else if (width < 0) {\n          action = ACTION_RESIZE_NORTHWEST;\n          width = -width;\n          x -= width;\n        } else if (height < 0) {\n          action = ACTION_RESIZE_SOUTHEAST;\n          height = -height;\n          y -= height;\n        }\n\n        break;\n\n      case ACTION_RESIZE_NORTHWEST:\n        if (hasValidAspectRatio) {\n          offsetY = offsetX / aspectRatio;\n        }\n\n        x += offsetX;\n        y += offsetY;\n        width -= offsetX;\n        height -= offsetY;\n\n        if (width < 0 && height < 0) {\n          action = ACTION_RESIZE_SOUTHEAST;\n          width = -width;\n          height = -height;\n          x -= width;\n          y -= height;\n        } else if (width < 0) {\n          action = ACTION_RESIZE_NORTHEAST;\n          width = -width;\n          x -= width;\n        } else if (height < 0) {\n          action = ACTION_RESIZE_SOUTHWEST;\n          height = -height;\n          y -= height;\n        }\n\n        break;\n\n      case ACTION_RESIZE_SOUTHEAST:\n        if (hasValidAspectRatio) {\n          offsetY = offsetX / aspectRatio;\n        }\n\n        width += offsetX;\n        height += offsetY;\n\n        if (width < 0 && height < 0) {\n          action = ACTION_RESIZE_NORTHWEST;\n          width = -width;\n          height = -height;\n          x -= width;\n          y -= height;\n        } else if (width < 0) {\n          action = ACTION_RESIZE_SOUTHWEST;\n          width = -width;\n          x -= width;\n        } else if (height < 0) {\n          action = ACTION_RESIZE_NORTHEAST;\n          height = -height;\n          y -= height;\n        }\n\n        break;\n\n      case ACTION_RESIZE_SOUTHWEST:\n        if (hasValidAspectRatio) {\n          offsetY = -offsetX / aspectRatio;\n        }\n\n        x += offsetX;\n        width -= offsetX;\n        height += offsetY;\n\n        if (width < 0 && height < 0) {\n          action = ACTION_RESIZE_NORTHEAST;\n          width = -width;\n          height = -height;\n          x -= width;\n          y -= height;\n        } else if (width < 0) {\n          action = ACTION_RESIZE_SOUTHEAST;\n          width = -width;\n          x -= width;\n        } else if (height < 0) {\n          action = ACTION_RESIZE_NORTHWEST;\n          height = -height;\n          y -= height;\n        }\n\n        break;\n\n      default:\n    }\n\n    if ($canvas) {\n      ($canvas as any).$setAction(action);\n    }\n\n    return this.$change(x, y, width, height);\n  }\n\n  /**\n   * Zooms the selection.\n   * @param {number} scale The zoom factor. Positive numbers for zooming in, and negative numbers for zooming out.\n   * @param {number} [x] The zoom origin in the horizontal, defaults to the center of the selection.\n   * @param {number} [y] The zoom origin in the vertical, defaults to the center of the selection.\n   * @returns {CropperSelection} Returns `this` for chaining.\n   */\n  $zoom(scale: number, x?: number, y?: number): this {\n    if (!this.zoomable || scale === 0) {\n      return this;\n    }\n\n    if (scale < 0) {\n      scale = 1 / (1 - scale);\n    } else {\n      scale += 1;\n    }\n\n    const { width, height } = this;\n    const newWidth = width * scale;\n    const newHeight = height * scale;\n    let newX = this.x;\n    let newY = this.y;\n\n    if (isNumber(x) && isNumber(y)) {\n      newX -= (newWidth - width) * ((x - this.x) / width);\n      newY -= (newHeight - height) * ((y - this.y) / height);\n    } else {\n      // Zoom from the center of the selection\n      newX -= (newWidth - width) / 2;\n      newY -= (newHeight - height) / 2;\n    }\n\n    return this.$change(newX, newY, newWidth, newHeight);\n  }\n\n  /**\n   * Changes the position and/or size of the selection.\n   * @param {number} x The new position in the horizontal direction.\n   * @param {number} y The new position in the vertical direction.\n   * @param {number} [width] The new width.\n   * @param {number} [height] The new height.\n   * @param {number} [aspectRatio] The new aspect ratio for this change only.\n   * @param {number} [_force] Force change.\n   * @returns {CropperSelection} Returns `this` for chaining.\n   */\n  $change(\n    x: number,\n    y: number,\n    width: number = this.width,\n    height: number = this.height,\n    aspectRatio: number = this.aspectRatio,\n    _force = false,\n  ): this {\n    if (\n      this.$changing\n      || !isNumber(x)\n      || !isNumber(y)\n      || !isNumber(width)\n      || !isNumber(height)\n      || width < 0\n      || height < 0\n    ) {\n      return this;\n    }\n\n    if (isPositiveNumber(aspectRatio)) {\n      ({ width, height } = getAdjustedSizes({ aspectRatio, width, height }, 'cover'));\n    }\n\n    if (!this.precise) {\n      x = Math.round(x);\n      y = Math.round(y);\n      width = Math.round(width);\n      height = Math.round(height);\n    }\n\n    if (\n      x === this.x\n      && y === this.y\n      && width === this.width\n      && height === this.height\n      && Object.is(aspectRatio, this.aspectRatio)\n      && !_force\n    ) {\n      return this;\n    }\n\n    if (this.hidden) {\n      this.hidden = false;\n    }\n\n    if (this.$emit(EVENT_CHANGE, {\n      x,\n      y,\n      width,\n      height,\n    }) === false) {\n      return this;\n    }\n\n    this.$changing = true;\n    this.x = x;\n    this.y = y;\n    this.width = width;\n    this.height = height;\n    this.$changing = false;\n\n    return this.$render();\n  }\n\n  /**\n   * Resets the selection to its initial position and size.\n   * @returns {CropperSelection} Returns `this` for chaining.\n   */\n  $reset(): this {\n    const {\n      x,\n      y,\n      width,\n      height,\n    } = this.$initialSelection;\n\n    return this.$change(x, y, width, height);\n  }\n\n  /**\n   * Clears the selection.\n   * @returns {CropperSelection} Returns `this` for chaining.\n   */\n  $clear(): this {\n    this.$change(0, 0, 0, 0, NaN, true);\n    this.hidden = true;\n    return this;\n  }\n\n  /**\n   * Refreshes the position or size of the selection.\n   * @returns {CropperSelection} Returns `this` for chaining.\n   */\n  $render(): this {\n    return this.$setStyles({\n      transform: `translate(${this.x}px, ${this.y}px)`,\n      width: this.width,\n      height: this.height,\n    });\n  }\n\n  /**\n   * Generates a real canvas element, with the image (selected area only) draw into if there is one.\n   * @param {object} [options] The available options.\n   * @param {number} [options.width] The width of the canvas.\n   * @param {number} [options.height] The height of the canvas.\n   * @param {Function} [options.beforeDraw] The function called before drawing the image onto the canvas.\n   * @returns {Promise} Returns a promise that resolves to the generated canvas element.\n   */\n  $toCanvas(options?: {\n    width?: number;\n    height?: number;\n    beforeDraw?: (context: CanvasRenderingContext2D, canvas: HTMLCanvasElement) => void;\n  }): Promise<HTMLCanvasElement> {\n    return new Promise<HTMLCanvasElement>((resolve, reject) => {\n      if (!this.isConnected) {\n        reject(new Error('The current element is not connected to the DOM.'));\n        return;\n      }\n\n      const canvas = document.createElement('canvas');\n      let { width, height } = this;\n      let scale = 1;\n\n      if (isPlainObject(options)\n        && (isPositiveNumber(options.width) || isPositiveNumber(options.height))) {\n        ({ width, height } = getAdjustedSizes({\n          aspectRatio: width / height,\n          width: options.width as number,\n          height: options.height as number,\n        }));\n        scale = width / this.width;\n      }\n\n      canvas.width = width;\n      canvas.height = height;\n\n      if (!this.$canvas) {\n        resolve(canvas);\n        return;\n      }\n\n      const cropperImage: CropperImage | null = this.$canvas.querySelector(\n        this.$getTagNameOf(CROPPER_IMAGE),\n      );\n\n      if (!cropperImage) {\n        resolve(canvas);\n        return;\n      }\n\n      cropperImage.$ready().then((image: HTMLImageElement) => {\n        const context = canvas.getContext('2d');\n\n        if (context) {\n          const [a, b, c, d, e, f] = cropperImage.$getTransform();\n          const offsetX = -this.x;\n          const offsetY = -this.y;\n          const translateX = ((offsetX * d) - (c * offsetY)) / ((a * d) - (c * b));\n          const translateY = ((offsetY * a) - (b * offsetX)) / ((a * d) - (c * b));\n          let newE = a * translateX + c * translateY + e;\n          let newF = b * translateX + d * translateY + f;\n          let destWidth = image.naturalWidth;\n          let destHeight = image.naturalHeight;\n\n          if (scale !== 1) {\n            newE *= scale;\n            newF *= scale;\n            destWidth *= scale;\n            destHeight *= scale;\n          }\n\n          const centerX = destWidth / 2;\n          const centerY = destHeight / 2;\n\n          context.fillStyle = 'transparent';\n          context.fillRect(0, 0, width, height);\n\n          if (isPlainObject(options) && isFunction(options.beforeDraw)) {\n            options.beforeDraw.call(this, context, canvas);\n          }\n\n          context.save();\n\n          // Move the transform origin to the center of the image.\n          // https://developer.mozilla.org/en-US/docs/Web/CSS/transform-origin\n          context.translate(centerX, centerY);\n          context.transform(a, b, c, d, newE, newF);\n\n          // Move the transform origin to the top-left of the image.\n          context.translate(-centerX, -centerY);\n          context.drawImage(image, 0, 0, destWidth, destHeight);\n          context.restore();\n        }\n\n        resolve(canvas);\n      }).catch(reject);\n    });\n  }\n}\n"
  },
  {
    "path": "packages/element-selection/src/style.ts",
    "content": "export default `\n:host {\n  display: block;\n  left: 0;\n  position: relative;\n  right: 0;\n}\n\n:host([outlined]) {\n  outline: 1px solid var(--theme-color);\n}\n\n:host([multiple]) {\n  outline: 1px dashed rgba(255, 255, 255, 0.5);\n}\n\n:host([multiple])::after {\n  bottom: 0;\n  content: '';\n  cursor: pointer;\n  display: block;\n  left: 0;\n  position: absolute;\n  right: 0;\n  top: 0;\n}\n\n:host([multiple][active]) {\n  outline-color: var(--theme-color);\n  z-index: 1;\n}\n\n:host([multiple]) > * {\n  visibility: hidden;\n}\n\n:host([multiple][active]) > * {\n  visibility: visible;\n}\n\n:host([multiple][active])::after {\n  display: none;\n}\n`;\n"
  },
  {
    "path": "packages/element-selection/tests/index.spec.ts",
    "content": "import {\n  ACTION_RESIZE_NORTH,\n  ACTION_RESIZE_EAST,\n  ACTION_RESIZE_SOUTH,\n  ACTION_RESIZE_WEST,\n  ACTION_RESIZE_NORTHEAST,\n  ACTION_RESIZE_NORTHWEST,\n  ACTION_RESIZE_SOUTHEAST,\n  ACTION_RESIZE_SOUTHWEST,\n} from '@cropper/utils';\nimport CropperSelection from '../src';\n\nCropperSelection.$define();\n\ndescribe('CropperSelection', () => {\n  describe('properties', () => {\n    describe('x', () => {\n      it('should be `0` by default', () => {\n        const element = new CropperSelection();\n\n        expect(element.x).toBe(0);\n      });\n\n      it('should be `1`', () => {\n        const element = new CropperSelection();\n\n        element.setAttribute('x', '1');\n        expect(element.x).toBe(1);\n      });\n    });\n\n    describe('y', () => {\n      it('should be `0` by default', () => {\n        const element = new CropperSelection();\n\n        expect(element.y).toBe(0);\n      });\n\n      it('should be `1`', () => {\n        const element = new CropperSelection();\n\n        element.setAttribute('y', '1');\n        expect(element.y).toBe(1);\n      });\n    });\n\n    describe('width', () => {\n      it('should be `0` by default', () => {\n        const element = new CropperSelection();\n\n        expect(element.width).toBe(0);\n      });\n\n      it('should be `1`', () => {\n        const element = new CropperSelection();\n\n        element.setAttribute('width', '1');\n        expect(element.width).toBe(1);\n      });\n    });\n\n    describe('height', () => {\n      it('should be `0` by default', () => {\n        const element = new CropperSelection();\n\n        expect(element.height).toBe(0);\n      });\n\n      it('should be `1`', () => {\n        const element = new CropperSelection();\n\n        element.setAttribute('height', '1');\n        expect(element.height).toBe(1);\n      });\n    });\n\n    describe('aspectRatio', () => {\n      it('should be `NaN` by default', () => {\n        const element = new CropperSelection();\n\n        expect(element.aspectRatio).toBeNaN();\n      });\n\n      it('should be `1`', () => {\n        const element = new CropperSelection();\n\n        element.setAttribute('aspect-ratio', '1');\n        expect(element.aspectRatio).toBe(1);\n      });\n    });\n\n    describe('initialAspectRatio', () => {\n      it('should be `NaN` by default', () => {\n        const element = new CropperSelection();\n\n        expect(element.initialAspectRatio).toBeNaN();\n      });\n\n      it('should be `1`', () => {\n        const element = new CropperSelection();\n\n        element.setAttribute('initial-aspect-ratio', '1');\n        expect(element.initialAspectRatio).toBe(1);\n      });\n    });\n\n    describe('initialCoverage', () => {\n      it('should be `NaN` by default', () => {\n        const element = new CropperSelection();\n\n        expect(element.initialCoverage).toBeNaN();\n      });\n\n      it('should be `0.5`', () => {\n        const element = new CropperSelection();\n\n        element.setAttribute('initial-coverage', '0.5');\n        expect(element.initialCoverage).toBe(0.5);\n      });\n    });\n\n    describe('dynamic', () => {\n      it('should be `false` by default', () => {\n        const element = new CropperSelection();\n\n        expect(element.dynamic).toBe(false);\n      });\n\n      it('should be `true`', () => {\n        const element = new CropperSelection();\n\n        element.setAttribute('dynamic', '');\n        expect(element.dynamic).toBe(true);\n      });\n    });\n\n    describe('movable', () => {\n      it('should be `false` by default', () => {\n        const element = new CropperSelection();\n\n        expect(element.movable).toBe(false);\n      });\n\n      it('should be `true`', () => {\n        const element = new CropperSelection();\n\n        element.setAttribute('movable', '');\n        expect(element.movable).toBe(true);\n      });\n    });\n\n    describe('resizable', () => {\n      it('should be `false` by default', () => {\n        const element = new CropperSelection();\n\n        expect(element.resizable).toBe(false);\n      });\n\n      it('should be `true`', () => {\n        const element = new CropperSelection();\n\n        element.setAttribute('resizable', '');\n        expect(element.resizable).toBe(true);\n      });\n    });\n\n    describe('zoomable', () => {\n      it('should be `false` by default', () => {\n        const element = new CropperSelection();\n\n        expect(element.zoomable).toBe(false);\n      });\n\n      it('should be `true`', () => {\n        const element = new CropperSelection();\n\n        element.setAttribute('zoomable', '');\n        expect(element.zoomable).toBe(true);\n      });\n    });\n\n    describe('outlined', () => {\n      it('should be `false` by default', () => {\n        const element = new CropperSelection();\n\n        expect(element.outlined).toBe(false);\n      });\n\n      it('should be `true`', () => {\n        const element = new CropperSelection();\n\n        element.setAttribute('outlined', '');\n        expect(element.outlined).toBe(true);\n      });\n    });\n\n    describe('precise', () => {\n      it('should be `false` by default', () => {\n        const element = new CropperSelection();\n\n        expect(element.precise).toBe(false);\n      });\n\n      it('should be `true`', () => {\n        const element = new CropperSelection();\n\n        element.setAttribute('precise', '');\n        expect(element.precise).toBe(true);\n      });\n    });\n  });\n\n  describe('methods', () => {\n    describe('$move', () => {\n      it('should move the selection', () => {\n        const element = new CropperSelection();\n\n        element.movable = true;\n        element.$move(1, 2);\n        expect(element.x).toBe(1);\n        expect(element.y).toBe(2);\n      });\n\n      it('should default to the first parameter for the second parameter', () => {\n        const element = new CropperSelection();\n\n        element.movable = true;\n        element.$move(1);\n        expect(element.x).toBe(1);\n        expect(element.y).toBe(1);\n      });\n    });\n\n    describe('$moveTo', () => {\n      it('should the selection to a specific position', () => {\n        const element = new CropperSelection();\n\n        element.movable = true;\n        element.$moveTo(1, 2);\n        expect(element.x).toBe(1);\n        expect(element.y).toBe(2);\n      });\n\n      it('should default to the first parameter for the second parameter', () => {\n        const element = new CropperSelection();\n\n        element.movable = true;\n        element.$moveTo(1);\n        expect(element.x).toBe(1);\n        expect(element.y).toBe(1);\n      });\n    });\n\n    describe('$resize', () => {\n      describe(ACTION_RESIZE_NORTH, () => {\n        it('should resize the north side', () => {\n          const element = new CropperSelection();\n\n          element.resizable = true;\n          element.$resize(ACTION_RESIZE_NORTH, 0, -1);\n          expect(element.y).toBe(-1);\n          expect(element.height).toBe(1);\n          element.$resize(ACTION_RESIZE_NORTH, 0, 1);\n          expect(element.y).toBe(0);\n          expect(element.height).toBe(0);\n        });\n\n        it('should resize the south side when the height is `0`', () => {\n          const element = new CropperSelection();\n\n          element.resizable = true;\n          element.$resize(ACTION_RESIZE_NORTH, 0, 1);\n          expect(element.y).toBe(0);\n          expect(element.height).toBe(1);\n        });\n\n        it('should resize the east and west sides as well when the aspect ratio is set', () => {\n          const element = new CropperSelection();\n\n          element.resizable = true;\n          element.aspectRatio = 1;\n          element.$resize(ACTION_RESIZE_NORTH, 0, -2);\n          expect(element.x).toBe(-1);\n          expect(element.y).toBe(-2);\n          expect(element.width).toBe(2);\n          expect(element.height).toBe(2);\n        });\n      });\n\n      describe(ACTION_RESIZE_EAST, () => {\n        it('should resize the east side', () => {\n          const element = new CropperSelection();\n\n          element.resizable = true;\n          element.$resize(ACTION_RESIZE_EAST, 1, 0);\n          expect(element.x).toBe(0);\n          expect(element.width).toBe(1);\n          element.$resize(ACTION_RESIZE_EAST, -1, 0);\n          expect(element.x).toBe(0);\n          expect(element.width).toBe(0);\n        });\n\n        it('should resize the west side when the width is `0`', () => {\n          const element = new CropperSelection();\n\n          element.resizable = true;\n          element.$resize(ACTION_RESIZE_EAST, -1, 0);\n          expect(element.x).toBe(-1);\n          expect(element.width).toBe(1);\n        });\n\n        it('should resize the south and north sides as well when the aspect ratio is set', () => {\n          const element = new CropperSelection();\n\n          element.resizable = true;\n          element.aspectRatio = 1;\n          element.$resize(ACTION_RESIZE_EAST, 2, 0);\n          expect(element.x).toBe(0);\n          expect(element.y).toBe(-1);\n          expect(element.width).toBe(2);\n          expect(element.height).toBe(2);\n        });\n      });\n\n      describe(ACTION_RESIZE_SOUTH, () => {\n        it('should resize the south side', () => {\n          const element = new CropperSelection();\n\n          element.resizable = true;\n          element.$resize(ACTION_RESIZE_SOUTH, 0, 1);\n          expect(element.y).toBe(0);\n          expect(element.height).toBe(1);\n          element.$resize(ACTION_RESIZE_SOUTH, 0, -1);\n          expect(element.y).toBe(0);\n          expect(element.height).toBe(0);\n        });\n\n        it('should resize the north side when the height is `0`', () => {\n          const element = new CropperSelection();\n\n          element.resizable = true;\n          element.$resize(ACTION_RESIZE_SOUTH, 0, -1);\n          expect(element.y).toBe(-1);\n          expect(element.height).toBe(1);\n        });\n\n        it('should resize the east and west sides as well when the aspect ratio is set', () => {\n          const element = new CropperSelection();\n\n          element.resizable = true;\n          element.aspectRatio = 1;\n          element.$resize(ACTION_RESIZE_SOUTH, 0, 2);\n          expect(element.x).toBe(-1);\n          expect(element.y).toBe(0);\n          expect(element.width).toBe(2);\n          expect(element.height).toBe(2);\n        });\n      });\n\n      describe(ACTION_RESIZE_WEST, () => {\n        it('should resize the west side', () => {\n          const element = new CropperSelection();\n\n          element.resizable = true;\n          element.$resize(ACTION_RESIZE_WEST, -1, 0);\n          expect(element.x).toBe(-1);\n          expect(element.width).toBe(1);\n          element.$resize(ACTION_RESIZE_WEST, 1, 0);\n          expect(element.x).toBe(0);\n          expect(element.width).toBe(0);\n        });\n\n        it('should resize the east side when the width is `0`', () => {\n          const element = new CropperSelection();\n\n          element.resizable = true;\n          element.$resize(ACTION_RESIZE_WEST, 1, 0);\n          expect(element.x).toBe(0);\n          expect(element.width).toBe(1);\n        });\n\n        it('should resize the south and north sides as well when the aspect ratio is set', () => {\n          const element = new CropperSelection();\n\n          element.resizable = true;\n          element.aspectRatio = 1;\n          element.$resize(ACTION_RESIZE_WEST, -2, 0);\n          expect(element.x).toBe(-2);\n          expect(element.y).toBe(-1);\n          expect(element.width).toBe(2);\n          expect(element.height).toBe(2);\n        });\n      });\n\n      describe(ACTION_RESIZE_NORTHEAST, () => {\n        it('should resize the northeast side', () => {\n          const element = new CropperSelection();\n\n          element.resizable = true;\n          element.$resize(ACTION_RESIZE_NORTHEAST, 1, -1);\n          expect(element.x).toBe(0);\n          expect(element.y).toBe(-1);\n          expect(element.width).toBe(1);\n          expect(element.height).toBe(1);\n          element.$resize(ACTION_RESIZE_NORTHEAST, -1, 1);\n          expect(element.x).toBe(0);\n          expect(element.y).toBe(0);\n          expect(element.width).toBe(0);\n          expect(element.height).toBe(0);\n        });\n\n        it('should resize the southwest side when the width and height are `0`', () => {\n          const element = new CropperSelection();\n\n          element.resizable = true;\n          element.$resize(ACTION_RESIZE_NORTHEAST, -1, 1);\n          expect(element.x).toBe(-1);\n          expect(element.y).toBe(0);\n          expect(element.height).toBe(1);\n          expect(element.height).toBe(1);\n        });\n\n        it('should resize the north side as well when the aspect ratio is set', () => {\n          const element = new CropperSelection();\n\n          element.resizable = true;\n          element.aspectRatio = 1;\n          element.$resize(ACTION_RESIZE_NORTHEAST, 1, 0);\n          expect(element.x).toBe(0);\n          expect(element.y).toBe(-1);\n          expect(element.width).toBe(1);\n          expect(element.height).toBe(1);\n        });\n      });\n\n      describe(ACTION_RESIZE_NORTHWEST, () => {\n        it('should resize the northwest side', () => {\n          const element = new CropperSelection();\n\n          element.resizable = true;\n          element.$resize(ACTION_RESIZE_NORTHWEST, -1, -1);\n          expect(element.x).toBe(-1);\n          expect(element.y).toBe(-1);\n          expect(element.width).toBe(1);\n          expect(element.height).toBe(1);\n          element.$resize(ACTION_RESIZE_NORTHWEST, 1, 1);\n          expect(element.x).toBe(0);\n          expect(element.y).toBe(0);\n          expect(element.width).toBe(0);\n          expect(element.height).toBe(0);\n        });\n\n        it('should resize the southeast side when the width and height are `0`', () => {\n          const element = new CropperSelection();\n\n          element.resizable = true;\n          element.$resize(ACTION_RESIZE_NORTHWEST, 1, 1);\n          expect(element.x).toBe(0);\n          expect(element.y).toBe(0);\n          expect(element.height).toBe(1);\n          expect(element.height).toBe(1);\n        });\n\n        it('should resize the north side as well when the aspect ratio is set', () => {\n          const element = new CropperSelection();\n\n          element.resizable = true;\n          element.aspectRatio = 1;\n          element.$resize(ACTION_RESIZE_NORTHWEST, -1, 0);\n          expect(element.x).toBe(-1);\n          expect(element.y).toBe(-1);\n          expect(element.width).toBe(1);\n          expect(element.height).toBe(1);\n        });\n      });\n\n      describe(ACTION_RESIZE_SOUTHEAST, () => {\n        it('should resize the southeast side', () => {\n          const element = new CropperSelection();\n\n          element.resizable = true;\n          element.$resize(ACTION_RESIZE_SOUTHEAST, 1, 1);\n          expect(element.x).toBe(0);\n          expect(element.y).toBe(0);\n          expect(element.width).toBe(1);\n          expect(element.height).toBe(1);\n          element.$resize(ACTION_RESIZE_SOUTHEAST, -1, -1);\n          expect(element.x).toBe(0);\n          expect(element.y).toBe(0);\n          expect(element.width).toBe(0);\n          expect(element.height).toBe(0);\n        });\n\n        it('should resize the northwest side when the width and height are `0`', () => {\n          const element = new CropperSelection();\n\n          element.resizable = true;\n          element.$resize(ACTION_RESIZE_SOUTHEAST, -1, -1);\n          expect(element.x).toBe(-1);\n          expect(element.y).toBe(-1);\n          expect(element.height).toBe(1);\n          expect(element.height).toBe(1);\n        });\n\n        it('should resize the south side as well when the aspect ratio is set', () => {\n          const element = new CropperSelection();\n\n          element.resizable = true;\n          element.aspectRatio = 1;\n          element.$resize(ACTION_RESIZE_SOUTHEAST, 1, 0);\n          expect(element.x).toBe(0);\n          expect(element.y).toBe(0);\n          expect(element.width).toBe(1);\n          expect(element.height).toBe(1);\n        });\n      });\n\n      describe(ACTION_RESIZE_SOUTHWEST, () => {\n        it('should resize the southwest side', () => {\n          const element = new CropperSelection();\n\n          element.resizable = true;\n          element.$resize(ACTION_RESIZE_SOUTHWEST, -1, 1);\n          expect(element.x).toBe(-1);\n          expect(element.y).toBe(0);\n          expect(element.width).toBe(1);\n          expect(element.height).toBe(1);\n          element.$resize(ACTION_RESIZE_SOUTHWEST, 1, -1);\n          expect(element.x).toBe(0);\n          expect(element.y).toBe(0);\n          expect(element.width).toBe(0);\n          expect(element.height).toBe(0);\n        });\n\n        it('should resize the northeast side when the width and height are `0`', () => {\n          const element = new CropperSelection();\n\n          element.resizable = true;\n          element.$resize(ACTION_RESIZE_SOUTHWEST, 1, -1);\n          expect(element.x).toBe(0);\n          expect(element.y).toBe(-1);\n          expect(element.height).toBe(1);\n          expect(element.height).toBe(1);\n        });\n\n        it('should resize the south side as well when the aspect ratio is set', () => {\n          const element = new CropperSelection();\n\n          element.resizable = true;\n          element.aspectRatio = 1;\n          element.$resize(ACTION_RESIZE_SOUTHWEST, -1, 0);\n          expect(element.x).toBe(-1);\n          expect(element.y).toBe(0);\n          expect(element.width).toBe(1);\n          expect(element.height).toBe(1);\n        });\n      });\n    });\n\n    describe('$zoom', () => {\n      it('should zoom in the selection', () => {\n        const element = new CropperSelection();\n\n        element.zoomable = true;\n        element.width = 1;\n        element.height = 1;\n        element.$zoom(1);\n        expect(element.width).toBe(2);\n        expect(element.height).toBe(2);\n      });\n\n      it('should zoom out the selection', () => {\n        const element = new CropperSelection();\n\n        element.zoomable = true;\n        element.width = 2;\n        element.height = 2;\n        element.$zoom(-1);\n        expect(element.width).toBe(1);\n        expect(element.height).toBe(1);\n      });\n    });\n\n    describe('$change', () => {\n      it('should change the position', () => {\n        const element = new CropperSelection();\n\n        element.$change(1, 1);\n        expect(element.x).toBe(1);\n        expect(element.y).toBe(1);\n        expect(element.width).toBe(0);\n        expect(element.height).toBe(0);\n      });\n\n      it('should change the size', () => {\n        const element = new CropperSelection();\n\n        element.$change(0, 0, 1, 1);\n        expect(element.width).toBe(1);\n        expect(element.height).toBe(1);\n      });\n\n      it('should adjust the width and height parameters when the aspect ratio is passed', () => {\n        const element = new CropperSelection();\n\n        element.$change(0, 0, 1, 2, 1);\n        expect(element.width).toBe(2);\n        expect(element.height).toBe(2);\n      });\n    });\n\n    describe('$reset', () => {\n      it('should reset the selection to its initial position and size', () => {\n        const element = new CropperSelection();\n\n        element.$change(1, 1, 1, 1);\n        element.$reset();\n        expect(element.x).toBe(0);\n        expect(element.y).toBe(0);\n        expect(element.width).toBe(0);\n        expect(element.height).toBe(0);\n      });\n    });\n\n    describe('$render', () => {\n      it('should refresh the position or size of the selection', () => {\n        const element = new CropperSelection();\n\n        element.x = 1;\n        element.y = 1;\n        element.width = 1;\n        element.height = 1;\n        expect(element.style.transform).toBe('');\n        expect(element.style.width).toBe('');\n        expect(element.style.height).toBe('');\n        element.$render();\n        expect(element.style.transform).toBe('translate(1px, 1px)');\n        expect(element.style.width).toBe('1px');\n        expect(element.style.height).toBe('1px');\n      });\n    });\n\n    describe('$toCanvas', () => {\n      it('should return a promise that resolves the generated canvas element', (done) => {\n        const element = new CropperSelection();\n\n        document.body.appendChild(element);\n\n        const promise = element.$toCanvas();\n\n        expect(promise).toBeInstanceOf(Promise);\n        promise.then((canvas) => {\n          expect(canvas).toBeInstanceOf(HTMLCanvasElement);\n          done();\n        });\n      });\n\n      it('should throw error when it is not connected to the DOM', (done) => {\n        const element = new CropperSelection();\n\n        element.$toCanvas().catch((error) => {\n          expect(error.message).toBe('The current element is not connected to the DOM.');\n          done();\n        });\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/element-shade/README.md",
    "content": "# @cropper/element-shade\n\n> A custom shade element for the Cropper.\n\n## Main npm package files\n\n```text\ndist/\n├── element-shade.js         (UMD, bundled)\n├── element-shade.min.js     (UMD, bundled, compressed)\n├── element-shade.raw.js     (UMD, unbundled, default)\n├── element-shade.esm.js     (ECMAScript Module, bundled)\n├── element-shade.esm.min.js (ECMAScript Module, bundled, compressed)\n├── element-shade.esm.raw.js (ECMAScript Module, unbundled)\n└── element-shade-shade.d.ts       (TypeScript Declaration File)\n```\n\n## Getting started\n\n### Installation\n\n```sh\nnpm install @cropper/element-shade\n```\n\n### Usage\n\n```js\nimport CropperShade from '@cropper/element-shade';\n\nCropperShade.$define();\n```\n\n```html\n<cropper-shade></cropper-shade>\n```\n\n## Versioning\n\nMaintained under the [Semantic Versioning guidelines](https://semver.org).\n\n## License\n\n[MIT](https://opensource.org/licenses/MIT)\n"
  },
  {
    "path": "packages/element-shade/api-extractor.json",
    "content": "{\n  \"extends\": \"../../api-extractor.json\",\n  \"mainEntryPointFilePath\": \"./.temp/packages/<unscopedPackageName>/src/index.d.ts\",\n  \"dtsRollup\": {\n    \"publicTrimmedFilePath\": \"./dist/<unscopedPackageName>.d.ts\"\n  }\n}\n"
  },
  {
    "path": "packages/element-shade/package.json",
    "content": "{\n  \"name\": \"@cropper/element-shade\",\n  \"version\": \"2.1.0\",\n  \"description\": \"A custom shade element for the Cropper.\",\n  \"main\": \"dist/element-shade.raw.js\",\n  \"module\": \"dist/element-shade.esm.raw.js\",\n  \"types\": \"dist/element-shade.d.ts\",\n  \"unpkg\": \"dist/element-shade.js\",\n  \"jsdelivr\": \"dist/element-shade.js\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"import\": {\n        \"types\": \"./dist/element-shade.d.ts\",\n        \"node\": {\n          \"production\": \"./dist/element-shade.esm.min.js\",\n          \"development\": \"./dist/element-shade.esm.js\",\n          \"default\": \"./dist/element-shade.esm.raw.js\"\n        },\n        \"default\": \"./dist/element-shade.esm.raw.js\"\n      },\n      \"require\": {\n        \"types\": \"./dist/element-shade.d.ts\",\n        \"node\": {\n          \"production\": \"./dist/element-shade.min.js\",\n          \"development\": \"./dist/element-shade.js\",\n          \"default\": \"./dist/element-shade.raw.js\"\n        },\n        \"default\": \"./dist/element-shade.raw.js\"\n      }\n    },\n    \"./dist/*\": \"./dist/*\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"scripts\": {\n    \"api-extractor\": \"api-extractor run --local --verbose\",\n    \"build\": \"npm run tsc && npm run api-extractor && npm run rollup\",\n    \"clean\": \"del-cli dist .temp\",\n    \"release\": \"npm run clean && npm run build\",\n    \"rollup\": \"rollup -c ../../rollup.config.js\",\n    \"tsc\": \"tsc --outDir ./.temp --declaration --emitDeclarationOnly\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/fengyuanchen/cropperjs.git\",\n    \"directory\": \"packages/element-shade\"\n  },\n  \"keywords\": [\n    \"shade\",\n    \"cropper\",\n    \"cropper.js\",\n    \"cropper-element\",\n    \"custom-element\",\n    \"web-component\"\n  ],\n  \"author\": \"Chen Fengyuan (https://chenfengyuan.com/)\",\n  \"license\": \"MIT\",\n  \"bugs\": \"https://github.com/fengyuanchen/cropperjs/issues\",\n  \"homepage\": \"https://github.com/fengyuanchen/cropperjs/tree/main/packages/element-shade/#readme\",\n  \"dependencies\": {\n    \"@cropper/element\": \"^2.1.0\",\n    \"@cropper/element-canvas\": \"^2.1.0\",\n    \"@cropper/element-selection\": \"^2.1.0\",\n    \"@cropper/utils\": \"^2.1.0\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  }\n}\n"
  },
  {
    "path": "packages/element-shade/src/index.ts",
    "content": "import CropperElement from '@cropper/element';\nimport type CropperCanvas from '@cropper/element-canvas';\nimport type CropperSelection from '@cropper/element-selection';\nimport {\n  ACTION_SELECT,\n  CROPPER_CANVAS,\n  CROPPER_SELECTION,\n  CROPPER_SHADE,\n  EVENT_ACTION_END,\n  EVENT_ACTION_START,\n  EVENT_CHANGE,\n  EVENT_RESIZE,\n  WINDOW,\n  isNumber,\n  off,\n  on,\n} from '@cropper/utils';\nimport style from './style';\n\nconst canvasCache = new WeakMap();\n\nexport default class CropperShade extends CropperElement {\n  static $name = CROPPER_SHADE;\n\n  static $version = '__VERSION__';\n\n  protected $onWindowResize: EventListener | null = null;\n\n  protected $onCanvasActionEnd: EventListener | null = null;\n\n  protected $onCanvasActionStart: EventListener | null = null;\n\n  protected $onSelectionChange: EventListener | null = null;\n\n  protected $style = style;\n\n  x = 0;\n\n  y = 0;\n\n  width = 0;\n\n  height = 0;\n\n  slottable = false;\n\n  themeColor = 'rgba(0, 0, 0, 0.65)';\n\n  protected set $canvas(element: CropperCanvas) {\n    canvasCache.set(this, element);\n  }\n\n  protected get $canvas(): CropperCanvas {\n    return canvasCache.get(this);\n  }\n\n  protected static get observedAttributes(): string[] {\n    return super.observedAttributes.concat([\n      'height',\n      'width',\n      'x',\n      'y',\n    ]);\n  }\n\n  protected connectedCallback(): void {\n    super.connectedCallback();\n\n    const $canvas: CropperCanvas | null = this.closest(this.$getTagNameOf(CROPPER_CANVAS));\n\n    if ($canvas) {\n      this.$canvas = $canvas;\n      this.style.position = 'absolute';\n\n      const $selection: CropperSelection | null = $canvas.querySelector(\n        this.$getTagNameOf(CROPPER_SELECTION),\n      );\n\n      if ($selection) {\n        this.$onWindowResize = this.$render.bind(this);\n        this.$onCanvasActionStart = (event) => {\n          if ($selection.hidden && (event as CustomEvent).detail.action === ACTION_SELECT) {\n            this.hidden = false;\n          }\n        };\n        this.$onCanvasActionEnd = (event) => {\n          if ($selection.hidden && (event as CustomEvent).detail.action === ACTION_SELECT) {\n            this.hidden = true;\n          }\n        };\n        this.$onSelectionChange = (event) => {\n          const {\n            x,\n            y,\n            width,\n            height,\n          } = event.defaultPrevented ? $selection : (event as CustomEvent).detail;\n\n          this.$change(x, y, width, height);\n\n          if ($selection.hidden || (x === 0 && y === 0 && width === 0 && height === 0)) {\n            this.hidden = true;\n          }\n        };\n        on(window, EVENT_RESIZE, this.$onWindowResize);\n        on($canvas, EVENT_ACTION_START, this.$onCanvasActionStart);\n        on($canvas, EVENT_ACTION_END, this.$onCanvasActionEnd);\n        on($canvas, EVENT_CHANGE, this.$onSelectionChange);\n      }\n    }\n\n    this.$render();\n  }\n\n  protected disconnectedCallback(): void {\n    const { $canvas } = this;\n\n    if ($canvas) {\n      if (this.$onWindowResize) {\n        off(window, EVENT_RESIZE, this.$onWindowResize);\n        this.$onWindowResize = null;\n      }\n\n      if (this.$onCanvasActionStart) {\n        off($canvas, EVENT_ACTION_START, this.$onCanvasActionStart);\n        this.$onCanvasActionStart = null;\n      }\n\n      if (this.$onCanvasActionEnd) {\n        off($canvas, EVENT_ACTION_END, this.$onCanvasActionEnd);\n        this.$onCanvasActionEnd = null;\n      }\n\n      if (this.$onSelectionChange) {\n        off($canvas, EVENT_CHANGE, this.$onSelectionChange);\n        this.$onSelectionChange = null;\n      }\n    }\n\n    super.disconnectedCallback();\n  }\n\n  /**\n   * Changes the position and/or size of the shade.\n   * @param {number} x The new position in the horizontal direction.\n   * @param {number} y The new position in the vertical direction.\n   * @param {number} [width] The new width.\n   * @param {number} [height] The new height.\n   * @returns {CropperShade} Returns `this` for chaining.\n   */\n  $change(x: number, y: number, width: number = this.width, height: number = this.height): this {\n    if (\n      !isNumber(x)\n      || !isNumber(y)\n      || !isNumber(width)\n      || !isNumber(height)\n      || (x === this.x && y === this.y && width === this.width && height === this.height)\n    ) {\n      return this;\n    }\n\n    if (this.hidden) {\n      this.hidden = false;\n    }\n\n    this.x = x;\n    this.y = y;\n    this.width = width;\n    this.height = height;\n\n    return this.$render();\n  }\n\n  /**\n   * Resets the shade to its initial position and size.\n   * @returns {CropperShade} Returns `this` for chaining.\n   */\n  $reset(): this {\n    return this.$change(0, 0, 0, 0);\n  }\n\n  /**\n   * Refreshes the position or size of the shade.\n   * @returns {CropperShade} Returns `this` for chaining.\n   */\n  $render(): this {\n    return this.$setStyles({\n      transform: `translate(${this.x}px, ${this.y}px)`,\n      width: this.width,\n      height: this.height,\n      outlineWidth: WINDOW.innerWidth * WINDOW.devicePixelRatio,\n    });\n  }\n}\n"
  },
  {
    "path": "packages/element-shade/src/style.ts",
    "content": "export default `\n:host {\n  display: block;\n  height: 0;\n  left: 0;\n  outline: var(--theme-color) solid 1px;\n  position: relative;\n  top: 0;\n  width: 0;\n}\n\n:host([transparent]) {\n  outline-color: transparent;\n}\n`;\n"
  },
  {
    "path": "packages/element-shade/tests/index.spec.ts",
    "content": "import CropperShade from '../src';\n\nCropperShade.$define();\n\ndescribe('CropperShade', () => {\n  describe('properties', () => {\n    describe('x', () => {\n      it('should be `0` by default', () => {\n        const element = new CropperShade();\n\n        expect(element.x).toBe(0);\n      });\n\n      it('should be `1`', () => {\n        const element = new CropperShade();\n\n        element.setAttribute('x', '1');\n        expect(element.x).toBe(1);\n      });\n    });\n\n    describe('y', () => {\n      it('should be `0` by default', () => {\n        const element = new CropperShade();\n\n        expect(element.y).toBe(0);\n      });\n\n      it('should be `1`', () => {\n        const element = new CropperShade();\n\n        element.setAttribute('y', '1');\n        expect(element.y).toBe(1);\n      });\n    });\n\n    describe('width', () => {\n      it('should be `0` by default', () => {\n        const element = new CropperShade();\n\n        expect(element.width).toBe(0);\n      });\n\n      it('should be `1`', () => {\n        const element = new CropperShade();\n\n        element.setAttribute('width', '1');\n        expect(element.width).toBe(1);\n      });\n    });\n\n    describe('height', () => {\n      it('should be `0` by default', () => {\n        const element = new CropperShade();\n\n        expect(element.height).toBe(0);\n      });\n\n      it('should be `1`', () => {\n        const element = new CropperShade();\n\n        element.setAttribute('height', '1');\n        expect(element.height).toBe(1);\n      });\n    });\n\n    describe('slottable', () => {\n      it('should be `false` by default', () => {\n        const element = new CropperShade();\n\n        expect(element.slottable).toBe(false);\n      });\n\n      it('should be `true`', () => {\n        const element = new CropperShade();\n\n        element.setAttribute('slottable', '');\n        expect(element.slottable).toBe(true);\n      });\n    });\n\n    describe('themeColor', () => {\n      it('should be `\"rgba(0, 0, 0, 0.65)\"` by default', () => {\n        const element = new CropperShade();\n\n        expect(element.themeColor).toBe('rgba(0, 0, 0, 0.65)');\n      });\n\n      it('should be `\"#000\"`', () => {\n        const element = new CropperShade();\n\n        element.setAttribute('theme-color', '#000');\n        expect(element.themeColor).toBe('#000');\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/element-viewer/README.md",
    "content": "# @cropper/element-viewer\n\n> A custom viewer element for the Cropper.\n\n## Main npm package files\n\n```text\ndist/\n├── element-viewer.js         (UMD, bundled)\n├── element-viewer.min.js     (UMD, bundled, compressed)\n├── element-viewer.raw.js     (UMD, unbundled, default)\n├── element-viewer.esm.js     (ECMAScript Module, bundled)\n├── element-viewer.esm.min.js (ECMAScript Module, bundled, compressed)\n├── element-viewer.esm.raw.js (ECMAScript Module, unbundled)\n└── element-viewer.d.ts       (TypeScript Declaration File)\n```\n\n## Getting started\n\n### Installation\n\n```sh\nnpm install @cropper/element-viewer\n```\n\n### Usage\n\n```js\nimport CropperViewer from '@cropper/element-viewer';\n\nCropperViewer.$define();\n```\n\n```html\n<cropper-viewer></cropper-viewer>\n```\n\n## Versioning\n\nMaintained under the [Semantic Versioning guidelines](https://semver.org).\n\n## License\n\n[MIT](https://opensource.org/licenses/MIT)\n"
  },
  {
    "path": "packages/element-viewer/api-extractor.json",
    "content": "{\n  \"extends\": \"../../api-extractor.json\",\n  \"mainEntryPointFilePath\": \"./.temp/packages/<unscopedPackageName>/src/index.d.ts\",\n  \"dtsRollup\": {\n    \"publicTrimmedFilePath\": \"./dist/<unscopedPackageName>.d.ts\"\n  }\n}\n"
  },
  {
    "path": "packages/element-viewer/package.json",
    "content": "{\n  \"name\": \"@cropper/element-viewer\",\n  \"version\": \"2.1.0\",\n  \"description\": \"A custom viewer element for the Cropper.\",\n  \"main\": \"dist/element-viewer.raw.js\",\n  \"module\": \"dist/element-viewer.esm.raw.js\",\n  \"types\": \"dist/element-viewer.d.ts\",\n  \"unpkg\": \"dist/element-viewer.js\",\n  \"jsdelivr\": \"dist/element-viewer.js\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"import\": {\n        \"types\": \"./dist/element-viewer.d.ts\",\n        \"node\": {\n          \"production\": \"./dist/element-viewer.esm.min.js\",\n          \"development\": \"./dist/element-viewer.esm.js\",\n          \"default\": \"./dist/element-viewer.esm.raw.js\"\n        },\n        \"default\": \"./dist/element-viewer.esm.raw.js\"\n      },\n      \"require\": {\n        \"types\": \"./dist/element-viewer.d.ts\",\n        \"node\": {\n          \"production\": \"./dist/element-viewer.min.js\",\n          \"development\": \"./dist/element-viewer.js\",\n          \"default\": \"./dist/element-viewer.raw.js\"\n        },\n        \"default\": \"./dist/element-viewer.raw.js\"\n      }\n    },\n    \"./dist/*\": \"./dist/*\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"scripts\": {\n    \"api-extractor\": \"api-extractor run --local --verbose\",\n    \"build\": \"npm run tsc && npm run api-extractor && npm run rollup\",\n    \"clean\": \"del-cli dist .temp\",\n    \"release\": \"npm run clean && npm run build\",\n    \"rollup\": \"rollup -c ../../rollup.config.js\",\n    \"tsc\": \"tsc --outDir ./.temp --declaration --emitDeclarationOnly\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/fengyuanchen/cropperjs.git\",\n    \"directory\": \"packages/element-viewer\"\n  },\n  \"keywords\": [\n    \"viewer\",\n    \"cropper\",\n    \"cropper.js\",\n    \"cropper-element\",\n    \"custom-element\",\n    \"web-component\"\n  ],\n  \"author\": \"Chen Fengyuan (https://chenfengyuan.com/)\",\n  \"license\": \"MIT\",\n  \"bugs\": \"https://github.com/fengyuanchen/cropperjs/issues\",\n  \"homepage\": \"https://github.com/fengyuanchen/cropperjs/tree/main/packages/element-viewer/#readme\",\n  \"dependencies\": {\n    \"@cropper/element\": \"^2.1.0\",\n    \"@cropper/element-canvas\": \"^2.1.0\",\n    \"@cropper/element-image\": \"^2.1.0\",\n    \"@cropper/element-selection\": \"^2.1.0\",\n    \"@cropper/utils\": \"^2.1.0\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  }\n}\n"
  },
  {
    "path": "packages/element-viewer/src/index.ts",
    "content": "import CropperElement from '@cropper/element';\nimport type { Selection } from '@cropper/element-selection';\nimport type CropperCanvas from '@cropper/element-canvas';\nimport type CropperImage from '@cropper/element-image';\nimport type CropperSelection from '@cropper/element-selection';\nimport {\n  CROPPER_CANVAS,\n  CROPPER_IMAGE,\n  CROPPER_SELECTION,\n  CROPPER_VIEWER,\n  EVENT_CHANGE,\n  EVENT_LOAD,\n  EVENT_TRANSFORM,\n  getRootDocument,\n  isElement,\n  off,\n  on,\n} from '@cropper/utils';\nimport style from './style';\n\nconst canvasCache = new WeakMap();\nconst imageCache = new WeakMap();\nconst selectionCache = new WeakMap();\nconst sourceImageCache = new WeakMap();\n\nexport const RESIZE_BOTH = 'both';\nexport const RESIZE_HORIZONTAL = 'horizontal';\nexport const RESIZE_VERTICAL = 'vertical';\nexport const RESIZE_NONE = 'none';\nexport default class CropperViewer extends CropperElement {\n  static $name = CROPPER_VIEWER;\n\n  static $version = '__VERSION__';\n\n  protected $onSelectionChange: EventListener | null = null;\n\n  protected $onSourceImageLoad: EventListener | null = null;\n\n  protected $onSourceImageTransform: EventListener | null = null;\n\n  protected $scale = 1;\n\n  protected $style = style;\n\n  resize: string = RESIZE_VERTICAL;\n\n  selection = '';\n\n  slottable = false;\n\n  protected set $image(element: CropperImage) {\n    imageCache.set(this, element);\n  }\n\n  protected get $image(): CropperImage {\n    return imageCache.get(this);\n  }\n\n  protected set $sourceImage(element: CropperImage) {\n    sourceImageCache.set(this, element);\n  }\n\n  protected get $sourceImage(): CropperImage {\n    return sourceImageCache.get(this);\n  }\n\n  protected set $canvas(element: CropperCanvas) {\n    canvasCache.set(this, element);\n  }\n\n  protected get $canvas(): CropperCanvas {\n    return canvasCache.get(this);\n  }\n\n  set $selection(element: CropperSelection) {\n    selectionCache.set(this, element);\n  }\n\n  get $selection(): CropperSelection {\n    return selectionCache.get(this);\n  }\n\n  protected static get observedAttributes(): string[] {\n    return super.observedAttributes.concat([\n      'resize',\n      'selection',\n    ]);\n  }\n\n  protected connectedCallback(): void {\n    super.connectedCallback();\n\n    let $selection: CropperSelection | null = null;\n\n    if (this.selection) {\n      $selection = getRootDocument(this)?.querySelector(this.selection) ?? null;\n    } else {\n      $selection = this.closest(this.$getTagNameOf(CROPPER_SELECTION));\n    }\n\n    if (isElement($selection)) {\n      this.$selection = $selection;\n      this.$onSelectionChange = this.$handleSelectionChange.bind(this);\n      on($selection, EVENT_CHANGE, this.$onSelectionChange);\n\n      const $canvas: CropperCanvas | null = $selection.closest(this.$getTagNameOf(CROPPER_CANVAS));\n\n      if ($canvas) {\n        this.$canvas = $canvas;\n\n        const $sourceImage: CropperImage | null = $canvas.querySelector(\n          this.$getTagNameOf(CROPPER_IMAGE),\n        );\n\n        if ($sourceImage) {\n          this.$sourceImage = $sourceImage;\n          this.$image = $sourceImage.cloneNode(true) as CropperImage;\n          this.$getShadowRoot().appendChild(this.$image);\n          this.$onSourceImageLoad = this.$handleSourceImageLoad.bind(this);\n          this.$onSourceImageTransform = this.$handleSourceImageTransform.bind(this);\n          on($sourceImage.$image, EVENT_LOAD, this.$onSourceImageLoad);\n          on($sourceImage, EVENT_TRANSFORM, this.$onSourceImageTransform);\n        }\n      }\n\n      this.$render();\n    }\n  }\n\n  protected disconnectedCallback(): void {\n    const { $selection, $sourceImage } = this;\n\n    if ($selection && this.$onSelectionChange) {\n      off($selection, EVENT_CHANGE, this.$onSelectionChange);\n      this.$onSelectionChange = null;\n    }\n\n    if ($sourceImage && this.$onSourceImageLoad) {\n      off($sourceImage.$image, EVENT_LOAD, this.$onSourceImageLoad);\n      this.$onSourceImageLoad = null;\n    }\n\n    if ($sourceImage && this.$onSourceImageTransform) {\n      off($sourceImage, EVENT_TRANSFORM, this.$onSourceImageTransform);\n      this.$onSourceImageTransform = null;\n    }\n\n    super.disconnectedCallback();\n  }\n\n  protected $handleSelectionChange(event: Event): void {\n    this.$render(event.defaultPrevented ? this.$selection : (event as CustomEvent).detail);\n  }\n\n  protected $handleSourceImageLoad(): void {\n    const { $image, $sourceImage } = this;\n    const oldSrc = $image.getAttribute('src');\n    const newSrc = $sourceImage.getAttribute('src');\n\n    if (newSrc && newSrc !== oldSrc) {\n      $image.setAttribute('src', newSrc);\n      $image.$ready(() => {\n        this.$render();\n      });\n    }\n  }\n\n  protected $handleSourceImageTransform(event?: Event): void {\n    this.$render(undefined, (event as CustomEvent).detail.matrix);\n  }\n\n  protected $render(selection?: Selection, matrix?: number[]): void {\n    const { $canvas, $selection } = this;\n\n    if (!selection && !$selection.hidden) {\n      selection = $selection;\n    }\n\n    if (!selection || (\n      selection.x === 0\n      && selection.y === 0\n      && selection.width === 0\n      && selection.height === 0\n    )) {\n      selection = {\n        x: 0,\n        y: 0,\n        width: $canvas.offsetWidth,\n        height: $canvas.offsetHeight,\n      };\n    }\n\n    const {\n      x,\n      y,\n      width,\n      height,\n    } = selection;\n    const styles: any = {};\n    const { clientWidth, clientHeight } = this;\n    let newWidth = clientWidth;\n    let newHeight = clientHeight;\n    let scale = NaN;\n\n    switch (this.resize) {\n      case RESIZE_BOTH:\n        scale = 1;\n        newWidth = width;\n        newHeight = height;\n        styles.width = width;\n        styles.height = height;\n        break;\n\n      case RESIZE_HORIZONTAL:\n        scale = height > 0 ? clientHeight / height : 0;\n        newWidth = width * scale;\n        styles.width = newWidth;\n        break;\n\n      case RESIZE_VERTICAL:\n        scale = width > 0 ? clientWidth / width : 0;\n        newHeight = height * scale;\n        styles.height = newHeight;\n        break;\n\n      case RESIZE_NONE:\n      default:\n        if (clientWidth > 0) {\n          scale = width > 0 ? clientWidth / width : 0;\n        } else if (clientHeight > 0) {\n          scale = height > 0 ? clientHeight / height : 0;\n        }\n    }\n\n    this.$scale = scale;\n    this.$setStyles(styles);\n\n    if (this.$sourceImage) {\n      // Transform the image by the selection offset after the next DOM update cycle\n      setTimeout(() => {\n        this.$transformImageByOffset(matrix ?? this.$sourceImage.$getTransform(), -x, -y);\n      });\n    }\n  }\n\n  protected $transformImageByOffset(matrix: number[], x: number, y: number): void {\n    const {\n      $image,\n      $scale,\n      $sourceImage,\n    } = this;\n\n    if ($sourceImage && $image && $scale >= 0) {\n      const [a, b, c, d, e, f] = matrix;\n      const translateX = ((x * d) - (c * y)) / ((a * d) - (c * b));\n      const translateY = ((y * a) - (b * x)) / ((a * d) - (c * b));\n      const newE = a * translateX + c * translateY + e;\n      const newF = b * translateX + d * translateY + f;\n\n      $image.$ready((image) => {\n        this.$setStyles.call($image, {\n          width: image.naturalWidth * $scale,\n          height: image.naturalHeight * $scale,\n        });\n      });\n      $image.$setTransform(a, b, c, d, newE * $scale, newF * $scale);\n    }\n  }\n}\n"
  },
  {
    "path": "packages/element-viewer/src/style.ts",
    "content": "export default `\n:host {\n  display: block;\n  height: 100%;\n  overflow: hidden;\n  position: relative;\n  width: 100%;\n}\n`;\n"
  },
  {
    "path": "packages/element-viewer/tests/index.spec.ts",
    "content": "import CropperViewer from '../src';\n\nCropperViewer.$define();\n\ndescribe('CropperViewer', () => {\n  describe('properties', () => {\n    describe('resize', () => {\n      it('should be `\"vertical\"` by default', () => {\n        const element = new CropperViewer();\n\n        expect(element.resize).toBe('vertical');\n      });\n\n      it('should be `\"both\"`', () => {\n        const element = new CropperViewer();\n\n        element.setAttribute('resize', 'both');\n        expect(element.resize).toBe('both');\n      });\n    });\n\n    describe('selection', () => {\n      it('should be empty by default', () => {\n        const element = new CropperViewer();\n\n        expect(element.selection).toBe('');\n      });\n\n      it('should be \"#selection\"', () => {\n        const element = new CropperViewer();\n\n        element.setAttribute('selection', '#selection');\n        expect(element.selection).toBe('#selection');\n      });\n    });\n\n    describe('slottable', () => {\n      it('should be `false` by default', () => {\n        const element = new CropperViewer();\n\n        expect(element.slottable).toBe(false);\n      });\n\n      it('should be `true`', () => {\n        const element = new CropperViewer();\n\n        element.setAttribute('slottable', '');\n        expect(element.slottable).toBe(true);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/elements/README.md",
    "content": "# @cropper/elements\n\n> A series of custom elements for the Cropper.\n\n## Main npm package files\n\n```text\ndist/\n├── elements.js         (UMD, bundled)\n├── elements.min.js     (UMD, bundled, compressed)\n├── elements.raw.js     (UMD, unbundled, default)\n├── elements.esm.js     (ECMAScript Module, bundled)\n├── elements.esm.min.js (ECMAScript Module, bundled, compressed)\n├── elements.esm.raw.js (ECMAScript Module, unbundled)\n└── elements.d.ts       (TypeScript Declaration File)\n```\n\n## Getting started\n\n### Installation\n\n```sh\nnpm install @cropper/elements\n```\n\n### Usage\n\n```js\nimport { CropperElement, CropperCanvas, CropperImage } from '@cropper/elements';\n\nclass MyCropperElement extends CropperElement {}\n\nMyCropperElement.$define();\nCropperCanvas.$define();\nCropperImage.$define();\n```\n\n## Versioning\n\nMaintained under the [Semantic Versioning guidelines](https://semver.org/).\n\n## License\n\n[MIT](https://opensource.org/licenses/MIT)\n"
  },
  {
    "path": "packages/elements/api-extractor.json",
    "content": "{\n  \"extends\": \"../../api-extractor.json\",\n  \"mainEntryPointFilePath\": \"./.temp/packages/<unscopedPackageName>/src/index.d.ts\",\n  \"dtsRollup\": {\n    \"publicTrimmedFilePath\": \"./dist/<unscopedPackageName>.d.ts\"\n  }\n}\n"
  },
  {
    "path": "packages/elements/package.json",
    "content": "{\n  \"name\": \"@cropper/elements\",\n  \"version\": \"2.1.0\",\n  \"description\": \"A series of custom elements for the Cropper.\",\n  \"main\": \"dist/elements.raw.js\",\n  \"module\": \"dist/elements.esm.raw.js\",\n  \"types\": \"dist/elements.d.ts\",\n  \"unpkg\": \"dist/elements.js\",\n  \"jsdelivr\": \"dist/elements.js\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"import\": {\n        \"types\": \"./dist/elements.d.ts\",\n        \"node\": {\n          \"production\": \"./dist/elements.esm.min.js\",\n          \"development\": \"./dist/elements.esm.js\",\n          \"default\": \"./dist/elements.esm.raw.js\"\n        },\n        \"default\": \"./dist/elements.esm.raw.js\"\n      },\n      \"require\": {\n        \"types\": \"./dist/elements.d.ts\",\n        \"node\": {\n          \"production\": \"./dist/elements.min.js\",\n          \"development\": \"./dist/elements.js\",\n          \"default\": \"./dist/elements.raw.js\"\n        },\n        \"default\": \"./dist/elements.raw.js\"\n      }\n    },\n    \"./dist/*\": \"./dist/*\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"scripts\": {\n    \"api-extractor\": \"api-extractor run --local --verbose\",\n    \"build\": \"npm run tsc && npm run api-extractor && npm run rollup\",\n    \"clean\": \"del-cli dist .temp\",\n    \"release\": \"npm run clean && npm run build\",\n    \"rollup\": \"rollup -c ../../rollup.config.js\",\n    \"tsc\": \"tsc --outDir ./.temp --declaration --emitDeclarationOnly\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/fengyuanchen/cropperjs.git\",\n    \"directory\": \"packages/elements\"\n  },\n  \"keywords\": [\n    \"elements\",\n    \"cropper\",\n    \"cropper.js\"\n  ],\n  \"author\": \"Chen Fengyuan (https://chenfengyuan.com/)\",\n  \"license\": \"MIT\",\n  \"bugs\": \"https://github.com/fengyuanchen/cropperjs/issues\",\n  \"homepage\": \"https://github.com/fengyuanchen/cropperjs/tree/main/packages/elements/#readme\",\n  \"dependencies\": {\n    \"@cropper/element\": \"^2.1.0\",\n    \"@cropper/element-canvas\": \"^2.1.0\",\n    \"@cropper/element-crosshair\": \"^2.1.0\",\n    \"@cropper/element-grid\": \"^2.1.0\",\n    \"@cropper/element-handle\": \"^2.1.0\",\n    \"@cropper/element-image\": \"^2.1.0\",\n    \"@cropper/element-selection\": \"^2.1.0\",\n    \"@cropper/element-shade\": \"^2.1.0\",\n    \"@cropper/element-viewer\": \"^2.1.0\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  }\n}\n"
  },
  {
    "path": "packages/elements/src/index.ts",
    "content": "export { default as CropperElement } from '@cropper/element';\nexport { default as CropperCanvas } from '@cropper/element-canvas';\nexport { default as CropperImage } from '@cropper/element-image';\nexport { default as CropperShade } from '@cropper/element-shade';\nexport { default as CropperHandle } from '@cropper/element-handle';\nexport { default as CropperSelection } from '@cropper/element-selection';\nexport { default as CropperGrid } from '@cropper/element-grid';\nexport { default as CropperCrosshair } from '@cropper/element-crosshair';\nexport { default as CropperViewer } from '@cropper/element-viewer';\n"
  },
  {
    "path": "packages/utils/README.md",
    "content": "# @cropper/utils\n\n> A series of common constants and utility functions for Cropper.\n\n## Main npm package files\n\n```text\ndist/\n├── utils.js         (UMD, bundled, default)\n├── utils.min.js     (UMD, bundled, compressed)\n├── utils.raw.js     (UMD, unbundled, default)\n├── utils.esm.js     (ECMAScript Module, bundled)\n├── utils.esm.min.js (ECMAScript Module, bundled, compressed)\n├── utils.esm.raw.js (ECMAScript Module, unbundled)\n└── utils.d.ts       (TypeScript Declaration File)\n```\n\n## Getting started\n\n### Installation\n\n```sh\nnpm install @cropper/utils\n```\n\n### Usage\n\n```js\nimport { NAMESPACE, isObject } from '@cropper/utils';\n\nconsole.log(NAMESPACE);\n// > \"cropper\"\n\nconsole.log(isObject({}));\n// > true\n\nconsole.log(isObject(null));\n// > false\n```\n\n## Versioning\n\nMaintained under the [Semantic Versioning guidelines](https://semver.org/).\n\n## License\n\n[MIT](https://opensource.org/licenses/MIT)\n"
  },
  {
    "path": "packages/utils/api-extractor.json",
    "content": "{\n  \"extends\": \"../../api-extractor.json\",\n  \"mainEntryPointFilePath\": \"./.temp/packages/<unscopedPackageName>/src/index.d.ts\",\n  \"dtsRollup\": {\n    \"publicTrimmedFilePath\": \"./dist/<unscopedPackageName>.d.ts\"\n  }\n}\n"
  },
  {
    "path": "packages/utils/package.json",
    "content": "{\n  \"name\": \"@cropper/utils\",\n  \"version\": \"2.1.0\",\n  \"description\": \"A series of common constants and utility functions for Cropper.\",\n  \"main\": \"dist/utils.raw.js\",\n  \"module\": \"dist/utils.esm.raw.js\",\n  \"types\": \"dist/utils.d.ts\",\n  \"unpkg\": \"dist/utils.js\",\n  \"jsdelivr\": \"dist/utils.js\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"import\": {\n        \"types\": \"./dist/utils.d.ts\",\n        \"node\": {\n          \"production\": \"./dist/utils.esm.min.js\",\n          \"development\": \"./dist/utils.esm.js\",\n          \"default\": \"./dist/utils.esm.raw.js\"\n        },\n        \"default\": \"./dist/utils.esm.raw.js\"\n      },\n      \"require\": {\n        \"types\": \"./dist/utils.d.ts\",\n        \"node\": {\n          \"production\": \"./dist/utils.min.js\",\n          \"development\": \"./dist/utils.js\",\n          \"default\": \"./dist/utils.raw.js\"\n        },\n        \"default\": \"./dist/utils.raw.js\"\n      }\n    },\n    \"./dist/*\": \"./dist/*\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"scripts\": {\n    \"api-extractor\": \"api-extractor run --local --verbose\",\n    \"build\": \"npm run tsc && npm run api-extractor && npm run rollup\",\n    \"clean\": \"del-cli dist .temp\",\n    \"release\": \"npm run clean && npm run build\",\n    \"rollup\": \"rollup -c ../../rollup.config.js\",\n    \"tsc\": \"tsc --outDir ./.temp --declaration --emitDeclarationOnly\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/fengyuanchen/cropperjs.git\",\n    \"directory\": \"packages/utils\"\n  },\n  \"keywords\": [\n    \"utils\",\n    \"utilities\",\n    \"constants\",\n    \"functions\",\n    \"cropper\",\n    \"cropper.js\"\n  ],\n  \"author\": \"Chen Fengyuan (https://chenfengyuan.com/)\",\n  \"license\": \"MIT\",\n  \"bugs\": \"https://github.com/fengyuanchen/cropperjs/issues\",\n  \"homepage\": \"https://github.com/fengyuanchen/cropperjs/tree/main/packages/utils/#readme\",\n  \"publishConfig\": {\n    \"access\": \"public\"\n  }\n}\n"
  },
  {
    "path": "packages/utils/src/constants.ts",
    "content": "export const IS_BROWSER = typeof window !== 'undefined' && typeof window.document !== 'undefined';\nexport const WINDOW: any = IS_BROWSER ? window : {};\nexport const IS_TOUCH_DEVICE = IS_BROWSER ? 'ontouchstart' in WINDOW.document.documentElement : false;\nexport const HAS_POINTER_EVENT = IS_BROWSER ? 'PointerEvent' in WINDOW : false;\nexport const NAMESPACE = 'cropper';\nexport const CROPPER_CANVAS = `${NAMESPACE}-canvas`;\nexport const CROPPER_CROSSHAIR = `${NAMESPACE}-crosshair`;\nexport const CROPPER_GIRD = `${NAMESPACE}-grid`;\nexport const CROPPER_HANDLE = `${NAMESPACE}-handle`;\nexport const CROPPER_IMAGE = `${NAMESPACE}-image`;\nexport const CROPPER_SELECTION = `${NAMESPACE}-selection`;\nexport const CROPPER_SHADE = `${NAMESPACE}-shade`;\nexport const CROPPER_VIEWER = `${NAMESPACE}-viewer`;\n\n// Actions\nexport const ACTION_SELECT = 'select';\nexport const ACTION_MOVE = 'move';\nexport const ACTION_SCALE = 'scale';\nexport const ACTION_ROTATE = 'rotate';\nexport const ACTION_TRANSFORM = 'transform';\nexport const ACTION_NONE = 'none';\nexport const ACTION_RESIZE_NORTH = 'n-resize';\nexport const ACTION_RESIZE_EAST = 'e-resize';\nexport const ACTION_RESIZE_SOUTH = 's-resize';\nexport const ACTION_RESIZE_WEST = 'w-resize';\nexport const ACTION_RESIZE_NORTHEAST = 'ne-resize';\nexport const ACTION_RESIZE_NORTHWEST = 'nw-resize';\nexport const ACTION_RESIZE_SOUTHEAST = 'se-resize';\nexport const ACTION_RESIZE_SOUTHWEST = 'sw-resize';\n\n// Attributes\nexport const ATTRIBUTE_ACTION = 'action';\n\n// Native events\nexport const EVENT_TOUCH_END = IS_TOUCH_DEVICE ? 'touchend touchcancel' : 'mouseup';\nexport const EVENT_TOUCH_MOVE = IS_TOUCH_DEVICE ? 'touchmove' : 'mousemove';\nexport const EVENT_TOUCH_START = IS_TOUCH_DEVICE ? 'touchstart' : 'mousedown';\nexport const EVENT_POINTER_DOWN = HAS_POINTER_EVENT ? 'pointerdown' : EVENT_TOUCH_START;\nexport const EVENT_POINTER_MOVE = HAS_POINTER_EVENT ? 'pointermove' : EVENT_TOUCH_MOVE;\nexport const EVENT_POINTER_UP = HAS_POINTER_EVENT ? 'pointerup pointercancel' : EVENT_TOUCH_END;\nexport const EVENT_ERROR = 'error';\nexport const EVENT_KEYDOWN = 'keydown';\nexport const EVENT_LOAD = 'load';\nexport const EVENT_RESIZE = 'resize';\nexport const EVENT_WHEEL = 'wheel';\n\n// Custom events\nexport const EVENT_ACTION = 'action';\nexport const EVENT_ACTION_END = 'actionend';\nexport const EVENT_ACTION_MOVE = 'actionmove';\nexport const EVENT_ACTION_START = 'actionstart';\nexport const EVENT_CHANGE = 'change';\nexport const EVENT_TRANSFORM = 'transform';\n"
  },
  {
    "path": "packages/utils/src/functions.ts",
    "content": "import { WINDOW } from './constants';\n\n/**\n * Check if the given value is a string.\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if the given value is a string, else `false`.\n */\nexport function isString(value: unknown): value is string {\n  return typeof value === 'string';\n}\n\n/**\n * Check if the given value is not a number.\n */\nexport const isNaN = Number.isNaN || WINDOW.isNaN;\n\n/**\n * Check if the given value is a number.\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if the given value is a number, else `false`.\n */\nexport function isNumber(value: unknown): value is number {\n  return typeof value === 'number' && !isNaN(value);\n}\n\n/**\n * Check if the given value is a positive number.\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if the given value is a positive number, else `false`.\n */\nexport function isPositiveNumber(value: unknown): value is number {\n  return isNumber(value) && value > 0 && value < Infinity;\n}\n\n/**\n * Check if the given value is undefined.\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if the given value is undefined, else `false`.\n */\nexport function isUndefined(value: unknown): value is undefined {\n  return typeof value === 'undefined';\n}\n\n/**\n * Check if the given value is an object.\n * @param {*} value - The value to check.\n * @returns {boolean} Returns `true` if the given value is an object, else `false`.\n */\nexport function isObject(value: unknown): value is Record<string, unknown> {\n  return typeof value === 'object' && value !== null;\n}\n\nconst { hasOwnProperty } = Object.prototype;\n\n/**\n * Check if the given value is a plain object.\n * @param {*} value - The value to check.\n * @returns {boolean} Returns `true` if the given value is a plain object, else `false`.\n */\nexport function isPlainObject(value: unknown): value is Record<string, unknown> {\n  if (!isObject(value)) {\n    return false;\n  }\n\n  try {\n    const { constructor } = value;\n    const { prototype } = constructor;\n\n    return constructor && prototype && hasOwnProperty.call(prototype, 'isPrototypeOf');\n  } catch (error) {\n    return false;\n  }\n}\n\n/**\n * Check if the given value is a function.\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if the given value is a function, else `false`.\n */\nexport function isFunction(value: unknown): value is (...args: unknown[]) => unknown {\n  return typeof value === 'function';\n}\n\n/**\n * Check if the given node is an element.\n * @param {*} node The node to check.\n * @returns {boolean} Returns `true` if the given node is an element; otherwise, `false`.\n */\nexport function isElement(node: unknown): node is Element {\n  return typeof node === 'object' && node !== null && (node as Node).nodeType === 1;\n}\n\nconst REGEXP_CAMEL_CASE = /([a-z\\d])([A-Z])/g;\n\n/**\n * Transform the given string from camelCase to kebab-case.\n * @param {string} value The value to transform.\n * @returns {string} Returns the transformed value.\n */\nexport function toKebabCase(value: string): string {\n  return String(value).replace(REGEXP_CAMEL_CASE, '$1-$2').toLowerCase();\n}\n\nconst REGEXP_KEBAB_CASE = /-[A-z\\d]/g;\n\n/**\n * Transform the given string from kebab-case to camelCase.\n * @param {string} value The value to transform.\n * @returns {string} Returns the transformed value.\n */\nexport function toCamelCase(value: string): string {\n  return value.replace(REGEXP_KEBAB_CASE, (substring: string) => substring.slice(1).toUpperCase());\n}\n\nconst REGEXP_SPACES = /\\s\\s*/;\n\n/**\n * Remove event listener from the event target.\n * {@link https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener}\n * @param {EventTarget} target The target of the event.\n * @param {string} types The types of the event.\n * @param {EventListenerOrEventListenerObject} listener The listener of the event.\n * @param {EventListenerOptions} [options] The options specify characteristics about the event listener.\n */\nexport function off(\n  target: EventTarget,\n  types: string,\n  listener: EventListenerOrEventListenerObject,\n  options?: EventListenerOptions,\n): void {\n  types.trim().split(REGEXP_SPACES).forEach((type) => {\n    target.removeEventListener(type, listener, options);\n  });\n}\n\n/**\n * Add event listener to the event target.\n * {@link https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener}\n * @param {EventTarget} target The target of the event.\n * @param {string} types The types of the event.\n * @param {EventListenerOrEventListenerObject} listener The listener of the event.\n * @param {AddEventListenerOptions} [options] The options specify characteristics about the event listener.\n */\nexport function on(\n  target: EventTarget,\n  types: string,\n  listener: EventListenerOrEventListenerObject,\n  options?: AddEventListenerOptions,\n): void {\n  types.trim().split(REGEXP_SPACES).forEach((type) => {\n    target.addEventListener(type, listener, options);\n  });\n}\n\n/**\n * Add once event listener to the event target.\n * @param {EventTarget} target The target of the event.\n * @param {string} types The types of the event.\n * @param {EventListenerOrEventListenerObject} listener The listener of the event.\n * @param {AddEventListenerOptions} [options] The options specify characteristics about the event listener.\n */\nexport function once(\n  target: EventTarget,\n  types: string,\n  listener: EventListenerOrEventListenerObject,\n  options?: AddEventListenerOptions,\n): void {\n  on(target, types, listener, {\n    ...options,\n    once: true,\n  });\n}\n\nconst defaultEventOptions: CustomEventInit = {\n  bubbles: true,\n  cancelable: true,\n  composed: true,\n};\n\n/**\n * Dispatch event on the event target.\n * {@link https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/dispatchEvent}\n * @param {EventTarget} target The target of the event.\n * @param {string} type The name of the event.\n * @param {*} [detail] The data passed when initializing the event.\n * @param {CustomEventInit} [options] The other event options.\n * @returns {boolean} Returns the result value.\n */\nexport function emit(\n  target: EventTarget,\n  type: string,\n  detail?: unknown,\n  options?: CustomEventInit,\n): boolean {\n  return target.dispatchEvent(new CustomEvent(type, {\n    ...defaultEventOptions,\n    detail,\n    ...options,\n  }));\n}\n\n/**\n * Get the real event target by checking composed path.\n * This is useful when dealing with events that can cross shadow DOM boundaries.\n * {@link https://developer.mozilla.org/en-US/docs/Web/API/Event/composedPath}\n * @param {Event} event The event object.\n * @returns {EventTarget | null} The first element in the composed path, or the original event target.\n */\nexport function getComposedPathTarget(event: Event): EventTarget | null {\n  if (typeof (event as any).composedPath === 'function') {\n    const path = (event as any).composedPath();\n    return path.find(isElement) || event.target;\n  }\n\n  return event.target;\n}\n\nconst resolvedPromise: Promise<any> = Promise.resolve();\n\n/**\n * Defers the callback to be executed after the next DOM update cycle.\n * @param {*} [context] The `this` context.\n * @param {Function} [callback] The callback to execute after the next DOM update cycle.\n * @returns {Promise} A promise that resolves to nothing.\n */\nexport function nextTick(context?: unknown, callback?: () => void): Promise<void> {\n  return callback\n    ? resolvedPromise.then(context ? callback.bind(context) : callback)\n    : resolvedPromise;\n}\n\n/**\n * Get the root document node.\n * @param {Element} element The target element.\n * @returns {Document|DocumentFragment|null} The document node.\n */\nexport function getRootDocument(element: Element): Document | DocumentFragment | null {\n  const rootNode = element.getRootNode();\n\n  switch (rootNode.nodeType) {\n    case 1:\n      return rootNode.ownerDocument;\n\n    case 9:\n      return rootNode as Document;\n\n    case 11:\n      return rootNode as DocumentFragment;\n\n    default:\n  }\n\n  return null;\n}\n\n/**\n * Get the offset base on the document.\n * @param {Element} element The target element.\n * @returns {object} The offset data.\n */\nexport function getOffset(element: Element): {\n  left: number;\n  top: number;\n} {\n  const { documentElement } = element.ownerDocument;\n  const box = element.getBoundingClientRect();\n\n  return {\n    left: box.left + (WINDOW.pageXOffset - documentElement.clientLeft),\n    top: box.top + (WINDOW.pageYOffset - documentElement.clientTop),\n  };\n}\n\nconst REGEXP_ANGLE_UNIT = /deg|g?rad|turn$/i;\n\n/**\n * Convert an angle to a radian number.\n * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/angle}\n * @param {number|string} angle The angle to convert.\n * @returns {number} Returns the radian number.\n */\nexport function toAngleInRadian(angle: number | string): number {\n  const value = parseFloat(angle as string) || 0;\n\n  if (value !== 0) {\n    const [unit = 'rad'] = String(angle).match(REGEXP_ANGLE_UNIT) || [];\n\n    switch (unit.toLowerCase()) {\n      case 'deg':\n        return (value / 360) * (Math.PI * 2);\n\n      case 'grad':\n        return (value / 400) * (Math.PI * 2);\n\n      case 'turn':\n        return value * (Math.PI * 2);\n\n      // case 'rad':\n      default:\n    }\n  }\n\n  return value;\n}\n\ninterface SizeAdjustmentData {\n  aspectRatio: number;\n  height: number;\n  width: number;\n}\n\ninterface SizeAdjustmentDataWithoutWidth {\n  aspectRatio: number;\n  height: number;\n}\n\ninterface SizeAdjustmentDataWithoutHeight {\n  aspectRatio: number;\n  width: number;\n}\n\ntype SizeAdjustmentType = 'contain' | 'cover';\nconst SIZE_ADJUSTMENT_TYPE_CONTAIN: SizeAdjustmentType = 'contain';\nconst SIZE_ADJUSTMENT_TYPE_COVER: SizeAdjustmentType = 'cover';\n\n/**\n * Get the max sizes in a rectangle under the given aspect ratio.\n * @param {object} data The original sizes.\n * @param {string} [type] The adjust type.\n * @returns {object} Returns the result sizes.\n */\nexport function getAdjustedSizes(\n  data: SizeAdjustmentData | SizeAdjustmentDataWithoutWidth | SizeAdjustmentDataWithoutHeight,\n  type: SizeAdjustmentType = SIZE_ADJUSTMENT_TYPE_CONTAIN,\n): {\n    width: number;\n    height: number;\n  } {\n  const { aspectRatio } = data;\n  let { width, height } = data as SizeAdjustmentData;\n  const isValidWidth = isPositiveNumber(width);\n  const isValidHeight = isPositiveNumber(height);\n\n  if (isValidWidth && isValidHeight) {\n    const adjustedWidth = height * aspectRatio;\n\n    if ((type === SIZE_ADJUSTMENT_TYPE_CONTAIN && adjustedWidth > width)\n      || (type === SIZE_ADJUSTMENT_TYPE_COVER && adjustedWidth < width)) {\n      height = width / aspectRatio;\n    } else {\n      width = height * aspectRatio;\n    }\n  } else if (isValidWidth) {\n    height = width / aspectRatio;\n  } else if (isValidHeight) {\n    width = height * aspectRatio;\n  }\n\n  return {\n    width,\n    height,\n  };\n}\n\n/**\n * Multiply multiple matrices.\n * @param {Array} matrix The first matrix.\n * @param {Array} args The rest matrices.\n * @returns {Array} Returns the result matrix.\n */\nexport function multiplyMatrices(matrix: number[], ...args: number[][]): number[] {\n  if (args.length === 0) {\n    return matrix;\n  }\n\n  const [a1, b1, c1, d1, e1, f1] = matrix;\n  const [a2, b2, c2, d2, e2, f2] = args[0];\n\n  // ┌ a1 c1 e1 ┐   ┌ a2 c2 e2 ┐\n  // │ b1 d1 f1 │ × │ b2 d2 f2 │\n  // └ 0  0  1  ┘   └ 0  0  1  ┘\n  matrix = [\n    a1 * a2 + c1 * b2/* + e1 * 0 */,\n    b1 * a2 + d1 * b2/* + f1 * 0 */,\n    a1 * c2 + c1 * d2/* + e1 * 0 */,\n    b1 * c2 + d1 * d2/* + f1 * 0 */,\n    a1 * e2 + c1 * f2 + e1/* * 1 */,\n    b1 * e2 + d1 * f2 + f1/* * 1 */,\n  ];\n\n  return multiplyMatrices(matrix, ...args.slice(1));\n}\n"
  },
  {
    "path": "packages/utils/src/index.ts",
    "content": "export * from './constants';\nexport * from './functions';\n"
  },
  {
    "path": "packages/utils/tests/index.spec.ts",
    "content": "import {\n  emit,\n  getAdjustedSizes,\n  getOffset,\n  isElement,\n  getComposedPathTarget,\n  isFunction,\n  isNaN,\n  isNumber,\n  isObject,\n  isPlainObject,\n  isPositiveNumber,\n  isString,\n  isUndefined,\n  multiplyMatrices,\n  off,\n  on,\n  once,\n  toAngleInRadian,\n  toCamelCase,\n  toKebabCase,\n} from '../src';\n\ndescribe('Utilities', () => {\n  describe('isString', () => {\n    it('should be `true` for `\"\"`', () => {\n      expect(isString('')).toBe(true);\n    });\n\n    it('should be `false` for `0`', () => {\n      expect(isString(0)).toBe(false);\n    });\n  });\n\n  describe('isNaN', () => {\n    it('should be `true` for `NaN`', () => {\n      expect(isNaN(NaN)).toBe(true);\n    });\n\n    it('should be `false` for `0`', () => {\n      expect(isNaN(0)).toBe(false);\n    });\n\n    it('should be `false` for `\"0\"`', () => {\n      expect(isNaN('0')).toBe(false);\n    });\n  });\n\n  describe('isNumber', () => {\n    it('should be `true` for `0`', () => {\n      expect(isNumber(0)).toBe(true);\n    });\n\n    it('should be `true` for `Infinity`', () => {\n      expect(isNumber(Infinity)).toBe(true);\n    });\n\n    it('should be `true` for `-Infinity`', () => {\n      expect(isNumber(-Infinity)).toBe(true);\n    });\n\n    it('should be `true` for `NaN`', () => {\n      expect(isNumber(NaN)).toBe(false);\n    });\n\n    it('should be `false` for `\"0\"`', () => {\n      expect(isNumber('0')).toBe(false);\n    });\n  });\n\n  describe('isPositiveNumber', () => {\n    it('should be `true` for `1`', () => {\n      expect(isPositiveNumber(1)).toBe(true);\n    });\n\n    it('should be `false` for `0`', () => {\n      expect(isPositiveNumber(0)).toBe(false);\n    });\n\n    it('should be `false` for `-1`', () => {\n      expect(isPositiveNumber(-1)).toBe(false);\n    });\n\n    it('should be `false` for `\"0\"`', () => {\n      expect(isPositiveNumber('0')).toBe(false);\n    });\n  });\n\n  describe('isUndefined', () => {\n    it('should be `true` for `undefined`', () => {\n      expect(isUndefined(undefined)).toBe(true);\n    });\n\n    it('should be `false` for `null`', () => {\n      expect(isUndefined(null)).toBe(false);\n    });\n  });\n\n  describe('isObject', () => {\n    it('should be `true` for `{}`', () => {\n      expect(isObject({})).toBe(true);\n    });\n\n    it('should be `false` for `null`', () => {\n      expect(isObject(null)).toBe(false);\n    });\n\n    it('should be `false` for `() => {}`', () => {\n      expect(isObject(() => {})).toBe(false);\n    });\n  });\n\n  describe('isPlainObject', () => {\n    it('should be `true` for `{}`', () => {\n      expect(isPlainObject({})).toBe(true);\n    });\n\n    it('should be `false` for `[]`', () => {\n      expect(isPlainObject([])).toBe(false);\n    });\n  });\n\n  describe('isFunction', () => {\n    it('should be `true` for `() => {}`', () => {\n      expect(isFunction(() => {})).toBe(true);\n    });\n\n    it('should be `false` for `[]`', () => {\n      expect(isFunction([])).toBe(false);\n    });\n  });\n\n  describe('isElement', () => {\n    it('should be `true` for `document.body`', () => {\n      expect(isElement(document.body)).toBe(true);\n    });\n\n    it('should be `false` for `document`', () => {\n      expect(isElement(document)).toBe(false);\n    });\n  });\n\n  describe('toKebabCase', () => {\n    it('should be `\"foobar\"` for `\"foobar\"`', () => {\n      expect(toKebabCase('foobar')).toBe('foobar');\n    });\n\n    it('should be `\"foo-bar\"` for `\"fooBar\"`', () => {\n      expect(toKebabCase('fooBar')).toBe('foo-bar');\n    });\n  });\n\n  describe('toCamelCase', () => {\n    it('should be `\"foobar\"` for `\"foobar\"`', () => {\n      expect(toKebabCase('foobar')).toBe('foobar');\n    });\n\n    it('should be `\"fooBar\"` for `\"foo-bar\"`', () => {\n      expect(toCamelCase('foo-bar')).toBe('fooBar');\n    });\n  });\n\n  describe('on', () => {\n    it('should add event listener to the element', (done) => {\n      const element = document.createElement('div');\n\n      on(element, 'click', (event) => {\n        expect(event.type).toBe('click');\n        done();\n      });\n      emit(element, 'click');\n    });\n\n    it('should support multiple event types', (done) => {\n      const element = document.createElement('div');\n      let count = 0;\n\n      on(element, 'focus blur', (event) => {\n        count += 1;\n\n        switch (count) {\n          case 1:\n            expect(event.type).toBe('focus');\n            break;\n\n          case 2:\n            expect(event.type).toBe('blur');\n            done();\n            break;\n\n          default:\n        }\n      });\n      emit(element, 'focus');\n      emit(element, 'blur');\n    });\n\n    it('should support options', (done) => {\n      const element = document.createElement('div');\n      let count = 0;\n\n      on(element, 'click', () => {\n        count += 1;\n\n        switch (count) {\n          case 1:\n            setTimeout(() => {\n              done();\n            }, 500);\n            break;\n\n          case 2:\n            throw new Error();\n\n          default:\n        }\n      }, {\n        once: true,\n      });\n      emit(element, 'click');\n      emit(element, 'click');\n    });\n  });\n\n  describe('once', () => {\n    it('should run the event listener once only', (done) => {\n      const element = document.createElement('div');\n      let count = 0;\n\n      once(element, 'click', () => {\n        count += 1;\n\n        switch (count) {\n          case 1:\n            done();\n            break;\n\n          case 2:\n            throw new Error();\n\n          default:\n        }\n      });\n      emit(element, 'click');\n      emit(element, 'click');\n    });\n\n    it('should support multiple event types', (done) => {\n      const element = document.createElement('div');\n      let count = 0;\n\n      once(element, 'focus blur', (event) => {\n        count += 1;\n\n        switch (count) {\n          case 1:\n            expect(event.type).toBe('focus');\n            break;\n\n          case 2:\n            expect(event.type).toBe('blur');\n            done();\n            break;\n\n          default:\n        }\n      });\n      emit(element, 'focus');\n      emit(element, 'blur');\n    });\n  });\n\n  describe('off', () => {\n    it('should remove event listener from the element', (done) => {\n      const element = document.createElement('div');\n      const listener = () => {\n        throw new Error();\n      };\n\n      on(element, 'click', listener);\n      off(element, 'click', listener);\n      emit(element, 'click');\n      setTimeout(() => {\n        done();\n      }, 500);\n    });\n\n    it('should support multiple event types', (done) => {\n      const element = document.createElement('div');\n      const listener = () => {\n        throw new Error();\n      };\n\n      on(element, 'focus blur', listener);\n      off(element, 'focus blur', listener);\n      emit(element, 'focus');\n      emit(element, 'blur');\n      setTimeout(() => {\n        done();\n      }, 500);\n    });\n  });\n\n  describe('emit', () => {\n    it('should dispatch native event on the element', (done) => {\n      const element = document.createElement('div');\n\n      on(element, 'click', (event) => {\n        expect(event.type).toBe('click');\n        done();\n      });\n      emit(element, 'click');\n    });\n\n    it('should dispatch custom event on the element', (done) => {\n      const element = document.createElement('div');\n\n      on(element, 'test', (event) => {\n        expect(event.type).toBe('test');\n        done();\n      });\n      emit(element, 'test');\n    });\n  });\n\n  describe('getComposedPathTarget', () => {\n    it('should return the first element in composedPath if composedPath exists', () => {\n      const div = document.createElement('div');\n      const span = document.createElement('span');\n      const event = {\n        composedPath: () => [null, span, div],\n        target: div,\n      } as unknown as Event;\n\n      expect(getComposedPathTarget(event)).toBe(span);\n    });\n\n    it('should return event.target if composedPath does not exist', () => {\n      const div = document.createElement('div');\n      const event = { target: div } as unknown as Event;\n\n      expect(getComposedPathTarget(event)).toBe(div);\n    });\n\n    it('should return event.target if composedPath returns no element', () => {\n      const div = document.createElement('div');\n      const event = {\n        composedPath: () => [null, undefined, 'string'],\n        target: div,\n      } as unknown as Event;\n\n      expect(getComposedPathTarget(event)).toBe(div);\n    });\n\n    it('should work with a real DOM event', () => {\n      const button = document.createElement('button');\n      document.body.appendChild(button);\n\n      const clickEvent = new MouseEvent('click', { bubbles: true });\n      let result: EventTarget | null = null;\n\n      button.addEventListener('click', (e) => {\n        result = getComposedPathTarget(e);\n      });\n\n      button.dispatchEvent(clickEvent);\n\n      expect(result).toBe(button);\n      document.body.removeChild(button);\n    });\n\n    it('should return the element inside shadow DOM using composedPath', () => {\n      const host = document.createElement('div');\n      const shadow = host.attachShadow({ mode: 'open' });\n      const innerDiv = document.createElement('div');\n      shadow.appendChild(innerDiv);\n      document.body.appendChild(host);\n\n      let result: EventTarget | null = null;\n\n      innerDiv.addEventListener('click', (e) => {\n        result = getComposedPathTarget(e);\n      });\n\n      const clickEvent = new MouseEvent('click', {\n        bubbles: true,\n        composed: true,\n      });\n      innerDiv.dispatchEvent(clickEvent);\n\n      expect(result).toBe(innerDiv);\n\n      document.body.removeChild(host);\n    });\n\n    it('should fallback to event.target if shadow DOM event but composedPath returns empty', () => {\n      const host = document.createElement('div');\n      const shadow = host.attachShadow({ mode: 'open' });\n      const innerDiv = document.createElement('div');\n      shadow.appendChild(innerDiv);\n      document.body.appendChild(host);\n\n      const event = {\n        target: innerDiv,\n        composedPath: () => [],\n      } as unknown as Event;\n\n      expect(getComposedPathTarget(event)).toBe(innerDiv);\n\n      document.body.removeChild(host);\n    });\n\n    it('should skip non-element nodes in composedPath', () => {\n      const div = document.createElement('div');\n      const event = {\n        composedPath: () => [null, 'string', div],\n        target: div,\n      } as unknown as Event;\n\n      expect(getComposedPathTarget(event)).toBe(div);\n    });\n  });\n\n  describe('getOffset', () => {\n    it('should get the offset base on the document', () => {\n      const element = document.createElement('div');\n\n      document.body.appendChild(element);\n\n      const offset = getOffset(element);\n\n      expect(offset.left).toBe(0);\n      expect(offset.top).toBe(0);\n    });\n  });\n\n  describe('toAngleInRadian', () => {\n    it('should convert an angle to a radian number.', () => {\n      expect(toAngleInRadian(Math.PI)).toBe(Math.PI);\n      expect(toAngleInRadian('3.14rad')).toBe(3.14);\n      expect(toAngleInRadian('180deg')).toBe(Math.PI);\n      expect(toAngleInRadian('200grad')).toBe(Math.PI);\n      expect(toAngleInRadian('0.5turn')).toBe(Math.PI);\n    });\n  });\n\n  describe('getAdjustedSizes', () => {\n    it('should contain the given sizes', () => {\n      const sizes = getAdjustedSizes({\n        width: 200,\n        height: 100,\n        aspectRatio: 1,\n      });\n\n      expect(sizes.width).toBe(100);\n      expect(sizes.height).toBe(100);\n    });\n\n    it('should cover the given sizes', () => {\n      const sizes = getAdjustedSizes({\n        width: 200,\n        height: 100,\n        aspectRatio: 1,\n      }, 'cover');\n\n      expect(sizes.width).toBe(200);\n      expect(sizes.height).toBe(200);\n    });\n\n    it('should compute the width bases on the given height', () => {\n      const sizes = getAdjustedSizes({\n        height: 100,\n        aspectRatio: 1,\n      });\n\n      expect(sizes.width).toBe(100);\n    });\n\n    it('should compute the height bases on the given width', () => {\n      const sizes = getAdjustedSizes({\n        width: 200,\n        aspectRatio: 1,\n      });\n\n      expect(sizes.height).toBe(200);\n    });\n  });\n\n  describe('multiplyMatrices', () => {\n    it('should return the first parameter directly.', () => {\n      const matrix = [1, 0, 0, 1, 0, 0];\n\n      expect(multiplyMatrices(matrix)).toBe(matrix);\n    });\n\n    it('should multiply matrices correctly.', () => {\n      expect(multiplyMatrices([1, 0, 0, 1, 0, 0], [1, 0, 0, 1, 1, 1])).toEqual([1, 0, 0, 1, 1, 1]);\n    });\n  });\n});\n"
  },
  {
    "path": "postcss.config.js",
    "content": "module.exports = {\n  plugins: {\n    autoprefixer: {},\n    stylelint: {\n      fix: true,\n    },\n    cssnano: {\n      preset: 'default',\n    },\n  },\n};\n"
  },
  {
    "path": "rollup.config.js",
    "content": "import fs from 'fs';\nimport changeCase from 'change-case';\nimport createBanner from 'create-banner';\nimport commonjs from '@rollup/plugin-commonjs';\nimport nodeResolve from '@rollup/plugin-node-resolve';\nimport postcss from 'rollup-plugin-inline-postcss';\nimport replace from '@rollup/plugin-replace';\nimport terser from '@rollup/plugin-terser';\nimport typescript from '@rollup/plugin-typescript';\nimport config from './tsconfig.json';\n\nconst pkg = JSON.parse(fs.readFileSync(`${process.cwd()}/package.json`));\nconst isCropperJS = pkg.name === 'cropperjs';\nconst normalizeGlobalName = (name) => changeCase.pascalCase(name.replace(/[a-z]+-/, ''));\nconst name = isCropperJS ? 'Cropper' : normalizeGlobalName(pkg.name);\nconst banner = createBanner({\n  data: {\n    name: isCropperJS ? 'Cropper.js' : name,\n    year: '2015-present',\n  },\n  template: 'inline',\n});\nconst bundles = ['unbundled', 'bundled'];\nconst formats = ['esm', 'umd'];\nconst modes = ['development', 'production'];\nconst external = Object.keys(pkg.dependencies || {});\nconst globals = external.reduce((map, key) => {\n  map[key] = normalizeGlobalName(key);\n  return map;\n}, {});\n\nexport default bundles.reduce((configs, bundle) => {\n  const isBundled = bundle === 'bundled';\n\n  return configs.concat(formats.map((format) => {\n    const isESM = format === 'esm';\n\n    return {\n      input: 'src/index.ts',\n      output: modes.reduce((outputs, mode) => {\n        const isProduction = mode === 'production';\n\n        return outputs.concat(!isBundled && isProduction ? [] : {\n          format,\n          name: isESM ? undefined : name,\n          banner: isCropperJS ? banner : undefined,\n          globals: isBundled ? undefined : globals,\n          file: (isESM ? pkg.module : pkg.main)\n            .replace('.raw', isBundled ? '' : '.raw')\n            .replace('.js', `${isProduction ? '.min' : ''}.js`),\n          plugins: isProduction ? [terser()] : undefined,\n        });\n      }, []),\n      external: isBundled ? undefined : external,\n      plugins: [\n        nodeResolve(),\n        commonjs(),\n        postcss({\n          include: [/\\/style\\.ts$/],\n          styleRegex: /(?:export default `)([^`]+)(?:`;)/g,\n        }),\n        typescript(config.compilerOptions),\n        replace({\n          preventAssignment: true,\n          __VERSION__: pkg.version,\n        }),\n      ],\n    };\n  }));\n}, []);\n"
  },
  {
    "path": "stylelint.config.js",
    "content": "module.exports = {\n  extends: [\n    'stylelint-config-recommended-scss',\n    'stylelint-config-recommended-vue/scss',\n  ],\n  plugins: [\n    'stylelint-order',\n  ],\n  rules: {\n    'selector-pseudo-class-no-unknown': null,\n    'no-descending-specificity': null,\n    'order/properties-alphabetical-order': true,\n  },\n};\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"rootDir\": \".\",\n    \"target\": \"es2016\",\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"strict\": true,\n    \"skipLibCheck\": true,\n    \"esModuleInterop\": true,\n    \"resolveJsonModule\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"lib\": [\n      \"dom\",\n      \"esnext\"\n    ],\n    \"paths\": {\n      \"@cropper/*\": [\n        \"packages/*/src\"\n      ],\n      \"cropperjs\": [\n        \"packages/cropperjs/src\"\n      ]\n    },\n    \"types\": [\n      \"jest\",\n      \"vite/client\"\n    ]\n  },\n  \"include\": [\n    \"*.js\",\n    \".*.js\",\n    \"docs/.vitepress/**/*\",\n    \"packages/*/src/*\",\n    \"packages/*/tests/*\",\n    \"types/*\"\n  ]\n}\n"
  },
  {
    "path": "types/index.d.ts",
    "content": "declare module '*.css' {\n  const content: string;\n\n  export default content;\n}\n\ndeclare module '*.vue' {\n  const content: any;\n\n  export default content;\n}\n"
  }
]