[
  {
    "path": ".changeset/README.md",
    "content": "# Changesets\n\nHello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works\nwith multi-package repos, or single-package repos to help you version and publish your code. You can\nfind the full documentation for it [in our repository](https://github.com/changesets/changesets)\n\nWe have a quick list of common questions to get you started engaging with this project in\n[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)\n"
  },
  {
    "path": ".changeset/config.json",
    "content": "{\n  \"$schema\": \"https://unpkg.com/@changesets/config@2.3.1/schema.json\",\n  \"changelog\": [\"@changesets/changelog-github\", {\"repo\": \"Shopify/draggable\"}],\n  \"commit\": false,\n  \"fixed\": [],\n  \"linked\": [],\n  \"access\": \"public\",\n  \"baseBranch\": \"main\",\n  \"updateInternalDependencies\": \"patch\",\n  \"ignore\": []\n}\n"
  },
  {
    "path": ".editorconfig",
    "content": "# editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_size = 2\nindent_style = space\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n# Markdown syntax specifies that trailing whitespaces can be meaningful,\n# so let’s not trim those. e.g. 2 trailing spaces = linebreak (<br />)\n# See https://daringfireball.net/projects/markdown/syntax#p\n[*.md]\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".eslintignore",
    "content": "build/\n"
  },
  {
    "path": ".eslintrc.js",
    "content": "const path = require('path');\n\nmodule.exports = {\n  extends: [\n    'plugin:@shopify/typescript',\n    'plugin:@shopify/jest',\n    'plugin:@shopify/prettier',\n  ],\n  parser: '@typescript-eslint/parser',\n  env: {\n    browser: true,\n    node: true,\n  },\n  rules: {\n    'jest/valid-title': 'off',\n  },\n  settings: {\n    'import/resolver': {\n      node: {\n        paths: [\n          path.resolve(__dirname, 'src'),\n          path.resolve(__dirname, 'test'),\n        ],\n      },\n    },\n  },\n};\n"
  },
  {
    "path": ".github/.probots.yml",
    "content": "enabled:\n  - cla\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "> Please use this template to help contributors get a detailed description of the issue or feature.\n\n**For support questions, please use stackoverflow.** This repository's issues are reserved for feature requests and bug reports.\n\n### 1. Apply either the `bug` or `feature-request` label\n\n_Select appropriate label in right sidebar..._\n\n### 2. Describe the `bug` or `feature`\n\n_Feature or expected behaviour explanation..._\n\n### 3. If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem\n\n_Please remember that with sample code it's easier to reproduce the bug and it's much faster to fix it._\n\n### 4. Please tell us about your environment:\n\n- **Library version:** [1.X.X | v1 stable | etc...]\n- **Browsers:** [all | Chrome vX | Firefox vX | Safari vX | Edge vX | iOS vX | Android vX | etc...]\n- **Tech stack:** [all | TypeScript vX | ES6/7 | ES5 | React | etc...]\n- **Other information:** [Detailed explanation | Stacktraces | Related issues | Suggestions how to fix | Links for us to have context | etc...]\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!-- Thank you for submitting a pull request! Please make sure you have read the [contribution guidelines](https://github.com/Shopify/draggable/blob/main/CONTRIBUTING.md) before proceeding. -->\n\n### This PR implements or fixes...\n\n<!-- Explain your changes -->\n\n### This PR closes the following issues...\n\n<!-- If applicable -->\n\n### Does this PR require the Docs to be updated?\n\n…\n\n### Does this PR require new tests?\n\n…\n\n### This branch been tested on... _(click all that apply / add new items)_\n\n**Browsers:**\n\n- [ ] Chrome _version_\n- [ ] Firefox _version_\n- [ ] Safari _version_\n- [ ] IE / Edge _version_\n- [ ] iOS Browser _version_\n- [ ] Android Browser _version_\n"
  },
  {
    "path": ".github/dependabot.yaml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: github-actions\n    directory: \"/\"\n    schedule:\n      interval: weekly\n"
  },
  {
    "path": ".github/workflows/changelog.yml",
    "content": "name: Changelog\n\non:\n  pull_request:\n    types:\n      - labeled\n      - unlabeled\n      - opened\n      - synchronize\n      - reopened\n\njobs:\n  check:\n    if: |\n      !contains(github.event.pull_request.head.ref, 'changeset-release') &&\n      !contains(github.event.pull_request.labels.*.name, '🤖Skip Changelog')\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout branch\n        uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0\n        with:\n          fetch-depth: 0\n\n      - name: Setup Node.js\n        uses: actions/setup-node@3235b876344d2a9aa001b8d1453c930bba69e610 # v3.9.1\n        with:\n          node-version: 20.17.0\n\n      - name: Check for Changeset\n        run: npx @changesets/cli status --since=\"origin/main\"\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout branch\n        uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0\n        with:\n          fetch-depth: 2\n\n      - uses: actions/setup-node@3235b876344d2a9aa001b8d1453c930bba69e610 # v3.9.1\n        with:\n          node-version: '20.17.0'\n\n      - name: Install dependencies\n        run: yarn --frozen-lockfile\n\n      - name: Lint\n        run: yarn lint\n\n      - name: Library typecheck\n        run: yarn type-check\n\n      - name: Build\n        run: yarn build\n\n      - name: Build Development\n        run: yarn build:development\n\n      - name: Test\n        run: yarn test\n\n      - name: Install dependencies\n        working-directory: examples/\n        run: yarn --frozen-lockfile\n\n      - name: Build examples\n        working-directory: examples/\n        run: yarn build\n"
  },
  {
    "path": ".github/workflows/cla.yml",
    "content": "# .github/workflows/cla.yml\nname: Contributor License Agreement (CLA)\n\non:\n  pull_request_target:\n    types: [opened, synchronize]\n  issue_comment:\n    types: [created]\n\njobs:\n  cla:\n    runs-on: ubuntu-latest\n    if: |\n      (github.event.issue.pull_request\n        && !github.event.issue.pull_request.merged_at\n        && contains(github.event.comment.body, 'signed')\n      )\n      || (github.event.pull_request && !github.event.pull_request.merged)\n    steps:\n      - uses: Shopify/shopify-cla-action@v1\n        with:\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n          cla-token: ${{ secrets.CLA_TOKEN }}"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\n\non:\n  push:\n    branches:\n      - main\n\nconcurrency: ${{ github.workflow }}-${{ github.ref }}\n\njobs:\n  release:\n    name: Release\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n      pull-requests: write\n      id-token: write\n    steps:\n      - name: Checkout Repo\n        uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Setup Node.js\n        uses: actions/setup-node@3235b876344d2a9aa001b8d1453c930bba69e610 # v3.9.1\n        with:\n          node-version: '20.17.0'\n          registry-url: 'https://registry.npmjs.org'\n          cache: 'yarn'\n\n      - name: Update NPM\n        run: npm install -g npm@11.5.1\n\n      - name: Install dependencies\n        run: yarn --frozen-lockfile\n\n      - name: Create release Pull Request or publish to NPM\n        id: changesets\n        uses: changesets/action@06245a4e0a36c064a573d4150030f5ec548e4fcc # v1.5.3\n        with:\n          publish: yarn release\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/snapit.yml",
    "content": "name: Snapshot\n\non:\n  issue_comment:\n    types:\n      - created\n\nconcurrency: ${{ github.workflow }}-${{ github.ref }}\n\njobs:\n  snapshot:\n    name: Snapshot Release\n    if: |\n      github.event.issue.pull_request &&\n      (startsWith(github.event.comment.body, '/snapit') || startsWith(github.event.comment.body, '/snapshot-release'))\n    runs-on: ubuntu-latest\n    steps:\n      - name: Enforce permission requirement\n        uses: prince-chrismc/check-actor-permissions-action@72c9eb81384517cbc49d765edc200af3131897ce # v1.0.0\n        with:\n          permission: write\n\n      - name: Add initial reaction\n        uses: peter-evans/create-or-update-comment@67dcc547d311b736a8e6c5c236542148a47adc3d # v2.1.1\n        with:\n          comment-id: ${{ github.event.comment.id }}\n          reactions: eyes\n\n      - name: Get PR branch\n        uses: xt0rted/pull-request-comment-branch@653a7d5ca8bd91d3c5cb83286063314d0b063b8e # v1.4.0\n        id: comment-branch\n\n      - name: Set latest commit status as pending\n        uses: myrotvorets/set-commit-status-action@7b093ccbb10e14939b7a4ae2630fe4cbc67c0651 # v2.0.1\n        with:\n          sha: ${{ steps.comment-branch.outputs.head_sha }}\n          token: ${{ secrets.GITHUB_TOKEN }}\n          status: pending\n\n      - name: Validate pull request\n        uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1\n        id: pr_data\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          script: |\n            try {\n              const pullRequest = await github.rest.pulls.get({\n                owner: context.repo.owner,\n                repo: context.repo.repo,\n                pull_number: context.issue.number,\n              })\n\n              // Pull request from fork\n              if (context.payload.repository.full_name !== pullRequest.data.head.repo.full_name) {\n                const errorMessage = '`/snapit` is not supported on pull requests from forked repositories.'\n\n                await github.rest.issues.createComment({\n                  issue_number: context.issue.number,\n                  owner: context.repo.owner,\n                  repo: context.repo.repo,\n                  body: errorMessage,\n                })\n\n                core.setFailed(errorMessage)\n              }\n            } catch (err) {\n              core.setFailed(`Request failed with error ${err}`)\n            }\n\n      - name: Checkout default branch\n        uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0\n\n      - name: Checkout pull request branch\n        run: hub pr checkout ${{ github.event.issue.number }}\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Reset changeset entries on changeset-release/main branch\n        run: |\n          if [[ $(git branch --show-current) == 'changeset-release/main' ]]; then\n            git checkout origin/main -- .changeset\n          fi\n\n      - name: Setup Node.js\n        uses: actions/setup-node@3235b876344d2a9aa001b8d1453c930bba69e610 # v3.9.1\n        with:\n          node-version: '20.17.0'\n\n      - name: Install dependencies\n        run: yarn --frozen-lockfile\n\n      - name: Create an .npmrc\n        env:\n          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}\n        run: |\n          cat << EOF > \"$HOME/.npmrc\"\n            //registry.npmjs.org/:_authToken=$NPM_TOKEN\n          EOF\n\n      - name: Create and publish snapshot release\n        uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1\n        id: snapshot-release\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          script: |\n            const execa = require('execa')\n\n            await execa.command('yarn changeset version --snapshot snapshot-release', { stdio: 'inherit' })\n\n            const releaseProcess = execa.command('yarn release --no-git-tags --snapshot --tag snapshot-release')\n            releaseProcess.stdout.pipe(process.stdout)\n\n            const {stdout} = await releaseProcess\n\n            const newTags = Array\n              .from(stdout.matchAll(/New tag:\\s+([^\\s\\n]+)/g))\n              .map(([_, tag]) => tag)\n\n            if (newTags.length) {\n              const multiple = newTags.length > 1\n\n              const body = (\n                `🫰✨ **Thanks @${context.actor}! ` +\n                `Your snapshot${multiple ? 's have' : ' has'} been published to npm.**\\n\\n` +\n                `Test the snapshot${multiple ? 's' : ''} by updating your \\`package.json\\` ` +\n                `with the newly published version${multiple ? 's' : ''}:\\n` +\n                newTags.map(tag => (\n                  '```sh\\n' +\n                  `yarn add ${tag}\\n` +\n                  '```'\n                )).join('\\n')\n              )\n              await github.rest.issues.createComment({\n                issue_number: context.issue.number,\n                owner: context.repo.owner,\n                repo: context.repo.repo,\n                body,\n              })\n              core.setOutput('succeeded', 'true')\n            } else {\n              await github.rest.issues.createComment({\n                issue_number: context.issue.number,\n                owner: context.repo.owner,\n                repo: context.repo.repo,\n                body: `💥 **Snapshot release unsuccessful!** No tags have been found.\\n\\n` +\n                      'Try running the command below and committing your changes.\\n\\n' +\n                      '```sh\\n' +\n                      'yarn changeset\\n' +\n                      '```',\n              })\n              core.setOutput('succeeded', 'false')\n            }\n\n      - name: Add success reaction\n        uses: peter-evans/create-or-update-comment@67dcc547d311b736a8e6c5c236542148a47adc3d # v2.1.1\n        if: ${{ steps.snapshot-release.outputs.succeeded == 'true' }}\n        with:\n          comment-id: ${{ github.event.comment.id }}\n          reactions: rocket\n\n      - name: Add failure reaction\n        uses: peter-evans/create-or-update-comment@67dcc547d311b736a8e6c5c236542148a47adc3d # v2.1.1\n        if: ${{ steps.snapshot-release.outputs.succeeded == 'false' }}\n        with:\n          comment-id: ${{ github.event.comment.id }}\n          reactions: confused\n\n      - name: Fail workflow if snapshot failed\n        uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1\n        if: ${{ steps.snapshot-release.outputs.succeeded == 'false' }}\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          script: |\n            core.setFailed('No snapshot tags have been found')\n\n      - name: Set latest commit status as ${{ job.status }}\n        uses: myrotvorets/set-commit-status-action@7b093ccbb10e14939b7a4ae2630fe4cbc67c0651 # v2.0.1\n        if: always()\n        with:\n          sha: ${{ steps.comment-branch.outputs.head_sha }}\n          token: ${{ secrets.GITHUB_TOKEN }}\n          status: ${{ job.status }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Ignore OS Specific files\n.DS_Store\n\n# Ignore the SASS cache\n.sass-cache\n\n# Logs\nlogs\n*.log\n\n# Dependency directory\n# Deployed apps should consider commenting this line out:\n# see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git\nnode_modules\n\nexample.html\n\ncoverage/\ndocs/\nbuild/\n.rollup.cache/\n\n.github/actions/**/*.js\n\n# 'cause we are all yarny and stuff\npackage-lock.json\n"
  },
  {
    "path": ".nvmrc",
    "content": "v20.17.0\n"
  },
  {
    "path": ".prettierignore",
    "content": "build/\n"
  },
  {
    "path": ".prettierrc",
    "content": "\"@shopify/prettier-config\"\n"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n  \"recommendations\": [\n    \"dbaeumer.vscode-eslint\",\n    \"editorconfig.editorconfig\",\n    \"esbenp.prettier-vscode\"\n  ]\n}\n"
  },
  {
    "path": ".vscode/launch.json",
    "content": "{\n  \"version\": \"1.0.0\",\n  \"configurations\": [\n    {\n      \"type\": \"node\",\n      \"request\": \"launch\",\n      \"name\": \"Jest Tests\",\n      \"program\": \"${workspaceRoot}/node_modules/.bin/jest\",\n      \"console\": \"integratedTerminal\",\n      \"args\": [\n          \"-i\",\n          \"--watchAll\"\n      ],\n    }\n  ]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"files.exclude\": {\n    \"**/.git\": true,\n    \"**/.DS_Store\": true,\n    \"**/node_modules\": true,\n    \".rollup.cache\": true,\n    \"coverage\": true\n  },\n  \"search.exclude\": {\n    \"node_modules/**/*\": true,\n    \".rollup.cache/**/*\": true,\n    \"coverage/**/*\": true,\n    \"build/**/*\": true,\n    \"docs/**/*\": true\n  },\n  \"[typescript]\": {\n    \"editor.formatOnSave\": false\n  },\n  \"editor.formatOnSave\": true,\n  \"editor.codeActionsOnSave\": {\n    \"source.fixAll.eslint\": true\n  },\n  \"javascript.validate.enable\": false,\n  \"eslint.validate\": [\"javascript\"]\n}\n"
  },
  {
    "path": "AUTHORS",
    "content": "Maintainer\n----------\nShopify\nhttps://github.com/Shopify\n\nOriginal Authors\n----------------\nMax Hoffmann / max.hoffmann@shopify.com / @tsov\nCurtis Dulmage / curtis.dulmage@shopify.com / @beefchimi\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## 1.2.1\n\n### Patch Changes\n\n- [#661](https://github.com/Shopify/draggable/pull/661) [`f0194c4`](https://github.com/Shopify/draggable/commit/f0194c493f2e9dabd4267f3316995c153ed3d3eb) Thanks [@tsov](https://github.com/tsov)! - Removes npm token from release workflow\n\n## 1.2.0\n\n### Minor Changes\n\n- [#658](https://github.com/Shopify/draggable/pull/658) [`bb778a4`](https://github.com/Shopify/draggable/commit/bb778a47a52c50e35fd20425e7e82280745a1e85) Thanks [@tsov](https://github.com/tsov)! - Upgrade to node version v20\n\n## 1.1.4\n\n### Patch Changes\n\n- [#621](https://github.com/Shopify/draggable/pull/621) [`a14a290`](https://github.com/Shopify/draggable/commit/a14a2903032a02d160958d3e557ba8d09180c382) Thanks [@tsov](https://github.com/tsov)! - Add provenance statement\n\n## 1.1.3\n\n### Patch Changes\n\n- [#586](https://github.com/Shopify/draggable/pull/586) [`c1df3a5`](https://github.com/Shopify/draggable/commit/c1df3a559ebd959a1054503ee423c6d76243fd0d) Thanks [@tsov](https://github.com/tsov)! - Adds AutoBind decorator\n\n- [#590](https://github.com/Shopify/draggable/pull/590) [`f05efb3`](https://github.com/Shopify/draggable/commit/f05efb3850eb44d83a4de5c03f1ee526ea5e318b) Thanks [@tsov](https://github.com/tsov)! - Fixes VSCode search to exclude generated files/folders\n\n- [#590](https://github.com/Shopify/draggable/pull/590) [`36e61b6`](https://github.com/Shopify/draggable/commit/36e61b6825c9c9911ce3ef5eb2ff962b091fd831) Thanks [@tsov](https://github.com/tsov)! - Converts ResizeMirror test to typescript\n\n## 1.1.2\n\n### Patch Changes\n\n- [#579](https://github.com/Shopify/draggable/pull/579) [`bb23ff2`](https://github.com/Shopify/draggable/commit/bb23ff21f693b623e76c935d8ea4fb58ac57d36c) Thanks [@tsov](https://github.com/tsov)! - Convert CollidableEvent to typescript\n\n- [#579](https://github.com/Shopify/draggable/pull/579) [`9154a96`](https://github.com/Shopify/draggable/commit/9154a9683a0fecfdc4a4759319221ca59c43421a) Thanks [@tsov](https://github.com/tsov)! - Convert SensorEvent to typescript\n\n- [#579](https://github.com/Shopify/draggable/pull/579) [`74c35e5`](https://github.com/Shopify/draggable/commit/74c35e5aa4bd5c3bb7dcaf8cc432c4d5d932ee3f) Thanks [@tsov](https://github.com/tsov)! - Convert SwappableEvent to typescript\n\n- [#579](https://github.com/Shopify/draggable/pull/579) [`fb5354f`](https://github.com/Shopify/draggable/commit/fb5354ffc7688433ef12bcf8ca1b9f473f78cf06) Thanks [@tsov](https://github.com/tsov)! - Convert SortableEvent to typescript\n\n- [#579](https://github.com/Shopify/draggable/pull/579) [`7734961`](https://github.com/Shopify/draggable/commit/773496192fc9a56f6cd24ec7a45f34c79aec4a6d) Thanks [@tsov](https://github.com/tsov)! - Converts DragEvent tests to typescript\n\n- [#579](https://github.com/Shopify/draggable/pull/579) [`a0c3c90`](https://github.com/Shopify/draggable/commit/a0c3c90d8e93ac8ac0e19e5d37e271e6d97c4fa6) Thanks [@tsov](https://github.com/tsov)! - Convert DroppableEvent to typescript\n\n- [#579](https://github.com/Shopify/draggable/pull/579) [`527dcb6`](https://github.com/Shopify/draggable/commit/527dcb67d50978ac81576603a57e42d77fff1eec) Thanks [@tsov](https://github.com/tsov)! - Converts MirrorEvent to typescript\n\n- [#579](https://github.com/Shopify/draggable/pull/579) [`7219781`](https://github.com/Shopify/draggable/commit/721978147cef7f3cc5971fa0cbd636c87ddbe2c7) Thanks [@tsov](https://github.com/tsov)! - Convert SnappableEvent to typescript\n\n- [#579](https://github.com/Shopify/draggable/pull/579) [`cc42520`](https://github.com/Shopify/draggable/commit/cc42520a2731191ae6459a22892d9c1da605b80d) Thanks [@tsov](https://github.com/tsov)! - Converts DraggableEvent to typescript\n\n## 1.1.1\n\n### Patch Changes\n\n- [#580](https://github.com/Shopify/draggable/pull/580) [`873ef2b`](https://github.com/Shopify/draggable/commit/873ef2b59a10361ba4a0e885aaea51d2b2b5c298) Thanks [@tsov](https://github.com/tsov)! - Removes unused packages dependencies\n\n- [#582](https://github.com/Shopify/draggable/pull/582) [`762ffbf`](https://github.com/Shopify/draggable/commit/762ffbf70d018fee101852c186e8d887d1127ce8) Thanks [@tsov](https://github.com/tsov)! - - Cleans up code comments from build folder\n  - Also resolves absolute paths for ts build declarations\n  - Renames build files with .cjs and .mjs\n\n## 1.1.0\n\n### Minor Changes\n\n- [#574](https://github.com/Shopify/draggable/pull/574) [`b81e8f6`](https://github.com/Shopify/draggable/commit/b81e8f678f8e5b27392b6c56b6bb2684a48c0fe8) Thanks [@tsov](https://github.com/tsov)! - Converted build from webpack to rollout. Import paths have changed\n\n## 1.0.1\n\n### Patch Changes\n\n- [#572](https://github.com/Shopify/draggable/pull/572) [`2a8ae0b`](https://github.com/Shopify/draggable/commit/2a8ae0b219beceb3338764afe25c2d6a9fe4f495) Thanks [@tsov](https://github.com/tsov)! - Converts ResizeMirror to typescript\n\n## 1.0.0\n\n### Patch Changes\n\n- Adds changeset dependency for version management\n\n## v1.0.0-beta.13 - 2021-05-17\n\n### Added\n\n- Add `mirror:moved` event\n- Cancel Dragging on ESC key up\n\n### Changed\n\n- Fixes add missing exports `DelayOptions` and `DelayOptions`\n- Fixes return early when the target isn't in handle or draggable elements sensor\n- Fixes the argument type of the `trigger` method\n\n## v1.0.0-beta.12 - 2020-09-29\n\n### Added\n\n- Added `drag:stopped` event that will be fired after drag finished\n- Support specifying an array of class name to Draggable `classes` option\n\n### Changed\n\n- Fixes incorrect `oldIndex` value when working with **nested sortable**\n- Fixes wrong same container checking bug when working with **nested sortable**\n- Fixes bug `drag:start` event was triggered during the delay time\n- Fixes missing `overContainer` property in **DragOutEvent**\n\n## v1.0.0-beta.11 - 2020-07-14\n\n### Added\n\n- Added `exclude` option to allow disable default plugins and sensors\n- Added missing plugin types\n- Support set the type of callback function according to the event type\n\n### Changed\n\n- Fixes drag start concurrency (`delay` and `distance` options)\n- Fixes text in mirror blurry\n- Fixes accidently append mirror\n\n## v1.0.0-beta.10 - 2020-06-18\n\n### Added\n\n- Added `SortAnimation` plugin\n- Added `distance?: number` to DraggableOptions TS interface\n\n### Changed\n\n- Fix mirror dimensions when `constrainDimensions` is active and not using fixed item width\n\n## v1.0.0-beta.9 - 2019-08-26\n\n### Added\n\n- Added `distance` option\n- Added `thresholdX` and `thresholdY` mirror options\n\n### Changed\n\n- Fixes preventing of contextmenu in MouseSensor\n- Fixes SortableEvent `over` and `overContainer` giving incorrect properties\n\n## v1.0.0-beta.8 - 2018-09-07\n\n### Changed\n\n- Announcement plugin to use `textContent` instead of `innerHTML`\n\n## v1.0.0-beta.7 - 2018-04-28\n\n### Added\n\n- ResizeMirror plugin\n\n### Changed\n\n- Fixed native drag events with draggable\n- Mouse position bug in scrollable\n\n## v1.0.0-beta.6 - 2018-04-04\n\n### Added\n\n- Focusable plugin\n- Added DroppableStart event for `Droppable`\n- Added DroppableStop event for `Droppable`\n- Added recommended VSCode settings\n\n### Changed\n\n- Fixed `addContainer`/`removeContainer` api\n- Touch sensor fixes (including iOS 11.3 issues)\n- Renames `DroppableOver` to `DroppableDropped`\n- Renames `DroppableOut` to `DroppableReturned`\n- Fix legacy bundle\n- Improved webpack building\n- Using `console.error` instead of throwing error\n\n## v1.0.0-beta.5 - 2018-03-02\n\n### Added\n\n- Increased Documentation coverage\n- Increased Test coverage, including better testing environment\n- Increased JSDoc coverage\n- Added docblock section to `CONTRIBUTING.md`\n- Added greenkeeper as integration for package dependency management\n- Added codecov as integration for tracking test coverage\n- Added github template issue\n- Added github template PR\n- Added yarn scripts for examples\n- Added SensorEvent to exports\n- Added yarn scripts for esdoc\n- Added `Announcement` plugin for screen reader support\n- Added cursor offset option for `Mirror` plugin\n- Added `scrollableElements` option to `Scrollable` plugin\n- Added `snappableElement` to `SnapEvent`\n- Added examples to published package\n- Added `Emitter` class for event emitting for draggable\n\n### Changed\n\n- Changed esdoc config\n- Changed node version `8.9.1` to `8.9.4`\n- Updated package dependencies\n- Updated roadmap section in README\n- Changed export statements\n- Fixes draggable state after canceling `drag:start`\n- Fixes `constrainDimensions` option for `Mirror` plugin\n- Fixes mirror position with touch devices and `Scrollable`\n- `AutoScroll` plugin has been renamed to `Scrollable`\n- Fixes scrolling edge cases with `Scrollable`\n- Fixes scrolling offset for touch devices in `Scrollable`\n- Fixes npm install issue\n- Fixes `overContainer` property for `DragOutContainerEvent`\n\n## v1.0.0-beta.4 - 2018-01-15\n\n### Added\n\n- Default `Draggable` plugins get exposed statically on `Draggable.Plugins`\n- Default `Scrollable` plugin for Draggable, which auto scrolls containers/viewport while dragging\n- `yarn watch` task for auto-building the library\n- `source:original` class option for Draggable\n- `Draggable#getDraggableElementsForContainer` method, which returns all draggable elements for a given container\n- `MirrorCreateEvent`, which allows for canceling mirror creation\n- `AbstractPlugin` to use as Base class for all Draggable plugins\n- More test coverage\n\n### Changed\n\n- Fixed `Sortable` sort logic by excluding mirror and original source elements in calculations\n- `Draggable` `appendTo` option now uses sources parent element as default, instead of `document.body`\n- `Draggable` appends over classes _after_ triggering over/out events\n- `Draggable` appends source into empty containers\n- Mirrors margin gets removed on creation in the mirror plugin\n- Fix for mirror when drag start gets canceled\n- Fixes memory leak in Draggable when calling `destroy()`\n- Fixes race condition for the `source:placed` class\n- Changed `AbstractEvent#_canceled` to use symbols for private instance variables\n- Some fixes for the documentation READMEs\n\n## v1.0.0-beta.3 - 2017-11-01\n\n### Added\n\n- Bundle split, draggable now exports multiple bundles\n  - Adds JS bundle per module\n  - Adds legacy bundle for IE11\n- Adds axis & dimension constraint options for mirror plugin\n- Basic swap animation plugin\n- Draggables API is now accessible via inheritance for `Sortable`, `Swappable` and `Droppable`\n- Draggables API extended\n  - `addSensor` to add sensor dynamically\n  - `removeSensor` to remove a sensor dynamically\n  - `addPlugin` to add a plugin dynamically\n  - `removePlugin` to remove a plugin dynamically\n  - `addContainer` to add a container dynamically\n  - `removeContainer` to remove a container dynamically\n  - `isDragging` to check if instance is currently dragging\n- New `sortable:sort` event that can be canceled to prevent sorting\n- New `swappable:swap` event that can be canceled to prevent swapping\n- Added more documentation\n\n### Changes\n\n- `SortableSortedEvent` (`sortable:sorted`) now returns correct indexes\n- `SortableStartEvent` gets fired now\n- Plugins and Sensors are exported with namespace\n- Removes reflow by removing unused lookup of next scroll parent\n- Draggable delay option is now `100` by default, instead of `0`\n- Draggables private methods are now really private\n- Sensor improvements\n  - `TouchSensor` now prevents scrolling without preventDefault\n  - `MouseSensor` now prevents native elements to start dragging during delay\n  - All sensors now listen to document rather than each container\n\n## v1.0.0-beta.2 - 2017-10-10\n\n### Added\n\n- Code of Conduct\n- Contribution guidelines\n- Documentation on `appendTo` option for `Draggable`\n- Added concept of `originalSource`\n- Fix for text selection issue\n- Fix for native drag events firing for the `MouseSensor`\n- Fix for missing `classes` option\n\n### Changes\n\n- README updates\n- Touch improvements\n- ForceTouchSensor is not included by default anymore\n- Folder/File restructure\n- Exports `AbstractEvent` as `BaseEvent`\n- Update node version from `8.2.1` to `8.6.0`\n- Clones event callbacks before triggering (to prevent mutation during iterations)\n- Improvements to `closest` utils helper\n\n## v1.0.0-beta - 2017-09-27\n\nInitial release\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to making participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, gender identity and expression, level of experience,\nnationality, personal appearance, race, religion, or sexual identity and\norientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n- Using welcoming and inclusive language\n- Being respectful of differing viewpoints and experiences\n- Gracefully accepting constructive criticism\n- Focusing on what is best for the community\n- Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n- The use of sexualized language or imagery and unwelcome sexual attention or\n  advances\n- Trolling, insulting/derogatory comments, and personal or political attacks\n- Public or private harassment\n- Publishing others' private information, such as a physical or electronic\n  address, without explicit permission\n- Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the project team at max.hoffmann@shopify.com or curtis.dulmage@shopify.com. All\ncomplaints will be reviewed and investigated and will result in a response that\nis deemed necessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality with regard to the reporter of an incident.\nFurther details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,\navailable at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html\n\n[homepage]: https://www.contributor-covenant.org\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to Draggable\n\nThank you for contributing to draggable :tada: Any contributions to draggable are\nappreciated and encouraged.\n\n## Table of contents\n\n1. [Code of Conduct](#code-of-conduct)\n2. [How to contribute](#how-to-contribute)\n   1. [Creating issues](#creating-issues)\n   2. [Opening pull requests](#opening-pull-requests)\n   3. [JS Docblocks](#js-docblocks)\n3. [How to run locally](#how-to-run-locally)\n\n## Code of Conduct\n\nThis project and everyone participating in it is governed by the [Code of Conduct document](https://github.com/Shopify/draggable/blob/main/CODE_OF_CONDUCT.md).\nBy participating, you are expected to uphold this code. Please report unacceptable behaviour to max.hoffmann@shopify.com or curtis.dulmage@shopify.com.\n\n## How to contribute\n\n### Creating issues\n\nBefore submitting issues, please have a quick look if there is an existing open issue here: [Issues](https://github.com/Shopify/draggable/issues). If no related issue can be found,\nplease open a new issue with labels: `bug`, `documentation`, `feature-request` or `question`.\n\n### Opening pull requests\n\nPull requests are more than welcome! Just make sure that to include a description of the problem and how you are attempting to fix the issue, or\nsimply follow the Pull Request description template.\n\nWe also require Pull Requests to sync with main via rebase (not merge). So when you need to sync up your branch with main use: `git pull --rebase origin main`,\nor if you need to sync up with another branch `git pull --rebase origin some-other-branch-name`. Doing so will remove of an extra merge commit in the git history.\nThis will also require a force push to the branch, e.g. `git push -u origin +some-branch`. The `+` in the last command indicates that you are force pushing changes.\n\nAdditionally we require commits to be atomic and squashed where needed. This will keep the git history clean on main. To squash commits use the `git rebase -i @~2`\ncommand to do an interactive rebase. This will allow you to merge multiple commits into one. To read up more on this please visit: [Git Tools Rewriting History](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History)\n\n### JS Docblocks\n\nPlease follow the libraries convention and use docblocks to document the code\n\nHere are some sample docblocks for different types (methods, properties, constants, classes, constructors):\n\n```js\n/**\n * Some method description\n * @param {ParameterType} parameterName\n * @return {ReturnType}\n * @private\n * @static\n * @readonly\n */\n\n/**\n * Some instance property description\n * @property {PropertyType} propertyName\n * @private\n * @static\n * @readonly\n */\n\n/**\n * Some constant description\n * @const {ConstType} constName\n */\n\n/**\n * Some class description\n * @class ClassName\n * @module ClassName\n * @extends BaseClassName\n */\n\n/**\n * Constructor description\n * @constructs ClassName\n */\n```\n\n## How to run locally\n\nHere some steps to run draggable locally:\n\n**via yarn**\n\n```\n$ cd draggable\n$ yarn install\n$ yarn test\n```\n\n**via npm**\n\n```\n$ cd draggable\n$ npm install\n$ npm run test\n```\n\nWe are still working on setting up interactive example pages.\n"
  },
  {
    "path": "LICENSE.md",
    "content": "MIT License\n\nCopyright (c) 2018-present Shopify\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "[![npm version](https://img.shields.io/npm/v/@shopify/draggable.svg?label=@shopify/draggable)](https://www.npmjs.com/package/@shopify/draggable) [![CI](https://github.com/shopify/draggable/workflows/CI/badge.svg)](https://github.com/Shopify/draggable/actions?query=branch%3Amain) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/Shopify/draggable/blob/main/CONTRIBUTING.md) ![Bundle size](https://img.shields.io/badge/Bundle%20size-16.2kB-red.svg)\n\n<a href=\"https://shopify.github.io/draggable\" title=\"Visit Draggable website\">\n  <img src=\"https://user-images.githubusercontent.com/643944/35602291-99e2c56e-0605-11e8-847f-95f1f6be1610.jpg\" alt=\"\">\n</a>\n\n# Development\n\n**Draggable is no longer maintained by its original authors.** Maintenance of this repo has been passed on to new collaborators and is no longer worked on by anyone at Shopify.\n\n**We are still looking for more maintainers!** If anyone is interested in answering / triaging issues, reviewing / rejecting / approving PRs, and authoring code for bug fixes / new features — please send an email to `max.hoffmann (at) shopify (dot) com`. You may be asked a few questions before obtaining collaboration permission, but if everything checks out, we will happily add you as a collaborator.\n\n---\n\nGet complete control over drag and drop behaviour with Draggable! Draggable abstracts\nnative browser events into a comprehensive API to create a custom drag and drop experience.\n`Draggable` comes with additional modules: `Sortable`, `Droppable`, `Swappable`. Draggable\nitself does not perform any sorting behaviour while dragging, but does the heavy lifting, e.g.\ncreates mirror, emits events, manages sensor events, makes elements draggable.\n\nThe additional modules are built on top of `Draggable` and therefore provide a similar API\ninterface, for more information read the documentation below.\n\n**Features**\n\n- Works with native drag, mouse, touch and force touch events\n- Can extend dragging behaviour by hooking into draggables event life cycle\n- Can extend drag detection by adding sensors to draggable\n- The library is targeted ES6 first\n\n## Table of Contents\n\n- [Install](#install)\n- [Documentation](#documentation)\n- [Contributing](#contributing)\n- [Roadmap](#roadmap)\n- [Copyright](#copyright)\n\n## Install\n\nYou can install the library via npm.\n\n```bash\nnpm install @shopify/draggable --save\n```\n\nor via yarn:\n\n```bash\nyarn add @shopify/draggable\n```\n\nor via CDN\n\n```html\n<!-- Entire bundle -->\n<script type=\"module\">\n  import {\n    Draggable,\n    Sortable,\n    Droppable,\n    Swappable,\n  } from 'https://cdn.jsdelivr.net/npm/@shopify/draggable/build/esm/index.mjs';\n</script>\n<!-- Draggable only -->\n<script type=\"module\">\n  import Draggable from 'https://cdn.jsdelivr.net/npm/@shopify/draggable/build/esm/Draggable/Draggable.mjs';\n</script>\n<!-- Sortable only -->\n<script type=\"module\">\n  import Sortable from 'https://cdn.jsdelivr.net/npm/@shopify/draggable/build/esm/Sortable/Sortable.mjs';\n</script>\n<!-- Droppable only -->\n<script type=\"module\">\n  import Droppable from 'https://cdn.jsdelivr.net/npm/@shopify/draggable/build/esm/Droppable/Droppable.mjs';\n</script>\n<!-- Swappable only -->\n<script type=\"module\">\n  import Swappable from 'https://cdn.jsdelivr.net/npm/@shopify/draggable/build/esm/Swappable/Swappable.mjs';\n</script>\n<!-- Plugins only -->\n<script type=\"module\">\n  import * as Plugins from 'https://cdn.jsdelivr.net/npm/@shopify/draggable/build/esm/Plugins/index.mjs';\n</script>\n<!-- UMD browser -->\n<script src=\"https://cdn.jsdelivr.net/npm/@shopify/draggable/build/umd/index.min.js\"></script>\n<script>\n  console.log(window.Draggable);\n</script>\n```\n\n## Browser Compatibility\n\nCheck the \"browserlist\" property in [package.json](https://github.com/Shopify/draggable/blob/main/package.json#L88) for more info\n\n| ![Chrome](https://raw.github.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png) | ![Firefox](https://raw.github.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png) | ![Opera](https://raw.github.com/alrra/browser-logos/master/src/opera/opera_48x48.png) | ![Safari](https://raw.github.com/alrra/browser-logos/master/src/safari/safari_48x48.png) | ![Edge](https://raw.github.com/alrra/browser-logos/master/src/edge/edge_48x48.png) |\n| ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |\n| Last 3 versions ✔                                                                       | Last 3 versions ✔                                                                          | Last 3 versions ✔                                                                    | Last 3 versions ✔                                                                       | Last 3 versions ✔                                                                 |\n\n## Documentation\n\nYou can find the documentation for each module within their respective directories.\n\n- [Draggable](src/Draggable)\n  - [DragEvent](src/Draggable/DragEvent)\n  - [DraggableEvent](src/Draggable/DraggableEvent)\n  - [Plugins](src/Draggable/Plugins)\n    - [Announcement](src/Draggable/Plugins/Announcement)\n    - [Focusable](src/Draggable/Plugins/Focusable)\n    - [Mirror](src/Draggable/Plugins/Mirror)\n    - [MirrorEvent](src/Draggable/Plugins/Mirror/MirrorEvent)\n    - [Scrollable](src/Draggable/Plugins/Scrollable)\n  - [Sensors](src/Draggable/Sensors)\n    - [DragSensor](src/Draggable/Sensors/DragSensor)\n    - [ForceTouchSensor](src/Draggable/Sensors/ForceTouchSensor)\n    - [MouseSensor](src/Draggable/Sensors/MouseSensor)\n    - [Sensor](src/Draggable/Sensors/Sensor)\n    - [SensorEvent](src/Draggable/Sensors/SensorEvent)\n    - [TouchSensor](src/Draggable/Sensors/TouchSensor)\n- [Droppable](src/Droppable)\n  - [DroppableEvent](src/Droppable/DroppableEvent)\n- [Plugins](src/Plugins)\n  - [Collidable](src/Plugins/Collidable)\n  - [ResizeMirror](src/Plugins/ResizeMirror)\n  - [Snappable](src/Plugins/Snappable)\n  - [SwapAnimation](src/Plugins/SwapAnimation)\n  - [SortAnimation](src/Plugins/SortAnimation)\n- [Sortable](src/Sortable)\n  - [SortableEvent](src/Sortable/SortableEvent)\n- [Swappable](src/Swappable)\n  - [SwappableEvent](src/Swappable/SwappableEvent)\n\n### TypeScript\n\nDraggable includes [TypeScript](http://typescriptlang.org) definitions.\n\n[Documentation](doc/typescript.md)\n\n## Running examples\n\nTo run the `examples` project locally, simply run the following from the `draggable` root:\n\n```bash\nyarn && yarn start\n```\n\nThis will start a server that hosts the contents of `examples/`. It also watches for file\nchanges from both `src/` and `examples/src` and reloads the browser.\n\n## Contributing\n\nContributions are more than welcome, the code base is still new and needs more love.\n\nFor more information, please checkout the [contributing document](https://github.com/Shopify/draggable/blob/main/CONTRIBUTING.md).\n\n## Related resources\n\n- [Ember CLI Shim](https://github.com/timrourke/ember-cli-shopify-draggable-shim) on Github by [@timrourke](https://github.com/timrourke)\n- [Ember CLI Shim](https://www.npmjs.com/package/ember-cli-shopify-draggable-shim) on NPM by [@timrourke](https://github.com/timrourke)\n\n## Copyright\n\nCopyright (c) 2018-present Shopify. See LICENSE.md for further details.\n"
  },
  {
    "path": "babel.config.js",
    "content": "module.exports = function (api) {\n  api.cache(true);\n\n  return {\n    presets: [\n      [\n        '@babel/preset-env',\n        {useBuiltIns: 'entry', corejs: '3.0', bugfixes: true},\n      ],\n      ['@babel/preset-typescript'],\n    ],\n    plugins: [['@babel/plugin-proposal-decorators', {version: '2023-05'}]],\n    assumptions: {\n      setPublicClassFields: true,\n      privateFieldsAsProperties: true,\n      // nothing accesses `document.all`:\n      noDocumentAll: true,\n      // nothing relies on class constructors invoked without `new` throwing:\n      noClassCalls: true,\n      // nothing should be relying on tagged template strings being frozen:\n      mutableTemplateObject: true,\n      // nothing is relying on Function.prototype.length:\n      ignoreFunctionLength: true,\n      // nothing is relying on mutable re-exported bindings:\n      constantReexports: true,\n      // don't bother marking Module records non-enumerable:\n      enumerableModuleMeta: true,\n      // nothing uses [[Symbol.toPrimitive]]:\n      // (see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toPrimitive)\n      ignoreToPrimitiveHint: true,\n      // nothing relies on spread copying Symbol keys:  ({...{ [Symbol()]: 1 }})\n      objectRestNoSymbols: true,\n      // nothing relies on `new (() => {})` throwing:\n      noNewArrows: true,\n      // transpile object spread to assignment instead of defineProperty():\n      setSpreadProperties: true,\n      // nothing should be using custom iterator protocol:\n      skipForOfIteratorClosing: true,\n      // nothing inherits from a constructor function with explicit return value:\n      superIsCallableConstructor: true,\n      // nothing relies on CJS-transpiled namespace imports having all properties prior to module execution completing:\n      noIncompleteNsImportDetection: true,\n    },\n  };\n};\n"
  },
  {
    "path": "config/typescript/rollup-plugin-includepaths.d.ts",
    "content": "declare module 'rollup-plugin-includepaths' {\n  interface Options {\n    include: {[key: string]: string};\n    paths: string[];\n    extensions: string[];\n  }\n  export = includePaths;\n  function includePaths(object: Options);\n}\n"
  },
  {
    "path": "doc/typescript.md",
    "content": "# Default usage\n\n```typescript\nimport {Sortable} from '@shopify/draggable';\n\nconst sortable = new Sortable(document.querySelectorAll('ul'), {\n  draggable: 'li',\n});\n\n// The type of the first argument is SortableEventNames\nsortable.on('sortable:sort', (evt) => {\n  // The type of evt is SortableSortEvent\n});\n\n// The type of the first argument is SortableEventNames\nsortable.on('drag:out:container', (evt) => {\n  // The type of evt is DragOutContainerEvent\n});\n```\n\n# Using plugins\n\nWhen creating an instance with plugins with events, you need to manually specify the event names.\n\n```typescript\nimport {Droppable, Plugins} from '@shopify/draggable';\n\n// 1. import the event names you need\nimport type {\n  DroppableEventNames,\n  CollidableEventNames,\n} from \"@shopify/draggable\";\n\n// 2. Specify the event names when create the instance\nconst droppable = new Droppable<DroppableEventNames | CollidableEventNames>(document.querySelectorAll('.container'), {\n  draggable: '.item',\n  dropzone: '.dropzone',\n  collidables: '.other-list',\n  plugins: [Plugins.Collidable],\n});\n\n// The type of the first argument can be DroppableEventNames or CollidableEventNames\ndroppable.on('droppable:dropped', (evt) => {\n  // The type of evt is DroppableDroppedEvent\n});\n\n// The type of the first argument can be DroppableEventNames or CollidableEventNames\ndroppable.on('collidable:in', (evt) => {\n  // The type of evt is CollidableInEvent\n});\n```\n"
  },
  {
    "path": "esdoc.json",
    "content": "{\n  \"source\": \"./src\",\n  \"destination\": \"./docs\",\n  \"plugins\": [\n    {\"name\": \"esdoc-standard-plugin\"},\n    {\"name\": \"esdoc-ecmascript-proposal-plugin\", \"option\": {\"all\": true}}\n  ]\n}\n"
  },
  {
    "path": "examples/.babelrc",
    "content": "{\n  \"presets\": [\n    [\n      \"babel-preset-shopify/web\",\n      {\n        \"babel-preset-shopify/web\": {\n          \"modules\": false,\n          \"useBuiltIns\": \"entry\"\n        }\n      }\n    ]\n  ]\n}\n"
  },
  {
    "path": "examples/.browserslistrc",
    "content": "> 1%\nLast 1 version\n"
  },
  {
    "path": "examples/.editorconfig",
    "content": "# editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_size = 2\nindent_style = space\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n# Markdown syntax specifies that trailing whitespaces can be meaningful,\n# so let’s not trim those. e.g. 2 trailing spaces = linebreak (<br />)\n# See https://daringfireball.net/projects/markdown/syntax#p\n[*.md]\ntrim_trailing_whitespace = false\n\n# Disable trailing whitespace removal in diff files,\n# where whitespace is meaningful\n[*.diff]\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "examples/.eslintignore",
    "content": "node_modules\npackages\ndist\nscrap\nsrc/scripts/vendor\n"
  },
  {
    "path": "examples/.eslintrc.js",
    "content": "/* eslint-env node */\nmodule.exports = {\n  extends: ['plugin:shopify/esnext', 'plugin:shopify/webpack', 'plugin:shopify/prettier'],\n  globals: {},\n  rules: {\n    'shopify/jsx-no-complex-expressions': 'off',\n    'eslint-comments/no-unlimited-disable': 0,\n    // 'import/no-default-export': ['error'],\n    'lines-around-comment': [\n      'error',\n      {\n        beforeBlockComment: false,\n        allowBlockStart: false,\n      },\n    ],\n    'no-console': 0,\n    'no-negated-condition': 'off',\n    // We are intentionally keeping `TODO` comments until a stable release\n    'no-warning-comments': 'off',\n    'import/no-cycle': 'off',\n    // TODO: Disabling for now, but we will want to re-enable in the future.\n    'import/no-default-export': 'off',\n  },\n  env: {\n    browser: true,\n  },\n  // Required, else eslint will look at the parent config\n  root: true,\n};\n"
  },
  {
    "path": "examples/.gitignore",
    "content": "# OS files\n.DS_Store\n\n# Caches\n.cache\n.eslintcache\n.sass-cache\n.npm\n*.tsbuildinfo\n\n# Dev\n.dev\n\n# Environment\n.env\n.env.test\n\n# Build\ndist\nscrap\npackages\n\n# Secrets\nsecrets.json\n\n# Reports\nassets-manifest.json\nbundle-report.html\n\n# Node/yarn\nnode_modules\n.yarn-integrity\n\n# Coverage\ncoverage\nlib-cov\n*.lcov\n\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n"
  },
  {
    "path": "examples/.nvmrc",
    "content": "v20.17.0\n"
  },
  {
    "path": "examples/.prettierignore",
    "content": "node_modules\npackages\n/.github\n/assets-manifest.json\n/bundle-report.html\n/dist\n/package.json\n"
  },
  {
    "path": "examples/.prettierrc",
    "content": "{\n  \"arrowParens\": \"always\",\n  \"bracketSpacing\": false,\n  \"printWidth\": 100,\n  \"singleQuote\": true,\n  \"trailingComma\": \"all\"\n}\n"
  },
  {
    "path": "examples/.stylelintignore",
    "content": "node_modules\npackages\n/.github\n/assets-manifest.json\n/bundle-report.html\n/dist\n/package.json\n"
  },
  {
    "path": "examples/.stylelintrc",
    "content": "{\n  \"extends\": [\n    \"stylelint-config-shopify/prettier\"\n  ],\n  \"rules\": {\n    \"shopify/content-no-strings\": true,\n    \"scss/at-if-no-null\": null,\n    \"scss/partial-no-import\": null,\n    \"color-no-hex\": null,\n    \"comment-empty-line-before\": null,\n    \"scss/double-slash-comment-empty-line-before\": null,\n    \"declaration-block-no-redundant-longhand-properties\": [\n      true,\n      {\n        ignoreShorthands: [\"/^grid.*/\"],\n      },\n    ],\n    \"declaration-property-value-blacklist\": null,\n    \"font-weight-notation\": null,\n    \"function-url-scheme-whitelist\": null,\n    \"no-unknown-animations\": null,\n    \"selector-class-pattern\": null,\n    \"selector-id-pattern\": null,\n    \"selector-max-class\": 4,\n    \"selector-max-id\": 1,\n    \"selector-max-type\": 2,\n    \"selector-max-combinators\": 3,\n    \"selector-max-compound-selectors\": 4,\n    \"selector-max-specificity\": \"1,5,1\",\n    \"selector-no-qualifying-type\": null\n  }\n}\n"
  },
  {
    "path": "examples/.vscode/extensions.json",
    "content": "{\n  \"recommendations\": [\n    \"christian-kohler.npm-intellisense\",\n    \"christian-kohler.path-intellisense\",\n    \"codezombiech.gitignore\",\n    \"dbaeumer.vscode-eslint\",\n    \"EditorConfig.editorconfig\",\n    \"esbenp.prettier-vscode\",\n    \"Gruntfuggly.todo-tree\",\n    \"hex-ci.stylelint-plus\",\n    \"mariusschulz.yarn-lock-syntax\",\n    \"mrmlnc.vscode-scss\",\n    \"ronnidc.nunjucks\"\n  ]\n}\n"
  },
  {
    "path": "examples/.vscode/settings.json",
    "content": "{\n  \"css.validate\": false,\n  \"scss.validate\": false,\n  \"editor.codeActionsOnSave\": {\n    \"source.fixAll\": true\n  },\n  \"editor.formatOnSave\": true,\n  \"editor.defaultFormatter\": \"esbenp.prettier-vscode\",\n  \"eslint.validate\": [\"javascript\", \"javascriptreact\", \"typescript\", \"typescriptreact\"],\n  \"stylelint.enable\": true,\n  \"stylelint.autoFixOnSave\": true,\n  \"files.exclude\": {\n    \"**/.git\": true,\n    \"**/.DS_Store\": true,\n    \"**/node_modules\": true,\n    \"lib\": true\n  },\n  \"search.exclude\": {\n    \"**/node_modules\": true,\n    \"lib\": true,\n    \"dist/\": true\n  }\n}\n"
  },
  {
    "path": "examples/README.md",
    "content": "![banner-examples](https://user-images.githubusercontent.com/643944/34655498-c9701942-f3d8-11e7-9dd5-6d225e7c5f6f.png)\n\n> The Examples site has been made available to aid developers interested in building web apps with Draggable and/or contributing back to the library.\n\nThe Examples sandbox aims to answer common questions concerning Draggable’s implementation, as well as provide solutions to any conceivable drag and drop problem.\n\nFor instance, our solution to resolving touch events required duplicating the `source` node in the DOM. Depending on your design, this could cause friction as you attempt to maintain the correct layout while dragging. A bit of clever CSS can help resolve such layout issues. There are several examples in this repo that demonstrate solutions to this exact layout concern.\n\n## Local development\n\n1. Clone the `draggable` repo: `git clone git@github.com:Shopify/draggable.git`.\n2. Run `yarn && yarn start` in the root `draggable` directory.\n3. You can now access the local site at `http://localhost:3000` _(local address may vary - see console for correct url)_.\n\n## Code style\n\nThis project uses Shopify’s [eslint](https://github.com/Shopify/eslint-plugin-shopify) and [stylelint](https://github.com/Shopify/stylelint-config-shopify) configs with a few alterations, defined in their respective `dot rc` file. If contributing back to this project, you must conform to the code style enforced by the config. If you are just playing around and don’t care about code style, just disable your editor’s linter.\n\n## Project structure\n\nThis project uses a co-located component structure, meaning everything relating to an individual component should all be found in the same folder. For the most part, you will work only in the `src/components` and `src/content` folders.\n\nThe root directory contains many config files that you can safely ignore unless you aim to change how files are compiled. It is strongly recommended to read the `package.json` to familiarize yourself with the available `scripts`.\n\n### `src/views`\n\nThis folder contains the top-level page templates. The project uses Nunjucks for a templating language.\n\nThere is only one layout template, `views/templates/document.html`, which is extended by all of the `.html` files in the root of `/views`. These root views do the following:\n\n1. Import global components such as `Sidebar` and `PageHeader`\n2. Define the `ViewAttr` for the view _(Page title, description, etc)_.\n3. Import and render the content component for that view.\n\n### `src/styles`\n\nThis is where “global” styles, along with the `examples-app.scss` manifest, are located. There shouldn’t be much reason to alter any of these files other than adding new imports to `examples-app.scss`.\n\nThis project uses [Threads](https://github.com/beefchimi/threads) to manage style properties. You can see all of the Threads theme values in `styles/themes/examples`.\n\n### `src/scripts`\n\nJust like with `styles/`, this is where our “global” scripts, along with the `examples-app.js` entry script is located. There shouldn’t be much reason to alter any of these files other than adding new imports and initializations to `examples-app.js`.\n\n### `src/content`\n\nThis folder contains all the “content components”, which means all the code specific to an individual Example. Content is grouped by the “example type”: `Draggable`, `Droppable`, `Sortable`, `Swappable`, or `Plugins`. There is also a `Home` folder just for the landing page, and a `shared` folder for common functions and mixins that are reused across multiple content components.\n\nEach individual example will have a single `.html` view, `.scss` file, and a `index.js`. The `index.js` is where you will initialize and author any `draggable` logic. The default function exported from each `index.js` file is imported and initialized in the `src/scripts/examples-app.js` entry file.\n\n### `src/components`\n\nHere are all of the shared components, such as the `Block` component, used in many examples as draggable elements.\n\nSometimes these are just `.scss` files, and the markup is written within a content component. If the styles contain values specific to that component, but need to be shared with other components, they will be split out into a `props.scss` file. Variant classes are split out into a `variants.scss` file.\n\nSome components have a `.html` file, which uses Nunjucks macros to define reusable components. For example, `Block` has several variations that can be toggled through its component API. Once a `Block` is imported into a view, you can render using:\n\n`{{ Block.render('one', {index: 1, draggable: true}) }}`\n\nThat will render all of the markup for a `Block` component, using the string `one` as its `Heading`, and appending the classes `Block--item1` and `Block--isDraggable`.\n\nSome components will also have their own JavaScript logic, such as `Plate`, which manages how the `Plate` component gets transformed via a drag event.\n\n### `src/root`\n\nThis folder simply contains any files that need to be copied to the root `dist/` folder, such as a `manifest.json` or `.htaccess`. There should be no reason to alter files in this folder.\n\n### `tools`\n\nAll of the build scripts are found in this folder. You shouldn’t need to go in here unless you want to change how the code is compiled.\n\nAll JavaScript is generated with source maps, so you should be able to dig through errors and `console.log` statements without issue.\n\n## Running a server / watching files\n\nRunning `yarn start` fires up a `browsersync` server to view the site. While running the server, when you add/make changes to any of the files in `src/`, the appropriate files will then be recompiled and output to the `dist/` folder. Once a file has been compiled, the browser will automatically reload the page. For `scss` changes, the new styles are injected into the page without causing a refresh.\n\nThe file watcher will also look for changes in the `draggable/lib` folder, which means you can be running the examples server and making changes to the core library at the same time. Scripts will get recompiled and the browser will reload.\n\n## Contributing\n\nIf while using Draggable, you encounter something that is not already covered in the Examples, please contribute back by creating a new Example under the correct grouping.\n\nWe ask that you follow our code style config and try your best to make the example consistent with the design of the site. Lean on the components already created to compose your example. If required, you can design and build your own components.\n\nThere is an [open issue](https://github.com/Shopify/draggable/issues/110) for building a CLI generator for new pages. If you are interested in taking this on, please assign yourself to the issue.\n\nIf you are unsure if your example has merit, feel free to [open an issue](https://github.com/Shopify/draggable/issues) and propose your idea. Always ping [@tsov](https://github.com/tsov) and [@beefchimi](https://github.com/beefchimi) for issues and code review!\n"
  },
  {
    "path": "examples/package.json",
    "content": "{\n  \"name\": \"draggable-examples\",\n  \"version\": \"1.1.0\",\n  \"description\": \"Examples for draggable.js\",\n  \"author\": \"Max Hoffmann and Curtis Dulmage\",\n  \"homepage\": \"https://shopify.github.io/draggable\",\n  \"license\": \"MIT\",\n  \"main\": \"tools/index.js\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/Shopify/draggable.git\"\n  },\n  \"keywords\": [\n    \"draggable\",\n    \"javascript\"\n  ],\n  \"bugs\": {\n    \"url\": \"https://github.com/Shopify/draggable/issues\"\n  },\n  \"config\": {\n    \"tools\": \"--require @babel/register --gulpfile tools\"\n  },\n  \"scripts\": {\n    \"clean\": \"rimraf dist bundle-report.html\",\n    \"views\": \"NODE_OPTIONS=--openssl-legacy-provider gulp views $npm_package_config_tools\",\n    \"scripts\": \"NODE_OPTIONS=--openssl-legacy-provider gulp scripts $npm_package_config_tools\",\n    \"styles\": \"NODE_OPTIONS=--openssl-legacy-provider gulp styles $npm_package_config_tools\",\n    \"start\": \"NODE_OPTIONS=--openssl-legacy-provider gulp start $npm_package_config_tools\",\n    \"build\": \"NODE_OPTIONS=--openssl-legacy-provider yarn run clean && gulp build $npm_package_config_tools\",\n    \"build:prod\": \"NODE_OPTIONS=--openssl-legacy-provider NODE_ENV=production yarn run build\",\n    \"lint:scss\": \"stylelint './src/**/*.scss'\",\n    \"prettier:scss\": \"prettier-stylelint './src/**/*.scss' --write -q\",\n    \"lint:js\": \"eslint './src/**/*.js' './tools/**/*.js' --max-warnings 0\",\n    \"prettier:js\": \"yarn run lint:js --fix\",\n    \"prepublish\": \"NODE_OPTIONS=--openssl-legacy-provider yarn run build\"\n  },\n  \"dependencies\": {\n    \"core-js\": \"^3.6.5\",\n    \"threads\": \"https://github.com/beefchimi/threads.git#v1.0.0-beta8\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.8.3\",\n    \"@babel/register\": \"^7.8.3\",\n    \"autoprefixer\": \"^9.8.6\",\n    \"babel-loader\": \"^8.1.0\",\n    \"babel-preset-shopify\": \"^21.0.0\",\n    \"browser-sync\": \"^2.26.12\",\n    \"cssnano\": \"^4.1.10\",\n    \"cssnano-preset-advanced\": \"^4.0.7\",\n    \"eslint\": \"^7.10.0\",\n    \"eslint-plugin-prettier\": \"^3.1.4\",\n    \"eslint-plugin-shopify\": \"^35.1.0\",\n    \"gulp\": \"^4.0.2\",\n    \"gulp-cli\": \"^2.3.0\",\n    \"gulp-data\": \"^1.3.1\",\n    \"gulp-htmlmin\": \"^5.0.1\",\n    \"gulp-nunjucks\": \"^5.1.0\",\n    \"gulp-postcss\": \"^9.0.0\",\n    \"gulp-sass\": \"^4.1.0\",\n    \"gulp-sourcemaps\": \"^2.6.5\",\n    \"install\": \"^0.13.0\",\n    \"nunjucks\": \"^3.2.2\",\n    \"prettier\": \"^2.1.2\",\n    \"prettier-stylelint\": \"^0.4.2\",\n    \"rimraf\": \"^3.0.2\",\n    \"stylelint\": \"^13.7.2\",\n    \"stylelint-config-shopify\": \"^7.4.0\",\n    \"webpack\": \"^4.44.2\",\n    \"webpack-bundle-analyzer\": \"^3.9.0\"\n  },\n  \"resolutions\": {\n    \"node-sass\": \"8.0.0\"\n  }\n}\n"
  },
  {
    "path": "examples/postcss.config.js",
    "content": "/* eslint-env node */\nmodule.exports = () => ({\n  plugins: {\n    autoprefixer: {},\n    cssnano: {\n      // reduceIdents causing problem with `grid-template-areas`\n      preset: ['advanced', {reduceIdents: false}],\n      autoprefixer: false,\n    },\n  },\n});\n"
  },
  {
    "path": "examples/src/components/Analytics/index.js",
    "content": "function gtag() {\n  window.dataLayer.push(arguments); // eslint-disable-line prefer-rest-params\n}\n\nexport default class Analytics {\n  constructor(ua) {\n    this.ua = ua;\n  }\n\n  init() {\n    if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') {\n      console.log('🤖 Analytics disabled in local development.');\n      return;\n    }\n\n    this._appendScript()\n      .then(() => {\n        window.dataLayer = window.dataLayer || [];\n\n        gtag('js', new Date());\n        gtag('config', this.ua);\n\n        return window.dataLayer;\n      })\n      .catch((error) => console.warn(error));\n  }\n\n  _appendScript() {\n    return new Promise((resolve, reject) => {\n      const script = document.createElement('script');\n      document.body.appendChild(script);\n      script.onload = resolve;\n      script.onerror = reject;\n      script.async = true;\n      script.src = `https://www.googletagmanager.com/gtag/js?id=${this.ua}`;\n    });\n  }\n}\n"
  },
  {
    "path": "examples/src/components/Block/Block.html",
    "content": "{% macro render(heading, options = {}) %}\n  {% set classes = ['Block'] %}\n\n  {% set classes = (classes.push('Block--typeShell') if options.shell, classes) %}\n\n  {% if options.type and (\n    options.type === 'Shell' or options.type === 'Hollow' or options.type === 'Stripes'\n  ) %}\n    {% set classes = (classes.push('Block--type' + options.type), classes) %}\n  {% endif %}\n\n  {% set classes = (classes.push('Block--item' + options.index) if options.index, classes) %}\n  {% set classes = (classes.push('Block--isDraggable') if options.draggable, classes) %}\n  {% set classes = (classes.push(options.classes | join(' ')) if options.classes, classes) %}\n  {% set classes = classes | join(' ') | trim %}\n\n  {% if options.draggable %}\n    {% set openingTag = '<span class=\"' + classes + '\" title=\"Click to drag\">' %}\n    {% set closingTag = '</span>' %}\n  {% else %}\n    {% set openingTag = '<span class=\"' + classes + '\">' %}\n    {% set closingTag = '</span>' %}\n  {% endif %}\n\n  {{ openingTag | safe }}\n    <div class=\"BlockContent\">\n      {% if heading.length > 1 %}\n        <h3 class=\"Heading Heading--size2 text-no-select\">{{ heading }}</h3>\n      {% endif %}\n\n      {% if options.draggable %}\n        <div class=\"Pattern Pattern--typeHalftone\"></div>\n        <div class=\"Pattern Pattern--typePlaced\"></div>\n      {% endif %}\n    </div>\n  {{ closingTag | safe }}\n{% endmacro %}\n"
  },
  {
    "path": "examples/src/components/Block/Block.scss",
    "content": "////\n/// Components\n/// Block\n////\n\n@import 'utils/shared/functions';\n@import 'utils/shared/layout';\n\n.BlockWrapper {\n  .Block {\n    height: 100%;\n  }\n}\n\n.Block {\n  position: relative;\n  display: block;\n}\n\n///\n/// Block Content\n.BlockContent {\n  @include flex-center;\n  position: relative;\n  min-height: rows(2, true);\n  height: 100%;\n  color: white;\n  background-color: get-color(coal, dark);\n  border: get-border(thin) solid get-color(coal, dark);\n\n  @media screen and (min-width: get-breakpoint(tablet)) {\n    min-height: rows(2);\n    border-width: get-border();\n  }\n\n  .Heading {\n    // vertical alignment\n    margin-top: -0.1em;\n  }\n}\n\n@import 'variants';\n"
  },
  {
    "path": "examples/src/components/Block/variants.scss",
    "content": "////\n/// Components\n/// Block variants\n////\n\n@import 'utils/shared/layout';\n@import 'components/Patterns/props';\n\n///\n/// BlockLayout\n.BlockLayout--typeFlex,\n.BlockLayout--typeFloat {\n  margin-top: -(get-border(thin));\n  margin-left: -(get-border(thin));\n\n  > .BlockWrapper {\n    margin-top: get-border(thin);\n    margin-left: get-border(thin);\n  }\n\n  > .Block {\n    padding-top: get-border(thin);\n    padding-left: get-border(thin);\n  }\n\n  @media screen and (min-width: get-breakpoint(tablet)) {\n    margin-top: -(get-border());\n    margin-left: -(get-border());\n\n    > .BlockWrapper {\n      margin-top: get-border();\n      margin-left: get-border();\n    }\n\n    > .Block {\n      padding-top: get-border();\n      padding-left: get-border();\n    }\n  }\n}\n\n.BlockLayout--typeFlex {\n  display: flex;\n  flex-wrap: wrap;\n\n  .Block {\n    flex: 1 1 100%;\n  }\n}\n\n.BlockLayout--typeFloat {\n  @include clearfix;\n\n  .Block {\n    float: left;\n    width: 100%;\n  }\n}\n\n.BlockLayout--typeGrid {\n  display: grid;\n  gap: get-border(thin);\n\n  @media screen and (min-width: get-breakpoint(tablet)) {\n    gap: get-border();\n  }\n}\n\n.BlockLayout--typePositioned {\n  position: relative;\n}\n\n///\n/// Block types\n.Block--typeShell {\n  .BlockContent {\n    color: get-color(coal, dark);\n    background-color: white;\n    border-color: currentColor;\n  }\n}\n\n.Block--typeHollow {\n  .BlockContent {\n    color: get-color(ash, dark);\n    background-color: white;\n    border-color: currentColor;\n  }\n}\n\n.Block--typeStripes {\n  .BlockContent {\n    @include stripes-bg;\n    color: get-color(coal, dark);\n    border-color: currentColor;\n    transition: color get-duration(fast) get-easing();\n  }\n}\n\n///\n/// Draggable Blocks\n.Block--isDraggable {\n  cursor: get-cursor(drag);\n\n  .BlockContent {\n    color: get-color(coal, dark);\n    background-color: white;\n    border-color: currentColor;\n    transition: color get-duration(fast) get-easing(),\n      background-color get-duration(fast) get-easing(), transform get-duration() get-easing(bungie);\n  }\n\n  // interaction\n  &:focus .BlockContent,\n  &:hover .BlockContent {\n    color: get-color(brand, blue);\n  }\n\n  &.draggable-source--is-dragging .BlockContent {\n    color: get-color(brand, blue);\n\n    .Pattern--typeHalftone {\n      @include pattern-halftone-animated;\n    }\n  }\n\n  &.draggable-source--placed .BlockContent {\n    .Pattern--typePlaced {\n      @include pattern-placed-animated;\n    }\n  }\n\n  &.draggable-mirror {\n    z-index: get-z-index(overlay);\n    transition: width get-duration() get-easing(bungie), height get-duration() get-easing(bungie);\n\n    .BlockContent {\n      height: 100%;\n      color: white;\n      background-color: get-color(brand, blue);\n      border-color: get-color(brand, blue);\n      transform: scale(1.025);\n    }\n  }\n}\n\n///\n/// Droppable\n.BlockWrapper {\n  position: relative;\n\n  &.draggable-dropzone--occupied {\n    .Block--typeHollow,\n    .Block--typeStripes {\n      @include position-cover;\n    }\n  }\n}\n\n///\n/// Collidable\n// stylelint-disable-next-line no-duplicate-selectors\n.Block--typeStripes {\n  // should be a native Draggable class name\n  &.isColliding {\n    .BlockContent {\n      color: get-color(brand, red);\n    }\n  }\n}\n"
  },
  {
    "path": "examples/src/components/Brand/Brand.html",
    "content": "<a href=\"./\" class=\"Brand\" title=\"Return to Examples index\">\n  <div class=\"SvgContainer BrandLogo\">\n    {% include 'components/Brand/Logo.html' %}\n  </div>\n\n  <div class=\"SvgContainer BrandWordmark\">\n    {% include 'components/Brand/Wordmark.html' %}\n  </div>\n</a>\n"
  },
  {
    "path": "examples/src/components/Brand/Brand.scss",
    "content": "////\n/// Components\n/// Brand\n////\n\n@import 'keyframes';\n@import 'props';\n\n.Brand {\n  cursor: get-cursor(rock);\n  display: flex;\n\n  &:active {\n    cursor: get-cursor(rock, active);\n  }\n\n  @media screen and (max-width: get-breakpoint() - 1px) {\n    justify-content: center;\n  }\n\n  @media screen and (min-width: get-breakpoint(desktop)) {\n    margin-left: -(get-spacing(tightest));\n  }\n}\n\n.BrandLogo {\n  flex: 0 0 $logo-width;\n}\n\n.BrandWordmark {\n  flex: 0 0 $wordmark-width;\n  margin-left: get-spacing(tighter);\n}\n\n.Svg--srcWordmark {\n  fill: get-color(brand, yellow);\n}\n\n///\n/// Logo\n.Wave--colorMask {\n  fill: get-color(coal, dark);\n}\n\n.Wave--colorPurple {\n  fill: get-color(brand, purple);\n}\n\n.Wave--colorRed {\n  fill: get-color(brand, red);\n}\n\n.Wave--colorOrange {\n  fill: get-color(brand, orange);\n}\n\n.Hand {\n  fill: get-color(brand, yellow);\n}\n\n// --- Interaction\n// stylelint-disable-next-line no-duplicate-selectors\n.Brand {\n  &:hover {\n    @include logo-animation;\n  }\n\n  // stylelint-disable-next-line no-duplicate-selectors\n  &:active {\n    .Wave--colorMask,\n    .Wave--colorPurple,\n    .Wave--colorRed,\n    .Wave--colorOrange,\n    .Hand {\n      animation-duration: get-duration();\n    }\n  }\n}\n"
  },
  {
    "path": "examples/src/components/Brand/Logo.html",
    "content": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 60 78\" class=\"Svg Svg--srcLogo\">\n  <path class=\"Wave Wave--colorMask\" d=\"M19.738 61.097c-7.5-4.3-11.2-12.8-9.8-20.7L6.294 38.33l-2.756 4.667c-6.4 11.1-2.6 25.4 8.5 31.9 11.2 6.4 25.5 2.6 31.9-8.5.9-1.6 1.8-3.1 2.7-4.7l-4-2.5c-6.2 5.2-15.3 6.3-22.9 1.9z\"/>\n  <path class=\"Wave Wave--colorPurple\" d=\"M19.738 61.097c-8.8-5.1-12.3-15.9-8.5-24.9l-2.783-1.6-2.717 4.7c-6.4 11.1-2.6 25.4 8.5 31.9 11.2 6.4 25.5 2.6 31.9-8.5.9-1.6 1.8-3.1 2.7-4.7l-3.3-1.9c-5.8 7.8-16.9 10.2-25.8 5z\"/>\n  <path class=\"Wave Wave--colorRed\" d=\"M46.938 54.097c-5.4 9.4-17.6 12.5-27.1 7s-12.9-17.6-7.5-27l1-1.7-2.7-1.6-2.7 4.7c-6.5 11.2-2.7 25.5 8.5 32 11.2 6.4 25.5 2.6 31.9-8.5.9-1.6 1.784-3.11 2.684-4.71l-3.184-1.79-.9 1.6z\"/>\n  <path class=\"Wave Wave--colorOrange\" d=\"M23.03 59.23c-10.797-6.323-14.684-20.024-8.998-30.983l-1.324-.87-2.77 4.62c-6.4 11.1-2.6 25.4 8.5 31.9 11.2 6.4 25.5 2.6 32-8.6.78-1.355 2.116-3.63 2.116-3.63l-1.524-.865c-6.54 10.625-17.063 14.677-28 8.427z\"/>\n  <path class=\"Hand\" d=\"M59.305 36.397c-.7-2.4-2.7-4.3-5.2-4.6 1-3.8-1.5-7.5-5.2-8 .9-3.5-1.4-7.1-5-7.8 3.9-6.7 5.2-8.1 4.4-11.3-1.5-5.5-8.9-6.4-11.7-1.5l-6.9 12c-2.9-3.4-8.2-2.8-10.4 1l-7.2 12.2c-6.4 11.1-2.6 25.4 8.5 31.9 11.2 6.4 25.5 2.6 32-8.6 6.4-11.1 7.6-12 6.7-15.3zm-10.4 13.2c-5.3 9.1-17 12.2-26.1 7-9.1-5.3-12.2-17-7-26.1 7.5-13.1 7.1-12.9 8.4-13.3 1.9-.5 3.4 1.5 2.4 3.1l-5.1 8.8c-1.4 2.4 2.3 4.6 3.7 2.1 15.8-27.4 14.9-26.6 16.3-26.9 1.1-.3 2.3.4 2.6 1.5.4 1.4.2.9-10.7 19.8-1.4 2.4 2.3 4.6 3.7 2.1l3.7-6.5c1.4-2.4 5.1-.3 3.7 2.1l-3.7 6.5c-1.4 2.4 2.3 4.6 3.7 2.1 1.4-2.4 1.7-3.5 3-3.9 1.1-.3 2.3.4 2.6 1.5.4 1.3-.4 2-4 8.2-1.4 2.4 2.3 4.6 3.7 2.1 1.3-2.3 1.7-3.5 2.9-3.8 1.1-.3 2.3.4 2.6 1.5.2 1.4-.4 1.7-6.4 12.1z\"/>\n</svg>\n"
  },
  {
    "path": "examples/src/components/Brand/Wordmark.html",
    "content": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 176 42\" class=\"Svg Svg--srcWordmark\">\n  <path d=\"M20.014.052h-.869c-.955 0-1.042.087-1.129.998l-2.04 16.282c-2.084-2.648-3.864-3.604-6.686-3.604-5.297 0-9.291 4.211-9.291 9.768 0 4.993 3.604 8.639 8.552 8.639 2.388 0 3.821-.651 5.991-2.779l-.174 1.172c-.044.26-.044.478-.044.564 0 .478.26.607 1.085.607h.869c.911 0 .998-.087 1.129-.998l3.603-29.48c.044-.26.044-.347.044-.565.001-.431-.259-.604-1.04-.604zM8.899 29.357c-3.343 0-5.774-2.474-5.774-5.86 0-3.995 2.779-6.99 6.425-6.99 3.343 0 5.818 2.345 5.818 5.513 0 4.125-2.865 7.337-6.469 7.337zm23.922-15.629c-1.737 0-2.866.607-4.689 2.432l.131-.825c.044-.304.044-.521.044-.565 0-.478-.26-.607-1.085-.607h-.869c-.911 0-.998.087-1.129.998l-1.867 15.37c-.044.217-.044.391-.044.564.044.478.26.607 1.042.607h.868c.956 0 1.042-.087 1.129-.998l.998-8.032c.478-3.994 1.954-6.035 4.384-6.035.347 0 .651.044.825.087.912.347.912.347 1.042.347.26 0 .391-.087.782-.607l.564-.825c.304-.347.391-.565.391-.694.001-.696-1.128-1.217-2.517-1.217zm21.185.434h-.869c-.956 0-1.042.087-1.129.998l-.217 1.867c-1.954-2.432-3.69-3.299-6.512-3.299-5.384 0-9.248 4.124-9.248 9.899 0 4.993 3.343 8.51 8.163 8.51 2.388 0 3.908-.738 6.078-2.908l-.172 1.302c-.044.304-.044.478-.044.564 0 .434.26.607 1.085.607h.869c.912 0 .998-.087 1.129-.998l1.867-15.37c.044-.217.044-.434.044-.565-.046-.476-.262-.607-1.044-.607zm-9.248 15.195c-3.212 0-5.6-2.518-5.6-5.86 0-3.951 2.779-6.99 6.382-6.99 3.299 0 5.731 2.388 5.731 5.6 0 4.038-2.909 7.25-6.513 7.25zm32.085-15.195h-.869c-.956 0-1.042.087-1.129.998l-.217 1.91c-2.127-2.474-3.864-3.343-6.512-3.343-5.34 0-9.421 4.255-9.421 9.812 0 4.906 3.603 8.596 8.379 8.596 2.258 0 3.821-.651 5.991-2.561l-.391 3.039c-.26 2.474-.738 3.821-1.65 4.776-1.042 1.129-2.735 1.78-4.646 1.78-2.952 0-5.036-1.52-5.253-3.777-.131-1.259-.131-1.259-1.129-1.259h-.738c-.912 0-1.085.131-1.085.869 0 4.081 3.43 6.947 8.336 6.947 2.908 0 5.427-1.042 6.947-2.866 1.345-1.607 1.867-3.212 2.345-7.12l2.041-16.628c.044-.217.044-.391.044-.565-.045-.477-.263-.608-1.043-.608zm-9.161 15.195c-3.386 0-5.862-2.518-5.862-5.948 0-3.951 2.822-6.903 6.556-6.903 3.43 0 5.862 2.388 5.862 5.818-.001 3.951-2.866 7.033-6.556 7.033zm31.909-15.195h-.869c-.956 0-1.042.087-1.129.998l-.217 1.91c-2.127-2.474-3.864-3.343-6.512-3.343-5.34 0-9.421 4.255-9.421 9.812 0 4.906 3.603 8.596 8.379 8.596 2.258 0 3.821-.651 5.991-2.561l-.391 3.04c-.26 2.474-.738 3.821-1.65 4.776-1.042 1.129-2.735 1.78-4.646 1.78-2.952 0-5.036-1.52-5.253-3.777-.131-1.259-.131-1.259-1.129-1.259h-.738c-.912 0-1.085.131-1.085.869 0 4.081 3.43 6.947 8.336 6.947 2.908 0 5.427-1.042 6.947-2.866 1.345-1.607 1.867-3.212 2.345-7.12l2.041-16.628c.044-.217.044-.391.044-.565-.043-.478-.261-.609-1.043-.609zm-9.159 15.195c-3.386 0-5.862-2.518-5.862-5.948 0-3.951 2.822-6.903 6.556-6.903 3.43 0 5.862 2.388 5.862 5.818-.001 3.951-2.866 7.033-6.556 7.033zm31.736-15.195h-.869c-.956 0-1.042.087-1.129.998l-.217 1.867c-1.954-2.432-3.69-3.299-6.512-3.299-5.384 0-9.248 4.124-9.248 9.899 0 4.993 3.343 8.51 8.163 8.51 2.388 0 3.908-.738 6.078-2.908l-.173 1.303c-.044.304-.044.478-.044.564 0 .434.26.607 1.085.607h.869c.912 0 .998-.087 1.129-.998l1.867-15.37c.044-.217.044-.434.044-.565-.045-.477-.262-.608-1.043-.608zm-9.248 15.195c-3.212 0-5.6-2.518-5.6-5.86 0-3.951 2.779-6.99 6.382-6.99 3.299 0 5.731 2.388 5.731 5.6 0 4.038-2.909 7.25-6.513 7.25zm25.267-15.629c-2.432 0-3.951.694-6.165 2.692l1.867-15.195c.044-.304.044-.478.044-.565 0-.434-.26-.607-1.085-.607h-.869c-.912 0-.998.087-1.129.998l-3.603 29.48c-.044.217-.044.391-.044.564.044.478.26.607 1.042.607h.868c.956 0 1.042-.087 1.129-.998l.26-1.954c2.041 2.474 3.864 3.386 6.729 3.386 5.167 0 9.248-4.342 9.248-9.812.001-4.992-3.515-8.596-8.292-8.596zm-1.345 15.629c-3.343 0-5.774-2.474-5.774-5.904 0-3.951 2.822-6.947 6.512-6.947 3.343 0 5.774 2.474 5.774 5.862 0 3.863-2.909 6.989-6.512 6.989zM155.813.052h-.869c-.912 0-.998.087-1.129.998l-3.603 29.48c-.044.217-.044.391-.044.564.044.478.26.607 1.042.607h.869c.956 0 1.042-.087 1.129-.998l3.646-29.48c.044-.304.044-.478.044-.565.001-.433-.26-.606-1.085-.606zm12.07 13.545c-5.167 0-9.074 4.298-9.074 10.073 0 5.166 3.473 8.596 8.77 8.596 2.388 0 4.428-.738 5.904-2.127.651-.607 1.042-1.129 1.042-1.432 0-.304-.087-.434-.738-.782l-.478-.304c-.434-.26-.565-.304-.651-.304a.718.718 0 0 0-.521.217c-1.259 1.345-2.605 1.954-4.515 1.954-1.997 0-3.69-.825-4.733-2.258-.651-.955-.912-1.867-.956-3.56h12.981c.869 0 1.085-.131 1.085-.825.002-5.644-3.167-9.248-8.116-9.248zm-5.688 7.555c.347-1.432.651-2.084 1.259-2.822.998-1.259 2.605-1.954 4.298-1.954 2.952 0 4.906 1.78 5.124 4.775h-10.681v.001z\"/>\n</svg>\n"
  },
  {
    "path": "examples/src/components/Brand/keyframes.scss",
    "content": "////\n/// Components\n/// Brand keyframes\n////\n\n@import 'props';\n\n@keyframes logo-outline-bounce {\n  25% {\n    transform: translate(-$logo-bounce-offset, $logo-bounce-offset);\n  }\n\n  75% {\n    transform: translate($logo-bounce-offset, -$logo-bounce-offset);\n  }\n}\n\n@keyframes logo-rainbow-mask {\n  0% {\n    fill: get-color(coal, dark);\n  }\n\n  25% {\n    fill: get-color(brand, purple);\n  }\n\n  50% {\n    fill: get-color(brand, red);\n  }\n\n  75% {\n    fill: get-color(brand, orange);\n  }\n}\n\n@keyframes logo-rainbow-purple {\n  0% {\n    fill: get-color(brand, purple);\n  }\n\n  25% {\n    fill: get-color(brand, red);\n  }\n\n  50% {\n    fill: get-color(brand, orange);\n  }\n\n  75% {\n    fill: get-color(coal, dark);\n  }\n}\n\n@keyframes logo-rainbow-red {\n  0% {\n    fill: get-color(brand, red);\n  }\n\n  25% {\n    fill: get-color(brand, orange);\n  }\n\n  50% {\n    fill: get-color(coal, dark);\n  }\n\n  75% {\n    fill: get-color(brand, purple);\n  }\n}\n\n@keyframes logo-rainbow-orange {\n  0% {\n    fill: get-color(brand, orange);\n  }\n\n  25% {\n    fill: get-color(coal, dark);\n  }\n\n  50% {\n    fill: get-color(brand, purple);\n  }\n\n  75% {\n    fill: get-color(brand, red);\n  }\n}\n"
  },
  {
    "path": "examples/src/components/Brand/props.scss",
    "content": "////\n/// Components\n/// Brand props\n////\n\n$logo-width: 4.6rem;\n$wordmark-width: 17.6rem;\n$logo-bounce-offset: 0.1rem;\n\n@mixin logo-animation {\n  .Wave--colorMask {\n    animation: logo-rainbow-mask get-duration(slow) get-easing() infinite;\n  }\n\n  .Wave--colorPurple {\n    animation: logo-rainbow-purple get-duration(slow) get-easing() infinite;\n  }\n\n  .Wave--colorRed {\n    animation: logo-rainbow-red get-duration(slow) get-easing() infinite;\n  }\n\n  .Wave--colorOrange {\n    animation: logo-rainbow-orange get-duration(slow) get-easing() infinite;\n  }\n\n  .Hand {\n    animation: logo-outline-bounce get-duration(slow) linear infinite;\n  }\n}\n"
  },
  {
    "path": "examples/src/components/Button/Button.scss",
    "content": "////\n/// Components\n/// Button\n////\n\n@import 'utils/shared/layout';\n\n.Button {\n  display: inline-block;\n  padding: get-spacing(tightest);\n  font-size: get-type-scale(button);\n  font-weight: get-type-scale(button, weight);\n  text-align: center;\n  white-space: nowrap;\n  color: get-color(coal, dark);\n  background-color: white;\n  border: 0.4rem solid get-color(coal, dark);\n  transition: color get-duration() get-easing(), background-color get-duration() get-easing(),\n    border-color get-duration() get-easing();\n\n  @media screen and (min-width: get-breakpoint(tablet)) {\n    padding-right: get-spacing(tighter);\n    padding-left: get-spacing(tighter);\n    font-size: get-type-scale(button, tablet);\n  }\n\n  &:focus,\n  &:hover {\n    color: get-color(brand, blue);\n    border-color: get-color(brand, blue);\n  }\n\n  &:active {\n    color: darken(get-color(brand, blue), 10%);\n    border-color: get-color(brand, blue);\n  }\n\n  &:disabled {\n    pointer-events: none;\n    color: get-color(ash);\n    border-color: get-color(ash);\n  }\n}\n\n///\n/// Button container\n.ButtonContainer {\n  display: flex;\n  flex-wrap: wrap;\n  justify-content: center;\n  align-items: center;\n  margin-top: -(get-spacing(tightest));\n  margin-left: -(get-spacing(tightest));\n\n  .Button {\n    @include flex-item-fix;\n    display: block;\n    flex: 0 0 auto;\n    margin-top: get-spacing(tightest);\n    margin-left: get-spacing(tightest);\n  }\n}\n\n///\n/// Variants\n@import 'variants';\n"
  },
  {
    "path": "examples/src/components/Button/variants.scss",
    "content": "////\n/// Components\n/// Button variants\n////\n\n.Button--isActive {\n  color: white;\n  background-color: get-color(coal, dark);\n\n  &:focus,\n  &:hover {\n    color: white;\n    background-color: get-color(brand, blue);\n    border-color: get-color(brand, blue);\n  }\n\n  &:active {\n    color: white;\n    background-color: darken(get-color(brand, blue), 10%);\n    border-color: darken(get-color(brand, blue), 10%);\n  }\n\n  &:disabled {\n    pointer-events: none;\n    color: white;\n    background-color: get-color(ash);\n    border-color: get-color(ash);\n  }\n}\n\n.Button--typePill {\n  border-radius: get-type-scale(button) * 2;\n}\n"
  },
  {
    "path": "examples/src/components/Document/Favicon.html",
    "content": "<meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n<meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\">\n<meta name=\"apple-mobile-web-app-title\" content=\"Draggable\">\n\n<link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"https://shopify.github.io/draggable/assets/img/favicons/favicon-16x16.png\">\n<link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"https://shopify.github.io/draggable/assets/img/favicons/favicon-32x32.png\">\n<link rel=\"icon\" type=\"image/png\" sizes=\"96x96\" href=\"https://shopify.github.io/draggable/assets/img/favicons/favicon-96x96.png\">\n<link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"https://shopify.github.io/draggable/assets/img/favicons/apple-touch-icon.png\">\n"
  },
  {
    "path": "examples/src/components/Document/Head.html",
    "content": "{% import 'components/Document/SocialShare.html' as SocialShare %}\n\n{% macro render(ViewAttr) %}\n  {% set url = 'https://shopify.github.io/draggable' %}\n  {% set siteName = 'Draggable JS Examples' %}\n  {% set titleSep = ' | ' %}\n\n  {% set parentFragment = ViewAttr.parent + titleSep if ViewAttr.parent %}\n  {% set childFragment = ViewAttr.child + titleSep if ViewAttr.child %}\n  {% set titleText = [parentFragment, childFragment, siteName] %}\n\n  {% set HeadAttr = {\n    siteName: siteName,\n    title: ViewAttr.subheading,\n    description: 'Draggable is a lightweight, responsive, modern drag and drop JavaScript library – the ideal choice for adding slick native-feeling drag and drop behaviour to your web apps.',\n    siteUrl: url,\n    socialImg: url + '/assets/img/social/draggable-social.png',\n    twitterHandle: '@Shopify',\n    twitterId: '17136315'\n  } %}\n\n  <head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n\n    <title>{{ titleText | join('') | trim }}</title>\n\n    <meta name=\"description\" content=\"{{ HeadAttr.description }}\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, viewport-fit=cover\">\n    <meta name=\"format-detection\" content=\"telephone=no\">\n    <meta name=\"theme-color\" content=\"#212529\">\n\n    {{ SocialShare.render(HeadAttr) }}\n    {% include 'components/Document/Favicon.html' %}\n\n    <link rel=\"manifest\" href=\"{{ url }}/manifest.json\">\n    <link rel=\"stylesheet\" href=\"assets/css/examples-app.css?beta=12\">\n\n    <script src=\"assets/js/examples-runtime.js?beta=12\" defer></script>\n    <!-- <script src=\"assets/js/examples-vendor.js?beta=12\" defer></script> -->\n    <script src=\"assets/js/examples-app.js?beta=12\" defer></script>\n  </head>\n{% endmacro %}\n"
  },
  {
    "path": "examples/src/components/Document/SocialShare.html",
    "content": "{% macro render(HeadAttr) %}\n  <meta property=\"og:type\" content=\"website\">\n  <meta property=\"og:site_name\" content=\"{{ HeadAttr.siteName }}\">\n  <meta property=\"og:title\" content=\"{{ HeadAttr.title }}\">\n  <meta property=\"og:description\" content=\"{{ HeadAttr.description }}\">\n  <meta property=\"og:image\" content=\"{{ HeadAttr.socialImg }}\">\n  <meta property=\"og:url\" content=\"{{ siteUrl }}\">\n  <meta property=\"twitter:card\" content=\"summary_large_image\">\n  <meta property=\"twitter:site\" content=\"{{ HeadAttr.twitterHandle }}\">\n  <meta property=\"twitter:account_id\" content=\"{{ HeadAttr.twitterId }}\">\n  <meta property=\"twitter:title\" content=\"{{ HeadAttr.title }}\">\n  <meta property=\"twitter:description\" content=\"{{ HeadAttr.description }}\">\n  <meta property=\"twitter:image\" content=\"{{ HeadAttr.socialImg }}\">\n{% endmacro %}\n"
  },
  {
    "path": "examples/src/components/GridOverlay/GridOverlay.scss",
    "content": "////\n/// Components\n/// Grid Overlay\n////\n\n@import 'utils/shared/functions';\n@import 'utils/shared/layout';\n@import 'components/Main/props';\n\n$grid-overlay-baseline: 0.1rem;\n\n.GridOverlay {\n  --columns: 4;\n  --repeating-width: calc(100% / var(--columns));\n  --gutter-size: get-layout-length(gutter, mobile);\n  --column-width: calc((100% / var(--columns)) - var(--gutter-size));\n  --baseline-stop: calc(var(--gutter-size) - #{$grid-overlay-baseline});\n  --row-height: calc(#{rows(1, true)} + var(--gutter-size));\n  --bg-width: calc(100% + var(--gutter-size));\n  --bg-baseline: repeating-linear-gradient(\n    to bottom,\n    get-color(purple),\n    get-color(purple) $grid-overlay-baseline,\n    transparent $grid-overlay-baseline,\n    transparent var(--baseline-stop),\n    get-color(purple) var(--baseline-stop),\n    get-color(purple) var(--gutter-size),\n    transparent var(--gutter-size),\n    transparent var(--row-height)\n  );\n  --bg-columns: repeating-linear-gradient(\n    to right,\n    get-color(purple, light),\n    get-color(purple, light) var(--column-width),\n    transparent var(--column-width),\n    transparent var(--repeating-width)\n  );\n\n  @media screen and (min-width: get-breakpoint(tablet)) {\n    --columns: 6;\n    --gutter-size: get-layout-length(gutter);\n    --row-height: calc(#{rows()} + var(--gutter-size));\n  }\n\n  @media screen and (min-width: get-breakpoint(desktop)) {\n    --columns: 8;\n  }\n\n  // positioned parent will be `body`\n  @include centered-width(get-layout-length(main-interior));\n  z-index: 1;\n  position: fixed;\n  top: 0;\n  bottom: 0;\n  left: get-spacing(tight);\n  right: get-spacing(tight);\n  min-height: 100vh;\n  background-image: var(--bg-baseline), var(--bg-columns);\n  background-size: var(--bg-width) 100%;\n  background-position: 0 calc(-1 * var(--gutter-size));\n  mix-blend-mode: multiply;\n  opacity: 0.8;\n  pointer-events: none;\n\n  @media screen and (min-width: get-breakpoint()) {\n    left: $main-padding-left-tablet;\n  }\n\n  @media screen and (min-width: get-breakpoint(desktop)) {\n    right: get-spacing(fattest);\n    left: $main-padding-left-desktop;\n  }\n}\n\n.GridOverlay--isHidden {\n  display: none;\n}\n"
  },
  {
    "path": "examples/src/components/Hamburger/Hamburger.html",
    "content": "<button type=\"button\" id=\"MobileNavActivator\" class=\"Hamburger\" aria-controls=\"Sidebar\" aria-expanded=\"false\">\n  <div class=\"HamburgerBun\">\n    <div class=\"HamburgerPatty\"></div>\n  </div>\n</button>\n"
  },
  {
    "path": "examples/src/components/Hamburger/Hamburger.scss",
    "content": "////\n/// Components\n/// Hamburger\n////\n\n@import 'utils/shared/layout';\n@import 'keyframes';\n@import 'props';\n\n.Hamburger {\n  z-index: get-z-index(hamburger);\n  position: absolute;\n  top: get-spacing();\n  left: 50%;\n  margin-left: -$hamburger-offset;\n  padding: get-spacing(tightest);\n  // prevent the 'X' on page load\n  opacity: 0;\n  animation: FadeActivator get-duration(slow) get-easing() forwards;\n\n  @media only screen and (min-width: get-breakpoint()) {\n    @include visually-hidden;\n  }\n\n  &[aria-expanded='true'] {\n    .HamburgerBun::before,\n    .HamburgerBun::after {\n      background-color: white;\n    }\n\n    .HamburgerBun::before {\n      animation-name: HamburgerBefore-In;\n    }\n\n    .HamburgerBun::after {\n      animation-name: HamburgerAfter-In;\n    }\n\n    .HamburgerPatty {\n      animation-name: HamburgerPatty-In;\n    }\n  }\n}\n\n///\n/// HamburgerBun menu styles\n.HamburgerBun {\n  position: relative;\n  width: $hamburger-width;\n  height: $hamburger-height;\n\n  &::before,\n  &::after {\n    content: '';\n    top: 0;\n    display: block;\n    transition: background-color get-duration() get-easing();\n  }\n\n  &::before,\n  &::after,\n  .HamburgerPatty {\n    position: absolute;\n    left: 0;\n    width: 100%;\n    height: $hamburger-line-height;\n    border-radius: $hamburger-line-height;\n    background-color: get-color(coal, dark);\n    animation-duration: get-duration();\n    animation-timing-function: get-easing();\n    animation-fill-mode: both;\n  }\n\n  &::before {\n    transform: translateY(0) rotate(0);\n    animation-name: HamburgerBefore-Out;\n  }\n\n  &::after {\n    transform: translateY($hamburger-after-y) rotate(0);\n    animation-name: HamburgerAfter-Out;\n  }\n}\n\n.HamburgerPatty {\n  top: $hamburger-vertical-center;\n  animation-name: HamburgerPatty-Out;\n}\n"
  },
  {
    "path": "examples/src/components/Hamburger/keyframes.scss",
    "content": "////\n/// Components\n/// Hamburger keyframes\n////\n\n@import 'props';\n\n@keyframes FadeActivator {\n  to {\n    opacity: 1;\n  }\n}\n\n@keyframes HamburgerBefore-In {\n  0% {\n    transform: translateY(0) rotate(0);\n  }\n\n  50% {\n    transform: translateY($hamburger-vertical-center) rotate(0);\n  }\n\n  100% {\n    transform: translateY($hamburger-vertical-center) rotate(45deg);\n  }\n}\n\n@keyframes HamburgerBefore-Out {\n  0% {\n    transform: translateY($hamburger-vertical-center) rotate(45deg);\n  }\n\n  50% {\n    transform: translateY($hamburger-vertical-center) rotate(0);\n  }\n\n  100% {\n    transform: translateY(0) rotate(0);\n  }\n}\n\n@keyframes HamburgerAfter-In {\n  0% {\n    transform: translateY($hamburger-after-y) rotate(0);\n  }\n\n  50% {\n    transform: translateY($hamburger-vertical-center) rotate(0);\n  }\n\n  100% {\n    transform: translateY($hamburger-vertical-center) rotate(-45deg);\n  }\n}\n\n@keyframes HamburgerAfter-Out {\n  0% {\n    transform: translateY($hamburger-vertical-center) rotate(-45deg);\n  }\n\n  50% {\n    transform: translateY($hamburger-vertical-center) rotate(0);\n  }\n\n  100% {\n    transform: translateY($hamburger-after-y) rotate(0);\n  }\n}\n\n@keyframes HamburgerPatty-In {\n  0%,\n  25% {\n    opacity: 1;\n  }\n\n  50%,\n  100% {\n    opacity: 0;\n  }\n}\n\n@keyframes HamburgerPatty-Out {\n  0%,\n  25% {\n    opacity: 0;\n  }\n\n  50%,\n  100% {\n    opacity: 1;\n  }\n}\n"
  },
  {
    "path": "examples/src/components/Hamburger/props.scss",
    "content": "////\n/// Components\n/// Hamburger props\n////\n\n$hamburger-width: 2.4rem;\n$hamburger-height: 2rem;\n$hamburger-line-height: 0.3rem;\n$hamburger-after-y: $hamburger-height - $hamburger-line-height;\n$hamburger-vertical-center: ($hamburger-height / 2) - ($hamburger-line-height / 2);\n$hamburger-offset: (get-spacing(tightest) * 2 + $hamburger-width) / 2;\n"
  },
  {
    "path": "examples/src/components/Handle/DragHandle.scss",
    "content": "////\n/// Components\n/// DragHandle\n////\n\n@import 'props';\n\n.DragHandle {\n  position: relative;\n  width: $handle-size;\n  height: $handle-size;\n  background-color: currentColor;\n  transition: background-color get-duration(fast) get-easing();\n\n  &::before,\n  &::after {\n    content: '';\n    position: absolute;\n    right: 0;\n    left: 0;\n    display: block;\n    height: $handle-stroke-width;\n    background-color: white;\n    transition: background-color get-duration(fast) get-easing();\n  }\n\n  &::before {\n    top: $handle-stroke-width;\n  }\n\n  &::after {\n    bottom: $handle-stroke-width;\n  }\n}\n"
  },
  {
    "path": "examples/src/components/Handle/NopeHandle.scss",
    "content": "////\n/// Components\n/// NopeHandle\n////\n\n@import 'props';\n\n.NopeHandle {\n  position: relative;\n  width: $handle-size;\n  height: $handle-size;\n  border: $handle-stroke-width solid currentColor;\n  border-radius: 50%;\n  transition: border-color get-duration(fast) get-easing();\n\n  &::before {\n    content: '';\n    position: absolute;\n    top: $handle-center - $handle-stroke-width;\n    left: -($handle-stroke-width / 2);\n    display: block;\n    width: $handle-size - $handle-stroke-width;\n    height: $handle-stroke-width;\n    background-color: currentColor;\n    transform: rotate(45deg);\n  }\n}\n"
  },
  {
    "path": "examples/src/components/Handle/props.scss",
    "content": "////\n/// Components\n/// Handle props\n////\n\n$handle-size: 2rem;\n$handle-stroke-width: 0.4rem;\n$handle-center: ($handle-size / 2) - ($handle-stroke-width / 2);\n\n@mixin drag-handle-dragging {\n  background-color: white;\n\n  &::before,\n  &::after {\n    background-color: get-color(brand, blue);\n  }\n}\n"
  },
  {
    "path": "examples/src/components/Heading/Heading.scss",
    "content": "////\n/// Components\n/// Heading\n////\n\n@import 'props';\n\n.Heading {\n  @include Heading;\n}\n\n.Subheading {\n  @include Subheading;\n}\n\n@import 'variants';\n"
  },
  {
    "path": "examples/src/components/Heading/props.scss",
    "content": "////\n/// Components\n/// Heading props\n////\n\n@mixin Heading {\n  letter-spacing: get-type-scale(base, tracking);\n  line-height: get-type-scale(base, leading);\n  color: currentColor;\n}\n\n@mixin Subheading {\n  font-size: get-type-scale(subheading);\n  font-weight: get-type-scale(subheading, weight);\n  letter-spacing: get-type-scale(base, tracking);\n  line-height: get-type-scale(base, leading);\n  color: get-color(coal, light);\n\n  @media screen and (min-width: get-breakpoint(tablet)) {\n    font-size: get-type-scale(subheading, tablet);\n  }\n}\n\n///\n/// Heading sizes\n@mixin HeadingSizeJumbo {\n  font-size: get-type-scale(jumbo);\n  font-weight: get-type-scale(jumbo, weight);\n  line-height: get-type-scale(jumbo, leading);\n\n  @media screen and (min-width: get-breakpoint(tablet)) {\n    font-size: get-type-scale(jumbo, tablet);\n  }\n\n  @media screen and (min-width: get-breakpoint(desktop)) {\n    font-size: get-type-scale(jumbo, desktop);\n  }\n}\n\n@mixin HeadingSize1 {\n  margin-bottom: get-spacing(tight);\n  font-size: get-type-scale(h1);\n  font-weight: get-type-scale(h1, weight);\n\n  @media screen and (min-width: get-breakpoint(tablet)) {\n    margin-bottom: get-spacing();\n    font-size: get-type-scale(h1, tablet);\n  }\n\n  @media screen and (min-width: get-breakpoint(desktop)) {\n    font-size: get-type-scale(h1, desktop);\n  }\n}\n\n@mixin HeadingSize2 {\n  font-size: get-type-scale(h2);\n  font-weight: get-type-scale(h2, weight);\n\n  @media screen and (min-width: get-breakpoint(tablet)) {\n    font-size: get-type-scale(h2, tablet);\n  }\n\n  @media screen and (min-width: get-breakpoint(desktop)) {\n    font-size: get-type-scale(h2, desktop);\n  }\n}\n\n@mixin HeadingSize3 {\n  font-size: get-type-scale(h3);\n  font-weight: get-type-scale(h3, weight);\n\n  @media screen and (min-width: get-breakpoint(tablet)) {\n    font-size: get-type-scale(h3, tablet);\n  }\n\n  @media screen and (min-width: get-breakpoint(desktop)) {\n    font-size: get-type-scale(h3, desktop);\n  }\n}\n\n@mixin HeadingSize4 {\n  font-size: get-type-scale(h4);\n  font-weight: get-type-scale(h4, weight);\n\n  @media screen and (min-width: get-breakpoint(tablet)) {\n    font-size: get-type-scale(h4, tablet);\n  }\n\n  @media screen and (min-width: get-breakpoint(desktop)) {\n    font-size: get-type-scale(h4, desktop);\n  }\n}\n"
  },
  {
    "path": "examples/src/components/Heading/variants.scss",
    "content": "////\n/// Components\n/// Heading variants\n////\n\n@import 'props';\n\n///\n/// Heading sizes\n.Heading--sizeJumbo {\n  @include HeadingSizeJumbo;\n}\n\n.Heading--size1 {\n  @include HeadingSize1;\n}\n\n.Heading--size2 {\n  @include HeadingSize2;\n}\n\n.Heading--size3 {\n  @include HeadingSize3;\n}\n\n.Heading--size4 {\n  @include HeadingSize4;\n}\n\n///\n/// Heading colors\n.Heading--colorWhite {\n  color: white;\n}\n"
  },
  {
    "path": "examples/src/components/Link/Link.scss",
    "content": "////\n/// Components\n/// Link\n////\n\n.Link {\n  color: get-color(purple, light);\n  transition: color get-duration() get-easing();\n\n  &:focus,\n  &:hover {\n    color: get-color(purple);\n  }\n\n  &:active {\n    color: get-color(purple, dark);\n  }\n}\n\n///\n/// Variants\n.Link--typeDark {\n  color: get-color(purple);\n\n  &:focus,\n  &:hover {\n    color: get-color(brand, yellow);\n  }\n\n  &:active {\n    color: get-color(brand, orange);\n  }\n}\n\n.Link--typeUnderlined {\n  display: inline-block;\n  font-size: 1.6rem;\n  font-weight: 700;\n  color: get-color(coal, dark);\n  text-decoration: underline;\n  text-decoration-line: underline;\n  text-decoration-skip: ink;\n\n  &:focus,\n  &:hover {\n    color: get-color(brand, blue);\n  }\n\n  &:active {\n    color: darken(get-color(brand, blue), 10%);\n  }\n}\n\n.Link--noWrap {\n  white-space: nowrap;\n}\n"
  },
  {
    "path": "examples/src/components/Main/Main.scss",
    "content": "////\n/// Components\n/// Main\n////\n\n@import 'utils/shared/layout';\n@import 'components/MobileNav/props';\n@import 'props';\n\n.Main {\n  padding: $mobile-nav-padding-top get-spacing(tight) get-spacing(looser);\n  min-height: 100vh;\n  overflow: hidden;\n  background-color: white;\n\n  @media screen and (min-width: get-breakpoint()) {\n    padding-top: get-spacing(looser);\n    padding-left: $main-padding-left-tablet;\n  }\n\n  @media screen and (min-width: get-breakpoint(desktop)) {\n    padding: get-spacing(fat) get-spacing(fattest) get-spacing(fat) $main-padding-left-desktop;\n  }\n}\n\n.MainInterior {\n  @include centered-width(get-layout-length(main-interior));\n}\n"
  },
  {
    "path": "examples/src/components/Main/props.scss",
    "content": "////\n/// Components\n/// Main props\n////\n\n$main-padding-left-tablet: get-layout-length(sidebar) + get-spacing(tight);\n$main-padding-left-desktop: get-layout-length(sidebar) + get-spacing(fattest);\n"
  },
  {
    "path": "examples/src/components/MobileNav/index.js",
    "content": "import debounce, {debounceDuration} from '../../scripts/utils/debounce';\n\n// equal to `get-breakpoint()` base value\nconst MAX_WIDTH = 960;\n\nconst Attrs = {\n  controls: 'aria-controls',\n  expanded: 'aria-expanded',\n  hidden: 'aria-hidden',\n};\n\nexport default class MobileNav {\n  constructor(activator) {\n    this.activator = activator;\n    this.target = document.getElementById(activator.getAttribute(Attrs.controls));\n  }\n\n  init() {\n    if (!this.target) {\n      console.error('The activator must have a valid `aria-controls` value. Target not found.');\n      return;\n    }\n\n    this._setState();\n    this.activator.addEventListener('click', this.toggle.bind(this));\n\n    window.addEventListener(\n      'resize',\n      debounce(() => {\n        this._setState();\n      }, debounceDuration),\n    );\n  }\n\n  expand(widthExceeded = false) {\n    const lockScrolling = !widthExceeded;\n    const willExpand = widthExceeded ? 'undefined' : 'false';\n\n    this.expanded = true;\n    this.activator.setAttribute(Attrs.expanded, 'true');\n    this.target.setAttribute(Attrs.hidden, willExpand);\n    document.documentElement.dataset.scrollLock = lockScrolling;\n  }\n\n  collapse() {\n    if (this.expanded === false) {\n      return;\n    }\n\n    this.expanded = false;\n    this.activator.setAttribute(Attrs.expanded, 'false');\n    this.target.setAttribute(Attrs.hidden, 'true');\n    document.documentElement.dataset.scrollLock = false;\n  }\n\n  toggle() {\n    return this.expanded ? this.collapse() : this.expand();\n  }\n\n  _setState() {\n    const windowWidth = document.documentElement.clientWidth;\n\n    // currently collapses when resizing within any mobile range...\n    // I should update this to remain `expanded` when resizing within that range\n    return windowWidth < MAX_WIDTH ? this.collapse() : this.expand(true);\n  }\n}\n"
  },
  {
    "path": "examples/src/components/MobileNav/props.scss",
    "content": "////\n/// Components\n/// MobileNav props\n////\n\n$mobile-nav-padding-top: get-spacing(looser) * 2;\n"
  },
  {
    "path": "examples/src/components/Navigation/NavList.html",
    "content": "{% macro render(ViewAttr, SubNav) %}\n  {% for Section, Links in SubNav %}\n    {% set listClasses = ['NavList'] %}\n    {% set listClasses = (\n      listClasses.push('NavList--isCurrent') if ViewAttr.parent == Section, listClasses\n    ) %}\n\n    <ul class=\"{{ listClasses | join(' ') | trim }}\">\n      <li class=\"NavItem\">\n        <span class=\"NavHeading\">{{ Section }}</span>\n      </li>\n\n      {% for Link in Links %}\n        {% set linkHref = Link | lower | replace(' ', '-') %}\n        {% set linkClasses = ['NavLink'] %}\n        {% set linkClasses = (\n          linkClasses.push('NavLink--isCurrent') if ViewAttr.child == Link, linkClasses\n        ) %}\n        {% set linkClasses = linkClasses | join(' ') | trim %}\n\n        {% if '~' in Link %}\n          {% set openingTag = '<span class=\"' + linkClasses + '\">' %}\n          {% set closingTag = '</span>' %}\n        {% else %}\n          {% set openingTag = '<a href=\"' + linkHref + '.html\" class=\"' + linkClasses + '\" title=\"See example: ' + Link + '\">' %}\n          {% set closingTag = '</a>' %}\n        {% endif %}\n\n        <li class=\"NavItem\">\n          {{ openingTag | safe }}\n            {{ Link | replace('~', '') }}\n          {{ closingTag | safe }}\n        </li>\n      {% endfor %}\n    </ul>\n  {% endfor %}\n{% endmacro %}\n"
  },
  {
    "path": "examples/src/components/Navigation/Navigation.html",
    "content": "{% import 'components/Navigation/NavList.html' as NavList %}\n\n{% macro render(ViewAttr, DataPages) %}\n  <nav class=\"Navigation\">\n    {% for SubNav in DataPages %}\n      {{ NavList.render(ViewAttr, SubNav) }}\n    {% endfor %}\n  </nav>\n{% endmacro %}\n"
  },
  {
    "path": "examples/src/components/Navigation/Navigation.scss",
    "content": "////\n/// Components\n/// Navigation\n////\n\n.Navigation {\n  padding: get-spacing(tight);\n\n  @media screen and (min-width: get-breakpoint()) {\n    flex: 1; // for sticky footer\n  }\n\n  @media screen and (min-width: get-breakpoint(desktop)) {\n    padding-right: get-spacing(loose);\n    padding-left: get-spacing(loose);\n  }\n}\n\n.NavList {\n  & + & {\n    margin-top: get-spacing();\n\n    @media screen and (min-width: get-breakpoint()) {\n      margin-top: get-spacing(looser);\n    }\n  }\n}\n\n.NavItem {\n  & + & {\n    margin-top: get-spacing(tightest) + $spacing-unit;\n\n    @media screen and (min-width: get-breakpoint()) {\n      margin-top: get-spacing(tighter);\n    }\n  }\n}\n\n.NavHeading,\n.NavLink {\n  font-size: get-type-scale(nav);\n  letter-spacing: get-type-scale(nav, tracking);\n  line-height: get-type-scale(nav, leading);\n\n  @media screen and (min-width: get-breakpoint()) {\n    font-size: get-type-scale(nav, '960');\n  }\n}\n\n.NavHeading {\n  font-weight: get-type-scale(h1, weight);\n  color: white;\n\n  .NavList--isCurrent & {\n    color: get-color(brand, yellow);\n  }\n}\n\n// Use .Link class?\n.NavLink {\n  font-weight: get-type-scale(subheading, weight);\n  color: get-color(coal, light);\n\n  &.NavLink--isCurrent {\n    pointer-events: none;\n    color: get-color(purple);\n  }\n\n  &.NavLink--isDisabled {\n    pointer-events: none;\n    color: get-color(coal, light);\n  }\n}\n\na.NavLink {\n  color: get-color(ash, dark);\n  transition: color get-duration() get-easing();\n\n  &:focus,\n  &:hover {\n    color: get-color(purple);\n  }\n\n  &:active {\n    color: get-color(purple, dark);\n  }\n}\n"
  },
  {
    "path": "examples/src/components/Page/Page.scss",
    "content": "////\n/// Components\n/// Page\n////\n\n@import 'utils/shared/layout';\n\nhtml {\n  @include scroll-lock;\n  font-family: get-font-stack();\n\n  ::selection {\n    color: white;\n    background-color: get-color(coal, dark);\n  }\n}\n\nbody {\n  cursor: get-cursor();\n  position: relative;\n  background-color: white;\n\n  // Better page overscroll... specifically adds color above and below the Sidebar\n  @media screen and (min-width: get-breakpoint()) {\n    &::before,\n    &::after {\n      content: '';\n      position: sticky;\n      left: 0;\n      display: block;\n      width: get-layout-length(sidebar) - get-spacing(fattest);\n      height: 0;\n    }\n\n    &::before {\n      top: 0;\n      box-shadow: 0 -#{get-spacing(fattest)} 0 get-spacing(fattest) get-color(coal, dark);\n    }\n\n    &::after {\n      bottom: 0;\n      box-shadow: 0 get-spacing(fattest) 0 get-spacing(fattest) get-color(coal, dark);\n    }\n  }\n}\n\n///\n/// Cursor styles\np,\nli,\ndd,\n.Heading,\n.Subheading {\n  cursor: get-cursor(text);\n}\n\na,\nbutton {\n  cursor: get-cursor(pointer);\n\n  &:active {\n    cursor: get-cursor(pointer, active);\n  }\n}\n\n.draggable--is-dragging {\n  &,\n  * {\n    cursor: get-cursor(drag, active);\n  }\n}\n\n// Set `state` cursors to never used tags only so they can be fetched on page load,\n// rather than the instance they are first requested...\n.preload-cursors {\n  @include visually-hidden;\n\n  b {\n    cursor: get-cursor(drag, active);\n  }\n\n  u {\n    cursor: get-cursor(pointer, active);\n  }\n\n  a {\n    cursor: get-cursor(rock, active);\n  }\n}\n"
  },
  {
    "path": "examples/src/components/PageHeader/PageHeader.html",
    "content": "{% macro render(ViewAttr) %}\n  {% set contentPath = '' %}\n  {% set contentPath = ViewAttr.parent + '/' if ViewAttr.child %}\n  {% set contentPath = contentPath + ViewAttr.id %}\n\n  <header class=\"PageHeader\">\n    <h1 class=\"Heading Heading--size1\">{{ ViewAttr.parent }}</h1>\n    <h2 class=\"Subheading\">{{ ViewAttr.subheading }}</h2>\n    <a href=\"https://github.com/Shopify/draggable/tree/main/examples/src/content/{{ contentPath }}\" class=\"Link Link--typeUnderlined\" title=\"See this code example\">View code on GitHub</a>\n  </header>\n{% endmacro %}\n"
  },
  {
    "path": "examples/src/components/PageHeader/PageHeader.scss",
    "content": "////\n/// Components\n/// PageHeader\n////\n\n@import 'utils/shared/layout';\n\n.PageHeader {\n  @include centered-width(get-layout-length(page-header));\n  margin-bottom: get-spacing(loose);\n  text-align: center;\n  color: get-color(coal, dark);\n\n  @media screen and (min-width: get-breakpoint(tablet)) {\n    margin-bottom: get-spacing(loosest);\n  }\n\n  @media screen and (min-width: get-breakpoint(desktop)) {\n    margin-bottom: get-spacing(fattest);\n  }\n\n  .Link {\n    margin-top: get-spacing(tight);\n\n    @media screen and (min-width: get-breakpoint(desktop)) {\n      margin-top: get-spacing();\n    }\n  }\n}\n"
  },
  {
    "path": "examples/src/components/PaperStack/PaperStack.scss",
    "content": "////\n/// Components\n/// PaperStack\n////\n\n@import 'utils/shared/layout';\n@import 'props';\n\n.PaperStack {\n  position: relative;\n  margin-right: auto;\n  margin-left: auto;\n  width: paper-stack-item(width);\n  height: paper-stack(height);\n\n  @media screen and (min-width: get-breakpoint(tablet)) {\n    width: paper-stack-item(width, tablet);\n    height: paper-stack(height, tablet);\n  }\n\n  @media screen and (min-width: get-breakpoint('1080p', wide)) {\n    width: paper-stack-item(width, desktop);\n    height: paper-stack(height, desktop);\n  }\n}\n\n.PaperStackItem {\n  position: absolute;\n  left: 0;\n  right: 0;\n  height: 0;\n}\n\n.PaperStackContent {\n  @include flex-center;\n  position: relative;\n  width: 100%;\n  height: paper-stack-item(height);\n  color: white;\n  background-color: get-color(coal, dark);\n  border: get-border() solid get-color(coal, dark);\n  border-top-width: get-border(thick);\n  transform: paper-stack-item(transform);\n  backface-visibility: hidden; // this might not be making any difference...\n\n  @media screen and (min-width: get-breakpoint(tablet)) {\n    height: paper-stack-item(height, tablet);\n  }\n\n  @media screen and (min-width: get-breakpoint('1080p', wide)) {\n    height: paper-stack-item(height, desktop);\n  }\n}\n\n.PaperStackHeading {\n  margin-top: paper-stack-heading(margin);\n  font-size: paper-stack-heading(size);\n  font-weight: paper-stack-heading(weight);\n  line-height: paper-stack-heading(leading);\n\n  @media screen and (min-width: get-breakpoint(tablet)) {\n    font-size: paper-stack-heading(size, tablet);\n  }\n\n  @media screen and (min-width: get-breakpoint('1080p', wide)) {\n    font-size: paper-stack-heading(size, desktop);\n  }\n}\n\n///\n/// Layout\n.PaperStackItem:nth-child(1n + 2) {\n  .PaperStackContent::after {\n    content: '';\n    position: absolute;\n    right: -(paper-stack-shadow(offset));\n    bottom: paper-stack-shadow(bottom);\n    left: -(paper-stack-shadow(offset));\n    display: block;\n    height: paper-stack-shadow(height);\n    background-color: get-color(coal, dark);\n    transition: transform get-duration() get-easing();\n\n    @media screen and (min-width: get-breakpoint(tablet)) {\n      bottom: paper-stack-shadow(bottom, tablet);\n      height: paper-stack-shadow(height, tablet);\n    }\n\n    @media screen and (min-width: get-breakpoint('1080p', wide)) {\n      bottom: paper-stack-shadow(bottom, desktop);\n      height: paper-stack-shadow(height, desktop);\n    }\n  }\n}\n\n// stylelint-disable-next-line no-duplicate-selectors\n.PaperStackItem {\n  @for $i from 1 through $paper-stack-item-count {\n    &:nth-child(#{$i}) {\n      z-index: ($paper-stack-item-count + 1) - $i;\n    }\n  }\n\n  @include paper-stack-item-offset;\n\n  @media screen and (min-width: get-breakpoint(tablet)) {\n    @include paper-stack-item-offset(tablet);\n  }\n\n  @media screen and (min-width: get-breakpoint('1080p', wide)) {\n    @include paper-stack-item-offset(desktop);\n  }\n}\n\n@import 'variants';\n"
  },
  {
    "path": "examples/src/components/PaperStack/PaperStackItem.html",
    "content": "{% macro render(heading, options = {}) %}\n  {% set classes = ['PaperStackItem'] %}\n  {% set classes = (classes.push('PaperStackItem--isDraggable') if options.draggable, classes) %}\n  {% set classes = (classes.push('PaperStackItem--item' + options.index) if options.index, classes) %}\n  {% set classes = (classes.push(options.classes | join(' ')) if options.classes, classes) %}\n  {% set classes = classes | join(' ') | trim %}\n\n  {% set tabIndex = 'tabindex=\"1\"' if options.draggable %}\n\n  <li class=\"{{ classes }}\" {{ tabIndex | safe }}>\n    <div class=\"PaperStackContent\">\n      <h4 class=\"Heading text-no-select PaperStackHeading\">{{ heading }}</h4>\n\n      {% if options.draggable %}\n        <div class=\"Pattern Pattern--typeHalftone\"></div>\n        <div class=\"Pattern Pattern--typePlaced\"></div>\n      {% endif %}\n    </div>\n  </li>\n{% endmacro %}\n"
  },
  {
    "path": "examples/src/components/PaperStack/props.scss",
    "content": "////\n/// Components\n/// PaperStack props\n////\n\n$paper-stack-block-name: unquote('PaperStackItem');\n$paper-stack-item-count: 4;\n\n///\n/// Size calc functions\n@function paper-stack-height($height, $spacing) {\n  @return $height - (($spacing / 2) * ($paper-stack-item-count - 1));\n}\n\n///\n/// Style prop maps\n$paper-stack-item: (\n  spacing: (\n    base: get-spacing(),\n    tablet: get-spacing(loose),\n    desktop: get-spacing(looser),\n  ),\n  width: (\n    base: 24rem,\n    tablet: 42rem,\n    desktop: 60rem,\n  ),\n  height: (\n    base: 38rem,\n    tablet: 58rem,\n    desktop: 78rem,\n  ),\n  transform: (\n    base: perspective(90rem) rotateX(60deg),\n    mirror: perspective(90rem) rotateX(58deg) scale(1.025),\n  ),\n);\n\n@function paper-stack-item($group: $threads-default-value, $variant: $threads-default-value) {\n  @return threads-value-get($paper-stack-item, $group, $variant);\n}\n\n$paper-stack-heading: (\n  size: (\n    base: 7.2rem,\n    tablet: 12rem,\n    desktop: 16rem,\n  ),\n  weight: 700,\n  leading: 1,\n  margin: 0.5em,\n);\n\n@function paper-stack-heading($group: $threads-default-value, $variant: $threads-default-value) {\n  @return threads-value-get($paper-stack-heading, $group, $variant);\n}\n\n$paper-stack-shadow: (\n  bottom: (\n    base: paper-stack-item(spacing) / 2,\n    tablet: paper-stack-item(spacing, tablet) / 2,\n    desktop: paper-stack-item(spacing, desktop) / 3,\n  ),\n  height: (\n    base: paper-stack-item(spacing),\n    tablet: paper-stack-item(spacing, tablet),\n    desktop: paper-stack-item(spacing, desktop),\n  ),\n  offset: (\n    base: 0.8rem,\n  ),\n);\n\n@function paper-stack-shadow($group: $threads-default-value, $variant: $threads-default-value) {\n  @return threads-value-get($paper-stack-shadow, $group, $variant);\n}\n\n$paper-stack: (\n  height: (\n    base: paper-stack-height(paper-stack-item(height), paper-stack-item(spacing)),\n    tablet: paper-stack-height(paper-stack-item(height, tablet), paper-stack-item(spacing, tablet)),\n    desktop:\n      paper-stack-height(paper-stack-item(height, desktop), paper-stack-item(spacing, desktop)),\n  ),\n);\n\n@function paper-stack($group: $threads-default-value, $variant: $threads-default-value) {\n  @return threads-value-get($paper-stack, $group, $variant);\n}\n\n///\n/// Mixins\n@mixin paper-stack-item-offset($breakpoint: $threads-default-value) {\n  $adjusted-top-offset: -((paper-stack-item(width, $breakpoint) / $paper-stack-item-count) +\n        (paper-stack-item(spacing, $breakpoint) / 2));\n\n  @for $i from 1 through $paper-stack-item-count {\n    &:nth-child(#{$i}),\n    &.draggable--original ~ .PaperStackItem:nth-child(#{$i + 1}) {\n      top: $adjusted-top-offset;\n    }\n\n    $adjusted-top-offset: $adjusted-top-offset + paper-stack-item(spacing, $breakpoint);\n  }\n}\n"
  },
  {
    "path": "examples/src/components/PaperStack/variants.scss",
    "content": "////\n/// Components\n/// PaperStack variants\n////\n\n@import 'components/Patterns/props';\n@import 'props';\n\n///\n/// Draggable layout\n.draggable--original:first-child + .PaperStackItem {\n  .PaperStackContent::after {\n    content: none;\n  }\n}\n\n///\n/// Draggable Items\n.PaperStackItem--isDraggable {\n  cursor: get-cursor(drag);\n\n  .PaperStackContent {\n    color: get-color(coal, dark);\n    background-color: white;\n    border-color: currentColor;\n    transition: color get-duration(fast) get-easing(),\n      background-color get-duration(fast) get-easing(), transform get-duration() get-easing();\n  }\n\n  // interaction\n  &:focus,\n  &:hover {\n    .PaperStackContent {\n      color: get-color(brand, blue);\n      transform: paper-stack-item(transform) translateY(get-spacing(tightest));\n\n      &::after {\n        transform: translateY(-(get-spacing(tightest)));\n      }\n    }\n  }\n\n  &.draggable-source--is-dragging {\n    .PaperStackContent {\n      color: get-color(brand, blue);\n\n      .Pattern--typeHalftone {\n        @include pattern-halftone-animated;\n      }\n    }\n  }\n\n  &.draggable-source--placed {\n    .PaperStackContent {\n      .Pattern--typePlaced {\n        @include pattern-placed-animated;\n      }\n    }\n  }\n\n  &.draggable-mirror {\n    z-index: get-z-index(overlay);\n\n    .PaperStackContent {\n      color: white;\n      background-color: get-color(brand, blue);\n      border-color: get-color(brand, blue);\n      transform: paper-stack-item(transform, mirror);\n\n      &::after {\n        content: none;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "examples/src/components/Patterns/Patterns.scss",
    "content": "////\n/// Components\n/// Patterns\n////\n\n@import 'utils/shared/layout';\n@import 'keyframes';\n@import 'props';\n\n:root {\n  --pattern-bg-color: white;\n}\n\n.Pattern {\n  @include visible(false);\n  @include position-cover;\n}\n\n.Pattern--typeStripes {\n  @include stripes-bg;\n}\n\n.Pattern--typeHalftone {\n  background-image: radial-gradient(currentColor 24%, transparent 25%),\n    radial-gradient(currentColor 24%, transparent 25%);\n  background-position: $halftone-bg-position-start;\n  background-size: $halftone-bg-size $halftone-bg-size;\n  animation: halftone get-duration(slow) steps(3) infinite both paused;\n  // transition seems to have no affect... maybe bring back the `fade-in` animation?\n  // transition: opacity get-duration() get-easing(), visibility get-duration() get-easing();\n}\n\n.Pattern--typePlaced {\n  background-color: get-color(brand, blue);\n}\n"
  },
  {
    "path": "examples/src/components/Patterns/keyframes.scss",
    "content": "////\n/// Components\n/// Patterns keyframes\n////\n\n@import 'props';\n\n@keyframes placed {\n  to {\n    transform: scale(0);\n  }\n}\n\n@keyframes halftone {\n  from {\n    background-position: $halftone-bg-position-start;\n  }\n\n  to {\n    background-position: $halftone-bg-position-end;\n  }\n}\n"
  },
  {
    "path": "examples/src/components/Patterns/props.scss",
    "content": "////\n/// Components\n/// Pattern props\n////\n\n@import 'utils/shared/layout';\n\n$stripes-bg-size: 0.8rem;\n$halftone-bg-size: 1.2rem;\n$halftone-bg-position-start: 0 0, ($halftone-bg-size / 2) ($halftone-bg-size / 2);\n$halftone-bg-position-end: ($halftone-bg-size / 2) ($halftone-bg-size / 2),\n  $halftone-bg-size $halftone-bg-size;\n\n@mixin stripes-bg {\n  background-image: repeating-linear-gradient(\n    -45deg,\n    var(--pattern-bg-color) 0%,\n    var(--pattern-bg-color) 40%,\n    currentColor 40%,\n    currentColor 50%,\n    var(--pattern-bg-color) 50%\n  );\n  background-size: $stripes-bg-size $stripes-bg-size;\n}\n\n@mixin pattern-bg-reset {\n  background-image: none;\n  background-color: transparent;\n  background-size: auto auto;\n}\n\n@mixin pattern-halftone-animated {\n  @include visible;\n  animation-play-state: running;\n}\n\n@mixin pattern-placed-animated {\n  @include visible;\n  animation: placed get-duration() get-easing() both;\n}\n"
  },
  {
    "path": "examples/src/components/PillSwitch/PillSwitch.html",
    "content": "{% macro render() %}\n  <article class=\"PillSwitch\">\n    <div class=\"PillSwitchTrack\">\n      <div class=\"Pattern Pattern--typeHalftone\"></div>\n    </div>\n\n    <span class=\"PillSwitchControl\">\n      <p class=\"Heading Heading--sizeJumbo text-no-select\" data-switch-off=\"off\" data-switch-on=\"on\">off</p>\n    </span>\n  </article>\n{% endmacro %}\n"
  },
  {
    "path": "examples/src/components/PillSwitch/PillSwitch.scss",
    "content": "////\n/// Components\n/// PillSwitch\n////\n\n@import 'utils/shared/layout';\n@import 'components/Patterns/props';\n@import 'props';\n\n.PillSwitch {\n  position: relative;\n  margin-right: auto;\n  margin-left: auto;\n  width: pill-switch(size);\n  height: pill-switch(size);\n\n  @media screen and (min-width: get-breakpoint(tablet)) {\n    width: pill-switch(size, tablet);\n    height: pill-switch(size, tablet);\n  }\n}\n\n.PillSwitchTrack {\n  @include pill-switch-track-layout;\n  @include pill-switch-border(get-border());\n  position: absolute;\n  top: 50%;\n  left: 50%;\n  color: get-color(brand, blue);\n  background-color: white;\n  transform: rotate(-45deg);\n  overflow: hidden;\n\n  &::after {\n    content: '';\n    position: absolute;\n    top: -(get-border());\n    right: -(get-border());\n    bottom: -(get-border());\n    left: -(get-border());\n    display: block;\n    background-color: get-color(coal, dark);\n    transition: opacity get-duration(fast) get-easing();\n  }\n\n  @media screen and (min-width: get-breakpoint(tablet)) {\n    @include pill-switch-track-layout('tablet');\n    @include pill-switch-border(get-border(thick));\n  }\n\n  // halftone is always animated and blue to mitigate slight transition inconsistency...\n  // ::after overlays it so its fine\n  .Pattern--typeHalftone {\n    @include pattern-halftone-animated;\n    height: pill-switch(size);\n    transform: rotate(45deg);\n\n    @media screen and (min-width: get-breakpoint(tablet)) {\n      height: pill-switch(size, tablet);\n    }\n  }\n}\n\n.PillSwitchControl {\n  @include flex-center;\n  position: absolute;\n  bottom: 0;\n  left: 0;\n  width: pill-switch(control-size);\n  height: pill-switch(control-size);\n  color: get-color(coal, dark);\n  background-color: white;\n  border: get-border() solid currentColor;\n  border-radius: 50%;\n  transition: color get-duration(fast) get-easing();\n\n  @media screen and (min-width: get-breakpoint(tablet)) {\n    width: pill-switch(control-size, tablet);\n    height: pill-switch(control-size, tablet);\n    border-width: get-border(thick);\n  }\n\n  // visually center\n  .Heading {\n    margin-top: -0.1em;\n    margin-left: -0.05em;\n  }\n\n  &:focus,\n  &:hover,\n  &.draggable-mirror {\n    color: get-color(brand, blue);\n  }\n\n  &:active {\n    color: get-color(brand, blue-dark);\n  }\n\n  &.draggable-source--is-dragging {\n    @include visible(false);\n  }\n}\n\n@import 'variants';\n"
  },
  {
    "path": "examples/src/components/PillSwitch/props.scss",
    "content": "////\n/// Components\n/// PillSwitch props\n////\n\n$pill-switch-size: 27.2rem;\n$pill-switch-size-tablet: 54.8rem;\n\n$pill-switch-control-size: 13rem;\n$pill-switch-control-size-tablet: 32rem;\n\n$pill-switch-radius: $pill-switch-control-size / 2;\n$pill-switch-radius-tablet: $pill-switch-control-size-tablet / 2;\n\n$pill-switch-data: (\n  size: (\n    base: $pill-switch-size,\n    tablet: $pill-switch-size-tablet,\n  ),\n  radius: (\n    base: $pill-switch-radius,\n    tablet: $pill-switch-radius-tablet,\n  ),\n  control-size: (\n    base: $pill-switch-control-size,\n    tablet: $pill-switch-control-size-tablet,\n  ),\n  track-width: (\n    base: $pill-switch-control-size * 2 + $pill-switch-radius,\n    tablet: $pill-switch-control-size-tablet * 2,\n  ),\n);\n\n@function pill-switch($group: $threads-default-value, $variant: $threads-default-value) {\n  @return threads-value-get($pill-switch-data, $group, $variant);\n}\n\n///\n/// Mixins\n@mixin pill-switch-track-layout($breakpoint: 'base') {\n  margin-top: -(pill-switch(radius, $breakpoint));\n  margin-left: -(pill-switch(track-width, $breakpoint) / 2);\n  width: pill-switch(track-width, $breakpoint);\n  height: pill-switch(control-size, $breakpoint);\n  border-radius: pill-switch(radius, $breakpoint);\n}\n\n@mixin pill-switch-border($width) {\n  // cannot use border as the psuedo elements will not overlap\n  box-shadow: inset 0 0 0 $width get-color(brand, blue);\n}\n"
  },
  {
    "path": "examples/src/components/PillSwitch/variants.scss",
    "content": "////\n/// Components\n/// PillSwitch variants\n////\n\n@import 'components/Patterns/props';\n\n.PillSwitch--isOn {\n  .PillSwitchTrack {\n    &::after {\n      opacity: 0;\n    }\n  }\n\n  .PillSwitchControl {\n    top: 0;\n    right: 0;\n    bottom: auto;\n    left: auto;\n    color: white;\n    background-color: get-color(brand, blue);\n    border-color: get-color(brand, blue);\n\n    &:focus,\n    &:hover,\n    &.draggable-mirror {\n      color: get-color(brand, blue);\n      background-color: white;\n    }\n\n    &:active {\n      color: get-color(brand, blue-dark);\n    }\n  }\n}\n"
  },
  {
    "path": "examples/src/components/Plate/Plate.html",
    "content": "{% macro render(index, options = {}) %}\n  {% set classes = ['Plate'] %}\n\n  {% if options.level and (\n    options.level === 'Top' or options.level === 'Middle' or options.level === 'Bottom'\n  ) %}\n    {% set classes = (classes.push('Plate--level' + options.level), classes) %}\n  {% endif %}\n\n  {% set classes = (classes.push('Plate--isDraggable') if options.draggable, classes) %}\n  {% set classes = (classes.push(options.classes | join(' ')) if options.classes, classes) %}\n  {% set classes = classes | join(' ') | trim %}\n\n  {% if options.draggable %}\n    {% set openingTag = '<span class=\"' + classes + '\" title=\"Click to drag\">' %}\n    {% set closingTag = '</span>' %}\n  {% else %}\n    {% set openingTag = '<span class=\"' + classes + '\">' %}\n    {% set closingTag = '</span>' %}\n  {% endif %}\n\n  {{ openingTag | safe }}\n    <div class=\"PlateShadowWrapper\">\n      <div class=\"PlateShadow\"></div>\n    </div>\n\n    <div class=\"PlateContent\">\n      {% if options.heading %}\n        <h2 class=\"Heading Heading--size1 text-no-select\">{{ options.heading }}</h2>\n      {% endif %}\n\n      <h3 class=\"Heading visually-hidden\">{{ index }}</h3>\n    </div>\n  {{ closingTag | safe }}\n{% endmacro %}\n"
  },
  {
    "path": "examples/src/components/Plate/Plate.scss",
    "content": "////\n/// Components\n/// Plate\n////\n\n@import 'utils/shared/layout';\n@import 'keyframes';\n@import 'props';\n\n.PlateWrapper {\n  @include centered-width($plate-max-size);\n  position: relative;\n  padding-top: 100%;\n  height: 0;\n\n  @media screen and (min-width: get-breakpoint('720p', wide)) {\n    padding-top: 0;\n    height: $plate-max-size;\n  }\n}\n\n.Plate {\n  @include position-cover;\n  height: 0;\n  color: white;\n  // required for more accurate :hover hitbox\n  border-radius: 50%;\n}\n\n.PlateShadowWrapper {\n  @include position-cover;\n  pointer-events: none;\n}\n\n.PlateShadow {\n  width: 100%;\n  height: 100%;\n\n  &::before {\n    content: '';\n    position: absolute;\n    top: 0;\n    left: 0;\n    display: block;\n    width: 100%;\n    height: 100%;\n    background-color: get-color(coal, dark);\n    border-radius: 50%;\n  }\n}\n\n.PlateContent {\n  @include position-cover;\n  @include flex-center;\n  background-color: get-color(coal, dark);\n  border-radius: 50%;\n\n  .Heading {\n    margin-bottom: 0;\n  }\n}\n\n@import 'variants';\n"
  },
  {
    "path": "examples/src/components/Plate/index.js",
    "content": "import flipSign from '../../scripts/utils/flip-sign';\n\nconst scaleFactor = 0.725;\nconst translateFactors = {\n  bottom: 0.075,\n  middle: 0.5,\n  top: 0.975,\n};\nconst Classes = {\n  bottom: 'Plate--levelBottom',\n  middle: 'Plate--levelMiddle',\n  top: 'Plate--levelTop',\n};\n\nfunction calculatePlateScale(value, max, factor) {\n  const step1 = Math.abs(value) / max;\n  const step2 = step1 - step1 * factor;\n\n  return 1 - step2;\n}\n\nexport default class Plate {\n  constructor(wrapper) {\n    this.wrapper = wrapper;\n    this.plates = {\n      bottom: wrapper.getElementsByClassName(Classes.bottom)[0],\n      middle: wrapper.getElementsByClassName(Classes.middle)[0],\n      top: wrapper.getElementsByClassName(Classes.top)[0],\n    };\n    this.threshold = {\n      min: -27.2,\n      max: 27.2,\n    };\n    this.initialMousePosition = {\n      x: 0,\n      y: 0,\n    };\n  }\n\n  setThreshold() {\n    const newThreshold = this.wrapper.offsetWidth / 10;\n\n    this.threshold = {\n      min: flipSign(newThreshold),\n      max: newThreshold,\n    };\n  }\n\n  setInitialMousePosition(sensorEvent) {\n    this.initialMousePosition.x = sensorEvent.clientX;\n    this.initialMousePosition.y = sensorEvent.clientY;\n  }\n\n  dragWarp(source, sensorEvent) {\n    const adjustedX = this._offsetWithinThreshold(this.initialMousePosition.x, sensorEvent.clientX);\n    const adjustedY = this._offsetWithinThreshold(this.initialMousePosition.y, sensorEvent.clientY);\n\n    this._scalePlates(adjustedX, adjustedY);\n    this._translateShadow(adjustedX, adjustedY);\n    this._translateEachPlate(adjustedX, adjustedY);\n  }\n\n  resetWarp() {\n    this._scalePlates(0, 0);\n    this._translateShadow(0, 0);\n    this._translateEachPlate(0, 0);\n  }\n\n  _offsetWithinThreshold(initialPosition, currentPosition) {\n    const updatedPosition = initialPosition - currentPosition;\n    let offset = updatedPosition;\n\n    if (updatedPosition < this.threshold.min) {\n      offset = this.threshold.min;\n    } else if (updatedPosition > this.threshold.max) {\n      offset = this.threshold.max;\n    }\n\n    return offset;\n  }\n\n  _scalePlates(x, y) {\n    const scaleX = calculatePlateScale(x, this.threshold.max, scaleFactor);\n    const scaleY = calculatePlateScale(y, this.threshold.max, scaleFactor);\n\n    this.wrapper.style.setProperty('--plate-scale-x', `${scaleX}`);\n    this.wrapper.style.setProperty('--plate-scale-y', `${scaleY}`);\n  }\n\n  _translateEachPlate(x, y) {\n    for (const plateLevel in this.plates) {\n      // eslint-disable-next-line no-prototype-builtins\n      if (this.plates.hasOwnProperty(plateLevel)) {\n        const translateX = flipSign(x * 2) * translateFactors[plateLevel];\n        const translateY = flipSign(y * 2) * translateFactors[plateLevel];\n\n        this.wrapper.style.setProperty(`--${plateLevel}-translate-x`, `${translateX}px`);\n        this.wrapper.style.setProperty(`--${plateLevel}-translate-y`, `${translateY}px`);\n      }\n    }\n  }\n\n  _translateShadow(x, y) {\n    this.wrapper.style.setProperty('--shadow-offset-x', `${x / 2}px`);\n    this.wrapper.style.setProperty('--shadow-offset-y', `${y / 2}px`);\n  }\n}\n"
  },
  {
    "path": "examples/src/components/Plate/keyframes.scss",
    "content": "////\n/// Components\n/// Plate keyframes\n////\n\n@keyframes plate-small {\n  0%,\n  100% {\n    transform: scale(1, 1);\n  }\n\n  10% {\n    transform: scale(1.1, 0.96);\n  }\n\n  25% {\n    transform: scale(0.925, 1.075);\n  }\n\n  50% {\n    transform: scale(1.0125, 0.975);\n  }\n\n  75% {\n    transform: scale(0.975, 1.0125);\n  }\n\n  95% {\n    transform: scale(1.005, 0.995);\n  }\n}\n\n@keyframes plate-medium {\n  0%,\n  100% {\n    transform: scale(1, 1);\n  }\n\n  10% {\n    transform: scale(1.15, 0.95);\n  }\n\n  25% {\n    transform: scale(0.9, 1.1);\n  }\n\n  50% {\n    transform: scale(1.025, 0.96);\n  }\n\n  75% {\n    transform: scale(0.98, 1.025);\n  }\n\n  95% {\n    transform: scale(1.01, 0.985);\n  }\n}\n\n@keyframes plate-big {\n  0%,\n  100% {\n    transform: scale(1, 1);\n  }\n\n  10% {\n    transform: scale(1.2, 0.9);\n  }\n\n  25% {\n    transform: scale(0.85, 1.15);\n  }\n\n  50% {\n    transform: scale(1.075, 0.95);\n  }\n\n  75% {\n    transform: scale(0.975, 1.05);\n  }\n\n  95% {\n    transform: scale(1.0125, 0.98);\n  }\n}\n\n@keyframes plate-placed {\n  0%,\n  100% {\n    color: get-color(coal, dark);\n  }\n\n  50% {\n    color: get-color(brand, blue);\n  }\n}\n"
  },
  {
    "path": "examples/src/components/Plate/props.scss",
    "content": "////\n/// Components\n/// Plate props\n////\n\n$plate-max-size: 80rem;\n"
  },
  {
    "path": "examples/src/components/Plate/variants.scss",
    "content": "////\n/// Components\n/// Plate variants\n////\n\n///\n/// Level variant\n.Plate--levelBottom {\n  padding-top: 100%;\n  width: 100%;\n\n  .PlateShadow {\n    transform: rotate(55deg);\n  }\n}\n\n.Plate--levelMiddle {\n  padding-top: 75%;\n  width: 75%;\n\n  .PlateShadow {\n    transform: rotate(45deg);\n  }\n}\n\n.Plate--levelTop {\n  padding-top: 50%;\n  width: 50%;\n\n  .PlateShadow {\n    transform: rotate(35deg);\n  }\n}\n\n///\n/// Draggable variant\n.PlateWrapper {\n  --shadow-offset-x: 0;\n  --shadow-offset-y: 0;\n  --plate-scale-x: 1;\n  --plate-scale-y: 1;\n  --bottom-translate-x: 0;\n  --bottom-translate-y: 0;\n  --middle-translate-x: 0;\n  --middle-translate-y: 0;\n  --top-translate-x: 0;\n  --top-translate-y: 0;\n\n  &.draggable-container--placed {\n    .Plate--levelBottom,\n    .Plate--levelMiddle,\n    .Plate--levelTop {\n      animation-name: plate-placed;\n      animation-duration: get-duration();\n      animation-timing-function: get-easing();\n    }\n\n    .Plate--levelBottom {\n      animation-delay: get-duration() / 2;\n    }\n\n    .Plate--levelMiddle {\n      animation-delay: get-duration() / 4;\n    }\n  }\n}\n\n.Plate--isDraggable {\n  color: get-color(coal, dark);\n  // helps smooth out on some devices: + transform get-duration(faster) linear\n  transition: color get-duration(fast) get-easing();\n\n  &:focus,\n  &:hover {\n    color: get-color(brand, blue);\n  }\n\n  .PlateShadowWrapper {\n    transform: translate3d(var(--shadow-offset-x), var(--shadow-offset-y), 0);\n  }\n\n  .PlateShadow::before {\n    background-color: currentColor;\n    animation-duration: get-duration(slow);\n    animation-timing-function: get-easing();\n  }\n\n  .PlateContent {\n    background-color: white;\n    border: get-border(flexible) solid currentColor;\n    animation-duration: get-duration(slow);\n    animation-timing-function: get-easing();\n\n    @media screen and (min-width: get-breakpoint()) {\n      border-width: get-border(thick);\n    }\n  }\n\n  &.Plate--levelBottom {\n    transform: translate3d(var(--bottom-translate-x), var(--bottom-translate-y), 0)\n      scale(var(--plate-scale-x), var(--plate-scale-y));\n\n    &:focus,\n    &:hover {\n      .PlateContent,\n      .PlateShadow::before {\n        animation-name: plate-small;\n      }\n    }\n  }\n\n  &.Plate--levelMiddle {\n    transform: translate3d(var(--middle-translate-x), var(--middle-translate-y), 0)\n      scale(var(--plate-scale-x), var(--plate-scale-y));\n\n    &:focus,\n    &:hover {\n      .PlateContent,\n      .PlateShadow::before {\n        animation-name: plate-medium;\n      }\n    }\n  }\n\n  &.Plate--levelTop {\n    transform: translate3d(var(--top-translate-x), var(--top-translate-y), 0)\n      scale(var(--plate-scale-x), var(--plate-scale-y));\n\n    &:focus,\n    &:hover {\n      .PlateContent,\n      .PlateShadow::before {\n        animation-name: plate-big;\n      }\n    }\n  }\n\n  // would be nice if there was a better way to suppress the creation of the mirror\n  &.draggable-mirror {\n    display: none;\n  }\n}\n\n.draggable-container--placed {\n  .Plate--levelBottom {\n    .PlateContent,\n    .PlateShadow::before {\n      animation-name: plate-small;\n    }\n  }\n\n  .Plate--levelMiddle {\n    .PlateContent,\n    .PlateShadow::before {\n      animation-name: plate-medium;\n    }\n  }\n\n  .Plate--levelTop {\n    .PlateContent,\n    .PlateShadow::before {\n      animation-name: plate-big;\n    }\n  }\n}\n"
  },
  {
    "path": "examples/src/components/Sidebar/Sidebar.html",
    "content": "{% import 'components/Navigation/Navigation.html' as Navigation %}\n\n{% macro render(ViewAttr, DataPages) %}\n  <aside id=\"Sidebar\" class=\"Sidebar\" aria-hidden=\"true\">\n    <header class=\"SidebarHeader\">\n      {% include 'components/Brand/Brand.html' %}\n    </header>\n\n    {{ Navigation.render(ViewAttr, DataPages) }}\n\n    <footer class=\"SidebarFooter\">\n      <p class=\"LegalText\"><a href=\"https://shopify.github.io/draggable/\" class=\"Link Link--typeDark\" title=\"Visit Draggable JS\">draggable.js</a> was developed by <a href=\"https://github.com/tsov\" class=\"Link Link--noWrap\" title=\"Github: tsov\">Max Hoffmann</a> and <a href=\"https://github.com/beefchimi\" class=\"Link Link--noWrap\" title=\"Github: beefchimi\">Curtis Dulmage</a>.</p>\n\n      <p class=\"LegalText\">Draggable is released under the <a href=\"https://github.com/Shopify/shopify.github.com/blob/main/LICENSE.md\" class=\"Link Link--noWrap\" title=\"View MIT License\" target=\"_blank\" rel=\"noopener\">MIT license</a>. You are free to use the code from this library for both personal and commercial use.</p>\n\n      <p class=\"preload-cursors\"><b>Special thanks</b> <u>to all of our</u> <a href=\"https://github.com/Shopify/draggable/graphs/contributors\" title=\"We love our contributors!\">amazing contributors</a>.</p>\n    </footer>\n  </aside>\n{% endmacro %}\n"
  },
  {
    "path": "examples/src/components/Sidebar/Sidebar.scss",
    "content": "////\n/// Components\n/// Sidebar\n////\n\n@import 'utils/shared/layout';\n@import 'components/MobileNav/props';\n\n.Sidebar {\n  @include position-cover;\n  z-index: get-z-index(sidebar);\n  background-color: get-color(coal, dark);\n  overflow-y: scroll;\n  -webkit-overflow-scrolling: touch;\n\n  @media screen and (max-width: get-breakpoint() - 1px) {\n    text-align: center;\n    transition: opacity get-duration() get-easing(), visibility get-duration() get-easing();\n\n    &::before {\n      content: '';\n      position: fixed;\n      top: 0;\n      left: 0;\n      right: 0;\n      display: block;\n      height: $mobile-nav-padding-top;\n      background: linear-gradient(\n        rgba(get-color(coal, dark), 0.9) 40%,\n        rgba(get-color(coal, dark), 0)\n      );\n    }\n\n    // protect against resizing visibility\n    &[aria-hidden='undefined'] {\n      display: none;\n    }\n\n    &[aria-hidden='true'] {\n      @include visible(false);\n    }\n  }\n\n  @media screen and (min-width: get-breakpoint()) {\n    position: fixed;\n    right: auto;\n    display: flex; // for sticky footer\n    flex-direction: column; // for sticky footer\n    width: get-layout-length(sidebar);\n  }\n}\n\n.SidebarHeader {\n  padding: $mobile-nav-padding-top get-spacing(tight) get-spacing(tight);\n\n  @media screen and (min-width: get-breakpoint()) {\n    padding-top: get-spacing(looser);\n  }\n\n  @media screen and (min-width: get-breakpoint(desktop)) {\n    padding-top: get-spacing(fat);\n    padding-right: get-spacing(loose);\n    padding-left: get-spacing(loose);\n  }\n}\n\n.SidebarFooter {\n  padding: get-spacing(loose) get-spacing(tight);\n\n  @media screen and (min-width: get-breakpoint(desktop)) {\n    padding: get-spacing(fat) get-spacing(loose);\n  }\n}\n\n.LegalText {\n  font-size: get-type-scale(footer);\n  font-weight: get-type-scale(footer, weight);\n  line-height: get-type-scale(footer, leading);\n  color: white;\n\n  & + & {\n    margin-top: get-spacing(tighter);\n  }\n\n  @media screen and (max-width: get-breakpoint() - 1px) {\n    margin-left: auto;\n    margin-right: auto;\n    max-width: get-layout-length(sidebar);\n  }\n}\n"
  },
  {
    "path": "examples/src/components/StackedList/StackedList.scss",
    "content": "////\n/// Components\n/// StackedList\n////\n\n@import 'utils/shared/layout';\n@import 'utils/shared/typography';\n@import 'components/Handle/props';\n@import 'components/Heading/props';\n@import 'components/Patterns/props';\n@import 'props';\n\n.StackedListWrapper {\n  @include stripes-bg;\n  position: relative;\n  color: get-color(coal, dark);\n  box-shadow: inset 0 0 0 get-border(thin) currentColor;\n  // `draggable-container-parent--capacity` changes the `color` on `StackedListWrapper`...\n  // This is supposed to \"trickle\" down and cause all children to transition color as well...\n  // Problem is, sometimes these transitions seem to collide and we don't get syncronised timing...\n  // Sadly, there is no way to target `box-shadow`'s color without the use of currentColor.\n  // transition: color get-duration(fast) get-easing();\n\n  @media screen and (min-width: get-breakpoint(tablet)) {\n    box-shadow: inset 0 0 0 get-border() currentColor;\n  }\n}\n\n.StackedListHeader,\n.StackedListContent {\n  // cannot be `min-height`, as a child's `height: 100%` will not work\n  // by using `height`, we will get visible scrollbars\n  height: stacked-list-item();\n\n  @media screen and (min-width: get-breakpoint(desktop)) {\n    height: stacked-list-item(base, desktop);\n  }\n}\n\n.StackedListHeader {\n  padding: stacked-list-header(padding);\n  background-color: currentColor;\n  transition: color get-duration(fast) get-easing();\n\n  @media screen and (min-width: get-breakpoint(tablet)) {\n    padding: stacked-list-header(padding, tablet);\n  }\n\n  p {\n    margin-top: 0.2em;\n    font-size: get-type-scale(paragraph);\n    color: white;\n\n    @media screen and (min-width: get-breakpoint(tablet)) {\n      font-size: get-type-scale(paragraph, tablet);\n    }\n\n    @media screen and (min-width: get-breakpoint(desktop)) {\n      font-size: get-type-scale(paragraph, desktop);\n    }\n  }\n}\n\n.StackedList {\n  position: relative;\n  margin-top: -(get-border(thin));\n\n  @media screen and (min-width: get-breakpoint(tablet)) {\n    margin-top: -(get-border());\n  }\n\n  &::before {\n    @include visible(false);\n    @include position-cover;\n    @include Heading;\n    @include HeadingSize4;\n    @include text-no-select;\n    // stylelint-disable-next-line shopify/content-no-strings\n    content: 'drop items here';\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    text-align: center;\n    width: 80%;\n    height: 80%;\n    background-color: white;\n    transition: opacity get-duration() get-easing(), visibility get-duration() get-easing();\n  }\n\n  &:empty {\n    &::before {\n      @include visible;\n    }\n  }\n}\n\n.StackedListItem {\n  cursor: get-cursor();\n\n  &:nth-child(1n + 2) {\n    margin-top: -(get-border(thin));\n\n    @media screen and (min-width: get-breakpoint(tablet)) {\n      margin-top: -(get-border());\n    }\n  }\n\n  .draggable--original:first-child + & {\n    margin-top: 0;\n  }\n}\n\n.StackedListContent {\n  position: relative;\n  display: flex;\n  align-items: center;\n  padding: get-spacing(tighter);\n  color: currentColor;\n  background-color: get-color(ash, light);\n  border: get-border(thin) solid currentColor;\n  transition: color get-duration(fast) get-easing(),\n    background-color get-duration(fast) get-easing(), transform get-duration() get-easing(bungie);\n\n  @media screen and (min-width: get-breakpoint(tablet)) {\n    border-width: get-border();\n  }\n\n  .Heading {\n    flex: 1 1 auto;\n  }\n\n  .DragHandle,\n  .NopeHandle {\n    flex: 0 0 $handle-size;\n    margin-left: get-spacing(tight);\n  }\n\n  // top border psuedo element... solution to create a collapsed border\n  // required to make the hover style highlight each side of the element\n  &::before {\n    content: '';\n    position: absolute;\n    top: -(get-border(thin));\n    right: -(get-border(thin));\n    left: -(get-border(thin));\n    display: block;\n    height: get-border(thin);\n    background-color: currentColor;\n    opacity: 0;\n    transition: color get-duration(fast) get-easing(), opacity get-duration(fast) get-easing();\n\n    @media screen and (min-width: get-breakpoint(tablet)) {\n      top: -(get-border());\n      right: -(get-border());\n      left: -(get-border());\n      height: get-border();\n    }\n  }\n}\n\n@import 'variants';\n"
  },
  {
    "path": "examples/src/components/StackedList/StackedListItem.html",
    "content": "{% macro render(heading, options = {}) %}\n  {% set classes = ['StackedListItem'] %}\n  {% set classes = (classes.push('StackedListItem--isDraggable') if options.draggable, classes) %}\n  {% set classes = (classes.push('StackedListItem--item' + options.index) if options.index, classes) %}\n  {% set classes = (classes.push(options.classes | join(' ')) if options.classes, classes) %}\n  {% set classes = classes | join(' ') | trim %}\n\n  {% set tabIndex = 'tabindex=\"1\"' if options.draggable %}\n  {% set iconClass = 'DragHandle' if options.draggable else 'NopeHandle' %}\n\n  <li class=\"{{ classes }}\" {{ tabIndex | safe }}>\n    <div class=\"StackedListContent\">\n      <h4 class=\"Heading Heading--size4 text-no-select\">{{ heading }}</h4>\n      <div class=\"{{ iconClass }}\"></div>\n\n      {% if options.draggable %}\n        <div class=\"Pattern Pattern--typeHalftone\"></div>\n        <div class=\"Pattern Pattern--typePlaced\"></div>\n      {% endif %}\n    </div>\n  </li>\n{% endmacro %}\n"
  },
  {
    "path": "examples/src/components/StackedList/props.scss",
    "content": "////\n/// Components\n/// StackedList props\n////\n\n$stacked-list-header: (\n  padding: (\n    base: get-spacing(tighter) + get-border(thin),\n    tablet: get-spacing(tighter) + get-border(),\n  ),\n);\n\n@function stacked-list-header($group: $threads-default-value, $variant: $threads-default-value) {\n  @return threads-value-get($stacked-list-header, $group, $variant);\n}\n\n// using static values instead of `rows()`\n// will consider switching later when I have more time\n$stacked-list-item: (\n  base: (\n    base: 7.2rem,\n    desktop: 8.6rem,\n  ),\n  medium: (\n    base: 10rem,\n    desktop: 16.4rem,\n  ),\n  large: (\n    base: 14.4rem,\n    desktop: 20.6rem,\n  ),\n);\n\n@function stacked-list-item($group: $threads-default-value, $variant: $threads-default-value) {\n  @return threads-value-get($stacked-list-item, $group, $variant);\n}\n\n///\n/// Mixins\n@mixin stacked-list-scroll-height($size: stacked-list-item(), $rows: 6, $border-width: thin) {\n  height: ($size * $rows) - (get-border($border-width) * ($rows - 1));\n}\n\n@mixin scroll-indicator($vertical: true) {\n  content: '';\n  position: absolute;\n  display: block;\n  background-color: currentColor;\n  transition: color get-duration(fast) get-easing(), opacity get-duration() get-easing();\n\n  @if $vertical {\n    right: 0;\n    bottom: 0;\n    left: 0;\n    height: get-border(thin);\n  } @else {\n    top: 0;\n    right: 0;\n    bottom: 0;\n    width: get-border(thin);\n  }\n\n  @media screen and (min-width: get-breakpoint(tablet)) {\n    @if $vertical {\n      height: get-border();\n    } @else {\n      width: get-border();\n    }\n  }\n}\n"
  },
  {
    "path": "examples/src/components/StackedList/variants.scss",
    "content": "////\n/// Components\n/// StackedList variants\n////\n\n@import 'components/Handle/props';\n@import 'components/Patterns/props';\n@import 'props';\n\n///\n/// Size variants\n.StackedListWrapper--sizeMedium {\n  .StackedListHeader,\n  .StackedListContent {\n    height: stacked-list-item(medium);\n\n    @media screen and (min-width: get-breakpoint(desktop)) {\n      height: stacked-list-item(medium, desktop);\n    }\n  }\n}\n\n.StackedListWrapper--sizeLarge {\n  .StackedListHeader,\n  .StackedListContent {\n    height: stacked-list-item(large);\n\n    @media screen and (min-width: get-breakpoint(desktop)) {\n      height: stacked-list-item(large, desktop);\n    }\n  }\n}\n\n///\n/// Vertical scroll variant\n/// Not to be paired with --axisHorizontal\n.StackedListWrapper--hasScrollIndicator {\n  &::after {\n    @include scroll-indicator;\n  }\n\n  // does not address :focus on child elements\n  &:hover {\n    &::after {\n      opacity: 0.1;\n    }\n  }\n}\n\n.StackedList--hasScroll {\n  @include stacked-list-scroll-height;\n  overflow-y: scroll;\n\n  @media screen and (min-width: get-breakpoint(desktop)) {\n    @include stacked-list-scroll-height(stacked-list-item(base, desktop), 7, base);\n  }\n\n  // Size variants\n\n  .StackedListWrapper--sizeMedium & {\n    @include stacked-list-scroll-height(stacked-list-item(medium), 3);\n\n    @media screen and (min-width: get-breakpoint(desktop)) {\n      @include stacked-list-scroll-height(stacked-list-item(medium, desktop), 3, base);\n    }\n  }\n\n  .StackedListWrapper--sizeLarge & {\n    @include stacked-list-scroll-height(stacked-list-item(large), 3);\n\n    @media screen and (min-width: get-breakpoint(desktop)) {\n      @include stacked-list-scroll-height(stacked-list-item(large, desktop), 3, base);\n    }\n  }\n}\n\n///\n/// Axis variant\n/// Has horizontal scrolling built-in\n.StackedListWrapper--axisHorizontal {\n  display: flex;\n\n  &::after {\n    @include scroll-indicator(false);\n  }\n\n  // does not address :focus on child elements\n  &:hover {\n    &::after {\n      opacity: 0.1;\n    }\n  }\n\n  .StackedListHeader,\n  .StackedListContent {\n    height: stacked-list-item();\n\n    @media screen and (min-width: get-breakpoint(tablet)) {\n      height: stacked-list-item(base, desktop);\n    }\n  }\n\n  .StackedListHeader {\n    flex: 0 0 stacked-list-item(large);\n\n    @media screen and (min-width: get-breakpoint(tablet)) {\n      flex-basis: stacked-list-item(large, desktop);\n    }\n  }\n\n  .StackedList {\n    display: flex;\n    flex: 1 1 auto;\n    margin-top: 0;\n    margin-left: -(get-border(thin));\n    overflow-x: scroll;\n\n    @media screen and (min-width: get-breakpoint(tablet)) {\n      margin-left: -(get-border());\n    }\n  }\n\n  .StackedListItem {\n    flex: 1 0 stacked-list-item(large);\n    max-width: stacked-list-item(large) * 2;\n\n    &:nth-child(1n + 2) {\n      margin-top: 0;\n      margin-left: -(get-border(thin));\n\n      @media screen and (min-width: get-breakpoint(tablet)) {\n        margin-left: -(get-border());\n      }\n    }\n\n    @media screen and (min-width: get-breakpoint(tablet)) {\n      flex-basis: stacked-list-item(large, desktop);\n      max-width: stacked-list-item(large, desktop) * 2;\n    }\n  }\n\n  .draggable--original:first-child + .StackedListItem {\n    margin-left: 0;\n  }\n\n  .StackedListContent {\n    flex-direction: column;\n    justify-content: center;\n    text-align: center;\n\n    &::before {\n      top: -(get-border(thin));\n      right: auto;\n      bottom: -(get-border(thin));\n      left: -(get-border(thin));\n      width: get-border(thin);\n      height: auto;\n\n      @media screen and (min-width: get-breakpoint(tablet)) {\n        top: -(get-border());\n        bottom: -(get-border());\n        left: -(get-border());\n        width: get-border();\n      }\n    }\n  }\n\n  .DragHandle,\n  .NopeHandle {\n    margin-left: 0;\n  }\n\n  &.StackedListWrapper--sizeMedium {\n    .StackedListHeader,\n    .StackedListContent {\n      height: stacked-list-item(medium);\n\n      @media screen and (min-width: get-breakpoint(tablet)) {\n        height: stacked-list-item(medium, desktop);\n      }\n    }\n  }\n\n  &.StackedListWrapper--sizeLarge {\n    .StackedListHeader,\n    .StackedListContent {\n      height: stacked-list-item(large);\n\n      @media screen and (min-width: get-breakpoint(tablet)) {\n        height: stacked-list-item(large, desktop);\n      }\n    }\n  }\n}\n\n///\n/// Draggable variant\n.StackedListItem--isDraggable {\n  cursor: get-cursor(drag);\n\n  .StackedListContent {\n    background-color: white;\n  }\n\n  // interaction\n  &:focus {\n    outline: none;\n  }\n\n  &:focus,\n  &:hover {\n    .StackedListContent {\n      color: get-color(brand, blue);\n    }\n\n    + .StackedListItem {\n      .StackedListContent::before {\n        background-color: get-color(brand, blue);\n        opacity: 1;\n      }\n    }\n  }\n\n  &.draggable-source--is-dragging {\n    .StackedListContent {\n      color: get-color(brand, blue);\n\n      .Pattern--typeHalftone {\n        @include pattern-halftone-animated;\n      }\n    }\n\n    + .StackedListItem,\n    + .draggable--original + .StackedListItem {\n      // stylelint-disable-next-line selector-max-class\n      .StackedListContent::before {\n        background-color: get-color(brand, blue);\n        opacity: 1;\n      }\n    }\n  }\n\n  &.draggable-source--placed {\n    .StackedListContent {\n      .Pattern--typePlaced {\n        @include pattern-placed-animated;\n      }\n    }\n  }\n\n  &.draggable-mirror {\n    z-index: get-z-index(overlay);\n    transition: width get-duration() get-easing(bungie), height get-duration() get-easing(bungie);\n\n    .StackedListContent {\n      height: 100%;\n      color: white;\n      background-color: get-color(brand, blue);\n      border-color: get-color(brand, blue);\n      transform: scale(1.025);\n\n      &::before,\n      &::after {\n        display: none;\n      }\n    }\n\n    .DragHandle {\n      @include drag-handle-dragging;\n    }\n  }\n}\n"
  },
  {
    "path": "examples/src/components/Svg/Svg.scss",
    "content": "////\n/// Components\n/// Svg\n////\n\na.SvgContainer {\n  display: block;\n  color: black; // default to black\n}\n\n.SvgContainer {\n  .Svg {\n    width: 100%;\n    height: 100%;\n    overflow: visible;\n  }\n\n  .Svg--heightAuto {\n    height: auto;\n  }\n}\n\n.Svg {\n  fill: currentColor;\n}\n"
  },
  {
    "path": "examples/src/content/Draggable/DragEvents/DragEvents.html",
    "content": "{% import 'components/PillSwitch/PillSwitch.html' as PillSwitch %}\n\n{% macro render(id) %}\n  <section id=\"{{ id }}\" class=\"{{ id }}\">\n    {{ PillSwitch.render() }}\n  </section>\n{% endmacro %}\n"
  },
  {
    "path": "examples/src/content/Draggable/DragEvents/DragEvents.scss",
    "content": "////\n/// Content\n/// DragEvents\n////\n\n.DragEvents {\n  // styles\n}\n"
  },
  {
    "path": "examples/src/content/Draggable/DragEvents/index.js",
    "content": "// eslint-disable-next-line import/no-unresolved\nimport {Draggable} from '@shopify/draggable';\n\nfunction translateMirror(mirror, mirrorCoords, containerRect) {\n  if (mirrorCoords.top < containerRect.top || mirrorCoords.left < containerRect.left) {\n    return;\n  }\n\n  requestAnimationFrame(() => {\n    mirror.style.transform = `translate3d(${mirrorCoords.left}px, ${mirrorCoords.top}px, 0)`;\n  });\n}\n\nfunction calcOffset(offset) {\n  return offset * 2 * 0.5;\n}\n\nexport default function DragEvents() {\n  const toggleClass = 'PillSwitch--isOn';\n  const containers = document.querySelectorAll('#DragEvents .PillSwitch');\n\n  if (containers.length === 0) {\n    return false;\n  }\n\n  const draggable = new Draggable(containers, {\n    draggable: '.PillSwitchControl',\n    delay: 0,\n  });\n\n  let isToggled = false;\n  let initialMousePosition;\n  let containerRect;\n  let dragRect;\n  let dragThreshold;\n  let headings;\n  let headingText;\n\n  // --- Draggable events --- //\n  draggable.on('drag:start', (evt) => {\n    initialMousePosition = {\n      x: evt.sensorEvent.clientX,\n      y: evt.sensorEvent.clientY,\n    };\n  });\n\n  draggable.on('mirror:created', (evt) => {\n    containerRect = evt.sourceContainer.getBoundingClientRect();\n    dragRect = evt.source.getBoundingClientRect();\n\n    const containerRectQuarter = containerRect.width / 4;\n    dragThreshold = isToggled ? containerRectQuarter * -1 : containerRectQuarter;\n    headings = {\n      source: evt.originalSource.querySelector('[data-switch-on]'),\n      mirror: evt.mirror.querySelector('[data-switch-on]'),\n    };\n    headingText = {\n      on: headings.source.dataset.switchOn,\n      off: headings.source.dataset.switchOff,\n    };\n  });\n\n  draggable.on('mirror:move', (evt) => {\n    // Required to help restrict the draggable element to the container\n    evt.cancel();\n\n    // We do not want to use `getBoundingClientRect` while dragging,\n    // as that would be very expensive.\n    // Instead, we look at the mouse position, which we can ballpark as being\n    // close to the center of the draggable element.\n    // We need to look at both the X and Y offset and determine which is the higher number.\n    // That way we can drag outside of the container and still have the\n    // draggable element move appropriately.\n    const offsetX = calcOffset(evt.sensorEvent.clientX - initialMousePosition.x);\n    const offsetY = calcOffset(initialMousePosition.y - evt.sensorEvent.clientY);\n    const offsetValue = offsetX > offsetY ? offsetX : offsetY;\n    const mirrorCoords = {\n      top: dragRect.top - offsetValue,\n      left: dragRect.left + offsetValue,\n    };\n\n    translateMirror(evt.mirror, mirrorCoords, containerRect);\n\n    if (isToggled && offsetValue < dragThreshold) {\n      evt.sourceContainer.classList.remove(toggleClass);\n      headings.source.textContent = headingText.off;\n      headings.mirror.textContent = headingText.off;\n      isToggled = false;\n    } else if (!isToggled && offsetValue > dragThreshold) {\n      evt.sourceContainer.classList.add(toggleClass);\n      headings.source.textContent = headingText.on;\n      headings.mirror.textContent = headingText.on;\n      isToggled = true;\n    }\n  });\n\n  const triggerMouseUpOnESC = (evt) => {\n    if (evt.key === 'Escape') {\n      draggable.cancel();\n    }\n  };\n\n  draggable.on('drag:start', () => {\n    document.addEventListener('keyup', triggerMouseUpOnESC);\n  });\n\n  return draggable;\n}\n"
  },
  {
    "path": "examples/src/content/Droppable/UniqueDropzone/UniqueDropzone.html",
    "content": "{% import 'components/Block/Block.html' as Block %}\n\n{% macro render(id) %}\n  <section id=\"{{ id }}\" class=\"{{ id }}\">\n    <article class=\"BlockLayout BlockLayout--typeFlex\">\n      <div class=\"BlockWrapper BlockWrapper--isDropzone draggable-dropzone--occupied\" data-dropzone=\"1\">\n        {{ Block.render('one', {type: 'Hollow'}) }}\n        {{ Block.render('one', {index: 1, draggable: true}) }}\n      </div>\n      <div class=\"BlockWrapper BlockWrapper--isDropzone draggable-dropzone--occupied\" data-dropzone=\"2\">\n        {{ Block.render('two', {type: 'Hollow'}) }}\n        {{ Block.render('two', {index: 2, draggable: true}) }}\n      </div>\n      <div class=\"BlockWrapper BlockWrapper--isDropzone draggable-dropzone--occupied\" data-dropzone=\"4\">\n        {{ Block.render('four', {type: 'Hollow'}) }}\n        {{ Block.render('four', {index: 4, draggable: true}) }}\n      </div>\n      <div class=\"BlockWrapper BlockWrapper--isDropzone draggable-dropzone--occupied\" data-dropzone=\"8\">\n        {{ Block.render('eight', {type: 'Hollow'}) }}\n        {{ Block.render('eight', {index: 8, draggable: true}) }}\n      </div>\n    </article>\n\n    <article class=\"BlockLayout BlockLayout--typeGrid\">\n      <div class=\"BlockWrapper BlockWrapper--isDropzone\" data-dropzone=\"1\">\n        {{ Block.render('', {type: 'Stripes'}) }}\n      </div>\n      <div class=\"BlockWrapper BlockWrapper--isDropzone\" data-dropzone=\"2\">\n        {{ Block.render('', {type: 'Stripes'}) }}\n      </div>\n      <div class=\"BlockWrapper\">\n        {{ Block.render('three', {index: 3, type: 'Shell'}) }}\n      </div>\n      <div class=\"BlockWrapper BlockWrapper--isDropzone\" data-dropzone=\"4\">\n        {{ Block.render('', {type: 'Stripes'}) }}\n      </div>\n      <div class=\"BlockWrapper\">\n        {{ Block.render('five', {index: 5, type: 'Shell'}) }}\n      </div>\n      <div class=\"BlockWrapper\">\n        {{ Block.render('six', {index: 6, type: 'Shell'}) }}\n      </div>\n      <div class=\"BlockWrapper\">\n        {{ Block.render('seven', {index: 7, type: 'Shell'}) }}\n      </div>\n      <div class=\"BlockWrapper BlockWrapper--isDropzone\" data-dropzone=\"8\">\n        {{ Block.render('', {type: 'Stripes'}) }}\n      </div>\n    </article>\n  </section>\n{% endmacro %}\n"
  },
  {
    "path": "examples/src/content/Droppable/UniqueDropzone/UniqueDropzone.scss",
    "content": "////\n/// Content\n/// UniqueDropzone\n////\n\n@import 'utils/shared/functions';\n\n$grid-area-names: a, b, c, d, e, f, g, h;\n$grid-rows: 6;\n$grid-columns: 4;\n\n.UniqueDropzone {\n  .BlockLayout {\n    .BlockWrapper {\n      .Block:nth-child(1n + 3):not(.draggable-source--is-dragging) {\n        min-height: 0;\n        height: 0;\n      }\n    }\n  }\n\n  .BlockLayout--typeFlex {\n    .BlockWrapper {\n      flex: 1 1 calc(50% - #{get-layout-length(gutter)});\n\n      @media screen and (min-width: get-breakpoint(mobile, wide)) {\n        flex-basis: calc(25% - #{get-layout-length(gutter)});\n      }\n    }\n  }\n\n  .BlockLayout--typeGrid {\n    grid-template-rows: repeat($grid-rows, 1fr);\n    grid-template-columns: repeat($grid-columns, 1fr);\n    grid-template-areas:\n      'a a b b'\n      'a a d d'\n      'c c d d'\n      'e e f f'\n      'e e g g'\n      'h h h h';\n    margin-top: get-border(thin);\n    border: get-border(thin) solid get-color(coal, dark);\n    background-color: get-color(coal, dark);\n\n    @media screen and (min-width: get-breakpoint(tablet)) {\n      // special fractions to line up with the flexContainer\n      grid-template-columns: 1fr 1.025fr 1.025fr 1fr;\n      grid-template-areas:\n        'a c c g'\n        'a c c g'\n        'a d f g'\n        'b d f g'\n        'b e f h'\n        'b e f h';\n      margin-top: get-border();\n      border-width: get-border();\n    }\n\n    .BlockWrapper--isDropzone {\n      &::before {\n        content: '';\n        position: absolute;\n        top: -(get-border(thin));\n        right: -(get-border(thin));\n        bottom: -(get-border(thin));\n        left: -(get-border(thin));\n        display: block;\n        pointer-events: none;\n        background-color: get-color(brand, blue);\n        opacity: 0;\n        transition: opacity get-duration(fast) get-easing();\n\n        // stylelint-disable-next-line max-nesting-depth\n        @media screen and (min-width: get-breakpoint(tablet)) {\n          top: -(get-border());\n          right: -(get-border());\n          bottom: -(get-border());\n          left: -(get-border());\n        }\n      }\n\n      // not focusable, but, whatever... this was hard to solve\n      &.draggable-dropzone--occupied:hover {\n        // stylelint-disable-next-line max-nesting-depth\n        &::before {\n          opacity: 1;\n        }\n      }\n    }\n\n    .BlockContent {\n      border: 0;\n    }\n\n    @for $i from 1 through length($grid-area-names) {\n      .BlockWrapper:nth-child(#{$i}) {\n        grid-area: nth($grid-area-names, $i);\n      }\n    }\n  }\n\n  // stylelint-disable-next-line no-duplicate-selectors\n  .BlockLayout--typeFlex {\n    .BlockContent {\n      @media screen and (min-width: get-breakpoint(tablet)) {\n        min-height: rows(3);\n      }\n\n      @media screen and (min-width: get-breakpoint(desktop)) {\n        min-height: rows(4);\n      }\n    }\n  }\n\n  // not quite lining up with the rows correctly...\n  // will likely need to tweak `grid` values\n  // stylelint-disable-next-line no-duplicate-selectors\n  .BlockLayout--typeGrid {\n    // stylelint-disable-next-line no-duplicate-selectors\n    .BlockContent {\n      @media screen and (min-width: get-breakpoint(tablet)) {\n        min-height: rows(3, false, true);\n      }\n\n      @media screen and (min-width: get-breakpoint(desktop)) {\n        min-height: rows(4, false, true);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "examples/src/content/Droppable/UniqueDropzone/index.js",
    "content": "// eslint-disable-next-line import/no-unresolved\nimport {Droppable} from '@shopify/draggable';\n\nexport default function UniqueDropzone() {\n  const containers = document.querySelectorAll('#UniqueDropzone .BlockLayout');\n\n  if (containers.length === 0) {\n    return false;\n  }\n\n  const droppable = new Droppable(containers, {\n    draggable: '.Block--isDraggable',\n    dropzone: '.BlockWrapper--isDropzone',\n    mirror: {\n      constrainDimensions: true,\n    },\n  });\n\n  let droppableOrigin;\n\n  // --- Draggable events --- //\n  droppable.on('drag:start', (evt) => {\n    droppableOrigin = evt.originalSource.parentNode.dataset.dropzone;\n  });\n\n  droppable.on('droppable:dropped', (evt) => {\n    if (droppableOrigin !== evt.dropzone.dataset.dropzone) {\n      evt.cancel();\n    }\n  });\n\n  return droppable;\n}\n"
  },
  {
    "path": "examples/src/content/Home/Home.html",
    "content": "{% import 'components/Plate/Plate.html' as Plate %}\n\n{% macro render(id) %}\n  <section id=\"{{ id }}\" class=\"{{ id }}\">\n    <article class=\"PlateWrapper\">\n      {{ Plate.render('Three', {level: 'Bottom', draggable: true}) }}\n      {{ Plate.render('Two',   {level: 'Middle', draggable: true}) }}\n      {{ Plate.render('One',   {heading: 'hello', level: 'Top', draggable: true}) }}\n    </article>\n  </section>\n{% endmacro %}\n"
  },
  {
    "path": "examples/src/content/Home/Home.scss",
    "content": "////\n/// Content\n/// Home\n////\n\n.Home {\n  // styles\n}\n"
  },
  {
    "path": "examples/src/content/Home/index.js",
    "content": "// eslint-disable-next-line import/no-unresolved\nimport {Draggable} from '@shopify/draggable';\n\n// eslint-disable-next-line shopify/strict-component-boundaries\nimport Plate from '../../components/Plate';\n\nexport default function Home() {\n  const containerSelector = '#Home .PlateWrapper';\n  const container = document.querySelector(containerSelector);\n\n  if (!container) {\n    return false;\n  }\n\n  const draggable = new Draggable(container, {\n    draggable: '.Plate',\n  });\n  const plates = new Plate(container);\n\n  // --- Draggable events --- //\n  draggable.on('drag:start', (evt) => {\n    plates.setThreshold();\n    plates.setInitialMousePosition(evt.sensorEvent);\n  });\n\n  draggable.on('drag:move', (evt) => {\n    // rAF seems to cause the animation to get stuck?\n    // requestAnimationFrame(() => {});\n    plates.dragWarp(evt.source, evt.sensorEvent);\n  });\n\n  draggable.on('drag:stop', () => {\n    plates.resetWarp();\n  });\n\n  return draggable;\n}\n"
  },
  {
    "path": "examples/src/content/Plugins/Collidable/Collidable.html",
    "content": "{% import 'components/Block/Block.html' as Block %}\n\n{% macro render(id) %}\n  <section id=\"{{ id }}\" class=\"{{ id }}\">\n    <article class=\"BlockLayout BlockLayout--typePositioned\">\n      <div class=\"BlockWrapper BlockWrapper--isDropzone draggable-dropzone--occupied\">\n        {{ Block.render('drop', {type: 'Hollow'}) }}\n        {{ Block.render('drag', {index: 1, draggable: true}) }}\n      </div>\n\n      {{ Block.render('', {index: 2, type: 'Stripes', classes: ['CollidableObstacle']}) }}\n      {{ Block.render('', {index: 3, type: 'Stripes', classes: ['CollidableObstacle']}) }}\n\n      <div class=\"BlockWrapper BlockWrapper--isDropzone\">\n        {{ Block.render('drop', {index: 4, type: 'Hollow'}) }}\n      </div>\n\n      {# If we ever implement a \"constrain to container\" feature, these can be removed #}\n      <div class=\"CollidableWall CollidableWall--itemTop CollidableObstacle\"></div>\n      <div class=\"CollidableWall CollidableWall--itemRight CollidableObstacle\"></div>\n      <div class=\"CollidableWall CollidableWall--itemBottom CollidableObstacle\"></div>\n      <div class=\"CollidableWall CollidableWall--itemLeft CollidableObstacle\"></div>\n    </article>\n  </section>\n{% endmacro %}\n"
  },
  {
    "path": "examples/src/content/Plugins/Collidable/Collidable.scss",
    "content": "////\n/// Content\n/// Collidable\n////\n\n@import 'props';\n\n.Collidable {\n  .BlockLayout--typePositioned {\n    width: 100%;\n    height: collidable(container-height);\n\n    @media screen and (min-width: get-breakpoint(tablet)) {\n      height: collidable(container-height, tablet);\n    }\n\n    @media screen and (min-width: get-breakpoint(desktop)) {\n      height: collidable(container-height, desktop);\n    }\n  }\n\n  .Block--item2,\n  .Block--item3,\n  .BlockWrapper:nth-child(1),\n  .BlockWrapper:nth-child(4) {\n    position: absolute;\n    width: calc(50% - #{get-border(thin) * 2.5});\n\n    @media screen and (min-width: get-breakpoint(tablet)) {\n      width: calc(25% - #{get-border()});\n    }\n  }\n\n  ///\n  /// Obstacles\n  .Block--item2,\n  .Block--item3 {\n    height: collidable(obstacle-height);\n\n    @media screen and (min-width: get-breakpoint(tablet)) {\n      height: collidable(obstacle-height, tablet);\n    }\n\n    @media screen and (min-width: get-breakpoint(desktop)) {\n      height: collidable(obstacle-height, desktop);\n    }\n  }\n\n  .Block--item2 {\n    top: collidable(container-padding);\n    right: collidable(container-padding);\n\n    @media screen and (min-width: get-breakpoint(tablet)) {\n      top: collidable(container-padding, tablet);\n      right: auto;\n      left: calc(25% + #{collidable(container-padding, tablet)});\n    }\n  }\n\n  .Block--item3 {\n    bottom: collidable(container-padding);\n    left: collidable(container-padding);\n\n    @media screen and (min-width: get-breakpoint(tablet)) {\n      bottom: collidable(container-padding, tablet);\n      right: collidable(container-padding, tablet);\n      left: auto;\n    }\n  }\n\n  ///\n  /// Droppables\n  .BlockWrapper:nth-child(1),\n  .BlockWrapper:nth-child(4) {\n    height: collidable(block-height);\n\n    @media screen and (min-width: get-breakpoint(tablet)) {\n      height: collidable(block-height, tablet);\n    }\n\n    @media screen and (min-width: get-breakpoint(desktop)) {\n      height: collidable(block-height, desktop);\n    }\n  }\n\n  .BlockWrapper:nth-child(1) {\n    top: collidable(container-padding);\n    left: collidable(container-padding);\n\n    @media screen and (min-width: get-breakpoint(tablet)) {\n      top: collidable(container-padding, tablet);\n      left: collidable(container-padding, tablet);\n    }\n  }\n\n  .BlockWrapper:nth-child(4) {\n    bottom: collidable(container-padding);\n    right: collidable(container-padding);\n\n    @media screen and (min-width: get-breakpoint(tablet)) {\n      top: collidable(container-padding, tablet);\n      right: collidable(container-padding, tablet);\n      bottom: auto;\n    }\n  }\n\n  ///\n  /// Walls\n  .CollidableWall {\n    position: absolute;\n    background-color: get-color(coal, dark);\n    transition: background-color get-duration() get-easing();\n  }\n\n  .CollidableWall--itemTop {\n    top: 0;\n    right: 0;\n    left: 0;\n    height: get-border(thin);\n\n    @media screen and (min-width: get-breakpoint(tablet)) {\n      height: get-border();\n    }\n  }\n\n  .CollidableWall--itemRight {\n    top: 0;\n    right: 0;\n    bottom: 0;\n    width: get-border(thin);\n\n    @media screen and (min-width: get-breakpoint(tablet)) {\n      width: get-border();\n    }\n  }\n\n  .CollidableWall--itemBottom {\n    right: 0;\n    bottom: 0;\n    left: 0;\n    height: get-border(thin);\n\n    @media screen and (min-width: get-breakpoint(tablet)) {\n      height: get-border();\n    }\n  }\n\n  .CollidableWall--itemLeft {\n    top: 0;\n    bottom: 0;\n    left: 0;\n    width: get-border(thin);\n\n    @media screen and (min-width: get-breakpoint(tablet)) {\n      width: get-border();\n    }\n  }\n\n  // Draggable\n  .draggable-mirror {\n    .BlockContent {\n      transform: scale(0.9);\n    }\n  }\n\n  .draggable-container--is-dragging:not(.draggable-container--over) {\n    .Block--typeStripes {\n      .BlockContent {\n        color: get-color(brand, red);\n      }\n    }\n\n    .CollidableWall {\n      background-color: get-color(brand, red);\n    }\n  }\n\n  // stylelint-disable-next-line no-duplicate-selectors\n  .CollidableWall {\n    &.isColliding {\n      background-color: get-color(brand, red);\n    }\n  }\n}\n"
  },
  {
    "path": "examples/src/content/Plugins/Collidable/index.js",
    "content": "// eslint-disable-next-line import/no-unresolved\nimport {Droppable, Plugins} from '@shopify/draggable';\n\nexport default function PluginsCollidable() {\n  const containerSelector = '#Collidable .BlockLayout';\n  const containers = document.querySelectorAll(containerSelector);\n  const wallClass = 'CollidableWall';\n  const walls = document.querySelectorAll(`.${wallClass}`);\n\n  if (containers.length === 0) {\n    return false;\n  }\n\n  const droppable = new Droppable(containers, {\n    draggable: '.Block--isDraggable',\n    dropzone: '.BlockWrapper--isDropzone',\n    collidables: '.CollidableObstacle',\n    mirror: {\n      appendTo: containerSelector,\n      constrainDimensions: true,\n    },\n    plugins: [Plugins.Collidable],\n  });\n\n  // --- Draggable events --- //\n  droppable.on('collidable:in', ({collidingElement}) => {\n    if (collidingElement.classList.contains(wallClass)) {\n      walls.forEach((wall) => wall.classList.add('isColliding'));\n    } else {\n      collidingElement.classList.add('isColliding');\n    }\n  });\n\n  droppable.on('collidable:out', ({collidingElement}) => {\n    if (collidingElement.classList.contains(wallClass)) {\n      walls.forEach((wall) => wall.classList.remove('isColliding'));\n    } else {\n      collidingElement.classList.remove('isColliding');\n    }\n  });\n\n  return droppable;\n}\n"
  },
  {
    "path": "examples/src/content/Plugins/Collidable/props.scss",
    "content": "////\n/// Content\n/// Collidable props\n////\n\n@import 'utils/shared/functions';\n\n$collidable-container-height: 60rem;\n$collidable-container-height-tablet: 64rem;\n$collidable-container-height-desktop: 80rem;\n\n$collidable-block-height: rows(3, true);\n$collidable-block-height-tablet: rows(4);\n$collidable-block-height-desktop: rows(5);\n\n$collidable: (\n  container-height: (\n    base: $collidable-container-height,\n    tablet: $collidable-container-height-tablet,\n    desktop: $collidable-container-height-desktop,\n  ),\n  container-padding: (\n    base: get-border(thin) * 2,\n    tablet: get-border() * 2,\n  ),\n  block-height: (\n    base: $collidable-block-height,\n    tablet: $collidable-block-height-tablet,\n    desktop: $collidable-block-height-desktop,\n  ),\n  obstacle-height: (\n    base: $collidable-container-height / 3,\n    tablet: $collidable-container-height-tablet - $collidable-block-height-tablet -\n      (\n        get-border() * 5,\n      ),\n    desktop: $collidable-container-height-desktop - $collidable-block-height-desktop -\n      (\n        get-border() * 5,\n      ),\n  ),\n);\n\n@function collidable($group: $threads-default-value, $variant: $threads-default-value) {\n  @return threads-value-get($collidable, $group, $variant);\n}\n"
  },
  {
    "path": "examples/src/content/Plugins/Snappable/Snappable.html",
    "content": "{% import 'components/Block/Block.html' as Block %}\n\n{% macro render(id) %}\n  <section id=\"{{ id }}\" class=\"{{ id }}\">\n    <article class=\"BlockLayout BlockLayout--typeGrid\">\n      <div class=\"BlockWrapper\">\n        {{ Block.render('snap', {index: 1, draggable: true, classes: ['draggable-source']}) }}\n      </div>\n      <div class=\"BlockWrapper\">\n        {{ Block.render('two', {index: 2}) }}\n      </div>\n      <div class=\"BlockWrapper\">\n        {{ Block.render('', {index: 3, type: 'Stripes', classes: ['draggable-source']}) }}\n      </div>\n      <div class=\"BlockWrapper\">\n        {{ Block.render('', {index: 4, type: 'Stripes', classes: ['draggable-source']}) }}\n      </div>\n\n      <div class=\"BlockWrapper\">\n        {{ Block.render('five', {index: 5}) }}\n      </div>\n      <div class=\"BlockWrapper\">\n        {{ Block.render('', {index: 6, type: 'Stripes', classes: ['draggable-source']}) }}\n      </div>\n      <div class=\"BlockWrapper\">\n        {{ Block.render('crack', {index: 7, draggable: true, classes: ['draggable-source']}) }}\n      </div>\n      <div class=\"BlockWrapper\">\n        {{ Block.render('eight', {index: 8}) }}\n      </div>\n\n      <div class=\"BlockWrapper\">\n        {{ Block.render('', {index: 9, type: 'Stripes', classes: ['draggable-source']}) }}\n      </div>\n      <div class=\"BlockWrapper\">\n        {{ Block.render('ten', {index: 10}) }}\n      </div>\n      <div class=\"BlockWrapper\">\n        {{ Block.render('', {index: 11, type: 'Stripes', classes: ['draggable-source']}) }}\n      </div>\n      <div class=\"BlockWrapper\">\n        {{ Block.render('pop', {index: 12, draggable: true, classes: ['draggable-source']}) }}\n      </div>\n\n      <div class=\"BlockWrapper\">\n        {{ Block.render('pow', {index: 13, draggable: true, classes: ['draggable-source']}) }}\n      </div>\n      <div class=\"BlockWrapper\">\n        {{ Block.render('', {index: 14, type: 'Stripes', classes: ['draggable-source']}) }}\n      </div>\n      <div class=\"BlockWrapper\">\n        {{ Block.render('fifteen', {index: 15}) }}\n      </div>\n      <div class=\"BlockWrapper\">\n        {{ Block.render('', {index: 16, type: 'Stripes', classes: ['draggable-source']}) }}\n      </div>\n    </article>\n  </section>\n{% endmacro %}\n"
  },
  {
    "path": "examples/src/content/Plugins/Snappable/Snappable.scss",
    "content": "////\n/// Content\n/// Snappable\n////\n\n@import 'utils/shared/functions';\n\n.Snappable {\n  .BlockLayout--typeGrid {\n    grid-template-columns: repeat(2, 1fr);\n\n    @media screen and (min-width: get-breakpoint(tablet)) {\n      grid-template-rows: repeat(4, rows(3));\n      grid-template-columns: repeat(4, 1fr);\n    }\n\n    @media screen and (min-width: get-breakpoint(desktop)) {\n      grid-template-rows: repeat(4, rows(4));\n    }\n  }\n}\n"
  },
  {
    "path": "examples/src/content/Plugins/Snappable/index.js",
    "content": "// eslint-disable-next-line import/no-unresolved\nimport {Swappable, Plugins} from '@shopify/draggable';\n\nexport default function PluginsSnappable() {\n  const containerSelector = '#Snappable .BlockLayout';\n  const containers = document.querySelectorAll(containerSelector);\n\n  if (containers.length === 0) {\n    return false;\n  }\n\n  const swappable = new Swappable(containers, {\n    mirror: {\n      appendTo: containerSelector,\n      constrainDimensions: true,\n    },\n    plugins: [Plugins.Snappable],\n  });\n\n  // --- Draggable events --- //\n  swappable.on('drag:start', (evt) => {\n    if (evt.originalSource.classList.contains('Block--typeStripes')) {\n      evt.cancel();\n    }\n  });\n\n  return swappable;\n}\n"
  },
  {
    "path": "examples/src/content/Plugins/SortAnimation/SortAnimation.html",
    "content": "{% import 'components/Block/Block.html' as Block %}\n\n{% macro render(id) %}\n  <section id=\"{{ id }}\" class=\"{{ id }}\">\n    <article class=\"BlockLayout BlockLayout--typeFlex\">\n      {{ Block.render('one',   {index: 1, draggable: true}) }}\n      {{ Block.render('two',   {index: 2, draggable: true}) }}\n      {{ Block.render('three', {index: 3, draggable: true}) }}\n      {{ Block.render('four',  {index: 4, draggable: true}) }}\n      {{ Block.render('five',  {index: 5, draggable: true}) }}\n      {{ Block.render('six',   {index: 6, draggable: true}) }}\n      {{ Block.render('seven', {index: 7, draggable: true}) }}\n      {{ Block.render('eight', {index: 8, draggable: true}) }}\n      {{ Block.render('nine',  {index: 9, draggable: true}) }}\n    </article>\n  </section>\n{% endmacro %}\n"
  },
  {
    "path": "examples/src/content/Plugins/SortAnimation/SortAnimation.scss",
    "content": "////\n/// Content\n/// SortAnimation\n////\n\n@import 'utils/shared/functions';\n@import 'utils/shared/layout';\n\n$sort-anim-block-name: unquote('Block');\n\n.SortAnimation {\n  @include draggable-source-layout($sort-anim-block-name, 1, 2, 4, 5, 7, 8) {\n    flex-basis: 50%;\n  }\n\n  @include draggable-source-layout($sort-anim-block-name, 3, 6, 9) {\n    flex-basis: 100%;\n  }\n\n  @media screen and (min-width: get-breakpoint(tablet)) {\n    @include draggable-source-layout($sort-anim-block-name, 1, 2, 3, 4, 5, 6, 7, 8, 9) {\n      flex-basis: 33.333%;\n    }\n  }\n\n  .BlockContent {\n    @media screen and (min-width: get-breakpoint(tablet)) {\n      height: rows(3);\n    }\n\n    @media screen and (min-width: get-breakpoint(desktop)) {\n      height: rows(4);\n    }\n\n    @media screen and (min-width: get-breakpoint('1080p', wide)) {\n      height: rows(5);\n    }\n  }\n}\n"
  },
  {
    "path": "examples/src/content/Plugins/SortAnimation/index.js",
    "content": "// eslint-disable-next-line import/no-unresolved\nimport {Sortable, Plugins} from '@shopify/draggable';\n\nexport default function SortAnimation() {\n  const containers = document.querySelectorAll('#SortAnimation .BlockLayout');\n\n  if (containers.length === 0) {\n    return false;\n  }\n\n  const sortable = new Sortable(containers, {\n    draggable: '.Block--isDraggable',\n    mirror: {\n      constrainDimensions: true,\n    },\n    plugins: [Plugins.SortAnimation],\n    swapAnimation: {\n      duration: 200,\n      easingFunction: 'ease-in-out',\n    },\n  });\n\n  return sortable;\n}\n"
  },
  {
    "path": "examples/src/content/Plugins/SwapAnimation/SwapAnimation.html",
    "content": "{% import 'components/Block/Block.html' as Block %}\n\n{% macro render(id) %}\n  <section id=\"{{ id }}\" class=\"{{ id }}\">\n    <article class=\"BlockLayout BlockLayout--typeFlex\">\n      {{ Block.render('one',   {index: 1}) }}\n      {{ Block.render('two',   {index: 2, draggable: true}) }}\n      {{ Block.render('three', {index: 3, draggable: true}) }}\n      {{ Block.render('four',  {index: 4, draggable: true}) }}\n      {{ Block.render('five',  {index: 5}) }}\n      {{ Block.render('six',   {index: 6, draggable: true}) }}\n      {{ Block.render('seven', {index: 7, draggable: true}) }}\n      {{ Block.render('eight', {index: 8, draggable: true}) }}\n      {{ Block.render('nine',  {index: 9}) }}\n    </article>\n  </section>\n{% endmacro %}\n"
  },
  {
    "path": "examples/src/content/Plugins/SwapAnimation/SwapAnimation.scss",
    "content": "////\n/// Content\n/// SwapAnimation\n////\n\n@import 'utils/shared/functions';\n@import 'utils/shared/layout';\n\n$swap-anim-block-name: unquote('Block');\n\n.SwapAnimation {\n  @include draggable-source-layout($swap-anim-block-name, 1, 2, 4, 5, 7, 8) {\n    flex-basis: 50%;\n  }\n\n  @include draggable-source-layout($swap-anim-block-name, 3, 6, 9) {\n    flex-basis: 100%;\n  }\n\n  @media screen and (min-width: get-breakpoint(tablet)) {\n    @include draggable-source-layout($swap-anim-block-name, 1, 2, 3, 4, 5, 6, 7, 8, 9) {\n      flex-basis: 33.333%;\n    }\n  }\n\n  .BlockContent {\n    @media screen and (min-width: get-breakpoint(tablet)) {\n      height: rows(3);\n    }\n\n    @media screen and (min-width: get-breakpoint(desktop)) {\n      height: rows(4);\n    }\n\n    @media screen and (min-width: get-breakpoint('1080p', wide)) {\n      height: rows(5);\n    }\n  }\n}\n"
  },
  {
    "path": "examples/src/content/Plugins/SwapAnimation/index.js",
    "content": "// eslint-disable-next-line import/no-unresolved\nimport {Sortable, Plugins} from '@shopify/draggable';\n\nexport default function PluginsSwapAnimation() {\n  const containers = document.querySelectorAll('#SwapAnimation .BlockLayout');\n\n  if (containers.length === 0) {\n    return false;\n  }\n\n  const sortable = new Sortable(containers, {\n    draggable: '.Block--isDraggable',\n    mirror: {\n      constrainDimensions: true,\n    },\n    plugins: [Plugins.SwapAnimation],\n    swapAnimation: {\n      duration: 200,\n      easingFunction: 'ease-in-out',\n    },\n  });\n\n  return sortable;\n}\n"
  },
  {
    "path": "examples/src/content/Sortable/MultipleContainers/MultipleContainers.html",
    "content": "{% import 'components/StackedList/StackedListItem.html' as StackedListItem %}\n\n{% macro render(id) %}\n  <section id=\"{{ id }}\" class=\"{{ id }}\">\n    <article id=\"ContainerOne\" class=\"StackedListWrapper StackedListWrapper--sizeLarge StackedListWrapper--axisHorizontal Container\">\n      <header class=\"StackedListHeader\">\n        <h3 class=\"Heading Heading--size3 Heading--colorWhite\">Container one</h3>\n      </header>\n\n      <ul class=\"StackedList\">\n        {{ StackedListItem.render('zebra',    {index: 1, draggable: true}) }}\n        {{ StackedListItem.render('giraffe',  {index: 2, draggable: true}) }}\n        {{ StackedListItem.render('baboon',   {index: 3                 }) }}\n        {{ StackedListItem.render('elephant', {index: 4, draggable: true}) }}\n        {{ StackedListItem.render('leopard',  {index: 5, draggable: true}) }}\n      </ul>\n    </article>\n\n    <article id=\"ContainerTwo\" class=\"StackedListWrapper StackedListWrapper--sizeMedium StackedListWrapper--hasScrollIndicator Container\">\n      <header class=\"StackedListHeader\">\n        <h3 class=\"Heading Heading--size3 Heading--colorWhite\">Container two</h3>\n        <p><em>3 item capacity</em></p>\n      </header>\n\n      <ul class=\"StackedList StackedList--hasScroll\">\n        {{ StackedListItem.render('fluorescent grey', {index: 6, draggable: true}) }}\n        {{ StackedListItem.render('rebecca purple',   {index: 7, draggable: true}) }}\n      </ul>\n    </article>\n\n    <article id=\"ContainerThree\" class=\"StackedListWrapper StackedListWrapper--hasScrollIndicator Container\">\n      <header class=\"StackedListHeader\">\n        <h3 class=\"Heading Heading--size3 Heading--colorWhite\">Container three</h3>\n      </header>\n\n      <ul class=\"StackedList StackedList--hasScroll\">\n        {{ StackedListItem.render('apple',         {index: 8,  draggable: true}) }}\n        {{ StackedListItem.render('banana',        {index: 9,  draggable: true}) }}\n        {{ StackedListItem.render('cucumber',      {index: 10, draggable: true}) }}\n        {{ StackedListItem.render('daikon radish', {index: 11                 }) }}\n        {{ StackedListItem.render('elderberry',    {index: 12, draggable: true}) }}\n        {{ StackedListItem.render('fresh thyme',   {index: 13, draggable: true}) }}\n        {{ StackedListItem.render('guava',         {index: 14, draggable: true}) }}\n      </ul>\n    </article>\n  </section>\n{% endmacro %}\n"
  },
  {
    "path": "examples/src/content/Sortable/MultipleContainers/MultipleContainers.scss",
    "content": "////\n/// Content\n/// MultipleContainers\n////\n\n@import 'utils/shared/layout';\n@import 'components/StackedList/props';\n\n$grid-columns: 8;\n\n.MultipleContainers {\n  display: grid;\n  gap: get-layout-length(gutter);\n  grid-template-columns: 100%;\n  grid-template-areas: 'a' 'b' 'c';\n\n  @media screen and (min-width: get-breakpoint(desktop)) {\n    grid-template-columns: repeat($grid-columns, 1fr);\n    grid-template-areas:\n      'a a a a a a a a'\n      'b b b b b c c c';\n  }\n\n  @media screen and (min-width: get-breakpoint('1080p', wide)) {\n    @include centered-width(columns(6));\n  }\n\n  .Container {\n    &:nth-child(1) {\n      grid-area: a;\n    }\n\n    &:nth-child(2) {\n      grid-area: b;\n    }\n\n    &:nth-child(3) {\n      grid-area: c;\n    }\n  }\n\n  // resolve slim padding-bottom when single column\n  .StackedListHeader {\n    @media screen and (min-width: get-breakpoint(tablet)) and (max-width: get-breakpoint(desktop) - 1px) {\n      height: auto;\n    }\n  }\n\n  .draggable--is-dragging & .draggable-container-parent--capacity {\n    color: get-color(brand, blue);\n  }\n}\n"
  },
  {
    "path": "examples/src/content/Sortable/MultipleContainers/index.js",
    "content": "// eslint-disable-next-line import/no-unresolved\nimport {Sortable, Plugins} from '@shopify/draggable';\n\nconst Classes = {\n  draggable: 'StackedListItem--isDraggable',\n  capacity: 'draggable-container-parent--capacity',\n};\n\nexport default function MultipleContainers() {\n  const containers = document.querySelectorAll('#MultipleContainers .StackedList');\n\n  if (containers.length === 0) {\n    return false;\n  }\n\n  const sortable = new Sortable(containers, {\n    draggable: `.${Classes.draggable}`,\n    mirror: {\n      constrainDimensions: true,\n    },\n    plugins: [Plugins.ResizeMirror],\n  });\n\n  const containerTwoCapacity = 3;\n  const containerTwoParent = sortable.containers[1].parentNode;\n  let currentMediumChildren;\n  let capacityReached;\n  let lastOverContainer;\n\n  // --- Draggable events --- //\n  sortable.on('drag:start', (evt) => {\n    currentMediumChildren = sortable.getDraggableElementsForContainer(sortable.containers[1])\n      .length;\n    capacityReached = currentMediumChildren === containerTwoCapacity;\n    lastOverContainer = evt.sourceContainer;\n    containerTwoParent.classList.toggle(Classes.capacity, capacityReached);\n  });\n\n  sortable.on('sortable:sort', (evt) => {\n    if (!capacityReached) {\n      return;\n    }\n\n    const sourceIsCapacityContainer = evt.dragEvent.sourceContainer === sortable.containers[1];\n\n    if (!sourceIsCapacityContainer && evt.dragEvent.overContainer === sortable.containers[1]) {\n      evt.cancel();\n    }\n  });\n\n  sortable.on('sortable:sorted', (evt) => {\n    if (lastOverContainer === evt.dragEvent.overContainer) {\n      return;\n    }\n\n    lastOverContainer = evt.dragEvent.overContainer;\n  });\n\n  return sortable;\n}\n"
  },
  {
    "path": "examples/src/content/Sortable/SimpleList/SimpleList.html",
    "content": "{% import 'components/StackedList/StackedListItem.html' as StackedListItem %}\n\n{% macro render(id) %}\n  <section id=\"{{ id }}\" class=\"{{ id }}\">\n    <article class=\"StackedListWrapper StackedListWrapper--hasScrollIndicator\">\n      <header class=\"StackedListHeader\">\n        <h3 class=\"Heading Heading--size3 Heading--colorWhite\">Simple list</h3>\n      </header>\n\n      <ul class=\"StackedList StackedList--hasScroll\">\n        {{ StackedListItem.render('item one',    {index: 1, draggable: true}) }}\n        {{ StackedListItem.render('item two',    {index: 2, draggable: true}) }}\n        {{ StackedListItem.render('item three',  {index: 3, draggable: true}) }}\n        {{ StackedListItem.render('item four',   {index: 4, draggable: true}) }}\n        {{ StackedListItem.render('item five',   {index: 5}) }}\n        {{ StackedListItem.render('item six',    {index: 6, draggable: true}) }}\n        {{ StackedListItem.render('item seven',  {index: 7, draggable: true}) }}\n        {{ StackedListItem.render('item eight',  {index: 8}) }}\n        {{ StackedListItem.render('item nine',   {index: 9}) }}\n        {{ StackedListItem.render('item ten',    {index: 10, draggable: true}) }}\n        {{ StackedListItem.render('item eleven', {index: 11}) }}\n        {{ StackedListItem.render('item twelve', {index: 12, draggable: true}) }}\n      </ul>\n    </article>\n  </section>\n{% endmacro %}\n"
  },
  {
    "path": "examples/src/content/Sortable/SimpleList/SimpleList.scss",
    "content": "////\n/// Content\n/// Simple list\n////\n\n@import 'utils/shared/layout';\n@import 'components/StackedList/props';\n\n// using an arbitrary width until I have time to fix the `columns` function\n$simple-list-width: 42rem;\n\n.SimpleList {\n  @include centered-width(columns(5));\n}\n"
  },
  {
    "path": "examples/src/content/Sortable/SimpleList/index.js",
    "content": "// eslint-disable-next-line import/no-unresolved\nimport {Sortable} from '@shopify/draggable';\n\nexport default function SimpleList() {\n  const containerSelector = '#SimpleList .StackedList';\n  const containers = document.querySelectorAll(containerSelector);\n\n  if (containers.length === 0) {\n    return false;\n  }\n\n  const sortable = new Sortable(containers, {\n    draggable: '.StackedListItem--isDraggable',\n    mirror: {\n      appendTo: containerSelector,\n      constrainDimensions: true,\n    },\n  });\n\n  return sortable;\n}\n"
  },
  {
    "path": "examples/src/content/Sortable/Transformed/Transformed.html",
    "content": "{% import 'components/PaperStack/PaperStackItem.html' as PaperStackItem %}\n\n{% macro render(id) %}\n  <section id=\"{{ id }}\" class=\"{{ id }}\">\n    <article class=\"PaperStackWrapper\">\n      <ul class=\"PaperStack\">\n        {{ PaperStackItem.render('one',   {index: 1, draggable: true}) }}\n        {{ PaperStackItem.render('two',   {index: 2, draggable: true}) }}\n        {{ PaperStackItem.render('three', {index: 3, draggable: true}) }}\n        {{ PaperStackItem.render('four',  {index: 4, draggable: true}) }}\n      </ul>\n    </article>\n  </section>\n{% endmacro %}\n"
  },
  {
    "path": "examples/src/content/Sortable/Transformed/Transformed.scss",
    "content": "////\n/// Content\n/// Transformed\n////\n\n.Transformed {\n  // styles\n}\n"
  },
  {
    "path": "examples/src/content/Sortable/Transformed/index.js",
    "content": "// eslint-disable-next-line import/no-unresolved\nimport {Sortable} from '@shopify/draggable';\n\nexport default function Transformed() {\n  const containerSelector = '#Transformed .PaperStack';\n  const containers = document.querySelectorAll(containerSelector);\n\n  if (containers.length === 0) {\n    return false;\n  }\n\n  const sortable = new Sortable(containers, {\n    draggable: '.PaperStackItem--isDraggable',\n    mirror: {\n      appendTo: containerSelector,\n      constrainDimensions: true,\n    },\n  });\n\n  return sortable;\n}\n"
  },
  {
    "path": "examples/src/content/Swappable/Flexbox/Flexbox.html",
    "content": "{% import 'components/Block/Block.html' as Block %}\n\n{% macro render(id) %}\n  <section id=\"{{ id }}\" class=\"{{ id }}\">\n    <article class=\"BlockLayout BlockLayout--typeFlex\">\n      {{ Block.render('one',   {index: 1, draggable: true}) }}\n      {{ Block.render('two',   {index: 2}) }}\n      {{ Block.render('three', {index: 3, draggable: true}) }}\n      {{ Block.render('four',  {index: 4, draggable: true}) }}\n      {{ Block.render('five',  {index: 5, draggable: true}) }}\n      {{ Block.render('six',   {index: 6}) }}\n      {{ Block.render('seven', {index: 7, draggable: true}) }}\n    </article>\n  </section>\n{% endmacro %}\n"
  },
  {
    "path": "examples/src/content/Swappable/Flexbox/Flexbox.scss",
    "content": "////\n/// Content\n/// Flexbox\n////\n\n@import 'utils/shared/functions';\n@import 'utils/shared/layout';\n\n$flexbox-block-name: unquote('Block');\n\n.Flexbox {\n  @include draggable-source-layout($flexbox-block-name, 1, 2, 4, 5) {\n    flex-basis: 50%;\n  }\n\n  @include draggable-source-layout($flexbox-block-name, 3, 6, 7) {\n    flex-basis: 100%;\n  }\n\n  @include draggable-source-layout($flexbox-block-name, 1, 2, 4, 5, 7) {\n    .BlockContent {\n      height: rows(2);\n    }\n  }\n\n  @include draggable-source-layout($flexbox-block-name, 3, 6) {\n    .BlockContent {\n      height: rows(3);\n    }\n  }\n\n  @media screen and (min-width: get-breakpoint(tablet)) {\n    @include draggable-source-layout($flexbox-block-name, 1, 2, 4, 5, 7) {\n      .BlockContent {\n        height: rows(3);\n      }\n    }\n\n    @include draggable-source-layout($flexbox-block-name, 3, 6) {\n      .BlockContent {\n        height: rows(4);\n      }\n    }\n  }\n\n  @media screen and (min-width: get-breakpoint(desktop)) {\n    @include draggable-source-layout($flexbox-block-name, 1, 2, 3) {\n      flex-basis: 33.333%;\n    }\n\n    @include draggable-source-layout($flexbox-block-name, 4) {\n      flex-basis: 25%;\n    }\n\n    @include draggable-source-layout($flexbox-block-name, 5) {\n      flex-basis: 75%;\n    }\n\n    @include draggable-source-layout($flexbox-block-name, 6, 7) {\n      flex-basis: 50%;\n    }\n\n    @include draggable-source-layout($flexbox-block-name, 1, 2, 3, 4, 6, 7) {\n      .BlockContent {\n        height: rows(4);\n      }\n    }\n\n    @include draggable-source-layout($flexbox-block-name, 4, 5) {\n      .BlockContent {\n        height: rows(5);\n      }\n    }\n  }\n\n  @media screen and (min-width: get-breakpoint('1080p', wide)) {\n    @include draggable-source-layout($flexbox-block-name, 1, 2, 3, 4, 6, 7) {\n      .BlockContent {\n        height: rows(5);\n      }\n    }\n\n    @include draggable-source-layout($flexbox-block-name, 4, 5) {\n      .BlockContent {\n        height: rows(6);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "examples/src/content/Swappable/Flexbox/index.js",
    "content": "// eslint-disable-next-line import/no-unresolved\nimport {Swappable, Plugins} from '@shopify/draggable';\n\nexport default function Flexbox() {\n  const containers = document.querySelectorAll('#Flexbox .BlockLayout');\n\n  if (containers.length === 0) {\n    return false;\n  }\n\n  const swappable = new Swappable(containers, {\n    draggable: '.Block--isDraggable',\n    mirror: {\n      constrainDimensions: true,\n    },\n    plugins: [Plugins.ResizeMirror],\n  });\n\n  return swappable;\n}\n"
  },
  {
    "path": "examples/src/content/Swappable/Floated/Floated.html",
    "content": "{% import 'components/Block/Block.html' as Block %}\n\n{% macro render(id) %}\n  <section id=\"{{ id }}\" class=\"{{ id }}\">\n    <article class=\"BlockLayout BlockLayout--typeFloat\">\n      {{ Block.render('one',   {index: 1, draggable: true}) }}\n      {{ Block.render('two',   {index: 2, draggable: true}) }}\n      {{ Block.render('three', {index: 3, draggable: true}) }}\n      {{ Block.render('four',  {index: 4, draggable: true}) }}\n      {{ Block.render('five',  {index: 5, draggable: true}) }}\n      {{ Block.render('six',   {index: 6}) }}\n      {{ Block.render('seven', {index: 7, draggable: true}) }}\n      {{ Block.render('eight', {index: 8, draggable: true}) }}\n    </article>\n  </section>\n{% endmacro %}\n"
  },
  {
    "path": "examples/src/content/Swappable/Floated/Floated.scss",
    "content": "////\n/// Content\n/// Floated\n////\n\n@import 'utils/shared/functions';\n@import 'utils/shared/layout';\n\n$floated-block-name: unquote('Block');\n\n.Floated {\n  @media screen and (max-width: get-breakpoint(tablet) - 1px) {\n    @include draggable-source-layout($floated-block-name, 1, 2, 3, 4, 5, 7, 8) {\n      width: 50%;\n    }\n\n    @include draggable-source-layout($floated-block-name, 6) {\n      width: 100%;\n    }\n\n    @include draggable-source-layout($floated-block-name, 3) {\n      .BlockContent {\n        height: rows(4, true);\n      }\n    }\n\n    .draggable--original ~ .Block:nth-child(3) {\n      .BlockContent {\n        height: rows(2, true);\n      }\n    }\n  }\n\n  @media screen and (min-width: get-breakpoint(tablet)) {\n    @include draggable-source-layout($floated-block-name, 1, 2, 3) {\n      width: 33.333%;\n    }\n\n    @include draggable-source-layout($floated-block-name, 4, 5, 6, 7, 8) {\n      width: 50%;\n    }\n\n    @include draggable-source-layout($floated-block-name, 1, 2, 3, 7, 8) {\n      .BlockContent {\n        height: rows(3);\n      }\n    }\n\n    @include draggable-source-layout($floated-block-name, 4) {\n      .BlockContent {\n        height: rows(6);\n      }\n    }\n\n    @include draggable-source-layout($floated-block-name, 5, 6) {\n      .BlockContent {\n        height: rows(3);\n      }\n    }\n  }\n\n  @media screen and (min-width: get-breakpoint(desktop)) {\n    @include draggable-source-layout($floated-block-name, 1, 2, 3, 7, 8) {\n      .BlockContent {\n        height: rows(4);\n      }\n    }\n  }\n\n  @media screen and (min-width: get-breakpoint('1080p', wide)) {\n    @include draggable-source-layout($floated-block-name, 4) {\n      .BlockContent {\n        height: rows(8);\n      }\n    }\n\n    @include draggable-source-layout($floated-block-name, 5, 6) {\n      .BlockContent {\n        height: rows(4);\n      }\n    }\n\n    @include draggable-source-layout($floated-block-name, 7, 8) {\n      .BlockContent {\n        height: rows(5);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "examples/src/content/Swappable/Floated/index.js",
    "content": "// eslint-disable-next-line import/no-unresolved\nimport {Swappable, Plugins} from '@shopify/draggable';\n\nexport default function Floated() {\n  const containers = document.querySelectorAll('#Floated .BlockLayout');\n\n  if (containers.length === 0) {\n    return false;\n  }\n\n  const swappable = new Swappable(containers, {\n    draggable: '.Block--isDraggable',\n    mirror: {\n      constrainDimensions: true,\n    },\n    plugins: [Plugins.ResizeMirror],\n  });\n\n  return swappable;\n}\n"
  },
  {
    "path": "examples/src/content/Swappable/GridLayout/GridLayout.html",
    "content": "{% import 'components/Block/Block.html' as Block %}\n\n{% macro render(id) %}\n  <section id=\"{{ id }}\" class=\"{{ id }}\">\n    <article class=\"BlockLayout BlockLayout--typeGrid\">\n      <div class=\"BlockWrapper\">\n        {{ Block.render('one',   {index: 1, draggable: true}) }}\n      </div>\n      <div class=\"BlockWrapper\">\n        {{ Block.render('two',   {index: 2, draggable: true}) }}\n      </div>\n      <div class=\"BlockWrapper\">\n        {{ Block.render('three', {index: 3}) }}\n      </div>\n      <div class=\"BlockWrapper\">\n        {{ Block.render('four',  {index: 4, draggable: true}) }}\n      </div>\n      <div class=\"BlockWrapper\">\n        {{ Block.render('five',  {index: 5, draggable: true}) }}\n      </div>\n      <div class=\"BlockWrapper\">\n        {{ Block.render('six',   {index: 6, draggable: true}) }}\n      </div>\n      <div class=\"BlockWrapper\">\n        {{ Block.render('seven', {index: 7}) }}\n      </div>\n    </article>\n  </section>\n{% endmacro %}\n"
  },
  {
    "path": "examples/src/content/Swappable/GridLayout/GridLayout.scss",
    "content": "////\n/// Content\n/// Grid Layout\n////\n\n@import 'utils/shared/functions';\n\n$grid-area-names: a, b, c, d, e, f, g;\n$grid-rows: 5;\n$grid-rows-tablet: 6;\n$grid-columns: 5;\n$grid-columns-tablet: 8;\n\n.GridLayout {\n  .BlockLayout--typeGrid {\n    grid-template-rows: repeat($grid-rows, 1fr);\n    grid-template-columns: repeat($grid-columns, 1fr);\n    grid-template-areas: 'a a a a a' 'b b c c c' 'd d c c c' 'd d e e e' 'f f f g g';\n\n    @media screen and (min-width: get-breakpoint(tablet)) {\n      grid-template-rows: repeat($grid-rows-tablet, rows(3));\n      grid-template-columns: repeat($grid-columns-tablet, 1fr);\n      grid-template-areas:\n        'a a a a b b b b'\n        'a a a a d d d d'\n        'c c c c d d d d'\n        'e e e e e e e e'\n        'f f f g g g g g'\n        'f f f g g g g g';\n    }\n\n    @media screen and (min-width: get-breakpoint(desktop)) {\n      grid-template-rows: repeat($grid-rows-tablet, rows(4));\n    }\n  }\n\n  @for $i from 1 through length($grid-area-names) {\n    .BlockWrapper:nth-child(#{$i}) {\n      grid-area: nth($grid-area-names, $i);\n    }\n  }\n}\n"
  },
  {
    "path": "examples/src/content/Swappable/GridLayout/index.js",
    "content": "// eslint-disable-next-line import/no-unresolved\nimport {Swappable, Plugins} from '@shopify/draggable';\n\nexport default function GridLayout() {\n  const containerSelector = '#GridLayout .BlockLayout';\n  const containers = document.querySelectorAll(containerSelector);\n\n  if (containers.length === 0) {\n    return false;\n  }\n\n  const swappable = new Swappable(containers, {\n    draggable: '.Block--isDraggable',\n    mirror: {\n      appendTo: containerSelector,\n      constrainDimensions: true,\n    },\n    plugins: [Plugins.ResizeMirror],\n  });\n\n  return swappable;\n}\n"
  },
  {
    "path": "examples/src/content/index.js",
    "content": "// Home page\nimport Home from './Home';\n// Draggable\nimport DragEvents from './Draggable/DragEvents';\n// Droppable\nimport UniqueDropzone from './Droppable/UniqueDropzone';\n// Sortable\nimport SimpleList from './Sortable/SimpleList';\nimport Transformed from './Sortable/Transformed';\nimport MultipleContainers from './Sortable/MultipleContainers';\n// Swappable\nimport Flexbox from './Swappable/Flexbox';\nimport Floated from './Swappable/Floated';\nimport GridLayout from './Swappable/GridLayout';\n// Plugins\nimport PluginsCollidable from './Plugins/Collidable';\nimport PluginsSnappable from './Plugins/Snappable';\nimport PluginsSwapAnimation from './Plugins/SwapAnimation';\nimport PluginsSortAnimation from './Plugins/SortAnimation';\n\nconst Content = {\n  Home,\n  DragEvents,\n  UniqueDropzone,\n  SimpleList,\n  Transformed,\n  MultipleContainers,\n  Flexbox,\n  Floated,\n  GridLayout,\n  PluginsCollidable,\n  PluginsSnappable,\n  PluginsSwapAnimation,\n  PluginsSortAnimation,\n};\n\nexport default Content;\n"
  },
  {
    "path": "examples/src/scripts/examples-app.js",
    "content": "// eslint-disable-next-line shopify/strict-component-boundaries\nimport Analytics from '../components/Analytics';\n// eslint-disable-next-line shopify/strict-component-boundaries\nimport MobileNav from '../components/MobileNav';\nimport Content from '../content';\n\n// Initialize Google Analytics\nconst gtagTracking = new Analytics('UA-107063633-1');\ngtagTracking.init();\n\n// Initialize navigation\nconst mobileNav = new MobileNav(document.getElementById('MobileNavActivator'));\nmobileNav.init();\n\n// Initialize all examples\nfor (const Example in Content) {\n  // eslint-disable-next-line no-prototype-builtins\n  if (Content.hasOwnProperty(Example)) {\n    Content[Example]();\n  }\n}\n"
  },
  {
    "path": "examples/src/scripts/utils/debounce.js",
    "content": "export default function debounce(callback, wait) {\n  let timeout = null;\n\n  return function (...args) {\n    const context = this; // eslint-disable-line consistent-this, babel/no-invalid-this\n\n    clearTimeout(timeout);\n    timeout = setTimeout(() => callback.apply(context, args), wait);\n  };\n}\n\nexport const debounceDuration = 60 * 6;\n"
  },
  {
    "path": "examples/src/scripts/utils/flip-sign.js",
    "content": "export default function flipSign(number) {\n  if (Math.sign(number) === 1) {\n    return -Math.abs(number);\n  } else if (Math.sign(number) === -1) {\n    return Math.abs(number);\n  } else {\n    return 0;\n  }\n}\n"
  },
  {
    "path": "examples/src/styles/examples-app.scss",
    "content": "////\n/// App Manifest\n////\n\n///\n/// Reset\n@import 'reset';\n\n///\n/// Threads + Examples themes\n@import 'threads/threads'; // imported from `node_modules`\n@import 'examples-theme/examples-theme';\n\n///\n/// Global Utilities\n@import 'utils/global/global-utils';\n\n///\n/// Base components\n@import 'components/Page/Page';\n@import 'components/Svg/Svg';\n@import 'components/Link/Link';\n@import 'components/Button/Button';\n@import 'components/Heading/Heading';\n@import 'components/Patterns/Patterns';\n@import 'components/Main/Main';\n@import 'components/GridOverlay/GridOverlay';\n@import 'components/PageHeader/PageHeader';\n@import 'components/Handle/DragHandle';\n@import 'components/Handle/NopeHandle';\n\n///\n/// Sidebar components\n@import 'components/Sidebar/Sidebar';\n@import 'components/Navigation/Navigation';\n@import 'components/Hamburger/Hamburger';\n@import 'components/Brand/Brand';\n\n///\n/// Example components\n@import 'components/Block/Block';\n@import 'components/PaperStack/PaperStack';\n@import 'components/PillSwitch/PillSwitch';\n@import 'components/Plate/Plate';\n@import 'components/StackedList/StackedList';\n\n///\n/// Example content\n@import 'content/Home/Home';\n@import 'content/Draggable/DragEvents/DragEvents';\n@import 'content/Droppable/UniqueDropzone/UniqueDropzone';\n@import 'content/Sortable/SimpleList/SimpleList';\n@import 'content/Sortable/Transformed/Transformed';\n@import 'content/Sortable/MultipleContainers/MultipleContainers';\n@import 'content/Swappable/Flexbox/Flexbox';\n@import 'content/Swappable/Floated/Floated';\n@import 'content/Swappable/GridLayout/GridLayout';\n@import 'content/Plugins/Collidable/Collidable';\n@import 'content/Plugins/Snappable/Snappable';\n@import 'content/Plugins/SwapAnimation/SwapAnimation';\n@import 'content/Plugins/SortAnimation/SortAnimation';\n"
  },
  {
    "path": "examples/src/styles/examples-theme/_border.scss",
    "content": "////\n/// Examples Theme\n/// Border\n////\n\n$examples-border-data: (\n  thin: 0.6rem,\n  base: 0.8rem,\n  thick: 1.6rem,\n  flexible: 2.5vw,\n);\n"
  },
  {
    "path": "examples/src/styles/examples-theme/_breakpoint.scss",
    "content": "////\n/// Examples Theme\n/// Breakpoint\n////\n\n$examples-breakpoint-data: (\n  base: 960px,\n  mobile: (\n    base: 320px,\n    wide: 480px,\n  ),\n  iphone6: (\n    base: 375px,\n    wide: 667px,\n  ),\n  tablet: (\n    base: 768px,\n    wide: 1024px,\n  ),\n  '720p': (\n    base: 720px,\n    wide: 1280px,\n  ),\n  '1080p': (\n    base: 1080px,\n    wide: 1920px,\n  ),\n  desktop: (\n    base: 1440px,\n    wide: 2560px,\n  ),\n);\n"
  },
  {
    "path": "examples/src/styles/examples-theme/_color.scss",
    "content": "/////\n/// Examples Theme\n/// Color\n////\n\n$examples-color-data: (\n  base: transparent,\n  ash: (\n    light: #fafbfc,\n    base: #ebeef0,\n    dark: #c3cfd8,\n  ),\n  coal: (\n    light: #798c9c,\n    base: #31373d,\n    dark: #212529,\n  ),\n  purple: (\n    light: #f1e2ff,\n    base: #ca8cff,\n    dark: #753fa3,\n  ),\n  brand: (\n    blue: #0042ff,\n    blue-dark: #0035cc,\n    yellow: #ffff00,\n    orange: #ffa500,\n    red: #ff0000,\n    purple: #800080,\n  ),\n);\n"
  },
  {
    "path": "examples/src/styles/examples-theme/_cursor.scss",
    "content": "////\n/// Examples Theme\n/// Cursor\n////\n\n$examples-cursor-data: (\n  base: #{url('https://shopify.github.io/draggable/assets/img/cursor-auto.png'),\n  auto},\n  text: #{url('https://shopify.github.io/draggable/assets/img/cursor-text.png'),\n  text},\n  pointer: (\n    base: #{url('https://shopify.github.io/draggable/assets/img/cursor-pointer.png'),\n    pointer},\n    active: #{url('https://shopify.github.io/draggable/assets/img/cursor-pointer-clicked.png'),\n    pointer},\n  ),\n  drag: (\n    base: #{url('https://shopify.github.io/draggable/assets/img/cursor-drag.png'),\n    auto},\n    active: #{url('https://shopify.github.io/draggable/assets/img/cursor-drag-clicked.png'),\n    auto},\n  ),\n  rock: (\n    base: #{url('https://shopify.github.io/draggable/assets/img/cursor-rock.png'),\n    pointer},\n    active: #{url('https://shopify.github.io/draggable/assets/img/cursor-rock-clicked.png'),\n    pointer},\n  ),\n);\n"
  },
  {
    "path": "examples/src/styles/examples-theme/_duration.scss",
    "content": "////\n/// Examples Theme\n/// Duration\n////\n\n$examples-duration-data: (\n  faster: 60ms,\n  fast: 120ms,\n  base: 240ms,\n  slow: 480ms,\n  slower: 960ms,\n);\n"
  },
  {
    "path": "examples/src/styles/examples-theme/_easing.scss",
    "content": "////\n/// Examples Theme\n/// Easing\n////\n\n$examples-easing-data: (\n  base: cubic-bezier(0.64, 0, 0.35, 1),\n  in: cubic-bezier(0.36, 0, 1, 1),\n  out: cubic-bezier(0, 0, 0.42, 1),\n  bungie: cubic-bezier(0.32, 1.46, 0.54, 1.28),\n);\n"
  },
  {
    "path": "examples/src/styles/examples-theme/_font-stack.scss",
    "content": "////\n/// Examples Theme\n/// Font Stack\n////\n\n$examples-font-stack-data: (\n  base: #{-apple-system,\n  BlinkMacSystemFont,\n  'San Francisco',\n  Roboto,\n  'Segoe UI',\n  Helvetica,\n  'Helvetica Neue',\n  sans-serif,\n  'Apple Color Emoji',\n  'Segoe UI Emoji',\n  'Segoe UI Symbol',\n  },\n  monospace: #{SFMono-Regular,\n  'Liberation Mono',\n  Courier,\n  Menlo,\n  Monaco,\n  Consolas,\n  'Lucida Console',\n  monospace,\n  },\n);\n"
  },
  {
    "path": "examples/src/styles/examples-theme/_layout-length.scss",
    "content": "////\n/// Examples Theme\n/// Layout Length\n////\n\n$examples-layout-length-data: (\n  base: 1rem,\n  gutter: (\n    mobile: 0.6rem,\n    base: 0.8rem,\n  ),\n  column: 9.3rem,\n  row: 4rem,\n  sidebar: 32rem,\n  page-header: 64.2rem,\n  main-interior: 144rem,\n);\n"
  },
  {
    "path": "examples/src/styles/examples-theme/_spacing.scss",
    "content": "////\n/// Examples Theme\n/// Spacing\n////\n\n$spacing-unit: 0.4rem;\n\n$examples-spacing-data: (\n  tightest: $spacing-unit * 2,\n  tighter: $spacing-unit * 4,\n  tight: $spacing-unit * 6,\n  base: $spacing-unit * 8,\n  loose: $spacing-unit * 10,\n  looser: $spacing-unit * 12,\n  loosest: $spacing-unit * 14,\n  fat: $spacing-unit * 16,\n  fatter: $spacing-unit * 18,\n  fattest: $spacing-unit * 20,\n);\n"
  },
  {
    "path": "examples/src/styles/examples-theme/_type-scale.scss",
    "content": "////\n/// Examples Theme\n/// Type Scale\n////\n\n$examples-type-scale-data: (\n  base: (\n    base: 1rem,\n    leading: 1.2,\n    tracking: -0.01em,\n  ),\n  button: (\n    base: 1.2rem,\n    tablet: 1.6rem,\n    weight: 900,\n  ),\n  nav: (\n    base: 3.2rem,\n    '960': 2.4rem,\n    leading: 0.9,\n    tracking: -0.1rem,\n  ),\n  paragraph: (\n    base: 2rem,\n    tablet: 2.4rem,\n    desktop: 2.8rem,\n  ),\n  jumbo: (\n    base: 5.6rem,\n    tablet: 8.2rem,\n    desktop: 12rem,\n    weight: 700,\n    leading: 1,\n  ),\n  h1: (\n    base: 4.2rem,\n    tablet: 6.4rem,\n    desktop: 9rem,\n    weight: 700,\n  ),\n  h2: (\n    base: 3.2rem,\n    tablet: 4.2rem,\n    desktop: 6.4rem,\n    weight: 700,\n  ),\n  h3: (\n    base: 2.4rem,\n    tablet: 2.8rem,\n    desktop: 3.2rem,\n    weight: 700,\n  ),\n  h4: (\n    base: 2rem,\n    tablet: 2.4rem,\n    desktop: 2.8rem,\n    weight: 700,\n  ),\n  subheading: (\n    base: 2.2rem,\n    tablet: 2.8rem,\n    weight: 600,\n  ),\n  footer: (\n    base: 1.2rem,\n    weight: 600,\n    leading: 1.4,\n  ),\n);\n"
  },
  {
    "path": "examples/src/styles/examples-theme/_z-index.scss",
    "content": "////\n/// Examples Theme\n/// Z-Index\n////\n\n$examples-z-index-data: (\n  background: 1,\n  base: 10,\n  foreground: 20,\n  sidebar: 30,\n  hamburger: 40,\n  overlay: 9999,\n);\n"
  },
  {
    "path": "examples/src/styles/examples-theme/examples-theme.scss",
    "content": "////\n/// Themes\n/// Examples\n////\n\n@import 'border';\n@import 'breakpoint';\n@import 'color';\n@import 'cursor';\n@import 'layout-length';\n@import 'duration';\n@import 'easing';\n@import 'font-stack';\n@import 'type-scale';\n@import 'spacing';\n@import 'z-index';\n\n///\n/// Merge Draggable properties into Threads\n@include threads-update-property('border', $examples-border-data);\n@include threads-update-property('breakpoint', $examples-breakpoint-data);\n@include threads-update-property('color', $examples-color-data);\n@include threads-update-property('cursor', $examples-cursor-data);\n@include threads-update-property('layout-length', $examples-layout-length-data);\n@include threads-update-property('duration', $examples-duration-data);\n@include threads-update-property('easing', $examples-easing-data);\n@include threads-update-property('font-stack', $examples-font-stack-data);\n@include threads-update-property('type-scale', $examples-type-scale-data);\n@include threads-update-property('spacing', $examples-spacing-data);\n@include threads-update-property('z-index', $examples-z-index-data);\n"
  },
  {
    "path": "examples/src/styles/reset.scss",
    "content": "////\n/// Global reset\n/// Based on normalize.css\n////\n\n///\n/// Document\nhtml {\n  font-family: sans-serif;\n  font-size: 62.5%;\n  font-display: swap;\n  line-height: 1;\n  text-size-adjust: 100%;\n  text-rendering: optimizeLegibility;\n  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n  box-sizing: border-box;\n\n  *,\n  *::after,\n  *::before {\n    box-sizing: inherit;\n  }\n}\n\n@viewport {\n  width: device-width;\n}\n\n:root {\n  tab-size: 2;\n}\n\nhtml,\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\nbody {\n  margin: 0;\n}\n\n///\n/// Links\na {\n  text-decoration: none;\n  background: transparent;\n\n  &:hover,\n  &:active {\n    outline: 0;\n  }\n}\n\na:focus,\n[tabindex]:focus {\n  outline: none;\n}\n\n///\n/// Typography\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\nli,\nol,\np,\nul {\n  margin: 0;\n  padding: 0;\n  font-weight: 400;\n  font-style: normal;\n}\n\nstrong {\n  font-weight: bolder;\n}\n\nsmall {\n  font-size: 80%;\n}\n\nsub,\nsup {\n  position: relative;\n  font-size: 75%;\n  line-height: 0;\n  vertical-align: baseline;\n}\n\nsup {\n  top: -0.5em;\n}\n\nsub {\n  bottom: -0.25em;\n}\n\nblockquote {\n  margin: 0;\n}\n\nabbr[title] {\n  text-decoration: underline dotted;\n}\n\nsummary {\n  display: list-item;\n}\n\n///\n/// Lists\nol,\nul {\n  list-style: none;\n\n  nav & {\n    list-style-image: none;\n  }\n}\n\nli {\n  list-style: none;\n}\n\n// Avoid 300ms click delay on touch devices that support 'touch-action'\na,\narea,\nbutton,\ninput,\nlabel,\nselect,\nsummary,\ntextarea,\n[role='button'] {\n  touch-action: manipulation;\n}\n\n///\n/// Embedded Content\nimg {\n  max-width: 100%;\n  height: auto;\n  border: 0;\n}\n\nimg,\nsvg {\n  display: block;\n  vertical-align: middle;\n}\n\n///\n/// Grouping Content\nfigure {\n  margin: 0;\n}\n\nhr {\n  box-sizing: content-box;\n  height: 0;\n}\n\npre {\n  margin-top: 0;\n  margin-bottom: 0;\n  overflow: auto;\n}\n\ncode,\nkbd,\nsamp,\npre {\n  font-family: monospace;\n  font-size: 1em;\n}\n\n///\n/// Forms\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n  margin: 0;\n  padding: 0;\n  font-family: inherit;\n  line-height: 1;\n  color: inherit;\n}\n\nbutton {\n  overflow: visible;\n}\n\nbutton,\nselect {\n  text-transform: none;\n}\n\nbutton,\n[role='button'],\ninput[type='button'],\ninput[type='reset'],\ninput[type='submit'] {\n  -webkit-appearance: button; // stylelint-disable-line property-no-vendor-prefix\n  cursor: pointer;\n}\n\ninput[disabled],\nbutton[disabled] {\n  cursor: default;\n}\n\nbutton::-moz-focus-inner,\n[type='button']::-moz-focus-inner,\n[type='reset']::-moz-focus-inner,\n[type='submit']::-moz-focus-inner {\n  padding: 0;\n  border-style: none;\n}\n\nbutton:-moz-focusring,\n[type='button']:-moz-focusring,\n[type='reset']:-moz-focusring,\n[type='submit']:-moz-focusring {\n  outline: 0.1rem dotted ButtonText;\n}\n\ninput {\n  line-height: normal;\n}\n\ninput[type='radio'],\ninput[type='checkbox'] {\n  box-sizing: border-box;\n  padding: 0;\n}\n\ninput[type='number']::-webkit-inner-spin-button,\ninput[type='number']::-webkit-outer-spin-button {\n  -webkit-appearance: none; // stylelint-disable-line property-no-vendor-prefix\n  margin: 0;\n  height: auto;\n}\n\ninput[type='search'] {\n  -webkit-appearance: textfield; // stylelint-disable-line property-no-vendor-prefix\n  outline-offset: -0.2rem;\n}\n\ninput[type='search']::-webkit-search-decoration,\ninput[type='search']::-webkit-search-cancel-button {\n  -webkit-appearance: none; // stylelint-disable-line property-no-vendor-prefix\n}\n\n::-webkit-file-upload-button {\n  -webkit-appearance: button; // stylelint-disable-line property-no-vendor-prefix\n  font: inherit;\n}\n\nfieldset {\n  margin: 0;\n  padding: 0;\n  border: none;\n}\n\nlegend {\n  border: 0;\n  padding: 0;\n}\n\nprogress {\n  vertical-align: baseline;\n}\n\ntextarea {\n  overflow: auto;\n}\n\noptgroup {\n  font-weight: bolder;\n}\n\nselect {\n  -webkit-appearance: none; // stylelint-disable-line property-no-vendor-prefix\n  -moz-appearance: none; // stylelint-disable-line property-no-vendor-prefix\n  cursor: pointer;\n}\n\nbutton,\ninput,\nselect,\ntextarea {\n  outline: none;\n  background: none;\n  border: none;\n  border-radius: 0;\n}\n\n[tabindex='-1']:focus {\n  // prevent focus outline on elements that cant be accessed via keyboard\n  // remove red border for invalid entries\n  outline: none !important; // stylelint-disable-line declaration-no-important\n}\n\nselect:focus {\n  outline: 0;\n}\n\ninput,\ninput:focus,\ninput:invalid,\nbutton:focus {\n  box-shadow: none;\n}\n\n/* stylelint-disable selector-no-vendor-prefix */\n::-webkit-input-placeholder {\n  box-shadow: none;\n}\n\n::-moz-placeholder {\n  box-shadow: none;\n  opacity: 1;\n}\n\n:-ms-input-placeholder {\n  box-shadow: none;\n}\n/* stylelint-enable selector-no-vendor-prefix */\n\n///\n/// Tables\ntable {\n  border-collapse: collapse;\n  border-spacing: 0;\n}\n\ntd,\nth {\n  padding: 0;\n}\n"
  },
  {
    "path": "examples/src/styles/utils/global/_animation.scss",
    "content": "////\n/// Global Utilities\n/// Animation\n////\n\n@keyframes jelly {\n  0% {\n    transform: scale(1, 1);\n  }\n  4% {\n    transform: scale(0.947, 0.961);\n  }\n  6% {\n    transform: scale(0.967, 0.99);\n  }\n  8% {\n    transform: scale(0.999, 1.034);\n  }\n  10% {\n    transform: scale(1.033, 1.075);\n  }\n  12% {\n    transform: scale(1.041, 1.084);\n  }\n  14% {\n    transform: scale(1.068, 1.1);\n  }\n  16% {\n    transform: scale(1.071, 1.1);\n  }\n  18% {\n    transform: scale(1.081, 1.086);\n  }\n  20% {\n    transform: scale(1.082, 1.078);\n  }\n  22% {\n    transform: scale(1.079, 1.06);\n  }\n  25% {\n    transform: scale(1.073, 1.044);\n  }\n  27% {\n    transform: scale(1.07, 1.041);\n  }\n  30% {\n    transform: scale(1.059, 1.035);\n  }\n  31% {\n    transform: scale(1.057, 1.035);\n  }\n  36% {\n    transform: scale(1.047, 1.044);\n  }\n  38% {\n    transform: scale(1.046, 1.047);\n  }\n  42% {\n    transform: scale(1.044, 1.053);\n  }\n  45% {\n    transform: scale(1.045, 1.055);\n  }\n  53% {\n    transform: scale(1.049, 1.051);\n  }\n  60% {\n    transform: scale(1.051, 1.049);\n  }\n  75% {\n    transform: scale(1.05, 1.05);\n  }\n  100% {\n    transform: scale(1.05, 1.05);\n  }\n}\n"
  },
  {
    "path": "examples/src/styles/utils/global/_layout.scss",
    "content": "////\n/// Global Utilities\n/// Layout\n////\n\n@import 'utils/shared/layout';\n\n.flex-item-fix {\n  @include flex-item-fix;\n}\n\n.visually-hidden {\n  @include visually-hidden;\n}\n\n.clearfix {\n  @include clearfix;\n}\n\n.hidden {\n  display: none !important; /* stylelint-disable-line declaration-no-important */\n}\n"
  },
  {
    "path": "examples/src/styles/utils/global/_typography.scss",
    "content": "////\n/// Global Utilities\n/// Typography\n////\n\n@import 'utils/shared/typography';\n\n.text-truncate {\n  @include text-truncate;\n}\n\n.text-truncate--inline-block {\n  @include text-truncate('inline-block');\n}\n\n.text-no-select {\n  @include text-no-select;\n}\n\n.text-center {\n  text-align: center;\n}\n\n.text-underline {\n  text-decoration: underline;\n}\n\n.text-nowrap {\n  white-space: nowrap;\n}\n\n.text-wrap {\n  white-space: normal;\n  word-wrap: break-word;\n  word-break: break-word;\n}\n\n.line-break--tablet-up {\n  display: none;\n\n  @media screen and (min-width: get-breakpoint(mobile, wide)) {\n    display: block;\n  }\n}\n"
  },
  {
    "path": "examples/src/styles/utils/global/global-utils.scss",
    "content": "////\n/// Global Utilities manifest\n////\n\n@import 'animation';\n@import 'layout';\n@import 'typography';\n"
  },
  {
    "path": "examples/src/styles/utils/shared/animation.scss",
    "content": "////\n/// Shared Utilities\n/// Animation\n////\n\n@mixin jelly-animation {\n  animation: jelly get-duration(slower) linear both;\n}\n"
  },
  {
    "path": "examples/src/styles/utils/shared/functions.scss",
    "content": "////\n/// Shared Utilities\n/// Functions\n////\n\n// Column based max-width layout\n// @param {Number} $count - Number of columns this element should span\n@function columns($count: 1) {\n  $column-count-max: 8;\n\n  @if type-of($count) != number or $count < 1 or $count > $column-count-max {\n    @error 'You did not provide a proper column count.';\n  }\n\n  $percentage: ($count / $column-count-max) * 100%;\n  $gutter-width: ($count - 1) * get-layout-length(gutter);\n\n  @return $percentage;\n}\n\n// Row based height layout\n// @param {Number} $count - Number of rows this element should span\n// @param {Boolean} $mobile - Use a smaller gutter if on mobile\n// @param {Boolean} $inclusive - For use when the element is meant to begin within a gutter\n@function rows($count: 1, $mobile: false, $inclusive: false) {\n  @if type-of($count) != number or $count < 1 {\n    @error 'You did not provide a proper row count.';\n  }\n\n  $gutter: if($mobile, get-layout-length(gutter, mobile), get-layout-length(gutter));\n  $inclusive-gutters: if($inclusive, $gutter, 0);\n\n  $row-height: $count * get-layout-length(row);\n  $gutter-height: ($count - 1) * $gutter - $inclusive-gutters;\n\n  @return $row-height + $gutter-height;\n}\n"
  },
  {
    "path": "examples/src/styles/utils/shared/layout.scss",
    "content": "////\n/// Shared Utilities\n/// Layout\n////\n\n/// Generate a selector chain to resolve draggable source layouts\n/// @param {ArgList} $nth-children - Any number of numeric values\n/// eg: (1, 2, 5, 7, 8, 9)\n@mixin draggable-source-layout($block-name, $nth-children...) {\n  $selector-chain: ();\n\n  @each $child in $nth-children {\n    $child-selector: unquote('.#{$block-name}:nth-child(#{$child})');\n    $adjacent-clone-selector: unquote(\n      '.#{$block-name}.draggable--original ~ .#{$block-name}:nth-child(#{$child + 1})'\n    );\n\n    $selector-chain: append($selector-chain, $child-selector, comma);\n    $selector-chain: append($selector-chain, $adjacent-clone-selector, comma);\n  }\n\n  #{$selector-chain} {\n    @content;\n  }\n}\n\n// Styles for (un)locking the ability to scroll the document.\n// Requires the `data-html-scroll` attribute on <html>.\n// Toggling of the attribute value is handled via JS.\n@mixin scroll-lock {\n  &[data-scroll-lock],\n  &[data-scroll-lock] body {\n    height: 100%;\n  }\n\n  &[data-scroll-lock='true'],\n  &[data-scroll-lock='true'] body {\n    overflow: hidden;\n    max-height: 100vh;\n  }\n}\n\n// Quick and easy max-width centered layout\n@mixin centered-width($max-width: null) {\n  margin-left: auto;\n  margin-right: auto;\n  max-width: $max-width;\n}\n\n// To be used on flex items. Resolves some common layout issues, such as\n// text truncation not respecting padding or breaking out of container.\n// https://css-tricks.com/flexbox-truncated-text/\n@mixin flex-item-fix {\n  min-width: 0;\n  max-width: 100%;\n}\n\n// Center any element, any where, any time\n@mixin flex-center($full-height: false) {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  flex-direction: column;\n  text-align: center;\n\n  @if $full-height {\n    height: 100vh;\n  }\n}\n\n// Toggle visibility + opacity\n// @param {String} $state - Switch visibility `on` or `off`.\n@mixin visible($isVisible: true) {\n  @if $isVisible {\n    opacity: 1;\n    visibility: visible;\n    pointer-events: auto;\n  } @else {\n    opacity: 0;\n    visibility: hidden;\n    pointer-events: none;\n  }\n}\n\n// Remove item from layout, but leave visible to screenreaders\n@mixin visually-hidden {\n  clip: rect(0 0 0 0);\n  position: absolute;\n  overflow: hidden;\n  margin: -0.1rem;\n  padding: 0;\n  width: 0.1rem;\n  height: 0.1rem;\n  border: 0;\n}\n\n// Set an element to cover its closest relatively positioned parent.\n// @param {String} $position (optional) - The position value you wish to use.\n// @param {Number} $z-index (optional) - Allows specifying a `z-index`\n@mixin position-cover($position: absolute, $z-index: auto) {\n  $positions-data: (static relative absolute fixed sticky);\n\n  @if not index($positions-data, $position) {\n    @error 'Position value `#{$position}` not found. Available positions: #{available-names($positions-data)}';\n  }\n\n  // can I specify it being unitless?\n  @if type-of($z-index) == number {\n    z-index: $z-index;\n  }\n\n  @if $position == 'static' {\n    position: static;\n    top: auto;\n    right: auto;\n    bottom: auto;\n    left: auto;\n    margin: 0;\n  } @else {\n    position: $position;\n    top: 0;\n    right: 0;\n    bottom: 0;\n    left: 0;\n    margin: auto;\n  }\n}\n\n// Private mixin - used by `aspect-ratio()`\n// @param {Number} $padding - Value to be used for padding-top.\n@mixin _aspect-ratio-padding-top($padding: 0%) {\n  padding-top: $padding;\n  width: 100%;\n  height: 0;\n}\n\n// Flexible element that retains its aspect-ratio\n// @param {Number} $width - Unitless ratio value used to represent width.\n// @param {Number} $height - Unitless ratio value used to represent height.\n// @param {Boolean} $psuedo (optional) - If you want the sizing to be managed by a ::before element.\n// ...elaborate on this explaination - what benefits does this provide?\n@mixin aspect-ratio($width, $height, $pseudo: false) {\n  // I think this conditional is broken\n  @if $width != unitless($width) or $height != unitless($height) {\n    @error 'You did not provide unitless numbers for `$width` and `$height`.';\n  }\n\n  $ratio-percentage: ($height / $width) * 100%;\n  position: relative;\n  overflow: hidden;\n\n  @if $pseudo {\n    &::before {\n      @include _aspect-ratio-padding-top($ratio-percentage);\n      content: '';\n      display: block;\n    }\n\n    // selector will not include pseudo elements\n    > * {\n      @include cover(absolute);\n    }\n  } @else {\n    @include _aspect-ratio-padding-top($ratio-percentage);\n  }\n}\n\n// Clear height for floated children\n// To be applied to a container\n@mixin clearfix {\n  &::after {\n    content: '';\n    display: table;\n    clear: both;\n  }\n}\n\n// Reset the `clearfix` style\n// Often used within a `@supports` block\n@mixin clearfix-reset {\n  &::after {\n    content: none;\n    display: none;\n    clear: none;\n  }\n}\n"
  },
  {
    "path": "examples/src/styles/utils/shared/typography.scss",
    "content": "////\n/// Shared Utilities\n/// Typography\n////\n\n// For truncating single line text.\n// @param {String} $display (optional) - The value to be used for `display`.\n// @param {Boolean} $descender-fix (optional) - Sometimes descenders are cutoff by overflow.\n@mixin text-truncate($display: block, $descender-fix: false) {\n  $padding-bottom: if($descender-fix, 0.25em, null);\n  display: $display;\n  padding-bottom: $padding-bottom;\n  max-width: 100%;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  overflow: hidden;\n}\n\n// For preventing any text selection.\n@mixin text-no-select {\n  pointer-events: none;\n  user-select: none;\n}\n"
  },
  {
    "path": "examples/src/views/collidable.html",
    "content": "{% extends 'templates/document.html' %}\n\n{% import 'components/Document/Head.html' as Head %}\n{% import 'components/Sidebar/Sidebar.html' as Sidebar %}\n{% import 'components/PageHeader/PageHeader.html' as PageHeader %}\n\n{% import 'content/Plugins/Collidable/Collidable.html' as Collidable %}\n\n{% set ViewAttr = {\n  id: 'Collidable',\n  parent: 'Plugins',\n  child: 'Collidable',\n  subheading: 'Enable collision detection by including the Collidable plugin. You can now resist dragging over all elements specified as collision obstacles.'\n} %}\n\n{% block PageId %}{{ ViewAttr.id }}{% endblock %}\n\n{% block head %}\n  {{ Head.render(ViewAttr) }}\n{% endblock %}\n\n{% block sidebar %}\n  {{ Sidebar.render(ViewAttr, DataPages) }}\n{% endblock %}\n\n{% block main %}\n  {{ PageHeader.render(ViewAttr) }}\n  {{ Collidable.render(ViewAttr.id) }}\n{% endblock %}\n"
  },
  {
    "path": "examples/src/views/data-pages.json",
    "content": "{\n  \"DataPages\": [\n    {\n      \"Draggable\": [\n        \"Drag events\",\n        \"~Restrict axis\"\n      ]\n    },\n    {\n      \"Droppable\": [\n        \"Unique dropzone\",\n        \"~Capacity\"\n      ]\n    },\n    {\n      \"Sortable\": [\n        \"Simple list\",\n        \"Transformed\",\n        \"Multiple containers\",\n        \"~Nested\"\n      ]\n    },\n    {\n      \"Swappable\": [\n        \"Flexbox\",\n        \"Floated\",\n        \"Grid layout\"\n      ]\n    },\n    {\n      \"Plugins\": [\n        \"Collidable\",\n        \"Snappable\",\n        \"~Swap Animation\",\n        \"Sort Animation\"\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "examples/src/views/drag-events.html",
    "content": "{% extends 'templates/document.html' %}\n\n{% import 'components/Document/Head.html' as Head %}\n{% import 'components/Sidebar/Sidebar.html' as Sidebar %}\n{% import 'components/PageHeader/PageHeader.html' as PageHeader %}\n\n{% import 'content/Draggable/DragEvents/DragEvents.html' as DragEvents %}\n\n{% set ViewAttr = {\n  id: 'DragEvents',\n  parent: 'Draggable',\n  child: 'Drag events',\n  subheading: 'You may not need to use any of the additional modules. Hook into any of Draggable’s events and write your own logic.'\n} %}\n\n{% block PageId %}{{ ViewAttr.id }}{% endblock %}\n\n{% block head %}\n  {{ Head.render(ViewAttr) }}\n{% endblock %}\n\n{% block sidebar %}\n  {{ Sidebar.render(ViewAttr, DataPages) }}\n{% endblock %}\n\n{% block main %}\n  {{ PageHeader.render(ViewAttr) }}\n  {{ DragEvents.render(ViewAttr.id) }}\n{% endblock %}\n"
  },
  {
    "path": "examples/src/views/flexbox.html",
    "content": "{% extends 'templates/document.html' %}\n\n{% import 'components/Document/Head.html' as Head %}\n{% import 'components/Sidebar/Sidebar.html' as Sidebar %}\n{% import 'components/PageHeader/PageHeader.html' as PageHeader %}\n\n{% import 'content/Swappable/Flexbox/Flexbox.html' as Flexbox %}\n\n{% set ViewAttr = {\n  id: 'Flexbox',\n  parent: 'Swappable',\n  child: 'Flexbox',\n  subheading: 'Maintaining layout while swapping direct children can be challenging. This example solves the problem using nth-child and adjacent sibling selectors.'\n} %}\n\n{% block PageId %}{{ ViewAttr.id }}{% endblock %}\n\n{% block head %}\n  {{ Head.render(ViewAttr) }}\n{% endblock %}\n\n{% block sidebar %}\n  {{ Sidebar.render(ViewAttr, DataPages) }}\n{% endblock %}\n\n{% block main %}\n  {{ PageHeader.render(ViewAttr) }}\n  {{ Flexbox.render(ViewAttr.id) }}\n{% endblock %}\n"
  },
  {
    "path": "examples/src/views/floated.html",
    "content": "{% extends 'templates/document.html' %}\n\n{% import 'components/Document/Head.html' as Head %}\n{% import 'components/Sidebar/Sidebar.html' as Sidebar %}\n{% import 'components/PageHeader/PageHeader.html' as PageHeader %}\n\n{% import 'content/Swappable/Floated/Floated.html' as Floated %}\n\n{% set ViewAttr = {\n  id: 'Floated',\n  parent: 'Swappable',\n  child: 'Floated',\n  subheading: 'Floated layouts work just like the Flexbox example. A bit of clever CSS can help mitigate any layout conflicts while dragging.'\n} %}\n\n{% block PageId %}{{ ViewAttr.id }}{% endblock %}\n\n{% block head %}\n  {{ Head.render(ViewAttr) }}\n{% endblock %}\n\n{% block sidebar %}\n  {{ Sidebar.render(ViewAttr, DataPages) }}\n{% endblock %}\n\n{% block main %}\n  {{ PageHeader.render(ViewAttr) }}\n  {{ Floated.render(ViewAttr.id) }}\n{% endblock %}\n"
  },
  {
    "path": "examples/src/views/grid-layout.html",
    "content": "{% extends 'templates/document.html' %}\n\n{% import 'components/Document/Head.html' as Head %}\n{% import 'components/Sidebar/Sidebar.html' as Sidebar %}\n{% import 'components/PageHeader/PageHeader.html' as PageHeader %}\n\n{% import 'content/Swappable/GridLayout/GridLayout.html' as GridLayout %}\n\n{% set ViewAttr = {\n  id: 'GridLayout',\n  parent: 'Swappable',\n  child: 'Grid layout',\n  subheading: 'Draggable children do not need to be direct descendants of their container. Wrapper elements can be used to maintain layout and simplify styling.'\n} %}\n\n{% block PageId %}{{ ViewAttr.id }}{% endblock %}\n\n{% block head %}\n  {{ Head.render(ViewAttr) }}\n{% endblock %}\n\n{% block sidebar %}\n  {{ Sidebar.render(ViewAttr, DataPages) }}\n{% endblock %}\n\n{% block main %}\n  {{ PageHeader.render(ViewAttr) }}\n  {{ GridLayout.render(ViewAttr.id) }}\n{% endblock %}\n"
  },
  {
    "path": "examples/src/views/index.html",
    "content": "{% extends 'templates/document.html' %}\n\n{% import 'components/Document/Head.html' as Head %}\n{% import 'components/Sidebar/Sidebar.html' as Sidebar %}\n{% import 'components/PageHeader/PageHeader.html' as PageHeader %}\n\n{% import 'content/Home/Home.html' as Home %}\n\n{% set ViewAttr = {\n  id: 'Home',\n  parent: 'Examples',\n  subheading: 'Draggable is a modern drag and drop JavaScript library. Lightweight, modular and accessible.'\n} %}\n\n{% block PageId %}{{ ViewAttr.id }}{% endblock %}\n\n{% block head %}\n  {{ Head.render(ViewAttr) }}\n{% endblock %}\n\n{% block sidebar %}\n  {{ Sidebar.render(ViewAttr, DataPages) }}\n{% endblock %}\n\n{% block main %}\n  {{ PageHeader.render(ViewAttr) }}\n  {{ Home.render(ViewAttr.id) }}\n{% endblock %}\n"
  },
  {
    "path": "examples/src/views/multiple-containers.html",
    "content": "{% extends 'templates/document.html' %}\n\n{% import 'components/Document/Head.html' as Head %}\n{% import 'components/Sidebar/Sidebar.html' as Sidebar %}\n{% import 'components/PageHeader/PageHeader.html' as PageHeader %}\n\n{% import 'content/Sortable/MultipleContainers/MultipleContainers.html' as MultipleContainers %}\n\n{% set ViewAttr = {\n  id: 'MultipleContainers',\n  parent: 'Sortable',\n  child: 'Multiple containers',\n  subheading: 'Sort elements across multiple containers. Each container can maintain its own set of rules.'\n} %}\n\n{% block PageId %}{{ ViewAttr.id }}{% endblock %}\n\n{% block head %}\n  {{ Head.render(ViewAttr) }}\n{% endblock %}\n\n{% block sidebar %}\n  {{ Sidebar.render(ViewAttr, DataPages) }}\n{% endblock %}\n\n{% block main %}\n  {{ PageHeader.render(ViewAttr) }}\n  {{ MultipleContainers.render(ViewAttr.id) }}\n{% endblock %}\n"
  },
  {
    "path": "examples/src/views/simple-list.html",
    "content": "{% extends 'templates/document.html' %}\n\n{% import 'components/Document/Head.html' as Head %}\n{% import 'components/Sidebar/Sidebar.html' as Sidebar %}\n{% import 'components/PageHeader/PageHeader.html' as PageHeader %}\n\n{% import 'content/Sortable/SimpleList/SimpleList.html' as SimpleList %}\n\n{% set ViewAttr = {\n  id: 'SimpleList',\n  parent: 'Sortable',\n  child: 'Simple list',\n  subheading: 'Sort elements in a single collection, maintaining order for all but the element being dragged.'\n} %}\n\n{% block PageId %}{{ ViewAttr.id }}{% endblock %}\n\n{% block head %}\n  {{ Head.render(ViewAttr) }}\n{% endblock %}\n\n{% block sidebar %}\n  {{ Sidebar.render(ViewAttr, DataPages) }}\n{% endblock %}\n\n{% block main %}\n  {{ PageHeader.render(ViewAttr) }}\n  {{ SimpleList.render(ViewAttr.id) }}\n{% endblock %}\n"
  },
  {
    "path": "examples/src/views/snappable.html",
    "content": "{% extends 'templates/document.html' %}\n\n{% import 'components/Document/Head.html' as Head %}\n{% import 'components/Sidebar/Sidebar.html' as Sidebar %}\n{% import 'components/PageHeader/PageHeader.html' as PageHeader %}\n\n{% import 'content/Plugins/Snappable/Snappable.html' as Snappable %}\n\n{% set ViewAttr = {\n  id: 'Snappable',\n  parent: 'Plugins',\n  child: 'Snappable',\n  subheading: 'Snappable turns draggable elements into suction cups. Drag an item close enough to another draggable and it will snap into place.'\n} %}\n\n{% block PageId %}{{ ViewAttr.id }}{% endblock %}\n\n{% block head %}\n  {{ Head.render(ViewAttr) }}\n{% endblock %}\n\n{% block sidebar %}\n  {{ Sidebar.render(ViewAttr, DataPages) }}\n{% endblock %}\n\n{% block main %}\n  {{ PageHeader.render(ViewAttr) }}\n  {{ Snappable.render(ViewAttr.id) }}\n{% endblock %}\n"
  },
  {
    "path": "examples/src/views/sort-animation.html",
    "content": "{% extends 'templates/document.html' %}\n\n{% import 'components/Document/Head.html' as Head %}\n{% import 'components/Sidebar/Sidebar.html' as Sidebar %}\n{% import 'components/PageHeader/PageHeader.html' as PageHeader %}\n\n{% import 'content/Plugins/SortAnimation/SortAnimation.html' as SortAnimation %}\n\n{% set ViewAttr = {\n  id: 'SortAnimation',\n  parent: 'Plugins',\n  child: 'Sort Animation',\n  subheading: 'Adds sort animation on all sorted elements with both horizontal and vertical within grid layout'\n} %}\n\n{% block PageId %}{{ ViewAttr.id }}{% endblock %}\n\n{% block head %}\n  {{ Head.render(ViewAttr) }}\n{% endblock %}\n\n{% block sidebar %}\n  {{ Sidebar.render(ViewAttr, DataPages) }}\n{% endblock %}\n\n{% block main %}\n  {{ PageHeader.render(ViewAttr) }}\n  {{ SortAnimation.render(ViewAttr.id) }}\n{% endblock %}\n"
  },
  {
    "path": "examples/src/views/swap-animation.html",
    "content": "{% extends 'templates/document.html' %}\n\n{% import 'components/Document/Head.html' as Head %}\n{% import 'components/Sidebar/Sidebar.html' as Sidebar %}\n{% import 'components/PageHeader/PageHeader.html' as PageHeader %}\n\n{% import 'content/Plugins/SwapAnimation/SwapAnimation.html' as SwapAnimation %}\n\n{% set ViewAttr = {\n  id: 'SwapAnimation',\n  parent: 'Plugins',\n  child: 'Swap Animation',\n  subheading: 'This example is currently waiting for SwapAnimation to support translating both X and Y coordinates.'\n} %}\n\n{% block PageId %}{{ ViewAttr.id }}{% endblock %}\n\n{% block head %}\n  {{ Head.render(ViewAttr) }}\n{% endblock %}\n\n{% block sidebar %}\n  {{ Sidebar.render(ViewAttr, DataPages) }}\n{% endblock %}\n\n{% block main %}\n  {{ PageHeader.render(ViewAttr) }}\n  {{ SwapAnimation.render(ViewAttr.id) }}\n{% endblock %}\n"
  },
  {
    "path": "examples/src/views/templates/document.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\" data-scroll-lock=\"false\">\n  {% block head %}{% endblock %}\n\n  <body id=\"Page{% block PageId %}{% endblock %}\">\n    {% include 'components/Hamburger/Hamburger.html' %}\n\n    {% block sidebar %}{% endblock %}\n\n    <main class=\"Main\" role=\"main\">\n      <div class=\"MainInterior\">\n        {% block main %}{% endblock %}\n      </div>\n    </main>\n\n    <div class=\"GridOverlay GridOverlay--isHidden\"></div>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/src/views/transformed.html",
    "content": "{% extends 'templates/document.html' %}\n\n{% import 'components/Document/Head.html' as Head %}\n{% import 'components/Sidebar/Sidebar.html' as Sidebar %}\n{% import 'components/PageHeader/PageHeader.html' as PageHeader %}\n\n{% import 'content/Sortable/Transformed/Transformed.html' as Transformed %}\n\n{% set ViewAttr = {\n  id: 'Transformed',\n  parent: 'Sortable',\n  child: 'Transformed',\n  subheading: 'Draggable’s mirror is positioned using transform3d(). Any existing transform styles will get removed. This is solved by introducing an interior wrapper.'\n} %}\n\n{% block PageId %}{{ ViewAttr.id }}{% endblock %}\n\n{% block head %}\n  {{ Head.render(ViewAttr) }}\n{% endblock %}\n\n{% block sidebar %}\n  {{ Sidebar.render(ViewAttr, DataPages) }}\n{% endblock %}\n\n{% block main %}\n  {{ PageHeader.render(ViewAttr) }}\n  {{ Transformed.render(ViewAttr.id) }}\n{% endblock %}\n"
  },
  {
    "path": "examples/src/views/unique-dropzone.html",
    "content": "{% extends 'templates/document.html' %}\n\n{% import 'components/Document/Head.html' as Head %}\n{% import 'components/Sidebar/Sidebar.html' as Sidebar %}\n{% import 'components/PageHeader/PageHeader.html' as PageHeader %}\n\n{% import 'content/Droppable/UniqueDropzone/UniqueDropzone.html' as UniqueDropzone %}\n\n{% set ViewAttr = {\n  id: 'UniqueDropzone',\n  parent: 'Droppable',\n  child: 'Unique dropzone',\n  subheading: 'Dropzones are intended to hold only one Droppable child. This example restricts each Droppable item to a specific Dropzone identifier.'\n} %}\n\n{% block PageId %}{{ ViewAttr.id }}{% endblock %}\n\n{% block head %}\n  {{ Head.render(ViewAttr) }}\n{% endblock %}\n\n{% block sidebar %}\n  {{ Sidebar.render(ViewAttr, DataPages) }}\n{% endblock %}\n\n{% block main %}\n  {{ PageHeader.render(ViewAttr) }}\n  {{ UniqueDropzone.render(ViewAttr.id) }}\n{% endblock %}\n"
  },
  {
    "path": "examples/tools/index.js",
    "content": "import gulp from 'gulp';\n\nimport tasks from './tasks';\nimport {watch} from './watch';\n\nexport const scripts = tasks.scripts;\nexport const styles = tasks.styles;\nexport const views = tasks.views;\nexport const server = tasks.startServer;\nexport const start = watch;\n\nexport const build = gulp.parallel(scripts, styles, views);\n"
  },
  {
    "path": "examples/tools/server.js",
    "content": "import browsersync from 'browser-sync';\n\nexport const server = browsersync.create();\n\n// reload function requires a callback in order to reload properly\nexport function reloadServer(done) {\n  server.reload();\n  done();\n}\n\nexport function startServer() {\n  const serverConfig = {\n    open: false,\n    server: './dist',\n    ghostMode: false,\n  };\n\n  server.init(serverConfig);\n}\n"
  },
  {
    "path": "examples/tools/tasks/index.js",
    "content": "import {startServer} from '../server';\n\nimport {scripts} from './scripts';\nimport {styles} from './styles';\nimport {views} from './views';\n\nconst tasks = {\n  startServer,\n  scripts,\n  styles,\n  views,\n};\n\nexport default tasks;\n"
  },
  {
    "path": "examples/tools/tasks/scripts.js",
    "content": "import webpack from 'webpack';\n\nimport {webpackConfig} from '../webpack.config';\n\nexport function scripts() {\n  return new Promise((resolve) =>\n    webpack(webpackConfig, (error, stats) => {\n      if (error) {\n        console.log('Webpack', error);\n      }\n\n      console.log(stats.toString({colors: true}));\n\n      resolve();\n    }),\n  );\n}\n"
  },
  {
    "path": "examples/tools/tasks/styles.js",
    "content": "import gulp from 'gulp';\nimport sass from 'gulp-sass';\nimport postcss from 'gulp-postcss';\nimport sourcemaps from 'gulp-sourcemaps';\n\nimport {server} from '../server';\n\nexport const srcStyles = 'src/styles/';\nconst distStyles = 'dist/assets/css/';\nconst stylesInputPath = `${srcStyles}examples-app.scss`;\nconst sassOptions = {\n  includePaths: ['./src', './node_modules'],\n};\n\nexport function styles() {\n  return gulp\n    .src(stylesInputPath)\n    .pipe(sourcemaps.init())\n    .pipe(sass(sassOptions).on('error', sass.logError))\n    .pipe(postcss())\n    .pipe(sourcemaps.write('./'))\n    .pipe(gulp.dest(distStyles))\n    .pipe(server.stream({match: '**/*.css'}));\n}\n"
  },
  {
    "path": "examples/tools/tasks/views.js",
    "content": "import fs from 'fs';\n\nimport gulp from 'gulp';\nimport data from 'gulp-data';\nimport htmlmin from 'gulp-htmlmin';\nimport nunjucks from 'nunjucks';\nimport gulpjucks from 'gulp-nunjucks';\n\nconst srcViews = 'src/views/';\nconst distViews = 'dist/';\nconst extViews = '*.+(html|njk)';\nexport const extAllViews = '*.+(html|njk|json|md)';\n\nexport function views() {\n  const nunjucksEnv = new nunjucks.Environment([\n    new nunjucks.FileSystemLoader('src/views'),\n    new nunjucks.FileSystemLoader('src'),\n  ]);\n  const dataPages = JSON.parse(fs.readFileSync('src/views/data-pages.json'));\n\n  return gulp\n    .src(`${srcViews}${extViews}`)\n    .pipe(data(() => dataPages))\n    .pipe(\n      gulpjucks.compile(\n        {},\n        {\n          env: nunjucksEnv,\n        },\n      ),\n    )\n    .pipe(\n      htmlmin({\n        minifyJS: true,\n        removeComments: true,\n        collapseWhitespace: true,\n      }),\n    )\n    .pipe(gulp.dest(distViews));\n}\n"
  },
  {
    "path": "examples/tools/watch.js",
    "content": "import gulp from 'gulp';\n\nimport {reloadServer, startServer} from './server';\nimport {scripts} from './tasks/scripts';\nimport {styles} from './tasks/styles';\nimport {extAllViews, views} from './tasks/views';\n\nfunction reportWatchStats(path) {\n  console.log(`File ${path} was changed`);\n}\n\nexport function watch() {\n  startServer();\n\n  gulp.watch(['packages/**/*.js', 'src/**/*.js'], gulp.series(scripts, reloadServer));\n\n  // only need to watch `styles` task, as it will `stream` changes to the server\n  const watchStyles = gulp.watch('src/**/*.scss', styles);\n  watchStyles.on('change', reportWatchStats);\n\n  const watchViews = gulp.watch(`src/**/${extAllViews}`, gulp.series(views, reloadServer));\n  watchViews.on('change', reportWatchStats);\n}\n"
  },
  {
    "path": "examples/tools/webpack.config.js",
    "content": "/* eslint-env node */\n\nimport path from 'path';\n\nimport initPlugins from './webpack.plugins';\n\nconst distPath = path.resolve(__dirname, '../dist');\nconst assetsPath = '/assets/js/';\nconst isProd = process.env.NODE_ENV === 'production';\nconst srcApp = 'src/scripts/examples-app.js';\nconst regexNodeMods = /[\\\\/]node_modules[\\\\/]/;\n\nexport const webpackConfig = {\n  mode: isProd ? 'production' : 'development',\n  context: distPath,\n  devtool: 'source-map',\n  entry: {\n    'examples-app': `../${srcApp}`,\n  },\n  output: {\n    path: distPath + assetsPath,\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.js$/,\n        exclude: regexNodeMods,\n        loader: 'babel-loader',\n      },\n    ],\n  },\n  optimization: {\n    runtimeChunk: {\n      name: 'examples-runtime',\n    },\n    splitChunks: {\n      cacheGroups: {\n        commons: {\n          test: regexNodeMods,\n          name: 'examples-vendor',\n          chunks: 'all',\n        },\n      },\n    },\n  },\n  // this slows down compile time, but since we are consuming the `lib` draggable,\n  // this makes our `import` paths cleaner and consistent\n  resolve: {\n    modules: [path.resolve(__dirname, '../packages'), path.resolve(__dirname, '../node_modules')],\n  },\n  plugins: initPlugins(isProd),\n};\n"
  },
  {
    "path": "examples/tools/webpack.plugins.js",
    "content": "import * as bundleAnalyzer from 'webpack-bundle-analyzer';\n\nexport default function initPlugins(isProd = false) {\n  const plugins = [];\n\n  if (isProd) {\n    console.log('Running in `production`');\n  }\n\n  plugins.push(\n    new bundleAnalyzer.BundleAnalyzerPlugin({\n      analyzerMode: 'static',\n      openAnalyzer: false,\n      reportFilename: '../../../bundle-report.html',\n    }),\n  );\n\n  return plugins;\n}\n"
  },
  {
    "path": "index.d.ts",
    "content": "declare module '@shopify/draggable/lib/draggable.bundle.legacy' {\n  export * from '@shopify/draggable';\n}\n\ndeclare module '@shopify/draggable/lib/es5/draggable.bundle' {\n  export * from '@shopify/draggable';\n}\n\ndeclare module '@shopify/draggable/lib/es5/draggable.bundle.legacy' {\n  export * from '@shopify/draggable';\n}\n\ndeclare module '@shopify/draggable' {\n  abstract class AbstractEvent<TData = {[key: string]: any}> {\n    // Abstract, waiting on https://github.com/Microsoft/TypeScript/issues/14600\n    static readonly type: string;\n    // Abstract, waiting on https://github.com/Microsoft/TypeScript/issues/14600\n    static readonly cancelable: boolean;\n    readonly type: string;\n    readonly cancelable: boolean;\n    constructor(data: TData);\n    cancel(): void;\n    canceled(): boolean;\n    clone(): AbstractEvent<TData>;\n  }\n\n  export {AbstractEvent as BaseEvent};\n\n  type GetEventByEventName<TEventName> =\n    TEventName extends 'draggable:initialize'\n      ? DraggableInitializedEvent\n      : TEventName extends 'draggable:destroy'\n      ? DraggableDestroyEvent\n      : TEventName extends 'drag:start'\n      ? DragStartEvent\n      : TEventName extends 'drag:move'\n      ? DragMoveEvent\n      : TEventName extends 'drag:over'\n      ? DragOverEvent\n      : TEventName extends 'drag:over:container'\n      ? DragOverContainerEvent\n      : TEventName extends 'drag:out'\n      ? DragOutEvent\n      : TEventName extends 'drag:out:container'\n      ? DragOutContainerEvent\n      : TEventName extends 'drag:stop'\n      ? DragStopEvent\n      : TEventName extends 'drag:stopped'\n      ? DragStoppedEvent\n      : TEventName extends 'drag:pressure'\n      ? DragPressureEvent\n      : TEventName extends 'mirror:create'\n      ? MirrorCreateEvent\n      : TEventName extends 'mirror:created'\n      ? MirrorCreatedEvent\n      : TEventName extends 'mirror:attached'\n      ? MirrorAttachedEvent\n      : TEventName extends 'mirror:move'\n      ? MirrorMoveEvent\n      : TEventName extends 'mirror:moved'\n      ? MirrorMovedEvent\n      : TEventName extends 'mirror:destroy'\n      ? MirrorDestroyEvent\n      : TEventName extends 'droppable:start'\n      ? DroppableStartEvent\n      : TEventName extends 'droppable:dropped'\n      ? DroppableDroppedEvent\n      : TEventName extends 'droppable:returned'\n      ? DroppableReturnedEvent\n      : TEventName extends 'droppable:stop'\n      ? DroppableStopEvent\n      : TEventName extends 'sortable:start'\n      ? SortableStartEvent\n      : TEventName extends 'sortable:sort'\n      ? SortableSortEvent\n      : TEventName extends 'sortable:sorted'\n      ? SortableSortedEvent\n      : TEventName extends 'sortable:stop'\n      ? SortableStopEvent\n      : TEventName extends 'swappable:start'\n      ? SwappableStartEvent\n      : TEventName extends 'swappable:swap'\n      ? SwappableSwapEvent\n      : TEventName extends 'swappable:swapped'\n      ? SwappableSwappedEvent\n      : TEventName extends 'swappable:stop'\n      ? SwappableStopEvent\n      : TEventName extends 'collidable:in'\n      ? CollidableInEvent\n      : TEventName extends 'collidable:out'\n      ? CollidableOutEvent\n      : TEventName extends 'snap:in'\n      ? SnapInEvent\n      : TEventName extends 'snap:out'\n      ? SnapOutEvent\n      : AbstractEvent;\n\n  export interface DelayOptions {\n    mouse?: number;\n    drag?: number;\n    touch?: number;\n  }\n\n  /**\n   * DragEvent\n   */\n  export class DragEvent extends AbstractEvent {\n    readonly source: HTMLElement;\n    readonly originalSource: HTMLElement;\n    readonly mirror: HTMLElement;\n    readonly sourceContainer: HTMLElement;\n    readonly sensorEvent: SensorEvent;\n    readonly originalEvent: Event;\n  }\n\n  export class DragStartEvent extends DragEvent {}\n\n  export class DragMoveEvent extends DragEvent {}\n\n  export class DragOverEvent extends DragEvent {\n    readonly overContainer: HTMLElement;\n    readonly over: HTMLElement;\n  }\n\n  export class DragOutEvent extends DragEvent {\n    readonly overContainer: HTMLElement;\n    readonly over: HTMLElement;\n  }\n\n  export class DragOverContainerEvent extends DragEvent {\n    readonly overContainer: HTMLElement;\n  }\n\n  export class DragOutContainerEvent extends DragEvent {\n    readonly overContainer: HTMLElement;\n  }\n\n  export class DragPressureEvent extends DragEvent {\n    readonly pressure: number;\n  }\n\n  export class DragStopEvent extends DragEvent {}\n\n  export class DragStoppedEvent extends DragEvent {}\n\n  /**\n   * DraggableEvent\n   */\n  export type DraggableEventNames =\n    | 'draggable:initialize'\n    | 'draggable:destroy'\n    | 'drag:start'\n    | 'drag:move'\n    | 'drag:over'\n    | 'drag:over:container'\n    | 'drag:out'\n    | 'drag:out:container'\n    | 'drag:stop'\n    | 'drag:pressure'\n    | MirrorEventNames;\n\n  export class DraggableEvent extends AbstractEvent {\n    readonly draggable: Draggable;\n  }\n  export class DraggableInitializedEvent extends DraggableEvent {}\n  export class DraggableDestroyEvent extends DraggableEvent {}\n\n  export type DraggableClassNames =\n    | 'body:dragging'\n    | 'container:dragging'\n    | 'source:dragging'\n    | 'source:placed'\n    | 'container:placed'\n    | 'draggable:over'\n    | 'container:over'\n    | 'source:original'\n    | 'mirror';\n\n  export type DraggableContainer = HTMLElement | HTMLElement[] | NodeList;\n\n  export interface DraggableOptions {\n    draggable?: string;\n    distance?: number;\n    handle?:\n      | string\n      | NodeList\n      | HTMLElement[]\n      | HTMLElement\n      | ((currentElement: HTMLElement) => HTMLElement);\n    delay?: number | DelayOptions;\n    plugins?: (typeof AbstractPlugin)[];\n    sensors?: Sensor[];\n    classes?: {[key in DraggableClassNames]: string | string[]};\n    announcements?: AnnouncementOptions;\n    collidables?: Collidables;\n    mirror?: MirrorOptions;\n    scrollable?: ScrollableOptions;\n    swapAnimation?: SwapAnimationOptions;\n    sortAnimation?: SortAnimationOptions;\n  }\n\n  export class Draggable<TEventListType = DraggableEventNames> {\n    static Plugins: {\n      Announcement: typeof Announcement;\n      Focusable: typeof Focusable;\n      Mirror: typeof Mirror;\n      Scrollable: typeof Scrollable;\n    };\n\n    constructor(containers: DraggableContainer, options?: DraggableOptions);\n    destroy(): void;\n    on<T extends TEventListType>(\n      eventName: T,\n      callback: (event: GetEventByEventName<T>) => void,\n    ): this;\n\n    off<T extends TEventListType>(\n      eventName: T,\n      callback: (event: GetEventByEventName<T>) => void,\n    ): this;\n\n    trigger(event: AbstractEvent): void;\n    addPlugin(...plugins: (typeof AbstractPlugin)[]): this;\n    removePlugin(...plugins: (typeof AbstractPlugin)[]): this;\n    addSensor(...sensors: (typeof Sensor)[]): this;\n    removeSensor(...sensors: (typeof Sensor)[]): this;\n    addContainer(...containers: HTMLElement[]): this;\n    removeContainer(...containers: HTMLElement[]): this;\n    getClassNameFor(name: DraggableClassNames): string;\n    getClassNamesFor(name: DraggableClassNames): string[];\n    isDragging(): boolean;\n    getDraggableElementsForContainer(container: HTMLElement): HTMLElement[];\n  }\n\n  export abstract class AbstractPlugin {\n    protected draggable: Draggable;\n    constructor(draggable: Draggable);\n    protected abstract attach(): void;\n    protected abstract detach(): void;\n  }\n\n  export {AbstractPlugin as BasePlugin};\n\n  /**\n   * Announcement Plugin\n   */\n  export interface AnnouncementOptions {\n    expire: number;\n    [key: string]: string | (() => string) | number;\n  }\n\n  class Announcement extends AbstractPlugin {\n    options: AnnouncementOptions;\n    protected attach(): void;\n    protected detach(): void;\n  }\n\n  /**\n   * Focusable Plugin\n   */\n  class Focusable extends AbstractPlugin {\n    getElements(): HTMLElement[];\n    protected attach(): void;\n    protected detach(): void;\n  }\n\n  /**\n   * Mirror Plugin\n   */\n  export type MirrorEventNames =\n    | 'mirror:create'\n    | 'mirror:created'\n    | 'mirror:attached'\n    | 'mirror:move'\n    | 'mirror:destroy';\n\n  export class MirrorEvent extends AbstractEvent {\n    readonly source: HTMLElement;\n    readonly originalSource: HTMLElement;\n    readonly sourceContainer: HTMLElement;\n    readonly sensorEvent: SensorEvent;\n    readonly originalEvent: Event;\n  }\n  export class MirrorCreateEvent extends MirrorEvent {}\n  export class MirrorCreatedEvent extends MirrorEvent {\n    readonly mirror: HTMLElement;\n  }\n  export class MirrorAttachedEvent extends MirrorEvent {\n    readonly mirror: HTMLElement;\n  }\n  export class MirrorMoveEvent extends MirrorEvent {\n    readonly mirror: HTMLElement;\n    readonly passedThreshX: boolean;\n    readonly passedThreshY: boolean;\n  }\n  export class MirrorMovedEvent extends MirrorEvent {\n    readonly mirror: HTMLElement;\n    readonly passedThreshX: boolean;\n    readonly passedThreshY: boolean;\n  }\n  export class MirrorDestroyEvent extends MirrorEvent {\n    readonly mirror: HTMLElement;\n  }\n\n  export interface MirrorOptions {\n    xAxis?: boolean;\n    yAxis?: boolean;\n    constrainDimensions?: boolean;\n    cursorOffsetX?: number;\n    cursorOffsetY?: number;\n    appendTo?: string | HTMLElement | ((source: HTMLElement) => HTMLElement);\n  }\n\n  class Mirror extends AbstractPlugin {\n    getElements(): HTMLElement[];\n    protected attach(): void;\n    protected detach(): void;\n  }\n\n  /**\n   * Scrollable Plugin\n   */\n  export interface ScrollableOptions {\n    speed?: number;\n    sensitivity?: number;\n    scrollableElements?: HTMLElement[];\n  }\n\n  class Scrollable extends AbstractPlugin {\n    protected attach(): void;\n    protected detach(): void;\n  }\n\n  /**\n   * Sensors\n   */\n  export class SensorEvent extends AbstractEvent {\n    readonly originalEvent: Event;\n    readonly clientX: number;\n    readonly clientY: number;\n    readonly target: HTMLElement;\n    readonly container: HTMLElement;\n    readonly pressure: number;\n  }\n\n  export class DragStartSensorEvent extends SensorEvent {}\n\n  export class DragMoveSensorEvent extends SensorEvent {}\n\n  export class DragStopSensorEvent extends SensorEvent {}\n\n  export class DragPressureSensorEvent extends SensorEvent {}\n\n  export interface SensorOptions {\n    delay?: number | DelayOptions;\n  }\n\n  export class Sensor {\n    constructor(\n      containers: HTMLElement | HTMLElement[] | NodeList,\n      options?: SensorOptions,\n    );\n\n    attach(): this;\n    detach(): this;\n    addContainer(...containers: HTMLElement[]): void;\n    removeContainer(...containers: HTMLElement[]): void;\n    trigger(element: HTMLElement, sensorEvent: SensorEvent): SensorEvent;\n  }\n\n  export interface Sensors {\n    DragSensor: typeof DragSensor;\n  }\n\n  export class DragSensor extends Sensor {}\n\n  export class ForceTouchSensor extends Sensor {}\n\n  export class MouseSensor extends Sensor {}\n\n  export class TouchSensor extends Sensor {}\n\n  /**\n   * Droppable\n   */\n  export type DroppableEventNames =\n    | 'droppable:start'\n    | 'droppable:dropped'\n    | 'droppable:returned'\n    | 'droppable:stop'\n    | DraggableEventNames;\n\n  export class DroppableEvent extends AbstractEvent {\n    readonly dragEvent: DragEvent;\n  }\n\n  export class DroppableStartEvent extends DroppableEvent {\n    dropzone: HTMLElement;\n  }\n\n  export class DroppableDroppedEvent extends DroppableEvent {\n    dropzone: HTMLElement;\n  }\n\n  export class DroppableReturnedEvent extends DroppableEvent {\n    dropzone: HTMLElement;\n  }\n\n  export class DroppableStopEvent extends DroppableEvent {\n    dropzone: HTMLElement;\n  }\n\n  export type DroppableClassNames =\n    | DraggableClassNames\n    | 'droppable:active'\n    | 'droppable:occupied';\n\n  export interface DroppableOptions extends DraggableOptions {\n    dropzone:\n      | string\n      | NodeList\n      | HTMLElement[]\n      | (() => NodeList | HTMLElement[]);\n    classes?: {[key in DroppableClassNames]: string};\n  }\n\n  export class Droppable<T = DroppableEventNames> extends Draggable<T> {\n    constructor(containers: DraggableContainer, options: DroppableOptions);\n    getClassNameFor(name: DroppableClassNames): string;\n  }\n\n  /**\n   * Sortable\n   */\n  export type SortableEventNames =\n    | 'sortable:start'\n    | 'sortable:sort'\n    | 'sortable:sorted'\n    | 'sortable:stop'\n    | DraggableEventNames;\n\n  export class SortableEvent extends AbstractEvent {\n    readonly dragEvent: DragEvent;\n  }\n\n  export class SortableStartEvent extends SortableEvent {\n    readonly startIndex: number;\n    readonly startContainer: HTMLElement;\n  }\n\n  export class SortableSortEvent extends SortableEvent {\n    readonly oldIndex: number;\n    readonly newIndex: number;\n    readonly oldContainer: HTMLElement;\n    readonly newContainer: HTMLElement;\n  }\n\n  export class SortableSortedEvent extends SortableEvent {\n    readonly oldIndex: number;\n    readonly newIndex: number;\n    readonly oldContainer: HTMLElement;\n    readonly newContainer: HTMLElement;\n  }\n\n  export class SortableStopEvent extends SortableEvent {\n    readonly oldIndex: number;\n    readonly newIndex: number;\n    readonly oldContainer: HTMLElement;\n    readonly newContainer: HTMLElement;\n  }\n\n  export class Sortable<T = SortableEventNames> extends Draggable<T> {}\n\n  /**\n   * Swappable\n   */\n  export type SwappableEventNames =\n    | 'swappable:start'\n    | 'swappable:swap'\n    | 'swappable:swapped'\n    | 'swappable:stop'\n    | DraggableEventNames;\n\n  export class SwappableEvent extends AbstractEvent {\n    readonly dragEvent: DragEvent;\n  }\n\n  export class SwappableStartEvent extends SwappableEvent {}\n\n  export class SwappableSwapEvent extends SwappableEvent {\n    readonly over: HTMLElement;\n    readonly overContainer: HTMLElement;\n  }\n\n  export class SwappableSwappedEvent extends SwappableEvent {\n    readonly swappedElement: HTMLElement;\n  }\n\n  export class SwappableStopEvent extends SwappableEvent {}\n\n  export class Swappable<T = SwappableEventNames> extends Draggable<T> {}\n\n  /**\n   * Collidable Plugin\n   */\n  export type CollidableEventNames = 'collidable:in' | 'collidable:out';\n\n  export class CollidableEvent extends AbstractEvent {\n    readonly dragEvent: DragEvent;\n    readonly collidingElement: HTMLElement;\n  }\n  export class CollidableInEvent extends CollidableEvent {}\n  export class CollidableOutEvent extends CollidableEvent {}\n\n  export type Collidables =\n    | string\n    | NodeList\n    | HTMLElement[]\n    | (() => NodeList | HTMLElement[]);\n\n  class Collidable extends AbstractPlugin {\n    protected attach(): void;\n    protected detach(): void;\n  }\n\n  /**\n   * ResizeMirror Plugin\n   */\n  class ResizeMirror extends AbstractPlugin {\n    protected attach(): void;\n    protected detach(): void;\n  }\n\n  /**\n   * Snappable Plugin\n   */\n  export type SnappableEventNames = 'snap:in' | 'snap:out';\n\n  export class SnapEvent extends AbstractEvent {\n    readonly dragEvent: DragEvent;\n    readonly snappable: HTMLElement;\n  }\n  export class SnapInEvent extends SnapEvent {}\n  export class SnapOutEvent extends SnapEvent {}\n\n  class Snappable extends AbstractPlugin {\n    protected attach(): void;\n    protected detach(): void;\n  }\n\n  /**\n   * SwapAnimation Plugin\n   */\n  export interface SwapAnimationOptions {\n    duration: number;\n    easingFunction: string;\n    horizontal: boolean;\n  }\n\n  class SwapAnimation extends AbstractPlugin {\n    protected attach(): void;\n    protected detach(): void;\n  }\n\n  /**\n   * SortAnimation\n   */\n  export interface SortAnimationOptions {\n    duration?: number;\n    easingFunction?: string;\n  }\n\n  class SortAnimation extends AbstractPlugin {\n    protected attach(): void;\n    protected detach(): void;\n  }\n\n  export const Plugins: {\n    Collidable: typeof Collidable;\n    SwapAnimation: typeof SwapAnimation;\n    SortAnimation: typeof SortAnimation;\n    ResizeMirror: typeof ResizeMirror;\n    Snappable: typeof Snappable;\n  };\n}\n"
  },
  {
    "path": "jest.config.js",
    "content": "module.exports = {\n  testEnvironment: 'jsdom',\n  testMatch: ['<rootDir>/src/**/*.test.(js|ts)'],\n  setupFiles: ['<rootDir>/test/environment.ts'],\n  setupFilesAfterEnv: ['<rootDir>/test/setup.ts'],\n  transform: {\n    '\\\\.(ts|js)': ['babel-jest'],\n  },\n  moduleFileExtensions: ['js', 'ts'],\n  collectCoverageFrom: [\n    'src/**/*.{js,ts}',\n    '!src/**/*/index.{js,ts}',\n    '!src/index.{js,ts}',\n  ],\n  moduleNameMapper: {\n    'shared/(.*)': '<rootDir>/src/shared/$1',\n  },\n  coverageDirectory: './coverage/',\n  collectCoverage: true,\n  moduleDirectories: ['node_modules', 'src', 'test'],\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"@shopify/draggable\",\n  \"version\": \"1.2.1\",\n  \"private\": false,\n  \"license\": \"MIT\",\n  \"description\": \"The JavaScript Drag & Drop library your grandparents warned you about.\",\n  \"author\": \"Shopify <dev@shopify.com>\",\n  \"homepage\": \"https://github.com/Shopify/draggable#readme\",\n  \"repository\": \"https://github.com/Shopify/draggable\",\n  \"contributors\": [\n    {\n      \"name\": \"Max Hoffmann\",\n      \"email\": \"max.hoffmann@shopify.com\"\n    }\n  ],\n  \"bugs\": {\n    \"url\": \"https://github.com/Shopify/draggable/issues\"\n  },\n  \"keywords\": [\n    \"shopify\",\n    \"draggable\",\n    \"drag-and-drop\",\n    \"es6\"\n  ],\n  \"main\": \"build/cjs/index.cjs\",\n  \"module\": \"build/esm/index.mjs\",\n  \"esnext\": \"build/esnext/index.mjs\",\n  \"umd\": \"build/umd/index.js\",\n  \"types\": \"./index.d.ts\",\n  \"scripts\": {\n    \"start\": \"concurrently \\\"yarn watch\\\" \\\"cd examples && yarn && yarn start\\\"\",\n    \"build\": \"yarn build:production\",\n    \"watch\": \"yarn build:development --watch\",\n    \"release\": \"yarn run build:production && changeset publish\",\n    \"lint\": \"eslint ./src ./test --max-warnings 0\",\n    \"type-check\": \"tsc -b\",\n    \"esdoc\": \"esdoc -c esdoc.json\",\n    \"test\": \"jest\",\n    \"build:development\": \"yarn rollup --config rollup.development.config.ts --configPlugin @rollup/plugin-typescript\",\n    \"build:production\": \"tsc && tsc-alias && yarn rollup --config rollup.config.ts --configPlugin @rollup/plugin-typescript && yarn uglifyjs --compress --mangle -- build/umd/index.js -o build/umd/index.min.js\",\n    \"verify\": \"yarn lint && yarn type-check && yarn build && yarn build:development && yarn test && yarn --cwd ./examples build\"\n  },\n  \"files\": [\n    \"build/**/*\",\n    \"!build/ts/**/*.tsbuildinfo\",\n    \"!build/ts/**/tests/\",\n    \"!build/ts/**/test/\",\n    \"index.d.ts\"\n  ],\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.22.20\",\n    \"@babel/plugin-proposal-decorators\": \"^7.23.0\",\n    \"@babel/preset-env\": \"^7.22.20\",\n    \"@babel/preset-typescript\": \"^7.23.0\",\n    \"@changesets/changelog-github\": \"^0.4.8\",\n    \"@changesets/cli\": \"^2.26.2\",\n    \"@rollup/plugin-babel\": \"^6.0.3\",\n    \"@rollup/plugin-commonjs\": \"^25.0.4\",\n    \"@rollup/plugin-node-resolve\": \"^15.2.1\",\n    \"@rollup/plugin-typescript\": \"^11.1.4\",\n    \"@shopify/eslint-plugin\": \"^43.0.0\",\n    \"@shopify/prettier-config\": \"^1.1.2\",\n    \"@shopify/typescript-configs\": \"^5.1.0\",\n    \"@types/jest\": \"^29.5.5\",\n    \"@types/node\": \"^20.6.3\",\n    \"concurrently\": \"^3.5.1\",\n    \"esdoc\": \"^1.1.0\",\n    \"esdoc-ecmascript-proposal-plugin\": \"^1.0.0\",\n    \"esdoc-standard-plugin\": \"^1.0.0\",\n    \"eslint\": \"^8.49.0\",\n    \"jest\": \"^29.7.0\",\n    \"jest-environment-jsdom\": \"^29.7.0\",\n    \"prettier\": \"^3.0.3\",\n    \"rollup\": \"^3.29.3\",\n    \"rollup-plugin-cleanup\": \"^3.2.1\",\n    \"rollup-plugin-includepaths\": \"^0.2.4\",\n    \"rollup-plugin-node-externals\": \"^6.1.1\",\n    \"timers\": \"^0.1.1\",\n    \"tsc-alias\": \"^1.8.8\",\n    \"typescript\": \"^5.2.2\",\n    \"uglify-js\": \"^3.17.4\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"@shopify:registry\": \"https://registry.npmjs.org\",\n    \"provenance\": false\n  },\n  \"browserslist\": [\n    \"last 3 chrome versions\",\n    \"last 3 firefox versions\",\n    \"last 3 opera versions\",\n    \"last 3 edge versions\",\n    \"last 3 safari versions\",\n    \"last 3 chromeandroid versions\",\n    \"last 1 firefoxandroid versions\",\n    \"ios >= 13.4\"\n  ]\n}\n"
  },
  {
    "path": "rollup.config.ts",
    "content": "import {readFileSync} from 'fs';\nimport * as path from 'path';\n\nimport {RollupOptions} from 'rollup';\nimport {babel, RollupBabelInputPluginOptions} from '@rollup/plugin-babel';\nimport commonjs from '@rollup/plugin-commonjs';\nimport {nodeResolve} from '@rollup/plugin-node-resolve';\nimport includePaths from 'rollup-plugin-includepaths';\nimport cleanupPlugin from 'rollup-plugin-cleanup';\n\nconst packageJSON = readFileSync(\n  new URL('./package.json', import.meta.url).pathname,\n) as unknown;\n\nconst pkg = JSON.parse(packageJSON as string);\nconst extensions = ['.js', '.ts'];\n\nexport function generateConfig({\n  output,\n  targets,\n}: {\n  output: RollupOptions['output'];\n  targets: RollupBabelInputPluginOptions['targets'];\n}): RollupOptions {\n  return {\n    input: './src/index',\n    plugins: [\n      includePaths({\n        include: {\n          shared: 'src/shared',\n        },\n        paths: ['src/'],\n        extensions: [...extensions],\n      }),\n      nodeResolve({extensions: [...extensions]}),\n      commonjs(),\n      babel({\n        extensions,\n        exclude: 'node_modules/**',\n        babelHelpers: 'bundled',\n        envName: 'production',\n        targets,\n      }),\n      cleanupPlugin({\n        extensions: [...extensions],\n        maxEmptyLines: 1,\n      }),\n    ],\n    output,\n  };\n}\n\nconst config = [\n  generateConfig({\n    targets: [...pkg.browserslist],\n    output: [\n      {\n        format: 'cjs',\n        dir: path.dirname(pkg.main),\n        preserveModules: true,\n        entryFileNames: '[name].cjs',\n        exports: 'named',\n      },\n      {\n        format: 'esm',\n        dir: path.dirname(pkg.module),\n        preserveModules: true,\n        entryFileNames: '[name].mjs',\n      },\n      {\n        format: 'umd',\n        dir: path.dirname(pkg.umd),\n        name: 'Draggable',\n      },\n    ],\n  }),\n  generateConfig({\n    targets: 'last 1 chrome versions',\n    output: [\n      {\n        format: 'esm',\n        dir: path.dirname(pkg.esnext),\n        preserveModules: true,\n        entryFileNames: '[name].mjs',\n      },\n    ],\n  }),\n];\n\nexport default config;\n"
  },
  {
    "path": "rollup.development.config.ts",
    "content": "import {generateConfig} from './rollup.config';\n\nconst config = generateConfig({\n  targets: 'last 1 chrome versions',\n  output: [\n    {\n      format: 'umd',\n      dir: './examples/packages/@shopify/draggable',\n      name: 'Draggable',\n    },\n  ],\n});\n\nexport default config;\n"
  },
  {
    "path": "src/Draggable/DragEvent/DragEvent.ts",
    "content": "import AbstractEvent from 'shared/AbstractEvent';\n\nimport {SensorEvent} from '../Sensors/SensorEvent';\n\n/**\n * DragEventData\n * @interface DragEventData\n */\nexport interface DragEventData {\n  source: HTMLElement;\n  originalSource: HTMLElement;\n  mirror: HTMLElement;\n  sourceContainer: HTMLElement;\n  sensorEvent: SensorEvent;\n}\n\n/**\n * Base drag event\n * @class DragEvent\n * @module DragEvent\n * @extends AbstractEvent\n */\nexport class DragEvent<\n  T extends DragEventData,\n> extends AbstractEvent<DragEventData> {\n  static type = 'drag';\n\n  /**\n   * DragEvent constructor.\n   * @constructs DragEvent\n   * @param {DragEventData} data - Event data\n   */\n  constructor(public data: T) {\n    super(data);\n  }\n\n  /**\n   * Draggables source element\n   * @property source\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get source() {\n    return this.data.source;\n  }\n\n  /**\n   * Draggables original source element\n   * @property originalSource\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get originalSource() {\n    return this.data.originalSource;\n  }\n\n  /**\n   * Draggables mirror element\n   * @property mirror\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get mirror() {\n    return this.data.mirror;\n  }\n\n  /**\n   * Draggables source container element\n   * @property sourceContainer\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get sourceContainer() {\n    return this.data.sourceContainer;\n  }\n\n  /**\n   * Sensor event\n   * @property sensorEvent\n   * @type {SensorEvent}\n   * @readonly\n   */\n  get sensorEvent() {\n    return this.data.sensorEvent;\n  }\n\n  /**\n   * Original event that triggered sensor event\n   * @property originalEvent\n   * @type {Event}\n   * @readonly\n   */\n  get originalEvent() {\n    if (this.sensorEvent) {\n      return this.sensorEvent.originalEvent;\n    }\n\n    return null;\n  }\n}\n\n/**\n * Drag start event\n * @class DragStartEvent\n * @module DragStartEvent\n * @extends DragEvent\n */\nexport class DragStartEvent extends DragEvent<DragEventData> {\n  static type = 'drag:start';\n  static cancelable = true;\n}\n\n/**\n * Drag move event\n * @class DragMoveEvent\n * @module DragMoveEvent\n * @extends DragEvent\n */\nexport class DragMoveEvent extends DragEvent<DragEventData> {\n  static type = 'drag:move';\n}\n\n/**\n * DragOverEventData\n * @interface DragOverEventData\n */\nexport interface DragOverEventData extends DragEventData {\n  overContainer: HTMLElement;\n  over: HTMLElement;\n}\n\n/**\n * Drag over event\n * @class DragOverEvent\n * @module DragOverEvent\n * @extends DragEvent\n */\nexport class DragOverEvent extends DragEvent<DragOverEventData> {\n  static type = 'drag:over';\n  static cancelable = true;\n\n  /**\n   * Draggable container you are over\n   * @property overContainer\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get overContainer() {\n    return this.data.overContainer;\n  }\n\n  /**\n   * Draggable element you are over\n   * @property over\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get over() {\n    return this.data.over;\n  }\n}\n\nexport function isDragOverEvent(\n  event: AbstractEvent<unknown>,\n): event is DragOverEvent {\n  return event.type === DragOverEvent.type;\n}\n\n/**\n * DragOutEventData\n * @interface DragOutEventData\n */\ninterface DragOutEventData extends DragEventData {\n  overContainer: HTMLElement;\n  over: HTMLElement;\n}\n\n/**\n * Drag out event\n * @class DragOutEvent\n * @module DragOutEvent\n * @extends DragEvent\n */\nexport class DragOutEvent extends DragEvent<DragOutEventData> {\n  static type = 'drag:out';\n\n  /**\n   * Draggable container you are over\n   * @property overContainer\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get overContainer() {\n    return this.data.overContainer;\n  }\n\n  /**\n   * Draggable element you left\n   * @property over\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get over() {\n    return this.data.over;\n  }\n}\n\n/**\n * DragOverContainerEventData\n * @interface DragOverContainerEventData\n */\ninterface DragOverContainerEventData extends DragEventData {\n  overContainer: HTMLElement;\n}\n\n/**\n * Drag over container event\n * @class DragOverContainerEvent\n * @module DragOverContainerEvent\n * @extends DragEvent\n */\nexport class DragOverContainerEvent extends DragEvent<DragOverContainerEventData> {\n  static type = 'drag:over:container';\n\n  /**\n   * Draggable container you are over\n   * @property overContainer\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get overContainer() {\n    return this.data.overContainer;\n  }\n}\n\n/**\n * DragOutContainerEventData\n * @interface DragOutContainerEventData\n */\ninterface DragOutContainerEventData extends DragEventData {\n  overContainer: HTMLElement;\n}\n\n/**\n * Drag out container event\n * @class DragOutContainerEvent\n * @module DragOutContainerEvent\n * @extends DragEvent\n */\nexport class DragOutContainerEvent extends DragEvent<DragOutContainerEventData> {\n  static type = 'drag:out:container';\n\n  /**\n   * Draggable container you left\n   * @property overContainer\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get overContainer() {\n    return this.data.overContainer;\n  }\n}\n\n/**\n * DragPressureEventData\n * @interface DragPressureEventData\n */\ninterface DragPressureEventData extends DragEventData {\n  pressure: number;\n}\n\n/**\n * Drag pressure event\n * @class DragPressureEvent\n * @module DragPressureEvent\n * @extends DragEvent\n */\nexport class DragPressureEvent extends DragEvent<DragPressureEventData> {\n  static type = 'drag:pressure';\n\n  /**\n   * Pressure applied on draggable element\n   * @property pressure\n   * @type {Number}\n   * @readonly\n   */\n  get pressure() {\n    return this.data.pressure;\n  }\n}\n\n/**\n * Drag stop event\n * @class DragStopEvent\n * @module DragStopEvent\n * @extends DragEvent\n */\nexport class DragStopEvent extends DragEvent<DragEventData> {\n  static type = 'drag:stop';\n  static cancelable = true;\n}\n\n/**\n * Drag stopped event\n * @class DragStoppedEvent\n * @module DragStoppedEvent\n * @extends DragEvent\n */\nexport class DragStoppedEvent extends DragEvent<DragEventData> {\n  static type = 'drag:stopped';\n}\n"
  },
  {
    "path": "src/Draggable/DragEvent/README.md",
    "content": "# Drag event\n\n## DragEvent\n\nThe base event for all Drag events that `Draggable` emits.\n\n|                   |             |\n| ----------------- | ----------- |\n| **Specification** | `DragEvent` |\n| **Interface**     | `DragEvent` |\n| **Cancelable**    | false       |\n| **Cancel action** | -           |\n| **type**          | `drag`      |\n\n### API\n\n**`dragEvent.source: HTMLElement`**  \nRead-only property for the source element. This is a straight copy of the `originalSource`\nelement, which can be moved around in the DOM.\n\n**`dragEvent.originalSource: String`**  \nRead-only property for the original source element that was picked up. This element never\nmoves in the DOM and gets hidden on `drag:start`.\n\n**`dragEvent.sourceContainer: String`**  \nRead-only property for the source elements container. This would be one of the containers that\nwas passed into Draggable.\n\n**`dragEvent.sensorEvent: SensorEvent`**  \nRead-only property for the original sensor event that triggered this event.\n\n**`dragEvent.originalEvent: SensorEvent`**  \nRead-only property for the original event that triggered the sensor event.\n\n## DragStartEvent\n\n`DragStartEvent` gets triggered by `Draggable` when drag interaction has started.\n\n|                   |                     |\n| ----------------- | ------------------- |\n| **Specification** | `DragEvent`         |\n| **Interface**     | `DragStartEvent`    |\n| **Cancelable**    | true                |\n| **Cancel action** | Prevents drag start |\n| **type**          | `drag:start`        |\n\n## DragMoveEvent\n\n`DragMoveEvent` gets triggered while moving the mouse after the `DragStartEvent` has triggered.\n\n|                   |                 |\n| ----------------- | --------------- |\n| **Specification** | `DragEvent`     |\n| **Interface**     | `DragMoveEvent` |\n| **Cancelable**    | false           |\n| **Cancel action** | -               |\n| **type**          | `drag:move`     |\n\n## DragOverEvent\n\n`DragOverEvent` gets triggered when hovering over another draggable element during a drag\ninteraction.\n\n|                   |                                                       |\n| ----------------- | ----------------------------------------------------- |\n| **Specification** | `DragEvent`                                           |\n| **Interface**     | `DragOverEvent`                                       |\n| **Cancelable**    | true                                                  |\n| **Cancel action** | Cancels default actions in `Sortable` and `Swappable` |\n| **type**          | `drag:over`                                           |\n\n### API\n\n**`dragOverEvent.over: HTMLElement`**  \nRead-only property for the draggable element that you are hovering over.\n\n**`dragOverEvent.overContainer: HTMLElement`**  \nRead-only property for the draggable container element that you are hovering over.\n\n## DragOutEvent\n\n`DragOutEvent` gets triggered after a `DragOverEvent` and indicates that you are leaving\na draggable element.\n\n|                   |                |\n| ----------------- | -------------- |\n| **Specification** | `DragEvent`    |\n| **Interface**     | `DragOutEvent` |\n| **Cancelable**    | false          |\n| **Cancel action** | -              |\n| **type**          | `drag:out`     |\n\n### API\n\n**`dragOutEvent.over: HTMLElement`**  \nRead-only property for the draggable element that you are leaving.\n\n**`dragOutEvent.overContainer: HTMLElement`**  \nRead-only property for the draggable container element that you are hovering over.\n\n## DragOverContainerEvent\n\n`DragOverContainerEvent` gets triggered when hovering over a container, other than the `sourceContainer` in `DragStartEvent`.\n\n|                   |                          |\n| ----------------- | ------------------------ |\n| **Specification** | `DragEvent`              |\n| **Interface**     | `DragOverContainerEvent` |\n| **Cancelable**    | false                    |\n| **Cancel action** | -                        |\n| **type**          | `drag:over:container`    |\n\n### API\n\n**`dragOverContainerEvent.overContainer: HTMLElement`**  \nRead-only property for the draggable container element that you are hovering over.\n\n## DragOutContainerEvent\n\n`DragOutContainerEvent` gets triggered after a `DragOverContainerEvent` and indicates that\nyou are leaving a draggable container element.\n\n|                   |                         |\n| ----------------- | ----------------------- |\n| **Specification** | `DragEvent`             |\n| **Interface**     | `DragOutContainerEvent` |\n| **Cancelable**    | false                   |\n| **Cancel action** | -                       |\n| **type**          | `drag:out:container`    |\n\n### API\n\n**`dragOutContainerEvent.overContainer: HTMLElement`**  \nRead-only property for the draggable container element that you are leaving.\n\n## DragPressureEvent\n\n`DragPressureEvent` gets triggered before and during drag interactions. This event\nonly fires when the `ForceTouchSensor` is included as a Sensor and a Force Touch trackpad\nis used with Safari.\n\n|                   |                     |\n| ----------------- | ------------------- |\n| **Specification** | `DragEvent`         |\n| **Interface**     | `DragPressureEvent` |\n| **Cancelable**    | false               |\n| **Cancel action** | -                   |\n| **type**          | `drag:pressure`     |\n\n### API\n\n**`dragPressureEvent.pressure: HTMLElement`**  \nRead-only property for pressure applied on a draggable element. Value ranges from `0.0` (no pressure) to `1.0` (maximum pressure).\n\n## DragStopEvent\n\n`DragStopEvent` gets triggered after `DragStartEvent`, once drag interactions have completed.\n\n|                   |                                                    |\n| ----------------- | -------------------------------------------------- |\n| **Specification** | `DragEvent`                                        |\n| **Interface**     | `DragStopEvent`                                    |\n| **Cancelable**    | true                                               |\n| **Cancel action** | Prevent item from being added where it was dropped |\n| **type**          | `drag:stop`                                        |\n\n## DragStoppedEvent\n\n`DragStoppedEvent` gets triggered after `DragStopEvent`. This event fires after `drag:stop` listeners have finished running,\nthe source element removed from the document and draggable classes are removed.\n\n|                   |                    |\n| ----------------- | ------------------ |\n| **Specification** | `DragEvent`        |\n| **Interface**     | `DragStoppedEvent` |\n| **Cancelable**    | false              |\n| **Cancel action** | -                  |\n| **type**          | `drag:stopped`     |\n"
  },
  {
    "path": "src/Draggable/DragEvent/index.ts",
    "content": "export * from './DragEvent';\n"
  },
  {
    "path": "src/Draggable/DragEvent/tests/DragEvent.test.ts",
    "content": "import {\n  DragEvent,\n  DragMoveEvent,\n  DragOutContainerEvent,\n  DragOutEvent,\n  DragOverContainerEvent,\n  DragOverEvent,\n  DragPressureEvent,\n  DragStartEvent,\n  DragStopEvent,\n  DragStoppedEvent,\n} from '../DragEvent';\nimport {SensorEvent} from '../../Sensors/SensorEvent';\n\nconst sensorEvent = new SensorEvent({\n  originalEvent: new Event('click'),\n  clientX: 0,\n  clientY: 0,\n  target: document.createElement('div'),\n  container: document.createElement('div'),\n  originalSource: document.createElement('div'),\n  pressure: 0,\n});\n\nconst defaultDragEventOptions = {\n  source: document.createElement('div'),\n  originalSource: document.createElement('div'),\n  mirror: document.createElement('div'),\n  sourceContainer: document.createElement('div'),\n  sensorEvent,\n};\n\ndescribe('DragEvent', () => {\n  describe('#constructor', () => {\n    it('should be instance of DragEvent', () => {\n      const event = new DragEvent(defaultDragEventOptions);\n\n      expect(event).toBeInstanceOf(DragEvent);\n    });\n\n    it('should initialize with `type` of `event`', () => {\n      const event = new DragEvent(defaultDragEventOptions);\n\n      expect(event.type).toBe('drag');\n    });\n\n    it('should initialize with source', () => {\n      const source = document.createElement('div');\n\n      const event = new DragEvent({\n        ...defaultDragEventOptions,\n        source,\n      });\n\n      expect(event.source).toBe(source);\n    });\n\n    it('should initialize with mirror', () => {\n      const mirror = document.createElement('div');\n\n      const event = new DragEvent({\n        ...defaultDragEventOptions,\n        mirror,\n      });\n\n      expect(event.mirror).toBe(mirror);\n    });\n\n    it('should initialize with sourceContainer', () => {\n      const sourceContainer = document.createElement('div');\n\n      const event = new DragEvent({\n        ...defaultDragEventOptions,\n        sourceContainer,\n      });\n\n      expect(event.sourceContainer).toBe(sourceContainer);\n    });\n\n    it('should initialize with sensorEvent', () => {\n      const event = new DragEvent(defaultDragEventOptions);\n\n      expect(event.sensorEvent).toBe(defaultDragEventOptions.sensorEvent);\n    });\n\n    it('should initialize with originalEvent', () => {\n      const originalEvent = new Event('click');\n\n      const event = new DragEvent({\n        ...defaultDragEventOptions,\n        sensorEvent: new SensorEvent({\n          ...sensorEvent.data,\n          originalEvent,\n        }),\n      });\n\n      expect(event.originalEvent).toBe(originalEvent);\n    });\n  });\n\n  describe('#originalEvent', () => {\n    it('should return null when initialized without sensorEvent', () => {\n      const event = new DragEvent({\n        ...defaultDragEventOptions,\n        sensorEvent: new SensorEvent({\n          ...sensorEvent.data,\n          originalEvent: undefined,\n        }),\n      });\n\n      expect(event.originalEvent).toBeUndefined();\n    });\n  });\n});\n\ndescribe('DragStartEvent', () => {\n  describe('#constructor', () => {\n    it('should be instance of DragStartEvent', () => {\n      const event = new DragStartEvent(defaultDragEventOptions);\n\n      expect(event).toBeInstanceOf(DragStartEvent);\n    });\n\n    it('should initialize with `type` of `drag:start`', () => {\n      const event = new DragStartEvent(defaultDragEventOptions);\n\n      expect(event.type).toBe('drag:start');\n    });\n  });\n});\n\ndescribe('DragMoveEvent', () => {\n  describe('#constructor', () => {\n    it('should be instance of DragMoveEvent', () => {\n      const event = new DragMoveEvent(defaultDragEventOptions);\n\n      expect(event).toBeInstanceOf(DragMoveEvent);\n    });\n\n    it('should initialize with `type` of `drag:move`', () => {\n      const event = new DragMoveEvent(defaultDragEventOptions);\n\n      expect(event.type).toBe('drag:move');\n    });\n  });\n});\n\nconst defaultDragOutContainerEventOptions = {\n  ...defaultDragEventOptions,\n  overContainer: document.createElement('div'),\n};\n\ndescribe('DragOutContainerEvent', () => {\n  describe('#constructor', () => {\n    it('should be instance of DragOutContainerEvent', () => {\n      const event = new DragOutContainerEvent(\n        defaultDragOutContainerEventOptions,\n      );\n\n      expect(event).toBeInstanceOf(DragOutContainerEvent);\n    });\n\n    it('should initialize with `type` of `drag:out:container`', () => {\n      const event = new DragOutContainerEvent(\n        defaultDragOutContainerEventOptions,\n      );\n\n      expect(event.type).toBe('drag:out:container');\n    });\n\n    it('should initialize with overContainer', () => {\n      const overContainer = document.createElement('div');\n\n      const event = new DragOutContainerEvent({\n        ...defaultDragOutContainerEventOptions,\n        overContainer,\n      });\n\n      expect(event.overContainer).toBe(overContainer);\n    });\n  });\n});\n\nconst defaultDragOutEventOptions = {\n  ...defaultDragEventOptions,\n  overContainer: document.createElement('div'),\n  over: document.createElement('div'),\n};\n\ndescribe('DragOutEvent', () => {\n  describe('#constructor', () => {\n    it('should be instance of DragOutEvent', () => {\n      const event = new DragOutEvent(defaultDragOutEventOptions);\n\n      expect(event).toBeInstanceOf(DragOutEvent);\n    });\n\n    it('should initialize with `type` of `drag:out`', () => {\n      const event = new DragOutEvent(defaultDragOutEventOptions);\n\n      expect(event.type).toBe('drag:out');\n    });\n\n    it('should initialize with overContainer', () => {\n      const overContainer = document.createElement('div');\n\n      const event = new DragOutEvent({\n        ...defaultDragOutEventOptions,\n        overContainer,\n      });\n\n      expect(event.overContainer).toBe(overContainer);\n    });\n\n    it('should initialize with over', () => {\n      const over = document.createElement('div');\n\n      const event = new DragOutEvent({\n        ...defaultDragOutEventOptions,\n        over,\n      });\n\n      expect(event.over).toBe(over);\n    });\n  });\n});\n\nconst defaultDragOverContainerEventOptions = {\n  ...defaultDragEventOptions,\n  overContainer: document.createElement('div'),\n  over: document.createElement('div'),\n};\n\ndescribe('DragOverContainerEvent', () => {\n  describe('#constructor', () => {\n    it('should be instance of DragOverContainerEvent', () => {\n      const event = new DragOverContainerEvent(\n        defaultDragOverContainerEventOptions,\n      );\n\n      expect(event).toBeInstanceOf(DragOverContainerEvent);\n    });\n\n    it('should initialize with `type` of `drag:over:container`', () => {\n      const event = new DragOverContainerEvent(\n        defaultDragOverContainerEventOptions,\n      );\n\n      expect(event.type).toBe('drag:over:container');\n    });\n\n    it('should initialize with overContainer', () => {\n      const overContainer = document.createElement('div');\n\n      const event = new DragOverContainerEvent({\n        ...defaultDragOverContainerEventOptions,\n        overContainer,\n      });\n\n      expect(event.overContainer).toBe(overContainer);\n    });\n  });\n});\n\nconst defaultDragOverEventOptions = {\n  ...defaultDragEventOptions,\n  overContainer: document.createElement('div'),\n  over: document.createElement('div'),\n};\n\ndescribe('DragOverEvent', () => {\n  describe('#constructor', () => {\n    it('should be instance of DragOverEvent', () => {\n      const event = new DragOverEvent(defaultDragOverEventOptions);\n\n      expect(event).toBeInstanceOf(DragOverEvent);\n    });\n\n    it('should initialize with `type` of `drag:over`', () => {\n      const event = new DragOverEvent(defaultDragOverEventOptions);\n\n      expect(event.type).toBe('drag:over');\n    });\n\n    it('should initialize with overContainer', () => {\n      const overContainer = document.createElement('div');\n\n      const event = new DragOverEvent({\n        ...defaultDragOverEventOptions,\n        overContainer,\n      });\n\n      expect(event.overContainer).toBe(overContainer);\n    });\n\n    it('should initialize with over', () => {\n      const over = document.createElement('div');\n\n      const event = new DragOverEvent({\n        ...defaultDragOverEventOptions,\n        over,\n      });\n\n      expect(event.over).toBe(over);\n    });\n  });\n});\n\nconst defaultDragPressureEventOptions = {\n  ...defaultDragEventOptions,\n  pressure: 0,\n};\n\ndescribe('DragPressureEvent', () => {\n  describe('#constructor', () => {\n    it('should be instance of DragPressureEvent', () => {\n      const event = new DragPressureEvent(defaultDragPressureEventOptions);\n\n      expect(event).toBeInstanceOf(DragPressureEvent);\n    });\n\n    it('should initialize with `type` of `drag:pressure`', () => {\n      const event = new DragPressureEvent(defaultDragPressureEventOptions);\n\n      expect(event.type).toBe('drag:pressure');\n    });\n\n    it('should initialize with pressure', () => {\n      const pressure = 0.5;\n\n      const event = new DragPressureEvent({\n        ...defaultDragPressureEventOptions,\n        pressure,\n      });\n\n      expect(event.pressure).toBe(pressure);\n    });\n  });\n});\n\ndescribe('DragStopEvent', () => {\n  describe('#constructor', () => {\n    it('should be instance of DragStopEvent', () => {\n      const event = new DragStopEvent(defaultDragEventOptions);\n\n      expect(event).toBeInstanceOf(DragStopEvent);\n    });\n\n    it('should initialize with `type` of `drag:stop`', () => {\n      const event = new DragStopEvent(defaultDragEventOptions);\n\n      expect(event.type).toBe('drag:stop');\n    });\n  });\n});\n\ndescribe('DragStoppedEvent', () => {\n  describe('#constructor', () => {\n    it('should be instance of DragStoppedEvent', () => {\n      const event = new DragStoppedEvent(defaultDragEventOptions);\n\n      expect(event).toBeInstanceOf(DragStoppedEvent);\n    });\n\n    it('should initialize with `type` of `drag:stopped`', () => {\n      const event = new DragStoppedEvent(defaultDragEventOptions);\n\n      expect(event.type).toBe('drag:stopped');\n    });\n  });\n});\n"
  },
  {
    "path": "src/Draggable/Draggable.js",
    "content": "import {closest} from 'shared/utils';\n\nimport {Announcement, Focusable, Mirror, Scrollable} from './Plugins';\nimport Emitter from './Emitter';\nimport {MouseSensor, TouchSensor} from './Sensors';\nimport {\n  DraggableInitializedEvent,\n  DraggableDestroyEvent,\n} from './DraggableEvent';\nimport {\n  DragStartEvent,\n  DragMoveEvent,\n  DragOutContainerEvent,\n  DragOutEvent,\n  DragOverContainerEvent,\n  DragOverEvent,\n  DragStopEvent,\n  DragPressureEvent,\n  DragStoppedEvent,\n} from './DragEvent';\n\nconst onDragStart = Symbol('onDragStart');\nconst onDragMove = Symbol('onDragMove');\nconst onDragStop = Symbol('onDragStop');\nconst onDragPressure = Symbol('onDragPressure');\nconst dragStop = Symbol('dragStop');\n\n/**\n * @const {Object} defaultAnnouncements\n * @const {Function} defaultAnnouncements['drag:start']\n * @const {Function} defaultAnnouncements['drag:stop']\n */\nconst defaultAnnouncements = {\n  'drag:start': (event) =>\n    `Picked up ${\n      event.source.textContent.trim() || event.source.id || 'draggable element'\n    }`,\n  'drag:stop': (event) =>\n    `Released ${\n      event.source.textContent.trim() || event.source.id || 'draggable element'\n    }`,\n};\n\nconst defaultClasses = {\n  'container:dragging': 'draggable-container--is-dragging',\n  'source:dragging': 'draggable-source--is-dragging',\n  'source:placed': 'draggable-source--placed',\n  'container:placed': 'draggable-container--placed',\n  'body:dragging': 'draggable--is-dragging',\n  'draggable:over': 'draggable--over',\n  'container:over': 'draggable-container--over',\n  'source:original': 'draggable--original',\n  mirror: 'draggable-mirror',\n};\n\nexport const defaultOptions = {\n  draggable: '.draggable-source',\n  handle: null,\n  delay: {},\n  distance: 0,\n  placedTimeout: 800,\n  plugins: [],\n  sensors: [],\n  exclude: {\n    plugins: [],\n    sensors: [],\n  },\n};\n\n/**\n * This is the core draggable library that does the heavy lifting\n * @class Draggable\n * @module Draggable\n */\nexport default class Draggable {\n  /**\n   * Default plugins draggable uses\n   * @static\n   * @property {Object} Plugins\n   * @property {Announcement} Plugins.Announcement\n   * @property {Focusable} Plugins.Focusable\n   * @property {Mirror} Plugins.Mirror\n   * @property {Scrollable} Plugins.Scrollable\n   * @type {Object}\n   */\n  static Plugins = {Announcement, Focusable, Mirror, Scrollable};\n\n  /**\n   * Default sensors draggable uses\n   * @static\n   * @property {Object} Sensors\n   * @property {MouseSensor} Sensors.MouseSensor\n   * @property {TouchSensor} Sensors.TouchSensor\n   * @type {Object}\n   */\n  static Sensors = {MouseSensor, TouchSensor};\n\n  /**\n   * Draggable constructor.\n   * @constructs Draggable\n   * @param {HTMLElement[]|NodeList|HTMLElement} containers - Draggable containers\n   * @param {Object} options - Options for draggable\n   */\n  constructor(containers = [document.body], options = {}) {\n    /**\n     * Draggable containers\n     * @property containers\n     * @type {HTMLElement[]}\n     */\n    if (containers instanceof NodeList || containers instanceof Array) {\n      this.containers = [...containers];\n    } else if (containers instanceof HTMLElement) {\n      this.containers = [containers];\n    } else {\n      throw new Error(\n        'Draggable containers are expected to be of type `NodeList`, `HTMLElement[]` or `HTMLElement`',\n      );\n    }\n\n    this.options = {\n      ...defaultOptions,\n      ...options,\n      classes: {\n        ...defaultClasses,\n        ...(options.classes || {}),\n      },\n      announcements: {\n        ...defaultAnnouncements,\n        ...(options.announcements || {}),\n      },\n      exclude: {\n        plugins: (options.exclude && options.exclude.plugins) || [],\n        sensors: (options.exclude && options.exclude.sensors) || [],\n      },\n    };\n\n    /**\n     * Draggables event emitter\n     * @property emitter\n     * @type {Emitter}\n     */\n    this.emitter = new Emitter();\n\n    /**\n     * Current drag state\n     * @property dragging\n     * @type {Boolean}\n     */\n    this.dragging = false;\n\n    /**\n     * Active plugins\n     * @property plugins\n     * @type {Plugin[]}\n     */\n    this.plugins = [];\n\n    /**\n     * Active sensors\n     * @property sensors\n     * @type {Sensor[]}\n     */\n    this.sensors = [];\n\n    this[onDragStart] = this[onDragStart].bind(this);\n    this[onDragMove] = this[onDragMove].bind(this);\n    this[onDragStop] = this[onDragStop].bind(this);\n    this[onDragPressure] = this[onDragPressure].bind(this);\n    this[dragStop] = this[dragStop].bind(this);\n\n    document.addEventListener('drag:start', this[onDragStart], true);\n    document.addEventListener('drag:move', this[onDragMove], true);\n    document.addEventListener('drag:stop', this[onDragStop], true);\n    document.addEventListener('drag:pressure', this[onDragPressure], true);\n\n    const defaultPlugins = Object.values(Draggable.Plugins).filter(\n      (Plugin) => !this.options.exclude.plugins.includes(Plugin),\n    );\n    const defaultSensors = Object.values(Draggable.Sensors).filter(\n      (sensor) => !this.options.exclude.sensors.includes(sensor),\n    );\n\n    this.addPlugin(...[...defaultPlugins, ...this.options.plugins]);\n    this.addSensor(...[...defaultSensors, ...this.options.sensors]);\n\n    const draggableInitializedEvent = new DraggableInitializedEvent({\n      draggable: this,\n    });\n\n    this.on('mirror:created', ({mirror}) => (this.mirror = mirror));\n    this.on('mirror:destroy', () => (this.mirror = null));\n\n    this.trigger(draggableInitializedEvent);\n  }\n\n  /**\n   * Destroys Draggable instance. This removes all internal event listeners and\n   * deactivates sensors and plugins\n   */\n  destroy() {\n    document.removeEventListener('drag:start', this[onDragStart], true);\n    document.removeEventListener('drag:move', this[onDragMove], true);\n    document.removeEventListener('drag:stop', this[onDragStop], true);\n    document.removeEventListener('drag:pressure', this[onDragPressure], true);\n\n    const draggableDestroyEvent = new DraggableDestroyEvent({\n      draggable: this,\n    });\n\n    this.trigger(draggableDestroyEvent);\n\n    this.removePlugin(...this.plugins.map((plugin) => plugin.constructor));\n    this.removeSensor(...this.sensors.map((sensor) => sensor.constructor));\n  }\n\n  /**\n   * Adds plugin to this draggable instance. This will end up calling the attach method of the plugin\n   * @param {...typeof Plugin} plugins - Plugins that you want attached to draggable\n   * @return {Draggable}\n   * @example draggable.addPlugin(CustomA11yPlugin, CustomMirrorPlugin)\n   */\n  addPlugin(...plugins) {\n    const activePlugins = plugins.map((Plugin) => new Plugin(this));\n\n    activePlugins.forEach((plugin) => plugin.attach());\n    this.plugins = [...this.plugins, ...activePlugins];\n\n    return this;\n  }\n\n  /**\n   * Removes plugins that are already attached to this draggable instance. This will end up calling\n   * the detach method of the plugin\n   * @param {...typeof Plugin} plugins - Plugins that you want detached from draggable\n   * @return {Draggable}\n   * @example draggable.removePlugin(MirrorPlugin, CustomMirrorPlugin)\n   */\n  removePlugin(...plugins) {\n    const removedPlugins = this.plugins.filter((plugin) =>\n      plugins.includes(plugin.constructor),\n    );\n\n    removedPlugins.forEach((plugin) => plugin.detach());\n    this.plugins = this.plugins.filter(\n      (plugin) => !plugins.includes(plugin.constructor),\n    );\n\n    return this;\n  }\n\n  /**\n   * Adds sensors to this draggable instance. This will end up calling the attach method of the sensor\n   * @param {...typeof Sensor} sensors - Sensors that you want attached to draggable\n   * @return {Draggable}\n   * @example draggable.addSensor(ForceTouchSensor, CustomSensor)\n   */\n  addSensor(...sensors) {\n    const activeSensors = sensors.map(\n      (Sensor) => new Sensor(this.containers, this.options),\n    );\n\n    activeSensors.forEach((sensor) => sensor.attach());\n    this.sensors = [...this.sensors, ...activeSensors];\n\n    return this;\n  }\n\n  /**\n   * Removes sensors that are already attached to this draggable instance. This will end up calling\n   * the detach method of the sensor\n   * @param {...typeof Sensor} sensors - Sensors that you want attached to draggable\n   * @return {Draggable}\n   * @example draggable.removeSensor(TouchSensor, DragSensor)\n   */\n  removeSensor(...sensors) {\n    const removedSensors = this.sensors.filter((sensor) =>\n      sensors.includes(sensor.constructor),\n    );\n\n    removedSensors.forEach((sensor) => sensor.detach());\n    this.sensors = this.sensors.filter(\n      (sensor) => !sensors.includes(sensor.constructor),\n    );\n\n    return this;\n  }\n\n  /**\n   * Adds container to this draggable instance\n   * @param {...HTMLElement} containers - Containers you want to add to draggable\n   * @return {Draggable}\n   * @example draggable.addContainer(document.body)\n   */\n  addContainer(...containers) {\n    this.containers = [...this.containers, ...containers];\n    this.sensors.forEach((sensor) => sensor.addContainer(...containers));\n    return this;\n  }\n\n  /**\n   * Removes container from this draggable instance\n   * @param {...HTMLElement} containers - Containers you want to remove from draggable\n   * @return {Draggable}\n   * @example draggable.removeContainer(document.body)\n   */\n  removeContainer(...containers) {\n    this.containers = this.containers.filter(\n      (container) => !containers.includes(container),\n    );\n    this.sensors.forEach((sensor) => sensor.removeContainer(...containers));\n    return this;\n  }\n\n  /**\n   * Adds listener for draggable events\n   * @param {String} type - Event name\n   * @param {...Function} callbacks - Event callbacks\n   * @return {Draggable}\n   * @example draggable.on('drag:start', (dragEvent) => dragEvent.cancel());\n   */\n  on(type, ...callbacks) {\n    this.emitter.on(type, ...callbacks);\n    return this;\n  }\n\n  /**\n   * Removes listener from draggable\n   * @param {String} type - Event name\n   * @param {Function} callback - Event callback\n   * @return {Draggable}\n   * @example draggable.off('drag:start', handlerFunction);\n   */\n  off(type, callback) {\n    this.emitter.off(type, callback);\n    return this;\n  }\n\n  /**\n   * Triggers draggable event\n   * @param {AbstractEvent} event - Event instance\n   * @return {Draggable}\n   * @example draggable.trigger(event);\n   */\n  trigger(event) {\n    this.emitter.trigger(event);\n    return this;\n  }\n\n  /**\n   * Returns class name for class identifier\n   * @param {String} name - Name of class identifier\n   * @return {String|null}\n   */\n  getClassNameFor(name) {\n    return this.getClassNamesFor(name)[0];\n  }\n\n  /**\n   * Returns class names for class identifier\n   * @return {String[]}\n   */\n  getClassNamesFor(name) {\n    const classNames = this.options.classes[name];\n\n    if (classNames instanceof Array) {\n      return classNames;\n    } else if (typeof classNames === 'string' || classNames instanceof String) {\n      return [classNames];\n    } else {\n      return [];\n    }\n  }\n\n  /**\n   * Returns true if this draggable instance is currently dragging\n   * @return {Boolean}\n   */\n  isDragging() {\n    return Boolean(this.dragging);\n  }\n\n  /**\n   * Returns all draggable elements\n   * @return {HTMLElement[]}\n   */\n  getDraggableElements() {\n    return this.containers.reduce((current, container) => {\n      return [...current, ...this.getDraggableElementsForContainer(container)];\n    }, []);\n  }\n\n  /**\n   * Returns draggable elements for a given container, excluding the mirror and\n   * original source element if present\n   * @param {HTMLElement} container\n   * @return {HTMLElement[]}\n   */\n  getDraggableElementsForContainer(container) {\n    const allDraggableElements = container.querySelectorAll(\n      this.options.draggable,\n    );\n\n    return [...allDraggableElements].filter((childElement) => {\n      return (\n        childElement !== this.originalSource && childElement !== this.mirror\n      );\n    });\n  }\n\n  /**\n   * Cancel dragging immediately\n   */\n  cancel() {\n    this[dragStop]();\n  }\n\n  /**\n   * Drag start handler\n   * @private\n   * @param {Event} event - DOM Drag event\n   */\n  [onDragStart](event) {\n    const sensorEvent = getSensorEvent(event);\n    const {target, container, originalSource} = sensorEvent;\n\n    if (!this.containers.includes(container)) {\n      return;\n    }\n\n    if (\n      this.options.handle &&\n      target &&\n      !closest(target, this.options.handle)\n    ) {\n      sensorEvent.cancel();\n      return;\n    }\n\n    this.originalSource = originalSource;\n    this.sourceContainer = container;\n\n    if (this.lastPlacedSource && this.lastPlacedContainer) {\n      clearTimeout(this.placedTimeoutID);\n      this.lastPlacedSource.classList.remove(\n        ...this.getClassNamesFor('source:placed'),\n      );\n      this.lastPlacedContainer.classList.remove(\n        ...this.getClassNamesFor('container:placed'),\n      );\n    }\n\n    this.source = this.originalSource.cloneNode(true);\n    this.originalSource.parentNode.insertBefore(\n      this.source,\n      this.originalSource,\n    );\n    this.originalSource.style.display = 'none';\n\n    const dragStartEvent = new DragStartEvent({\n      source: this.source,\n      originalSource: this.originalSource,\n      sourceContainer: container,\n      sensorEvent,\n    });\n\n    this.trigger(dragStartEvent);\n\n    this.dragging = !dragStartEvent.canceled();\n\n    if (dragStartEvent.canceled()) {\n      this.source.remove();\n      this.originalSource.style.display = null;\n      return;\n    }\n\n    this.originalSource.classList.add(\n      ...this.getClassNamesFor('source:original'),\n    );\n    this.source.classList.add(...this.getClassNamesFor('source:dragging'));\n    this.sourceContainer.classList.add(\n      ...this.getClassNamesFor('container:dragging'),\n    );\n    document.body.classList.add(...this.getClassNamesFor('body:dragging'));\n    applyUserSelect(document.body, 'none');\n\n    requestAnimationFrame(() => {\n      const oldSensorEvent = getSensorEvent(event);\n      const newSensorEvent = oldSensorEvent.clone({target: this.source});\n\n      this[onDragMove]({\n        ...event,\n        detail: newSensorEvent,\n      });\n    });\n  }\n\n  /**\n   * Drag move handler\n   * @private\n   * @param {Event} event - DOM Drag event\n   */\n  [onDragMove](event) {\n    if (!this.dragging) {\n      return;\n    }\n\n    const sensorEvent = getSensorEvent(event);\n    const {container} = sensorEvent;\n    let target = sensorEvent.target;\n\n    const dragMoveEvent = new DragMoveEvent({\n      source: this.source,\n      originalSource: this.originalSource,\n      sourceContainer: container,\n      sensorEvent,\n    });\n\n    this.trigger(dragMoveEvent);\n\n    if (dragMoveEvent.canceled()) {\n      sensorEvent.cancel();\n    }\n\n    target = closest(target, this.options.draggable);\n    const withinCorrectContainer = closest(sensorEvent.target, this.containers);\n    const overContainer = sensorEvent.overContainer || withinCorrectContainer;\n    const isLeavingContainer =\n      this.currentOverContainer && overContainer !== this.currentOverContainer;\n    const isLeavingDraggable = this.currentOver && target !== this.currentOver;\n    const isOverContainer =\n      overContainer && this.currentOverContainer !== overContainer;\n    const isOverDraggable =\n      withinCorrectContainer && target && this.currentOver !== target;\n\n    if (isLeavingDraggable) {\n      const dragOutEvent = new DragOutEvent({\n        source: this.source,\n        originalSource: this.originalSource,\n        sourceContainer: container,\n        sensorEvent,\n        over: this.currentOver,\n        overContainer: this.currentOverContainer,\n      });\n\n      this.currentOver.classList.remove(\n        ...this.getClassNamesFor('draggable:over'),\n      );\n      this.currentOver = null;\n\n      this.trigger(dragOutEvent);\n    }\n\n    if (isLeavingContainer) {\n      const dragOutContainerEvent = new DragOutContainerEvent({\n        source: this.source,\n        originalSource: this.originalSource,\n        sourceContainer: container,\n        sensorEvent,\n        overContainer: this.currentOverContainer,\n      });\n\n      this.currentOverContainer.classList.remove(\n        ...this.getClassNamesFor('container:over'),\n      );\n      this.currentOverContainer = null;\n\n      this.trigger(dragOutContainerEvent);\n    }\n\n    if (isOverContainer) {\n      overContainer.classList.add(...this.getClassNamesFor('container:over'));\n\n      const dragOverContainerEvent = new DragOverContainerEvent({\n        source: this.source,\n        originalSource: this.originalSource,\n        sourceContainer: container,\n        sensorEvent,\n        overContainer,\n      });\n\n      this.currentOverContainer = overContainer;\n\n      this.trigger(dragOverContainerEvent);\n    }\n\n    if (isOverDraggable) {\n      target.classList.add(...this.getClassNamesFor('draggable:over'));\n\n      const dragOverEvent = new DragOverEvent({\n        source: this.source,\n        originalSource: this.originalSource,\n        sourceContainer: container,\n        sensorEvent,\n        overContainer,\n        over: target,\n      });\n\n      this.currentOver = target;\n\n      this.trigger(dragOverEvent);\n    }\n  }\n\n  /**\n   * Drag stop handler\n   * @private\n   * @param {Event} event - DOM Drag event\n   */\n  [dragStop](event) {\n    if (!this.dragging) {\n      return;\n    }\n\n    this.dragging = false;\n\n    const dragStopEvent = new DragStopEvent({\n      source: this.source,\n      originalSource: this.originalSource,\n      sensorEvent: event ? event.sensorEvent : null,\n      sourceContainer: this.sourceContainer,\n    });\n\n    this.trigger(dragStopEvent);\n\n    if (!dragStopEvent.canceled())\n      this.source.parentNode.insertBefore(this.originalSource, this.source);\n    this.source.remove();\n    this.originalSource.style.display = '';\n\n    this.source.classList.remove(...this.getClassNamesFor('source:dragging'));\n    this.originalSource.classList.remove(\n      ...this.getClassNamesFor('source:original'),\n    );\n    this.originalSource.classList.add(\n      ...this.getClassNamesFor('source:placed'),\n    );\n    this.sourceContainer.classList.add(\n      ...this.getClassNamesFor('container:placed'),\n    );\n    this.sourceContainer.classList.remove(\n      ...this.getClassNamesFor('container:dragging'),\n    );\n    document.body.classList.remove(...this.getClassNamesFor('body:dragging'));\n    applyUserSelect(document.body, '');\n\n    if (this.currentOver) {\n      this.currentOver.classList.remove(\n        ...this.getClassNamesFor('draggable:over'),\n      );\n    }\n\n    if (this.currentOverContainer) {\n      this.currentOverContainer.classList.remove(\n        ...this.getClassNamesFor('container:over'),\n      );\n    }\n\n    this.lastPlacedSource = this.originalSource;\n    this.lastPlacedContainer = this.sourceContainer;\n\n    this.placedTimeoutID = setTimeout(() => {\n      if (this.lastPlacedSource) {\n        this.lastPlacedSource.classList.remove(\n          ...this.getClassNamesFor('source:placed'),\n        );\n      }\n\n      if (this.lastPlacedContainer) {\n        this.lastPlacedContainer.classList.remove(\n          ...this.getClassNamesFor('container:placed'),\n        );\n      }\n\n      this.lastPlacedSource = null;\n      this.lastPlacedContainer = null;\n    }, this.options.placedTimeout);\n\n    const dragStoppedEvent = new DragStoppedEvent({\n      source: this.source,\n      originalSource: this.originalSource,\n      sensorEvent: event ? event.sensorEvent : null,\n      sourceContainer: this.sourceContainer,\n    });\n\n    this.trigger(dragStoppedEvent);\n\n    this.source = null;\n    this.originalSource = null;\n    this.currentOverContainer = null;\n    this.currentOver = null;\n    this.sourceContainer = null;\n  }\n\n  /**\n   * Drag stop handler\n   */\n  [onDragStop](event) {\n    this[dragStop](event);\n  }\n\n  /**\n   * Drag pressure handler\n   * @private\n   * @param {Event} event - DOM Drag event\n   */\n  [onDragPressure](event) {\n    if (!this.dragging) {\n      return;\n    }\n\n    const sensorEvent = getSensorEvent(event);\n    const source =\n      this.source ||\n      closest(sensorEvent.originalEvent.target, this.options.draggable);\n\n    const dragPressureEvent = new DragPressureEvent({\n      sensorEvent,\n      source,\n      pressure: sensorEvent.pressure,\n    });\n\n    this.trigger(dragPressureEvent);\n  }\n}\n\nfunction getSensorEvent(event) {\n  return event.detail;\n}\n\nfunction applyUserSelect(element, value) {\n  element.style.webkitUserSelect = value;\n  element.style.mozUserSelect = value;\n  element.style.msUserSelect = value;\n  element.style.oUserSelect = value;\n  element.style.userSelect = value;\n}\n"
  },
  {
    "path": "src/Draggable/DraggableEvent/DraggableEvent.ts",
    "content": "import AbstractEvent from 'shared/AbstractEvent';\nimport {FixMeAny} from 'shared/types';\n\n/**\n * DraggableEventData\n * @interface DraggableEventData\n */\ninterface DraggableEventData {\n  draggable: FixMeAny;\n}\n\n/**\n * Base draggable event\n * @class DraggableEvent\n * @module DraggableEvent\n * @extends AbstractEvent\n */\nexport class DraggableEvent extends AbstractEvent<DraggableEventData> {\n  static type = 'draggable';\n\n  /**\n   * Draggable instance\n   * @property draggable\n   * @type {Draggable}\n   * @readonly\n   */\n  get draggable() {\n    return this.data.draggable;\n  }\n}\n\n/**\n * Draggable initialized event\n * @class DraggableInitializedEvent\n * @module DraggableInitializedEvent\n * @extends DraggableEvent\n */\nexport class DraggableInitializedEvent extends DraggableEvent {\n  static type = 'draggable:initialize';\n}\n\n/**\n * Draggable destory event\n * @class DraggableInitializedEvent\n * @module DraggableDestroyEvent\n * @extends DraggableDestroyEvent\n */\nexport class DraggableDestroyEvent extends DraggableEvent {\n  static type = 'draggable:destroy';\n}\n"
  },
  {
    "path": "src/Draggable/DraggableEvent/README.md",
    "content": "## DraggableEvent\n\nThe base draggable event for all Draggable events that `Draggable` emits.\n\n| | |\n| --------------------- | ---------------------------------------------------------- |\n| **Interface**         | `DraggableEvent`                                           |\n| **Cancelable**        | false                                                      |\n| **Cancel action**     | -                                                          |\n| **type**              | `draggable`                                                |\n\n### API\n\n**`draggableEvent.draggable: Draggable`**  \nRead-only property for the current draggable instance\n\n## DraggableInitializedEvent\n\n`DraggableInitializedEvent` gets triggered by `Draggable` when initialized.\n\n| | |\n| --------------------- | ---------------------------------------------------------- |\n| **Specification**     | `DraggableEvent`                                           |\n| **Interface**         | `DraggableInitializedEvent`                                |\n| **Cancelable**        | false                                                      |\n| **Cancel action**     | -                                                          |\n| **type**              | `draggable:initialized`                                    |\n\n## DraggableDestroyEvent\n\n`DraggableDestroyEvent` gets triggered by `Draggable` when destroyed.\n\n| | |\n| --------------------- | ---------------------------------------------------------- |\n| **Specification**     | `DraggableEvent`                                           |\n| **Interface**         | `DraggableDestroyEvent`                                    |\n| **Cancelable**        | false                                                      |\n| **Cancel action**     | -                                                          |\n| **type**              | `draggable:destroy`                                        |\n"
  },
  {
    "path": "src/Draggable/DraggableEvent/index.ts",
    "content": "export * from './DraggableEvent';\n"
  },
  {
    "path": "src/Draggable/Emitter/Emitter.ts",
    "content": "import AbstractEvent from 'shared/AbstractEvent';\n\ntype CallbackFunction = (event: AbstractEvent<unknown>) => void;\n\ninterface Callback {\n  [key: string]: CallbackFunction[];\n}\n\n/**\n * The Emitter is a simple emitter class that provides you with `on()`, `off()` and `trigger()` methods\n * @class Emitter\n * @module Emitter\n */\nexport default class Emitter {\n  public callbacks: Callback = {};\n\n  /**\n   * Registers callbacks by event name\n   * @param {String} type\n   * @param {...Function} callbacks\n   */\n  on(type: string, ...callbacks: CallbackFunction[]) {\n    if (!this.callbacks[type]) {\n      this.callbacks[type] = [];\n    }\n\n    this.callbacks[type].push(...callbacks);\n\n    return this;\n  }\n\n  /**\n   * Unregisters callbacks by event name\n   * @param {String} type\n   * @param {Function} callback\n   */\n  off(type: string, callback: CallbackFunction) {\n    if (!this.callbacks[type]) {\n      return null;\n    }\n\n    const copy = this.callbacks[type].slice(0);\n\n    for (let i = 0; i < copy.length; i++) {\n      if (callback === copy[i]) {\n        this.callbacks[type].splice(i, 1);\n      }\n    }\n\n    return this;\n  }\n\n  /**\n   * Triggers event callbacks by event object\n   * @param {AbstractEvent} event\n   */\n  trigger(event: AbstractEvent<unknown>) {\n    if (!this.callbacks[event.type]) {\n      return null;\n    }\n\n    const callbacks = [...this.callbacks[event.type]];\n    const caughtErrors = [];\n\n    for (let i = callbacks.length - 1; i >= 0; i--) {\n      const callback = callbacks[i];\n\n      try {\n        callback(event);\n      } catch (error) {\n        caughtErrors.push(error);\n      }\n    }\n\n    if (caughtErrors.length) {\n      /* eslint-disable no-console */\n      console.error(\n        `Draggable caught errors while triggering '${event.type}'`,\n        caughtErrors,\n      );\n      /* eslint-enable no-console */\n    }\n\n    return this;\n  }\n}\n"
  },
  {
    "path": "src/Draggable/Emitter/index.ts",
    "content": "export {default} from './Emitter';\n"
  },
  {
    "path": "src/Draggable/Emitter/tests/Emitter.test.ts",
    "content": "import AbstractEvent from 'shared/AbstractEvent';\n\nimport Emitter from '../Emitter';\n\nclass TestEvent extends AbstractEvent<unknown> {}\n\ndescribe('Emitter', () => {\n  let emitter: Emitter;\n\n  beforeEach(() => {\n    emitter = new Emitter();\n  });\n\n  describe('#on', () => {\n    it('registers a callback by event type', () => {\n      const callback = jest.fn();\n\n      emitter.on('event', callback);\n\n      expect(emitter.callbacks.event).toContain(callback);\n    });\n\n    it('registers multiple callbacks by event type', () => {\n      const callbacks = [jest.fn(), jest.fn()];\n\n      emitter.on('event', ...callbacks);\n\n      expect(emitter.callbacks.event[0]).toStrictEqual(callbacks[0]);\n      expect(emitter.callbacks.event[1]).toStrictEqual(callbacks[1]);\n    });\n  });\n\n  describe('#off', () => {\n    it('removes a callback by event type', () => {\n      const callback = jest.fn();\n\n      emitter.on('event', callback);\n\n      expect(emitter.callbacks.event).toContain(callback);\n\n      emitter.off('event', callback);\n\n      expect(emitter.callbacks.event).not.toContain(callback);\n    });\n  });\n\n  describe('#trigger', () => {\n    it('triggers callbacks on event with test event', () => {\n      const testEvent = new TestEvent({});\n      const callback = jest.fn();\n\n      emitter.on('event', callback);\n      emitter.trigger(testEvent);\n\n      expect(callback).toHaveBeenCalled();\n      expect(callback).toHaveBeenCalledWith(testEvent);\n    });\n\n    it('catches errors from listeners and re-throws at the end of the trigger phase', () => {\n      const consoleErrorSpy = jest.fn();\n\n      const testEvent = new TestEvent({});\n      const error = new Error('Error');\n      const callbacks = [\n        jest.fn(),\n        () => {\n          throw error;\n        },\n        jest.fn(),\n      ];\n\n      /* eslint-disable no-console */\n      console.error = consoleErrorSpy;\n      /* eslint-enable no-console */\n\n      emitter.on('event', ...callbacks);\n\n      emitter.trigger(testEvent);\n\n      expect(consoleErrorSpy).toHaveBeenCalled();\n      expect(consoleErrorSpy).toHaveBeenCalledWith(\n        \"Draggable caught errors while triggering 'event'\",\n        [error],\n      );\n\n      expect(callbacks[0]).toHaveBeenCalled();\n      expect(callbacks[2]).toHaveBeenCalled();\n    });\n  });\n});\n"
  },
  {
    "path": "src/Draggable/Plugins/Announcement/Announcement.js",
    "content": "import AbstractPlugin from 'shared/AbstractPlugin';\n\nconst onInitialize = Symbol('onInitialize');\nconst onDestroy = Symbol('onDestroy');\nconst announceEvent = Symbol('announceEvent');\nconst announceMessage = Symbol('announceMessage');\n\nconst ARIA_RELEVANT = 'aria-relevant';\nconst ARIA_ATOMIC = 'aria-atomic';\nconst ARIA_LIVE = 'aria-live';\nconst ROLE = 'role';\n\n/**\n * Announcement default options\n * @property {Object} defaultOptions\n * @property {Number} defaultOptions.expire\n * @type {Object}\n */\nexport const defaultOptions = {\n  expire: 7000,\n};\n\n/**\n * Announcement plugin\n * @class Announcement\n * @module Announcement\n * @extends AbstractPlugin\n */\nexport default class Announcement extends AbstractPlugin {\n  /**\n   * Announcement constructor.\n   * @constructs Announcement\n   * @param {Draggable} draggable - Draggable instance\n   */\n  constructor(draggable) {\n    super(draggable);\n\n    /**\n     * Plugin options\n     * @property options\n     * @type {Object}\n     */\n    this.options = {\n      ...defaultOptions,\n      ...this.getOptions(),\n    };\n\n    /**\n     * Original draggable trigger method. Hack until we have onAll or on('all')\n     * @property originalTriggerMethod\n     * @type {Function}\n     */\n    this.originalTriggerMethod = this.draggable.trigger;\n\n    this[onInitialize] = this[onInitialize].bind(this);\n    this[onDestroy] = this[onDestroy].bind(this);\n  }\n\n  /**\n   * Attaches listeners to draggable\n   */\n  attach() {\n    this.draggable.on('draggable:initialize', this[onInitialize]);\n  }\n\n  /**\n   * Detaches listeners from draggable\n   */\n  detach() {\n    this.draggable.off('draggable:destroy', this[onDestroy]);\n  }\n\n  /**\n   * Returns passed in options\n   */\n  getOptions() {\n    return this.draggable.options.announcements || {};\n  }\n\n  /**\n   * Announces event\n   * @private\n   * @param {AbstractEvent} event\n   */\n  [announceEvent](event) {\n    const message = this.options[event.type];\n\n    if (message && typeof message === 'string') {\n      this[announceMessage](message);\n    }\n\n    if (message && typeof message === 'function') {\n      this[announceMessage](message(event));\n    }\n  }\n\n  /**\n   * Announces message to screen reader\n   * @private\n   * @param {String} message\n   */\n  [announceMessage](message) {\n    announce(message, {expire: this.options.expire});\n  }\n\n  /**\n   * Initialize hander\n   * @private\n   */\n  [onInitialize]() {\n    // Hack until there is an api for listening for all events\n    this.draggable.trigger = (event) => {\n      try {\n        this[announceEvent](event);\n      } finally {\n        // Ensure that original trigger is called\n        this.originalTriggerMethod.call(this.draggable, event);\n      }\n    };\n  }\n\n  /**\n   * Destroy hander\n   * @private\n   */\n  [onDestroy]() {\n    this.draggable.trigger = this.originalTriggerMethod;\n  }\n}\n\n/**\n * @const {HTMLElement} liveRegion\n */\nconst liveRegion = createRegion();\n\n/**\n * Announces message via live region\n * @param {String} message\n * @param {Object} options\n * @param {Number} options.expire\n */\nfunction announce(message, {expire}) {\n  const element = document.createElement('div');\n\n  element.textContent = message;\n  liveRegion.appendChild(element);\n\n  return setTimeout(() => {\n    liveRegion.removeChild(element);\n  }, expire);\n}\n\n/**\n * Creates region element\n * @return {HTMLElement}\n */\nfunction createRegion() {\n  const element = document.createElement('div');\n\n  element.setAttribute('id', 'draggable-live-region');\n  element.setAttribute(ARIA_RELEVANT, 'additions');\n  element.setAttribute(ARIA_ATOMIC, 'true');\n  element.setAttribute(ARIA_LIVE, 'assertive');\n  element.setAttribute(ROLE, 'log');\n\n  element.style.position = 'fixed';\n  element.style.width = '1px';\n  element.style.height = '1px';\n  element.style.top = '-1px';\n  element.style.overflow = 'hidden';\n\n  return element;\n}\n\n// Append live region element as early as possible\ndocument.addEventListener('DOMContentLoaded', () => {\n  document.body.appendChild(liveRegion);\n});\n"
  },
  {
    "path": "src/Draggable/Plugins/Announcement/README.md",
    "content": "## Announcement\n\nThe Announcement plugin listens to _all_ draggable events and allows you to define announcements via options for events\nthat are read back through a screen reader.\n\n### API\n\n**`new Announcement(draggable: Draggable): Announcement`**  \nTo create an announcement plugin instance.\n\n### Options\n\n**`expire {Number}`**  \nHow long messages should stay inside the live region (in milliseconds). Default: `7000`\n\n**`'drag:start' {String|Function:String}`**  \nDefine an announcement on `drag:start`. Default: `Picked up draggable element`\n\n**`'drag:stop' {String|Function:String}`**  \nDefine an announcement on `drag:stop`. Default: `Dropped draggable element`\n\n**`'sortable:sorted' {String|Function:String}`**  \nDefine an announcement on `sortable:sorted`. No default\n\n**`'swappable:swapped' {String|Function:String}`**  \nDefine an announcement on `swappable:swapped`. No default\n\n**`'droppable:dropped' {String|Function:String}`**  \nDefine an announcement on `droppable:dropped`. No default\n\n_And any other events you can think of..._\n\n### Examples\n\n#### Static messages\n\n```js\nimport {Sortable} from '@shopify/draggable';\n\nconst announcements = {\n  'drag:start': 'Draggable element picked up',\n  'drag:stop': 'Draggable element dropped',\n  'sortable:stopped': 'Draggable elements swapped',\n}\n\nconst sortable = new Sortable(document.querySelectorAll('ul'), {\n  draggable: 'li',\n  announcements,\n});\n```\n\n#### Dynamic messages\n\n```js\nimport {Sortable} from '@shopify/draggable';\n\nconst announcements = {\n  'drag:start': (dragEvent) => {\n    return `Picked up ${dragEvent.source.getAttribute('data-name')}`;\n  },\n\n  'drag:stop': (dragEvent) => {\n    return `Dropped ${dragEvent.source.getAttribute('data-name')}`\n  },\n\n  'sortable:sorted': (sortableEvent) => {\n    return `Sorted ${sortableEvent.dragEvent.source.getAttribute('data-name')} with ${sortableEvent.dragEvent.over.getAttribute('data-name')}`;\n  },\n}\n\nconst sortable = new Sortable(document.querySelectorAll('ul'), {\n  draggable: 'li',\n  announcements,\n});\n```\n"
  },
  {
    "path": "src/Draggable/Plugins/Announcement/index.js",
    "content": "import Announcement, {defaultOptions} from './Announcement';\n\nexport default Announcement;\nexport {defaultOptions};\n"
  },
  {
    "path": "src/Draggable/Plugins/Focusable/Focusable.js",
    "content": "import AbstractPlugin from 'shared/AbstractPlugin';\n\nconst onInitialize = Symbol('onInitialize');\nconst onDestroy = Symbol('onDestroy');\n\n/**\n * Focusable default options\n * @property {Object} defaultOptions\n * @type {Object}\n */\nconst defaultOptions = {};\n\n/**\n * Focusable plugin\n * @class Focusable\n * @module Focusable\n * @extends AbstractPlugin\n */\nexport default class Focusable extends AbstractPlugin {\n  /**\n   * Focusable constructor.\n   * @constructs Focusable\n   * @param {Draggable} draggable - Draggable instance\n   */\n  constructor(draggable) {\n    super(draggable);\n\n    /**\n     * Focusable options\n     * @property {Object} options\n     * @type {Object}\n     */\n    this.options = {\n      ...defaultOptions,\n      ...this.getOptions(),\n    };\n\n    this[onInitialize] = this[onInitialize].bind(this);\n    this[onDestroy] = this[onDestroy].bind(this);\n  }\n\n  /**\n   * Attaches listeners to draggable\n   */\n  attach() {\n    this.draggable\n      .on('draggable:initialize', this[onInitialize])\n      .on('draggable:destroy', this[onDestroy]);\n  }\n\n  /**\n   * Detaches listeners from draggable\n   */\n  detach() {\n    this.draggable\n      .off('draggable:initialize', this[onInitialize])\n      .off('draggable:destroy', this[onDestroy]);\n\n    // Remove modified elements when detach\n    this[onDestroy]();\n  }\n\n  /**\n   * Returns options passed through draggable\n   * @return {Object}\n   */\n  getOptions() {\n    return this.draggable.options.focusable || {};\n  }\n\n  /**\n   * Returns draggable containers and elements\n   * @return {HTMLElement[]}\n   */\n  getElements() {\n    return [\n      ...this.draggable.containers,\n      ...this.draggable.getDraggableElements(),\n    ];\n  }\n\n  /**\n   * Intialize handler\n   * @private\n   */\n  [onInitialize]() {\n    // Can wait until the next best frame is available\n    requestAnimationFrame(() => {\n      this.getElements().forEach((element) => decorateElement(element));\n    });\n  }\n\n  /**\n   * Destroy handler\n   * @private\n   */\n  [onDestroy]() {\n    // Can wait until the next best frame is available\n    requestAnimationFrame(() => {\n      this.getElements().forEach((element) => stripElement(element));\n    });\n  }\n}\n\n/**\n * Keeps track of all the elements that are missing tabindex attributes\n * so they can be reset when draggable gets destroyed\n * @const {HTMLElement[]} elementsWithMissingTabIndex\n */\nconst elementsWithMissingTabIndex = [];\n\n/**\n * Decorates element with tabindex attributes\n * @param {HTMLElement} element\n * @return {Object}\n * @private\n */\nfunction decorateElement(element) {\n  const hasMissingTabIndex = Boolean(\n    !element.getAttribute('tabindex') && element.tabIndex === -1,\n  );\n\n  if (hasMissingTabIndex) {\n    elementsWithMissingTabIndex.push(element);\n    element.tabIndex = 0;\n  }\n}\n\n/**\n * Removes elements tabindex attributes\n * @param {HTMLElement} element\n * @private\n */\nfunction stripElement(element) {\n  const tabIndexElementPosition = elementsWithMissingTabIndex.indexOf(element);\n\n  if (tabIndexElementPosition !== -1) {\n    element.tabIndex = -1;\n    elementsWithMissingTabIndex.splice(tabIndexElementPosition, 1);\n  }\n}\n"
  },
  {
    "path": "src/Draggable/Plugins/Focusable/README.md",
    "content": "## Focusable\n\nThe Focusable plugin finds all draggable containers and elements on initialization\nand decorates them with `tabindex` attributes.\nThe plugin will not override existing `tabindex` attributes on elements. This will\nmake draggable elements and containers focusable.\n\n### API\n\n**`new Focusable(draggable: Draggable): Focusable`**  \nTo create an focusable plugin instance.\n\n**`focusable.getElements(): HTMLElement[]`**  \nReturns container and draggable elements.\n\n### Options\n\n_No options_\n"
  },
  {
    "path": "src/Draggable/Plugins/Focusable/index.js",
    "content": "import Focusable from './Focusable';\n\nexport default Focusable;\n"
  },
  {
    "path": "src/Draggable/Plugins/Focusable/tests/Focusable.test.js",
    "content": "import {createSandbox, waitForRequestAnimationFrame} from 'helper';\n\nimport Draggable from '../../..';\nimport Focusable from '..';\n\nconst sampleMarkup = `\n  <ul class=\"Container\">\n    <li>First item</li>\n    <li>Second item</li>\n    <li>Third item</li>\n    <li>Forth item</li>\n    <button>Fifth item</button>\n  </ul>\n  <div class=\"Container\">\n    <div tabindex=\"1\"></div>\n  </div>\n`;\n\ndescribe('Focusable', () => {\n  let sandbox;\n  let containers;\n  let draggable;\n\n  beforeEach(() => {\n    sandbox = createSandbox(sampleMarkup);\n    containers = sandbox.querySelectorAll('.Container');\n  });\n\n  afterEach(() => {\n    draggable.destroy();\n    sandbox.remove();\n  });\n\n  it('is included by default', () => {\n    draggable = new Draggable(containers, {\n      draggable: 'li',\n    });\n\n    const focusablePlugin = draggable.plugins.find(\n      (plugin) => plugin.constructor === Focusable,\n    );\n\n    expect(focusablePlugin).toBeInstanceOf(Focusable);\n  });\n\n  it('sets tabindex properties', () => {\n    draggable = new Draggable(containers, {\n      draggable: 'li',\n    });\n\n    const elements = [\n      ...draggable.containers,\n      ...draggable.getDraggableElements(),\n    ];\n\n    waitForRequestAnimationFrame();\n\n    elements.forEach((element) => {\n      expect(element.tabIndex).toBe(0);\n    });\n  });\n\n  it('removes tabindex properties', () => {\n    draggable = new Draggable(containers, {\n      draggable: 'li',\n    });\n\n    const elements = [\n      ...draggable.containers,\n      ...draggable.getDraggableElements(),\n    ];\n\n    waitForRequestAnimationFrame();\n\n    draggable.destroy();\n\n    waitForRequestAnimationFrame();\n\n    elements.forEach((element) => {\n      expect(element.tabIndex).toBe(-1);\n    });\n  });\n\n  it('does not remove tabindex properties for natively focusable elements', () => {\n    draggable = new Draggable(containers, {\n      draggable: 'li, button',\n    });\n\n    waitForRequestAnimationFrame();\n\n    const nativelyFocusableElement = document.querySelector('button');\n\n    draggable.destroy();\n\n    waitForRequestAnimationFrame();\n\n    expect(nativelyFocusableElement.tabIndex).toBe(0);\n  });\n\n  it('does not remove tabindex properties for element with tabindex attribute', () => {\n    const elementWithTabIndexAttribute = document.querySelector('[tabindex]');\n    const originalTabIndex = elementWithTabIndexAttribute.tabIndex;\n\n    draggable = new Draggable(containers, {\n      draggable: 'li, [tabindex]',\n    });\n\n    waitForRequestAnimationFrame();\n\n    draggable.destroy();\n\n    waitForRequestAnimationFrame();\n\n    expect(elementWithTabIndexAttribute.tabIndex).toStrictEqual(\n      originalTabIndex,\n    );\n  });\n});\n"
  },
  {
    "path": "src/Draggable/Plugins/Mirror/Mirror.js",
    "content": "import AbstractPlugin from 'shared/AbstractPlugin';\n\nimport {\n  MirrorCreateEvent,\n  MirrorCreatedEvent,\n  MirrorAttachedEvent,\n  MirrorMoveEvent,\n  MirrorMovedEvent,\n  MirrorDestroyEvent,\n} from './MirrorEvent';\n\nexport const onDragStart = Symbol('onDragStart');\nexport const onDragMove = Symbol('onDragMove');\nexport const onDragStop = Symbol('onDragStop');\nexport const onMirrorCreated = Symbol('onMirrorCreated');\nexport const onMirrorMove = Symbol('onMirrorMove');\nexport const onScroll = Symbol('onScroll');\nexport const getAppendableContainer = Symbol('getAppendableContainer');\n\n/**\n * Mirror default options\n * @property {Object} defaultOptions\n * @property {Boolean} defaultOptions.constrainDimensions\n * @property {Boolean} defaultOptions.xAxis\n * @property {Boolean} defaultOptions.yAxis\n * @property {null} defaultOptions.cursorOffsetX\n * @property {null} defaultOptions.cursorOffsetY\n * @type {Object}\n */\nexport const defaultOptions = {\n  constrainDimensions: false,\n  xAxis: true,\n  yAxis: true,\n  cursorOffsetX: null,\n  cursorOffsetY: null,\n  thresholdX: null,\n  thresholdY: null,\n};\n\n/**\n * Mirror plugin which controls the mirror positioning while dragging\n * @class Mirror\n * @module Mirror\n * @extends AbstractPlugin\n */\nexport default class Mirror extends AbstractPlugin {\n  /**\n   * Mirror constructor.\n   * @constructs Mirror\n   * @param {Draggable} draggable - Draggable instance\n   */\n  constructor(draggable) {\n    super(draggable);\n\n    /**\n     * Mirror options\n     * @property {Object} options\n     * @property {Boolean} options.constrainDimensions\n     * @property {Boolean} options.xAxis\n     * @property {Boolean} options.yAxis\n     * @property {Number|null} options.cursorOffsetX\n     * @property {Number|null} options.cursorOffsetY\n     * @property {String|HTMLElement|Function} options.appendTo\n     * @type {Object}\n     */\n    this.options = {\n      ...defaultOptions,\n      ...this.getOptions(),\n    };\n\n    /**\n     * Scroll offset for touch devices because the mirror is positioned fixed\n     * @property {Object} scrollOffset\n     * @property {Number} scrollOffset.x\n     * @property {Number} scrollOffset.y\n     */\n    this.scrollOffset = {x: 0, y: 0};\n\n    /**\n     * Initial scroll offset for touch devices because the mirror is positioned fixed\n     * @property {Object} scrollOffset\n     * @property {Number} scrollOffset.x\n     * @property {Number} scrollOffset.y\n     */\n    this.initialScrollOffset = {\n      x: window.scrollX,\n      y: window.scrollY,\n    };\n\n    this[onDragStart] = this[onDragStart].bind(this);\n    this[onDragMove] = this[onDragMove].bind(this);\n    this[onDragStop] = this[onDragStop].bind(this);\n    this[onMirrorCreated] = this[onMirrorCreated].bind(this);\n    this[onMirrorMove] = this[onMirrorMove].bind(this);\n    this[onScroll] = this[onScroll].bind(this);\n  }\n\n  /**\n   * Attaches plugins event listeners\n   */\n  attach() {\n    this.draggable\n      .on('drag:start', this[onDragStart])\n      .on('drag:move', this[onDragMove])\n      .on('drag:stop', this[onDragStop])\n      .on('mirror:created', this[onMirrorCreated])\n      .on('mirror:move', this[onMirrorMove]);\n  }\n\n  /**\n   * Detaches plugins event listeners\n   */\n  detach() {\n    this.draggable\n      .off('drag:start', this[onDragStart])\n      .off('drag:move', this[onDragMove])\n      .off('drag:stop', this[onDragStop])\n      .off('mirror:created', this[onMirrorCreated])\n      .off('mirror:move', this[onMirrorMove]);\n  }\n\n  /**\n   * Returns options passed through draggable\n   * @return {Object}\n   */\n  getOptions() {\n    return this.draggable.options.mirror || {};\n  }\n\n  [onDragStart](dragEvent) {\n    if (dragEvent.canceled()) {\n      return;\n    }\n\n    if ('ontouchstart' in window) {\n      document.addEventListener('scroll', this[onScroll], true);\n    }\n\n    this.initialScrollOffset = {\n      x: window.scrollX,\n      y: window.scrollY,\n    };\n\n    const {source, originalSource, sourceContainer, sensorEvent} = dragEvent;\n\n    // Last sensor position of mirror move\n    this.lastMirrorMovedClient = {\n      x: sensorEvent.clientX,\n      y: sensorEvent.clientY,\n    };\n\n    const mirrorCreateEvent = new MirrorCreateEvent({\n      source,\n      originalSource,\n      sourceContainer,\n      sensorEvent,\n      dragEvent,\n    });\n\n    this.draggable.trigger(mirrorCreateEvent);\n\n    if (isNativeDragEvent(sensorEvent) || mirrorCreateEvent.canceled()) {\n      return;\n    }\n\n    const appendableContainer =\n      this[getAppendableContainer](source) || sourceContainer;\n    this.mirror = source.cloneNode(true);\n\n    const mirrorCreatedEvent = new MirrorCreatedEvent({\n      source,\n      originalSource,\n      sourceContainer,\n      sensorEvent,\n      dragEvent,\n      mirror: this.mirror,\n    });\n\n    const mirrorAttachedEvent = new MirrorAttachedEvent({\n      source,\n      originalSource,\n      sourceContainer,\n      sensorEvent,\n      dragEvent,\n      mirror: this.mirror,\n    });\n\n    this.draggable.trigger(mirrorCreatedEvent);\n    appendableContainer.appendChild(this.mirror);\n    this.draggable.trigger(mirrorAttachedEvent);\n  }\n\n  [onDragMove](dragEvent) {\n    if (!this.mirror || dragEvent.canceled()) {\n      return;\n    }\n\n    const {source, originalSource, sourceContainer, sensorEvent} = dragEvent;\n\n    let passedThreshX = true;\n    let passedThreshY = true;\n\n    if (this.options.thresholdX || this.options.thresholdY) {\n      const {x: lastX, y: lastY} = this.lastMirrorMovedClient;\n\n      if (Math.abs(lastX - sensorEvent.clientX) < this.options.thresholdX) {\n        passedThreshX = false;\n      } else {\n        this.lastMirrorMovedClient.x = sensorEvent.clientX;\n      }\n\n      if (Math.abs(lastY - sensorEvent.clientY) < this.options.thresholdY) {\n        passedThreshY = false;\n      } else {\n        this.lastMirrorMovedClient.y = sensorEvent.clientY;\n      }\n\n      if (!passedThreshX && !passedThreshY) {\n        return;\n      }\n    }\n\n    const mirrorMoveEvent = new MirrorMoveEvent({\n      source,\n      originalSource,\n      sourceContainer,\n      sensorEvent,\n      dragEvent,\n      mirror: this.mirror,\n      passedThreshX,\n      passedThreshY,\n    });\n\n    this.draggable.trigger(mirrorMoveEvent);\n  }\n\n  [onDragStop](dragEvent) {\n    if ('ontouchstart' in window) {\n      document.removeEventListener('scroll', this[onScroll], true);\n    }\n\n    this.initialScrollOffset = {x: 0, y: 0};\n    this.scrollOffset = {x: 0, y: 0};\n\n    if (!this.mirror) {\n      return;\n    }\n\n    const {source, sourceContainer, sensorEvent} = dragEvent;\n\n    const mirrorDestroyEvent = new MirrorDestroyEvent({\n      source,\n      mirror: this.mirror,\n      sourceContainer,\n      sensorEvent,\n      dragEvent,\n    });\n\n    this.draggable.trigger(mirrorDestroyEvent);\n\n    if (!mirrorDestroyEvent.canceled()) {\n      this.mirror.remove();\n    }\n  }\n\n  [onScroll]() {\n    this.scrollOffset = {\n      x: window.scrollX - this.initialScrollOffset.x,\n      y: window.scrollY - this.initialScrollOffset.y,\n    };\n  }\n\n  /**\n   * Mirror created handler\n   * @param {MirrorCreatedEvent} mirrorEvent\n   * @return {Promise}\n   * @private\n   */\n  [onMirrorCreated]({mirror, source, sensorEvent}) {\n    const mirrorClasses = this.draggable.getClassNamesFor('mirror');\n\n    const setState = ({mirrorOffset, initialX, initialY, ...args}) => {\n      this.mirrorOffset = mirrorOffset;\n      this.initialX = initialX;\n      this.initialY = initialY;\n      this.lastMovedX = initialX;\n      this.lastMovedY = initialY;\n      return {mirrorOffset, initialX, initialY, ...args};\n    };\n\n    mirror.style.display = 'none';\n\n    const initialState = {\n      mirror,\n      source,\n      sensorEvent,\n      mirrorClasses,\n      scrollOffset: this.scrollOffset,\n      options: this.options,\n      passedThreshX: true,\n      passedThreshY: true,\n    };\n\n    return (\n      Promise.resolve(initialState)\n        // Fix reflow here\n        .then(computeMirrorDimensions)\n        .then(calculateMirrorOffset)\n        .then(resetMirror)\n        .then(addMirrorClasses)\n        .then(positionMirror({initial: true}))\n        .then(removeMirrorID)\n        .then(setState)\n    );\n  }\n\n  /**\n   * Mirror move handler\n   * @param {MirrorMoveEvent} mirrorEvent\n   * @return {Promise|null}\n   * @private\n   */\n  [onMirrorMove](mirrorEvent) {\n    if (mirrorEvent.canceled()) {\n      return null;\n    }\n\n    const setState = ({lastMovedX, lastMovedY, ...args}) => {\n      this.lastMovedX = lastMovedX;\n      this.lastMovedY = lastMovedY;\n\n      return {lastMovedX, lastMovedY, ...args};\n    };\n    const triggerMoved = (args) => {\n      const mirrorMovedEvent = new MirrorMovedEvent({\n        source: mirrorEvent.source,\n        originalSource: mirrorEvent.originalSource,\n        sourceContainer: mirrorEvent.sourceContainer,\n        sensorEvent: mirrorEvent.sensorEvent,\n        dragEvent: mirrorEvent.dragEvent,\n        mirror: this.mirror,\n        passedThreshX: mirrorEvent.passedThreshX,\n        passedThreshY: mirrorEvent.passedThreshY,\n      });\n\n      this.draggable.trigger(mirrorMovedEvent);\n\n      return args;\n    };\n\n    const initialState = {\n      mirror: mirrorEvent.mirror,\n      sensorEvent: mirrorEvent.sensorEvent,\n      mirrorOffset: this.mirrorOffset,\n      options: this.options,\n      initialX: this.initialX,\n      initialY: this.initialY,\n      scrollOffset: this.scrollOffset,\n      passedThreshX: mirrorEvent.passedThreshX,\n      passedThreshY: mirrorEvent.passedThreshY,\n      lastMovedX: this.lastMovedX,\n      lastMovedY: this.lastMovedY,\n    };\n\n    return Promise.resolve(initialState)\n      .then(positionMirror({raf: true}))\n      .then(setState)\n      .then(triggerMoved);\n  }\n\n  /**\n   * Returns appendable container for mirror based on the appendTo option\n   * @private\n   * @param {Object} options\n   * @param {HTMLElement} options.source - Current source\n   * @return {HTMLElement}\n   */\n  [getAppendableContainer](source) {\n    const appendTo = this.options.appendTo;\n\n    if (typeof appendTo === 'string') {\n      return document.querySelector(appendTo);\n    } else if (appendTo instanceof HTMLElement) {\n      return appendTo;\n    } else if (typeof appendTo === 'function') {\n      return appendTo(source);\n    } else {\n      return source.parentNode;\n    }\n  }\n}\n\n/**\n * Computes mirror dimensions based on the source element\n * Adds sourceRect to state\n * @param {Object} state\n * @param {HTMLElement} state.source\n * @return {Promise}\n * @private\n */\nfunction computeMirrorDimensions({source, ...args}) {\n  return withPromise((resolve) => {\n    const sourceRect = source.getBoundingClientRect();\n    resolve({source, sourceRect, ...args});\n  });\n}\n\n/**\n * Calculates mirror offset\n * Adds mirrorOffset to state\n * @param {Object} state\n * @param {SensorEvent} state.sensorEvent\n * @param {DOMRect} state.sourceRect\n * @return {Promise}\n * @private\n */\nfunction calculateMirrorOffset({sensorEvent, sourceRect, options, ...args}) {\n  return withPromise((resolve) => {\n    const top =\n      options.cursorOffsetY === null\n        ? sensorEvent.clientY - sourceRect.top\n        : options.cursorOffsetY;\n    const left =\n      options.cursorOffsetX === null\n        ? sensorEvent.clientX - sourceRect.left\n        : options.cursorOffsetX;\n\n    const mirrorOffset = {top, left};\n\n    resolve({sensorEvent, sourceRect, mirrorOffset, options, ...args});\n  });\n}\n\n/**\n * Applys mirror styles\n * @param {Object} state\n * @param {HTMLElement} state.mirror\n * @param {HTMLElement} state.source\n * @param {Object} state.options\n * @return {Promise}\n * @private\n */\nfunction resetMirror({mirror, source, options, ...args}) {\n  return withPromise((resolve) => {\n    let offsetHeight;\n    let offsetWidth;\n\n    if (options.constrainDimensions) {\n      const computedSourceStyles = getComputedStyle(source);\n      offsetHeight = computedSourceStyles.getPropertyValue('height');\n      offsetWidth = computedSourceStyles.getPropertyValue('width');\n    }\n\n    mirror.style.display = null;\n    mirror.style.position = 'fixed';\n    mirror.style.pointerEvents = 'none';\n    mirror.style.top = 0;\n    mirror.style.left = 0;\n    mirror.style.margin = 0;\n\n    if (options.constrainDimensions) {\n      mirror.style.height = offsetHeight;\n      mirror.style.width = offsetWidth;\n    }\n\n    resolve({mirror, source, options, ...args});\n  });\n}\n\n/**\n * Applys mirror class on mirror element\n * @param {Object} state\n * @param {HTMLElement} state.mirror\n * @param {String[]} state.mirrorClasses\n * @return {Promise}\n * @private\n */\nfunction addMirrorClasses({mirror, mirrorClasses, ...args}) {\n  return withPromise((resolve) => {\n    mirror.classList.add(...mirrorClasses);\n    resolve({mirror, mirrorClasses, ...args});\n  });\n}\n\n/**\n * Removes source ID from cloned mirror element\n * @param {Object} state\n * @param {HTMLElement} state.mirror\n * @return {Promise}\n * @private\n */\nfunction removeMirrorID({mirror, ...args}) {\n  return withPromise((resolve) => {\n    mirror.removeAttribute('id');\n    delete mirror.id;\n    resolve({mirror, ...args});\n  });\n}\n\n/**\n * Positions mirror with translate3d\n * @param {Object} state\n * @param {HTMLElement} state.mirror\n * @param {SensorEvent} state.sensorEvent\n * @param {Object} state.mirrorOffset\n * @param {Number} state.initialY\n * @param {Number} state.initialX\n * @param {Object} state.options\n * @return {Promise}\n * @private\n */\nfunction positionMirror({withFrame = false, initial = false} = {}) {\n  return ({\n    mirror,\n    sensorEvent,\n    mirrorOffset,\n    initialY,\n    initialX,\n    scrollOffset,\n    options,\n    passedThreshX,\n    passedThreshY,\n    lastMovedX,\n    lastMovedY,\n    ...args\n  }) => {\n    return withPromise(\n      (resolve) => {\n        const result = {\n          mirror,\n          sensorEvent,\n          mirrorOffset,\n          options,\n          ...args,\n        };\n\n        if (mirrorOffset) {\n          const x = passedThreshX\n            ? Math.round(\n                (sensorEvent.clientX - mirrorOffset.left - scrollOffset.x) /\n                  (options.thresholdX || 1),\n              ) * (options.thresholdX || 1)\n            : Math.round(lastMovedX);\n          const y = passedThreshY\n            ? Math.round(\n                (sensorEvent.clientY - mirrorOffset.top - scrollOffset.y) /\n                  (options.thresholdY || 1),\n              ) * (options.thresholdY || 1)\n            : Math.round(lastMovedY);\n\n          if ((options.xAxis && options.yAxis) || initial) {\n            mirror.style.transform = `translate3d(${x}px, ${y}px, 0)`;\n          } else if (options.xAxis && !options.yAxis) {\n            mirror.style.transform = `translate3d(${x}px, ${initialY}px, 0)`;\n          } else if (options.yAxis && !options.xAxis) {\n            mirror.style.transform = `translate3d(${initialX}px, ${y}px, 0)`;\n          }\n\n          if (initial) {\n            result.initialX = x;\n            result.initialY = y;\n          }\n\n          result.lastMovedX = x;\n          result.lastMovedY = y;\n        }\n\n        resolve(result);\n      },\n      {frame: withFrame},\n    );\n  };\n}\n\n/**\n * Wraps functions in promise with potential animation frame option\n * @param {Function} callback\n * @param {Object} options\n * @param {Boolean} options.raf\n * @return {Promise}\n * @private\n */\nfunction withPromise(callback, {raf = false} = {}) {\n  return new Promise((resolve, reject) => {\n    if (raf) {\n      requestAnimationFrame(() => {\n        callback(resolve, reject);\n      });\n    } else {\n      callback(resolve, reject);\n    }\n  });\n}\n\n/**\n * Returns true if the sensor event was triggered by a native browser drag event\n * @param {SensorEvent} sensorEvent\n */\nfunction isNativeDragEvent(sensorEvent) {\n  return /^drag/.test(sensorEvent.originalEvent.type);\n}\n"
  },
  {
    "path": "src/Draggable/Plugins/Mirror/MirrorEvent/MirrorEvent.ts",
    "content": "import AbstractEvent from 'shared/AbstractEvent';\n\nimport {DragEvent, DragEventData} from '../../../DragEvent';\nimport {SensorEvent} from '../../../Sensors/SensorEvent';\n\ninterface MirrorEventData {\n  source: HTMLElement;\n  originalSource: HTMLElement;\n  sourceContainer: HTMLElement;\n  sensorEvent: SensorEvent;\n  dragEvent: DragEvent<DragEventData>;\n}\n\n/**\n * Base mirror event\n * @class MirrorEvent\n * @module MirrorEvent\n * @extends AbstractEvent\n */\nexport class MirrorEvent<\n  T extends MirrorEventData,\n> extends AbstractEvent<MirrorEventData> {\n  /**\n   * MirrorEvent constructor.\n   * @constructs MirrorEvent\n   * @param {MirrorEventData} data - Event data\n   */\n  constructor(public data: T) {\n    super(data);\n  }\n\n  /**\n   * Draggables source element\n   * @property source\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get source() {\n    return this.data.source;\n  }\n\n  /**\n   * Draggables original source element\n   * @property originalSource\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get originalSource() {\n    return this.data.originalSource;\n  }\n\n  /**\n   * Draggables source container element\n   * @property sourceContainer\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get sourceContainer() {\n    return this.data.sourceContainer;\n  }\n\n  /**\n   * Sensor event\n   * @property sensorEvent\n   * @type {SensorEvent}\n   * @readonly\n   */\n  get sensorEvent() {\n    return this.data.sensorEvent;\n  }\n\n  /**\n   * Drag event\n   * @property dragEvent\n   * @type {DragEvent}\n   * @readonly\n   */\n  get dragEvent() {\n    return this.data.dragEvent;\n  }\n\n  /**\n   * Original event that triggered sensor event\n   * @property originalEvent\n   * @type {Event}\n   * @readonly\n   */\n  get originalEvent() {\n    if (this.sensorEvent) {\n      return this.sensorEvent.originalEvent;\n    }\n\n    return null;\n  }\n}\n\n/**\n * Mirror create event\n * @class MirrorCreateEvent\n * @module MirrorCreateEvent\n * @extends MirrorEvent\n */\nexport class MirrorCreateEvent extends MirrorEvent<MirrorEventData> {\n  static type = 'mirror:create';\n}\n\ninterface MirrorCreatedEventData extends MirrorEventData {\n  mirror: HTMLElement;\n}\n\n/**\n * Mirror created event\n * @class MirrorCreatedEvent\n * @module MirrorCreatedEvent\n * @extends MirrorEvent\n */\nexport class MirrorCreatedEvent extends MirrorEvent<MirrorCreatedEventData> {\n  static type = 'mirror:created';\n\n  /**\n   * Draggables mirror element\n   * @property mirror\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get mirror() {\n    return this.data.mirror;\n  }\n}\n\ninterface MirrorAttachedEventData extends MirrorEventData {\n  mirror: HTMLElement;\n}\n\n/**\n * Mirror attached event\n * @class MirrorAttachedEvent\n * @module MirrorAttachedEvent\n * @extends MirrorEvent\n */\nexport class MirrorAttachedEvent extends MirrorEvent<MirrorAttachedEventData> {\n  static type = 'mirror:attached';\n\n  /**\n   * Draggables mirror element\n   * @property mirror\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get mirror() {\n    return this.data.mirror;\n  }\n}\n\ninterface MirrorMoveEventData extends MirrorEventData {\n  mirror: HTMLElement;\n  passedThreshX: boolean;\n  passedThreshY: boolean;\n}\n\n/**\n * Mirror move event\n * @class MirrorMoveEvent\n * @module MirrorMoveEvent\n * @extends MirrorEvent\n */\nexport class MirrorMoveEvent extends MirrorEvent<MirrorMoveEventData> {\n  static type = 'mirror:move';\n  static cancelable = true;\n\n  /**\n   * Draggables mirror element\n   * @property mirror\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get mirror() {\n    return this.data.mirror;\n  }\n\n  /**\n   * Sensor has exceeded mirror's threshold on x axis\n   * @type {Boolean}\n   * @readonly\n   */\n  get passedThreshX() {\n    return this.data.passedThreshX;\n  }\n\n  /**\n   * Sensor has exceeded mirror's threshold on y axis\n   * @type {Boolean}\n   * @readonly\n   */\n  get passedThreshY() {\n    return this.data.passedThreshY;\n  }\n}\n\ninterface MirrorMovedEventData extends MirrorEventData {\n  mirror: HTMLElement;\n  passedThreshX: boolean;\n  passedThreshY: boolean;\n}\n\n/**\n * Mirror moved event\n * @class MirrorMovedEvent\n * @module MirrorMovedEvent\n * @extends MirrorEvent\n */\nexport class MirrorMovedEvent extends MirrorEvent<MirrorMovedEventData> {\n  static type = 'mirror:moved';\n\n  /**\n   * Draggables mirror element\n   * @property mirror\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get mirror() {\n    return this.data.mirror;\n  }\n\n  /**\n   * Sensor has exceeded mirror's threshold on x axis\n   * @type {Boolean}\n   * @readonly\n   */\n  get passedThreshX() {\n    return this.data.passedThreshX;\n  }\n\n  /**\n   * Sensor has exceeded mirror's threshold on y axis\n   * @type {Boolean}\n   * @readonly\n   */\n  get passedThreshY() {\n    return this.data.passedThreshY;\n  }\n}\n\ninterface MirrorDestroyEventData extends MirrorEventData {\n  mirror: HTMLElement;\n}\n\n/**\n * Mirror destroy event\n * @class MirrorDestroyEvent\n * @module MirrorDestroyEvent\n * @extends MirrorEvent\n */\nexport class MirrorDestroyEvent extends MirrorEvent<MirrorDestroyEventData> {\n  static type = 'mirror:destroy';\n  static cancelable = true;\n\n  /**\n   * Draggables mirror element\n   * @property mirror\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get mirror() {\n    return this.data.mirror;\n  }\n}\n"
  },
  {
    "path": "src/Draggable/Plugins/Mirror/MirrorEvent/README.md",
    "content": "## MirrorEvent\n\nThe base mirror event for all Mirror events that `Draggable` emits.\n\n|                   |               |\n| ----------------- | ------------- |\n| **Interface**     | `MirrorEvent` |\n| **Cancelable**    | false         |\n| **Cancel action** | -             |\n| **type**          | `mirror`      |\n\n### API\n\n**`mirrorEvent.source: HTMLElement`**  \nRead-only property for the source element. This is a straight copy of the `originalSource`\nelement, which can be moved around in the DOM.\n\n**`mirrorEvent.originalSource: HTMLElement`**  \nRead-only property for the original source element that was picked up. This element never\nmoves in the DOM and gets hidden on `drag:start`.\n\n**`mirrorEvent.sourceContainer: HTMLElement`**  \nRead-only property for the source elements container. This would be one of the containers that\nwas passed into Draggable.\n\n**`mirrorEvent.sensorEvent: SensorEvent`**  \nRead-only property for the original sensor event that triggered this event.\n\n**`mirrorEvent.originalEvent: SensorEvent`**  \nRead-only property for the original event that triggered the sensor event.\n\n## MirrorCreateEvent\n\n`MirrorCreateEvent` gets triggered by `Draggable` before the mirror element gets created.\n\n|                   |                                            |\n| ----------------- | ------------------------------------------ |\n| **Specification** | `MirrorEvent`                              |\n| **Interface**     | `MirrorCreateEvent`                        |\n| **Cancelable**    | true                                       |\n| **Cancel action** | Cancels the creation of the mirror element |\n| **type**          | `mirror:create`                            |\n\n## MirrorCreatedEvent\n\n`MirrorCreatedEvent` gets triggered by `Draggable` when the mirror element has\nbeen created.\n\n|                   |                      |\n| ----------------- | -------------------- |\n| **Specification** | `MirrorEvent`        |\n| **Interface**     | `MirrorCreatedEvent` |\n| **Cancelable**    | false                |\n| **Cancel action** | -                    |\n| **type**          | `mirror:created`     |\n\n### API\n\n**`mirrorEvent.mirror: HTMLElement`**  \nRead-only property for the mirror element, which is also a copy of the `originalSource` element.\nThe mirror follows your mouse/touch movements.\n\n## MirrorAttachedEvent\n\n`MirrorAttachedEvent` gets triggered when the mirror has been appended to the DOM.\n\n|                   |                       |\n| ----------------- | --------------------- |\n| **Specification** | `MirrorEvent`         |\n| **Interface**     | `MirrorAttachedEvent` |\n| **Cancelable**    | false                 |\n| **Cancel action** | -                     |\n| **type**          | `mirror:attached`     |\n\n### API\n\n**`mirrorEvent.mirror: HTMLElement`**  \nRead-only property for the mirror element, which is also a copy of the `originalSource` element.\nThe mirror follows your mouse/touch movements.\n\n## MirrorMoveEvent\n\n`MirrorMoveEvent` gets triggered when moving the mirror around.\n\n|                   |                       |\n| ----------------- | --------------------- |\n| **Specification** | `MirrorEvent`         |\n| **Interface**     | `MirrorMoveEvent`     |\n| **Cancelable**    | true                  |\n| **Cancel action** | Stops mirror movement |\n| **type**          | `mirror:move`         |\n\n### API\n\n**`mirrorEvent.mirror: HTMLElement`**  \nRead-only property for the mirror element, which is also a copy of the `originalSource` element.\nThe mirror follows your mouse/touch movements.\n\n**`mirrorEvent.passedThreshX: Booolean`**  \nRead-only property for whether or not the mirror's threshold has been exceeded in the x axis.\n\n**`mirrorEvent.passedThreshY: Booolean`**  \nRead-only property for whether or not the mirror's threshold has been exceeded in the y axis.\n\n## MirrorMovedEvent\n\n`MirrorMovedEvent` gets triggered when the **mirror:move** event was done.\n\n|                   |                    |\n| ----------------- | ------------------ |\n| **Specification** | `MirrorEvent`      |\n| **Interface**     | `MirrorMovedEvent` |\n| **Cancelable**    | false              |\n| **Cancel action** | -                  |\n| **type**          | `mirror:moved`     |\n\n### API\n\n**`mirrorEvent.mirror: HTMLElement`**  \nRead-only property for the mirror element, which is also a copy of the `originalSource` element.\nThe mirror follows your mouse/touch movements.\n\n**`mirrorEvent.passedThreshX: Booolean`**  \nRead-only property for whether or not the mirror's threshold has been exceeded in the x axis.\n\n**`mirrorEvent.passedThreshY: Booolean`**  \nRead-only property for whether or not the mirror's threshold has been exceeded in the y axis.\n\n## MirrorDestroyEvent\n\n`MirrorDestroyEvent` gets triggered before the mirror gets removed from the DOM.\n\n|                   |                                                |\n| ----------------- | ---------------------------------------------- |\n| **Specification** | `MirrorEvent`                                  |\n| **Interface**     | `MirrorDestroyEvent`                           |\n| **Cancelable**    | true                                           |\n| **Cancel action** | Cancels the removal of the mirror from the DOM |\n| **type**          | `mirror:destroy`                               |\n\n### API\n\n**`mirrorEvent.mirror: HTMLElement`**  \nRead-only property for the mirror element, which is also a copy of the `originalSource` element.\nThe mirror follows your mouse/touch movements.\n"
  },
  {
    "path": "src/Draggable/Plugins/Mirror/MirrorEvent/index.ts",
    "content": "export * from './MirrorEvent';\n"
  },
  {
    "path": "src/Draggable/Plugins/Mirror/README.md",
    "content": "## Mirror\n\nThe mirror plugin listens to Draggables `drag:start`, `drag:move` and `drag:stop` events to control the mirror.\nIt emits events like `mirror:create`, `mirror:created`, `mirror:attached`, `mirror:move` and `mirror:destroy`.\nThis plugin is used by draggable by default, but could potentially be replaced with a custom plugin.\n\n### API\n\n**`new Mirror(draggable: Draggable): Mirror`**  \nTo create a mirror plugin instance.\n\n### Options\n\n**`xAxis {Boolean}`**  \nIf enabled, the mirror will move on the x axis. Default: `true`\n\n**`yAxis {Boolean}`**  \nIf enabled, the mirror will move on the y axis. Default: `true`\n\n**`constrainDimensions {Boolean}`**  \nIf enabled, the source elements height and width will be applied to the mirror. Default: `false`\n\n**`cursorOffsetX {Number|null}`**  \nDefining this sets the offset from cursor to mirror manually on the x axis. Default: `null`\n\n**`cursorOffsetY {Number|null}`**  \nDefining this sets the offset from cursor to mirror manually on the y axis. Default: `null`\n\n**`appendTo {String|HTMLElement|Function}`**\nThe mirror plugin allows you to specify where the mirror should be appended to. For clarification,\nthis is not where the source will be placed, only the temporary mirror element, which is the element\nthat follows your cursor as you drag. You can specify a css selector, a HTMLElement or a function\nthat returns a HTMLElement. Default is the source parent element.\n\n**`thresholdX {Number|null}`**\nDefining this sets a threshold that must be exceeded by the mouse for the mirror to move on the x axis. Default: `null`\n\n**`thresholdY {Number|null}`**\nDefining this sets a threshold that must be exceeded by the mouse for the mirror to move on the y axis. Default: `null`\n\n### Events\n\n| Name                                | Description                                           | Cancelable | Cancelable action        |\n| ----------------------------------- | ----------------------------------------------------- | ---------- | ------------------------ |\n| [`mirror:create`][mirrorcreate]     | Gets fired before draggable mirror gets created       | true       | Prevents mirror creation |\n| [`mirror:created`][mirrorcreated]   | Gets fired when draggable mirror gets created         | false      | -                        |\n| [`mirror:attached`][mirrorattached] | Gets fired when draggable mirror gets attached to DOM | false      | -                        |\n| [`mirror:move`][mirrormove]         | Gets fired when draggable mirror moves                | true       | Stop mirror movement     |\n| [`mirror:destroy`][mirrordestroy]   | Gets fired when draggable mirror gets removed         | true       | Stop mirror removal      |\n\n[mirrorcreate]: MirrorEvent#mirrorcreateevent\n[mirrorcreated]: MirrorEvent#mirrorcreatedevent\n[mirrorattached]: MirrorEvent#mirrorattachedevent\n[mirrormove]: MirrorEvent#mirrormoveevent\n[mirrordestroy]: MirrorEvent#mirrordestroyevent\n\n### Examples\n\n#### y Axis only\n\n```js\nimport {Draggable} from '@shopify/draggable';\n\nconst draggable = new Draggable(document.querySelectorAll('ul'), {\n  draggable: 'li',\n  mirror: {\n    constrainDimensions: true,\n    cursorOffsetX: 10,\n    cursorOffsetY: 10,\n    xAxis: false,\n  },\n});\n```\n\n#### x Axis only\n\n```js\nimport {Sortable} from '@shopify/draggable';\n\nconst sortable = new Sortable(document.querySelectorAll('ul'), {\n  draggable: 'li',\n  mirror: {\n    constrainDimensions: true,\n    cursorOffsetX: 10,\n    cursorOffsetY: 10,\n    yAxis: false,\n  },\n});\n```\n\n#### Appending mirror\n\n```js\nimport {Sortable} from '@shopify/draggable';\n\nconst sortable = new Sortable(document.querySelectorAll('ul'), {\n  draggable: 'li',\n  mirror: {\n    appendTo: '.some-other-element',\n  },\n});\n```\n"
  },
  {
    "path": "src/Draggable/Plugins/Mirror/index.js",
    "content": "import Mirror, {defaultOptions} from './Mirror';\n\nexport default Mirror;\nexport {defaultOptions};\n"
  },
  {
    "path": "src/Draggable/Plugins/Mirror/tests/Mirror.test.js",
    "content": "import {\n  createSandbox,\n  clickMouse,\n  moveMouse,\n  releaseMouse,\n  DRAG_DELAY,\n  waitForDragDelay,\n  waitForPromisesToResolve,\n  waitForRequestAnimationFrame,\n} from 'helper';\n\nimport {\n  MirrorCreateEvent,\n  MirrorCreatedEvent,\n  MirrorAttachedEvent,\n  MirrorMoveEvent,\n  MirrorMovedEvent,\n  MirrorDestroyEvent,\n} from '../MirrorEvent';\nimport Draggable from '../../..';\n\nconst sampleMarkup = `\n  <ul>\n    <li>First item</li>\n  </ul>\n`;\n\ndescribe('Mirror', () => {\n  let sandbox;\n  let container;\n  let draggableElement;\n  let draggable;\n\n  const draggableOptions = {\n    draggable: 'li',\n    delay: DRAG_DELAY,\n  };\n\n  beforeEach(() => {\n    sandbox = createSandbox(sampleMarkup);\n    container = sandbox.querySelector('ul');\n    draggableElement = sandbox.querySelector('li');\n  });\n\n  afterEach(() => {\n    draggable.destroy();\n    sandbox.remove();\n  });\n\n  it('creates mirror element on `drag:start`', async () => {\n    draggable = new Draggable(container, draggableOptions);\n\n    clickMouse(draggableElement);\n    waitForDragDelay();\n\n    await waitForPromisesToResolve();\n\n    const mirrorElement = document.querySelector('.draggable-mirror');\n\n    expect(mirrorElement).toBeInstanceOf(HTMLElement);\n\n    releaseMouse(draggable.source);\n  });\n\n  it('triggers `mirror:create` event on `drag:start`', () => {\n    draggable = new Draggable(container, draggableOptions);\n\n    const mirrorCreateHandler = jest.fn();\n    let dragEvent;\n\n    draggable.on('mirror:create', mirrorCreateHandler);\n    draggable.on(\n      'drag:start',\n      (dragStartEvent) => (dragEvent = dragStartEvent),\n    );\n\n    clickMouse(draggableElement);\n    waitForDragDelay();\n\n    expect(mirrorCreateHandler).toHaveBeenCalledWithEvent(MirrorCreateEvent);\n    expect(mirrorCreateHandler).toHaveBeenCalledWithEventProperties({\n      dragEvent,\n      source: dragEvent.source,\n      originalSource: dragEvent.originalSource,\n      sourceContainer: dragEvent.sourceContainer,\n      sensorEvent: dragEvent.sensorEvent,\n      originalEvent: dragEvent.originalEvent,\n    });\n\n    releaseMouse(draggable.source);\n  });\n\n  it('prevents mirror creation when `drag:start` gets canceled', () => {\n    draggable = new Draggable(container, draggableOptions);\n\n    draggable.on('drag:start', (dragEvent) => {\n      dragEvent.cancel();\n    });\n\n    clickMouse(draggableElement);\n    waitForDragDelay();\n\n    const mirrorElement = document.querySelector('.draggable-mirror');\n\n    expect(mirrorElement).toBeNull();\n\n    releaseMouse(draggable.source);\n  });\n\n  it('prevents mirror creation when `mirror:create` gets canceled', () => {\n    draggable = new Draggable(container, draggableOptions);\n\n    draggable.on('mirror:create', (mirrorEvent) => {\n      mirrorEvent.cancel();\n    });\n\n    clickMouse(draggableElement);\n    waitForDragDelay();\n\n    const mirrorElement = document.querySelector('.draggable-mirror');\n\n    expect(mirrorElement).toBeNull();\n\n    releaseMouse(draggable.source);\n  });\n\n  it('triggers `mirror:created` event on `drag:start`', async () => {\n    draggable = new Draggable(container, draggableOptions);\n\n    const mirrorCreatedHandler = jest.fn();\n    let dragEvent;\n\n    draggable.on('mirror:created', mirrorCreatedHandler);\n    draggable.on(\n      'drag:start',\n      (dragStartEvent) => (dragEvent = dragStartEvent),\n    );\n\n    clickMouse(draggableElement);\n    waitForDragDelay();\n\n    await waitForPromisesToResolve();\n\n    const mirrorElement = document.querySelector('.draggable-mirror');\n\n    expect(mirrorCreatedHandler).toHaveBeenCalledWithEvent(MirrorCreatedEvent);\n    expect(mirrorCreatedHandler).toHaveBeenCalledWithEventProperties({\n      dragEvent,\n      mirror: mirrorElement,\n      source: dragEvent.source,\n      originalSource: dragEvent.originalSource,\n      sourceContainer: dragEvent.sourceContainer,\n      sensorEvent: dragEvent.sensorEvent,\n      originalEvent: dragEvent.originalEvent,\n    });\n\n    releaseMouse(draggable.source);\n  });\n\n  it('triggers `mirror:attached` event on `drag:start`', async () => {\n    draggable = new Draggable(container, draggableOptions);\n\n    const mirrorAttachedHandler = jest.fn();\n    let dragEvent;\n\n    draggable.on('mirror:attached', mirrorAttachedHandler);\n    draggable.on(\n      'drag:start',\n      (dragStartEvent) => (dragEvent = dragStartEvent),\n    );\n\n    clickMouse(draggableElement);\n    waitForDragDelay();\n\n    await waitForPromisesToResolve();\n\n    const mirrorElement = document.querySelector('.draggable-mirror');\n\n    expect(mirrorAttachedHandler).toHaveBeenCalledWithEvent(\n      MirrorAttachedEvent,\n    );\n    expect(mirrorAttachedHandler).toHaveBeenCalledWithEventProperties({\n      dragEvent,\n      mirror: mirrorElement,\n      source: dragEvent.source,\n      originalSource: dragEvent.originalSource,\n      sourceContainer: dragEvent.sourceContainer,\n      sensorEvent: dragEvent.sensorEvent,\n      originalEvent: dragEvent.originalEvent,\n    });\n\n    releaseMouse(draggable.source);\n  });\n\n  it('triggers `mirror:move` event on `drag:move`', async () => {\n    draggable = new Draggable(container, draggableOptions);\n\n    const mirrorMoveHandler = jest.fn();\n    let dragEvent;\n\n    draggable.on('mirror:move', mirrorMoveHandler);\n    draggable.on('drag:move', (dragMoveEvent) => (dragEvent = dragMoveEvent));\n\n    clickMouse(draggableElement);\n    waitForDragDelay();\n\n    await waitForPromisesToResolve();\n\n    moveMouse(document.body);\n\n    const mirrorElement = document.querySelector('.draggable-mirror');\n\n    expect(mirrorMoveHandler).toHaveBeenCalledWithEvent(MirrorMoveEvent);\n    expect(mirrorMoveHandler).toHaveBeenCalledWithEventProperties({\n      dragEvent,\n      mirror: mirrorElement,\n      source: dragEvent.source,\n      originalSource: dragEvent.originalSource,\n      sourceContainer: dragEvent.sourceContainer,\n      sensorEvent: dragEvent.sensorEvent,\n      originalEvent: dragEvent.originalEvent,\n    });\n\n    releaseMouse(draggable.source);\n  });\n\n  it('triggers `mirror:moved` event on `drag:move` was done', async () => {\n    draggable = new Draggable(container, draggableOptions);\n\n    const mirrorMovedHandler = jest.fn();\n    let mirrorMoveEvent;\n\n    draggable.on('mirror:moved', mirrorMovedHandler);\n    draggable.on('mirror:move', (evt) => (mirrorMoveEvent = evt));\n\n    clickMouse(draggableElement);\n    waitForDragDelay();\n\n    await waitForPromisesToResolve();\n\n    moveMouse(document.body);\n\n    await waitForPromisesToResolve();\n\n    expect(mirrorMovedHandler).toHaveBeenCalledWithEvent(MirrorMovedEvent);\n    expect(mirrorMovedHandler).toHaveBeenCalledWithEventProperties({\n      dragEvent: mirrorMoveEvent.dragEvent,\n      mirror: mirrorMoveEvent.mirror,\n      source: mirrorMoveEvent.source,\n      originalSource: mirrorMoveEvent.originalSource,\n      sourceContainer: mirrorMoveEvent.sourceContainer,\n      sensorEvent: mirrorMoveEvent.sensorEvent,\n      originalEvent: mirrorMoveEvent.originalEvent,\n    });\n\n    releaseMouse(draggable.source);\n  });\n\n  it('prevents `mirror:move` event trigger when `drag:move` gets canceled', async () => {\n    draggable = new Draggable(container, draggableOptions);\n\n    const mirrorMoveHandler = jest.fn();\n    draggable.on('mirror:move', mirrorMoveHandler);\n    draggable.on('drag:move', (dragEvent) => {\n      dragEvent.cancel();\n    });\n\n    clickMouse(draggableElement);\n    waitForDragDelay();\n\n    await waitForPromisesToResolve();\n\n    moveMouse(document.body);\n\n    expect(mirrorMoveHandler).not.toHaveBeenCalledWithEvent(MirrorMoveEvent);\n\n    releaseMouse(draggable.source);\n  });\n\n  it('moves mirror on `mirror:move`', async () => {\n    draggable = new Draggable(container, draggableOptions);\n\n    clickMouse(draggableElement);\n    waitForDragDelay();\n    waitForRequestAnimationFrame();\n\n    await waitForPromisesToResolve();\n\n    const mirrorElement = document.querySelector('.draggable-mirror');\n\n    expect(mirrorElement.style.transform).toBe('translate3d(0px, 0px, 0)');\n\n    moveMouse(document.body, {\n      clientX: 100,\n      clientY: 100,\n    });\n\n    await waitForPromisesToResolve();\n    waitForRequestAnimationFrame();\n\n    expect(mirrorElement.style.transform).toBe('translate3d(100px, 100px, 0)');\n\n    moveMouse(document.body, {\n      clientX: 23,\n      clientY: 172,\n    });\n\n    await waitForPromisesToResolve();\n    waitForRequestAnimationFrame();\n\n    expect(mirrorElement.style.transform).toBe('translate3d(23px, 172px, 0)');\n\n    releaseMouse(draggable.source);\n  });\n\n  it('moves mirror only when past `thresholdX` or `thresholdY`', async () => {\n    draggable = new Draggable(container, {\n      ...draggableOptions,\n      mirror: {\n        thresholdX: 10,\n        thresholdY: 50,\n      },\n    });\n\n    clickMouse(draggableElement);\n    waitForDragDelay();\n    waitForRequestAnimationFrame();\n\n    await waitForPromisesToResolve();\n\n    const mirrorElement = document.querySelector('.draggable-mirror');\n\n    moveMouse(document.body, {\n      clientX: 5,\n      clientY: 10,\n    });\n\n    await waitForPromisesToResolve();\n    waitForRequestAnimationFrame();\n\n    expect(mirrorElement.style.transform).toBe('translate3d(0px, 0px, 0)');\n\n    moveMouse(document.body, {\n      clientX: 10,\n      clientY: 40,\n    });\n\n    await waitForPromisesToResolve();\n    waitForRequestAnimationFrame();\n\n    expect(mirrorElement.style.transform).toBe('translate3d(10px, 0px, 0)');\n\n    moveMouse(document.body, {\n      clientX: 100,\n      clientY: 100,\n    });\n\n    await waitForPromisesToResolve();\n    waitForRequestAnimationFrame();\n\n    expect(mirrorElement.style.transform).toBe('translate3d(100px, 100px, 0)');\n\n    releaseMouse(draggable.source);\n  });\n\n  it('prevents mirror movement when `mirror:move` gets canceled', async () => {\n    draggable = new Draggable(container, draggableOptions);\n\n    draggable.on('mirror:move', (mirrorEvent) => {\n      mirrorEvent.cancel();\n    });\n\n    clickMouse(draggableElement);\n    waitForDragDelay();\n    waitForRequestAnimationFrame();\n\n    await waitForPromisesToResolve();\n\n    const mirrorElement = document.querySelector('.draggable-mirror');\n    const originalTransform = mirrorElement.style.transform;\n\n    expect(originalTransform).toBe('translate3d(0px, 0px, 0)');\n\n    moveMouse(document.body, {\n      clientX: 100,\n      clientY: 100,\n    });\n\n    await waitForPromisesToResolve();\n    waitForRequestAnimationFrame();\n\n    expect(mirrorElement.style.transform).toBe(originalTransform);\n\n    releaseMouse(draggable.source);\n  });\n\n  it('triggers `mirror:destroy` event on `drag:stop`', async () => {\n    draggable = new Draggable(container, draggableOptions);\n\n    const mirrorDestroyHandler = jest.fn();\n    let dragEvent;\n\n    draggable.on('mirror:destroy', mirrorDestroyHandler);\n    draggable.on('drag:stop', (dragStopEvent) => (dragEvent = dragStopEvent));\n\n    clickMouse(draggableElement);\n    waitForDragDelay();\n\n    await waitForPromisesToResolve();\n\n    const mirrorElement = document.querySelector('.draggable-mirror');\n\n    releaseMouse(draggable.source);\n\n    expect(mirrorDestroyHandler).toHaveBeenCalledWithEvent(MirrorDestroyEvent);\n    expect(mirrorDestroyHandler).toHaveBeenCalledWithEventProperties({\n      dragEvent,\n      mirror: mirrorElement,\n      source: dragEvent.source,\n      sourceContainer: dragEvent.sourceContainer,\n      sensorEvent: dragEvent.sensorEvent,\n      originalEvent: dragEvent.originalEvent,\n    });\n  });\n\n  it('destroys mirror on `mirror:destroy`', async () => {\n    draggable = new Draggable(container, draggableOptions);\n\n    let mirrorElement;\n\n    clickMouse(draggableElement);\n    waitForDragDelay();\n\n    await waitForPromisesToResolve();\n\n    mirrorElement = document.querySelector('.draggable-mirror');\n\n    expect(mirrorElement).toBeInstanceOf(HTMLElement);\n\n    releaseMouse(draggable.source);\n\n    mirrorElement = document.querySelector('.draggable-mirror');\n\n    expect(mirrorElement).toBeNull();\n  });\n\n  it('prevents mirror destruction when `mirror:destroy` gets canceled', async () => {\n    draggable = new Draggable(container, draggableOptions);\n\n    let mirrorElement;\n\n    draggable.on('mirror:destroy', (mirrorEvent) => {\n      mirrorEvent.cancel();\n    });\n\n    clickMouse(draggableElement);\n    waitForDragDelay();\n\n    await waitForPromisesToResolve();\n\n    mirrorElement = document.querySelector('.draggable-mirror');\n\n    expect(mirrorElement).toBeInstanceOf(HTMLElement);\n\n    releaseMouse(draggable.source);\n\n    mirrorElement = document.querySelector('.draggable-mirror');\n\n    expect(mirrorElement).toBeInstanceOf(HTMLElement);\n  });\n\n  it('appends mirror to source container by default', async () => {\n    draggable = new Draggable(container, draggableOptions);\n\n    clickMouse(draggableElement);\n    waitForDragDelay();\n\n    await waitForPromisesToResolve();\n\n    const mirrorElement = document.querySelector('.draggable-mirror');\n\n    expect(mirrorElement.parentNode).toBe(draggable.sourceContainer);\n\n    releaseMouse(draggable.source);\n  });\n\n  it('appends mirror by css selector', async () => {\n    draggable = new Draggable(container, {\n      ...draggableOptions,\n      mirror: {appendTo: 'body'},\n    });\n\n    clickMouse(draggableElement);\n    waitForDragDelay();\n\n    await waitForPromisesToResolve();\n\n    const mirrorElement = document.querySelector('.draggable-mirror');\n\n    expect(mirrorElement.parentNode).toBe(document.body);\n\n    releaseMouse(draggable.source);\n  });\n\n  it('appends mirror by function', async () => {\n    draggable = new Draggable(container, {\n      ...draggableOptions,\n      mirror: {appendTo: () => document.body},\n    });\n\n    clickMouse(draggableElement);\n    waitForDragDelay();\n\n    await waitForPromisesToResolve();\n\n    const mirrorElement = document.querySelector('.draggable-mirror');\n\n    expect(mirrorElement.parentNode).toBe(document.body);\n\n    releaseMouse(draggable.source);\n  });\n\n  it('appends mirror by element', async () => {\n    draggable = new Draggable(container, {\n      ...draggableOptions,\n      mirror: {appendTo: document.body},\n    });\n\n    clickMouse(draggableElement);\n    waitForDragDelay();\n\n    await waitForPromisesToResolve();\n\n    const mirrorElement = document.querySelector('.draggable-mirror');\n\n    expect(mirrorElement.parentNode).toBe(document.body);\n\n    releaseMouse(draggable.source);\n  });\n\n  describe('when `drag:stopped`', () => {\n    it('mirror element was removed from document', async () => {\n      draggable = new Draggable(container, draggableOptions);\n\n      clickMouse(draggableElement);\n      waitForDragDelay();\n\n      await waitForPromisesToResolve();\n\n      const mirrorElement = document.querySelector('.draggable-mirror');\n\n      draggable.on('drag:stopped', () => {\n        expect(mirrorElement.parentNode).toBeNull();\n      });\n\n      releaseMouse(draggable.source);\n    });\n  });\n});\n"
  },
  {
    "path": "src/Draggable/Plugins/README.md",
    "content": "## Draggable plugins\n\nThese plugins are included by draggable by default\n\n- [Announcement](Announcement)\n- [Focusable](Focusable)\n- [Mirror](Mirror)\n- [Scrollable](Scrollable)\n"
  },
  {
    "path": "src/Draggable/Plugins/Scrollable/README.md",
    "content": "## Scrollable\n\nThe auto scroll plugin listens to Draggables `drag:start`, `drag:move` and `drag:stop` events to determine when to scroll\nthe document it's on.\nThis plugin is used by draggable by default, but could potentially be replaced with a custom plugin.\n\n### API\n\n**`new Scrollable(draggable: Draggable): Scrollable`**\nTo create an auto scroll plugin instance.\n\n### Options\n\n**`speed {Number}`**\nDetermines the scroll speed. Default: `10`\n\n**`sensitivity {Number}`**\nDetermines the sensitivity of scrolling. Default: `30`\n\n**`scrollableElements {HTMLElement[]}`**\nAllows users to specify their own scrollable elements, rather than letting Draggable compute these automatically. Default: `[]`\n\n### Examples\n\n```js\nimport {Draggable} from '@shopify/draggable';\n\nconst customScrollableElements = document.querySelectorAll('.my-custom-scroll-elements')\n\nconst draggable = new Draggable(document.querySelectorAll('ul'), {\n  draggable: 'li',\n  scrollable: {\n    speed: 6,\n    sensitivity: 12,\n    scrollableElements: [\n      ...customScrollableElements,\n    ],\n  },\n});\n```\n\n#### Removing the plugin\n\n```js\nimport {Draggable} from '@shopify/draggable';\n\nconst draggable = new Draggable(document.querySelectorAll('ul'), {\n  draggable: 'li',\n});\n\n// Removes Scrollable plugin\ndraggable.removePlugin(Draggable.Plugin.Scrollable);\n\n// Adds custom scroll plugin\ndraggable.addPlugin(CustomScrollPlugin);\n```\n"
  },
  {
    "path": "src/Draggable/Plugins/Scrollable/Scrollable.js",
    "content": "import AbstractPlugin from 'shared/AbstractPlugin';\nimport {closest} from 'shared/utils';\n\nexport const onDragStart = Symbol('onDragStart');\nexport const onDragMove = Symbol('onDragMove');\nexport const onDragStop = Symbol('onDragStop');\nexport const scroll = Symbol('scroll');\n\n/**\n * Scrollable default options\n * @property {Object} defaultOptions\n * @property {Number} defaultOptions.speed\n * @property {Number} defaultOptions.sensitivity\n * @property {HTMLElement[]} defaultOptions.scrollableElements\n * @type {Object}\n */\nexport const defaultOptions = {\n  speed: 6,\n  sensitivity: 50,\n  scrollableElements: [],\n};\n\n/**\n * Scrollable plugin which scrolls the closest scrollable parent\n * @class Scrollable\n * @module Scrollable\n * @extends AbstractPlugin\n */\nexport default class Scrollable extends AbstractPlugin {\n  /**\n   * Scrollable constructor.\n   * @constructs Scrollable\n   * @param {Draggable} draggable - Draggable instance\n   */\n  constructor(draggable) {\n    super(draggable);\n\n    /**\n     * Scrollable options\n     * @property {Object} options\n     * @property {Number} options.speed\n     * @property {Number} options.sensitivity\n     * @property {HTMLElement[]} options.scrollableElements\n     * @type {Object}\n     */\n    this.options = {\n      ...defaultOptions,\n      ...this.getOptions(),\n    };\n\n    /**\n     * Keeps current mouse position\n     * @property {Object} currentMousePosition\n     * @property {Number} currentMousePosition.clientX\n     * @property {Number} currentMousePosition.clientY\n     * @type {Object|null}\n     */\n    this.currentMousePosition = null;\n\n    /**\n     * Scroll animation frame\n     * @property scrollAnimationFrame\n     * @type {Number|null}\n     */\n    this.scrollAnimationFrame = null;\n\n    /**\n     * Closest scrollable element\n     * @property scrollableElement\n     * @type {HTMLElement|null}\n     */\n    this.scrollableElement = null;\n\n    /**\n     * Animation frame looking for the closest scrollable element\n     * @property findScrollableElementFrame\n     * @type {Number|null}\n     */\n    this.findScrollableElementFrame = null;\n\n    this[onDragStart] = this[onDragStart].bind(this);\n    this[onDragMove] = this[onDragMove].bind(this);\n    this[onDragStop] = this[onDragStop].bind(this);\n    this[scroll] = this[scroll].bind(this);\n  }\n\n  /**\n   * Attaches plugins event listeners\n   */\n  attach() {\n    this.draggable\n      .on('drag:start', this[onDragStart])\n      .on('drag:move', this[onDragMove])\n      .on('drag:stop', this[onDragStop]);\n  }\n\n  /**\n   * Detaches plugins event listeners\n   */\n  detach() {\n    this.draggable\n      .off('drag:start', this[onDragStart])\n      .off('drag:move', this[onDragMove])\n      .off('drag:stop', this[onDragStop]);\n  }\n\n  /**\n   * Returns options passed through draggable\n   * @return {Object}\n   */\n  getOptions() {\n    return this.draggable.options.scrollable || {};\n  }\n\n  /**\n   * Returns closest scrollable elements by element\n   * @param {HTMLElement} target\n   * @return {HTMLElement}\n   */\n  getScrollableElement(target) {\n    if (this.hasDefinedScrollableElements()) {\n      return (\n        closest(target, this.options.scrollableElements) ||\n        document.documentElement\n      );\n    } else {\n      return closestScrollableElement(target);\n    }\n  }\n\n  /**\n   * Returns true if at least one scrollable element have been defined via options\n   * @param {HTMLElement} target\n   * @return {Boolean}\n   */\n  hasDefinedScrollableElements() {\n    return Boolean(this.options.scrollableElements.length !== 0);\n  }\n\n  /**\n   * Drag start handler. Finds closest scrollable parent in separate frame\n   * @param {DragStartEvent} dragEvent\n   * @private\n   */\n  [onDragStart](dragEvent) {\n    this.findScrollableElementFrame = requestAnimationFrame(() => {\n      this.scrollableElement = this.getScrollableElement(dragEvent.source);\n    });\n  }\n\n  /**\n   * Drag move handler. Remembers mouse position and initiates scrolling\n   * @param {DragMoveEvent} dragEvent\n   * @private\n   */\n  [onDragMove](dragEvent) {\n    this.findScrollableElementFrame = requestAnimationFrame(() => {\n      this.scrollableElement = this.getScrollableElement(\n        dragEvent.sensorEvent.target,\n      );\n    });\n\n    if (!this.scrollableElement) {\n      return;\n    }\n\n    const sensorEvent = dragEvent.sensorEvent;\n    const scrollOffset = {x: 0, y: 0};\n\n    if ('ontouchstart' in window) {\n      scrollOffset.y =\n        window.pageYOffset ||\n        document.documentElement.scrollTop ||\n        document.body.scrollTop ||\n        0;\n      scrollOffset.x =\n        window.pageXOffset ||\n        document.documentElement.scrollLeft ||\n        document.body.scrollLeft ||\n        0;\n    }\n\n    this.currentMousePosition = {\n      clientX: sensorEvent.clientX - scrollOffset.x,\n      clientY: sensorEvent.clientY - scrollOffset.y,\n    };\n\n    this.scrollAnimationFrame = requestAnimationFrame(this[scroll]);\n  }\n\n  /**\n   * Drag stop handler. Cancels scroll animations and resets state\n   * @private\n   */\n  [onDragStop]() {\n    cancelAnimationFrame(this.scrollAnimationFrame);\n    cancelAnimationFrame(this.findScrollableElementFrame);\n\n    this.scrollableElement = null;\n    this.scrollAnimationFrame = null;\n    this.findScrollableElementFrame = null;\n    this.currentMousePosition = null;\n  }\n\n  /**\n   * Scroll function that does the heavylifting\n   * @private\n   */\n  [scroll]() {\n    if (!this.scrollableElement || !this.currentMousePosition) {\n      return;\n    }\n\n    cancelAnimationFrame(this.scrollAnimationFrame);\n\n    const {speed, sensitivity} = this.options;\n\n    const rect = this.scrollableElement.getBoundingClientRect();\n    const bottomCutOff = rect.bottom > window.innerHeight;\n    const topCutOff = rect.top < 0;\n    const cutOff = topCutOff || bottomCutOff;\n\n    const documentScrollingElement = getDocumentScrollingElement();\n    const scrollableElement = this.scrollableElement;\n    const clientX = this.currentMousePosition.clientX;\n    const clientY = this.currentMousePosition.clientY;\n\n    if (\n      scrollableElement !== document.body &&\n      scrollableElement !== document.documentElement &&\n      !cutOff\n    ) {\n      const {offsetHeight, offsetWidth} = scrollableElement;\n\n      if (rect.top + offsetHeight - clientY < sensitivity) {\n        scrollableElement.scrollTop += speed;\n      } else if (clientY - rect.top < sensitivity) {\n        scrollableElement.scrollTop -= speed;\n      }\n\n      if (rect.left + offsetWidth - clientX < sensitivity) {\n        scrollableElement.scrollLeft += speed;\n      } else if (clientX - rect.left < sensitivity) {\n        scrollableElement.scrollLeft -= speed;\n      }\n    } else {\n      const {innerHeight, innerWidth} = window;\n\n      if (clientY < sensitivity) {\n        documentScrollingElement.scrollTop -= speed;\n      } else if (innerHeight - clientY < sensitivity) {\n        documentScrollingElement.scrollTop += speed;\n      }\n\n      if (clientX < sensitivity) {\n        documentScrollingElement.scrollLeft -= speed;\n      } else if (innerWidth - clientX < sensitivity) {\n        documentScrollingElement.scrollLeft += speed;\n      }\n    }\n\n    this.scrollAnimationFrame = requestAnimationFrame(this[scroll]);\n  }\n}\n\n/**\n * Returns true if the passed element has overflow\n * @param {HTMLElement} element\n * @return {Boolean}\n * @private\n */\nfunction hasOverflow(element) {\n  const overflowRegex = /(auto|scroll)/;\n  const computedStyles = getComputedStyle(element, null);\n\n  const overflow =\n    computedStyles.getPropertyValue('overflow') +\n    computedStyles.getPropertyValue('overflow-y') +\n    computedStyles.getPropertyValue('overflow-x');\n\n  return overflowRegex.test(overflow);\n}\n\n/**\n * Returns true if the passed element is statically positioned\n * @param {HTMLElement} element\n * @return {Boolean}\n * @private\n */\nfunction isStaticallyPositioned(element) {\n  const position = getComputedStyle(element).getPropertyValue('position');\n  return position === 'static';\n}\n\n/**\n * Finds closest scrollable element\n * @param {HTMLElement} element\n * @return {HTMLElement}\n * @private\n */\nfunction closestScrollableElement(element) {\n  if (!element) {\n    return getDocumentScrollingElement();\n  }\n\n  const position = getComputedStyle(element).getPropertyValue('position');\n  const excludeStaticParents = position === 'absolute';\n\n  const scrollableElement = closest(element, (parent) => {\n    if (excludeStaticParents && isStaticallyPositioned(parent)) {\n      return false;\n    }\n    return hasOverflow(parent);\n  });\n\n  if (position === 'fixed' || !scrollableElement) {\n    return getDocumentScrollingElement();\n  } else {\n    return scrollableElement;\n  }\n}\n\n/**\n * Returns element that scrolls document\n * @return {HTMLElement}\n * @private\n */\nfunction getDocumentScrollingElement() {\n  return document.scrollingElement || document.documentElement;\n}\n"
  },
  {
    "path": "src/Draggable/Plugins/Scrollable/index.js",
    "content": "import Scrollable, {defaultOptions} from './Scrollable';\n\nexport default Scrollable;\nexport {defaultOptions};\n"
  },
  {
    "path": "src/Draggable/Plugins/index.js",
    "content": "export {\n  default as Announcement,\n  defaultOptions as defaultAnnouncementOptions,\n} from './Announcement';\nexport {default as Focusable} from './Focusable';\nexport {\n  default as Mirror,\n  defaultOptions as defaultMirrorOptions,\n} from './Mirror';\nexport {\n  default as Scrollable,\n  defaultOptions as defaultScrollableOptions,\n} from './Scrollable';\n"
  },
  {
    "path": "src/Draggable/README.md",
    "content": "## Draggable\n\n### Usage\n\n- NPM:\n\n```js\nimport {Draggable} from '@shopify/draggable';\n// Or\nimport Draggable from '@shopify/draggable/build/esm/Draggable/Draggable';\n\nconst draggable = new Draggable(document.querySelectorAll('ul'), {\n  draggable: 'li',\n});\n```\n\n- Browser (as a module):\n\n```html\n<script type=\"module\">\n  import Draggable from 'https://cdn.jsdelivr.net/npm/@shopify/draggable/build/esm/Draggable/Draggable.mjs';\n\n  const draggable = new Draggable(document.querySelectorAll('ul'), {\n    draggable: 'li',\n  });\n</script>\n```\n\n- Browser (Standalone):\n\n```html\n<script src=\"https://cdn.jsdelivr.net/npm/@shopify/draggable/build/umd/index.min.js\"></script>\n<script>\n  const draggable = new Draggable.Draggable(document.querySelectorAll('ul'), {\n    draggable: 'li',\n  });\n</script>\n```\n\n### API\n\n**`new Draggable(containers: HTMLElement[]|NodeList|HTMLElement, options: Object): Draggable`**  \nTo create a draggable instance you need to specify the container(s) that hold draggable items, e.g.\n`document.body` would work too. The second argument is an options object, which is described\nbelow.\n\n**`draggable.destroy(): void`**  \nDetaches all sensors and listeners, and cleans up after itself.\n\n**`draggable.on(eventName: String, listener: Function): Draggable`**  \nDraggable is an event emitter, so you can register callbacks for events. Draggable\nalso supports method chaining.\n\n**`draggable.off(eventName: String, listener: Function): Draggable`**  \nYou can unregister listeners by using `.off()`, make sure to provide the same callback.\n\n**`draggable.trigger(event: AbstractEvent): void`**  \nYou can trigger events through draggable. This is used to fire events internally or by\nextensions of Draggable.\n\n**`draggable.addPlugin(plugins: ...typeof Plugin): Draggable`**  \nAdds plugins to this draggable instance.\n\n**`draggable.removePlugin(plugins: ...typeof Plugin): Draggable`**  \nRemoves plugins that are already attached to this draggable instance.\n\n**`draggable.addSensor(sensors: ...typeof Sensor): Draggable`**  \nAdds sensors to this draggable instance.\n\n**`draggable.removeSensor(sensors: ...typeof Sensor): Draggable`**  \nRemoves sensors that are already attached to this draggable instance.\n\n**`draggable.addContainer(containers: ...HTMLElement): Draggable`**  \nAdds containers to this draggable instance.\n\n**`draggable.removeContainer(containers: ...HTMLElement): Draggable`**  \nRemoves containers from this draggable instance.\n\n**`draggable.getClassNameFor(name: String): String`**  \nReturns class name for class identifier, check the classes table below for identifiers.\n\n**`draggable.getClassNamesFor(name: String): String[]`**  \nReturns array of class name for class identifier, useful when working with atomic css, check the classes table below for identifiers.\n\n**`draggable.isDragging(): Boolean`**  \nReturns true or false, depending on this draggables dragging state.\n\n**`draggable.getDraggableElementsForContainer(container: HTMLElement): HTMLElement[]`**  \nReturns draggable elements for given container, excluding potential mirror or original so\nurce.\n**`draggable.cancel(): void`**  \nCancel current dragging state immediately\n_NOTE_: Can't revert elements that were changed to the beginning state (e.g sorted elements)\n\n### Options\n\n**`draggable {String}`**  \nA css selector for draggable elements within the `containers` specified. By default it will\nlook for an element with `.draggable-source` class. Default: `.draggable-source`\n\n**`handle {String}`**  \nSpecify a css selector for a handle element if you don't want to allow drag action\non the entire element. Default: `null`\n\n**`delay {Number|Object}`**  \nIf you want to delay a drag start you can specify delay in milliseconds. This can be useful\nfor draggable elements within scrollable containers. To allow touch scrolling, we set 100ms delay for TouchSensor by default. Default:\n\n```js\n{\n  mouse: 0,\n  drag: 0,\n  touch: 100,\n}\n```\n\nYou can set the same delay for all sensors by setting a number, or set an object to set the delay for each sensor separately.\n\n**`distance {Number}`**  \nThe distance you want the pointer to have moved before drag starts. This can be useful\nfor clickable draggable elements, such as links. Default: `0`\n\n**`plugins {Plugin[]}`**  \nPlugins add behaviour to Draggable by hooking into its life cycle, e.g. one of the default\nplugins controls the mirror movement. Default: `[]`\n\n**`sensors {Sensor[]}`**  \nSensors dictate how drag operations get triggered, by listening to native browser events.\nBy default draggable includes the `MouseSensor` & `TouchSensor`. Default: `[]`\n\n**`classes {{String: String|String[]}}`**  \nDraggable adds classes to elements to indicate state. These classes can be used to add styling\non elements in certain states. Accept String or Array of strings.\n\n**NOTE**: When specifying multiple classes to an indicate state, the first class MUST be unique for that state to avoid duplicate classes for other states. IE doesn't support add or remove multiple classes. If you want to use multiple classes in IE, you need to add a classList polyfill to your project first.\n\n**`exclude {plugins: Plugin[], sensors: Sensor[]}`**  \nAllow excluding default plugins and default sensors. Use with caution as it may create strange behavior.\n\n### Events\n\n| Name                                       | Description                                               | Cancelable | Cancelable action   |\n| ------------------------------------------ | --------------------------------------------------------- | ---------- | ------------------- |\n| [`draggable:initialize`][draggableinit]    | Gets fired when draggable gets initialized                | false      | -                   |\n| [`draggable:destroy`][draggabledest]       | Gets fired when draggable gets destroyed                  | false      | -                   |\n| [`drag:start`][dragstart]                  | Gets fired when drag action begins                        | true       | Prevents drag start |\n| [`drag:move`][dragmove]                    | Gets fired when moving a draggable around                 | false      | -                   |\n| [`drag:over`][dragover]                    | Gets fired when dragging over other draggable             | false      | -                   |\n| [`drag:over:container`][dragovercontainer] | Gets fired when dragging over other draggable container   | false      | -                   |\n| [`drag:out`][dragout]                      | Gets fired when dragging out of other draggable           | false      | -                   |\n| [`drag:out:container`][dragoutcontainer]   | Gets fired when dragging out of other draggable container | false      | -                   |\n| [`drag:stop`][dragstop]                    | Gets fired when draggable has been released               | false      | -                   |\n| [`drag:stopped`][dragstopped]              | Gets fired when draggable finished                        | false      | -                   |\n| [`drag:pressure`][dragpressure]            | Gets fired when using force touch on draggable element    | false      | -                   |\n\n[draggableinit]: DraggableEvent#draggableinitializedevent\n[draggabledest]: DraggableEvent#draggabledestroyevent\n[dragstart]: DragEvent#dragstartevent\n[dragmove]: DragEvent#dragmoveevent\n[dragover]: DragEvent#dragoverevent\n[dragovercontainer]: DragEvent#dragovercontainerevent\n[dragout]: DragEvent#dragoutevent\n[dragoutcontainer]: DragEvent#dragoutcontainerevent\n[dragstop]: DragEvent#dragstopevent\n[dragstopped]: DragEvent#dragstoppedevent\n[dragpressure]: DragEvent#dragpressureevent\n\n### Classes\n\n| Name                 | Description                                                           | Default                            |\n| -------------------- | --------------------------------------------------------------------- | ---------------------------------- |\n| `body:dragging`      | Classes added on the document body while dragging                     | `draggable--is-dragging`           |\n| `container:dragging` | Classes added on the container where the draggable was picked up from | `draggable-container--is-dragging` |\n| `source:dragging`    | Classes added on the draggable element that has been picked up        | `draggable-source--is-dragging`    |\n| `source:placed`      | Classes added on the draggable element on `drag:stop`                 | `draggable-source--placed`         |\n| `container:placed`   | Classes added on the draggable container element on `drag:stop`       | `draggable-container--placed`      |\n| `draggable:over`     | Classes added on draggable element you are dragging over              | `draggable--over`                  |\n| `container:over`     | Classes added on draggable container element you are dragging over    | `draggable-container--over`        |\n| `source:original`    | Classes added on the original source element, which is hidden on drag | `draggable--original`              |\n| `mirror`             | Classes added on the mirror element                                   | `draggable-mirror`                 |\n\n### Examples\n\nThis sample code will make list items draggable:\n\n```js\nimport {Draggable} from '@shopify/draggable';\n\nconst draggable = new Draggable(document.querySelectorAll('ul'), {\n  draggable: 'li',\n});\n\ndraggable.on('drag:start', () => console.log('drag:start'));\ndraggable.on('drag:move', () => console.log('drag:move'));\ndraggable.on('drag:stop', () => console.log('drag:stop'));\n```\n\nCreate draggable which excluded some default plugins and sensor:\n\n```js\nimport {Draggable} from '@shopify/draggable';\n\nconst draggable = new Draggable(document.querySelectorAll('ul'), {\n  draggable: 'li',\n  exclude: {\n    plugins: [Draggable.Plugins.Focusable],\n    sensors: [Draggable.Sensors.TouchSensor],\n  },\n});\n```\n\nCreate draggable with specific classes:\n\n```js\nimport {Draggable} from '@shopify/draggable';\n\nconst draggable = new Draggable(document.querySelectorAll('ul'), {\n  draggable: 'li',\n  classes: {\n    'draggable:over': ['draggable--over', 'bg-red-200', 'bg-opacity-25'],\n  },\n});\n```\n\nCancel dragging on ESC key up:\n\n```js\nimport {Draggable} from '@shopify/draggable';\n\nconst draggable = new Draggable(document.querySelectorAll('ul'), {\n  draggable: 'li',\n  classes: {\n    'draggable:over': ['draggable--over', 'bg-red-200', 'bg-opacity-25'],\n  },\n});\n```\n"
  },
  {
    "path": "src/Draggable/Sensors/DragSensor/DragSensor.js",
    "content": "import {closest} from 'shared/utils';\n\nimport Sensor from '../Sensor';\nimport {\n  DragStartSensorEvent,\n  DragMoveSensorEvent,\n  DragStopSensorEvent,\n} from '../SensorEvent';\n\nconst onMouseDown = Symbol('onMouseDown');\nconst onMouseUp = Symbol('onMouseUp');\nconst onDragStart = Symbol('onDragStart');\nconst onDragOver = Symbol('onDragOver');\nconst onDragEnd = Symbol('onDragEnd');\nconst onDrop = Symbol('onDrop');\nconst reset = Symbol('reset');\n\n/**\n * This sensor picks up native browser drag events and dictates drag operations\n * @class DragSensor\n * @module DragSensor\n * @extends Sensor\n */\nexport default class DragSensor extends Sensor {\n  /**\n   * DragSensor constructor.\n   * @constructs DragSensor\n   * @param {HTMLElement[]|NodeList|HTMLElement} containers - Containers\n   * @param {Object} options - Options\n   */\n  constructor(containers = [], options = {}) {\n    super(containers, options);\n\n    /**\n     * Mouse down timer which will end up setting the draggable attribute, unless canceled\n     * @property mouseDownTimeout\n     * @type {Number}\n     */\n    this.mouseDownTimeout = null;\n\n    /**\n     * Draggable element needs to be remembered to unset the draggable attribute after drag operation has completed\n     * @property draggableElement\n     * @type {HTMLElement}\n     */\n    this.draggableElement = null;\n\n    /**\n     * Native draggable element could be links or images, their draggable state will be disabled during drag operation\n     * @property nativeDraggableElement\n     * @type {HTMLElement}\n     */\n    this.nativeDraggableElement = null;\n\n    this[onMouseDown] = this[onMouseDown].bind(this);\n    this[onMouseUp] = this[onMouseUp].bind(this);\n    this[onDragStart] = this[onDragStart].bind(this);\n    this[onDragOver] = this[onDragOver].bind(this);\n    this[onDragEnd] = this[onDragEnd].bind(this);\n    this[onDrop] = this[onDrop].bind(this);\n  }\n\n  /**\n   * Attaches sensors event listeners to the DOM\n   */\n  attach() {\n    document.addEventListener('mousedown', this[onMouseDown], true);\n  }\n\n  /**\n   * Detaches sensors event listeners to the DOM\n   */\n  detach() {\n    document.removeEventListener('mousedown', this[onMouseDown], true);\n  }\n\n  /**\n   * Drag start handler\n   * @private\n   * @param {Event} event - Drag start event\n   */\n  [onDragStart](event) {\n    // Need for firefox. \"text\" key is needed for IE\n    event.dataTransfer.setData('text', '');\n    event.dataTransfer.effectAllowed = this.options.type;\n\n    const target = document.elementFromPoint(event.clientX, event.clientY);\n    const originalSource = this.draggableElement;\n\n    if (!originalSource) {\n      return;\n    }\n\n    const dragStartEvent = new DragStartSensorEvent({\n      clientX: event.clientX,\n      clientY: event.clientY,\n      target,\n      originalSource,\n      container: this.currentContainer,\n      originalEvent: event,\n    });\n\n    // Workaround\n    setTimeout(() => {\n      this.trigger(this.currentContainer, dragStartEvent);\n\n      if (dragStartEvent.canceled()) {\n        this.dragging = false;\n      } else {\n        this.dragging = true;\n      }\n    }, 0);\n  }\n\n  /**\n   * Drag over handler\n   * @private\n   * @param {Event} event - Drag over event\n   */\n  [onDragOver](event) {\n    if (!this.dragging) {\n      return;\n    }\n\n    const target = document.elementFromPoint(event.clientX, event.clientY);\n    const container = this.currentContainer;\n\n    const dragMoveEvent = new DragMoveSensorEvent({\n      clientX: event.clientX,\n      clientY: event.clientY,\n      target,\n      container,\n      originalEvent: event,\n    });\n\n    this.trigger(container, dragMoveEvent);\n\n    if (!dragMoveEvent.canceled()) {\n      event.preventDefault();\n      event.dataTransfer.dropEffect = this.options.type;\n    }\n  }\n\n  /**\n   * Drag end handler\n   * @private\n   * @param {Event} event - Drag end event\n   */\n  [onDragEnd](event) {\n    if (!this.dragging) {\n      return;\n    }\n\n    document.removeEventListener('mouseup', this[onMouseUp], true);\n\n    const target = document.elementFromPoint(event.clientX, event.clientY);\n    const container = this.currentContainer;\n\n    const dragStopEvent = new DragStopSensorEvent({\n      clientX: event.clientX,\n      clientY: event.clientY,\n      target,\n      container,\n      originalEvent: event,\n    });\n\n    this.trigger(container, dragStopEvent);\n\n    this.dragging = false;\n    this.startEvent = null;\n\n    this[reset]();\n  }\n\n  /**\n   * Drop handler\n   * @private\n   * @param {Event} event - Drop event\n   */\n  [onDrop](event) {\n    event.preventDefault();\n  }\n\n  /**\n   * Mouse down handler\n   * @private\n   * @param {Event} event - Mouse down event\n   */\n  [onMouseDown](event) {\n    // Firefox bug for inputs within draggables https://bugzilla.mozilla.org/show_bug.cgi?id=739071\n    if (event.target && (event.target.form || event.target.contenteditable)) {\n      return;\n    }\n\n    const target = event.target;\n    this.currentContainer = closest(target, this.containers);\n\n    if (!this.currentContainer) {\n      return;\n    }\n\n    if (\n      this.options.handle &&\n      target &&\n      !closest(target, this.options.handle)\n    ) {\n      return;\n    }\n\n    const originalSource = closest(target, this.options.draggable);\n\n    if (!originalSource) {\n      return;\n    }\n\n    const nativeDraggableElement = closest(\n      event.target,\n      (element) => element.draggable,\n    );\n\n    if (nativeDraggableElement) {\n      nativeDraggableElement.draggable = false;\n      this.nativeDraggableElement = nativeDraggableElement;\n    }\n\n    document.addEventListener('mouseup', this[onMouseUp], true);\n    document.addEventListener('dragstart', this[onDragStart], false);\n    document.addEventListener('dragover', this[onDragOver], false);\n    document.addEventListener('dragend', this[onDragEnd], false);\n    document.addEventListener('drop', this[onDrop], false);\n\n    this.startEvent = event;\n\n    this.mouseDownTimeout = setTimeout(() => {\n      originalSource.draggable = true;\n      this.draggableElement = originalSource;\n    }, this.delay.drag);\n  }\n\n  /**\n   * Mouse up handler\n   * @private\n   * @param {Event} event - Mouse up event\n   */\n  [onMouseUp]() {\n    this[reset]();\n  }\n\n  /**\n   * Mouse up handler\n   * @private\n   * @param {Event} event - Mouse up event\n   */\n  [reset]() {\n    clearTimeout(this.mouseDownTimeout);\n\n    document.removeEventListener('mouseup', this[onMouseUp], true);\n    document.removeEventListener('dragstart', this[onDragStart], false);\n    document.removeEventListener('dragover', this[onDragOver], false);\n    document.removeEventListener('dragend', this[onDragEnd], false);\n    document.removeEventListener('drop', this[onDrop], false);\n\n    if (this.nativeDraggableElement) {\n      this.nativeDraggableElement.draggable = true;\n      this.nativeDraggableElement = null;\n    }\n\n    if (this.draggableElement) {\n      this.draggableElement.draggable = false;\n      this.draggableElement = null;\n    }\n  }\n}\n"
  },
  {
    "path": "src/Draggable/Sensors/DragSensor/README.md",
    "content": "## Drag Sensor\n\n_Draggable does not use this sensor by default_\n\nPicks up browser drag events and triggers the events below on a source container.\n\n- `drag:start`\n- `drag:move`\n- `drag:stop`\n\n### API\n\n**`new DragSensor(containers: HTMLElement[]|NodeList|HTMLElement, options: Object): DragSensor`**  \nTo create a mouse sensor, specify the containers it should pay attention to. Sensors will always\ntrigger sensor events on container element.\n\n**`dragSensor.attach(): void`**  \nAttaches sensors to the DOM\n\n**`dragSensor.detach(): void`**  \nDetaches sensors to the DOM\n\n### Options\n\n**`draggable {String}`**  \nA css selector for draggable elements within the `containers` specified.\n\n**`delay {Number}`**  \nThis value will delay touch start\n\n### Known issues\n\nThe drag sensor uses the native Drag and Drop API and therefor Draggable does not create\na mirror. This means there is less control over the mirror.\n\n### Example\n\n```js\nimport {Draggable, Sensors} from '@shopify/draggable';\n\nconst draggable = new Draggable(containers, {\n  sensors: [Sensors.DragSensor],\n});\n\n// Remove default mouse sensor\ndraggable.removeSensor(Sensors.MouseSensor);\n```\n\n### Caveats\n\n- The `distance` option will not work with this sensor.\n"
  },
  {
    "path": "src/Draggable/Sensors/DragSensor/index.js",
    "content": "import DragSensor from './DragSensor';\n\nexport default DragSensor;\n"
  },
  {
    "path": "src/Draggable/Sensors/DragSensor/tests/DragSensor.test.js",
    "content": "import {\n  createSandbox,\n  dragStart,\n  dragOver,\n  dragStop,\n  clickMouse,\n  releaseMouse,\n  DRAG_DELAY,\n  waitForDragDelay,\n} from 'helper';\n\nimport DragSensor from '..';\n\nconst sampleMarkup = `\n  <ul>\n    <li class=\"draggable\">First item</li>\n    <li class=\"draggable\">Second item</li>\n    <li class=\"non-draggable\">Non draggable item</li>\n  </ul>\n`;\n\ndescribe('DragSensor', () => {\n  let sandbox;\n  let dragSensor;\n  let draggableElement;\n  let nonDraggableElement;\n\n  function setup(optionsParam = {}) {\n    const options = {\n      draggable: '.draggable',\n      delay: 0,\n      distance: 0,\n      ...optionsParam,\n    };\n\n    sandbox = createSandbox(sampleMarkup);\n    const containers = sandbox.querySelectorAll('ul');\n    draggableElement = sandbox.querySelector('.draggable');\n    nonDraggableElement = sandbox.querySelector('.non-draggable');\n    dragSensor = new DragSensor(containers, options);\n    dragSensor.attach();\n  }\n\n  function teardown() {\n    dragSensor.detach();\n    sandbox.remove();\n  }\n\n  describe('common', () => {\n    beforeEach(() => {\n      setup();\n    });\n\n    afterEach(teardown);\n\n    /* eslint-disable jest/no-disabled-tests */\n    it.skip('mousedown handler adds draggable attribute', () => {\n      expect(draggableElement.draggable).toBeUndefined();\n\n      clickMouse(draggableElement);\n      waitForDragDelay();\n\n      expect(draggableElement.draggable).toBe(true);\n\n      releaseMouse(draggableElement);\n\n      expect(draggableElement.draggable).toBe(false);\n    });\n    /* eslint-enable jest/no-disabled-tests */\n\n    it('triggers `drag:start` sensor event on dragstart', () => {\n      function dragFlow() {\n        clickMouse(draggableElement);\n        waitForDragDelay();\n        dragStart(draggableElement);\n        waitForDragDelay();\n        dragStop(draggableElement);\n        releaseMouse(document.body);\n      }\n\n      expect(dragFlow).toHaveTriggeredSensorEvent('drag:start');\n    });\n\n    it('cancels `drag:start` event when canceling sensor event', () => {\n      sandbox.addEventListener('drag:start', (event) => {\n        event.detail.cancel();\n      });\n\n      function dragFlow() {\n        clickMouse(draggableElement);\n        waitForDragDelay();\n        dragStart(draggableElement);\n        waitForDragDelay();\n        dragStop(draggableElement);\n        releaseMouse(document.body);\n      }\n\n      expect(dragFlow).toHaveCanceledSensorEvent('drag:start');\n    });\n\n    it('does not trigger `drag:start` event releasing mouse before timeout', () => {\n      function dragFlow() {\n        clickMouse(draggableElement);\n        waitForDragDelay();\n        dragStart(draggableElement);\n        waitForDragDelay();\n        dragStop(draggableElement);\n        releaseMouse(document.body);\n      }\n\n      function hastyDragFlow() {\n        clickMouse(draggableElement);\n        dragStart(draggableElement);\n        releaseMouse(document.body);\n      }\n\n      expect(hastyDragFlow).not.toHaveTriggeredSensorEvent('drag:start');\n\n      expect(hastyDragFlow).not.toHaveTriggeredSensorEvent('drag:stop');\n\n      expect(dragFlow).toHaveTriggeredSensorEvent('drag:start');\n\n      expect(dragFlow).toHaveTriggeredSensorEvent('drag:stop');\n    });\n\n    it('triggers `drag:move` event while moving the mouse', () => {\n      function dragFlow() {\n        clickMouse(draggableElement);\n        waitForDragDelay();\n        dragStart(draggableElement);\n        waitForDragDelay();\n        dragOver(document.body);\n        dragStop(draggableElement);\n        releaseMouse(document.body);\n      }\n\n      expect(dragFlow).toHaveTriggeredSensorEvent('drag:move');\n    });\n\n    it('triggers `drag:stop` event when releasing mouse', () => {\n      function dragFlow() {\n        clickMouse(draggableElement);\n        waitForDragDelay();\n        dragStart(draggableElement);\n        waitForDragDelay();\n        dragOver(document.body);\n        dragStop(draggableElement);\n        releaseMouse(document.body);\n      }\n\n      expect(dragFlow).toHaveTriggeredSensorEvent('drag:stop');\n    });\n\n    it('does not trigger `drag:start` event when clicking on none draggable element', () => {\n      function dragFlow() {\n        clickMouse(document.body);\n        waitForDragDelay();\n        dragStart(document.body);\n        waitForDragDelay();\n        dragStop(draggableElement);\n        releaseMouse(document.body);\n\n        clickMouse(nonDraggableElement);\n        waitForDragDelay();\n        dragStart(nonDraggableElement);\n        waitForDragDelay();\n        dragStop(draggableElement);\n        releaseMouse(document.body);\n      }\n\n      expect(dragFlow).not.toHaveTriggeredSensorEvent('drag:start');\n    });\n  });\n\n  describe('using delay', () => {\n    beforeEach(() => {\n      setup({delay: DRAG_DELAY});\n    });\n\n    afterEach(teardown);\n\n    it('triggers `drag:start` sensor event after delay', () => {\n      function dragFlow() {\n        clickMouse(draggableElement);\n        waitForDragDelay();\n        dragStart(draggableElement);\n        waitForDragDelay();\n        dragStop(draggableElement);\n        releaseMouse(document.body);\n      }\n\n      expect(dragFlow).toHaveTriggeredSensorEvent('drag:start');\n    });\n\n    it('does not trigger `drag:start` event releasing mouse before delay', () => {\n      function dragFlow() {\n        clickMouse(draggableElement);\n        waitForDragDelay();\n        dragStart(draggableElement);\n        waitForDragDelay();\n        dragStop(draggableElement);\n        releaseMouse(document.body);\n      }\n\n      function hastyDragFlow() {\n        clickMouse(draggableElement);\n        dragStart(draggableElement);\n        dragStop(draggableElement);\n        releaseMouse(document.body);\n      }\n\n      expect(hastyDragFlow).not.toHaveTriggeredSensorEvent('drag:start');\n\n      expect(hastyDragFlow).not.toHaveTriggeredSensorEvent('drag:stop');\n\n      expect(dragFlow).toHaveTriggeredSensorEvent('drag:start');\n\n      expect(dragFlow).toHaveTriggeredSensorEvent('drag:stop');\n    });\n\n    it('triggers `drag:move` event while moving the mouse after delay', () => {\n      function dragFlow() {\n        clickMouse(draggableElement);\n        waitForDragDelay();\n        dragStart(draggableElement);\n        waitForDragDelay();\n        dragOver(document.body);\n        dragStop(draggableElement);\n        releaseMouse(document.body);\n      }\n\n      expect(dragFlow).toHaveTriggeredSensorEvent('drag:move');\n    });\n  });\n});\n"
  },
  {
    "path": "src/Draggable/Sensors/ForceTouchSensor/ForceTouchSensor.js",
    "content": "import {closest} from 'shared/utils';\n\nimport Sensor from '../Sensor';\nimport {\n  DragStartSensorEvent,\n  DragMoveSensorEvent,\n  DragStopSensorEvent,\n  DragPressureSensorEvent,\n} from '../SensorEvent';\n\nconst onMouseForceWillBegin = Symbol('onMouseForceWillBegin');\nconst onMouseForceDown = Symbol('onMouseForceDown');\nconst onMouseDown = Symbol('onMouseDown');\nconst onMouseForceChange = Symbol('onMouseForceChange');\nconst onMouseMove = Symbol('onMouseMove');\nconst onMouseUp = Symbol('onMouseUp');\nconst onMouseForceGlobalChange = Symbol('onMouseForceGlobalChange');\n\n/**\n * This sensor picks up native force touch events and dictates drag operations\n * @class ForceTouchSensor\n * @module ForceTouchSensor\n * @extends Sensor\n */\nexport default class ForceTouchSensor extends Sensor {\n  /**\n   * ForceTouchSensor constructor.\n   * @constructs ForceTouchSensor\n   * @param {HTMLElement[]|NodeList|HTMLElement} containers - Containers\n   * @param {Object} options - Options\n   */\n  constructor(containers = [], options = {}) {\n    super(containers, options);\n\n    /**\n     * Draggable element needs to be remembered to unset the draggable attribute after drag operation has completed\n     * @property mightDrag\n     * @type {Boolean}\n     */\n    this.mightDrag = false;\n\n    this[onMouseForceWillBegin] = this[onMouseForceWillBegin].bind(this);\n    this[onMouseForceDown] = this[onMouseForceDown].bind(this);\n    this[onMouseDown] = this[onMouseDown].bind(this);\n    this[onMouseForceChange] = this[onMouseForceChange].bind(this);\n    this[onMouseMove] = this[onMouseMove].bind(this);\n    this[onMouseUp] = this[onMouseUp].bind(this);\n  }\n\n  /**\n   * Attaches sensors event listeners to the DOM\n   */\n  attach() {\n    for (const container of this.containers) {\n      container.addEventListener(\n        'webkitmouseforcewillbegin',\n        this[onMouseForceWillBegin],\n        false,\n      );\n      container.addEventListener(\n        'webkitmouseforcedown',\n        this[onMouseForceDown],\n        false,\n      );\n      container.addEventListener('mousedown', this[onMouseDown], true);\n      container.addEventListener(\n        'webkitmouseforcechanged',\n        this[onMouseForceChange],\n        false,\n      );\n    }\n\n    document.addEventListener('mousemove', this[onMouseMove]);\n    document.addEventListener('mouseup', this[onMouseUp]);\n  }\n\n  /**\n   * Detaches sensors event listeners to the DOM\n   */\n  detach() {\n    for (const container of this.containers) {\n      container.removeEventListener(\n        'webkitmouseforcewillbegin',\n        this[onMouseForceWillBegin],\n        false,\n      );\n      container.removeEventListener(\n        'webkitmouseforcedown',\n        this[onMouseForceDown],\n        false,\n      );\n      container.removeEventListener('mousedown', this[onMouseDown], true);\n      container.removeEventListener(\n        'webkitmouseforcechanged',\n        this[onMouseForceChange],\n        false,\n      );\n    }\n\n    document.removeEventListener('mousemove', this[onMouseMove]);\n    document.removeEventListener('mouseup', this[onMouseUp]);\n  }\n\n  /**\n   * Mouse force will begin handler\n   * @private\n   * @param {Event} event - Mouse force will begin event\n   */\n  [onMouseForceWillBegin](event) {\n    event.preventDefault();\n    this.mightDrag = true;\n  }\n\n  /**\n   * Mouse force down handler\n   * @private\n   * @param {Event} event - Mouse force down event\n   */\n  [onMouseForceDown](event) {\n    if (this.dragging) {\n      return;\n    }\n\n    const target = document.elementFromPoint(event.clientX, event.clientY);\n    const container = event.currentTarget;\n\n    if (\n      this.options.handle &&\n      target &&\n      !closest(target, this.options.handle)\n    ) {\n      return;\n    }\n\n    const originalSource = closest(target, this.options.draggable);\n\n    if (!originalSource) {\n      return;\n    }\n\n    const dragStartEvent = new DragStartSensorEvent({\n      clientX: event.clientX,\n      clientY: event.clientY,\n      target,\n      container,\n      originalSource,\n      originalEvent: event,\n    });\n\n    this.trigger(container, dragStartEvent);\n\n    this.currentContainer = container;\n    this.dragging = !dragStartEvent.canceled();\n    this.mightDrag = false;\n  }\n\n  /**\n   * Mouse up handler\n   * @private\n   * @param {Event} event - Mouse up event\n   */\n  [onMouseUp](event) {\n    if (!this.dragging) {\n      return;\n    }\n\n    const dragStopEvent = new DragStopSensorEvent({\n      clientX: event.clientX,\n      clientY: event.clientY,\n      target: null,\n      container: this.currentContainer,\n      originalEvent: event,\n    });\n\n    this.trigger(this.currentContainer, dragStopEvent);\n\n    this.currentContainer = null;\n    this.dragging = false;\n    this.mightDrag = false;\n  }\n\n  /**\n   * Mouse down handler\n   * @private\n   * @param {Event} event - Mouse down event\n   */\n  [onMouseDown](event) {\n    if (!this.mightDrag) {\n      return;\n    }\n\n    // Need workaround for real click\n    // Cancel potential drag events\n    event.stopPropagation();\n    event.stopImmediatePropagation();\n    event.preventDefault();\n  }\n\n  /**\n   * Mouse move handler\n   * @private\n   * @param {Event} event - Mouse force will begin event\n   */\n  [onMouseMove](event) {\n    if (!this.dragging) {\n      return;\n    }\n\n    const target = document.elementFromPoint(event.clientX, event.clientY);\n\n    const dragMoveEvent = new DragMoveSensorEvent({\n      clientX: event.clientX,\n      clientY: event.clientY,\n      target,\n      container: this.currentContainer,\n      originalEvent: event,\n    });\n\n    this.trigger(this.currentContainer, dragMoveEvent);\n  }\n\n  /**\n   * Mouse force change handler\n   * @private\n   * @param {Event} event - Mouse force change event\n   */\n  [onMouseForceChange](event) {\n    if (this.dragging) {\n      return;\n    }\n\n    const target = event.target;\n    const container = event.currentTarget;\n\n    const dragPressureEvent = new DragPressureSensorEvent({\n      pressure: event.webkitForce,\n      clientX: event.clientX,\n      clientY: event.clientY,\n      target,\n      container,\n      originalEvent: event,\n    });\n\n    this.trigger(container, dragPressureEvent);\n  }\n\n  /**\n   * Mouse force global change handler\n   * @private\n   * @param {Event} event - Mouse force global change event\n   */\n  [onMouseForceGlobalChange](event) {\n    if (!this.dragging) {\n      return;\n    }\n\n    const target = event.target;\n\n    const dragPressureEvent = new DragPressureSensorEvent({\n      pressure: event.webkitForce,\n      clientX: event.clientX,\n      clientY: event.clientY,\n      target,\n      container: this.currentContainer,\n      originalEvent: event,\n    });\n\n    this.trigger(this.currentContainer, dragPressureEvent);\n  }\n}\n"
  },
  {
    "path": "src/Draggable/Sensors/ForceTouchSensor/README.md",
    "content": "## Force Touch Sensor\n\n**WORK IN PROGRESS**\n\n_Draggable does not use this sensor by default_\n\nPicks up browser force touch events and triggers the events below on a source container.\nThis sensor only works for Macbook Pros with force touch trackpads\n\n- `drag:start`\n- `drag:move`\n- `drag:stop`\n- `drag:pressure`\n\n### API\n\n**`new ForceTouchSensor(containers: HTMLElement[]|NodeList|HTMLElement, options: Object): ForceTouchSensor`**  \nTo create a force touch sensor, specify the containers it should pay attention to. Sensors will always\ntrigger sensor events on container element.\n\n**`forceTouchSensor.attach(): void`**  \nAttaches sensors to the DOM\n\n**`forceTouchSensor.detach(): void`**  \nDetaches sensors to the DOM\n\n### Options\n\n**`draggable {String}`**  \nA css selector for draggable elements within the `containers` specified.\n\n**`delay {Number}`**  \nThis value will delay force touch start\n\n**`handle {String}`**  \nSpecify a css selector for a handle element if you don't want to allow drag action on the entire element.\n\n### Example\n\n```js\nimport {Draggable, Sensors} from '@shopify/draggable';\n\nconst draggable = new Draggable(containers, {\n  sensors: [Sensors.ForceTouchSensor],\n});\n```\n\n### Caveats\n\n- When used in Safari with force touch track pad, make sure to add visual guidelines to the user to indicate that force needs to be used to start drag operation.\n"
  },
  {
    "path": "src/Draggable/Sensors/ForceTouchSensor/index.js",
    "content": "import ForceTouchSensor from './ForceTouchSensor';\n\nexport default ForceTouchSensor;\n"
  },
  {
    "path": "src/Draggable/Sensors/MouseSensor/MouseSensor.js",
    "content": "import {closest, distance as euclideanDistance} from 'shared/utils';\n\nimport Sensor from '../Sensor';\nimport {\n  DragStartSensorEvent,\n  DragMoveSensorEvent,\n  DragStopSensorEvent,\n} from '../SensorEvent';\n\nconst onContextMenuWhileDragging = Symbol('onContextMenuWhileDragging');\nconst onMouseDown = Symbol('onMouseDown');\nconst onMouseMove = Symbol('onMouseMove');\nconst onMouseUp = Symbol('onMouseUp');\nconst startDrag = Symbol('startDrag');\nconst onDistanceChange = Symbol('onDistanceChange');\n\n/**\n * This sensor picks up native browser mouse events and dictates drag operations\n * @class MouseSensor\n * @module MouseSensor\n * @extends Sensor\n */\nexport default class MouseSensor extends Sensor {\n  /**\n   * MouseSensor constructor.\n   * @constructs MouseSensor\n   * @param {HTMLElement[]|NodeList|HTMLElement} containers - Containers\n   * @param {Object} options - Options\n   */\n  constructor(containers = [], options = {}) {\n    super(containers, options);\n\n    /**\n     * Mouse down timer which will end up triggering the drag start operation\n     * @property mouseDownTimeout\n     * @type {Number}\n     */\n    this.mouseDownTimeout = null;\n\n    /**\n     * Save pageX coordinates for delay drag\n     * @property {Numbre} pageX\n     * @private\n     */\n    this.pageX = null;\n\n    /**\n     * Save pageY coordinates for delay drag\n     * @property {Numbre} pageY\n     * @private\n     */\n    this.pageY = null;\n\n    this[onContextMenuWhileDragging] =\n      this[onContextMenuWhileDragging].bind(this);\n    this[onMouseDown] = this[onMouseDown].bind(this);\n    this[onMouseMove] = this[onMouseMove].bind(this);\n    this[onMouseUp] = this[onMouseUp].bind(this);\n    this[startDrag] = this[startDrag].bind(this);\n    this[onDistanceChange] = this[onDistanceChange].bind(this);\n  }\n\n  /**\n   * Attaches sensors event listeners to the DOM\n   */\n  attach() {\n    document.addEventListener('mousedown', this[onMouseDown], true);\n  }\n\n  /**\n   * Detaches sensors event listeners to the DOM\n   */\n  detach() {\n    document.removeEventListener('mousedown', this[onMouseDown], true);\n  }\n\n  /**\n   * Mouse down handler\n   * @private\n   * @param {Event} event - Mouse down event\n   */\n  [onMouseDown](event) {\n    if (event.button !== 0 || event.ctrlKey || event.metaKey) {\n      return;\n    }\n    const container = closest(event.target, this.containers);\n\n    if (!container) {\n      return;\n    }\n\n    if (\n      this.options.handle &&\n      event.target &&\n      !closest(event.target, this.options.handle)\n    ) {\n      return;\n    }\n\n    const originalSource = closest(event.target, this.options.draggable);\n\n    if (!originalSource) {\n      return;\n    }\n\n    const {delay} = this;\n    const {pageX, pageY} = event;\n\n    Object.assign(this, {pageX, pageY});\n    this.onMouseDownAt = Date.now();\n    this.startEvent = event;\n\n    this.currentContainer = container;\n    this.originalSource = originalSource;\n    document.addEventListener('mouseup', this[onMouseUp]);\n    document.addEventListener('dragstart', preventNativeDragStart);\n    document.addEventListener('mousemove', this[onDistanceChange]);\n\n    this.mouseDownTimeout = window.setTimeout(() => {\n      this[onDistanceChange]({pageX: this.pageX, pageY: this.pageY});\n    }, delay.mouse);\n  }\n\n  /**\n   * Start the drag\n   * @private\n   */\n  [startDrag]() {\n    const startEvent = this.startEvent;\n    const container = this.currentContainer;\n    const originalSource = this.originalSource;\n\n    const dragStartEvent = new DragStartSensorEvent({\n      clientX: startEvent.clientX,\n      clientY: startEvent.clientY,\n      target: startEvent.target,\n      container,\n      originalSource,\n      originalEvent: startEvent,\n    });\n\n    this.trigger(this.currentContainer, dragStartEvent);\n\n    this.dragging = !dragStartEvent.canceled();\n\n    if (this.dragging) {\n      document.addEventListener(\n        'contextmenu',\n        this[onContextMenuWhileDragging],\n        true,\n      );\n      document.addEventListener('mousemove', this[onMouseMove]);\n    }\n  }\n\n  /**\n   * Detect change in distance, starting drag when both\n   * delay and distance requirements are met\n   * @private\n   * @param {Event} event - Mouse move event\n   */\n  [onDistanceChange](event) {\n    const {pageX, pageY} = event;\n    const {distance} = this.options;\n    const {startEvent, delay} = this;\n\n    Object.assign(this, {pageX, pageY});\n\n    if (!this.currentContainer) {\n      return;\n    }\n\n    const timeElapsed = Date.now() - this.onMouseDownAt;\n    const distanceTravelled =\n      euclideanDistance(startEvent.pageX, startEvent.pageY, pageX, pageY) || 0;\n\n    clearTimeout(this.mouseDownTimeout);\n\n    if (timeElapsed < delay.mouse) {\n      // moved during delay\n      document.removeEventListener('mousemove', this[onDistanceChange]);\n    } else if (distanceTravelled >= distance) {\n      document.removeEventListener('mousemove', this[onDistanceChange]);\n      this[startDrag]();\n    }\n  }\n\n  /**\n   * Mouse move handler\n   * @private\n   * @param {Event} event - Mouse move event\n   */\n  [onMouseMove](event) {\n    if (!this.dragging) {\n      return;\n    }\n\n    const target = document.elementFromPoint(event.clientX, event.clientY);\n\n    const dragMoveEvent = new DragMoveSensorEvent({\n      clientX: event.clientX,\n      clientY: event.clientY,\n      target,\n      container: this.currentContainer,\n      originalEvent: event,\n    });\n\n    this.trigger(this.currentContainer, dragMoveEvent);\n  }\n\n  /**\n   * Mouse up handler\n   * @private\n   * @param {Event} event - Mouse up event\n   */\n  [onMouseUp](event) {\n    clearTimeout(this.mouseDownTimeout);\n\n    if (event.button !== 0) {\n      return;\n    }\n\n    document.removeEventListener('mouseup', this[onMouseUp]);\n    document.removeEventListener('dragstart', preventNativeDragStart);\n    document.removeEventListener('mousemove', this[onDistanceChange]);\n\n    if (!this.dragging) {\n      return;\n    }\n\n    const target = document.elementFromPoint(event.clientX, event.clientY);\n\n    const dragStopEvent = new DragStopSensorEvent({\n      clientX: event.clientX,\n      clientY: event.clientY,\n      target,\n      container: this.currentContainer,\n      originalEvent: event,\n    });\n\n    this.trigger(this.currentContainer, dragStopEvent);\n\n    document.removeEventListener(\n      'contextmenu',\n      this[onContextMenuWhileDragging],\n      true,\n    );\n    document.removeEventListener('mousemove', this[onMouseMove]);\n\n    this.currentContainer = null;\n    this.dragging = false;\n    this.startEvent = null;\n  }\n\n  /**\n   * Context menu handler\n   * @private\n   * @param {Event} event - Context menu event\n   */\n  [onContextMenuWhileDragging](event) {\n    event.preventDefault();\n  }\n}\n\nfunction preventNativeDragStart(event) {\n  event.preventDefault();\n}\n"
  },
  {
    "path": "src/Draggable/Sensors/MouseSensor/README.md",
    "content": "## Mouse Sensor\n\n_Draggable uses this sensor by default_\n\nPicks up browser mouse events and triggers the events below on a source container.\n\n- `drag:start`\n- `drag:move`\n- `drag:stop`\n\n### API\n\n**`new MouseSensor(containers: HTMLElement[]|NodeList|HTMLElement, options: Object): MouseSensor`**  \nTo create a mouse sensor, specify the containers it should pay attention to. Sensors will always\ntrigger sensor events on container element.\n\n**`mouseSensor.attach(): void`**  \nAttaches sensors to the DOM\n\n**`mouseSensor.detach(): void`**  \nDetaches sensors to the DOM\n\n### Options\n\n**`draggable {String}`**  \nA css selector for draggable elements within the `containers` specified.\n\n**`delay {Number}`**  \nThis value will delay touch start.\n\n**`distance {Number}`**  \nThe distance you want the pointer to have moved before drag starts.\n\n**`handle {String}`**  \nSpecify a css selector for a handle element if you don't want to allow drag action on the entire element.\n"
  },
  {
    "path": "src/Draggable/Sensors/MouseSensor/index.js",
    "content": "import MouseSensor from './MouseSensor';\n\nexport default MouseSensor;\n"
  },
  {
    "path": "src/Draggable/Sensors/MouseSensor/tests/MouseSensor.test.js",
    "content": "import {\n  createSandbox,\n  triggerEvent,\n  waitForDragDelay,\n  DRAG_DELAY,\n  clickMouse,\n  moveMouse,\n  releaseMouse,\n} from 'helper';\n\nimport MouseSensor from '..';\n\nconst sampleMarkup = `\n  <ul>\n    <li class=\"draggable\">\n      <div class=\"handle\">First handle</div>\n      First item\n    </li>\n    <li class=\"draggable\">\n      <div class=\"handle\">Second handle</div>\n      Second item\n    </li>\n    <li class=\"non-draggable\">\n      <div class=\"handle\">Non draggable handle</div>\n      Non draggable item\n    </li>\n  </ul>\n`;\n\ndescribe('MouseSensor', () => {\n  let sandbox;\n  let mouseSensor;\n  let draggableElement;\n  let nonDraggableElement;\n\n  function setup(optionsParam = {}) {\n    const options = {\n      draggable: '.draggable',\n      delay: 0,\n      distance: 0,\n      ...optionsParam,\n    };\n\n    sandbox = createSandbox(sampleMarkup);\n    const containers = sandbox.querySelectorAll('ul');\n    draggableElement = sandbox.querySelector('.draggable');\n    nonDraggableElement = sandbox.querySelector('.non-draggable');\n    mouseSensor = new MouseSensor(containers, options);\n    mouseSensor.attach();\n  }\n\n  function teardown() {\n    mouseSensor.detach();\n    sandbox.remove();\n  }\n\n  describe('common', () => {\n    beforeEach(() => {\n      setup();\n    });\n\n    afterEach(teardown);\n\n    it('does not trigger `drag:start` event when clicking on non draggable element', () => {\n      function dragFlow() {\n        clickMouse(document.body);\n        waitForDragDelay();\n        clickMouse(nonDraggableElement);\n        waitForDragDelay();\n      }\n\n      expect(dragFlow).not.toHaveTriggeredSensorEvent('drag:start');\n    });\n\n    it('prevents context menu while dragging', () => {\n      let contextMenuEvent = triggerEvent(draggableElement, 'contextmenu');\n\n      expect(contextMenuEvent).not.toHaveDefaultPrevented();\n\n      clickMouse(draggableElement);\n      waitForDragDelay();\n      contextMenuEvent = triggerEvent(draggableElement, 'contextmenu');\n\n      expect(contextMenuEvent).toHaveDefaultPrevented();\n\n      releaseMouse(draggableElement);\n    });\n\n    it('prevents native drag when initiating drag flow', () => {\n      let dragEvent = triggerEvent(draggableElement, 'dragstart');\n\n      expect(dragEvent).not.toHaveDefaultPrevented();\n\n      clickMouse(draggableElement);\n      dragEvent = triggerEvent(draggableElement, 'dragstart');\n\n      expect(dragEvent).toHaveDefaultPrevented();\n\n      releaseMouse(document.body);\n    });\n\n    it('does not prevent `dragstart` event when attempting to drag outside of draggable container', () => {\n      clickMouse(document.body);\n      moveMouse(document, {pageX: 1, pageY: 1});\n      const nativeDragEvent = triggerEvent(draggableElement, 'dragstart');\n\n      expect(nativeDragEvent).not.toHaveDefaultPrevented();\n\n      releaseMouse(document.body);\n    });\n\n    it('does not prevent `dragstart` event when attempting to drag non draggable element', () => {\n      clickMouse(nonDraggableElement);\n      moveMouse(document, {pageX: 1, pageY: 1});\n      const nativeDragEvent = triggerEvent(nonDraggableElement, 'dragstart');\n\n      expect(nativeDragEvent).not.toHaveDefaultPrevented();\n\n      releaseMouse(document.body);\n    });\n\n    it('triggers `drag:stop` event when releasing mouse while dragging', () => {\n      function dragFlow() {\n        clickMouse(draggableElement);\n        waitForDragDelay();\n        releaseMouse(document.body);\n      }\n\n      expect(dragFlow).toHaveTriggeredSensorEvent('drag:stop');\n    });\n\n    it('does not trigger `drag:start` event when right clicking or holding ctrl or meta key', () => {\n      function dragFlowWithRightClick() {\n        clickMouse(draggableElement, {button: 2});\n        waitForDragDelay();\n        releaseMouse(document.body);\n      }\n\n      function dragFlowWithCtrlKey() {\n        clickMouse(draggableElement, {ctrlKey: true});\n        waitForDragDelay();\n        releaseMouse(document.body);\n      }\n\n      function dragFlowWithMetaKey() {\n        clickMouse(draggableElement, {metaKey: true});\n        waitForDragDelay();\n        releaseMouse(document.body);\n      }\n\n      [\n        dragFlowWithRightClick,\n        dragFlowWithCtrlKey,\n        dragFlowWithMetaKey,\n      ].forEach((dragFlow) => {\n        expect(dragFlow).not.toHaveTriggeredSensorEvent('drag:start');\n      });\n    });\n\n    it('cancels `drag:start` event when canceling sensor event', () => {\n      sandbox.addEventListener('drag:start', (event) => {\n        event.detail.cancel();\n      });\n\n      function dragFlow() {\n        clickMouse(draggableElement);\n        waitForDragDelay();\n        releaseMouse(draggableElement);\n      }\n\n      expect(dragFlow).toHaveCanceledSensorEvent('drag:start');\n    });\n  });\n\n  describe('using handle', () => {\n    let handleInDraggableElement;\n    let handleInNonDraggableElement;\n\n    beforeEach(() => {\n      setup({handle: '.handle'});\n      handleInDraggableElement = sandbox.querySelector('.draggable .handle');\n      handleInNonDraggableElement = sandbox.querySelector(\n        '.non-draggable .handle',\n      );\n    });\n\n    afterEach(teardown);\n\n    it('does not prevent `dragstart` event when attempting to drag handle in non draggable element', () => {\n      clickMouse(handleInNonDraggableElement);\n      moveMouse(document, {pageX: 1, pageY: 1});\n      const nativeDragEvent = triggerEvent(\n        handleInNonDraggableElement,\n        'dragstart',\n      );\n\n      expect(nativeDragEvent).not.toHaveDefaultPrevented();\n\n      releaseMouse(document.body);\n    });\n\n    it('prevent `dragstart` event when attempting to drag handle in draggable element', () => {\n      clickMouse(handleInDraggableElement);\n      moveMouse(document, {pageX: 1, pageY: 1});\n      const nativeDragEvent = triggerEvent(\n        handleInDraggableElement,\n        'dragstart',\n      );\n\n      expect(nativeDragEvent).toHaveDefaultPrevented();\n\n      releaseMouse(document.body);\n    });\n\n    it('does not prevent `dragstart` event when attempting to drag outside of handle inside of draggable', () => {\n      clickMouse(draggableElement);\n      moveMouse(document, {pageX: 1, pageY: 1});\n      const nativeDragEvent = triggerEvent(draggableElement, 'dragstart');\n\n      expect(nativeDragEvent).not.toHaveDefaultPrevented();\n\n      releaseMouse(document.body);\n    });\n  });\n\n  describe('using distance', () => {\n    beforeEach(() => {\n      setup({distance: 1});\n    });\n\n    afterEach(teardown);\n\n    it('triggers `drag:start` sensor event on mousemove after distance has been met', () => {\n      function dragFlow() {\n        clickMouse(draggableElement);\n        moveMouse(draggableElement, {pageY: 1, pageX: 0});\n        releaseMouse(document.body);\n      }\n\n      expect(dragFlow).toHaveTriggeredSensorEvent('drag:start');\n    });\n\n    it('does not trigger `drag:start` event releasing mouse before distance has been met', () => {\n      function dragFlow() {\n        clickMouse(draggableElement);\n        moveMouse(draggableElement, {pageY: 1, pageX: 0});\n        releaseMouse(document.body);\n      }\n\n      function hastyDragFlow() {\n        clickMouse(draggableElement);\n        releaseMouse(document.body);\n      }\n\n      expect(hastyDragFlow).not.toHaveTriggeredSensorEvent('drag:start');\n\n      expect(hastyDragFlow).not.toHaveTriggeredSensorEvent('drag:stop');\n\n      expect(dragFlow).toHaveTriggeredSensorEvent('drag:start');\n\n      expect(dragFlow).toHaveTriggeredSensorEvent('drag:stop');\n    });\n\n    it('triggers `drag:move` event while moving the mouse after distance has been met', () => {\n      function dragFlow() {\n        clickMouse(draggableElement);\n        moveMouse(draggableElement, {pageY: 1, pageX: 0});\n        moveMouse(document.body);\n        releaseMouse(document.body);\n      }\n\n      expect(dragFlow).toHaveTriggeredSensorEvent('drag:move');\n    });\n  });\n\n  describe('using delay', () => {\n    beforeEach(() => {\n      setup({delay: DRAG_DELAY});\n    });\n\n    afterEach(teardown);\n\n    it('triggers `drag:start` sensor event after delay', () => {\n      function dragFlow() {\n        clickMouse(draggableElement);\n        waitForDragDelay();\n        releaseMouse(document.body);\n      }\n\n      expect(dragFlow).toHaveTriggeredSensorEvent('drag:start');\n    });\n\n    it('does not trigger `drag:start` event releasing mouse before delay', () => {\n      function dragFlow() {\n        clickMouse(draggableElement);\n        waitForDragDelay();\n        releaseMouse(document.body);\n      }\n\n      function hastyDragFlow() {\n        clickMouse(draggableElement);\n        releaseMouse(document.body);\n      }\n\n      expect(hastyDragFlow).not.toHaveTriggeredSensorEvent('drag:start');\n\n      expect(hastyDragFlow).not.toHaveTriggeredSensorEvent('drag:stop');\n\n      expect(dragFlow).toHaveTriggeredSensorEvent('drag:start');\n\n      expect(dragFlow).toHaveTriggeredSensorEvent('drag:stop');\n    });\n\n    it('triggers `drag:move` event while moving the mouse after delay', () => {\n      function dragFlow() {\n        clickMouse(draggableElement);\n        waitForDragDelay();\n        moveMouse(document.body);\n        releaseMouse(document.body);\n      }\n\n      expect(dragFlow).toHaveTriggeredSensorEvent('drag:move');\n    });\n  });\n\n  describe('delay and distance', () => {\n    beforeEach(() => {\n      setup({delay: DRAG_DELAY, distance: 1});\n    });\n\n    afterEach(teardown);\n\n    it('does not trigger `drag:start` before delay ends', () => {\n      function dragFlow() {\n        clickMouse(draggableElement);\n        moveMouse(draggableElement, {pageY: 1, pageX: 0});\n        releaseMouse(document.body);\n      }\n      expect(dragFlow).not.toHaveTriggeredSensorEvent('drag:start');\n    });\n\n    it('does not trigger `drag:start` before distance is met', () => {\n      function dragFlow() {\n        clickMouse(draggableElement);\n        waitForDragDelay();\n        releaseMouse(document.body);\n      }\n      expect(dragFlow).not.toHaveTriggeredSensorEvent('drag:start');\n    });\n\n    it('does not trigger `drag:start` sensor event when moved during delay', () => {\n      function dragFlow() {\n        clickMouse(draggableElement);\n        moveMouse(draggableElement, {pageY: 1, pageX: 0});\n        const dateMock = waitForDragDelay({restoreDateMock: false});\n        moveMouse(draggableElement, {pageY: 2, pageX: 0});\n        waitForDragDelay();\n        releaseMouse(document.body);\n        dateMock.mockRestore();\n      }\n      expect(dragFlow).not.toHaveTriggeredSensorEvent('drag:start', 1);\n    });\n\n    it('only triggers `drag:start` sensor event once when distance and delay are met at the same time', () => {\n      function dragFlow() {\n        clickMouse(draggableElement);\n        const next = Date.now() + DRAG_DELAY;\n        const dateMock = jest.spyOn(Date, 'now').mockImplementation(() => {\n          return next;\n        });\n        moveMouse(draggableElement, {pageY: 1, pageX: 0});\n        jest.advanceTimersByTime(DRAG_DELAY);\n        releaseMouse(document.body);\n        dateMock.mockRestore();\n      }\n      expect(dragFlow).toHaveTriggeredSensorEvent('drag:start', 1);\n    });\n\n    it('only triggers `drag:start` sensor event once when distance is met after delay', () => {\n      function dragFlow() {\n        clickMouse(draggableElement);\n        const next = Date.now() + DRAG_DELAY + 1;\n        const dateMock = jest.spyOn(Date, 'now').mockImplementation(() => {\n          return next;\n        });\n        jest.advanceTimersByTime(DRAG_DELAY + 1);\n        moveMouse(draggableElement, {pageY: 1, pageX: 0});\n        releaseMouse(document.body);\n        dateMock.mockRestore();\n      }\n      expect(dragFlow).toHaveTriggeredSensorEvent('drag:start', 1);\n    });\n  });\n});\n"
  },
  {
    "path": "src/Draggable/Sensors/README.md",
    "content": "## Sensors\n\nSensors pick up native browser events and translates them to `drag:start`, `drag:move` and `drag:stop`\nevents. Sensors abstract the browser API away for Draggable, so it's easier to focus on DnD operations.\n\n### Sensors\n\n- [DragSensor](DragSensor)\n- [ForceTouchSensor](ForceTouchSensor)\n- [MouseSensor](MouseSensor)\n- [TouchSensor](TouchSensor)\n"
  },
  {
    "path": "src/Draggable/Sensors/Sensor/README.md",
    "content": "## Sensor\n\nBase sensor which includes a minimal API. Inherit from this class to create your own\ncustom sensor.\n\nCurrently triggers these sensor events:\n\n- `drag:start`\n- `drag:move`\n- `drag:stop`\n- `drag:pressure`\n\n### API\n\n**`new Sensor(containers: HTMLElement[]|NodeList|HTMLElement, options: Object): Sensor`**  \nTo create a sensor, specify the containers it should pay attention to. Sensors will always\ntrigger sensor events on container element.\n\n**`sensor.attach(): void`**  \nAttaches sensors to the DOM\n\n**`sensor.detach(): void`**  \nDetaches sensors to the DOM\n\n**`sensor.trigger(element: HTMLElement, sensorEvent: SensorEvent): void`**  \nTriggers sensor event on container element\n\n### Options\n\n**`draggable {String}`**  \nA css selector for draggable elements within the `containers` specified.\n\n**`delay {Number}`**  \nThis value will delay drag start.\n\n**`distance {Number}`**  \nThe distance you want the pointer to have moved before drag starts.\n\n**`handle {String}`**  \nSpecify a css selector for a handle element if you don't want to allow drag action on the entire element.\n"
  },
  {
    "path": "src/Draggable/Sensors/Sensor/Sensor.js",
    "content": "const defaultDelay = {\n  mouse: 0,\n  drag: 0,\n  touch: 100,\n};\n\n/**\n * Base sensor class. Extend from this class to create a new or custom sensor\n * @class Sensor\n * @module Sensor\n */\nexport default class Sensor {\n  /**\n   * Sensor constructor.\n   * @constructs Sensor\n   * @param {HTMLElement[]|NodeList|HTMLElement} containers - Containers\n   * @param {Object} options - Options\n   */\n  constructor(containers = [], options = {}) {\n    /**\n     * Current containers\n     * @property containers\n     * @type {HTMLElement[]}\n     */\n    this.containers = [...containers];\n\n    /**\n     * Current options\n     * @property options\n     * @type {Object}\n     */\n    this.options = {...options};\n\n    /**\n     * Current drag state\n     * @property dragging\n     * @type {Boolean}\n     */\n    this.dragging = false;\n\n    /**\n     * Current container\n     * @property currentContainer\n     * @type {HTMLElement}\n     */\n    this.currentContainer = null;\n\n    /**\n     * Draggables original source element\n     * @property originalSource\n     * @type {HTMLElement}\n     */\n    this.originalSource = null;\n\n    /**\n     * The event of the initial sensor down\n     * @property startEvent\n     * @type {Event}\n     */\n    this.startEvent = null;\n\n    /**\n     * The delay of each sensor\n     * @property delay\n     * @type {Object}\n     */\n    this.delay = calcDelay(options.delay);\n  }\n\n  /**\n   * Attaches sensors event listeners to the DOM\n   * @return {Sensor}\n   */\n  attach() {\n    return this;\n  }\n\n  /**\n   * Detaches sensors event listeners to the DOM\n   * @return {Sensor}\n   */\n  detach() {\n    return this;\n  }\n\n  /**\n   * Adds container to this sensor instance\n   * @param {...HTMLElement} containers - Containers you want to add to this sensor\n   * @example draggable.addContainer(document.body)\n   */\n  addContainer(...containers) {\n    this.containers = [...this.containers, ...containers];\n  }\n\n  /**\n   * Removes container from this sensor instance\n   * @param {...HTMLElement} containers - Containers you want to remove from this sensor\n   * @example draggable.removeContainer(document.body)\n   */\n  removeContainer(...containers) {\n    this.containers = this.containers.filter(\n      (container) => !containers.includes(container),\n    );\n  }\n\n  /**\n   * Triggers event on target element\n   * @param {HTMLElement} element - Element to trigger event on\n   * @param {SensorEvent} sensorEvent - Sensor event to trigger\n   */\n  trigger(element, sensorEvent) {\n    const event = document.createEvent('Event');\n    event.detail = sensorEvent;\n    event.initEvent(sensorEvent.type, true, true);\n    element.dispatchEvent(event);\n    this.lastEvent = sensorEvent;\n\n    return sensorEvent;\n  }\n}\n\n/**\n * Calculate the delay of each sensor through the delay in the options\n * @param {undefined|Number|Object} optionsDelay - the delay in the options\n * @return {Object}\n */\nfunction calcDelay(optionsDelay) {\n  const delay = {};\n\n  if (optionsDelay === undefined) {\n    return {...defaultDelay};\n  }\n\n  if (typeof optionsDelay === 'number') {\n    for (const key in defaultDelay) {\n      if (Object.prototype.hasOwnProperty.call(defaultDelay, key)) {\n        delay[key] = optionsDelay;\n      }\n    }\n    return delay;\n  }\n\n  for (const key in defaultDelay) {\n    if (Object.prototype.hasOwnProperty.call(defaultDelay, key)) {\n      if (optionsDelay[key] === undefined) {\n        delay[key] = defaultDelay[key];\n      } else {\n        delay[key] = optionsDelay[key];\n      }\n    }\n  }\n\n  return delay;\n}\n"
  },
  {
    "path": "src/Draggable/Sensors/Sensor/index.js",
    "content": "import Sensor from './Sensor';\n\nexport default Sensor;\n"
  },
  {
    "path": "src/Draggable/Sensors/Sensor/tests/Sensor.test.js",
    "content": "import Sensor from '../Sensor';\n\ndescribe('Sensor', () => {\n  describe('#constructor', () => {\n    it('should initialize with default containers and options', () => {\n      const sensor = new Sensor();\n\n      expect(sensor.containers).toMatchObject([]);\n      expect(sensor.options).toMatchObject({});\n    });\n\n    it('should initialize with containers and options', () => {\n      const expectedContainers = ['expectedContainer'];\n      const expectedOptions = {expectedOptions: true};\n      const sensor = new Sensor(expectedContainers, expectedOptions);\n\n      expect(sensor.containers).toStrictEqual(expectedContainers);\n      expect(sensor.options).toStrictEqual(expectedOptions);\n    });\n\n    describe('should initialize with correct delay', () => {\n      it('unset', () => {\n        const sensor = new Sensor(undefined, {});\n\n        expect(sensor.delay).toStrictEqual({\n          mouse: 0,\n          drag: 0,\n          touch: 100,\n        });\n      });\n\n      it('number', () => {\n        const sensor = new Sensor(undefined, {delay: 42});\n\n        expect(sensor.delay).toStrictEqual({\n          mouse: 42,\n          drag: 42,\n          touch: 42,\n        });\n      });\n\n      it('object', () => {\n        const sensor = new Sensor(undefined, {delay: {mouse: 42, drag: 142}});\n\n        expect(sensor.delay).toStrictEqual({\n          mouse: 42,\n          drag: 142,\n          touch: 100,\n        });\n      });\n    });\n  });\n\n  describe('#attach', () => {\n    it('should return self', () => {\n      const sensor = new Sensor();\n      const returnValue = sensor.attach();\n\n      expect(returnValue).toBe(sensor);\n    });\n  });\n\n  describe('#detach', () => {\n    it('should return self', () => {\n      const sensor = new Sensor();\n      const returnValue = sensor.attach();\n\n      expect(returnValue).toBe(sensor);\n    });\n  });\n\n  describe('#addContainer', () => {\n    it('adds container to sensor', () => {\n      const containers = [document.documentElement, document.body];\n      const sensor = new Sensor();\n\n      expect(sensor.containers).toStrictEqual([]);\n\n      sensor.addContainer(...containers);\n\n      expect(sensor.containers).toStrictEqual(containers);\n    });\n  });\n\n  describe('#removeContainer', () => {\n    it('removes container to sensor', () => {\n      const containers = [document.documentElement, document.body];\n      const sensor = new Sensor(containers);\n\n      expect(sensor.containers).toStrictEqual(containers);\n\n      sensor.removeContainer(...containers);\n\n      expect(sensor.containers).toStrictEqual([]);\n    });\n  });\n\n  describe('#trigger', () => {\n    it('should dispatch event on element', () => {\n      const sensor = new Sensor();\n      const element = document.createElement('div');\n      const expectedEvent = {\n        type: 'my:event',\n        value: 'some value',\n      };\n\n      let eventDispatched;\n\n      element.addEventListener(\n        'my:event',\n        (event) => {\n          eventDispatched = event;\n        },\n        true,\n      );\n\n      const returnValue = sensor.trigger(element, expectedEvent);\n\n      expect(eventDispatched.detail).toBe(expectedEvent);\n      expect(eventDispatched.type).toBe('my:event');\n      expect(eventDispatched.target).toBe(element);\n      expect(returnValue).toBe(expectedEvent);\n      expect(sensor.lastEvent).toBe(expectedEvent);\n    });\n  });\n});\n"
  },
  {
    "path": "src/Draggable/Sensors/SensorEvent/README.md",
    "content": "## SensorEvent\n\nThe base sensor event for all Sensor events that sensors emits.\n\n|                   |               |\n| ----------------- | ------------- |\n| **Interface**     | `SensorEvent` |\n| **Cancelable**    | false         |\n| **Cancel action** | -             |\n| **type**          | `sensor`      |\n\n### API\n\n**`sensorEvent.originalEvent: Event`**  \nRead-only property for the original event that triggered the sensor event.\n\n**`sensorEvent.clientX: Number`**  \nRead-only property for current X coordinates.\n\n**`sensorEvent.clientY: Number`**  \nRead-only property for current Y coordinates.\n\n**`sensorEvent.target: HTMLElement`**  \nRead-only property for the normalized target for both touch and mouse events.\nReturns the element that is behind cursor or touch pointer.\n\n**`sensorEvent.container: Number`**  \nRead-only property for the container that fired the sensor event.\n\n**`sensorEvent.originalSource: String`**  \nRead-only property for the original source element that was picked up.\n\n**`sensorEvent.pressure: Number`**  \nRead-only property for the pressure applied.\n\n## DragStartSensorEvent\n\n`DragStartSensorEvent` gets triggered by sensors for drag start.\n\n|                   |                        |\n| ----------------- | ---------------------- |\n| **Specification** | `SensorEvent`          |\n| **Interface**     | `DragStartSensorEvent` |\n| **Cancelable**    | true                   |\n| **Cancel action** | Prevents drag start    |\n| **type**          | `drag:start`           |\n\n## DragMoveSensorEvent\n\n`DragMoveSensorEvent` gets triggered by sensors for drag move.\n\n|                   |                       |\n| ----------------- | --------------------- |\n| **Specification** | `SensorEvent`         |\n| **Interface**     | `DragMoveSensorEvent` |\n| **Cancelable**    | false                 |\n| **Cancel action** | -                     |\n| **type**          | `drag:move`           |\n\n## DragStopSensorEvent\n\n`DragStopSensorEvent` gets triggered by sensors for drag stop.\n\n|                   |                       |\n| ----------------- | --------------------- |\n| **Specification** | `SensorEvent`         |\n| **Interface**     | `DragStopSensorEvent` |\n| **Cancelable**    | false                 |\n| **Cancel action** | -                     |\n| **type**          | `drag:stop`           |\n\n## DragPressureSensorEvent\n\n`DragPressureSensorEvent` gets triggered by sensors for drag pressure.\n\n|                   |                           |\n| ----------------- | ------------------------- |\n| **Specification** | `SensorEvent`             |\n| **Interface**     | `DragPressureSensorEvent` |\n| **Cancelable**    | false                     |\n| **Cancel action** | -                         |\n| **type**          | `drag:pressure`           |\n"
  },
  {
    "path": "src/Draggable/Sensors/SensorEvent/SensorEvent.ts",
    "content": "import AbstractEvent from 'shared/AbstractEvent';\n\ninterface SensorEventData {\n  clientX: number;\n  clientY: number;\n  target: HTMLElement;\n  container: HTMLElement;\n  originalSource: HTMLElement;\n  originalEvent?: Event;\n  pressure?: number;\n}\n\n/**\n * Base sensor event\n * @class SensorEvent\n * @module SensorEvent\n * @extends AbstractEvent\n */\nexport class SensorEvent extends AbstractEvent<SensorEventData> {\n  /**\n   * Original browser event that triggered a sensor\n   * @property originalEvent\n   * @type {Event}\n   * @readonly\n   */\n  get originalEvent() {\n    return this.data.originalEvent;\n  }\n\n  /**\n   * Normalized clientX for both touch and mouse events\n   * @property clientX\n   * @type {Number}\n   * @readonly\n   */\n  get clientX() {\n    return this.data.clientX;\n  }\n\n  /**\n   * Normalized clientY for both touch and mouse events\n   * @property clientY\n   * @type {Number}\n   * @readonly\n   */\n  get clientY() {\n    return this.data.clientY;\n  }\n\n  /**\n   * Normalized target for both touch and mouse events\n   * Returns the element that is behind cursor or touch pointer\n   * @property target\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get target() {\n    return this.data.target;\n  }\n\n  /**\n   * Container that initiated the sensor\n   * @property container\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get container() {\n    return this.data.container;\n  }\n\n  /**\n   * Draggables original source element\n   * @property originalSource\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get originalSource() {\n    return this.data.originalSource;\n  }\n\n  /**\n   * Trackpad pressure\n   * @property pressure\n   * @type {Number}\n   * @readonly\n   */\n  get pressure() {\n    return this.data.pressure;\n  }\n}\n\n/**\n * Drag start sensor event\n * @class DragStartSensorEvent\n * @module DragStartSensorEvent\n * @extends SensorEvent\n */\nexport class DragStartSensorEvent extends SensorEvent {\n  static type = 'drag:start';\n}\n\n/**\n * Drag move sensor event\n * @class DragMoveSensorEvent\n * @module DragMoveSensorEvent\n * @extends SensorEvent\n */\nexport class DragMoveSensorEvent extends SensorEvent {\n  static type = 'drag:move';\n}\n\n/**\n * Drag stop sensor event\n * @class DragStopSensorEvent\n * @module DragStopSensorEvent\n * @extends SensorEvent\n */\nexport class DragStopSensorEvent extends SensorEvent {\n  static type = 'drag:stop';\n}\n\n/**\n * Drag pressure sensor event\n * @class DragPressureSensorEvent\n * @module DragPressureSensorEvent\n * @extends SensorEvent\n */\nexport class DragPressureSensorEvent extends SensorEvent {\n  static type = 'drag:pressure';\n}\n"
  },
  {
    "path": "src/Draggable/Sensors/SensorEvent/index.ts",
    "content": "export * from './SensorEvent';\n"
  },
  {
    "path": "src/Draggable/Sensors/TouchSensor/README.md",
    "content": "## Touch Sensor\n\n_Draggable uses this sensor by default_\n\nPicks up browser touch events and triggers the events below on a source container.\n\n- `drag:start`\n- `drag:move`\n- `drag:stop`\n\n### API\n\n**`new TouchSensor(containers: HTMLElement[]|NodeList|HTMLElement, options: Object): TouchSensor`**  \nTo create a touch sensor, specify the containers it should pay attention to. Sensors will always\ntrigger sensor events on container element.\n\n**`touchSensor.attach(): void`**  \nAttaches sensors to the DOM\n\n**`touchSensor.detach(): void`**  \nDetaches sensors to the DOM\n\n### Options\n\n**`draggable {String}`**  \nA css selector for draggable elements within the `containers` specified.\n\n**`delay {Number}`**  \nThis value will delay touch start.\n\n**`distance {Number}`**  \nThe distance you want the pointer to have moved before drag starts.\n\n**`handle {String}`**  \nSpecify a css selector for a handle element if you don't want to allow drag action on the entire element.\n"
  },
  {
    "path": "src/Draggable/Sensors/TouchSensor/TouchSensor.js",
    "content": "import {\n  closest,\n  distance as euclideanDistance,\n  touchCoords,\n} from 'shared/utils';\n\nimport Sensor from '../Sensor';\nimport {\n  DragStartSensorEvent,\n  DragMoveSensorEvent,\n  DragStopSensorEvent,\n} from '../SensorEvent';\n\nconst onTouchStart = Symbol('onTouchStart');\nconst onTouchEnd = Symbol('onTouchEnd');\nconst onTouchMove = Symbol('onTouchMove');\nconst startDrag = Symbol('startDrag');\nconst onDistanceChange = Symbol('onDistanceChange');\n\n/**\n * Prevents scrolling when set to true\n * @var {Boolean} preventScrolling\n */\nlet preventScrolling = false;\n\n// WebKit requires cancelable `touchmove` events to be added as early as possible\nwindow.addEventListener(\n  'touchmove',\n  (event) => {\n    if (!preventScrolling) {\n      return;\n    }\n\n    // Prevent scrolling\n    event.preventDefault();\n  },\n  {passive: false},\n);\n\n/**\n * This sensor picks up native browser touch events and dictates drag operations\n * @class TouchSensor\n * @module TouchSensor\n * @extends Sensor\n */\nexport default class TouchSensor extends Sensor {\n  /**\n   * TouchSensor constructor.\n   * @constructs TouchSensor\n   * @param {HTMLElement[]|NodeList|HTMLElement} containers - Containers\n   * @param {Object} options - Options\n   */\n  constructor(containers = [], options = {}) {\n    super(containers, options);\n\n    /**\n     * Closest scrollable container so accidental scroll can cancel long touch\n     * @property currentScrollableParent\n     * @type {HTMLElement}\n     */\n    this.currentScrollableParent = null;\n\n    /**\n     * TimeoutID for managing delay\n     * @property tapTimeout\n     * @type {Number}\n     */\n    this.tapTimeout = null;\n\n    /**\n     * touchMoved indicates if touch has moved during tapTimeout\n     * @property touchMoved\n     * @type {Boolean}\n     */\n    this.touchMoved = false;\n\n    /**\n     * Save pageX coordinates for delay drag\n     * @property {Numbre} pageX\n     * @private\n     */\n    this.pageX = null;\n\n    /**\n     * Save pageY coordinates for delay drag\n     * @property {Numbre} pageY\n     * @private\n     */\n    this.pageY = null;\n\n    this[onTouchStart] = this[onTouchStart].bind(this);\n    this[onTouchEnd] = this[onTouchEnd].bind(this);\n    this[onTouchMove] = this[onTouchMove].bind(this);\n    this[startDrag] = this[startDrag].bind(this);\n    this[onDistanceChange] = this[onDistanceChange].bind(this);\n  }\n\n  /**\n   * Attaches sensors event listeners to the DOM\n   */\n  attach() {\n    document.addEventListener('touchstart', this[onTouchStart]);\n  }\n\n  /**\n   * Detaches sensors event listeners to the DOM\n   */\n  detach() {\n    document.removeEventListener('touchstart', this[onTouchStart]);\n  }\n\n  /**\n   * Touch start handler\n   * @private\n   * @param {Event} event - Touch start event\n   */\n  [onTouchStart](event) {\n    const container = closest(event.target, this.containers);\n\n    if (!container) {\n      return;\n    }\n\n    if (\n      this.options.handle &&\n      event.target &&\n      !closest(event.target, this.options.handle)\n    ) {\n      return;\n    }\n\n    const originalSource = closest(event.target, this.options.draggable);\n\n    if (!originalSource) {\n      return;\n    }\n\n    const {distance = 0} = this.options;\n    const {delay} = this;\n    const {pageX, pageY} = touchCoords(event);\n\n    Object.assign(this, {pageX, pageY});\n    this.onTouchStartAt = Date.now();\n    this.startEvent = event;\n    this.currentContainer = container;\n    this.originalSource = originalSource;\n\n    document.addEventListener('touchend', this[onTouchEnd]);\n    document.addEventListener('touchcancel', this[onTouchEnd]);\n    document.addEventListener('touchmove', this[onDistanceChange]);\n    container.addEventListener('contextmenu', onContextMenu);\n\n    if (distance) {\n      preventScrolling = true;\n    }\n\n    this.tapTimeout = window.setTimeout(() => {\n      this[onDistanceChange]({\n        touches: [{pageX: this.pageX, pageY: this.pageY}],\n      });\n    }, delay.touch);\n  }\n\n  /**\n   * Start the drag\n   * @private\n   */\n  [startDrag]() {\n    const startEvent = this.startEvent;\n    const container = this.currentContainer;\n    const touch = touchCoords(startEvent);\n    const originalSource = this.originalSource;\n\n    const dragStartEvent = new DragStartSensorEvent({\n      clientX: touch.pageX,\n      clientY: touch.pageY,\n      target: startEvent.target,\n      container,\n      originalSource,\n      originalEvent: startEvent,\n    });\n\n    this.trigger(this.currentContainer, dragStartEvent);\n\n    this.dragging = !dragStartEvent.canceled();\n\n    if (this.dragging) {\n      document.addEventListener('touchmove', this[onTouchMove]);\n    }\n    preventScrolling = this.dragging;\n  }\n\n  /**\n   * Touch move handler prior to drag start.\n   * @private\n   * @param {Event} event - Touch move event\n   */\n  [onDistanceChange](event) {\n    const {distance} = this.options;\n    const {startEvent, delay} = this;\n    const start = touchCoords(startEvent);\n    const current = touchCoords(event);\n    const timeElapsed = Date.now() - this.onTouchStartAt;\n    const distanceTravelled = euclideanDistance(\n      start.pageX,\n      start.pageY,\n      current.pageX,\n      current.pageY,\n    );\n\n    Object.assign(this, current);\n\n    clearTimeout(this.tapTimeout);\n\n    if (timeElapsed < delay.touch) {\n      // moved during delay\n      document.removeEventListener('touchmove', this[onDistanceChange]);\n    } else if (distanceTravelled >= distance) {\n      document.removeEventListener('touchmove', this[onDistanceChange]);\n      this[startDrag]();\n    }\n  }\n\n  /**\n   * Mouse move handler while dragging\n   * @private\n   * @param {Event} event - Touch move event\n   */\n  [onTouchMove](event) {\n    if (!this.dragging) {\n      return;\n    }\n    const {pageX, pageY} = touchCoords(event);\n    const target = document.elementFromPoint(\n      pageX - window.scrollX,\n      pageY - window.scrollY,\n    );\n\n    const dragMoveEvent = new DragMoveSensorEvent({\n      clientX: pageX,\n      clientY: pageY,\n      target,\n      container: this.currentContainer,\n      originalEvent: event,\n    });\n\n    this.trigger(this.currentContainer, dragMoveEvent);\n  }\n\n  /**\n   * Touch end handler\n   * @private\n   * @param {Event} event - Touch end event\n   */\n  [onTouchEnd](event) {\n    clearTimeout(this.tapTimeout);\n    preventScrolling = false;\n\n    document.removeEventListener('touchend', this[onTouchEnd]);\n    document.removeEventListener('touchcancel', this[onTouchEnd]);\n    document.removeEventListener('touchmove', this[onDistanceChange]);\n\n    if (this.currentContainer) {\n      this.currentContainer.removeEventListener('contextmenu', onContextMenu);\n    }\n\n    if (!this.dragging) {\n      return;\n    }\n\n    document.removeEventListener('touchmove', this[onTouchMove]);\n\n    const {pageX, pageY} = touchCoords(event);\n    const target = document.elementFromPoint(\n      pageX - window.scrollX,\n      pageY - window.scrollY,\n    );\n\n    event.preventDefault();\n\n    const dragStopEvent = new DragStopSensorEvent({\n      clientX: pageX,\n      clientY: pageY,\n      target,\n      container: this.currentContainer,\n      originalEvent: event,\n    });\n\n    this.trigger(this.currentContainer, dragStopEvent);\n\n    this.currentContainer = null;\n    this.dragging = false;\n    this.startEvent = null;\n  }\n}\n\nfunction onContextMenu(event) {\n  event.preventDefault();\n  event.stopPropagation();\n}\n"
  },
  {
    "path": "src/Draggable/Sensors/TouchSensor/index.js",
    "content": "import TouchSensor from './TouchSensor';\n\nexport default TouchSensor;\n"
  },
  {
    "path": "src/Draggable/Sensors/TouchSensor/tests/TouchSensor.test.js",
    "content": "import {\n  createSandbox,\n  triggerEvent,\n  waitForDragDelay,\n  DRAG_DELAY,\n  touchStart,\n  touchMove,\n  touchRelease,\n} from 'helper';\n\nimport TouchSensor from '..';\n\nconst sampleMarkup = `\n  <ul>\n    <li class=\"draggable\">First item</li>\n    <li class=\"draggable\">Second item</li>\n    <li class=\"non-draggable\">Non draggable item</li>\n  </ul>\n`;\n\ndescribe('TouchSensor', () => {\n  let sandbox;\n  let touchSensor;\n  let draggableElement;\n  let nonDraggableElement;\n\n  function setup(optionsParam = {}) {\n    const options = {\n      draggable: '.draggable',\n      delay: 0,\n      distance: 0,\n      ...optionsParam,\n    };\n\n    sandbox = createSandbox(sampleMarkup);\n    const containers = sandbox.querySelectorAll('ul');\n    draggableElement = sandbox.querySelector('.draggable');\n    nonDraggableElement = sandbox.querySelector('.non-draggable');\n    touchSensor = new TouchSensor(containers, options);\n    touchSensor.attach();\n  }\n\n  function teardown() {\n    touchSensor.detach();\n    sandbox.remove();\n  }\n\n  describe('common', () => {\n    beforeEach(() => {\n      setup();\n    });\n\n    afterEach(teardown);\n\n    it('cancels `drag:start` event when canceling sensor event', () => {\n      sandbox.addEventListener('drag:start', (event) => {\n        event.detail.cancel();\n      });\n\n      function dragFlow() {\n        touchStart(draggableElement);\n        waitForDragDelay();\n        touchRelease(draggableElement);\n      }\n\n      expect(dragFlow).toHaveCanceledSensorEvent('drag:start');\n    });\n\n    it('prevents `drag:start` when trying to drag a none draggable element', () => {\n      function dragFlow() {\n        touchStart(document.body);\n        waitForDragDelay();\n        touchStart(nonDraggableElement);\n        waitForDragDelay();\n      }\n\n      expect(dragFlow).not.toHaveTriggeredSensorEvent('drag:start');\n    });\n\n    it('prevents context menu while dragging', () => {\n      touchStart(draggableElement);\n      let contextMenuEvent = triggerEvent(draggableElement, 'contextmenu');\n      waitForDragDelay();\n\n      expect(contextMenuEvent.defaultPrevented).toBe(true);\n\n      expect(contextMenuEvent.stoppedPropagation).toBe(true);\n\n      touchRelease(draggableElement);\n      contextMenuEvent = triggerEvent(draggableElement, 'contextmenu');\n\n      expect(contextMenuEvent.defaultPrevented).toBe(false);\n\n      expect(contextMenuEvent.stoppedPropagation).toBeUndefined();\n    });\n\n    it('prevents scroll on touchmove while dragging', () => {\n      let touchMoveEvent = touchMove(draggableElement);\n\n      expect(touchMoveEvent.defaultPrevented).toBe(false);\n\n      touchStart(draggableElement);\n      waitForDragDelay();\n      touchMoveEvent = touchMove(draggableElement);\n\n      expect(touchMoveEvent.defaultPrevented).toBe(true);\n\n      touchRelease(draggableElement);\n    });\n\n    it('prevents clicking on touchend after dragging', () => {\n      let touchEndEvent = touchRelease(draggableElement);\n\n      expect(touchEndEvent.defaultPrevented).toBe(false);\n\n      touchStart(draggableElement);\n      waitForDragDelay();\n      touchEndEvent = touchRelease(draggableElement);\n\n      expect(touchEndEvent.defaultPrevented).toBe(true);\n    });\n  });\n\n  describe('using distance', () => {\n    beforeEach(() => {\n      setup({distance: 1});\n    });\n\n    afterEach(teardown);\n\n    it('does not trigger `drag:start` before distance has been travelled', () => {\n      function dragFlow() {\n        touchStart(draggableElement);\n        touchRelease(draggableElement);\n      }\n\n      expect(dragFlow).not.toHaveTriggeredSensorEvent('drag:start');\n    });\n\n    it('triggers `drag:start` sensor event after distance requirement has been met', () => {\n      function dragFlow() {\n        touchStart(draggableElement);\n        touchMove(draggableElement, {touches: [{pageX: 1, pageY: 0}]});\n      }\n\n      expect(dragFlow).toHaveTriggeredSensorEvent('drag:start');\n    });\n\n    it('triggers `drag:move` event while moving the finger after delay', () => {\n      function dragFlow() {\n        touchStart(draggableElement);\n        touchMove(draggableElement, {touches: [{pageX: 1, pageY: 0}]});\n        touchMove(draggableElement);\n        touchRelease(draggableElement);\n      }\n\n      expect(dragFlow).toHaveTriggeredSensorEvent('drag:move');\n    });\n\n    it('triggers `drag:stop` event when releasing the finger after dragging has started', () => {\n      function dragFlow() {\n        touchStart(draggableElement);\n        touchMove(draggableElement, {touches: [{pageX: 1, pageY: 0}]});\n        touchRelease(draggableElement);\n      }\n\n      expect(dragFlow).toHaveTriggeredSensorEvent('drag:stop');\n    });\n\n    it('does not triggers `drag:stop` event when releasing the finger before dragging has started', () => {\n      function dragFlow() {\n        touchStart(draggableElement);\n        touchRelease(draggableElement);\n      }\n\n      expect(dragFlow).not.toHaveTriggeredSensorEvent('drag:stop');\n    });\n  });\n\n  describe('using delay', () => {\n    beforeEach(() => {\n      setup({delay: DRAG_DELAY});\n    });\n\n    afterEach(teardown);\n\n    it('does not trigger `drag:start` before delay ends', () => {\n      function dragFlow() {\n        touchStart(draggableElement);\n        touchRelease(draggableElement);\n      }\n\n      expect(dragFlow).not.toHaveTriggeredSensorEvent('drag:start');\n    });\n\n    it('triggers `drag:start` sensor event on touchstart after delay', () => {\n      function dragFlow() {\n        touchStart(draggableElement);\n        waitForDragDelay();\n      }\n\n      expect(dragFlow).toHaveTriggeredSensorEvent('drag:start');\n    });\n\n    it('triggers `drag:move` event while moving the finger after delay', () => {\n      function dragFlow() {\n        touchStart(draggableElement);\n        waitForDragDelay();\n        touchMove(draggableElement);\n        touchRelease(draggableElement);\n      }\n\n      expect(dragFlow).toHaveTriggeredSensorEvent('drag:move');\n    });\n\n    it('triggers `drag:stop` event when releasing the finger after dragging has started', () => {\n      function dragFlow() {\n        touchStart(draggableElement);\n        waitForDragDelay();\n        touchMove(draggableElement);\n        touchRelease(draggableElement);\n      }\n\n      expect(dragFlow).toHaveTriggeredSensorEvent('drag:stop');\n    });\n\n    it('does not triggers `drag:stop` event when releasing the finger before dragging has started', () => {\n      function dragFlow() {\n        touchStart(draggableElement);\n        touchMove(draggableElement);\n        touchRelease(draggableElement);\n      }\n\n      expect(dragFlow).not.toHaveTriggeredSensorEvent('drag:stop');\n    });\n  });\n\n  describe('delay and distance', () => {\n    beforeEach(() => {\n      setup({delay: DRAG_DELAY, distance: 1});\n    });\n\n    afterEach(teardown);\n\n    it('does not trigger `drag:start` before delay ends', () => {\n      function dragFlow() {\n        touchStart(draggableElement);\n        touchMove(draggableElement, {touches: [{pageX: 1, pageY: 0}]});\n        touchRelease(draggableElement);\n      }\n\n      expect(dragFlow).not.toHaveTriggeredSensorEvent('drag:start');\n    });\n\n    it('does not trigger `drag:start` before distance requirement is met', () => {\n      function dragFlow() {\n        touchStart(draggableElement);\n        waitForDragDelay();\n        touchRelease(draggableElement);\n      }\n\n      expect(dragFlow).not.toHaveTriggeredSensorEvent('drag:start');\n    });\n\n    it('does not trigger `drag:start` sensor event when moved during delay', () => {\n      function dragFlow() {\n        touchStart(draggableElement);\n        touchMove(draggableElement, {touches: [{pageX: 1, pageY: 0}]});\n        const dateMock = waitForDragDelay({restoreDateMock: false});\n        touchMove(draggableElement, {touches: [{pageX: 2, pageY: 0}]});\n        touchRelease(draggableElement);\n        dateMock.mockRestore();\n      }\n\n      expect(dragFlow).not.toHaveTriggeredSensorEvent('drag:start');\n    });\n\n    it('only triggers `drag:start` sensor event once when delay ends at the same time distance is met', () => {\n      function dragFlow() {\n        touchStart(draggableElement);\n        const next = Date.now() + DRAG_DELAY;\n        const dateMock = jest.spyOn(Date, 'now').mockImplementation(() => {\n          return next;\n        });\n        touchMove(draggableElement, {touches: [{pageX: 1, pageY: 0}]});\n        jest.advanceTimersByTime(DRAG_DELAY);\n        touchRelease(draggableElement);\n        dateMock.mockRestore();\n      }\n\n      expect(dragFlow).toHaveTriggeredSensorEvent('drag:start', 1);\n    });\n\n    it('only triggers `drag:start` sensor event once when distance is met after delay', () => {\n      function dragFlow() {\n        touchStart(draggableElement);\n        // do not use waitForDragDelay as it will reset the mock before touchMove\n        const next = Date.now() + DRAG_DELAY + 1;\n        const dateMock = jest.spyOn(Date, 'now').mockImplementation(() => {\n          return next;\n        });\n        jest.advanceTimersByTime(DRAG_DELAY + 1);\n        touchMove(draggableElement, {touches: [{pageX: 1, pageY: 1}]});\n        dateMock.mockRestore();\n        touchRelease(draggableElement);\n      }\n\n      expect(dragFlow).toHaveTriggeredSensorEvent('drag:start', 1);\n    });\n  });\n});\n"
  },
  {
    "path": "src/Draggable/Sensors/index.js",
    "content": "export {default as Sensor} from './Sensor';\nexport {default as MouseSensor} from './MouseSensor';\nexport {default as TouchSensor} from './TouchSensor';\nexport {default as DragSensor} from './DragSensor';\nexport {default as ForceTouchSensor} from './ForceTouchSensor';\n\nexport * from './SensorEvent';\n"
  },
  {
    "path": "src/Draggable/index.js",
    "content": "import Draggable from './Draggable';\n\nexport default Draggable;\n\nexport * from './DragEvent';\nexport * from './DraggableEvent';\nexport * from './Plugins';\nexport * from './Sensors';\n"
  },
  {
    "path": "src/Draggable/tests/Draggable.test.js",
    "content": "import {\n  createSandbox,\n  triggerEvent,\n  TestPlugin,\n  clickMouse,\n  moveMouse,\n  releaseMouse,\n  waitForDragDelay,\n  waitForRequestAnimationFrame,\n} from 'helper';\n\nimport Draggable, {defaultOptions} from '../Draggable';\nimport {\n  DragStartEvent,\n  DragMoveEvent,\n  DragStopEvent,\n  DragStoppedEvent,\n} from '../DragEvent';\nimport {\n  DraggableInitializedEvent,\n  DraggableDestroyEvent,\n} from '../DraggableEvent';\nimport {Focusable, Mirror, Scrollable, Announcement} from '../Plugins';\nimport {MouseSensor, TouchSensor} from '../Sensors';\n\nconst sampleMarkup = `\n  <ul class=\"Container\">\n    <li>First item</li>\n    <li>Second item</li>\n  </ul>\n  <ul class=\"DynamicContainer\">\n    <li>First item</li>\n    <li>Second item</li>\n  </ul>\n`;\n\ndescribe('Draggable', () => {\n  let sandbox;\n  let containers;\n  const expectedClientX = 39;\n  const expectedClientY = 82;\n\n  beforeEach(() => {\n    sandbox = createSandbox(sampleMarkup);\n    containers = sandbox.querySelectorAll('.Container');\n  });\n\n  afterEach(() => {\n    sandbox.remove();\n  });\n\n  describe('.Plugins', () => {\n    it('should be available statically', () => {\n      expect(Draggable.Plugins).toBeDefined();\n      expect(Draggable.Plugins.Mirror).toStrictEqual(Mirror);\n      expect(Draggable.Plugins.Focusable).toStrictEqual(Focusable);\n      expect(Draggable.Plugins.Scrollable).toStrictEqual(Scrollable);\n    });\n  });\n\n  describe('#constructor', () => {\n    it('should be an instance of Draggable', () => {\n      const draggable = new Draggable(containers, {\n        draggable: 'li',\n        delay: 0,\n      });\n\n      expect(draggable).toBeInstanceOf(Draggable);\n    });\n\n    it('should initialize with default options', () => {\n      const newInstance = new Draggable(containers);\n\n      Object.keys(defaultOptions).forEach((key) => {\n        expect(newInstance.options[key]).toStrictEqual(defaultOptions[key]);\n      });\n    });\n\n    /* eslint-disable jest/no-disabled-tests */\n    it.skip('should set containers', () => {\n      const newInstance = new Draggable(containers);\n\n      expect(newInstance.containers).toMatchObject(\n        Array.prototype.slice.call(containers),\n      );\n    });\n\n    it.skip('should set single container if a list is not passed', () => {\n      const newInstance = new Draggable(containers[0]);\n\n      expect(newInstance.containers).toMatchObject([containers[0]]);\n    });\n    /* eslint-enable jest/no-disabled-tests */\n\n    it('should throw error if `containers` argument is wrong type', () => {\n      expect(() => {\n        return new Draggable({});\n      }).toThrow(\n        'Draggable containers are expected to be of type `NodeList`, `HTMLElement[]` or `HTMLElement`',\n      );\n\n      expect(() => {\n        return new Draggable('.li');\n      }).toThrow(\n        'Draggable containers are expected to be of type `NodeList`, `HTMLElement[]` or `HTMLElement`',\n      );\n    });\n\n    it('should attach default plugins', () => {\n      const newInstance = new Draggable();\n\n      expect(newInstance.plugins).toHaveLength(4);\n\n      expect(newInstance.plugins[0]).toBeInstanceOf(Announcement);\n\n      expect(newInstance.plugins[1]).toBeInstanceOf(Focusable);\n\n      expect(newInstance.plugins[2]).toBeInstanceOf(Mirror);\n\n      expect(newInstance.plugins[3]).toBeInstanceOf(Scrollable);\n    });\n\n    it('should remove default plugins from the list of exclude plugins', () => {\n      const newInstance = new Draggable([], {\n        exclude: {\n          plugins: [Draggable.Plugins.Focusable],\n        },\n      });\n\n      expect(newInstance.plugins).toHaveLength(3);\n\n      newInstance.plugins.forEach((plugin) => {\n        expect(plugin).not.toBeInstanceOf(Focusable);\n      });\n    });\n\n    it('should attach custom plugins', () => {\n      const newInstance = new Draggable([], {\n        plugins: [TestPlugin],\n      });\n\n      expect(newInstance.plugins).toHaveLength(5);\n\n      const customPlugin = newInstance.plugins[4];\n\n      expect(customPlugin.draggable).toBe(newInstance);\n\n      expect(customPlugin.attach).toHaveBeenCalled();\n\n      expect(customPlugin.detach).not.toHaveBeenCalled();\n    });\n\n    it('should attach sensors', () => {\n      const newInstance = new Draggable([], {\n        native: false,\n      });\n\n      expect(newInstance.sensors).toHaveLength(2);\n\n      expect(newInstance.sensors[0]).toBeInstanceOf(MouseSensor);\n\n      expect(newInstance.sensors[1]).toBeInstanceOf(TouchSensor);\n    });\n\n    it('should remove default sensors from the list of exclude sensors', () => {\n      const newInstance = new Draggable([], {\n        exclude: {\n          sensors: [Draggable.Sensors.TouchSensor],\n        },\n      });\n\n      expect(newInstance.sensors).toHaveLength(1);\n\n      newInstance.sensors.forEach((sensor) => {\n        expect(sensor).not.toBeInstanceOf(TouchSensor);\n      });\n    });\n\n    it('should trigger DraggableInitializedEvent on init', () => {\n      const spy = jest.spyOn(Draggable.prototype, 'trigger');\n      const newInstance = new Draggable();\n\n      expect(spy.mock.calls).toHaveLength(1);\n\n      expect(spy.mock.calls[0][0]).toBeInstanceOf(DraggableInitializedEvent);\n\n      expect(spy.mock.calls[0][0].draggable).toBe(newInstance);\n\n      spy.mockReset();\n      spy.mockRestore();\n    });\n  });\n\n  describe('#destroy', () => {\n    it('triggers `draggable:destroy` event on destroy', () => {\n      const newInstance = new Draggable();\n      const callback = jest.fn();\n\n      newInstance.on('draggable:destroy', callback);\n\n      newInstance.destroy();\n\n      const call = callback.mock.calls[0][0];\n\n      expect(call.type).toBe('draggable:destroy');\n\n      expect(call).toBeInstanceOf(DraggableDestroyEvent);\n\n      expect(call.draggable).toBe(newInstance);\n    });\n\n    it('should call Plugin#detach once on each of provided plugins', () => {\n      const plugins = [TestPlugin, TestPlugin, TestPlugin];\n      const newInstance = new Draggable([], {\n        plugins,\n      });\n      const expectedPlugins = newInstance.plugins.slice();\n\n      newInstance.destroy();\n\n      expect(expectedPlugins[4].detach).toHaveBeenCalled();\n\n      expect(expectedPlugins[4].detach).toHaveBeenCalledTimes(1);\n\n      expect(expectedPlugins[5].detach).toHaveBeenCalled();\n\n      expect(expectedPlugins[5].detach).toHaveBeenCalledTimes(1);\n\n      expect(expectedPlugins[6].detach).toHaveBeenCalled();\n\n      expect(expectedPlugins[6].detach).toHaveBeenCalledTimes(1);\n    });\n\n    it('should remove all sensor event listeners', () => {\n      jest.spyOn(document, 'removeEventListener').mockImplementation();\n\n      const newInstance = new Draggable();\n\n      newInstance.destroy();\n\n      const mockCalls = document.removeEventListener.mock.calls;\n\n      expect(mockCalls[0][0]).toBe('drag:start');\n      expect(mockCalls[1][0]).toBe('drag:move');\n      expect(mockCalls[2][0]).toBe('drag:stop');\n      expect(mockCalls[3][0]).toBe('drag:pressure');\n\n      document.removeEventListener.mockRestore();\n    });\n  });\n\n  describe('#on', () => {\n    it('should add an event handler to the list of callbacks', () => {\n      const newInstance = new Draggable();\n      function stubHandler() {\n        /* do nothing */\n      }\n\n      expect('my:event' in newInstance.emitter.callbacks).toBe(false);\n\n      newInstance.on('my:event', stubHandler);\n\n      expect('my:event' in newInstance.emitter.callbacks).toBe(true);\n\n      expect(newInstance.emitter.callbacks['my:event']).toMatchObject([\n        stubHandler,\n      ]);\n    });\n\n    it('should return draggable instance', () => {\n      const newInstance = new Draggable();\n      function stubHandler() {\n        /* do nothing */\n      }\n\n      expect('my:event' in newInstance.emitter.callbacks).toBe(false);\n\n      const returnValue = newInstance.on('my:event', stubHandler);\n\n      expect(returnValue).toBe(newInstance);\n    });\n  });\n\n  describe('#off', () => {\n    it('should remove event handler from the list of callbacks', () => {\n      const newInstance = new Draggable();\n      function stubHandler() {\n        /* do nothing */\n      }\n\n      newInstance.on('my:event', stubHandler);\n\n      expect('my:event' in newInstance.emitter.callbacks).toBe(true);\n\n      newInstance.off('my:event', stubHandler);\n\n      expect('my:event' in newInstance.emitter.callbacks).toBe(true);\n\n      expect(newInstance.emitter.callbacks['my:event']).toMatchObject([]);\n    });\n\n    it('should return draggable instance', () => {\n      const newInstance = new Draggable();\n      function stubHandler() {\n        /* do nothing */\n      }\n\n      newInstance.on('my:event', stubHandler);\n\n      expect('my:event' in newInstance.emitter.callbacks).toBe(true);\n\n      const returnValue = newInstance.off('my:event', stubHandler);\n\n      expect(returnValue).toBe(newInstance);\n    });\n  });\n\n  describe('#trigger', () => {\n    it('should invoke bound event', () => {\n      const newInstance = new Draggable(containers);\n      const handler = jest.fn();\n      const expectedEvent = new Event('my:event');\n\n      newInstance.on('my:event', handler);\n\n      newInstance.trigger(expectedEvent);\n\n      expect(handler.mock.calls).toHaveLength(1);\n\n      expect(handler.mock.calls[0]).toHaveLength(1);\n\n      expect(handler.mock.calls[0][0]).toBe(expectedEvent);\n    });\n  });\n\n  describe('#getDraggableElementsForContainer', () => {\n    it('returns draggable elements, excluding mirror and original source', () => {\n      const newInstance = new Draggable(containers, {\n        draggable: 'li',\n      });\n      const draggableElement = sandbox.querySelector('li');\n\n      triggerEvent(draggableElement, 'mousedown', {button: 0});\n\n      // Wait for delay\n      waitForDragDelay();\n\n      const containerChildren = newInstance.getDraggableElementsForContainer(\n        draggableElement.parentNode,\n      );\n\n      expect(containerChildren).toHaveLength(2);\n\n      triggerEvent(draggableElement, 'mouseup', {button: 0});\n    });\n  });\n\n  describe('#addContainer', () => {\n    it('adds single container dynamically', () => {\n      const dragOverContainerHandler = jest.fn();\n      const newInstance = new Draggable(containers, {\n        draggable: 'li',\n      });\n\n      newInstance.on('drag:over:container', dragOverContainerHandler);\n\n      const draggableElement = document.querySelector('li');\n      const dynamicContainer = document.querySelector('.DynamicContainer');\n\n      clickMouse(draggableElement);\n      waitForDragDelay();\n      moveMouse(dynamicContainer);\n\n      // will be called once after delay\n      expect(dragOverContainerHandler).toHaveBeenCalledTimes(1);\n\n      releaseMouse(newInstance.source);\n\n      newInstance.addContainer(dynamicContainer);\n\n      expect(newInstance.containers).toStrictEqual([\n        ...containers,\n        dynamicContainer,\n      ]);\n\n      clickMouse(draggableElement);\n      waitForDragDelay();\n      moveMouse(dynamicContainer);\n      expect(dragOverContainerHandler).toHaveBeenCalledTimes(3);\n\n      releaseMouse(newInstance.source);\n    });\n  });\n\n  describe('#removeContainer', () => {\n    it('removes single container dynamically', () => {\n      let dragOverContainerHandler = jest.fn();\n      const allContainers = document.querySelectorAll(\n        '.Container, .DynamicContainer',\n      );\n      const newInstance = new Draggable(allContainers, {\n        draggable: 'li',\n      });\n\n      newInstance.on('drag:over:container', dragOverContainerHandler);\n\n      const draggableElement = document.querySelector('li');\n      const dynamicContainer = document.querySelector('.DynamicContainer');\n\n      clickMouse(draggableElement);\n      waitForDragDelay();\n      moveMouse(dynamicContainer);\n\n      expect(dragOverContainerHandler).toHaveBeenCalledTimes(2);\n\n      releaseMouse(newInstance.source);\n\n      newInstance.removeContainer(dynamicContainer);\n\n      expect(newInstance.containers).toStrictEqual([...containers]);\n\n      dragOverContainerHandler = jest.fn();\n      newInstance.on('drag:over:container', dragOverContainerHandler);\n\n      clickMouse(draggableElement);\n      waitForDragDelay();\n      moveMouse(dynamicContainer);\n\n      expect(dragOverContainerHandler).toHaveBeenCalledTimes(1);\n\n      releaseMouse(newInstance.source);\n    });\n  });\n\n  describe('#getDraggableElements', () => {\n    it('returns draggable elements', () => {\n      const draggable = new Draggable(containers, {\n        draggable: 'li',\n      });\n\n      expect(draggable.getDraggableElements()).toStrictEqual([\n        ...document.querySelectorAll('.Container li'),\n      ]);\n    });\n\n    it('returns draggable elements after adding a container', () => {\n      const dynamicContainer = document.querySelector('.DynamicContainer');\n      const draggable = new Draggable(containers, {\n        draggable: 'li',\n      });\n\n      expect(draggable.getDraggableElements()).toStrictEqual([\n        ...document.querySelectorAll('.Container li'),\n      ]);\n\n      draggable.addContainer(dynamicContainer);\n\n      expect(draggable.getDraggableElements()).toStrictEqual([\n        ...document.querySelectorAll('li'),\n      ]);\n\n      draggable.removeContainer(dynamicContainer);\n\n      expect(draggable.getDraggableElements()).toStrictEqual([\n        ...document.querySelectorAll('.Container li'),\n      ]);\n    });\n  });\n\n  it('triggers `drag:move` as part of the `drag:start` event', () => {\n    const dragStartHandler = jest.fn();\n    const dragMoveHandler = jest.fn();\n\n    let dragStartTarget;\n    let dragMoveTarget;\n\n    const draggable = new Draggable(containers, {\n      draggable: 'li',\n    });\n\n    draggable.on('drag:start', (dragStartEvent) => {\n      dragStartTarget = dragStartEvent.sensorEvent.target;\n      dragStartHandler(dragStartEvent);\n    });\n\n    draggable.on('drag:move', (dragMoveEvent) => {\n      dragMoveTarget = dragMoveEvent.sensorEvent.target;\n      dragMoveHandler(dragMoveEvent);\n    });\n\n    const draggableElement = document.querySelector('li');\n\n    clickMouse(draggableElement);\n    waitForDragDelay();\n    waitForRequestAnimationFrame();\n\n    expect(dragStartHandler).toHaveBeenCalledTimes(1);\n    expect(dragMoveHandler).toHaveBeenCalledTimes(1);\n\n    expect(draggableElement).not.toStrictEqual(draggable.source);\n    expect(dragStartTarget).toBe(draggableElement);\n    expect(dragMoveTarget).toBe(draggable.source);\n\n    releaseMouse(draggable.source);\n  });\n\n  it('triggers `drag:start` drag event on mousedown', () => {\n    const newInstance = new Draggable(containers, {\n      draggable: 'li',\n    });\n    const draggableElement = sandbox.querySelector('li');\n    document.elementFromPoint = () => draggableElement;\n\n    const callback = jest.fn();\n    newInstance.on('drag:start', callback);\n\n    triggerEvent(draggableElement, 'mousedown', {button: 0});\n    // Wait for delay\n    waitForDragDelay();\n\n    const call = callback.mock.calls[0][0];\n\n    expect(call.type).toBe('drag:start');\n\n    expect(call).toBeInstanceOf(DragStartEvent);\n\n    triggerEvent(draggableElement, 'mouseup', {button: 0});\n  });\n\n  it('should trigger `drag:start` drag event on dragstart', () => {\n    const newInstance = new Draggable(containers, {\n      draggable: 'li',\n    });\n    const draggableElement = sandbox.querySelector('li');\n    document.elementFromPoint = () => draggableElement;\n\n    const callback = jest.fn();\n    newInstance.on('drag:start', callback);\n\n    triggerEvent(draggableElement, 'mousedown', {button: 0});\n\n    // Wait for delay\n    waitForDragDelay();\n\n    triggerEvent(draggableElement, 'dragstart', {button: 0});\n\n    const call = callback.mock.calls[0][0];\n\n    expect(call.type).toBe('drag:start');\n\n    expect(call).toBeInstanceOf(DragStartEvent);\n\n    triggerEvent(draggableElement, 'mouseup', {button: 0});\n  });\n\n  it('cleans up when `drag:start` event is canceled', () => {\n    const newInstance = new Draggable(containers, {\n      draggable: 'li',\n    });\n    const draggableElement = sandbox.querySelector('li');\n    document.elementFromPoint = () => draggableElement;\n\n    let source;\n    let originalSource;\n    const callback = jest.fn((event) => {\n      source = event.source;\n      originalSource = event.originalSource;\n      event.cancel();\n    });\n\n    newInstance.on('drag:start', callback);\n\n    triggerEvent(draggableElement, 'mousedown', {button: 0});\n\n    // Wait for delay\n    waitForDragDelay();\n\n    triggerEvent(draggableElement, 'dragstart', {button: 0});\n\n    expect(newInstance.dragging).toBe(false);\n    expect(source.parentNode).toBeNull();\n    expect(originalSource.style.display).toBe('');\n  });\n\n  it('triggers `drag:move` drag event on mousedown', () => {\n    const newInstance = new Draggable(containers, {\n      draggable: 'li',\n    });\n    const draggableElement = sandbox.querySelector('li');\n    document.elementFromPoint = () => draggableElement;\n\n    triggerEvent(draggableElement, 'mousedown', {button: 0});\n\n    // Wait for delay\n    waitForDragDelay();\n\n    const callback = jest.fn();\n    newInstance.on('drag:move', callback);\n\n    triggerEvent(document.body, 'mousemove', {\n      clientX: expectedClientX,\n      clientY: expectedClientY,\n    });\n\n    const call = callback.mock.calls[0][0];\n    const sensorEvent = call.data.sensorEvent;\n\n    expect(call.type).toBe('drag:move');\n\n    expect(call).toBeInstanceOf(DragMoveEvent);\n\n    expect(sensorEvent.clientX).toBe(expectedClientX);\n\n    expect(sensorEvent.clientY).toBe(expectedClientY);\n\n    triggerEvent(draggableElement, 'mouseup', {button: 0});\n  });\n\n  it('triggers `drag:stop` drag event on mouseup', () => {\n    const newInstance = new Draggable(containers, {\n      draggable: 'li',\n    });\n    const draggableElement = sandbox.querySelector('li');\n    document.elementFromPoint = () => draggableElement;\n\n    triggerEvent(draggableElement, 'mousedown', {button: 0});\n\n    // Wait for delay\n    waitForDragDelay();\n\n    const callback = jest.fn();\n    newInstance.on('drag:stop', callback);\n\n    triggerEvent(draggableElement, 'mouseup', {button: 0});\n\n    const call = callback.mock.calls[0][0];\n\n    expect(call.type).toBe('drag:stop');\n\n    expect(call).toBeInstanceOf(DragStopEvent);\n  });\n\n  it('triggers `drag:stop` drag event on cancel', () => {\n    const newInstance = new Draggable(containers, {\n      draggable: 'li',\n    });\n\n    const draggableElement = sandbox.querySelector('li');\n    document.elementFromPoint = () => draggableElement;\n\n    triggerEvent(draggableElement, 'mousedown', {button: 0});\n\n    // Wait for delay\n    waitForDragDelay();\n\n    const callback = jest.fn();\n    newInstance.on('drag:stop', callback);\n\n    newInstance.cancel();\n\n    const call = callback.mock.calls[0][0];\n\n    expect(call.type).toBe('drag:stop');\n\n    expect(call).toBeInstanceOf(DragStopEvent);\n  });\n\n  it('triggers `drag:stopped` drag event on mouseup', () => {\n    const newInstance = new Draggable(containers, {\n      draggable: 'li',\n    });\n    const draggableElement = sandbox.querySelector('li');\n    document.elementFromPoint = () => draggableElement;\n\n    triggerEvent(draggableElement, 'mousedown', {button: 0});\n\n    // Wait for delay\n    waitForDragDelay();\n\n    const callback = jest.fn();\n    newInstance.on('drag:stopped', callback);\n\n    triggerEvent(draggableElement, 'mouseup', {button: 0});\n\n    const call = callback.mock.calls[0][0];\n\n    expect(call.type).toBe('drag:stopped');\n\n    expect(call).toBeInstanceOf(DragStoppedEvent);\n  });\n\n  it('triggers `drag:stopped` drag event on cancel', () => {\n    const newInstance = new Draggable(containers, {\n      draggable: 'li',\n    });\n    const draggableElement = sandbox.querySelector('li');\n    document.elementFromPoint = () => draggableElement;\n\n    triggerEvent(draggableElement, 'mousedown', {button: 0});\n\n    // Wait for delay\n    waitForDragDelay();\n\n    const callback = jest.fn();\n    newInstance.on('drag:stopped', callback);\n\n    newInstance.cancel();\n\n    const call = callback.mock.calls[0][0];\n\n    expect(call.type).toBe('drag:stopped');\n\n    expect(call).toBeInstanceOf(DragStoppedEvent);\n  });\n\n  it('adds `source:dragging` classname to draggable element on mousedown', () => {\n    const newInstance = new Draggable(containers, {\n      draggable: 'li',\n    });\n    const draggableElement = sandbox.querySelector('li');\n    document.elementFromPoint = () => draggableElement;\n\n    triggerEvent(draggableElement, 'mousedown', {button: 0});\n\n    // Wait for delay\n    waitForDragDelay();\n\n    expect(newInstance.source.classList).toContain(\n      'draggable-source--is-dragging',\n    );\n\n    triggerEvent(draggableElement, 'mouseup', {button: 0});\n  });\n\n  it('removes `source:dragging` classname from draggable element on mouseup', () => {\n    const newInstance = new Draggable(containers, {\n      draggable: 'li',\n    });\n    const draggableElement = sandbox.querySelector('li');\n    document.elementFromPoint = () => draggableElement;\n\n    triggerEvent(draggableElement, 'mousedown', {button: 0});\n\n    // Wait for delay\n    waitForDragDelay();\n\n    const source = newInstance.source;\n\n    expect(source.classList).toContain('draggable-source--is-dragging');\n\n    triggerEvent(draggableElement, 'mouseup', {button: 0});\n\n    expect(source.classList).not.toContain('draggable-source--is-dragging');\n  });\n\n  it('removes `source:dragging` classname from draggable element on dragEvent.cancel()', () => {\n    const newInstance = new Draggable(containers, {\n      draggable: 'li',\n    });\n    const draggableElement = sandbox.querySelector('li');\n    document.elementFromPoint = () => draggableElement;\n\n    newInstance.on('drag:start', (event) => {\n      event.cancel();\n    });\n\n    triggerEvent(draggableElement, 'mousedown', {button: 0});\n\n    // Wait for delay\n    waitForDragDelay();\n\n    const source = newInstance.source;\n\n    expect(source.classList).not.toContain('draggable-source--is-dragging');\n\n    triggerEvent(draggableElement, 'mouseup', {button: 0});\n  });\n\n  it('adds `body:dragging` classname to body on mousedown', () => {\n    (() =>\n      new Draggable(containers, {\n        draggable: 'li',\n      }))();\n    const draggableElement = sandbox.querySelector('li');\n    document.elementFromPoint = () => draggableElement;\n\n    triggerEvent(draggableElement, 'mousedown', {button: 0});\n\n    // Wait for delay\n    waitForDragDelay();\n\n    expect(document.body.classList).toContain('draggable--is-dragging');\n\n    triggerEvent(draggableElement, 'mouseup', {button: 0});\n  });\n\n  it('removes `body:dragging` classname from body on mouseup', () => {\n    (() =>\n      new Draggable(containers, {\n        draggable: 'li',\n      }))();\n    const draggableElement = sandbox.querySelector('li');\n    document.elementFromPoint = () => draggableElement;\n\n    triggerEvent(draggableElement, 'mousedown', {button: 0});\n\n    // Wait for delay\n    waitForDragDelay();\n    expect(document.body.classList).toContain('draggable--is-dragging');\n\n    triggerEvent(document.body, 'mouseup', {button: 0});\n\n    expect(document.body.classList).not.toContain('draggable--is-dragging');\n  });\n\n  it('removes `body:dragging` classname from body on dragEvent.cancel()', () => {\n    const newInstance = new Draggable(containers, {\n      draggable: 'li',\n    });\n    const draggableElement = sandbox.querySelector('li');\n    document.elementFromPoint = () => draggableElement;\n\n    newInstance.on('drag:start', (event) => {\n      event.cancel();\n    });\n\n    triggerEvent(draggableElement, 'mousedown', {button: 0});\n\n    // Wait for delay\n    waitForDragDelay();\n\n    expect(document.body.classList).not.toContain('draggable--is-dragging');\n\n    triggerEvent(draggableElement, 'mouseup', {button: 0});\n  });\n\n  it('adds `container:placed` classname to draggable container element on mouseup', () => {\n    (() =>\n      new Draggable(containers, {\n        draggable: 'li',\n      }))();\n    const draggableElement = sandbox.querySelector('li');\n    document.elementFromPoint = () => draggableElement;\n\n    triggerEvent(draggableElement, 'mousedown', {button: 0});\n\n    // Wait for delay\n    waitForDragDelay();\n\n    triggerEvent(draggableElement, 'mouseup', {button: 0});\n\n    expect(containers[0].classList).toContain('draggable-container--placed');\n  });\n\n  it('removes `container:placed` classname from draggable container element on mouseup after delay', () => {\n    (() =>\n      new Draggable(containers, {\n        draggable: 'li',\n      }))();\n    const draggableElement = sandbox.querySelector('li');\n    document.elementFromPoint = () => draggableElement;\n\n    triggerEvent(draggableElement, 'mousedown', {button: 0});\n\n    // Wait for delay\n    waitForDragDelay();\n\n    triggerEvent(document.body, 'mouseup', {button: 0});\n\n    expect(containers[0].classList).toContain('draggable-container--placed');\n\n    // Wait for default draggable.options.placedTimeout delay\n    jest.advanceTimersByTime(800);\n\n    expect(containers[0].classList).not.toContain(\n      'draggable-container--placed',\n    );\n  });\n\n  it('adds `container:dragging` classname to draggable container element on mousedown', () => {\n    (() =>\n      new Draggable(containers, {\n        draggable: 'li',\n      }))();\n    const draggableElement = sandbox.querySelector('li');\n    document.elementFromPoint = () => draggableElement;\n\n    triggerEvent(draggableElement, 'mousedown', {button: 0});\n\n    // Wait for delay\n    waitForDragDelay();\n\n    expect(containers[0].classList).toContain(\n      'draggable-container--is-dragging',\n    );\n\n    triggerEvent(draggableElement, 'mouseup', {button: 0});\n  });\n\n  it('removes `container:dragging` classname from draggable container element on mouseup', () => {\n    (() =>\n      new Draggable(containers, {\n        draggable: 'li',\n      }))();\n    const draggableElement = sandbox.querySelector('li');\n    document.elementFromPoint = () => draggableElement;\n\n    triggerEvent(draggableElement, 'mousedown', {button: 0});\n\n    // Wait for delay\n    waitForDragDelay();\n\n    expect(containers[0].classList).toContain(\n      'draggable-container--is-dragging',\n    );\n\n    triggerEvent(document.body, 'mouseup', {button: 0});\n\n    expect(containers[0].classList).not.toContain(\n      'draggable-container--is-dragging',\n    );\n  });\n\n  it('removes `container:dragging` classname from draggable container element on dragEvent.cancel()', () => {\n    const newInstance = new Draggable(containers, {\n      draggable: 'li',\n    });\n    const draggableElement = sandbox.querySelector('li');\n\n    newInstance.on('drag:start', (event) => {\n      event.cancel();\n    });\n\n    triggerEvent(draggableElement, 'mousedown', {button: 0});\n\n    // Wait for delay\n    waitForDragDelay();\n\n    expect(containers[0].classList).not.toContain(\n      'draggable-container--is-dragging',\n    );\n\n    triggerEvent(draggableElement, 'mouseup', {button: 0});\n  });\n\n  it('adds and removes `source:original` on start and stop', () => {\n    const newInstance = new Draggable(containers, {\n      draggable: 'li',\n    });\n    const draggableElement = sandbox.querySelector('li');\n    document.elementFromPoint = () => draggableElement;\n\n    triggerEvent(draggableElement, 'mousedown', {button: 0});\n\n    // Wait for delay\n    waitForDragDelay();\n\n    expect(\n      draggableElement.classList.contains(\n        newInstance.getClassNameFor('source:original'),\n      ),\n    ).toBe(true);\n\n    triggerEvent(document.body, 'mouseup', {button: 0});\n\n    expect(\n      draggableElement.classList.contains(\n        newInstance.getClassNameFor('source:original'),\n      ),\n    ).toBe(false);\n  });\n\n  it('should have multiple classes for `source:original` on start', () => {\n    const sourceOriginalClasses = ['draggable--original', 'class1', 'class2'];\n    const newInstance = new Draggable(containers, {\n      draggable: 'li',\n      classes: {\n        'source:original': sourceOriginalClasses,\n      },\n    });\n    const draggableElement = sandbox.querySelector('li');\n    document.elementFromPoint = () => draggableElement;\n\n    triggerEvent(draggableElement, 'mousedown', {button: 0});\n\n    // Wait for delay\n    waitForDragDelay();\n\n    expect(newInstance.getClassNamesFor('source:original')).toStrictEqual(\n      sourceOriginalClasses,\n    );\n\n    triggerEvent(document.body, 'mouseup', {button: 0});\n  });\n\n  it('should removes all draggable classes for `source:original` on stop', () => {\n    const sourceOriginalClasses = ['draggable--original', 'class1', 'class2'];\n    const newInstance = new Draggable(containers, {\n      draggable: 'li',\n      classes: {\n        'source:original': sourceOriginalClasses,\n      },\n    });\n    const draggableElement = sandbox.querySelector('li');\n    document.elementFromPoint = () => draggableElement;\n\n    triggerEvent(draggableElement, 'mousedown', {button: 0});\n\n    // Wait for delay\n    waitForDragDelay();\n\n    triggerEvent(document.body, 'mouseup', {button: 0});\n\n    newInstance.getClassNamesFor('source:original').forEach((className) => {\n      expect(draggableElement.classList).not.toContain(className);\n    });\n  });\n\n  it('`drag:out:container` event specifies leaving container', () => {\n    const newInstance = new Draggable(containers, {\n      draggable: 'li',\n    });\n\n    newInstance.on('drag:over:container', (dragEvent) => {\n      expect(dragEvent.overContainer).toStrictEqual(containers[0]);\n    });\n\n    newInstance.on('drag:out:container', (dragEvent) => {\n      expect(dragEvent.overContainer).toStrictEqual(containers[0]);\n    });\n\n    const draggableElement = sandbox.querySelector('li');\n    document.elementFromPoint = () => draggableElement;\n\n    triggerEvent(draggableElement, 'mousedown', {button: 0});\n\n    // Wait for delay\n    waitForDragDelay();\n\n    expect(newInstance.isDragging()).toBe(true);\n\n    document.elementFromPoint = () => draggableElement.nextElementSibling;\n    triggerEvent(draggableElement.nextElementSibling, 'mousemove', {button: 0});\n\n    // Wait for delay\n    waitForDragDelay();\n\n    document.elementFromPoint = () => document.body;\n    triggerEvent(document.body, 'mousemove', {button: 0});\n\n    // Wait for delay\n    waitForDragDelay();\n\n    triggerEvent(document.body, 'mouseup', {button: 0});\n  });\n\n  describe('when `drag:stopped`', () => {\n    it('source element was removed from document', () => {\n      const newInstance = new Draggable(containers, {\n        draggable: 'li',\n      });\n      const draggableElement = sandbox.querySelector('li');\n      document.elementFromPoint = () => draggableElement;\n\n      newInstance.on('drag:stopped', (event) => {\n        expect(event.source.parentNode).toBeNull();\n      });\n\n      clickMouse(draggableElement);\n\n      // Wait for delay\n      waitForDragDelay();\n\n      releaseMouse(newInstance.source);\n    });\n  });\n\n  describe('when `drag:out`', () => {\n    it('should trigger dragOutEvent', () => {\n      const newInstance = new Draggable(containers, {\n        draggable: 'li',\n      });\n      const draggableElement = sandbox.querySelector('li');\n      document.elementFromPoint = () => draggableElement;\n\n      newInstance.on('drag:out', (event) => {\n        expect(event.overContainer).toBe(containers[0]);\n      });\n\n      clickMouse(draggableElement);\n\n      waitForDragDelay();\n\n      document.elementFromPoint = () => draggableElement.nextElementSibling;\n      moveMouse(draggableElement.nextElementSibling);\n\n      waitForDragDelay();\n\n      releaseMouse(newInstance.source);\n    });\n  });\n});\n"
  },
  {
    "path": "src/Droppable/Droppable.js",
    "content": "import {closest} from 'shared/utils';\n\nimport Draggable from '../Draggable';\n\nimport {\n  DroppableStartEvent,\n  DroppableDroppedEvent,\n  DroppableReturnedEvent,\n  DroppableStopEvent,\n} from './DroppableEvent';\n\nconst onDragStart = Symbol('onDragStart');\nconst onDragMove = Symbol('onDragMove');\nconst onDragStop = Symbol('onDragStop');\nconst dropInDropzone = Symbol('dropInDropZone');\nconst returnToOriginalDropzone = Symbol('returnToOriginalDropzone');\nconst closestDropzone = Symbol('closestDropzone');\nconst getDropzones = Symbol('getDropzones');\n\n/**\n * Returns an announcement message when the Draggable element is dropped into a dropzone element\n * @param {DroppableDroppedEvent} droppableEvent\n * @return {String}\n */\nfunction onDroppableDroppedDefaultAnnouncement({dragEvent, dropzone}) {\n  const sourceText =\n    dragEvent.source.textContent.trim() ||\n    dragEvent.source.id ||\n    'draggable element';\n  const dropzoneText =\n    dropzone.textContent.trim() || dropzone.id || 'droppable element';\n\n  return `Dropped ${sourceText} into ${dropzoneText}`;\n}\n\n/**\n * Returns an announcement message when the Draggable element has returned to its original dropzone element\n * @param {DroppableReturnedEvent} droppableEvent\n * @return {String}\n */\nfunction onDroppableReturnedDefaultAnnouncement({dragEvent, dropzone}) {\n  const sourceText =\n    dragEvent.source.textContent.trim() ||\n    dragEvent.source.id ||\n    'draggable element';\n  const dropzoneText =\n    dropzone.textContent.trim() || dropzone.id || 'droppable element';\n\n  return `Returned ${sourceText} from ${dropzoneText}`;\n}\n\n/**\n * @const {Object} defaultAnnouncements\n * @const {Function} defaultAnnouncements['droppable:dropped']\n * @const {Function} defaultAnnouncements['droppable:returned']\n */\nconst defaultAnnouncements = {\n  'droppable:dropped': onDroppableDroppedDefaultAnnouncement,\n  'droppable:returned': onDroppableReturnedDefaultAnnouncement,\n};\n\nconst defaultClasses = {\n  'droppable:active': 'draggable-dropzone--active',\n  'droppable:occupied': 'draggable-dropzone--occupied',\n};\n\nconst defaultOptions = {\n  dropzone: '.draggable-droppable',\n};\n\n/**\n * Droppable is built on top of Draggable and allows dropping draggable elements\n * into dropzone element\n * @class Droppable\n * @module Droppable\n * @extends Draggable\n */\nexport default class Droppable extends Draggable {\n  /**\n   * Droppable constructor.\n   * @constructs Droppable\n   * @param {HTMLElement[]|NodeList|HTMLElement} containers - Droppable containers\n   * @param {Object} options - Options for Droppable\n   */\n  constructor(containers = [], options = {}) {\n    super(containers, {\n      ...defaultOptions,\n      ...options,\n      classes: {\n        ...defaultClasses,\n        ...(options.classes || {}),\n      },\n      announcements: {\n        ...defaultAnnouncements,\n        ...(options.announcements || {}),\n      },\n    });\n\n    /**\n     * All dropzone elements on drag start\n     * @property dropzones\n     * @type {HTMLElement[]}\n     */\n    this.dropzones = null;\n\n    /**\n     * Last dropzone element that the source was dropped into\n     * @property lastDropzone\n     * @type {HTMLElement}\n     */\n    this.lastDropzone = null;\n\n    /**\n     * Initial dropzone element that the source was drag from\n     * @property initialDropzone\n     * @type {HTMLElement}\n     */\n    this.initialDropzone = null;\n\n    this[onDragStart] = this[onDragStart].bind(this);\n    this[onDragMove] = this[onDragMove].bind(this);\n    this[onDragStop] = this[onDragStop].bind(this);\n\n    this.on('drag:start', this[onDragStart])\n      .on('drag:move', this[onDragMove])\n      .on('drag:stop', this[onDragStop]);\n  }\n\n  /**\n   * Destroys Droppable instance.\n   */\n  destroy() {\n    super.destroy();\n\n    this.off('drag:start', this[onDragStart])\n      .off('drag:move', this[onDragMove])\n      .off('drag:stop', this[onDragStop]);\n  }\n\n  /**\n   * Drag start handler\n   * @private\n   * @param {DragStartEvent} event - Drag start event\n   */\n  [onDragStart](event) {\n    if (event.canceled()) {\n      return;\n    }\n\n    this.dropzones = [...this[getDropzones]()];\n    const dropzone = closest(event.sensorEvent.target, this.options.dropzone);\n\n    if (!dropzone) {\n      event.cancel();\n      return;\n    }\n\n    const droppableStartEvent = new DroppableStartEvent({\n      dragEvent: event,\n      dropzone,\n    });\n\n    this.trigger(droppableStartEvent);\n\n    if (droppableStartEvent.canceled()) {\n      event.cancel();\n      return;\n    }\n\n    this.initialDropzone = dropzone;\n\n    for (const dropzoneElement of this.dropzones) {\n      if (\n        dropzoneElement.classList.contains(\n          this.getClassNameFor('droppable:occupied'),\n        )\n      ) {\n        continue;\n      }\n\n      dropzoneElement.classList.add(\n        ...this.getClassNamesFor('droppable:active'),\n      );\n    }\n  }\n\n  /**\n   * Drag move handler\n   * @private\n   * @param {DragMoveEvent} event - Drag move event\n   */\n  [onDragMove](event) {\n    if (event.canceled()) {\n      return;\n    }\n\n    const dropzone = this[closestDropzone](event.sensorEvent.target);\n    const overEmptyDropzone =\n      dropzone &&\n      !dropzone.classList.contains(this.getClassNameFor('droppable:occupied'));\n\n    if (overEmptyDropzone && this[dropInDropzone](event, dropzone)) {\n      this.lastDropzone = dropzone;\n    } else if (\n      (!dropzone || dropzone === this.initialDropzone) &&\n      this.lastDropzone\n    ) {\n      this[returnToOriginalDropzone](event);\n      this.lastDropzone = null;\n    }\n  }\n\n  /**\n   * Drag stop handler\n   * @private\n   * @param {DragStopEvent} event - Drag stop event\n   */\n  [onDragStop](event) {\n    const droppableStopEvent = new DroppableStopEvent({\n      dragEvent: event,\n      dropzone: this.lastDropzone || this.initialDropzone,\n    });\n\n    this.trigger(droppableStopEvent);\n\n    const occupiedClasses = this.getClassNamesFor('droppable:occupied');\n\n    for (const dropzone of this.dropzones) {\n      dropzone.classList.remove(...this.getClassNamesFor('droppable:active'));\n    }\n\n    if (this.lastDropzone && this.lastDropzone !== this.initialDropzone) {\n      this.initialDropzone.classList.remove(...occupiedClasses);\n    }\n\n    this.dropzones = null;\n    this.lastDropzone = null;\n    this.initialDropzone = null;\n  }\n\n  /**\n   * Drops a draggable element into a dropzone element\n   * @private\n   * @param {DragMoveEvent} event - Drag move event\n   * @param {HTMLElement} dropzone - Dropzone element to drop draggable into\n   */\n  [dropInDropzone](event, dropzone) {\n    const droppableDroppedEvent = new DroppableDroppedEvent({\n      dragEvent: event,\n      dropzone,\n    });\n\n    this.trigger(droppableDroppedEvent);\n\n    if (droppableDroppedEvent.canceled()) {\n      return false;\n    }\n\n    const occupiedClasses = this.getClassNamesFor('droppable:occupied');\n\n    if (this.lastDropzone) {\n      this.lastDropzone.classList.remove(...occupiedClasses);\n    }\n\n    dropzone.appendChild(event.source);\n    dropzone.classList.add(...occupiedClasses);\n\n    return true;\n  }\n\n  /**\n   * Moves the previously dropped element back into its original dropzone\n   * @private\n   * @param {DragMoveEvent} event - Drag move event\n   */\n  [returnToOriginalDropzone](event) {\n    const droppableReturnedEvent = new DroppableReturnedEvent({\n      dragEvent: event,\n      dropzone: this.lastDropzone,\n    });\n\n    this.trigger(droppableReturnedEvent);\n\n    if (droppableReturnedEvent.canceled()) {\n      return;\n    }\n\n    this.initialDropzone.appendChild(event.source);\n    this.lastDropzone.classList.remove(\n      ...this.getClassNamesFor('droppable:occupied'),\n    );\n  }\n\n  /**\n   * Returns closest dropzone element for even target\n   * @private\n   * @param {HTMLElement} target - Event target\n   * @return {HTMLElement|null}\n   */\n  [closestDropzone](target) {\n    if (!this.dropzones) {\n      return null;\n    }\n\n    return closest(target, this.dropzones);\n  }\n\n  /**\n   * Returns all current dropzone elements for this draggable instance\n   * @private\n   * @return {NodeList|HTMLElement[]|Array}\n   */\n  [getDropzones]() {\n    const dropzone = this.options.dropzone;\n\n    if (typeof dropzone === 'string') {\n      return document.querySelectorAll(dropzone);\n    } else if (dropzone instanceof NodeList || dropzone instanceof Array) {\n      return dropzone;\n    } else if (typeof dropzone === 'function') {\n      return dropzone();\n    } else {\n      return [];\n    }\n  }\n}\n"
  },
  {
    "path": "src/Droppable/DroppableEvent/DroppableEvent.ts",
    "content": "import AbstractEvent from 'shared/AbstractEvent';\n\nimport {DragEvent, DragEventData} from '../../Draggable/DragEvent';\n\ninterface DroppableEventData {\n  dragEvent: DragEvent<DragEventData>;\n}\n\n/**\n * Base droppable event\n * @class DroppableEvent\n * @module DroppableEvent\n * @extends AbstractEvent\n */\nexport class DroppableEvent<\n  T extends DroppableEventData,\n> extends AbstractEvent<DroppableEventData> {\n  static type = 'droppable';\n\n  /**\n   * DroppableEvent constructor.\n   * @constructs DroppableEvent\n   * @param {DroppableEventData} data - Event data\n   */\n  constructor(public data: T) {\n    super(data);\n  }\n\n  /**\n   * Original drag event that triggered this droppable event\n   * @property dragEvent\n   * @type {DragEvent}\n   * @readonly\n   */\n  get dragEvent() {\n    return this.data.dragEvent;\n  }\n}\n\ninterface DroppableStartEventData extends DroppableEventData {\n  dropzone: HTMLElement;\n}\n\n/**\n * Droppable start event\n * @class DroppableStartEvent\n * @module DroppableStartEvent\n * @extends DroppableEvent\n */\nexport class DroppableStartEvent extends DroppableEvent<DroppableStartEventData> {\n  static type = 'droppable:start';\n  static cancelable = true;\n\n  /**\n   * The initial dropzone element of the currently dragging draggable element\n   * @property dropzone\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get dropzone() {\n    return this.data.dropzone;\n  }\n}\n\ninterface DroppableDroppedEventData extends DroppableEventData {\n  dropzone: HTMLElement;\n}\n\n/**\n * Droppable dropped event\n * @class DroppableDroppedEvent\n * @module DroppableDroppedEvent\n * @extends DroppableEvent\n */\nexport class DroppableDroppedEvent extends DroppableEvent<DroppableDroppedEventData> {\n  static type = 'droppable:dropped';\n  static cancelable = true;\n\n  /**\n   * The dropzone element you dropped the draggable element into\n   * @property dropzone\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get dropzone() {\n    return this.data.dropzone;\n  }\n}\n\ninterface DroppableReturnedEventData extends DroppableEventData {\n  dropzone: HTMLElement;\n}\n\n/**\n * Droppable returned event\n * @class DroppableReturnedEvent\n * @module DroppableReturnedEvent\n * @extends DroppableEvent\n */\nexport class DroppableReturnedEvent extends DroppableEvent<DroppableReturnedEventData> {\n  static type = 'droppable:returned';\n  static cancelable = true;\n\n  /**\n   * The dropzone element you dragged away from\n   * @property dropzone\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get dropzone() {\n    return this.data.dropzone;\n  }\n}\n\ninterface DroppableStopEventData extends DroppableEventData {\n  dropzone: HTMLElement;\n}\n\n/**\n * Droppable stop event\n * @class DroppableStopEvent\n * @module DroppableStopEvent\n * @extends DroppableEvent\n */\nexport class DroppableStopEvent extends DroppableEvent<DroppableStopEventData> {\n  static type = 'droppable:stop';\n  static cancelable = true;\n\n  /**\n   * The final dropzone element of the draggable element\n   * @property dropzone\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get dropzone() {\n    return this.data.dropzone;\n  }\n}\n"
  },
  {
    "path": "src/Droppable/DroppableEvent/README.md",
    "content": "## DroppableEvent\n\nThe base droppable event for all Droppable events that `Droppable` emits.\n\n|                   |                  |\n| ----------------- | ---------------- |\n| **Interface**     | `DroppableEvent` |\n| **Cancelable**    | false            |\n| **Cancel action** | -                |\n| **type**          | `droppable`      |\n\n### API\n\n**`droppableEvent.dragEvent: DragEvent`**  \nRead-only property for the original drag event that triggered the droppable event.\n\n## DroppableStartEvent\n\n`DroppableStartEvent` gets triggered by `Droppable` before dropping the draggable element into a dropzone element.\n\n|                   |                       |\n| ----------------- | --------------------- |\n| **Specification** | `DroppableEvent`      |\n| **Interface**     | `DroppableStartEvent` |\n| **Cancelable**    | true                  |\n| **Cancel action** | Prevents drag         |\n| **type**          | `droppable:start`     |\n\n### API\n\n**`droppableEvent.dropzone: HTMLElement`**  \nRead-only property for the initial dropzone element of the currently dragging draggable element\n\n## DroppableDroppedEvent\n\n`DroppableDroppedEvent` gets triggered by `Droppable` before dropping the draggable element into a dropzone element.\n\n|                   |                         |\n| ----------------- | ----------------------- |\n| **Specification** | `DroppableEvent`        |\n| **Interface**     | `DroppableDroppedEvent` |\n| **Cancelable**    | true                    |\n| **Cancel action** | Prevents drop           |\n| **type**          | `droppable:dropped`     |\n\n### API\n\n**`droppableEvent.dropzone: HTMLElement`**  \nRead-only property for the dropzone element you dropped the draggable element into\n\n## DroppableReturnedEvent\n\n`DroppableReturnedEvent` gets triggered by `Droppable` before moving the draggable element to its original dropzone.\n\n|                   |                                      |\n| ----------------- | ------------------------------------ |\n| **Specification** | `DroppableEvent`                     |\n| **Interface**     | `DroppableReturnedEvent`             |\n| **Cancelable**    | true                                 |\n| **Cancel action** | Prevents return of draggable element |\n| **type**          | `droppable:returned`                 |\n\n### API\n\n**`droppableEvent.dropzone: HTMLElement`**  \nRead-only property for the dropzone element you dragged away from\n\n## DroppableStopEvent\n\n`DroppableStopEvent` gets triggered by `Droppable` before dropping the draggable element into a dropzone element.\n\n|                   |                      |\n| ----------------- | -------------------- |\n| **Specification** | `DroppableEvent`     |\n| **Interface**     | `DroppableStopEvent` |\n| **Cancelable**    | false                |\n| **Cancel action** | -                    |\n| **type**          | `droppable:stop`    |\n\n### API\n\n**`droppableEvent.dropzone: HTMLElement`**  \nRead-only property for the final dropzone element of the draggable element\n"
  },
  {
    "path": "src/Droppable/DroppableEvent/index.ts",
    "content": "export * from './DroppableEvent';\n"
  },
  {
    "path": "src/Droppable/README.md",
    "content": "## Droppable\n\nDroppable is built on top of Draggable and allows you to declare draggable and droppable elements via options.\nDroppable fires four events on top of the draggable events: `droppable:start`, `droppable:dropped`, `droppable:returned` and `droppable:stop`.\nDroppable elements must begin in an occupied dropzone (see below, [Classes](#classes) and example),\nso they may returned if the drag is canceled or returned.\n\n### Usage\n\n- NPM:\n\n```js\nimport {Droppable} from '@shopify/draggable';\n// Or\nimport Droppable from '@shopify/draggable/build/esm/Droppable/Droppable';\n\nconst droppable = new Droppable(document.querySelectorAll('.container'), {\n  draggable: '.item',\n  dropzone: '.dropzone',\n});\n```\n\n- Browser (as a module):\n\n```html\n<script type=\"module\">\n  import Droppable from 'https://cdn.jsdelivr.net/npm/@shopify/draggable/build/esm/Droppable/Droppable.mjs';\n\n  const droppable = new Droppable(document.querySelectorAll('.container'), {\n    draggable: '.item',\n    dropzone: '.dropzone',\n  });\n</script>\n```\n\n- Browser (Standalone):\n\n```html\n<script src=\"https://cdn.jsdelivr.net/npm/@shopify/draggable/build/umd/index.min.js\"></script>\n<script>\n  const droppable = new Draggable.Droppable(\n    document.querySelectorAll('.container'),\n    {\n      draggable: '.item',\n      dropzone: '.dropzone',\n    },\n  );\n</script>\n```\n\n### API\n\nCheck out [Draggable API](../Draggable#api) for the base API\n\n### Options\n\nCheck out [Draggable options](../Draggable#options) for the base options\n\n**`dropzone {String|HTMLElement[]|NodeList|Function}`**\nA css selector string, an array of elements, a NodeList or a function returning elements for dropzone\nelements within the `containers`.\n\n### Events\n\nCheck out [Draggable events](../Draggable#events) for the base events\n\n| Name                                      | Description                                                              | Cancelable | Cancelable action |\n| ----------------------------------------- | ------------------------------------------------------------------------ | ---------- | ----------------- |\n| [`droppable:start`][droppablestart]       | Gets fired before dropping the draggable element into a dropzone         | true       | Prevents drag     |\n| [`droppable:dropped`][droppabledropped]   | Gets fired when dropping draggable element into a dropzone               | true       | Prevents drop     |\n| [`droppable:returned`][droppablereturned] | Gets fired when draggable elements returns to original dropzone          | true       | Prevents return   |\n| [`droppable:stop`][droppablestop]         | Gets fired before dropping the draggable element into a dropzone element | false      | -                 |\n\n[droppablestart]: DroppableEvent#droppablestartevent\n[droppabledropped]: DroppableEvent#droppabledroppedevent\n[droppablereturned]: DroppableEvent#droppablereturnedevent\n[droppablestop]: DroppableEvent#droppablestopevent\n\n### Classes\n\nCheck out [Draggable class identifiers](../Draggable#classes) for the base class identifiers\n\n| Name                 | Description                                                              | Default                         |\n| -------------------- | ------------------------------------------------------------------------ | ------------------------------- |\n| `droppable:active`   | Class added to the unoccupied dropzone elements when drag starts         | `draggable-droppable--active`   |\n| `droppable:occupied` | Class added to the dropzone element when it contains a draggable element | `draggable-droppable--occupied` |\n\n### Example\n\nThis sample HTML and JavaScript will make `.item` elements draggable and droppable among all `.dropzone` elements:\n\n```html\n<div class=\"container\">\n  <div class=\"dropzone draggable-dropzone--occupied\">\n    <div class=\"item\">A</div>\n  </div>\n  <div class=\"dropzone draggable-dropzone--occupied\">\n    <div class=\"item\">B</div>\n  </div>\n  <div class=\"dropzone draggable-dropzone--occupied\">\n    <div class=\"item\">C</div>\n  </div>\n</div>\n\n<div class=\"container\">\n  <div class=\"dropzone\"></div>\n</div>\n\n<style>\n  .item {\n    height: 100%;\n  }\n  .dropzone {\n    outline: solid 1px;\n    height: 50px;\n  }\n  .draggable-dropzone--occupied {\n    background: lightgreen;\n  }\n</style>\n```\n\n```js\nimport {Droppable} from '@shopify/draggable';\n\nconst droppable = new Droppable(document.querySelectorAll('.container'), {\n  draggable: '.item',\n  dropzone: '.dropzone',\n});\n\ndroppable.on('droppable:dropped', () => console.log('droppable:dropped'));\ndroppable.on('droppable:returned', () => console.log('droppable:returned'));\n```\n"
  },
  {
    "path": "src/Droppable/index.js",
    "content": "import Droppable from './Droppable';\n\nexport default Droppable;\nexport * from './DroppableEvent';\n"
  },
  {
    "path": "src/Droppable/tests/Droppable.test.js",
    "content": "import {\n  createSandbox,\n  clickMouse,\n  moveMouse,\n  releaseMouse,\n  waitForDragDelay,\n  DRAG_DELAY,\n} from 'helper';\n\nimport Droppable, {\n  DroppableStartEvent,\n  DroppableDroppedEvent,\n  DroppableReturnedEvent,\n  DroppableStopEvent,\n} from '..';\n\nconst sampleMarkup = `\n  <div class=\"Container\">\n    <div class=\"Dropzone isOccupied\">\n      <div class=\"Draggable\"></div>\n    </div>\n    <div class=\"Dropzone\"></div>\n    <div class=\"Dropzone isOccupied\">\n      <div class=\"Draggable\"></div>\n    </div>\n    <div class=\"Draggable\"></div>\n  </div>\n`;\n\ndescribe('Droppable', () => {\n  let sandbox;\n  let containers;\n  let draggableElement;\n  let dropzoneElements;\n  let droppable;\n  let firstDropzone;\n  let secondDropzone;\n  let occupiedDropzone;\n  let dropzonelessDraggableElement;\n\n  beforeEach(() => {\n    sandbox = createSandbox(sampleMarkup);\n    containers = sandbox.querySelectorAll('.Container');\n    draggableElement = sandbox.querySelector('.Draggable');\n    dropzoneElements = sandbox.querySelectorAll('.Dropzone');\n    dropzonelessDraggableElement = sandbox.querySelectorAll('.Draggable')[2];\n    firstDropzone = dropzoneElements[0];\n    secondDropzone = dropzoneElements[1];\n    occupiedDropzone = dropzoneElements[2];\n    droppable = new Droppable(containers, {\n      draggable: '.Draggable',\n      dropzone: '.Dropzone',\n      delay: DRAG_DELAY,\n      classes: {\n        'droppable:occupied': 'isOccupied',\n      },\n    });\n  });\n\n  afterEach(() => {\n    droppable.destroy();\n    sandbox.remove();\n  });\n\n  describe('triggers', () => {\n    let eventHandler;\n    let originalDragEvents;\n\n    beforeEach(() => {\n      eventHandler = jest.fn();\n      originalDragEvents = [];\n    });\n\n    it('droppable:start event', () => {\n      droppable.on('drag:start', (dragEvent) =>\n        originalDragEvents.push(dragEvent),\n      );\n      droppable.on('droppable:start', eventHandler);\n\n      move();\n\n      expect(eventHandler).toHaveBeenCalledWithEvent(DroppableStartEvent);\n\n      expect(eventHandler).toHaveBeenCalledWithEventProperties({\n        dragEvent: originalDragEvents[0],\n        dropzone: firstDropzone,\n      });\n    });\n\n    it('droppable:dropped event', () => {\n      droppable.on('drag:move', (dragEvent) =>\n        originalDragEvents.push(dragEvent),\n      );\n      droppable.on('droppable:dropped', eventHandler);\n\n      move();\n\n      expect(eventHandler).toHaveBeenCalledWithEvent(DroppableDroppedEvent);\n\n      expect(eventHandler).toHaveBeenCalledWithEventProperties({\n        dragEvent: originalDragEvents[0],\n        dropzone: secondDropzone,\n      });\n    });\n\n    it('droppable:returned event', () => {\n      droppable.on('drag:move', (dragEvent) =>\n        originalDragEvents.push(dragEvent),\n      );\n      droppable.on('droppable:returned', eventHandler);\n\n      move(() => {\n        moveMouse(firstDropzone);\n        moveMouse(secondDropzone);\n      });\n\n      expect(eventHandler).toHaveBeenCalledWithEvent(DroppableReturnedEvent);\n\n      expect(eventHandler).toHaveBeenCalledWithEventProperties({\n        dragEvent: originalDragEvents[1],\n        dropzone: secondDropzone,\n      });\n    });\n\n    it('droppable:stop event', () => {\n      droppable.on('drag:stop', (dragEvent) =>\n        originalDragEvents.push(dragEvent),\n      );\n      droppable.on('droppable:stop', eventHandler);\n\n      move();\n\n      expect(eventHandler).toHaveBeenCalledWithEvent(DroppableStopEvent);\n\n      expect(eventHandler).toHaveBeenCalledWithEventProperties({\n        dragEvent: originalDragEvents[0],\n        dropzone: secondDropzone,\n      });\n    });\n  });\n\n  it('prevents drag when canceling sortable start event', () => {\n    droppable.on('droppable:start', (droppableEvent) => {\n      droppableEvent.cancel();\n    });\n\n    clickMouse(draggableElement);\n    waitForDragDelay();\n    moveMouse(secondDropzone);\n\n    expect(droppable.isDragging()).toBe(false);\n\n    releaseMouse(droppable.source);\n  });\n\n  it('drops draggable element in an empty droppable element', () => {\n    expect(draggableElement.parentNode).toBe(firstDropzone);\n\n    clickMouse(draggableElement);\n    waitForDragDelay();\n    moveMouse(secondDropzone);\n\n    expect(droppable.source.parentNode).toBe(secondDropzone);\n\n    releaseMouse(droppable.source);\n  });\n\n  it('prevents drop on occupied droppable element', () => {\n    const handler = jest.fn();\n\n    droppable.on('droppable:dropped', handler);\n\n    expect(draggableElement.parentNode).toBe(firstDropzone);\n\n    clickMouse(draggableElement);\n    waitForDragDelay();\n    moveMouse(occupiedDropzone);\n\n    expect(droppable.source.parentNode).toBe(firstDropzone);\n\n    expect(handler).not.toHaveBeenCalled();\n\n    releaseMouse(droppable.source);\n  });\n\n  it('prevents drop when droppable:dropped event gets canceled', () => {\n    droppable.on('droppable:dropped', (droppableEvent) => {\n      droppableEvent.cancel();\n    });\n\n    clickMouse(draggableElement);\n    waitForDragDelay();\n    moveMouse(secondDropzone);\n\n    expect(droppable.source.parentNode).toBe(firstDropzone);\n\n    releaseMouse(droppable.source);\n  });\n\n  it('prevents release when droppable:returned event gets canceled', () => {\n    droppable.on('droppable:returned', (droppableEvent) => {\n      droppableEvent.cancel();\n    });\n\n    clickMouse(draggableElement);\n    waitForDragDelay();\n    moveMouse(secondDropzone);\n    moveMouse(firstDropzone);\n\n    expect(droppable.source.parentNode).toBe(secondDropzone);\n\n    releaseMouse(droppable.source);\n  });\n\n  describe('when dragging element without dropzone as parent', () => {\n    it('does not trigger droppable:start event', () => {\n      const droppableStartHandler = jest.fn();\n      droppable.on('droppable:start', droppableStartHandler);\n\n      clickMouse(dropzonelessDraggableElement);\n      waitForDragDelay();\n\n      expect(droppableStartHandler).not.toHaveBeenCalled();\n\n      releaseMouse(droppable.source);\n    });\n\n    it('cancels drag:start event', () => {\n      droppable.on('drag:start', (dragEvent) => {\n        requestAnimationFrame(() => {\n          // because user defined get triggered first we need to\n          // wait for droppable to cancel the event\n          expect(dragEvent.canceled()).toBe(true);\n        });\n      });\n\n      clickMouse(dropzonelessDraggableElement);\n      waitForDragDelay();\n\n      releaseMouse(droppable.source);\n    });\n  });\n\n  function move(\n    optionalMoves = () => {\n      /* noop */\n    },\n  ) {\n    clickMouse(draggableElement);\n    waitForDragDelay();\n    moveMouse(secondDropzone);\n    optionalMoves();\n    releaseMouse(droppable.source);\n  }\n});\n"
  },
  {
    "path": "src/Plugins/Collidable/Collidable.js",
    "content": "import AbstractPlugin from 'shared/AbstractPlugin';\nimport {closest} from 'shared/utils';\n\nimport {CollidableInEvent, CollidableOutEvent} from './CollidableEvent';\n\nconst onDragMove = Symbol('onDragMove');\nconst onDragStop = Symbol('onDragStop');\nconst onRequestAnimationFrame = Symbol('onRequestAnimationFrame');\n\n/**\n * Collidable plugin which detects colliding elements while dragging\n * @class Collidable\n * @module Collidable\n * @extends AbstractPlugin\n */\nexport default class Collidable extends AbstractPlugin {\n  /**\n   * Collidable constructor.\n   * @constructs Collidable\n   * @param {Draggable} draggable - Draggable instance\n   */\n  constructor(draggable) {\n    super(draggable);\n\n    /**\n     * Keeps track of currently colliding elements\n     * @property {HTMLElement|null} currentlyCollidingElement\n     * @type {HTMLElement|null}\n     */\n    this.currentlyCollidingElement = null;\n\n    /**\n     * Keeps track of currently colliding elements\n     * @property {HTMLElement|null} lastCollidingElement\n     * @type {HTMLElement|null}\n     */\n    this.lastCollidingElement = null;\n\n    /**\n     * Animation frame for finding colliding elements\n     * @property {Number|null} currentAnimationFrame\n     * @type {Number|null}\n     */\n    this.currentAnimationFrame = null;\n\n    this[onDragMove] = this[onDragMove].bind(this);\n    this[onDragStop] = this[onDragStop].bind(this);\n    this[onRequestAnimationFrame] = this[onRequestAnimationFrame].bind(this);\n  }\n\n  /**\n   * Attaches plugins event listeners\n   */\n  attach() {\n    this.draggable\n      .on('drag:move', this[onDragMove])\n      .on('drag:stop', this[onDragStop]);\n  }\n\n  /**\n   * Detaches plugins event listeners\n   */\n  detach() {\n    this.draggable\n      .off('drag:move', this[onDragMove])\n      .off('drag:stop', this[onDragStop]);\n  }\n\n  /**\n   * Returns current collidables based on `collidables` option\n   * @return {HTMLElement[]}\n   */\n  getCollidables() {\n    const collidables = this.draggable.options.collidables;\n\n    if (typeof collidables === 'string') {\n      return Array.prototype.slice.call(document.querySelectorAll(collidables));\n    } else if (\n      collidables instanceof NodeList ||\n      collidables instanceof Array\n    ) {\n      return Array.prototype.slice.call(collidables);\n    } else if (collidables instanceof HTMLElement) {\n      return [collidables];\n    } else if (typeof collidables === 'function') {\n      return collidables();\n    } else {\n      return [];\n    }\n  }\n\n  /**\n   * Drag move handler\n   * @private\n   * @param {DragMoveEvent} event - Drag move event\n   */\n  [onDragMove](event) {\n    const target = event.sensorEvent.target;\n\n    this.currentAnimationFrame = requestAnimationFrame(\n      this[onRequestAnimationFrame](target),\n    );\n\n    if (this.currentlyCollidingElement) {\n      event.cancel();\n    }\n\n    const collidableInEvent = new CollidableInEvent({\n      dragEvent: event,\n      collidingElement: this.currentlyCollidingElement,\n    });\n\n    const collidableOutEvent = new CollidableOutEvent({\n      dragEvent: event,\n      collidingElement: this.lastCollidingElement,\n    });\n\n    const enteringCollidable = Boolean(\n      this.currentlyCollidingElement &&\n        this.lastCollidingElement !== this.currentlyCollidingElement,\n    );\n    const leavingCollidable = Boolean(\n      !this.currentlyCollidingElement && this.lastCollidingElement,\n    );\n\n    if (enteringCollidable) {\n      if (this.lastCollidingElement) {\n        this.draggable.trigger(collidableOutEvent);\n      }\n\n      this.draggable.trigger(collidableInEvent);\n    } else if (leavingCollidable) {\n      this.draggable.trigger(collidableOutEvent);\n    }\n\n    this.lastCollidingElement = this.currentlyCollidingElement;\n  }\n\n  /**\n   * Drag stop handler\n   * @private\n   * @param {DragStopEvent} event - Drag stop event\n   */\n  [onDragStop](event) {\n    const lastCollidingElement =\n      this.currentlyCollidingElement || this.lastCollidingElement;\n    const collidableOutEvent = new CollidableOutEvent({\n      dragEvent: event,\n      collidingElement: lastCollidingElement,\n    });\n\n    if (lastCollidingElement) {\n      this.draggable.trigger(collidableOutEvent);\n    }\n\n    this.lastCollidingElement = null;\n    this.currentlyCollidingElement = null;\n  }\n\n  /**\n   * Animation frame function\n   * @private\n   * @param {HTMLElement} target - Current move target\n   * @return {Function}\n   */\n  [onRequestAnimationFrame](target) {\n    return () => {\n      const collidables = this.getCollidables();\n      this.currentlyCollidingElement = closest(target, (element) =>\n        collidables.includes(element),\n      );\n    };\n  }\n}\n"
  },
  {
    "path": "src/Plugins/Collidable/CollidableEvent/CollidableEvent.ts",
    "content": "import AbstractEvent from 'shared/AbstractEvent';\n\nimport {DragEvent, DragEventData} from '../../../Draggable/DragEvent';\n\ninterface CollidableEventData {\n  dragEvent: DragEvent<DragEventData>;\n}\n\n/**\n * Base collidable event\n * @class CollidableEvent\n * @module CollidableEvent\n * @extends AbstractEvent\n */\nexport class CollidableEvent<\n  T extends CollidableEventData,\n> extends AbstractEvent<CollidableEventData> {\n  static type = 'collidable';\n\n  /**\n   * CollidableEvent constructor.\n   * @constructs CollidableEvent\n   * @param {CollidableEventData} data - Event data\n   */\n  constructor(public data: T) {\n    super(data);\n  }\n\n  /**\n   * Drag event that triggered this colliable event\n   * @property dragEvent\n   * @type {DragEvent}\n   * @readonly\n   */\n  get dragEvent() {\n    return this.data.dragEvent;\n  }\n}\n\ninterface CollidableInEventData extends CollidableEventData {\n  collidingElement: HTMLElement;\n}\n\n/**\n * Collidable in event\n * @class CollidableInEvent\n * @module CollidableInEvent\n * @extends CollidableEvent\n */\nexport class CollidableInEvent extends CollidableEvent<CollidableInEventData> {\n  static type = 'collidable:in';\n\n  /**\n   * Element you are currently colliding with\n   * @property collidingElement\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get collidingElement() {\n    return this.data.collidingElement;\n  }\n}\n\ninterface CollidableOutEventData extends CollidableEventData {\n  collidingElement: HTMLElement;\n}\n\n/**\n * Collidable out event\n * @class CollidableOutEvent\n * @module CollidableOutEvent\n * @extends CollidableEvent\n */\nexport class CollidableOutEvent extends CollidableEvent<CollidableOutEventData> {\n  static type = 'collidable:out';\n\n  /**\n   * Element you were previously colliding with\n   * @property collidingElement\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get collidingElement() {\n    return this.data.collidingElement;\n  }\n}\n"
  },
  {
    "path": "src/Plugins/Collidable/CollidableEvent/README.md",
    "content": "## Collidable event\n\nThe base collidable event for all Collidable events that `Collidable` emits.\n\n| | |\n| --------------------- | ---------------------------------------------------------- |\n| **Interface**         | `CollidableEvent`                                          |\n| **Cancelable**        | false                                                      |\n| **Cancel action**     | -                                                          |\n| **type**              | `collidable`                                               |\n\n### API\n\n**`collidableEvent.dragEvent: DragEvent`**  \nRead-only property for drag event that triggered this collidable event\n\n## CollidableInEvent\n\n`CollidableInEvent` gets triggered by `Collidable` when colliding with an element specified by the\n`collidable` options.\n\n| | |\n| --------------------- | ---------------------------------------------------------- |\n| **Specification**     | `CollidableEvent`                                          |\n| **Interface**         | `CollidableInEvent`                                        |\n| **Cancelable**        | false                                                      |\n| **Cancel action**     | -                                                          |\n| **type**              | `collidable:in`                                            |\n\n### API\n\n**`collidableEvent.collidingElement: HTMLElement`**  \nRead-only property for currently colliding element\n\n## CollidableOutEvent\n\n`CollidableOutEvent` gets triggered by `Collidable` when leaving a colliding area.\n\n| | |\n| --------------------- | ---------------------------------------------------------- |\n| **Specification**     | `CollidableEvent`                                          |\n| **Interface**         | `CollidableOutEvent`                                       |\n| **Cancelable**        | false                                                      |\n| **Cancel action**     | -                                                          |\n| **type**              | `collidable:out`                                           |\n\n### API\n\n**`collidableEvent.collidingElement: HTMLElement`**  \nRead-only property for previously colliding element\n"
  },
  {
    "path": "src/Plugins/Collidable/CollidableEvent/index.ts",
    "content": "export * from './CollidableEvent';\n"
  },
  {
    "path": "src/Plugins/Collidable/README.md",
    "content": "## Collidable\n\nWhen you use the collidable plugin you can specify which elements you **can't** drag over and it will freeze\nthe mirror movement for you. These currently only work with `Sortable`, `Swappable` and `Droppable`.\n\nThis plugin is not included by default, so make sure to import it before using.\n\n### Usage\n\n- NPM:\n\n```js\nimport {Draggable, Plugins} from '@shopify/draggable';\n// Or\nimport Draggable from '@shopify/draggable/build/esm/Draggable/Draggable';\nimport Collidable from '@shopify/draggable/build/esm/Plugins/Collidable';\n\nconst draggable = new Draggable(document.querySelectorAll('ul'), {\n  draggable: 'li',\n  collidables: '.other-list',\n  plugins: [Plugins.Collidable], // Or [Collidable]\n});\n```\n\n- Browser (as a module):\n\n```html\n<script type=\"module\">\n  import Draggable from 'https://cdn.jsdelivr.net/npm/@shopify/draggable/build/esm/Draggable/Draggable.mjs';\n  import Collidable from 'https://cdn.jsdelivr.net/npm/@shopify/draggable/build/esm/Plugins/Collidable.mjs';\n\n  const draggable = new Draggable(document.querySelectorAll('ul'), {\n    draggable: 'li',\n    collidables: '.other-list',\n    plugins: [Collidable],\n  });\n</script>\n```\n\n- Browser (Standalone):\n\n```html\n<script src=\"https://cdn.jsdelivr.net/npm/@shopify/draggable/build/umd/index.min.js\"></script>\n<script>\n  const draggable = new Draggable.Draggable(document.querySelectorAll('ul'), {\n    draggable: 'li',\n    collidables: '.other-list',\n    plugins: [Draggable.Plugins.Collidable],\n  });\n</script>\n```\n\n### Options\n\n**`collidables {String|HTMLElement[]|NodeList|HTMLElement|Function}`**  \nA css selector string, an array of elements, a NodeList, a HTMLElement or a function returning elements for collidable elements.\n\n### Events\n\n| Name                              | Description                                             | Cancelable | Cancelable action |\n| --------------------------------- | ------------------------------------------------------- | ---------- | ----------------- |\n| [`collidable:in`][collidablein]   | Gets fired when dragging near a collidable element      | false      | -                 |\n| [`collidable:out`][collidableout] | Gets fired when dragging away from a collidable element | false      | -                 |\n\n[collidablein]: CollidableEvent#collidableinevent\n[collidableout]: CollidableEvent#collidableoutevent\n\n### Example\n\n```js\nimport {Sortable, Plugins} from '@shopify/draggable';\n\nconst sortable = new Sortable(document.querySelectorAll('ul'), {\n  draggable: 'li',\n  collidables: '.other-list',\n  plugins: [Plugins.Collidable],\n});\n\nsortable.on('collidable:in', () => console.log('collidable:in'));\nsortable.on('collidable:out', () => console.log('collidable:out'));\n```\n\n### Plans\n\n- Improving collision detection for mirror\n\n### Caveats\n\n- Currently only bases collision based on mouse cursor and not mirror element\n"
  },
  {
    "path": "src/Plugins/Collidable/index.js",
    "content": "import Collidable from './Collidable';\n\nexport default Collidable;\nexport * from './CollidableEvent';\n"
  },
  {
    "path": "src/Plugins/README.md",
    "content": "## Plugins\n"
  },
  {
    "path": "src/Plugins/ResizeMirror/README.md",
    "content": "## ResizeMirror\n\nThe ResizeMirror plugin resizes the mirror element to the dimensions of the draggable element that the mirror is hovering over.\n\nIt will also appends the mirror element to whatever draggable container element the mirror is hovering over.\nYou can add transitions to the mirror element to animate the resizing.\n\nThis plugin is not included in the default Draggable bundle, so you'll need to import it separately.\n\n![grid-mirror-resize](https://user-images.githubusercontent.com/643944/39401902-197a93d4-4b1f-11e8-8e2a-9c3070a6fb95.gif)\n\n> **Example of `ResizeMirror` in action.** Custom transitions are applied via CSS _(not provided by the plugin)_ – [Grid Layout Example](https://shopify.github.io/draggable/examples/grid-layout.html)\n\n### Usage\n\n- NPM:\n\n```js\nimport {Draggable, Plugins} from '@shopify/draggable';\n// Or\nimport Draggable from '@shopify/draggable/build/esm/Draggable/Draggable';\nimport ResizeMirror from '@shopify/draggable/build/esm/Plugins/ResizeMirror';\n\nconst draggable = new Draggable(document.querySelectorAll('ul'), {\n  draggable: 'li',\n  plugins: [Plugins.ResizeMirror], // Or [ResizeMirror]\n});\n```\n\n- Browser (as a module):\n\n```html\n<script type=\"module\">\n  import Draggable from 'https://cdn.jsdelivr.net/npm/@shopify/draggable/build/esm/Draggable/Draggable.mjs';\n  import ResizeMirror from 'https://cdn.jsdelivr.net/npm/@shopify/draggable/build/esm/Plugins/ResizeMirror.mjs';\n\n  const draggable = new Draggable(document.querySelectorAll('ul'), {\n    draggable: 'li',\n    plugins: [ResizeMirror],\n  });\n</script>\n```\n\n- Browser (Standalone):\n\n```html\n<script src=\"https://cdn.jsdelivr.net/npm/@shopify/draggable/build/umd/index.min.js\"></script>\n<script>\n  const draggable = new Draggable.Draggable(document.querySelectorAll('ul'), {\n    draggable: 'li',\n    plugins: [Draggable.Plugins.ResizeMirror],\n  });\n</script>\n```\n\n### API\n\n**`new ResizeMirror(draggable: Draggable): ResizeMirror`**\nCreates an instance of the ResizeMirror plugin.\n\n### Options\n\n_No options_\n\n### Examples\n\n```js\nimport {Sortable, Plugins} from '@shopify/draggable';\n\nconst sortable = new Sortable(document.querySelectorAll('ul'), {\n  draggable: 'li',\n  plugins: [Plugins.ResizeMirror],\n});\n```\n\n### Caveats\n\n- The mirror is not re-positioned under the cursor once resized, i.e. if the mirror shrinks/expands the mirror may no longer be directly beneath the cursor\n"
  },
  {
    "path": "src/Plugins/ResizeMirror/ResizeMirror.ts",
    "content": "import AbstractPlugin from 'shared/AbstractPlugin';\nimport {requestNextAnimationFrame, AutoBind} from 'shared/utils';\nimport {FixMeAny} from 'shared/types';\n\nimport {MirrorCreatedEvent} from '../../Draggable/Plugins/Mirror/MirrorEvent';\nimport {\n  DragOverEvent,\n  DragOverContainerEvent,\n  isDragOverEvent,\n} from '../../Draggable/DragEvent';\n\n/**\n * ResizeMirror default options\n * @property {Object} defaultOptions\n * @type {Object}\n */\nexport const defaultOptions = {};\n\n/**\n * The ResizeMirror plugin resizes the mirror element to the dimensions of the draggable element that the mirror is hovering over\n * @class ResizeMirror\n * @module ResizeMirror\n * @extends AbstractPlugin\n */\nexport default class ResizeMirror extends AbstractPlugin {\n  private lastWidth: number;\n  private lastHeight: number;\n  private mirror: HTMLElement | null;\n  /**\n   * ResizeMirror constructor.\n   * @constructs ResizeMirror\n   * @param {Draggable} draggable - Draggable instance\n   */\n  constructor(draggable: FixMeAny) {\n    super(draggable);\n\n    /**\n     * ResizeMirror remembers the last width when resizing the mirror\n     * to avoid additional writes to the DOM\n     * @property {number} lastWidth\n     */\n    this.lastWidth = 0;\n\n    /**\n     * ResizeMirror remembers the last height when resizing the mirror\n     * to avoid additional writes to the DOM\n     * @property {number} lastHeight\n     */\n    this.lastHeight = 0;\n\n    /**\n     * Keeps track of the mirror element\n     * @property {HTMLElement} mirror\n     */\n    this.mirror = null;\n  }\n\n  /**\n   * Attaches plugins event listeners\n   */\n  attach() {\n    this.draggable\n      .on('mirror:created', this.onMirrorCreated)\n      .on('drag:over', this.onDragOver)\n      .on('drag:over:container', this.onDragOver);\n  }\n\n  /**\n   * Detaches plugins event listeners\n   */\n  detach() {\n    this.draggable\n      .off('mirror:created', this.onMirrorCreated)\n      .off('mirror:destroy', this.onMirrorDestroy)\n      .off('drag:over', this.onDragOver)\n      .off('drag:over:container', this.onDragOver);\n  }\n\n  /**\n   * Returns options passed through draggable\n   * @return {Object}\n   */\n  getOptions() {\n    return this.draggable.options.resizeMirror || {};\n  }\n\n  /**\n   * Mirror created handler\n   * @param {MirrorCreatedEvent} mirrorEvent\n   * @private\n   */\n  @AutoBind\n  private onMirrorCreated({mirror}: MirrorCreatedEvent) {\n    this.mirror = mirror;\n  }\n\n  /**\n   * Mirror destroy handler\n   * @param {MirrorDestroyEvent} mirrorEvent\n   * @private\n   */\n  @AutoBind\n  private onMirrorDestroy() {\n    this.mirror = null;\n  }\n\n  /**\n   * Drag over handler\n   * @param {DragOverEvent | DragOverContainer} dragEvent\n   * @private\n   */\n  @AutoBind\n  private onDragOver(dragEvent: DragOverEvent | DragOverContainerEvent) {\n    this.resize(dragEvent);\n  }\n\n  /**\n   * Resize function for\n   * @param {DragOverEvent | DragOverContainer} dragEvent\n   * @private\n   */\n  private resize(dragEvent: DragOverEvent | DragOverContainerEvent) {\n    requestAnimationFrame(() => {\n      let over: HTMLElement | null = null;\n      const {overContainer} = dragEvent;\n\n      if (this.mirror == null || this.mirror.parentNode == null) {\n        return;\n      }\n\n      if (this.mirror.parentNode !== overContainer) {\n        overContainer.appendChild(this.mirror);\n      }\n\n      if (isDragOverEvent(dragEvent)) {\n        over = dragEvent.over;\n      }\n\n      const overElement =\n        over ||\n        this.draggable.getDraggableElementsForContainer(overContainer)[0];\n\n      if (!overElement) {\n        return;\n      }\n\n      requestNextAnimationFrame(() => {\n        const overRect = overElement.getBoundingClientRect();\n\n        if (\n          this.mirror == null ||\n          (this.lastHeight === overRect.height &&\n            this.lastWidth === overRect.width)\n        ) {\n          return;\n        }\n\n        this.mirror.style.width = `${overRect.width}px`;\n        this.mirror.style.height = `${overRect.height}px`;\n\n        this.lastWidth = overRect.width;\n        this.lastHeight = overRect.height;\n      });\n    });\n  }\n}\n"
  },
  {
    "path": "src/Plugins/ResizeMirror/index.ts",
    "content": "import ResizeMirror, {defaultOptions} from './ResizeMirror';\n\nexport default ResizeMirror;\nexport {defaultOptions};\n"
  },
  {
    "path": "src/Plugins/ResizeMirror/tests/ResizeMirror.test.ts",
    "content": "import {\n  createSandbox,\n  waitForRequestAnimationFrame,\n  clickMouse,\n  moveMouse,\n  releaseMouse,\n  waitForDragDelay,\n  waitForPromisesToResolve,\n  DRAG_DELAY,\n  drag,\n} from 'helper';\nimport {FixMeAny} from 'shared/types';\n\n/* eslint-disable @typescript-eslint/ban-ts-comment */\n// @ts-ignore\nimport Draggable from '../../../Draggable';\n/* eslint-enable @typescript-eslint/ban-ts-comment */\nimport ResizeMirror from '..';\n\nconst sampleMarkup = `\n  <ul class=\"Container\">\n    <li>Smaller item</li>\n  </ul>\n  <ul class=\"Container\">\n    <li>Larger item</li>\n  </ul>\n  <ul class=\"Container\">\n    <!-- Empty -->\n  </ul>\n`;\n\ndescribe('ResizeMirror', () => {\n  const smallerDraggableDimensions = {\n    width: 100,\n    height: 30,\n  };\n\n  const largerDraggableDimensions = {\n    width: smallerDraggableDimensions.width * 2,\n    height: smallerDraggableDimensions.height * 2,\n  };\n\n  let sandbox: HTMLDivElement;\n  let containers: HTMLElement[];\n  let draggable: FixMeAny;\n  let draggables: HTMLElement[];\n  let smallerDraggable: HTMLElement;\n  let largerDraggable: HTMLElement;\n\n  beforeEach(() => {\n    sandbox = createSandbox(sampleMarkup);\n    containers = [...sandbox.querySelectorAll<HTMLElement>('.Container')];\n    draggables = [...sandbox.querySelectorAll<HTMLElement>('li')];\n\n    smallerDraggable = draggables[0];\n    largerDraggable = draggables[1];\n\n    mockDimensions(smallerDraggable, smallerDraggableDimensions);\n    mockDimensions(largerDraggable, largerDraggableDimensions);\n\n    draggable = new Draggable(containers, {\n      draggable: 'li',\n      delay: DRAG_DELAY,\n      plugins: [ResizeMirror],\n    });\n  });\n\n  afterEach(() => {\n    draggable.destroy();\n    sandbox.remove();\n  });\n\n  it('resizes mirror based on over element', async () => {\n    clickMouse(smallerDraggable);\n    waitForDragDelay();\n    await waitForPromisesToResolve();\n\n    const mirror = document.querySelector<HTMLElement>('.draggable-mirror')!;\n\n    expect(mirror.style).toMatchObject({\n      width: `${smallerDraggableDimensions.width}px`,\n      height: `${smallerDraggableDimensions.height}px`,\n    });\n\n    moveMouse(largerDraggable);\n    waitForRequestAnimationFrame();\n    waitForNextRequestAnimationFrame();\n\n    expect(mirror.style).toMatchObject({\n      width: `${largerDraggableDimensions.width}px`,\n      height: `${largerDraggableDimensions.height}px`,\n    });\n\n    moveMouse(smallerDraggable);\n    waitForRequestAnimationFrame();\n    waitForNextRequestAnimationFrame();\n\n    expect(mirror.style).toMatchObject({\n      width: `${smallerDraggableDimensions.width}px`,\n      height: `${smallerDraggableDimensions.height}px`,\n    });\n\n    releaseMouse(largerDraggable);\n  });\n\n  it('appends mirror in over container', async () => {\n    clickMouse(smallerDraggable);\n    waitForDragDelay();\n    await waitForPromisesToResolve();\n\n    const mirror = document.querySelector<HTMLElement>('.draggable-mirror')!;\n\n    moveMouse(largerDraggable);\n    waitForRequestAnimationFrame();\n\n    expect(mirror.parentNode).toBe(containers[1]);\n\n    releaseMouse(largerDraggable);\n  });\n\n  it('appends mirror only for different parent containers', async () => {\n    clickMouse(smallerDraggable);\n    waitForDragDelay();\n    await waitForPromisesToResolve();\n\n    const mirror = document.querySelector<HTMLElement>('.draggable-mirror')!;\n\n    const mockedAppendChild = withMockedAppendChild(() => {\n      moveMouse(smallerDraggable);\n      waitForRequestAnimationFrame();\n    });\n\n    expect(mirror.parentNode).toBe(draggable.sourceContainer);\n    expect(mockedAppendChild).not.toHaveBeenCalled();\n\n    releaseMouse(largerDraggable);\n  });\n\n  it('dont appends mirror when mirror was removed', async () => {\n    drag({from: smallerDraggable, to: smallerDraggable});\n    drag({from: smallerDraggable, to: smallerDraggable});\n\n    await waitForPromisesToResolve();\n    waitForRequestAnimationFrame();\n\n    const mirror = document.querySelector('.draggable-mirror');\n\n    expect(mirror).toBeNull();\n  });\n});\n\nfunction mockDimensions(element: HTMLElement, {width = 0, height = 0}) {\n  Object.assign(element.style, {\n    width: `${width}px`,\n    height: `${height}px`,\n  });\n\n  const properties = {\n    width,\n    height,\n    top: 0,\n    left: 0,\n    right: width,\n    bottom: height,\n    x: 0,\n    y: 0,\n  };\n\n  element.getBoundingClientRect = () => ({\n    ...properties,\n    toJSON() {\n      return {...properties};\n    },\n  });\n\n  return element;\n}\n\nfunction waitForNextRequestAnimationFrame() {\n  waitForRequestAnimationFrame();\n  waitForRequestAnimationFrame();\n}\n\nfunction withMockedAppendChild(callback: () => void) {\n  const mock = jest.fn();\n  const appendChild = Node.prototype.appendChild;\n  /* eslint-disable @typescript-eslint/ban-ts-comment */\n  // @ts-ignore\n  Node.prototype.appendChild = function (this: Node, ...args) {\n    mock(...args);\n    return appendChild.call(this, ...args);\n  };\n  /* eslint-enable @typescript-eslint/ban-ts-comment */\n  callback();\n  Node.prototype.appendChild = appendChild;\n  return mock;\n}\n"
  },
  {
    "path": "src/Plugins/Snappable/README.md",
    "content": "## Snappable\n\nSnappable simulates a \"snap\" by hiding the mirror and removing the `'source:dragging'` class from the source.\nIt also sets the `'source:placed'` class for potential drop animations.\n\nThis plugin is not included by default, so make sure to import it before using.\n\n### Usage\n\n- NPM:\n\n```js\nimport {Draggable, Plugins} from '@shopify/draggable';\n// Or\nimport Draggable from '@shopify/draggable/build/esm/Draggable/Draggable';\nimport Snappable from '@shopify/draggable/build/esm/Plugins/Snappable';\n\nconst draggable = new Draggable(document.querySelectorAll('ul'), {\n  draggable: 'li',\n  plugins: [Plugins.Snappable], // Or [Snappable]\n});\n```\n\n- Browser (as a module):\n\n```html\n<script type=\"module\">\n  import Draggable from 'https://cdn.jsdelivr.net/npm/@shopify/draggable/build/esm/Draggable/Draggable.mjs';\n  import Snappable from 'https://cdn.jsdelivr.net/npm/@shopify/draggable/build/esm/Plugins/Snappable.mjs';\n\n  const draggable = new Draggable(document.querySelectorAll('ul'), {\n    draggable: 'li',\n    plugins: [Snappable],\n  });\n</script>\n```\n\n- Browser (Standalone):\n\n```html\n<script src=\"https://cdn.jsdelivr.net/npm/@shopify/draggable/build/umd/index.min.js\"></script>\n<script>\n  const draggable = new Draggable.Draggable(document.querySelectorAll('ul'), {\n    draggable: 'li',\n    plugins: [Draggable.Plugins.Snappable],\n  });\n</script>\n```\n\n### Options\n\n_No options_\n\n### Events\n\n| Name                  | Description                             | Cancelable | Cancelable action     |\n| --------------------- | --------------------------------------- | ---------- | --------------------- |\n| [`snap:in`][snapin]   | Gets fired when just before snapping in | true       | Prevents snapping     |\n| [`snap:out`][snapout] | Gets fired when snapping out            | true       | Prevents snapping out |\n\n[snapin]: SnappableEvent#snapinevent\n[snapout]: SnappableEvent#snapoutevent\n\n### Example\n\n```js\nimport {Sortable, Plugins} from '@shopify/draggable';\n\nconst sortable = new Sortable(document.querySelectorAll('ul'), {\n  draggable: 'li',\n  plugins: [Plugins.Snappable],\n});\n\nsortable.on('snap:in', () => console.log('snap:in'));\nsortable.on('snap:out', () => console.log('snap:out'));\n```\n"
  },
  {
    "path": "src/Plugins/Snappable/Snappable.js",
    "content": "import AbstractPlugin from 'shared/AbstractPlugin';\n\nimport {SnapInEvent, SnapOutEvent} from './SnappableEvent';\n\nconst onDragStart = Symbol('onDragStart');\nconst onDragStop = Symbol('onDragStop');\nconst onDragOver = Symbol('onDragOver');\nconst onDragOut = Symbol('onDragOut');\nconst onMirrorCreated = Symbol('onMirrorCreated');\nconst onMirrorDestroy = Symbol('onMirrorDestroy');\n\n/**\n * Snappable plugin which snaps draggable elements into place\n * @class Snappable\n * @module Snappable\n * @extends AbstractPlugin\n */\nexport default class Snappable extends AbstractPlugin {\n  /**\n   * Snappable constructor.\n   * @constructs Snappable\n   * @param {Draggable} draggable - Draggable instance\n   */\n  constructor(draggable) {\n    super(draggable);\n\n    /**\n     * Keeps track of the first source element\n     * @property {HTMLElement|null} firstSource\n     */\n    this.firstSource = null;\n\n    /**\n     * Keeps track of the mirror element\n     * @property {HTMLElement} mirror\n     */\n    this.mirror = null;\n\n    this[onDragStart] = this[onDragStart].bind(this);\n    this[onDragStop] = this[onDragStop].bind(this);\n    this[onDragOver] = this[onDragOver].bind(this);\n    this[onDragOut] = this[onDragOut].bind(this);\n    this[onMirrorCreated] = this[onMirrorCreated].bind(this);\n    this[onMirrorDestroy] = this[onMirrorDestroy].bind(this);\n  }\n\n  /**\n   * Attaches plugins event listeners\n   */\n  attach() {\n    this.draggable\n      .on('drag:start', this[onDragStart])\n      .on('drag:stop', this[onDragStop])\n      .on('drag:over', this[onDragOver])\n      .on('drag:out', this[onDragOut])\n      .on('droppable:over', this[onDragOver])\n      .on('droppable:out', this[onDragOut])\n      .on('mirror:created', this[onMirrorCreated])\n      .on('mirror:destroy', this[onMirrorDestroy]);\n  }\n\n  /**\n   * Detaches plugins event listeners\n   */\n  detach() {\n    this.draggable\n      .off('drag:start', this[onDragStart])\n      .off('drag:stop', this[onDragStop])\n      .off('drag:over', this[onDragOver])\n      .off('drag:out', this[onDragOut])\n      .off('droppable:over', this[onDragOver])\n      .off('droppable:out', this[onDragOut])\n      .off('mirror:created', this[onMirrorCreated])\n      .off('mirror:destroy', this[onMirrorDestroy]);\n  }\n\n  /**\n   * Drag start handler\n   * @private\n   * @param {DragStartEvent} event - Drag start event\n   */\n  [onDragStart](event) {\n    if (event.canceled()) {\n      return;\n    }\n\n    this.firstSource = event.source;\n  }\n\n  /**\n   * Drag stop handler\n   * @private\n   * @param {DragStopEvent} event - Drag stop event\n   */\n  [onDragStop]() {\n    this.firstSource = null;\n  }\n\n  /**\n   * Drag over handler\n   * @private\n   * @param {DragOverEvent|DroppableOverEvent} event - Drag over event\n   */\n  [onDragOver](event) {\n    if (event.canceled()) {\n      return;\n    }\n\n    const source = event.source || event.dragEvent.source;\n\n    if (source === this.firstSource) {\n      this.firstSource = null;\n      return;\n    }\n\n    const snapInEvent = new SnapInEvent({\n      dragEvent: event,\n      snappable: event.over || event.droppable,\n    });\n\n    this.draggable.trigger(snapInEvent);\n\n    if (snapInEvent.canceled()) {\n      return;\n    }\n\n    if (this.mirror) {\n      this.mirror.style.display = 'none';\n    }\n\n    source.classList.remove(\n      ...this.draggable.getClassNamesFor('source:dragging'),\n    );\n    source.classList.add(...this.draggable.getClassNamesFor('source:placed'));\n\n    // Need to cancel this in drag out\n    setTimeout(() => {\n      source.classList.remove(\n        ...this.draggable.getClassNamesFor('source:placed'),\n      );\n    }, this.draggable.options.placedTimeout);\n  }\n\n  /**\n   * Drag out handler\n   * @private\n   * @param {DragOutEvent|DroppableOutEvent} event - Drag out event\n   */\n  [onDragOut](event) {\n    if (event.canceled()) {\n      return;\n    }\n\n    const source = event.source || event.dragEvent.source;\n\n    const snapOutEvent = new SnapOutEvent({\n      dragEvent: event,\n      snappable: event.over || event.droppable,\n    });\n\n    this.draggable.trigger(snapOutEvent);\n\n    if (snapOutEvent.canceled()) {\n      return;\n    }\n\n    if (this.mirror) {\n      this.mirror.style.display = '';\n    }\n\n    source.classList.add(...this.draggable.getClassNamesFor('source:dragging'));\n  }\n\n  /**\n   * Mirror created handler\n   * @param {MirrorCreatedEvent} mirrorEvent\n   * @private\n   */\n  [onMirrorCreated]({mirror}) {\n    this.mirror = mirror;\n  }\n\n  /**\n   * Mirror destroy handler\n   * @param {MirrorDestroyEvent} mirrorEvent\n   * @private\n   */\n  [onMirrorDestroy]() {\n    this.mirror = null;\n  }\n}\n"
  },
  {
    "path": "src/Plugins/Snappable/SnappableEvent/README.md",
    "content": "## Snap event\n\nThe base snap event for all Snap events that `Snappable` emits.\n\n| | |\n| --------------------- | ---------------------------------------------------------- |\n| **Interface**         | `SnapEvent`                                                |\n| **Cancelable**        | false                                                      |\n| **Cancel action**     | -                                                          |\n| **type**              | `snap`                                                     |\n\n### API\n\n**`snapEvent.dragEvent: DragEvent`**  \nRead-only property for drag event that triggered this snappable event\n\n**`snapEvent.snappable: HTMLElement`**  \nRead-only property for the element that you are about to snap into or out of\n\n## SnapInEvent\n\n`SnapInEvent` gets triggered by `Snappable` before snapping into place.\n\n| | |\n| --------------------- | ---------------------------------------------------------- |\n| **Specification**     | `SnapEvent`                                                |\n| **Interface**         | `SnapInEvent`                                              |\n| **Cancelable**        | true                                                       |\n| **Cancel action**     | Prevents snap in effect                                    |\n| **type**              | `snap:in`                                                  |\n\n## SnapOutEvent\n\n`SnapOutEvent` gets triggered by `Snappable` before snapping out.\n\n| | |\n| --------------------- | ---------------------------------------------------------- |\n| **Specification**     | `SnapEvent`                                                |\n| **Interface**         | `SnapOutEvent`                                             |\n| **Cancelable**        | true                                                       |\n| **Cancel action**     | Prevents snap out effect                                   |\n| **type**              | `snap:out`                                                 |\n"
  },
  {
    "path": "src/Plugins/Snappable/SnappableEvent/SnappableEvent.ts",
    "content": "import AbstractEvent from 'shared/AbstractEvent';\n\nimport {DragEvent, DragEventData} from '../../../Draggable/DragEvent';\n\ninterface SnapEventData {\n  dragEvent: DragEvent<DragEventData>;\n  snappable: HTMLElement;\n}\n\n/**\n * Base snap event\n * @class SnapEvent\n * @module SnapEvent\n * @extends AbstractEvent\n */\nexport class SnapEvent extends AbstractEvent<SnapEventData> {\n  static type = 'snap';\n\n  /**\n   * Drag event that triggered this snap event\n   * @property dragEvent\n   * @type {DragEvent}\n   * @readonly\n   */\n  get dragEvent() {\n    return this.data.dragEvent;\n  }\n\n  /**\n   * Snappable element\n   * @property snappable\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get snappable() {\n    return this.data.snappable;\n  }\n}\n\n/**\n * Snap in event\n * @class SnapInEvent\n * @module SnapInEvent\n * @extends SnapEvent\n */\nexport class SnapInEvent extends SnapEvent {\n  static type = 'snap:in';\n  static cancelable = true;\n}\n\n/**\n * Snap out event\n * @class SnapOutEvent\n * @module SnapOutEvent\n * @extends SnapEvent\n */\nexport class SnapOutEvent extends SnapEvent {\n  static type = 'snap:out';\n  static cancelable = true;\n}\n"
  },
  {
    "path": "src/Plugins/Snappable/SnappableEvent/index.ts",
    "content": "export * from './SnappableEvent';\n"
  },
  {
    "path": "src/Plugins/Snappable/index.js",
    "content": "import Snappable from './Snappable';\n\nexport default Snappable;\nexport * from './SnappableEvent';\n"
  },
  {
    "path": "src/Plugins/SortAnimation/README.md",
    "content": "## SortAnimation\n\nThe sort animation plugin currently only works with `Sortable`. It adds sort animation on `sortable:sorted` with both horizontal and vertical within grid layout,\nand animates all sorted elements via `translate3d`. It is currently possible to change the duration and\nthe easing function of the animation.\n\nIt different with [SwapAnimation](https://github.com/Shopify/draggable/tree/main/src/Plugins/SwapAnimation) plugin because SwapAnimation only support horizontal or vertical layout.\n\nThis plugin is not included by default, so make sure to import it before using.\n\n**NOTE**: Don't use this plugin with [SwapAnimation](https://github.com/Shopify/draggable/tree/main/src/Plugins/SwapAnimation) plugin to avoid conflict.\n\n### Usage\n\n- NPM:\n\n```js\nimport {Sortable, Plugins} from '@shopify/draggable';\n// Or\nimport Sortable from '@shopify/draggable/build/esm/Sortable/Sortable';\nimport SortAnimation from '@shopify/draggable/build/esm/Plugins/SortAnimation';\n\nconst sortable = new Sortable(document.querySelectorAll('ul'), {\n  draggable: 'li',\n  sortAnimation: {\n    duration: 200,\n    easingFunction: 'ease-in-out',\n  },\n  plugins: [Plugins.SortAnimation], // Or [SortAnimation]\n});\n```\n\n- Browser (as a module):\n\n```html\n<script type=\"module\">\n  import Sortable from 'https://cdn.jsdelivr.net/npm/@shopify/draggable/build/esm/Sortable/Sortable.mjs';\n  import Snappable from 'https://cdn.jsdelivr.net/npm/@shopify/draggable/build/esm/Plugins/Snappable.mjs';\n\n  const sortable = new Sortable(document.querySelectorAll('ul'), {\n    draggable: 'li',\n    sortAnimation: {\n      duration: 200,\n      easingFunction: 'ease-in-out',\n    },\n    plugins: [Snappable],\n  });\n</script>\n```\n\n- Browser (Standalone):\n\n```html\n<script src=\"https://cdn.jsdelivr.net/npm/@shopify/draggable/build/umd/index.min.js\"></script>\n<script>\n  const sortable = new Draggable.Sortable(document.querySelectorAll('ul'), {\n    draggable: 'li',\n    sortAnimation: {\n      duration: 200,\n      easingFunction: 'ease-in-out',\n    },\n    plugins: [Draggable.Plugins.SortAnimation],\n  });\n</script>\n```\n\n### API\n\n**`new SortAnimation(draggable: Draggable): SortAnimation`**  \nTo create a sort animation plugin instance.\n\n### Options\n\n**`duration {Integer}`**  \nThe duration option allows you to specify the animation during for a single sort. Default: `150`\n\n**`easingFunction {String}`**  \nThe easing option allows you to specify an animation easing function. Default: `'ease-in-out'`\n\n### Examples\n\n```js\nimport {Sortable, Plugins} from '@shopify/draggable';\n\nconst sortable = new Sortable(document.querySelectorAll('ul'), {\n  draggable: 'li',\n  sortAnimation: {\n    duration: 200,\n    easingFunction: 'ease-in-out',\n  },\n  plugins: [Plugins.SortAnimation],\n});\n```\n\n### Caveats\n\n- Only works within same container\n- Animations don't stagger\n- Only works with `Sortable`\n"
  },
  {
    "path": "src/Plugins/SortAnimation/SortAnimation.js",
    "content": "import AbstractPlugin from 'shared/AbstractPlugin';\n\nconst onSortableSorted = Symbol('onSortableSorted');\nconst onSortableSort = Symbol('onSortableSort');\n\n/**\n * SortAnimation default options\n * @property {Object} defaultOptions\n * @property {Number} defaultOptions.duration\n * @property {String} defaultOptions.easingFunction\n * @type {Object}\n */\nexport const defaultOptions = {\n  duration: 150,\n  easingFunction: 'ease-in-out',\n};\n\n/**\n * SortAnimation plugin adds sort animation for sortable\n * @class SortAnimation\n * @module SortAnimation\n * @extends AbstractPlugin\n */\nexport default class SortAnimation extends AbstractPlugin {\n  /**\n   * SortAnimation constructor.\n   * @constructs SortAnimation\n   * @param {Draggable} draggable - Draggable instance\n   */\n  constructor(draggable) {\n    super(draggable);\n\n    /**\n     * SortAnimation options\n     * @property {Object} options\n     * @property {Number} defaultOptions.duration\n     * @property {String} defaultOptions.easingFunction\n     * @type {Object}\n     */\n    this.options = {\n      ...defaultOptions,\n      ...this.getOptions(),\n    };\n\n    /**\n     * Last animation frame\n     * @property {Number} lastAnimationFrame\n     * @type {Number}\n     */\n    this.lastAnimationFrame = null;\n    this.lastElements = [];\n\n    this[onSortableSorted] = this[onSortableSorted].bind(this);\n    this[onSortableSort] = this[onSortableSort].bind(this);\n  }\n\n  /**\n   * Attaches plugins event listeners\n   */\n  attach() {\n    this.draggable.on('sortable:sort', this[onSortableSort]);\n    this.draggable.on('sortable:sorted', this[onSortableSorted]);\n  }\n\n  /**\n   * Detaches plugins event listeners\n   */\n  detach() {\n    this.draggable.off('sortable:sort', this[onSortableSort]);\n    this.draggable.off('sortable:sorted', this[onSortableSorted]);\n  }\n\n  /**\n   * Returns options passed through draggable\n   * @return {Object}\n   */\n  getOptions() {\n    return this.draggable.options.sortAnimation || {};\n  }\n\n  /**\n   * Sortable sort handler\n   * @param {SortableSortEvent} sortableEvent\n   * @private\n   */\n  [onSortableSort]({dragEvent}) {\n    const {sourceContainer} = dragEvent;\n    const elements =\n      this.draggable.getDraggableElementsForContainer(sourceContainer);\n    this.lastElements = Array.from(elements).map((el) => {\n      return {\n        domEl: el,\n        offsetTop: el.offsetTop,\n        offsetLeft: el.offsetLeft,\n      };\n    });\n  }\n\n  /**\n   * Sortable sorted handler\n   * @param {SortableSortedEvent} sortableEvent\n   * @private\n   */\n  [onSortableSorted]({oldIndex, newIndex}) {\n    if (oldIndex === newIndex) {\n      return;\n    }\n\n    const effectedElements = [];\n    let start;\n    let end;\n    let num;\n    if (oldIndex > newIndex) {\n      start = newIndex;\n      end = oldIndex - 1;\n      num = 1;\n    } else {\n      start = oldIndex + 1;\n      end = newIndex;\n      num = -1;\n    }\n\n    for (let i = start; i <= end; i++) {\n      const from = this.lastElements[i];\n      const to = this.lastElements[i + num];\n      effectedElements.push({from, to});\n    }\n    cancelAnimationFrame(this.lastAnimationFrame);\n\n    // Can be done in a separate frame\n    this.lastAnimationFrame = requestAnimationFrame(() => {\n      effectedElements.forEach((element) => animate(element, this.options));\n    });\n  }\n}\n\n/**\n * Animates two elements\n * @param {Object} element\n * @param {Object} element.from\n * @param {Object} element.to\n * @param {Object} options\n * @param {Number} options.duration\n * @param {String} options.easingFunction\n * @private\n */\nfunction animate({from, to}, {duration, easingFunction}) {\n  const domEl = from.domEl;\n  const x = from.offsetLeft - to.offsetLeft;\n  const y = from.offsetTop - to.offsetTop;\n\n  domEl.style.pointerEvents = 'none';\n  domEl.style.transform = `translate3d(${x}px, ${y}px, 0)`;\n\n  requestAnimationFrame(() => {\n    domEl.addEventListener('transitionend', resetElementOnTransitionEnd);\n    domEl.style.transition = `transform ${duration}ms ${easingFunction}`;\n    domEl.style.transform = '';\n  });\n}\n\n/**\n * Resets animation style properties after animation has completed\n * @param {Event} event\n * @private\n */\nfunction resetElementOnTransitionEnd(event) {\n  event.target.style.transition = '';\n  event.target.style.pointerEvents = '';\n  event.target.removeEventListener(\n    'transitionend',\n    resetElementOnTransitionEnd,\n  );\n}\n"
  },
  {
    "path": "src/Plugins/SortAnimation/index.js",
    "content": "import SortAnimation, {defaultOptions} from './SortAnimation';\n\nexport default SortAnimation;\nexport {defaultOptions};\n"
  },
  {
    "path": "src/Plugins/SwapAnimation/README.md",
    "content": "## SwapAnimation\n\nThe swap animation plugin currently only works with `Sortable`. It adds a swap animation on `sortable:sorted`,\nand animates both `source` and `over` via `translate3d`. It is currently possible to change the duration and\nthe easing function of the animation.\n\nThis plugin is not included by default, so make sure to import it before using.\n\n### Usage\n\n- NPM:\n\n```js\nimport {Sortable, Plugins} from '@shopify/draggable';\n// Or\nimport Sortable from '@shopify/draggable/build/esm/Sortable/Sortable';\nimport SwapAnimation from '@shopify/draggable/build/esm/Plugins/SwapAnimation';\n\nconst sortable = new Sortable(document.querySelectorAll('ul'), {\n  draggable: 'li',\n  swapAnimation: {\n    duration: 200,\n    easingFunction: 'ease-in-out',\n    horizontal: true,\n  },\n  plugins: [Plugins.SwapAnimation], // Or [SwapAnimation]\n});\n```\n\n- Browser (as a module):\n\n```html\n<script type=\"module\">\n  import Sortable from 'https://cdn.jsdelivr.net/npm/@shopify/draggable/build/esm/Sortable/Sortable.mjs';\n  import SwapAnimation from 'https://cdn.jsdelivr.net/npm/@shopify/draggable/build/esm/Plugins/SwapAnimation.mjs';\n\n  const sortable = new Sortable(document.querySelectorAll('ul'), {\n    draggable: 'li',\n    swapAnimation: {\n      duration: 200,\n      easingFunction: 'ease-in-out',\n      horizontal: true,\n    },\n    plugins: [SwapAnimation],\n  });\n</script>\n```\n\n- Browser (Standalone):\n\n```html\n<script src=\"https://cdn.jsdelivr.net/npm/@shopify/draggable/build/umd/index.min.js\"></script>\n<script>\n  const sortable = new Draggable.Sortable(document.querySelectorAll('ul'), {\n    draggable: 'li',\n    swapAnimation: {\n      duration: 200,\n      easingFunction: 'ease-in-out',\n      horizontal: true,\n    },\n    plugins: [Draggable.Plugins.SwapAnimation],\n  });\n</script>\n```\n\n### API\n\n**`new SwapAnimation(draggable: Draggable): SwapAnimation`**  \nTo create a swap animation plugin instance.\n\n### Options\n\n**`duration {Integer}`**  \nThe duration option allows you to specify the animation during for a single swap. Default: `150`\n\n**`easingFunction {String}`**  \nThe easing option allows you to specify an animation easing function. Default: `'ease-in-out'`\n\n**`horizontal {Boolean}`**\nThe horizontal option allows you to set the elements to animate horizontally. Default: `false`\n\n### Examples\n\n```js\nimport {Sortable, Plugins} from '@shopify/draggable';\n\nconst sortable = new Sortable(document.querySelectorAll('ul'), {\n  draggable: 'li',\n  swapAnimation: {\n    duration: 200,\n    easingFunction: 'ease-in-out',\n    horizontal: true,\n  },\n  plugins: [Plugins.SwapAnimation],\n});\n```\n\n### Plans\n\n- Add support for staggering animations\n- Find cross-container animation solution\n- Add support for `Swappable` and `Droppable`\n\n### Caveats\n\n- Only works within same container\n- Animations don't stagger\n- Only works with `Sortable`\n"
  },
  {
    "path": "src/Plugins/SwapAnimation/SwapAnimation.ts",
    "content": "import AbstractPlugin from 'shared/AbstractPlugin';\nimport {FixMeAny} from 'shared/types';\nimport {AutoBind} from 'shared/utils';\n\ninterface Options {\n  duration: number;\n  easingFunction: string;\n  horizontal: boolean;\n}\n\n/**\n * SwapAnimation default options\n * @property {Object} defaultOptions\n * @property {Number} defaultOptions.duration\n * @property {String} defaultOptions.easingFunction\n * @property {Boolean} defaultOptions.horizontal\n * @type {Object}\n */\nexport const defaultOptions: Options = {\n  duration: 150,\n  easingFunction: 'ease-in-out',\n  horizontal: false,\n};\n\n/**\n * SwapAnimation plugin adds swap animations for sortable\n * @class SwapAnimation\n * @module SwapAnimation\n * @extends AbstractPlugin\n */\nexport default class SwapAnimation extends AbstractPlugin {\n  private options: Options;\n  private lastAnimationFrame: number | null;\n  /**\n   * SwapAnimation constructor.\n   * @constructs SwapAnimation\n   * @param {Draggable} draggable - Draggable instance\n   */\n  constructor(draggable: FixMeAny) {\n    super(draggable);\n\n    /**\n     * SwapAnimation options\n     * @property {Object} options\n     * @property {Number} defaultOptions.duration\n     * @property {String} defaultOptions.easingFunction\n     * @type {Object}\n     */\n    this.options = {\n      ...defaultOptions,\n      ...this.getOptions(),\n    };\n\n    /**\n     * Last animation frame\n     * @property {Number} lastAnimationFrame\n     * @type {Number}\n     */\n    this.lastAnimationFrame = null;\n  }\n\n  /**\n   * Attaches plugins event listeners\n   */\n  attach() {\n    this.draggable.on('sortable:sorted', this.onSortableSorted);\n  }\n\n  /**\n   * Detaches plugins event listeners\n   */\n  detach() {\n    this.draggable.off('sortable:sorted', this.onSortableSorted);\n  }\n\n  /**\n   * Returns options passed through draggable\n   * @return {Object}\n   */\n  getOptions() {\n    return this.draggable.options.swapAnimation || {};\n  }\n\n  /**\n   * Sortable sorted handler\n   * @param {SortableSortedEvent} sortableEvent\n   * @private\n   */\n  @AutoBind\n  onSortableSorted({oldIndex, newIndex, dragEvent}: FixMeAny) {\n    const {source, over} = dragEvent;\n\n    if (this.lastAnimationFrame) {\n      cancelAnimationFrame(this.lastAnimationFrame);\n    }\n\n    // Can be done in a separate frame\n    this.lastAnimationFrame = requestAnimationFrame(() => {\n      if (oldIndex >= newIndex) {\n        animate(source, over, this.options);\n      } else {\n        animate(over, source, this.options);\n      }\n    });\n  }\n}\n\n/**\n * Animates two elements\n * @param {HTMLElement} from\n * @param {HTMLElement} to\n * @param {Object} options\n * @param {Number} options.duration\n * @param {String} options.easingFunction\n * @param {String} options.horizontal\n * @private\n */\nfunction animate(\n  from: HTMLElement,\n  to: HTMLElement,\n  {duration, easingFunction, horizontal}: Options,\n) {\n  for (const element of [from, to]) {\n    element.style.pointerEvents = 'none';\n  }\n\n  if (horizontal) {\n    const width = from.offsetWidth;\n    from.style.transform = `translate3d(${width}px, 0, 0)`;\n    to.style.transform = `translate3d(-${width}px, 0, 0)`;\n  } else {\n    const height = from.offsetHeight;\n    from.style.transform = `translate3d(0, ${height}px, 0)`;\n    to.style.transform = `translate3d(0, -${height}px, 0)`;\n  }\n\n  requestAnimationFrame(() => {\n    for (const element of [from, to]) {\n      element.addEventListener('transitionend', resetElementOnTransitionEnd);\n      element.style.transition = `transform ${duration}ms ${easingFunction}`;\n      element.style.transform = '';\n    }\n  });\n}\n\n/**\n * Resets animation style properties after animation has completed\n * @param {Event} event\n * @private\n */\nfunction resetElementOnTransitionEnd(event: Event) {\n  if (event.target == null || !isHTMLElement(event.target)) {\n    return;\n  }\n\n  event.target.style.transition = '';\n  event.target.style.pointerEvents = '';\n  event.target.removeEventListener(\n    'transitionend',\n    resetElementOnTransitionEnd,\n  );\n}\n\nfunction isHTMLElement(eventTarget: EventTarget): eventTarget is HTMLElement {\n  return Boolean('style' in eventTarget);\n}\n"
  },
  {
    "path": "src/Plugins/SwapAnimation/index.ts",
    "content": "import SwapAnimation, {defaultOptions} from './SwapAnimation';\n\nexport default SwapAnimation;\nexport {defaultOptions};\n"
  },
  {
    "path": "src/Plugins/index.js",
    "content": "export {default as Collidable} from './Collidable';\nexport {\n  default as ResizeMirror,\n  defaultOptions as defaultResizeMirrorOptions,\n} from './ResizeMirror';\nexport {default as Snappable} from './Snappable';\nexport {\n  default as SwapAnimation,\n  defaultOptions as defaultSwapAnimationOptions,\n} from './SwapAnimation';\nexport {\n  default as SortAnimation,\n  defaultOptions as defaultSortAnimationOptions,\n} from './SortAnimation';\n"
  },
  {
    "path": "src/Sortable/README.md",
    "content": "## Sortable\n\nSortable is built on top of Draggable and allows you to reorder elements. It maintains the order internally and fires\nfour events on top of the draggable events: `sortable:start`, `sortable:sort`, `sortable:sorted` and `sortable:stop`.\n\nMake sure to nest draggable elements as immediate children elements to their corresponding containers, this is a requirement for `Sortable`.\n\n### Usage\n\n- NPM:\n\n```js\nimport {Sortable} from '@shopify/draggable';\n// Or\nimport Sortable from '@shopify/draggable/build/esm/Sortable/Sortable';\n\nconst sortable = new Sortable(document.querySelectorAll('ul'), {\n  draggable: 'li',\n});\n```\n\n- Browser (as a module):\n\n```html\n<script type=\"module\">\n  import Sortable from 'https://cdn.jsdelivr.net/npm/@shopify/draggable/build/esm/Sortable/Sortable.mjs';\n\n  const sortable = new Sortable(document.querySelectorAll('ul'), {\n    draggable: 'li',\n  });\n</script>\n```\n\n- Browser (Standalone):\n\n```html\n<script src=\"https://cdn.jsdelivr.net/npm/@shopify/draggable/build/umd/index.min.js\"></script>\n<script>\n  const sortable = new Draggable.Sortable(document.querySelectorAll('ul'), {\n    draggable: 'li',\n  });\n</script>\n```\n\n### API\n\nCheck out [Draggables API](../Draggable#api) for the base API\n\n### Options\n\nCheck out [Draggables options](../Draggable#options) for the base options\n\n### Events\n\nCheck out [Draggables events](../Draggable#events) for base events\n\n| Name                                | Description                                       | Cancelable | Cancelable action   |\n| ----------------------------------- | ------------------------------------------------- | ---------- | ------------------- |\n| [`sortable:start`][sortablestart]   | Gets fired when drag action begins                | true       | Prevents drag start |\n| [`sortable:sort`][sortablesort]     | Gets fired before sorting                         | true       | Prevents sorting    |\n| [`sortable:sorted`][sortablesorted] | Gets fired when the source gets sorted in the DOM | false      | -                   |\n| [`sortable:stop`][sortablestop]     | Gets fired when dragging over other draggable     | false      | -                   |\n\n[sortablestart]: SortableEvent#sortablestartevent\n[sortablesort]: SortableEvent#sortablesortevent\n[sortablesorted]: SortableEvent#sortablesortedevent\n[sortablestop]: SortableEvent#sortablestopevent\n\n### Classes\n\nCheck out [Draggables class identifiers](../Draggable#classes)\n\n### Example\n\nThis sample code will make list items sortable:\n\n```js\nimport {Sortable} from '@shopify/draggable';\n\nconst sortable = new Sortable(document.querySelectorAll('ul'), {\n  draggable: 'li',\n});\n\nsortable.on('sortable:start', () => console.log('sortable:start'));\nsortable.on('sortable:sort', () => console.log('sortable:sort'));\nsortable.on('sortable:sorted', () => console.log('sortable:sorted'));\nsortable.on('sortable:stop', () => console.log('sortable:stop'));\n```\n\n### Plans\n\n- Add `copy` option, which will allow draggable elements to be copied when dropped in a different container\n- Add `removeOnSpill` option, which will allow draggable elements to be removed from the DOM when dropped outside a container\n\n### Caveats\n\n- Needs draggable elements to be immediate children of draggable containers.\n- Currently just appends draggable elements in different containers\n"
  },
  {
    "path": "src/Sortable/Sortable.js",
    "content": "import Draggable from '../Draggable';\n\nimport {\n  SortableStartEvent,\n  SortableSortEvent,\n  SortableSortedEvent,\n  SortableStopEvent,\n} from './SortableEvent';\n\nconst onDragStart = Symbol('onDragStart');\nconst onDragOverContainer = Symbol('onDragOverContainer');\nconst onDragOver = Symbol('onDragOver');\nconst onDragStop = Symbol('onDragStop');\n\n/**\n * Returns announcement message when a Draggable element has been sorted with another Draggable element\n * or moved into a new container\n * @param {SortableSortedEvent} sortableEvent\n * @return {String}\n */\nfunction onSortableSortedDefaultAnnouncement({dragEvent}) {\n  const sourceText =\n    dragEvent.source.textContent.trim() ||\n    dragEvent.source.id ||\n    'sortable element';\n\n  if (dragEvent.over) {\n    const overText =\n      dragEvent.over.textContent.trim() ||\n      dragEvent.over.id ||\n      'sortable element';\n    const isFollowing =\n      dragEvent.source.compareDocumentPosition(dragEvent.over) &\n      Node.DOCUMENT_POSITION_FOLLOWING;\n\n    if (isFollowing) {\n      return `Placed ${sourceText} after ${overText}`;\n    } else {\n      return `Placed ${sourceText} before ${overText}`;\n    }\n  } else {\n    // need to figure out how to compute container name\n    return `Placed ${sourceText} into a different container`;\n  }\n}\n\n/**\n * @const {Object} defaultAnnouncements\n * @const {Function} defaultAnnouncements['sortable:sorted']\n */\nconst defaultAnnouncements = {\n  'sortable:sorted': onSortableSortedDefaultAnnouncement,\n};\n\n/**\n * Sortable is built on top of Draggable and allows sorting of draggable elements. Sortable will keep\n * track of the original index and emits the new index as you drag over draggable elements.\n * @class Sortable\n * @module Sortable\n * @extends Draggable\n */\nexport default class Sortable extends Draggable {\n  /**\n   * Sortable constructor.\n   * @constructs Sortable\n   * @param {HTMLElement[]|NodeList|HTMLElement} containers - Sortable containers\n   * @param {Object} options - Options for Sortable\n   */\n  constructor(containers = [], options = {}) {\n    super(containers, {\n      ...options,\n      announcements: {\n        ...defaultAnnouncements,\n        ...(options.announcements || {}),\n      },\n    });\n\n    /**\n     * start index of source on drag start\n     * @property startIndex\n     * @type {Number}\n     */\n    this.startIndex = null;\n\n    /**\n     * start container on drag start\n     * @property startContainer\n     * @type {HTMLElement}\n     * @default null\n     */\n    this.startContainer = null;\n\n    this[onDragStart] = this[onDragStart].bind(this);\n    this[onDragOverContainer] = this[onDragOverContainer].bind(this);\n    this[onDragOver] = this[onDragOver].bind(this);\n    this[onDragStop] = this[onDragStop].bind(this);\n\n    this.on('drag:start', this[onDragStart])\n      .on('drag:over:container', this[onDragOverContainer])\n      .on('drag:over', this[onDragOver])\n      .on('drag:stop', this[onDragStop]);\n  }\n\n  /**\n   * Destroys Sortable instance.\n   */\n  destroy() {\n    super.destroy();\n\n    this.off('drag:start', this[onDragStart])\n      .off('drag:over:container', this[onDragOverContainer])\n      .off('drag:over', this[onDragOver])\n      .off('drag:stop', this[onDragStop]);\n  }\n\n  /**\n   * Returns true index of element within its container during drag operation, i.e. excluding mirror and original source\n   * @param {HTMLElement} element - An element\n   * @return {Number}\n   */\n  index(element) {\n    return this.getSortableElementsForContainer(element.parentNode).indexOf(\n      element,\n    );\n  }\n\n  /**\n   * Returns sortable elements for a given container, excluding the mirror and\n   * original source element if present\n   * @param {HTMLElement} container\n   * @return {HTMLElement[]}\n   */\n  getSortableElementsForContainer(container) {\n    const allSortableElements = container.querySelectorAll(\n      this.options.draggable,\n    );\n\n    return [...allSortableElements].filter((childElement) => {\n      return (\n        childElement !== this.originalSource &&\n        childElement !== this.mirror &&\n        childElement.parentNode === container\n      );\n    });\n  }\n\n  /**\n   * Drag start handler\n   * @private\n   * @param {DragStartEvent} event - Drag start event\n   */\n  [onDragStart](event) {\n    this.startContainer = event.source.parentNode;\n    this.startIndex = this.index(event.source);\n\n    const sortableStartEvent = new SortableStartEvent({\n      dragEvent: event,\n      startIndex: this.startIndex,\n      startContainer: this.startContainer,\n    });\n\n    this.trigger(sortableStartEvent);\n\n    if (sortableStartEvent.canceled()) {\n      event.cancel();\n    }\n  }\n\n  /**\n   * Drag over container handler\n   * @private\n   * @param {DragOverContainerEvent} event - Drag over container event\n   */\n  [onDragOverContainer](event) {\n    if (event.canceled()) {\n      return;\n    }\n\n    const {source, over, overContainer} = event;\n    const oldIndex = this.index(source);\n\n    const sortableSortEvent = new SortableSortEvent({\n      dragEvent: event,\n      currentIndex: oldIndex,\n      source,\n      over,\n    });\n\n    this.trigger(sortableSortEvent);\n\n    if (sortableSortEvent.canceled()) {\n      return;\n    }\n\n    const children = this.getSortableElementsForContainer(overContainer);\n    const moves = move({source, over, overContainer, children});\n\n    if (!moves) {\n      return;\n    }\n\n    const {oldContainer, newContainer} = moves;\n    const newIndex = this.index(event.source);\n\n    const sortableSortedEvent = new SortableSortedEvent({\n      dragEvent: event,\n      oldIndex,\n      newIndex,\n      oldContainer,\n      newContainer,\n    });\n\n    this.trigger(sortableSortedEvent);\n  }\n\n  /**\n   * Drag over handler\n   * @private\n   * @param {DragOverEvent} event - Drag over event\n   */\n  [onDragOver](event) {\n    if (event.over === event.originalSource || event.over === event.source) {\n      return;\n    }\n\n    const {source, over, overContainer} = event;\n    const oldIndex = this.index(source);\n\n    const sortableSortEvent = new SortableSortEvent({\n      dragEvent: event,\n      currentIndex: oldIndex,\n      source,\n      over,\n    });\n\n    this.trigger(sortableSortEvent);\n\n    if (sortableSortEvent.canceled()) {\n      return;\n    }\n\n    const children = this.getDraggableElementsForContainer(overContainer);\n    const moves = move({source, over, overContainer, children});\n\n    if (!moves) {\n      return;\n    }\n\n    const {oldContainer, newContainer} = moves;\n    const newIndex = this.index(source);\n\n    const sortableSortedEvent = new SortableSortedEvent({\n      dragEvent: event,\n      oldIndex,\n      newIndex,\n      oldContainer,\n      newContainer,\n    });\n\n    this.trigger(sortableSortedEvent);\n  }\n\n  /**\n   * Drag stop handler\n   * @private\n   * @param {DragStopEvent} event - Drag stop event\n   */\n  [onDragStop](event) {\n    const sortableStopEvent = new SortableStopEvent({\n      dragEvent: event,\n      oldIndex: this.startIndex,\n      newIndex: this.index(event.source),\n      oldContainer: this.startContainer,\n      newContainer: event.source.parentNode,\n    });\n\n    this.trigger(sortableStopEvent);\n\n    this.startIndex = null;\n    this.startContainer = null;\n  }\n}\n\nfunction index(element) {\n  return Array.prototype.indexOf.call(element.parentNode.children, element);\n}\n\nfunction move({source, over, overContainer, children}) {\n  const emptyOverContainer = !children.length;\n  const differentContainer = source.parentNode !== overContainer;\n  const sameContainer = over && source.parentNode === over.parentNode;\n\n  if (emptyOverContainer) {\n    return moveInsideEmptyContainer(source, overContainer);\n  } else if (sameContainer) {\n    return moveWithinContainer(source, over);\n  } else if (differentContainer) {\n    return moveOutsideContainer(source, over, overContainer);\n  } else {\n    return null;\n  }\n}\n\nfunction moveInsideEmptyContainer(source, overContainer) {\n  const oldContainer = source.parentNode;\n\n  overContainer.appendChild(source);\n\n  return {oldContainer, newContainer: overContainer};\n}\n\nfunction moveWithinContainer(source, over) {\n  const oldIndex = index(source);\n  const newIndex = index(over);\n\n  if (oldIndex < newIndex) {\n    source.parentNode.insertBefore(source, over.nextElementSibling);\n  } else {\n    source.parentNode.insertBefore(source, over);\n  }\n\n  return {oldContainer: source.parentNode, newContainer: source.parentNode};\n}\n\nfunction moveOutsideContainer(source, over, overContainer) {\n  const oldContainer = source.parentNode;\n\n  if (over) {\n    over.parentNode.insertBefore(source, over);\n  } else {\n    // need to figure out proper position\n    overContainer.appendChild(source);\n  }\n\n  return {oldContainer, newContainer: source.parentNode};\n}\n"
  },
  {
    "path": "src/Sortable/SortableEvent/README.md",
    "content": "## SortableEvent\n\nThe base sortable event for all Sortable events that `Sortable` emits.\n\n| | |\n| --------------------- | ---------------------------------------------------------- |\n| **Interface**         | `SortableEvent`                                            |\n| **Cancelable**        | false                                                      |\n| **Cancel action**     | -                                                          |\n| **type**              | `sortable`                                                 |\n\n### API\n\n**`sortableEvent.dragEvent: DragEvent`**  \nRead-only property for the original drag event that triggered the sortable event.\n\n## SortableStartEvent\n\n`SortableStartEvent` gets triggered by `Sortable` when on drag start.\n\n| | |\n| --------------------- | ---------------------------------------------------------- |\n| **Specification**     | `SortableEvent`                                            |\n| **Interface**         | `SortableStartEvent`                                       |\n| **Cancelable**        | true                                                       |\n| **Cancel action**     | Prevents drag start                                        |\n| **type**              | `sortable:start`                                           |\n\n### API\n\n**`sortableEvent.startIndex: Number`**  \nRead-only property for the start index of the current draggable source.\n\n**`sortableEvent.startContainer: HTMLElement`**  \nRead-only property for the start container of the current draggable source.\n\n## SortableSortEvent\n\n`SortableSortEvent` gets triggered by `Sortable` before sorting with another draggable.\n\n| | |\n| --------------------- | ---------------------------------------------------------- |\n| **Specification**     | `SortableEvent`                                            |\n| **Interface**         | `SortableSortEvent`                                        |\n| **Cancelable**        | true                                                       |\n| **Cancel action**     | Prevents sorting                                           |\n| **type**              | `sortable:sort`                                            |\n\n### API\n\n**`sortableEvent.currentIndex: Number`**  \nRead-only property for the index of the current draggable source.\n\n**`sortableEvent.over: HTMLElement`**  \nRead-only property for the draggable source you are hovering over.\n\n**`sortableEvent.overContainer: HTMLElement`**  \nRead-only property for the container of the draggable source you are hovering over.\n\n## SortableSortedEvent\n\n`SortableSortedEvent` gets triggered by `Sortable` when sorted with another draggable.\n\n| | |\n| --------------------- | ---------------------------------------------------------- |\n| **Specification**     | `SortableEvent`                                            |\n| **Interface**         | `SortableSortedEvent`                                      |\n| **Cancelable**        | false                                                      |\n| **Cancel action**     | -                                                          |\n| **type**              | `sortable:sorted`                                          |\n\n### API\n\n**`sortableEvent.oldIndex: Number`**  \nRead-only property for the old index of the current draggable source.\n\n**`sortableEvent.newIndex: Number`**  \nRead-only property for the new index of the current draggable source.\n\n**`sortableEvent.oldContainer: HTMLElement`**  \nRead-only property for the old container of the current draggable source.\n\n**`sortableEvent.newContainer: HTMLElement`**  \nRead-only property for the new container of the current draggable source.\n\n## SortableStopEvent\n\n`SortableStopEvent` gets triggered by `Sortable` on drag stop.\n\n| | |\n| --------------------- | ---------------------------------------------------------- |\n| **Specification**     | `SortableEvent`                                            |\n| **Interface**         | `SortableStopEvent`                                        |\n| **Cancelable**        | false                                                      |\n| **Cancel action**     | -                                                          |\n| **type**              | `sortable:stop`                                            |\n\n### API\n\n**`sortableEvent.oldIndex: Number`**  \nRead-only property for the old index of the current draggable source.\n\n**`sortableEvent.newIndex: Number`**  \nRead-only property for the new index of the current draggable source.\n\n**`sortableEvent.oldContainer: HTMLElement`**  \nRead-only property for the old container of the current draggable source.\n\n**`sortableEvent.newContainer: HTMLElement`**  \nRead-only property for the new container of the current draggable source.\n"
  },
  {
    "path": "src/Sortable/SortableEvent/SortableEvent.ts",
    "content": "import AbstractEvent from 'shared/AbstractEvent';\n\nimport {\n  DragEvent,\n  DragEventData,\n  DragOverEvent,\n  DragOutEvent,\n  DragOverContainerEvent,\n  DragOutContainerEvent,\n} from '../../Draggable/DragEvent';\n\ninterface SortableEventData {\n  dragEvent: DragEvent<DragEventData>;\n}\n\n/**\n * Base sortable event\n * @class SortableEvent\n * @module SortableEvent\n * @extends AbstractEvent\n */\nexport class SortableEvent<\n  T extends SortableEventData,\n> extends AbstractEvent<SortableEventData> {\n  static type = 'sortable';\n\n  /**\n   * SortableEvent constructor.\n   * @constructs SortableEvent\n   * @param {SortableEventData} data - Event data\n   */\n  constructor(public data: T) {\n    super(data);\n  }\n\n  /**\n   * Original drag event that triggered this sortable event\n   * @property dragEvent\n   * @type {DragEvent}\n   * @readonly\n   */\n  get dragEvent() {\n    return this.data.dragEvent;\n  }\n}\n\ninterface SortableStartEventData extends SortableEventData {\n  startIndex: number;\n  startContainer: HTMLElement;\n}\n\n/**\n * Sortable start event\n * @class SortableStartEvent\n * @module SortableStartEvent\n * @extends SortableEvent\n */\nexport class SortableStartEvent extends SortableEvent<SortableStartEventData> {\n  static type = 'sortable:start';\n  static cancelable = true;\n\n  /**\n   * Start index of source on sortable start\n   * @property startIndex\n   * @type {Number}\n   * @readonly\n   */\n  get startIndex() {\n    return this.data.startIndex;\n  }\n\n  /**\n   * Start container on sortable start\n   * @property startContainer\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get startContainer() {\n    return this.data.startContainer;\n  }\n}\n\ninterface SortableSortEventData extends SortableEventData {\n  dragEvent:\n    | DragOverEvent\n    | DragOutEvent\n    | DragOverContainerEvent\n    | DragOutContainerEvent;\n  currentIndex: number;\n  over: HTMLElement;\n}\n\n/**\n * Sortable sort event\n * @class SortableSortEvent\n * @module SortableSortEvent\n * @extends SortableEvent\n */\nexport class SortableSortEvent extends SortableEvent<SortableSortEventData> {\n  static type = 'sortable:sort';\n  static cancelable = true;\n\n  /**\n   * Index of current draggable element\n   * @property currentIndex\n   * @type {Number}\n   * @readonly\n   */\n  get currentIndex() {\n    return this.data.currentIndex;\n  }\n\n  /**\n   * Draggable element you are hovering over\n   * @property over\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get over() {\n    return this.data.over;\n  }\n\n  /**\n   * Draggable container element you are hovering over\n   * @property overContainer\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get overContainer() {\n    return this.data.dragEvent.overContainer;\n  }\n}\n\ninterface SortableSortedEventData extends SortableEventData {\n  oldIndex: number;\n  newIndex: number;\n  oldContainer: HTMLElement;\n  newContainer: HTMLElement;\n}\n\n/**\n * Sortable sorted event\n * @class SortableSortedEvent\n * @module SortableSortedEvent\n * @extends SortableEvent\n */\nexport class SortableSortedEvent extends SortableEvent<SortableSortedEventData> {\n  static type = 'sortable:sorted';\n\n  /**\n   * Index of last sorted event\n   * @property oldIndex\n   * @type {Number}\n   * @readonly\n   */\n  get oldIndex() {\n    return this.data.oldIndex;\n  }\n\n  /**\n   * New index of this sorted event\n   * @property newIndex\n   * @type {Number}\n   * @readonly\n   */\n  get newIndex() {\n    return this.data.newIndex;\n  }\n\n  /**\n   * Old container of draggable element\n   * @property oldContainer\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get oldContainer() {\n    return this.data.oldContainer;\n  }\n\n  /**\n   * New container of draggable element\n   * @property newContainer\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get newContainer() {\n    return this.data.newContainer;\n  }\n}\n\ninterface SortableStopEventData extends SortableEventData {\n  oldIndex: number;\n  newIndex: number;\n  oldContainer: HTMLElement;\n  newContainer: HTMLElement;\n}\n\n/**\n * Sortable stop event\n * @class SortableStopEvent\n * @module SortableStopEvent\n * @extends SortableEvent\n */\nexport class SortableStopEvent extends SortableEvent<SortableStopEventData> {\n  static type = 'sortable:stop';\n\n  /**\n   * Original index on sortable start\n   * @property oldIndex\n   * @type {Number}\n   * @readonly\n   */\n  get oldIndex() {\n    return this.data.oldIndex;\n  }\n\n  /**\n   * New index of draggable element\n   * @property newIndex\n   * @type {Number}\n   * @readonly\n   */\n  get newIndex() {\n    return this.data.newIndex;\n  }\n\n  /**\n   * Original container of draggable element\n   * @property oldContainer\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get oldContainer() {\n    return this.data.oldContainer;\n  }\n\n  /**\n   * New container of draggable element\n   * @property newContainer\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get newContainer() {\n    return this.data.newContainer;\n  }\n}\n"
  },
  {
    "path": "src/Sortable/SortableEvent/index.ts",
    "content": "export * from './SortableEvent';\n"
  },
  {
    "path": "src/Sortable/index.js",
    "content": "import Sortable from './Sortable';\n\nexport default Sortable;\nexport * from './SortableEvent';\n"
  },
  {
    "path": "src/Sortable/tests/Sortable.test.js",
    "content": "import {\n  createSandbox,\n  clickMouse,\n  moveMouse,\n  releaseMouse,\n  waitForDragDelay,\n  DRAG_DELAY,\n} from 'helper';\n\nimport Sortable from '..';\n\nconst sampleMarkup = `\n  <ul>\n    <li>First item</li>\n    <li>Second item</li>\n    <li>Third item</li>\n    <li>Forth item</li>\n  </ul>\n  <ul>\n    <li>Fith item</li>\n    <li>Sixth item</li>\n    <li>Seventh item</li>\n    <li>Eighth item</li>\n  </ul>\n`;\n\ndescribe('Sortable', () => {\n  let sandbox;\n  let containers;\n  let draggableElements;\n  let sortable;\n\n  let firstContainer;\n  let secondContainer;\n  let firstItem;\n  let secondItem;\n  let thirdItem;\n  let forthItem;\n  let fifthItem;\n  let sixthItem;\n  let seventhItem;\n  let eighthItem;\n\n  beforeEach(() => {\n    sandbox = createSandbox(sampleMarkup);\n    containers = sandbox.querySelectorAll('ul');\n    draggableElements = sandbox.querySelectorAll('li');\n    sortable = new Sortable(containers, {\n      draggable: 'li',\n      delay: DRAG_DELAY,\n    });\n\n    firstContainer = containers[0];\n    secondContainer = containers[1];\n    firstItem = draggableElements[0];\n    secondItem = draggableElements[1];\n    thirdItem = draggableElements[2];\n    forthItem = draggableElements[3];\n    fifthItem = draggableElements[4];\n    sixthItem = draggableElements[5];\n    seventhItem = draggableElements[6];\n    eighthItem = draggableElements[7];\n  });\n\n  afterEach(() => {\n    sortable.destroy();\n    sandbox.remove();\n  });\n\n  it('triggers events', () => {\n    const sortableStart = jest.fn();\n    const sortableSort = jest.fn();\n    const sortableSorted = jest.fn();\n    const sortableStop = jest.fn();\n\n    sortable.on('sortable:start', sortableStart);\n    sortable.on('sortable:sort', sortableSort);\n    sortable.on('sortable:sorted', sortableSorted);\n    sortable.on('sortable:stop', sortableStop);\n\n    clickMouse(firstItem);\n    waitForDragDelay();\n    moveMouse(secondItem);\n    releaseMouse(sortable.source);\n\n    expect(sortableStart).toHaveBeenCalled();\n\n    expect(sortableSort).toHaveBeenCalled();\n\n    expect(sortableSorted).toHaveBeenCalled();\n\n    expect(sortableStop).toHaveBeenCalled();\n  });\n\n  it('prevents drag when canceling sortable start event', () => {\n    sortable.on('sortable:start', (sortableEvent) => {\n      sortableEvent.cancel();\n    });\n\n    clickMouse(firstItem);\n    waitForDragDelay();\n    moveMouse(secondItem);\n\n    expect(sortable.isDragging()).toBe(false);\n\n    releaseMouse(sortable.source);\n  });\n\n  it('sorts two first elements', () => {\n    draggableElements = sandbox.querySelectorAll('li');\n\n    expect(draggableElements[0]).toBe(firstItem);\n\n    expect(draggableElements[1]).toBe(secondItem);\n\n    clickMouse(firstItem);\n    waitForDragDelay();\n    moveMouse(secondItem);\n    releaseMouse(sortable.source);\n\n    draggableElements = sandbox.querySelectorAll('li');\n\n    expect(draggableElements[0]).toBe(secondItem);\n\n    expect(draggableElements[1]).toBe(firstItem);\n  });\n\n  it('sorts elements as you drag within a single container', () => {\n    draggableElements = sortable.getDraggableElementsForContainer(\n      containers[0],\n    );\n    expect(draggableElements).toHaveOrder([\n      firstItem,\n      secondItem,\n      thirdItem,\n      forthItem,\n    ]);\n\n    clickMouse(firstItem);\n    waitForDragDelay();\n    moveMouse(secondItem);\n\n    draggableElements =\n      sortable.getDraggableElementsForContainer(firstContainer);\n    expect(draggableElements).toHaveOrder([\n      secondItem,\n      // original firstItem\n      sortable.source,\n      thirdItem,\n      forthItem,\n    ]);\n\n    moveMouse(thirdItem);\n\n    draggableElements =\n      sortable.getDraggableElementsForContainer(firstContainer);\n    expect(draggableElements).toHaveOrder([\n      secondItem,\n      thirdItem,\n      // original firstItem\n      sortable.source,\n      forthItem,\n    ]);\n\n    moveMouse(forthItem);\n\n    draggableElements =\n      sortable.getDraggableElementsForContainer(firstContainer);\n    expect(draggableElements).toHaveOrder([\n      secondItem,\n      thirdItem,\n      forthItem,\n      // original firstItem\n      sortable.source,\n    ]);\n\n    releaseMouse(sortable.source);\n\n    draggableElements =\n      sortable.getDraggableElementsForContainer(firstContainer);\n    expect(draggableElements).toHaveOrder([\n      secondItem,\n      thirdItem,\n      forthItem,\n      firstItem,\n    ]);\n  });\n\n  it('sorts elements as you drag between multiple containers', () => {\n    draggableElements =\n      sortable.getDraggableElementsForContainer(firstContainer);\n    expect(draggableElements).toHaveOrder([\n      firstItem,\n      secondItem,\n      thirdItem,\n      forthItem,\n    ]);\n\n    draggableElements =\n      sortable.getDraggableElementsForContainer(secondContainer);\n    expect(draggableElements).toHaveOrder([\n      fifthItem,\n      sixthItem,\n      seventhItem,\n      eighthItem,\n    ]);\n\n    clickMouse(firstItem);\n    waitForDragDelay();\n    moveMouse(fifthItem);\n\n    draggableElements =\n      sortable.getDraggableElementsForContainer(firstContainer);\n    expect(draggableElements).toHaveOrder([secondItem, thirdItem, forthItem]);\n\n    draggableElements =\n      sortable.getDraggableElementsForContainer(secondContainer);\n    expect(draggableElements).toHaveOrder([\n      // original firstItem\n      sortable.source,\n      fifthItem,\n      sixthItem,\n      seventhItem,\n      eighthItem,\n    ]);\n\n    moveMouse(eighthItem);\n\n    draggableElements =\n      sortable.getDraggableElementsForContainer(firstContainer);\n    expect(draggableElements).toHaveOrder([secondItem, thirdItem, forthItem]);\n\n    draggableElements =\n      sortable.getDraggableElementsForContainer(secondContainer);\n    expect(draggableElements).toHaveOrder([\n      fifthItem,\n      sixthItem,\n      seventhItem,\n      eighthItem,\n      // original firstItem\n      sortable.source,\n    ]);\n\n    releaseMouse(sortable.source);\n\n    draggableElements =\n      sortable.getDraggableElementsForContainer(firstContainer);\n    expect(draggableElements).toHaveOrder([secondItem, thirdItem, forthItem]);\n\n    draggableElements =\n      sortable.getDraggableElementsForContainer(secondContainer);\n    expect(draggableElements).toHaveOrder([\n      fifthItem,\n      sixthItem,\n      seventhItem,\n      eighthItem,\n      firstItem,\n    ]);\n  });\n\n  it('prevents sorting when sortable:sort event gets canceled', () => {\n    sortable.on('sortable:sort', (sortableEvent) => {\n      sortableEvent.cancel();\n    });\n\n    clickMouse(firstItem);\n    waitForDragDelay();\n    moveMouse(secondItem);\n\n    draggableElements =\n      sortable.getDraggableElementsForContainer(firstContainer);\n    expect(draggableElements).toHaveOrder([\n      sortable.source,\n      secondItem,\n      thirdItem,\n      forthItem,\n    ]);\n\n    releaseMouse(sortable.source);\n\n    draggableElements =\n      sortable.getDraggableElementsForContainer(firstContainer);\n    expect(draggableElements).toHaveOrder([\n      firstItem,\n      secondItem,\n      thirdItem,\n      forthItem,\n    ]);\n  });\n\n  it('sorts elements into empty container', () => {\n    [fifthItem, sixthItem, seventhItem, eighthItem].forEach((item) => {\n      clickMouse(item);\n      waitForDragDelay();\n      moveMouse(firstItem);\n      releaseMouse(sortable.source);\n    });\n\n    draggableElements =\n      sortable.getDraggableElementsForContainer(firstContainer);\n    expect(draggableElements).toHaveOrder([\n      fifthItem,\n      sixthItem,\n      seventhItem,\n      eighthItem,\n      firstItem,\n      secondItem,\n      thirdItem,\n      forthItem,\n    ]);\n\n    draggableElements =\n      sortable.getDraggableElementsForContainer(secondContainer);\n    expect(draggableElements).toHaveOrder([]);\n\n    clickMouse(firstItem);\n    waitForDragDelay();\n    moveMouse(secondContainer);\n\n    draggableElements =\n      sortable.getDraggableElementsForContainer(firstContainer);\n    expect(draggableElements).toHaveOrder([\n      fifthItem,\n      sixthItem,\n      seventhItem,\n      eighthItem,\n      secondItem,\n      thirdItem,\n      forthItem,\n    ]);\n\n    draggableElements =\n      sortable.getDraggableElementsForContainer(secondContainer);\n    expect(draggableElements).toHaveOrder([sortable.source]);\n\n    releaseMouse(sortable.source);\n\n    draggableElements =\n      sortable.getDraggableElementsForContainer(firstContainer);\n    expect(draggableElements).toHaveOrder([\n      fifthItem,\n      sixthItem,\n      seventhItem,\n      eighthItem,\n      secondItem,\n      thirdItem,\n      forthItem,\n    ]);\n\n    draggableElements =\n      sortable.getDraggableElementsForContainer(secondContainer);\n    expect(draggableElements).toHaveOrder([firstItem]);\n  });\n});\n"
  },
  {
    "path": "src/Swappable/README.md",
    "content": "## Swappable\n\nSwappable is built on top of Draggable and allows you to swap elements by dragging over them. No order will be maintained (unlike Sortable),\nso any draggable element that gets dragged over will be swapped with the source element.\n\n### Usage\n\n- NPM:\n\n```js\nimport {Swappable} from '@shopify/draggable';\n// Or\nimport Swappable from '@shopify/draggable/build/esm/Swappable/Swappable';\n\nconst swappable = new Swappable(document.querySelectorAll('ul'), {\n  draggable: 'li',\n});\n```\n\n- Browser (as a module):\n\n```html\n<script type=\"module\">\n  import Swappable from 'https://cdn.jsdelivr.net/npm/@shopify/draggable/build/esm/Swappable/Swappable.mjs';\n\n  const swappable = new Swappable(document.querySelectorAll('ul'), {\n    draggable: 'li',\n  });\n</script>\n```\n\n- Browser (Standalone):\n\n```html\n<script src=\"https://cdn.jsdelivr.net/npm/@shopify/draggable/build/umd/index.min.js\"></script>\n<script>\n  const swappable = new Draggable.Swappable(document.querySelectorAll('ul'), {\n    draggable: 'li',\n  });\n</script>\n```\n\n### API\n\nCheck out [Draggables API](../Draggable#api) for the base API\n\n### Options\n\nCheck out [Draggables options](../Draggable#options) for the base options\n\n### Events\n\nCheck out [Draggables events](../Draggable#events) for base events\n\n| Name                                    | Description                                         | Cancelable | Cancelable action |\n| --------------------------------------- | --------------------------------------------------- | ---------- | ----------------- |\n| [`swappable:start`][swappablestart]     | Gets fired when starting to drag                    | true       | Prevents drag     |\n| [`swappable:swap`][swappableswap]       | Gets fired before the source gets swapped           | true       | Prevents swap     |\n| [`swappable:swapped`][swappableswapped] | Gets fired before the source gets swapped           | false      | -                 |\n| [`swappable:stop`][swappablestop]       | Gets fired when dragging out of a droppable element | false      | -                 |\n\n[swappablestart]: SwappableEvent#swappablestartevent\n[swappableswap]: SwappableEvent#swappableswapevent\n[swappableswapped]: SwappableEvent#swappableswappedevent\n[swappablestop]: SwappableEvent#swappablestopevent\n\n### Classes\n\nCheck out [Draggables class identifiers](../Draggable#classes)\n\n### Example\n\nThis sample code will make list items draggable and allows you to swap them with other draggable elements:\n\n```js\nimport {Swappable} from '@shopify/draggable';\n\nconst swappable = new Swappable(document.querySelectorAll('ul'), {\n  draggable: 'li',\n});\n\nswappable.on('swappable:start', () => console.log('swappable:start'));\nswappable.on('swappable:swapped', () => console.log('swappable:swapped'));\nswappable.on('swappable:stop', () => console.log('swappable:stop'));\n```\n"
  },
  {
    "path": "src/Swappable/Swappable.js",
    "content": "import Draggable from '../Draggable';\n\nimport {\n  SwappableStartEvent,\n  SwappableSwapEvent,\n  SwappableSwappedEvent,\n  SwappableStopEvent,\n} from './SwappableEvent';\n\nconst onDragStart = Symbol('onDragStart');\nconst onDragOver = Symbol('onDragOver');\nconst onDragStop = Symbol('onDragStop');\n\n/**\n * Returns an announcement message when the Draggable element is swapped with another draggable element\n * @param {SwappableSwappedEvent} swappableEvent\n * @return {String}\n */\nfunction onSwappableSwappedDefaultAnnouncement({dragEvent, swappedElement}) {\n  const sourceText =\n    dragEvent.source.textContent.trim() ||\n    dragEvent.source.id ||\n    'swappable element';\n  const overText =\n    swappedElement.textContent.trim() ||\n    swappedElement.id ||\n    'swappable element';\n\n  return `Swapped ${sourceText} with ${overText}`;\n}\n\n/**\n * @const {Object} defaultAnnouncements\n * @const {Function} defaultAnnouncements['swappabled:swapped']\n */\nconst defaultAnnouncements = {\n  'swappabled:swapped': onSwappableSwappedDefaultAnnouncement,\n};\n\n/**\n * Swappable is built on top of Draggable and allows swapping of draggable elements.\n * Order is irrelevant to Swappable.\n * @class Swappable\n * @module Swappable\n * @extends Draggable\n */\nexport default class Swappable extends Draggable {\n  /**\n   * Swappable constructor.\n   * @constructs Swappable\n   * @param {HTMLElement[]|NodeList|HTMLElement} containers - Swappable containers\n   * @param {Object} options - Options for Swappable\n   */\n  constructor(containers = [], options = {}) {\n    super(containers, {\n      ...options,\n      announcements: {\n        ...defaultAnnouncements,\n        ...(options.announcements || {}),\n      },\n    });\n\n    /**\n     * Last draggable element that was dragged over\n     * @property lastOver\n     * @type {HTMLElement}\n     */\n    this.lastOver = null;\n\n    this[onDragStart] = this[onDragStart].bind(this);\n    this[onDragOver] = this[onDragOver].bind(this);\n    this[onDragStop] = this[onDragStop].bind(this);\n\n    this.on('drag:start', this[onDragStart])\n      .on('drag:over', this[onDragOver])\n      .on('drag:stop', this[onDragStop]);\n  }\n\n  /**\n   * Destroys Swappable instance.\n   */\n  destroy() {\n    super.destroy();\n\n    this.off('drag:start', this._onDragStart)\n      .off('drag:over', this._onDragOver)\n      .off('drag:stop', this._onDragStop);\n  }\n\n  /**\n   * Drag start handler\n   * @private\n   * @param {DragStartEvent} event - Drag start event\n   */\n  [onDragStart](event) {\n    const swappableStartEvent = new SwappableStartEvent({\n      dragEvent: event,\n    });\n\n    this.trigger(swappableStartEvent);\n\n    if (swappableStartEvent.canceled()) {\n      event.cancel();\n    }\n  }\n\n  /**\n   * Drag over handler\n   * @private\n   * @param {DragOverEvent} event - Drag over event\n   */\n  [onDragOver](event) {\n    if (\n      event.over === event.originalSource ||\n      event.over === event.source ||\n      event.canceled()\n    ) {\n      return;\n    }\n\n    const swappableSwapEvent = new SwappableSwapEvent({\n      dragEvent: event,\n      over: event.over,\n      overContainer: event.overContainer,\n    });\n\n    this.trigger(swappableSwapEvent);\n\n    if (swappableSwapEvent.canceled()) {\n      return;\n    }\n\n    // swap originally swapped element back\n    if (this.lastOver && this.lastOver !== event.over) {\n      swap(this.lastOver, event.source);\n    }\n\n    if (this.lastOver === event.over) {\n      this.lastOver = null;\n    } else {\n      this.lastOver = event.over;\n    }\n\n    swap(event.source, event.over);\n\n    const swappableSwappedEvent = new SwappableSwappedEvent({\n      dragEvent: event,\n      swappedElement: event.over,\n    });\n\n    this.trigger(swappableSwappedEvent);\n  }\n\n  /**\n   * Drag stop handler\n   * @private\n   * @param {DragStopEvent} event - Drag stop event\n   */\n  [onDragStop](event) {\n    const swappableStopEvent = new SwappableStopEvent({\n      dragEvent: event,\n    });\n\n    this.trigger(swappableStopEvent);\n    this.lastOver = null;\n  }\n}\n\nfunction withTempElement(callback) {\n  const tmpElement = document.createElement('div');\n  callback(tmpElement);\n  tmpElement.remove();\n}\n\nfunction swap(source, over) {\n  const overParent = over.parentNode;\n  const sourceParent = source.parentNode;\n\n  withTempElement((tmpElement) => {\n    sourceParent.insertBefore(tmpElement, source);\n    overParent.insertBefore(source, over);\n    sourceParent.insertBefore(over, tmpElement);\n  });\n}\n"
  },
  {
    "path": "src/Swappable/SwappableEvent/README.md",
    "content": "## Swappable\n\nThe base swappable event for all Swappable events that `Swappable` emits.\n\n| | |\n| --------------------- | ---------------------------------------------------------- |\n| **Interface**         | `SwappableEvent`                                           |\n| **Cancelable**        | false                                                      |\n| **Cancel action**     | -                                                          |\n| **type**              | `swappable`                                                |\n\n### API\n\n**`swappableEvent.dragEvent: DragEvent`**  \nRead-only property for the original drag event that triggered the swappable event.\n\n## SwappableStartEvent\n\n`SwappableStartEvent` gets triggered by `Swappable` on drag start.\n\n| | |\n| --------------------- | ---------------------------------------------------------- |\n| **Specification**     | `SwappableEvent`                                           |\n| **Interface**         | `SwappableStartEvent`                                      |\n| **Cancelable**        | true                                                       |\n| **Cancel action**     | Prevents drag start                                        |\n| **type**              | `swappable:start`                                          |\n\n## SwappableSwapEvent\n\n`SwappableSwapEvent` gets triggered by `Swappable` before swapping with another draggable.\n\n| | |\n| --------------------- | ---------------------------------------------------------- |\n| **Specification**     | `SwappableEvent`                                           |\n| **Interface**         | `SwappableSwapEvent`                                       |\n| **Cancelable**        | true                                                       |\n| **Cancel action**     | Prevents swapping                                          |\n| **type**              | `swappable:swap`                                           |\n\n### API\n\n**`swappableEvent.over: HTMLElement`**  \nRead-only property for the draggable element that you are over.\n\n**`swappableEvent.overContainer: HTMLElement`**  \nRead-only property for the draggable container element that you are over.\n\n## SwappableSwappedEvent\n\n`SwappableSwappedEvent` gets triggered by `Swappable` when sorted with another draggable.\n\n| | |\n| --------------------- | ---------------------------------------------------------- |\n| **Specification**     | `SwappableEvent`                                           |\n| **Interface**         | `SwappableSwappedEvent`                                    |\n| **Cancelable**        | false                                                      |\n| **Cancel action**     | -                                                          |\n| **type**              | `swappable:swapped`                                         |\n\n### API\n\n**`swappableEvent.swappedElement: HTMLElement`**  \nRead-only property for the draggable element you swapped with.\n\n## SwappableStopEvent\n\n`SwappableStopEvent` gets triggered by `Swappable` on drag stop.\n\n| | |\n| --------------------- | ---------------------------------------------------------- |\n| **Specification**     | `SwappableEvent`                                           |\n| **Interface**         | `SwappableStopEvent`                                       |\n| **Cancelable**        | false                                                      |\n| **Cancel action**     | -                                                          |\n| **type**              | `swappable:stop`                                           |\n"
  },
  {
    "path": "src/Swappable/SwappableEvent/SwappableEvent.ts",
    "content": "import AbstractEvent from 'shared/AbstractEvent';\n\nimport {DragEvent, DragEventData} from '../../Draggable/DragEvent';\n\ninterface SwappableEventData {\n  dragEvent: DragEvent<DragEventData>;\n}\n\n/**\n * Base swappable event\n * @class SwappableEvent\n * @module SwappableEvent\n * @extends AbstractEvent\n */\nexport class SwappableEvent<\n  T extends SwappableEventData,\n> extends AbstractEvent<SwappableEventData> {\n  static type = 'swappable';\n\n  /**\n   * SwappableEvent constructor.\n   * @constructs SwappableEvent\n   * @param {SwappableEventData} data - Event data\n   */\n  constructor(public data: T) {\n    super(data);\n  }\n\n  /**\n   * Original drag event that triggered this swappable event\n   * @property dragEvent\n   * @type {DragEvent}\n   * @readonly\n   */\n  get dragEvent() {\n    return this.data.dragEvent;\n  }\n}\n\n/**\n * Swappable start event\n * @class SwappableStartEvent\n * @module SwappableStartEvent\n * @extends SwappableEvent\n */\nexport class SwappableStartEvent extends SwappableEvent<SwappableEventData> {\n  static type = 'swappable:start';\n  static cancelable = true;\n}\n\ninterface SwappableSwapEventData extends SwappableEventData {\n  over: HTMLElement;\n  overContainer: HTMLElement;\n}\n\n/**\n * Swappable swap event\n * @class SwappableSwapEvent\n * @module SwappableSwapEvent\n * @extends SwappableEvent\n */\nexport class SwappableSwapEvent extends SwappableEvent<SwappableSwapEventData> {\n  static type = 'swappable:swap';\n  static cancelable = true;\n\n  /**\n   * Draggable element you are over\n   * @property over\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get over() {\n    return this.data.over;\n  }\n\n  /**\n   * Draggable container you are over\n   * @property overContainer\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get overContainer() {\n    return this.data.overContainer;\n  }\n}\n\ninterface SwappableSwappedEventData extends SwappableEventData {\n  swappedElement: HTMLElement;\n}\n\n/**\n * Swappable swapped event\n * @class SwappableSwappedEvent\n * @module SwappableSwappedEvent\n * @extends SwappableEvent\n */\nexport class SwappableSwappedEvent extends SwappableEvent<SwappableSwappedEventData> {\n  static type = 'swappable:swapped';\n\n  /**\n   * The draggable element that you swapped with\n   * @property swappedElement\n   * @type {HTMLElement}\n   * @readonly\n   */\n  get swappedElement() {\n    return this.data.swappedElement;\n  }\n}\n\n/**\n * Swappable stop event\n * @class SwappableStopEvent\n * @module SwappableStopEvent\n * @extends SwappableEvent\n */\nexport class SwappableStopEvent extends SwappableEvent<SwappableEventData> {\n  static type = 'swappable:stop';\n}\n"
  },
  {
    "path": "src/Swappable/SwappableEvent/index.ts",
    "content": "export * from './SwappableEvent';\n"
  },
  {
    "path": "src/Swappable/index.js",
    "content": "import Swappable from './Swappable';\n\nexport default Swappable;\nexport * from './SwappableEvent';\n"
  },
  {
    "path": "src/Swappable/tests/Swappable.test.js",
    "content": "import {\n  createSandbox,\n  clickMouse,\n  moveMouse,\n  releaseMouse,\n  waitForDragDelay,\n  DRAG_DELAY,\n} from 'helper';\n\nimport Swappable from '..';\n\nconst sampleMarkup = `\n  <ul>\n    <li>First item</li>\n    <li>Second item</li>\n    <li>Third item</li>\n    <li>Forth item</li>\n  </ul>\n  <ul>\n    <li>Fith item</li>\n    <li>Sixth item</li>\n    <li>Seventh item</li>\n    <li>Eighth item</li>\n  </ul>\n`;\n\ndescribe('Swappable', () => {\n  let sandbox;\n  let containers;\n  let draggableElements;\n  let swappable;\n\n  let firstContainer;\n  let secondContainer;\n  let firstItem;\n  let secondItem;\n  let thirdItem;\n  let forthItem;\n  let fifthItem;\n  let sixthItem;\n  let seventhItem;\n  let eighthItem;\n\n  beforeEach(() => {\n    sandbox = createSandbox(sampleMarkup);\n    containers = sandbox.querySelectorAll('ul');\n    draggableElements = sandbox.querySelectorAll('li');\n    swappable = new Swappable(containers, {\n      draggable: 'li',\n      delay: DRAG_DELAY,\n    });\n\n    firstContainer = containers[0];\n    secondContainer = containers[1];\n    firstItem = draggableElements[0];\n    secondItem = draggableElements[1];\n    thirdItem = draggableElements[2];\n    forthItem = draggableElements[3];\n    fifthItem = draggableElements[4];\n    sixthItem = draggableElements[5];\n    seventhItem = draggableElements[6];\n    eighthItem = draggableElements[7];\n  });\n\n  afterEach(() => {\n    swappable.destroy();\n    sandbox.remove();\n  });\n\n  it('triggers events', () => {\n    const swappableStart = jest.fn();\n    const swappableSwap = jest.fn();\n    const swappableSwapped = jest.fn();\n    const swappableStop = jest.fn();\n\n    swappable.on('swappable:start', swappableStart);\n    swappable.on('swappable:swap', swappableSwap);\n    swappable.on('swappable:swapped', swappableSwapped);\n    swappable.on('swappable:stop', swappableStop);\n\n    clickMouse(firstItem);\n    waitForDragDelay();\n    moveMouse(secondItem);\n    releaseMouse(swappable.source);\n\n    expect(swappableStart).toHaveBeenCalled();\n\n    expect(swappableSwap).toHaveBeenCalled();\n\n    expect(swappableSwapped).toHaveBeenCalled();\n\n    expect(swappableStop).toHaveBeenCalled();\n  });\n\n  it('prevents drag when canceling sortable start event', () => {\n    swappable.on('swappable:start', (swappableEvent) => {\n      swappableEvent.cancel();\n    });\n\n    clickMouse(firstItem);\n    waitForDragDelay();\n    moveMouse(secondItem);\n\n    expect(swappable.isDragging()).toBe(false);\n\n    releaseMouse(swappable.source);\n  });\n\n  it('swaps two first elements', () => {\n    draggableElements = sandbox.querySelectorAll('li');\n\n    expect(draggableElements[0]).toBe(firstItem);\n\n    expect(draggableElements[1]).toBe(secondItem);\n\n    clickMouse(firstItem);\n    waitForDragDelay();\n    moveMouse(secondItem);\n    releaseMouse(swappable.source);\n\n    draggableElements = sandbox.querySelectorAll('li');\n\n    expect(draggableElements[0]).toBe(secondItem);\n\n    expect(draggableElements[1]).toBe(firstItem);\n  });\n\n  it('swaps elements as you drag within a single container', () => {\n    draggableElements = swappable.getDraggableElementsForContainer(\n      containers[0],\n    );\n    expect(draggableElements).toHaveOrder([\n      firstItem,\n      secondItem,\n      thirdItem,\n      forthItem,\n    ]);\n\n    clickMouse(firstItem);\n    waitForDragDelay();\n    moveMouse(secondItem);\n\n    draggableElements =\n      swappable.getDraggableElementsForContainer(firstContainer);\n    expect(draggableElements).toHaveOrder([\n      secondItem,\n      // original firstItem\n      swappable.source,\n      thirdItem,\n      forthItem,\n    ]);\n\n    moveMouse(thirdItem);\n\n    draggableElements =\n      swappable.getDraggableElementsForContainer(firstContainer);\n    expect(draggableElements).toHaveOrder([\n      thirdItem,\n      secondItem,\n      // original firstItem\n      swappable.source,\n      forthItem,\n    ]);\n\n    moveMouse(forthItem);\n\n    draggableElements =\n      swappable.getDraggableElementsForContainer(firstContainer);\n    expect(draggableElements).toHaveOrder([\n      forthItem,\n      secondItem,\n      thirdItem,\n      // original firstItem\n      swappable.source,\n    ]);\n\n    releaseMouse(swappable.source);\n\n    draggableElements =\n      swappable.getDraggableElementsForContainer(firstContainer);\n    expect(draggableElements).toHaveOrder([\n      forthItem,\n      secondItem,\n      thirdItem,\n      firstItem,\n    ]);\n  });\n\n  it('sorts elements as you drag between multiple containers', () => {\n    draggableElements =\n      swappable.getDraggableElementsForContainer(firstContainer);\n    expect(draggableElements).toHaveOrder([\n      firstItem,\n      secondItem,\n      thirdItem,\n      forthItem,\n    ]);\n\n    draggableElements =\n      swappable.getDraggableElementsForContainer(secondContainer);\n    expect(draggableElements).toHaveOrder([\n      fifthItem,\n      sixthItem,\n      seventhItem,\n      eighthItem,\n    ]);\n\n    clickMouse(firstItem);\n    waitForDragDelay();\n    moveMouse(fifthItem);\n\n    draggableElements =\n      swappable.getDraggableElementsForContainer(firstContainer);\n    expect(draggableElements).toHaveOrder([\n      fifthItem,\n      secondItem,\n      thirdItem,\n      forthItem,\n    ]);\n\n    draggableElements =\n      swappable.getDraggableElementsForContainer(secondContainer);\n    expect(draggableElements).toHaveOrder([\n      // original firstItem\n      swappable.source,\n      sixthItem,\n      seventhItem,\n      eighthItem,\n    ]);\n\n    moveMouse(eighthItem);\n\n    draggableElements =\n      swappable.getDraggableElementsForContainer(firstContainer);\n    expect(draggableElements).toHaveOrder([\n      eighthItem,\n      secondItem,\n      thirdItem,\n      forthItem,\n    ]);\n\n    draggableElements =\n      swappable.getDraggableElementsForContainer(secondContainer);\n    expect(draggableElements).toHaveOrder([\n      fifthItem,\n      sixthItem,\n      seventhItem,\n      // original firstItem\n      swappable.source,\n    ]);\n\n    releaseMouse(swappable.source);\n\n    draggableElements =\n      swappable.getDraggableElementsForContainer(firstContainer);\n    expect(draggableElements).toHaveOrder([\n      eighthItem,\n      secondItem,\n      thirdItem,\n      forthItem,\n    ]);\n\n    draggableElements =\n      swappable.getDraggableElementsForContainer(secondContainer);\n    expect(draggableElements).toHaveOrder([\n      fifthItem,\n      sixthItem,\n      seventhItem,\n      firstItem,\n    ]);\n  });\n\n  it('prevents sorting when sortable:sort event gets canceled', () => {\n    swappable.on('swappable:swap', (swappableEvent) => {\n      swappableEvent.cancel();\n    });\n\n    clickMouse(firstItem);\n    waitForDragDelay();\n    moveMouse(secondItem);\n\n    draggableElements =\n      swappable.getDraggableElementsForContainer(firstContainer);\n    expect(draggableElements).toHaveOrder([\n      swappable.source,\n      secondItem,\n      thirdItem,\n      forthItem,\n    ]);\n\n    releaseMouse(swappable.source);\n\n    draggableElements =\n      swappable.getDraggableElementsForContainer(firstContainer);\n    expect(draggableElements).toHaveOrder([\n      firstItem,\n      secondItem,\n      thirdItem,\n      forthItem,\n    ]);\n  });\n});\n"
  },
  {
    "path": "src/index.js",
    "content": "import AbstractEvent from 'shared/AbstractEvent';\nimport AbstractPlugin from 'shared/AbstractPlugin';\n\nimport * as Sensors from './Draggable/Sensors';\nimport * as Plugins from './Plugins';\n\nexport {AbstractEvent as BaseEvent, AbstractPlugin as BasePlugin};\n\nexport {default as Draggable} from './Draggable';\nexport {default as Droppable} from './Droppable';\nexport {default as Swappable} from './Swappable';\nexport {default as Sortable} from './Sortable';\n\nexport {Sensors, Plugins};\n"
  },
  {
    "path": "src/shared/AbstractEvent/AbstractEvent.ts",
    "content": "/**\n * All events fired by draggable inherit this class. You can call `cancel()` to\n * cancel a specific event or you can check if an event has been canceled by\n * calling `canceled()`.\n * @abstract\n * @class AbstractEvent\n * @module AbstractEvent\n */\nexport class AbstractEvent<T> {\n  /**\n   * Event type\n   * @static\n   * @abstract\n   * @property type\n   * @type {String}\n   */\n  static type = 'event';\n  /**\n   * Event cancelable\n   * @static\n   * @abstract\n   * @property cancelable\n   * @type {Boolean}\n   */\n  static cancelable = false;\n\n  /**\n   * Private instance variable to track canceled state\n   * @private\n   * @type {Boolean}\n   */\n  private _canceled = false;\n\n  /**\n   * AbstractEvent constructor.\n   * @constructs AbstractEvent\n   * @param {T} data - Event data\n   */\n  constructor(public data: T) {}\n\n  /**\n   * Read-only type\n   * @abstract\n   * @return {String}\n   */\n  get type() {\n    return (this.constructor as typeof AbstractEvent).type;\n  }\n\n  /**\n   * Read-only cancelable\n   * @abstract\n   * @return {Boolean}\n   */\n  get cancelable() {\n    return (this.constructor as typeof AbstractEvent).cancelable;\n  }\n\n  /**\n   * Cancels the event instance\n   * @abstract\n   */\n  cancel() {\n    this._canceled = true;\n  }\n\n  /**\n   * Check if event has been canceled\n   * @abstract\n   * @return {Boolean}\n   */\n  canceled() {\n    return this._canceled;\n  }\n\n  /**\n   * Returns new event instance with existing event data.\n   * This method allows for overriding of event data.\n   * @param {T} data\n   * @return {AbstractEvent}\n   */\n  clone(data: Partial<T>): AbstractEvent<T> {\n    return new (this.constructor as typeof AbstractEvent)({\n      ...this.data,\n      ...data,\n    });\n  }\n}\n"
  },
  {
    "path": "src/shared/AbstractEvent/README.md",
    "content": "## Abstract event\n\nThis is the base class for all events draggable emits. Inherit from this class for creating\nyour own custom events.\n\n| | |\n| --------------------- | ---------------------------------------------------------- |\n| **Specification**     | `AbstractEvent`                                            |\n| **Interface**         | `AbstractEvent`                                            |\n| **Cancelable**        | false                                                      |\n| **Cancel action**     | -                                                          |\n| **type**              | `event`                                                    |\n\n### API\n\n**`new AbstractEvent(data: Object): AbstractEvent`**  \nCreates an `AbstractEvent` instance.\n\n**`abstractEvent.cancel(): void`**  \nCancels drag start event.\n\n**`abstractEvent.canceled(): Boolean`**  \nChecks if event has been canceled.\n\n**`abstractEvent.type: String`**  \nRead-only property to find out event type\n\n**`abstractEvent.cancelable: Boolean`**  \nRead-only property to check if event is cancelable\n\n**`abstractEvent.clone(data: Object): AbstractEvent`**  \nCreates an `AbstractEvent` instance with existing event data. This method allows\nfor overriding of event data.\n"
  },
  {
    "path": "src/shared/AbstractEvent/index.ts",
    "content": "export {AbstractEvent as default} from './AbstractEvent';\n"
  },
  {
    "path": "src/shared/AbstractEvent/tests/AbstractEvent.test.ts",
    "content": "import {AbstractEvent} from '../AbstractEvent';\n\ndescribe('AbstractEvent', () => {\n  it('should be of type AbstractEvent', () => {\n    const event = new AbstractEvent({});\n\n    expect(event).toBeInstanceOf(AbstractEvent);\n  });\n\n  it('should initialize with correct type', () => {\n    const event = new AbstractEvent({});\n\n    expect(event.type).toBe('event');\n  });\n\n  it('should initialize in uncancelable state', () => {\n    const event = new AbstractEvent({});\n\n    expect(event.cancelable).toBe(false);\n  });\n\n  it('should initialize in uncancelled state', () => {\n    const event = new AbstractEvent({});\n\n    expect(event.canceled()).toBe(false);\n  });\n\n  it('should initialize with data', () => {\n    const event = new AbstractEvent({\n      foo: 'bar',\n    });\n\n    expect(event.data).toMatchObject({\n      foo: 'bar',\n    });\n  });\n\n  it('should cancel event', () => {\n    const event = new AbstractEvent({});\n\n    expect(event.canceled()).toBe(false);\n\n    event.cancel();\n\n    expect(event.canceled()).toBe(true);\n  });\n});\n"
  },
  {
    "path": "src/shared/AbstractPlugin/AbstractPlugin.ts",
    "content": "import {FixMeAny} from 'shared/types';\n\n/**\n * All draggable plugins inherit from this class.\n * @abstract\n * @class AbstractPlugin\n * @module AbstractPlugin\n */\nexport abstract class AbstractPlugin {\n  /**\n   * AbstractPlugin constructor.\n   * @constructs AbstractPlugin\n   * @param {Draggable} draggable - Draggable instance\n   */\n  constructor(protected draggable: FixMeAny) {}\n\n  /**\n   * Override to add listeners\n   * @abstract\n   */\n  attach() {\n    throw new Error('Not Implemented');\n  }\n\n  /**\n   * Override to remove listeners\n   * @abstract\n   */\n  detach() {\n    throw new Error('Not Implemented');\n  }\n}\n"
  },
  {
    "path": "src/shared/AbstractPlugin/README.md",
    "content": "## Abstract plugin\n\nThis is the base class for all draggable plugins.\n\n### API\n\n**`new AbstractPlugin(draggable: Draggable): AbstractPlugin`**  \nCreates an `AbstractPlugin` instance.\n\n**`abstractEvent.attach(): void`**  \nAttaches listeners for plugin.\n\n**`abstractEvent.detach(): void`**  \nDetaches listeners for plugin.\n"
  },
  {
    "path": "src/shared/AbstractPlugin/index.ts",
    "content": "export {AbstractPlugin as default} from './AbstractPlugin';\n"
  },
  {
    "path": "src/shared/README.md",
    "content": "## Shared\n"
  },
  {
    "path": "src/shared/types.ts",
    "content": "export type FixMeAny = any;\nexport type FixMeUnknown = unknown;\n"
  },
  {
    "path": "src/shared/utils/README.md",
    "content": "## Utils\n"
  },
  {
    "path": "src/shared/utils/closest/README.md",
    "content": "## closest\n"
  },
  {
    "path": "src/shared/utils/closest/closest.ts",
    "content": "declare global {\n  export interface Node {\n    correspondingUseElement: SVGUseElement;\n    correspondingElement: SVGElement;\n  }\n}\n\ntype CallbackFunction = (element: Node) => boolean;\ntype Value = string | CallbackFunction | NodeList | Node | Node[];\n\n/**\n * Get the closest parent element node of a given node that matches the given\n * selector string or matching function\n *\n * @param {Node} node The child element to find a parent of\n * @param {String|Function} selector The string or function to use to match\n *     the parent node\n * @return {Node|null}\n */\nexport default function closest(node?: Node, value?: Value) {\n  if (node == null) {\n    return null;\n  }\n\n  function conditionFn(currentNode: Node | null): boolean {\n    if (currentNode == null || value == null) {\n      return false;\n    } else if (isSelector(value)) {\n      return Element.prototype.matches.call(currentNode, value);\n    } else if (isNodeList(value)) {\n      return [...value].includes(currentNode);\n    } else if (isElement(value)) {\n      return value === currentNode;\n    } else if (isFunction(value)) {\n      return value(currentNode);\n    } else {\n      return false;\n    }\n  }\n\n  let current: Node | null = node;\n\n  do {\n    current =\n      current.correspondingUseElement ||\n      current.correspondingElement ||\n      current;\n\n    if (conditionFn(current)) {\n      return current;\n    }\n\n    current = current?.parentNode || null;\n  } while (\n    current != null &&\n    current !== document.body &&\n    current !== document\n  );\n\n  return null;\n}\n\nfunction isSelector(value: Value): value is string {\n  return Boolean(typeof value === 'string');\n}\n\nfunction isNodeList(value: Value): value is NodeList | Node[] {\n  return Boolean(value instanceof NodeList || value instanceof Array);\n}\n\nfunction isElement(value: Value): value is Node {\n  return Boolean(value instanceof Node);\n}\n\nfunction isFunction(value: Value): value is (element: Node) => boolean {\n  return Boolean(typeof value === 'function');\n}\n"
  },
  {
    "path": "src/shared/utils/closest/index.ts",
    "content": "import closest from './closest';\n\nexport default closest;\n"
  },
  {
    "path": "src/shared/utils/closest/tests/closest.test.ts",
    "content": "import {createSandbox} from 'helper';\n\nimport closest from '../closest';\n\nconst sampleMarkup = `\n  <div class=\"tree\">\n    <section class=\"branch\">\n      <ul class=\"twig\">\n        <li class=\"leaf\"></li>\n      </ul>\n    </section>\n  </div>\n`;\n\ndescribe('utils', () => {\n  let sandbox: HTMLDivElement;\n\n  beforeEach(() => {\n    sandbox = createSandbox(sampleMarkup);\n  });\n\n  afterEach(() => {\n    sandbox.remove();\n  });\n\n  it('should return null when no element specified', () => {\n    expect(closest()).toBeNull();\n  });\n\n  it('should return null when string selector does not match', () => {\n    const element = sandbox.querySelector('.leaf')!;\n\n    expect(closest(element, 'will-not-match')).toBeNull();\n  });\n\n  it('should return null when function selector does not match', () => {\n    const element = sandbox.querySelector('.leaf')!;\n    function selector() {\n      return false;\n    }\n\n    expect(closest(element, selector)).toBeNull();\n  });\n\n  it('should return null when selector targets child element', () => {\n    const element = sandbox.querySelector('.twig')!;\n\n    expect(closest(element, '.leaf')).toBeNull();\n  });\n\n  it('should match element via callback', () => {\n    const element = sandbox.querySelector('.leaf')!;\n\n    function callback(currentElement: Node) {\n      return (currentElement as HTMLElement).classList.contains('leaf');\n    }\n\n    expect(closest(element, callback)).toBe(element);\n  });\n\n  [\n    '.twig',\n    'ul',\n    '.branch',\n    'section',\n    '.tree',\n    'div',\n    'body',\n    'document',\n  ].forEach((expectedMatchingSelector) => {\n    it(`should return matched element when selector targets parent element matching selector ${expectedMatchingSelector}`, () => {\n      const element = sandbox.querySelector('.leaf')!;\n      const expected = sandbox.querySelector(expectedMatchingSelector);\n\n      expect(closest(element, expectedMatchingSelector)).toBe(expected);\n    });\n  });\n});\n"
  },
  {
    "path": "src/shared/utils/decorators/AutoBind.ts",
    "content": "export function AutoBind<T extends (...args: any[]) => any>(\n  originalMethod: T,\n  {name, addInitializer}: ClassMethodDecoratorContext<ThisParameterType<T>, T>,\n) {\n  addInitializer(function (this: ThisParameterType<T>) {\n    /* eslint-disable @typescript-eslint/ban-ts-comment */\n    // @ts-ignore\n    this[name as PropertyKey] = originalMethod.bind(this);\n    /* eslint-enable @typescript-eslint/ban-ts-comment */\n  });\n}\n"
  },
  {
    "path": "src/shared/utils/decorators/index.ts",
    "content": "export {AutoBind} from './AutoBind';\n"
  },
  {
    "path": "src/shared/utils/distance/distance.ts",
    "content": "/**\n * Returns the distance between two points\n * @param  {Number} x1 The X position of the first point\n * @param  {Number} y1 The Y position of the first point\n * @param  {Number} x2 The X position of the second point\n * @param  {Number} y2 The Y position of the second point\n * @return {Number}\n */\nexport default function distance(\n  x1: number,\n  y1: number,\n  x2: number,\n  y2: number,\n) {\n  return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);\n}\n"
  },
  {
    "path": "src/shared/utils/distance/index.ts",
    "content": "import distance from './distance';\n\nexport default distance;\n"
  },
  {
    "path": "src/shared/utils/index.ts",
    "content": "export {default as closest} from './closest';\nexport {AutoBind} from './decorators';\nexport {default as requestNextAnimationFrame} from './requestNextAnimationFrame';\nexport {default as distance} from './distance';\nexport {default as touchCoords} from './touchCoords';\n"
  },
  {
    "path": "src/shared/utils/requestNextAnimationFrame/README.md",
    "content": "## requestNextAnimationFrame\n\nUtility method that requests the next animation frame, which is achieved by double wrapping animation frames.\n"
  },
  {
    "path": "src/shared/utils/requestNextAnimationFrame/index.ts",
    "content": "import requestNextAnimationFrame from './requestNextAnimationFrame';\n\nexport default requestNextAnimationFrame;\n"
  },
  {
    "path": "src/shared/utils/requestNextAnimationFrame/requestNextAnimationFrame.ts",
    "content": "export default function requestNextAnimationFrame(callback: () => void) {\n  return requestAnimationFrame(() => {\n    requestAnimationFrame(callback);\n  });\n}\n"
  },
  {
    "path": "src/shared/utils/touchCoords/index.ts",
    "content": "import touchCoords from './touchCoords';\n\nexport default touchCoords;\n"
  },
  {
    "path": "src/shared/utils/touchCoords/touchCoords.ts",
    "content": "/**\n * Returns the first touch event found in touches or changedTouches of a touch events.\n * @param {TouchEvent} event a touch event\n * @return {Touch} a touch object\n */\nexport default function touchCoords(event: TouchEvent) {\n  const {touches, changedTouches} = event;\n  return (touches && touches[0]) || (changedTouches && changedTouches[0]);\n}\n"
  },
  {
    "path": "test/environment.ts",
    "content": "import {REQUEST_ANIMATION_FRAME_TIMEOUT} from './helpers/environment';\n\ndeclare global {\n  export interface Event {\n    stoppedPropagation: boolean;\n  }\n}\n\nwindow.requestAnimationFrame = (callback) => {\n  return setTimeout(callback, REQUEST_ANIMATION_FRAME_TIMEOUT);\n};\n\nwindow.cancelAnimationFrame = (id) => {\n  return clearTimeout(id);\n};\n\nEvent.prototype.stopPropagation = (function (_super) {\n  return function (\n    this: Event,\n    ...args: Parameters<typeof Event.prototype.stopPropagation>\n  ) {\n    const returnValue = _super.call(this, ...args);\n    this.stoppedPropagation = true;\n    return returnValue;\n  };\n})(Event.prototype.stopPropagation);\n\nEvent.prototype.stopImmediatePropagation = (function (_super) {\n  return function (\n    this: Event,\n    ...args: Parameters<typeof Event.prototype.stopImmediatePropagation>\n  ) {\n    const returnValue = _super.call(this, ...args);\n    this.stoppedPropagation = true;\n    return returnValue;\n  };\n})(Event.prototype.stopImmediatePropagation);\n"
  },
  {
    "path": "test/helper.ts",
    "content": "export * from './helpers';\n"
  },
  {
    "path": "test/helpers/constants.ts",
    "content": "export const defaultTouchEventOptions = {\n  touches: [\n    {\n      pageX: 0,\n      pageY: 0,\n    },\n  ],\n};\n\nexport const defaultMouseEventOptions = {\n  button: 0,\n  clientX: 0,\n  clientY: 0,\n  pageX: 0,\n  pageY: 0,\n};\n\nexport const DRAG_DELAY = 100;\n"
  },
  {
    "path": "test/helpers/environment.ts",
    "content": "import {setImmediate} from 'timers';\n\nexport function createSandbox(content: string) {\n  const sandbox = document.createElement('div');\n  sandbox.innerHTML = content;\n  document.body.appendChild(sandbox);\n\n  return sandbox;\n}\n\nexport function withElementFromPoint(\n  elementFromPoint: HTMLElement,\n  callback: () => void,\n) {\n  const originalElementFromPoint = document.elementFromPoint;\n  document.elementFromPoint = () => elementFromPoint;\n  callback();\n  document.elementFromPoint = originalElementFromPoint;\n}\n\nexport const REQUEST_ANIMATION_FRAME_TIMEOUT = 15;\n\nexport function waitForRequestAnimationFrame(\n  requestAnimationFrameTimeout = REQUEST_ANIMATION_FRAME_TIMEOUT,\n) {\n  jest.advanceTimersByTime(requestAnimationFrameTimeout + 1);\n}\n\nexport function waitForPromisesToResolve() {\n  return new Promise((resolve) => setImmediate(resolve));\n}\n"
  },
  {
    "path": "test/helpers/event.ts",
    "content": "import {withElementFromPoint} from './environment';\n\nexport function triggerEvent(\n  element: HTMLElement,\n  type: string,\n  data: {[key: string]: any} = {},\n) {\n  const event = document.createEvent('Event');\n  event.initEvent(type, true, true);\n\n  for (const key in data) {\n    if (Object.prototype.hasOwnProperty.call(data, key)) {\n      Object.defineProperty(event, key, {\n        value: data[key],\n      });\n    }\n  }\n\n  withElementFromPoint(element, () => {\n    element.dispatchEvent(event);\n  });\n\n  return event;\n}\n"
  },
  {
    "path": "test/helpers/index.ts",
    "content": "export * from './constants';\nexport * from './environment';\nexport * from './event';\nexport * from './module';\nexport * from './plugin';\nexport * from './sensor';\n"
  },
  {
    "path": "test/helpers/module.ts",
    "content": "import {\n  waitForDragDelay,\n  clickMouse,\n  moveMouse,\n  releaseMouse,\n  touchStart,\n  touchMove,\n  touchRelease,\n  dragStart,\n  dragOver,\n  dragDrop,\n  dragStop,\n} from './sensor';\n\ninterface Options {\n  from: HTMLElement;\n  to: HTMLElement;\n  sensor?: 'mouse' | 'touch' | 'drag';\n}\n\nexport function drag({from, to, sensor = 'mouse'}: Options) {\n  if (sensor === 'mouse') {\n    clickMouse(from);\n    waitForDragDelay();\n    moveMouse(to);\n    releaseMouse(to);\n  } else if (sensor === 'touch') {\n    touchStart(from);\n    waitForDragDelay();\n    touchMove(to);\n    touchRelease(to);\n  } else if (sensor === 'drag') {\n    clickMouse(from);\n    waitForDragDelay();\n    dragStart(from);\n    waitForDragDelay();\n    dragOver(to);\n    dragDrop(to);\n    dragStop(to);\n  } else {\n    throw new Error(`Sensor '${sensor}' is not yet implemented`);\n  }\n}\n"
  },
  {
    "path": "test/helpers/plugin.ts",
    "content": "import AbstractPlugin from 'shared/AbstractPlugin';\n\nexport class TestPlugin extends AbstractPlugin {\n  constructor(draggable: any) {\n    super(draggable);\n\n    /* eslint-disable jest/prefer-spy-on */\n    this.attach = jest.fn();\n    this.detach = jest.fn();\n    /* eslint-enable jest/prefer-spy-on */\n  }\n\n  attach() {\n    return jest.fn();\n  }\n\n  detach() {\n    return jest.fn();\n  }\n}\n"
  },
  {
    "path": "test/helpers/sensor.ts",
    "content": "import {\n  DRAG_DELAY,\n  defaultTouchEventOptions,\n  defaultMouseEventOptions,\n} from './constants';\nimport {triggerEvent} from './event';\n\nexport function waitForDragDelay({\n  dragDelay = DRAG_DELAY,\n  restoreDateMock = true,\n} = {}) {\n  const next = Date.now() + dragDelay + 1;\n  const dateMock = jest.spyOn(Date, 'now').mockImplementation(() => {\n    return next;\n  });\n  jest.advanceTimersByTime(dragDelay + 1);\n  if (restoreDateMock) {\n    dateMock.mockRestore();\n  }\n  return dateMock;\n}\n\nexport function clickMouse(element: HTMLElement, options = {}) {\n  return triggerEvent(element, 'mousedown', {\n    ...defaultMouseEventOptions,\n    ...options,\n  });\n}\n\nexport function moveMouse(element: HTMLElement, options = {}) {\n  return triggerEvent(element, 'mousemove', {\n    ...defaultMouseEventOptions,\n    ...options,\n  });\n}\n\nexport function releaseMouse(element: HTMLElement, options = {}) {\n  return triggerEvent(element, 'mouseup', {\n    ...defaultMouseEventOptions,\n    ...options,\n  });\n}\n\nexport function touchStart(element: HTMLElement, options = {}) {\n  return triggerEvent(element, 'touchstart', {\n    ...defaultTouchEventOptions,\n    ...options,\n  });\n}\n\nexport function touchMove(element: HTMLElement, options = {}) {\n  return triggerEvent(element, 'touchmove', {\n    ...defaultTouchEventOptions,\n    ...options,\n  });\n}\n\nexport function touchRelease(element: HTMLElement, options = {}) {\n  return triggerEvent(element, 'touchend', {\n    ...defaultTouchEventOptions,\n    ...options,\n  });\n}\n\nexport function dragStart(element: HTMLElement, options = {}) {\n  return triggerEvent(element, 'dragstart', {\n    dataTransfer: getDataTransferStub(),\n    ...options,\n  });\n}\n\nexport function dragOver(element: HTMLElement, options = {}) {\n  return triggerEvent(element, 'dragover', {\n    dataTransfer: getDataTransferStub(),\n    ...options,\n  });\n}\n\nexport function dragDrop(element: HTMLElement, options = {}) {\n  return triggerEvent(element, 'drop', {\n    dataTransfer: getDataTransferStub(),\n    ...options,\n  });\n}\n\nexport function dragStop(element: HTMLElement, options = {}) {\n  return triggerEvent(element, 'dragend', {\n    dataTransfer: getDataTransferStub(),\n    ...options,\n  });\n}\n\nexport function getDataTransferStub() {\n  return {\n    setData: jest.fn(),\n    effectAllowed: null,\n  };\n}\n"
  },
  {
    "path": "test/matchers/dom-event.ts",
    "content": "function toHaveDefaultPrevented(event: Event) {\n  const pass = Boolean(event.defaultPrevented);\n\n  return {\n    pass,\n    message: () => {\n      const expectation = pass ? 'not to have' : 'to have';\n\n      return `Expected dom event '${event.type}' ${expectation} default prevented`;\n    },\n  };\n}\n\nfunction toHaveStoppedPropagation(event: Event) {\n  const pass = Boolean(event.stoppedPropagation);\n\n  return {\n    pass,\n    message: () => {\n      const expectation = pass ? 'not to have' : 'to have';\n\n      return `Expected dom event '${event.type}' ${expectation} stopped propagation`;\n    },\n  };\n}\n\nexpect.extend({\n  toHaveDefaultPrevented,\n  toHaveStoppedPropagation,\n});\n"
  },
  {
    "path": "test/matchers/event.ts",
    "content": "import AbstractEvent from 'shared/AbstractEvent';\n\nimport {expectation} from './utils';\n\nfunction toHaveBeenCalledWithEvent(\n  this: any,\n  jestFunction: ReturnType<typeof jest.fn>,\n  expectedEventConstructor: typeof AbstractEvent<unknown>,\n) {\n  const mockFunction = jestFunction.mock;\n  const mockCalls = mockFunction.calls;\n  let pass: boolean;\n  let message;\n\n  pass = this.isNot && mockCalls.length === 0;\n  if (pass) {\n    message = () =>\n      `Expected ${expectedEventConstructor.type} event ${expectation(\n        !pass,\n      )} triggered`;\n    return {pass: !pass, message};\n  }\n\n  pass = !mockCalls.length;\n  if (pass) {\n    message = () =>\n      `Expected ${expectedEventConstructor.type} event ${expectation(\n        pass,\n      )} triggered`;\n    return {pass, message};\n  }\n\n  const event = mockCalls[0][0];\n\n  pass = !event;\n  if (pass) {\n    message = () =>\n      `Expected ${expectedEventConstructor.type} event ${expectation(\n        pass,\n      )} triggered with an event instance`;\n    return {pass, message};\n  }\n\n  pass = event.constructor === expectedEventConstructor;\n\n  return {\n    pass,\n    message: () =>\n      `Expected ${event.type} event ${expectation(pass)} triggered with ${\n        expectedEventConstructor.name\n      } instance`,\n  };\n}\n\nfunction toHaveBeenCalledWithEventProperties(\n  jestFunction: ReturnType<typeof jest.fn>,\n  expectedProperties: {[key: string]: any},\n) {\n  const mockFunction = jestFunction.mock;\n  const mockCalls = mockFunction.calls;\n  const event = mockCalls[0][0];\n  const expectedPropertyEntries = Object.entries(expectedProperties);\n\n  const badMatches = expectedPropertyEntries\n    .map(([key, value]) => ({key, value}))\n    .filter(({key, value}) => event[key] !== value);\n\n  const receivedPropertyEntries = Object.entries(event);\n\n  const pass = Boolean(!badMatches.length);\n\n  return {\n    pass,\n    message: () => {\n      const listOfExpectedProperties = expectedPropertyEntries.map(\n        ([key, value]) => `${key}=${JSON.stringify(value)}`,\n      );\n      const listOfReceivedProperties = receivedPropertyEntries.map(\n        ([key, value]) => `${key}=${JSON.stringify(value)}`,\n      );\n\n      return `Expected ${event.type} event ${expectation(\n        pass,\n      )} the following properties:\\n${listOfExpectedProperties.join(\n        '\\n',\n      )}\\n\\nInstead received:\\n${listOfReceivedProperties.join('\\n')}\\n`;\n    },\n  };\n}\n\nexpect.extend({\n  toHaveBeenCalledWithEvent,\n  toHaveBeenCalledWithEventProperties,\n});\n"
  },
  {
    "path": "test/matchers/index.ts",
    "content": "import './dom-event';\nimport './event';\nimport './order';\nimport './sensor';\n"
  },
  {
    "path": "test/matchers/order.ts",
    "content": "function toHaveOrder(actualOrder: HTMLElement[], expectedOrder: HTMLElement[]) {\n  const incorrectPositions = expectedOrder\n    .map((element, index) => actualOrder[index] === element)\n    .filter((isCorrectOrder) => !isCorrectOrder);\n\n  const pass = Boolean(!incorrectPositions.length);\n\n  return {\n    pass,\n    message: () => {\n      const expectation = pass ? 'not to be' : 'to be';\n      let message = `Expected order ${expectation}:\\n`;\n\n      message += expectedOrder\n        .map((element) => {\n          return `${(element.textContent || '').trim()}`;\n        })\n        .join('\\n');\n\n      message += '\\n \\nInstead received:\\n';\n\n      message += actualOrder\n        .map((element) => {\n          return `${(element.textContent || '').trim()}`;\n        })\n        .join('\\n');\n\n      return message;\n    },\n  };\n}\n\nexpect.extend({\n  toHaveOrder,\n});\n"
  },
  {
    "path": "test/matchers/sensor.ts",
    "content": "function toHaveTriggeredSensorEvent(\n  received: () => void,\n  expectedEventName: string,\n  count: number,\n) {\n  let triggered = false;\n  let callCount = 0;\n  function callback() {\n    if (count !== undefined) {\n      callCount++;\n    }\n    triggered = true;\n  }\n\n  document.addEventListener(expectedEventName, callback);\n  received();\n  document.removeEventListener(expectedEventName, callback);\n\n  const pass =\n    Boolean(triggered) && Boolean(count === undefined || callCount === count);\n\n  return {\n    pass,\n    message: () => {\n      const expectation = pass ? 'not to have been' : 'to have been';\n      const defaultMessage = `Expected sensor event '${expectedEventName}' ${expectation} to be triggered`;\n\n      return count ? `${defaultMessage} ${count} time(s)` : defaultMessage;\n    },\n  };\n}\n\nfunction toHaveCanceledSensorEvent(\n  received: () => void,\n  expectedEventName: string,\n) {\n  let canceled = false;\n\n  function callback(event: any) {\n    canceled = event.detail.canceled();\n  }\n\n  document.addEventListener(expectedEventName, callback);\n  received();\n  document.removeEventListener(expectedEventName, callback);\n\n  const pass = Boolean(canceled);\n\n  return {\n    pass,\n    message: () => {\n      const expectation = pass ? 'not to have been' : 'to have been';\n\n      return `Expected sensor event '${expectedEventName}' ${expectation} canceled`;\n    },\n  };\n}\n\nexpect.extend({\n  toHaveTriggeredSensorEvent,\n  toHaveCanceledSensorEvent,\n});\n"
  },
  {
    "path": "test/matchers/utils.ts",
    "content": "export function expectation(passed: boolean) {\n  return passed ? 'not to have' : 'to have';\n}\n"
  },
  {
    "path": "test/setup.ts",
    "content": "import './matchers';\n\n/* eslint-disable jest/require-top-level-describe */\nbeforeEach(() => {\n  jest.useFakeTimers();\n});\n\nafterEach(() => {\n  jest.runAllTimers();\n});\n/* eslint-enable jest/require-top-level-describe */\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"extends\": \"@shopify/typescript-configs/library\",\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"emitDeclarationOnly\": true,\n    \"outDir\": \"build/ts\",\n    \"rootDir\": \"./\",\n    \"strictFunctionTypes\": false,\n    \"emitDecoratorMetadata\": false,\n    \"experimentalDecorators\": false,\n    \"paths\": {\n      \"shared/*\": [\"./src/shared/*\"],\n      \"helper\": [\"./test/helper.ts\"]\n    }\n  },\n  \"include\": [\n    \"./config/typescript/**/*\",\n    \"./src/**/*\",\n    \"./test/**/*\",\n    \"./rollup.config.ts\",\n    \"./rollup.development.config.ts\"\n  ]\n}\n"
  }
]