[
  {
    "path": ".babelrc.js",
    "content": "'use strict';\n\nmodule.exports = {\n  \"presets\": [\n    [\n      \"@babel/preset-env\",\n      {\n        targets: \"> 0.25%, not dead\"\n      },\n    ],\n    \"@babel/react\",\n    \"@babel/preset-flow\"\n  ],\n  \"plugins\": [\n    \"@babel/plugin-transform-flow-comments\",\n    \"@babel/plugin-transform-class-properties\",\n    \"transform-inline-environment-variables\"\n  ]\n}\n"
  },
  {
    "path": ".browserslistrc",
    "content": "> 0.25%\nie 11\nnot dead"
  },
  {
    "path": ".eslintrc",
    "content": "{\n  \"parser\": \"@babel/eslint-parser\",\n  \"extends\": \"eslint:recommended\",\n  \"plugins\": [\n    \"react\"\n  ],\n  \"ignorePatterns\": [\"build/**/*.js\"],\n  \"rules\": {\n    \"strict\": 0,\n    \"quotes\": [1, \"single\"],\n    \"curly\": [1, \"multi-line\"],\n    \"camelcase\": 0,\n    \"comma-dangle\": 0,\n    \"no-console\": 2,\n    \"no-use-before-define\": [1, \"nofunc\"],\n    \"no-underscore-dangle\": 0,\n    \"no-unused-vars\": [1, {ignoreRestSiblings: true}],\n    \"new-cap\": 0,\n    \"prefer-const\": 1,\n    \"semi\": 1\n  },\n  env: {\n    \"browser\": true,\n    \"node\": true\n  },\n  globals: {\n    // For Flow\n    \"ReactElement\",\n    \"ReactClass\",\n    \"$Exact\",\n    \"Partial\",\n    \"$Keys\",\n    \"MouseTouchEvent\",\n  }\n}\n"
  },
  {
    "path": ".flowconfig",
    "content": "[ignore]\n<PROJECT_ROOT>/node_modules/webpack-cli.*\n<PROJECT_ROOT>/node_modules/.*malformed_package_json.*\n\n[include]\nlib/\nindex.js\n\n[options]\nsharedmemory.heap_size=3221225472\nexact_by_default=true\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n    branches: [master]\n  pull_request:\n    branches: [master]\n\njobs:\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: '20'\n          cache: 'yarn'\n\n      - name: Install dependencies\n        run: yarn install --frozen-lockfile\n\n      - name: Lint\n        run: yarn lint\n\n  test:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        node-version: [20, 22, 24]\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup Node.js ${{ matrix.node-version }}\n        uses: actions/setup-node@v4\n        with:\n          node-version: ${{ matrix.node-version }}\n          cache: 'yarn'\n\n      - name: Install dependencies\n        run: yarn install --frozen-lockfile\n\n      - name: Run unit tests\n        run: yarn test\n\n      - name: Build\n        run: yarn build\n\n  test-browser:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: '20'\n          cache: 'yarn'\n\n      - name: Install dependencies\n        run: yarn install --frozen-lockfile\n\n      - name: Build\n        run: yarn build\n\n      - name: Run browser tests\n        run: yarn test:browser\n\n  coverage:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: '20'\n          cache: 'yarn'\n\n      - name: Install dependencies\n        run: yarn install --frozen-lockfile\n\n      - name: Run tests with coverage\n        run: yarn test:coverage\n\n  typecheck:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: '20'\n          cache: 'yarn'\n\n      - name: Install dependencies\n        run: yarn install --frozen-lockfile\n\n      - name: Flow check\n        run: yarn flow\n\n      - name: TypeScript check\n        run: yarn tsc -p typings\n"
  },
  {
    "path": ".github/workflows/gh-pages.yml",
    "content": "name: Build and Deploy to GitHub Pages\npermissions:\n  contents: write\non:\n  push:\n    tags:\n      - \"*\"\n  workflow_dispatch:\njobs:\n  build-and-deploy:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout 🛎️\n        uses: actions/checkout@v4\n\n      - name: Install and Build\n        run: |\n          yarn\n          yarn build\n          yarn build-example\n\n      - name: Deploy 🚀\n        uses: JamesIves/github-pages-deploy-action@v4\n        with:\n          branch: gh-pages # The branch the action should deploy to.\n          folder: example # The folder the action should deploy.\n"
  },
  {
    "path": ".gitignore",
    "content": ".idea\n*.iml\nnode_modules/\nbuild/\nexample/build/\ncoverage/\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: node_js\nnode_js:\n  - \"10\"\n  - \"12\"\n  - \"node\" # latest\ncache: yarn\nenv:\n  - MOZ_HEADLESS=1\naddons:\n  firefox: latest\nscript:\n  - make lint\n  - make test\n  - make test-phantom\nemail:\n  on_failure: change\n  on_success: never\n\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n### 4.5.0 (Jun 25, 2025)\n\n- Internal: Update clsx version (#754)\n- Fix: bounds=\"selector\" functionality when in a Shadow DOM tree. (#763)\n- Perf: Update nodeRef type for React v19 compatibility (#769)\n- Fix: forgotten requestAnimationFrame call (#773)\n- Perf: setState in lifecycles + forced reflow (#556)\n- Fix: add allowMobileScroll prop to allow for clicks to optionally pass through on mobile (#760)\n\n### 4.4.6 (Sep 27, 2023)\n\n- Fix: state inconsistency in React 18 #699\n- Internal: devDependencies updates\n\n### 4.4.5 (Apr 26, 2022)\n\n- Fix: `grid` prop unused in `handleDragStop` #621\n- Fix: `children` prop missing in TypeScript definition #648\n- Internal: Various devDep updates\n\n### 4.4.4 (Aug 27, 2021)\n\n- Fix: Ensure `documentElement.style` actually exists. Fixes crashes in some obscure environments. #574 #575\n- Fix: Add react/react-dom as `peerDependencies` again to fix Yarn PnP\n- Size: Replace `classnames` with `clsx` to save a few bytes\n- Internal: Additional tests on `ref` functionality and additional README content on `nodeRef`\n- Internal: Lots of devDependencies updates\n- Docs: Various README and demo updates, see git commits\n\n### 4.4.3 (June 8, 2020)\n\n- Add `nodeRef` to TypeScript definitions\n\n### 4.4.2 (May 14, 2020)\n\n- Fix: Remove \"module\" from package.json (it is no longer being built)\n  - Fixes #482\n\n### 4.4.1 (May 12, 2020)\n\n- Fix: Remove \"module\" definition in package.json\n  - Giving up on this: there isn't a great reason to publish modules\n    here as they won't be significantly tree-shook, and it bloats\n    the published package.\n  - Fixes incompatiblity in 4.4.0 with webpack, where webpack is now\n    selecting \"module\" because \"browser\" is no longer present.\n\n### 4.4.0 (May 12, 2020)\n\n- Add `nodeRef`:\n  - If running in React Strict mode, ReactDOM.findDOMNode() is deprecated.\n    Unfortunately, in order for `<Draggable>` to work properly, we need raw access\n    to the underlying DOM node. If you want to avoid the warning, pass a `nodeRef`\n    as in this example:\n    ```js\n    function MyComponent() {\n      const nodeRef = React.useRef(null);\n      return (\n        <Draggable nodeRef={nodeRef}>\n          <div ref={nodeRef}>Example Target</div>\n        </Draggable>\n      );\n    }\n    ````\n    This can be used for arbitrarily nested components, so long as the ref ends up\n    pointing to the actual child DOM node and not a custom component.\n    Thanks to react-transition-group for the inspiration.\n    `nodeRef` is also available on `<DraggableCore>`.\n- Remove \"browser\" field in \"package.json\":\n  - There is nothing special in the browser build that is actually practical\n    for modern use. The \"browser\" field, as defined in \n    https://github.com/defunctzombie/package-browser-field-spec#overview,\n    indicates that you should use it if you are directly accessing globals,\n    using browser-specific features, dom manipulation, etc.\n    \n    React components like react-draggable are built to do minimal raw\n    DOM manipulation, and to always gate this behind conditionals to ensure\n    that server-side rendering still works. We don't make any changes\n    to any of that for the \"browser\" build, so it's entirely redundant.\n    \n    This should also fix the \"Super expression must either be null or\n    a function\" error (#472) that some users have experienced with particular\n    bundler configurations.\n\n    The browser build may still be imported at \"build/web/react-draggable.min.js\".\n    This is to prevent breakage only. The file is no longer minified to prevent\n    possible [terser bugs](https://github.com/terser/terser/issues/308).\n  - The browser build will likely be removed entirely in 5.0.\n- Fix: Make `bounds` optional in TypeScript [#473](https://github.com/strml/react-draggable/pull/473)\n\n### 4.3.1 (Apr 11, 2020) \n\n> This is a bugfix release.\n\n- Happy Easter!\n- Fixed a serious bug that caused `<DraggableCore>` not to pass styles.\n  - `React.cloneElement` has an odd quirk. When you do:\n    ```js\n    return React.cloneElement(this.props.children, {style: this.props.children.props.style});\n    ```\n    , `style` ends up undefined.\n- Fixed a bug that caused debug output to show up in the build. \n  - `babel-loader` cache does not invalidate when it should. I had modified webpack.config.js in the last version but it reused stale cache.\n\n### 4.3.0 (Apr 10, 2020)\n\n- Fix setState warning after dismount if drag still active. Harmless, but prints a warning. [#424](https://github.com/mzabriskie/react-draggable/pull/424)\n- Fix a long-standing issue where text inputs would unfocus upon dismounting a `<Draggable>`.\n  - Thanks @schnerd, [#450](https://github.com/mzabriskie/react-draggable/pull/450)\n- Fix an issue where the insides of a `<Draggable>` were not scrollable on touch devices due to the outer container having `touch-action: none`.\n    - This was a long-standing hack for mobile devices. Without it, the page will scroll while you drag the element.\n    - The new solution will simply cancel the touch event `e.preventDefault()`. However, due to changes in Chrome >= 56, this is only possible on \n      non-passive event handlers. To fix this, we now add/remove the touchEvent on lifecycle events rather than using React's event system.\n    - [#465](https://github.com/mzabriskie/react-draggable/pull/465)\n- Upgrade devDeps and fix security warnings. None of them actually applied to this module.\n\n### 4.2.0 (Dec 2, 2019)\n\n- Fix: Apply `scale` parameter also while dragging an element. [#438](https://github.com/mzabriskie/react-draggable/pull/438)\n- Fix: Don't ship `process.env.DRAGGABLE_DEBUG` checks in cjs/esm. [#445](https://github.com/mzabriskie/react-draggable/pull/445)\n\n### 4.1.0 (Oct 25, 2019)\n\n- Add `\"module\"` to `package.json`. There are now three builds:\n  * **`\"main\"`**: ES5-compatible CJS build, suitable for most use cases with maximum compatibility.\n    - For legacy reasons, this has exports of the following shape, which ensures no surprises in CJS or ESM polyfilled environments:\n      ```js\n      module.exports = Draggable;\n      module.exports.default = Draggable;\n      module.exports.DraggableCore = DraggableCore;\n      ```\n  * **`\"web\"`**: Minified UMD bundle exporting to `window.ReactDraggable` with the same ES compatibility as the \"main\" build.\n  * **`\"module\"`**: ES6-compatible build using import/export.\n\n  This should fix issues like https://github.com/STRML/react-resizable/issues/113 while allowing modern bundlers to consume esm modules in the future.\n  \n  No compatibility changes are expected.\n\n### 4.0.3 (Sep 10, 2019)\n\n- Add typings and sourcemap to published npm package.\n  - This compresses well so it does not bloat the package by much. Would be nice if npm had another delivery mechanism for optional modes, like web/TS.\n\n### 4.0.2 (Sep 9, 2019)\n\n- Republish to fix packaging errors. Fixes #426\n\n### 4.0.1 (Sep 7, 2019)\n\n- Republish of 4.0.0 to fix a mistake where webpack working files were erroneously included in the package. Use this release instead as it is much smaller.\n\n### 4.0.0 (Aug 26, 2019)\n\n> This is a major release due to a React compatibility change. If you are already on React >= 16.3, this upgrade is non-breaking.\n\n- *Requires React 16.3+ due to use of `getDerivedStateFromProps`.\n  - See https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html for why this was done.\n- Upgraded build environment to Babel 7.\n- Switched build from rollup to webpack@4 to simplify.\n- Added CJS build that does not bundle `classNames` & `prop-types` into the build. This should result in marginally smaller bundle sizes for applications that use bundlers.\n- Removed Bower build.\n\n### 3.3.2 (Aug 16, 2019)\n\n- Use `all: inherit` instead of `background: transparent;` to fix selection styles.\n  - Fixes https://github.com/mzabriskie/react-draggable/issues/315\n\n### 3.3.1 (Aug 12, 2019)\n\n- Fix React 16.9 `componentWillMount` deprecation.\n\n### 3.3.0 (Apr 18, 2019)\n\n- Addition of `positionOffset` prop, which can be Numbers (px) or string percentages (like `\"10%\"`). See the README for more.\n\n### 3.2.1 (Mar 1, 2019)\n\n- Reverted https://github.com/mzabriskie/react-draggable/pull/361.\n\n### ~3.2.0 (Feb 27, 2019)~\n\n> Note: this release has been pulled due to an inadvertent breaking change. See https://github.com/mzabriskie/react-draggable/issues/391\n\n- Feature: `defaultPosition` now allows string offsets (like {x: '10%', y: '10%'}) then calculates deltas from there. See the examples and the PR [#361](https://github.com/mzabriskie/react-draggable/pull/361/). Thanks to @tnrich and @eric-burel.\n- Bugfix: Export `DraggableEvent` type for Flow consumers. Thanks @elie222.\n\n### 3.1.1 (Dec 21, 2018)\n\n- Bugfix: Minor type change on DraggableEventHandler TypeScript export ([#374](https://github.com/mzabriskie/react-draggable/pull/374))\n\n### 3.1.0 (Dec 21, 2018)\n\n- Feature: Added `scale` prop ([#352](https://github.com/mzabriskie/react-draggable/pull/352))\n  - Thanks, @wootencl\n- Bugfix: Remove process.browser which is missing in browser ([#329]((https://github.com/mzabriskie/react-draggable/pull/329))\n- Bugfix: Fix selection api on IE ([#292](https://github.com/mzabriskie/react-draggable/pull/292))\n- Bugfix: Fixes some issues in the type definitions for TypeScript ([#331]((https://github.com/mzabriskie/react-draggable/pull/331))\n- Bugfix: Fix compare where portal elements are different instance to main window ([#359]((https://github.com/mzabriskie/react-draggable/pull/359))\n\n### 3.0.5 (Jan 11, 2018)\n\n- Bugfix: Fix crash in test environments during removeUserSelectStyles().\n\n### 3.0.4 (Nov 27, 2017)\n\n- Bugfix: Fix \"Cannot call property 'call' of undefined\" (matchesSelector)\n  - Fixes [#300](https://github.com/mzabriskie/react-draggable/issues/300)\n\n### 3.0.3 (Aug 31, 2017)\n\n- Bugfix: Fix deprecation warnings caused by `import * as React` (Flow best practice).\n  - See https://github.com/facebook/react/issues/10583\n\n### 3.0.2 (Aug 22, 2017)\n\n> 3.0.0 and 3.0.1 have been unpublished due to a large logfile making it into the package.\n\n- Bugfix: Tweaked `.npmignore`.\n\n### 3.0.1 (Aug 21, 2017)\n\n- Bugfix: Flow-type should no longer throw errors for consumers.\n  - It appears Flow can't resolve a sub-package's interfaces.\n\n### 3.0.0 (Aug 21, 2017)\n\n> Due to an export change, this is semver-major.\n\n- Breaking: For TypeScript users, `<Draggable>` is now exported as `module.exports` and `module.exports.default`.\n- Potentially Breaking: We no longer set `user-select: none` on all elements while dragging. Instead,\n  the [`::selection` psuedo element](https://developer.mozilla.org/en-US/docs/Web/CSS/::selection) is used.\n  - Depending on your application, this could cause issues, so be sure to test.\n- Bugfix: Pass bounded `x`/`y` to callbacks. See [#226](https://github.com/mzabriskie/react-draggable/pull/226).\n- Internal: Upgraded dependencies.\n\n### 2.2.6 (Apr 30, 2017)\n\n- Bugfix: Missing export default on TS definition (thanks @lostfictions)\n- Internal: TS test suite (thanks @lostfictions)\n\n### 2.2.5 (Apr 28, 2017)\n\n- Bugfix: Typescript definition was incorrect. [#244](https://github.com/mzabriskie/react-draggable/issues/244)\n\n### 2.2.4 (Apr 27, 2017)\n\n- Internal: Moved `PropTypes` access to `prop-types` package for React 15.5 (prep for 16)\n- Feature: Added TypeScript definitions (thanks @erfangc)\n- Bugfix: No longer can erroneously add user-select style multiple times\n- Bugfix: OffsetParent with padding problem, fixes [#218](https://github.com/mzabriskie/react-draggable/issues/218)\n- Refactor: Misc example updates.\n\n### 2.2.3 (Nov 21, 2016)\n\n- Bugfix: Fix an issue with the entire window scrolling on a drag on iDevices. Thanks @JaneCoder. See #183\n\n### 2.2.2 (Sep 14, 2016)\n\n- Bugfix: Fix references to global when grabbing `SVGElement`, see [#162](https://github.com/mzabriskie/react-draggable/issues/162)\n- Bugfix: Get `ownerDocument` before `onStop`, fixes [#198](https://github.com/mzabriskie/react-draggable/issues/198)\n\n### 2.2.1 (Aug 11, 2016)\n\n- Bugfix: Fix `getComputedStyle` error: see [#186](https://github.com/mzabriskie/react-draggable/issues/186), #190\n\n### 2.2.0 (Jul 29, 2016)\n\n- Addition: `offsetParent` property for an arbitrary ancestor for offset calculations.\n  - Fixes e.g. dragging with a floating `offsetParent`.\n    - Ref: https://github.com/mzabriskie/react-draggable/issues/170\n- Enhancement: Make this library iframe-aware.\n  - Ref: https://github.com/mzabriskie/react-draggable/pull/177\n  - Thanks to @acusti for tests\n- Bugfix: Lint/Test Fixes for new Flow & React versions\n\n### 2.1.2 (Jun 5, 2016)\n\n- Bugfix: Fix `return false` to cancel `onDrag` breaking on both old and new browsers due to missing `typeArg` and/or\n  unsupported `MouseEventConstructor`. Fixes [#164](https://github.com/mzabriskie/react-draggable/issues/164).\n\n### 2.1.1 (May 22, 2016)\n\n- Bugfix: `<DraggableCore>` wasn't calling back with the DOM node.\n- Internal: Rework test suite to use power-assert.\n\n### 2.1.0 (May 20, 2016)\n\n- Fix improperly missed `handle` or `cancel` selectors if the event originates from a child of the handle or cancel.\n  - Fixes a longstanding issue, [#88](https://github.com/mzabriskie/react-draggable/pull/88)\n  - This was pushed to a minor release as there may be edge cases (perhaps workarounds) where this changes behavior.\n\n### 2.0.2 (May 19, 2016)\n\n- Fix `cannot access clientX of undefined` on some touch-enabled platforms.\n  - Fixes [#159](https://github.com/mzabriskie/react-draggable/pull/159),\n    [#118](https://github.com/mzabriskie/react-draggable/pull/118)\n- Fixed a bug with multi-finger multitouch if > 1 finger triggered an event at the same time.\n\n### 2.0.1 (May 19, 2016)\n\n- Finally fixed the IE10 constructor bug. Thanks @davidstubbs [#158](https://github.com/mzabriskie/react-draggable/pull/158)\n\n### 2.0.0 (May 10, 2016)\n\n- This is a breaking change. See the changes below in the beta releases.\n  - Note the changes to event callbacks and `position` / `defaultPosition`.\n- Changes from 2.0.0-beta3:\n  - Small bugfixes for Flow 0.24 compatibility.\n  - Don't assume `global.SVGElement`. Fixes JSDOM & [#123](https://github.com/mzabriskie/react-draggable/issues/123).\n\n### 2.0.0-beta3 (Apr 19, 2016)\n\n- Flow comments are now in the build. Other projects, such as React-Grid-Layout and React-Resizable, will\n  rely on them in their build and export their own comments.\n\n### 2.0.0-beta2 (Apr 14, 2016)\n\n- We're making a small deviation from React Core's controlled vs. uncontrolled scheme; for convenience,\n  `<Draggable>`s with a `position` property will still be draggable, but will revert to their old position\n  on drag stop. Attach an `onStop` or `onDrag` handler to synchronize state.\n  - A warning has been added informing users of this. If you make `<Draggable>` controlled but attach no callback\n    handlers, a warning will be printed.\n\n### 2.0.0-beta1 (Apr 14, 2016)\n\n- Due to API changes, this is a major release.\n\n#### Breaking Changes:\n\n- Both `<DraggableCore>` and `<Draggable>` have had their callback types changed and unified.\n\n```js\ntype DraggableEventHandler = (e: Event, data: DraggableData) => void | false;\ntype DraggableData = {\n  node: HTMLElement,\n  // lastX + deltaX === x\n  x: number, y: number,\n  deltaX: number, deltaY: number,\n  lastX: number, lastY: number\n};\n```\n\n- The `start` option has been renamed to `defaultPosition`.\n- The `zIndex` option has been removed.\n\n#### Possibly Breaking Changes:\n\n- When determining deltas, we now use a new method that checks the delta against the Draggable's `offsetParent`.\n  This method allows us to support arbitrary nested scrollable ancestors without scroll handlers!\n  - This may cause issues in certain layouts. If you find one, please open an issue.\n\n#### Enhancements:\n\n- `<Draggable>` now has a `position` attribute. Its relationship to `defaultPosition` is much like\n  `value` to `defaultValue` on React `<input>` nodes. If set, the position is fixed and cannot be mutated.\n  If empty, the component will manage its own state. See [#140](https://github.com/mzabriskie/react-draggable/pull/140)\n  for more info & motivations.\n- Misc. bugfixes.\n\n### 1.4.0-beta1 (Apr 13, 2016)\n\n- Major improvements to drag tracking that now support even nested scroll boxes.\n  - This revision is being done as a pre-release to ensure there are no unforeseen issues with the offset changes.\n\n### 1.3.7 (Apr 8, 2016)\n\n- Fix `user-select` prefixing, which may be different than the prefix required for `transform`.\n\n### 1.3.6 (Apr 8, 2016)\n\n- Republished after 1.3.5 contained a bundling error.\n\n### 1.3.5 (Apr 8, 2016)\n\n- Add React v15 to devDeps. `<Draggable>` supports both `v0.14` and `v15`.\n- Enhancement: Clean up usage of browser prefixes; modern browsers will no longer use them.\n  - This also removes the duplicated `user-select` style that is created on the `<body>` while dragging.\n- Internal: Test fixes.\n\n### 1.3.4 (Mar 5, 2016)\n\n- Bugfix: Scrolling while dragging caused items to move unpredictably.\n\n### 1.3.3 (Feb 11, 2016)\n\n- Bugfix: #116: Android/Chrome are finicky; give up on canceling ghost clicks entirely.\n\n### 1.3.2 (Feb 11, 2016)\n\n- Bugfix: #116: Child inputs not focusing on touch events.\n\n### 1.3.1 (Feb 10, 2016)\n\n- Internal: Babel 6 and Flow definitions\n- Bugfix: 1.3.0 broke string bounds ('parent', selectors, etc.).\n- Bugfix: 1.3.0 wasn't updating deltaX and deltaY on a bounds hit.\n\n### 1.3.0 (Feb 10, 2016)\n\n- Possibly breaking change: bounds are calculated before `<Draggable>` fires `drag` events, as they should have been.\n- Added `'none'` axis type. This allows using `<Draggable>` somewhat like `<DraggableCore>` - state will be kept\n  internally (which makes bounds checks etc possible), but updates will not be flushed to the DOM.\n- Performance tweaks.\n\n### 1.2.0 (Feb 5, 2016)\n\n- Added arbitrary boundary selector. Now you don't have to just use `'parent'`, you can select any element\n  on the page, including `'body'`.\n- Bugfix: Prevent invariant if a `<Draggable>` is unmounted while dragging.\n- Bugfix: Fix #133, where items would eagerly start dragging off the mouse cursor if you hit boundaries and\n  came back. This is due to how `<DraggableCore>` handles deltas only and does not keep state. Added new state\n  properties `slackX` and `slackY` to `<Draggable>` to handle this and restore pre-v1 behavior.\n\n### 1.1.3 (Nov 25, 2015)\n\n- Bugfix: Server-side rendering with react-rails, which does bad things like mock `window`\n\n### 1.1.2 (Nov 23, 2015)\n\n- Bugfix: `<Draggable>` was calling back with clientX/Y, not offsetX/Y as it did pre-1.0. This unintended\n  behavior has been fixed and a test has been added.\n\n### 1.1.1 (Nov 14, 2015)\n\n- Bugfix: Clean up scroll events if a component is unmounted before drag stops.\n- Bugfix: `NaN` was returning from scroll events due to event structure change.\n- Add scroll drag modulation test.\n\n### 1.1.0 (Nov 14, 2015)\n\n- Move `grid` into `<DraggableCore>` directly. It will continue to work on `<Draggable>`.\n- Development fixes.\n\n### 1.0.2 (Nov 7, 2015)\n\n- Fix `enableUserSelectHack` not properly disabling.\n- Fix a crash when the user scrolls the page with a Draggable active.\n\n### 1.0.1 (Oct 28, 2015)\n\n- Fix missing dist files for webpack.\n- Ignore non-primary clicks. Added `allowAnyClick` option to allow other click types.\n\n### 1.0.0 (Oct 27, 2015)\n\n- Breaking: Removed `resetState()` instance method\n- Breaking: Removed `moveOnStartChange` prop\n- Breaking: React `0.14` support only.\n- Refactored project.\n- Module now exports a `<DraggableCore>` element upon which `<Draggable>` is based.\n  This module is useful for building libraries and is completely stateless.\n\n### 0.8.5 (Oct 20, 2015)\n\n- Bugfix: isElementSVG no longer can be overwritten by getInitialState (#83)\n- Bugfix: Fix for element prefixes in JSDOM\n\n### 0.8.4 (Oct 15, 2015)\n\n- Bugfix: SVG elements now properly use `transform` attribute instead of `style`. Thanks @martinRoss\n\n### 0.8.3 (Oct 12, 2015)\n\n- Bugfix: Short-circuiting drag throws due to `e.changedTouches` check.\n\n### 0.8.2 (Sep 21, 2015)\n\n- Handle scrolling while dragging. (#60)\n- Add multi-touch support. (#68)\n- IE fixes.\n- Documentation updates. (#77)\n\n### 0.8.1 (June 3, 2015)\n\n- Add `resetState()` instance method for use by parents. See README (\"State Problems?\").\n\n### 0.8.0 (May 19, 2015)\n\n- Touch/mouse events rework. Fixes #51, #37, and #43, as well as IE11 support.\n- Moved mousemove/mouseup and touch event handlers to document from window. Fixes IE9/10 support.\n  IE8 is still not supported, as it is not supported by React.\n\n### 0.7.4 (May 18, 2015)\n\n- Fix a bug where a quick drag out of bounds to `0,0` would cause the element to remain in an inaccurate position,\n  because the translation was removed from the CSS. See #55.\n\n### 0.7.3 (May 13, 2015)\n\n- Removed a `moveOnStartChange` optimization that was causing problems when attempting to move a `<Draggable>` back\n  to its initial position. See https://github.com/STRML/react-grid-layout/issues/56\n\n### 0.7.2 (May 8, 2015)\n\n- Added `moveOnStartChange` property. See README.\n\n### 0.7.1 (May 7, 2015)\n\n- The `start` param is back. Pass `{x: Number, y: Number}` to kickoff the CSS transform. Useful in certain\n  cases for simpler callback math (so you don't have to know its existing relative position and add it to\n  the dragged position). Fixes #52.\n\n### 0.7.0 (May 7, 2015)\n\n- Breaking change: `bounds` with coordinates was confusing because it was using the item's width/height,\n  which was not intuitive. When providing coordinates, `bounds` now simply restricts movement in each\n  direction by that many pixels.\n\n### 0.6.0 (May 2, 2015)\n\n- Breaking change: Cancel dragging when onDrag or onStart handlers return an explicit `false`.\n- Fix sluggish movement when `grid` option was active.\n- Example updates.\n- Move `user-select:none` hack to document.body for better highlight prevention.\n- Add `bounds` option to restrict dragging within parent or within coordinates.\n\n### 0.5.0 (May 2, 2015)\n\n- Remove browserify browser config, reactify, and jsx pragma. Fixes #38\n- Use React.cloneElement instead of addons cloneWithProps (requires React 0.13)\n- Move to CSS transforms. Simplifies implementation and fixes #48, #34, #31.\n- Fixup linting and space/tab errors. Fixes #46.\n\n### 0.4.3 (Apr 30, 2015)\n\n- Fix React.addons error caused by faulty test.\n\n### 0.4.2 (Apr 30, 2015)\n\n- Add `\"browser\"` config to package.json for browserify imports (fix #45).\n- Remove unnecessary `emptyFunction` and `React.addons.classSet` imports.\n\n### 0.4.1 (Apr 30, 2015)\n\n- Remove react/addons dependency (now depending on `react` directly).\n- Add MIT License file.\n- Fix an issue where browser may be detected as touch-enabled but touch event isn't thrown.\n\n### 0.4.0 (Jan 03, 2015)\n\n- Improving accuracy of snap to grid\n- Updating to React 0.12\n- Adding dragging className\n- Adding reactify support for browserify\n- Fixing issue with server side rendering\n\n### 0.3.0 (Oct 21, 2014)\n\n- Adding support for touch devices\n\n### 0.2.1 (Sep 10, 2014)\n\n- Exporting as ReactDraggable\n\n### 0.2.0 (Sep 10, 2014)\n\n- Adding support for snapping to a grid\n- Adding support for specifying start position\n- Ensure event handlers are destroyed on unmount\n- Adding browserify support\n- Adding bower support\n\n### 0.1.1 (Jul 26, 2014)\n\n- Fixing dragging not stopping on mouseup in some cases\n\n### 0.1.0 (Jul 25, 2014)\n\n- Initial release\n"
  },
  {
    "path": "CLAUDE.md",
    "content": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Build Commands\n\n**This project uses Yarn, not npm.** Always use `yarn` for package management.\n\nThis project uses Make for builds. Key commands:\n\n- `make build` - Full build (cleans, then builds CJS and web bundles)\n- `make lint` - Runs Flow type checker, ESLint, and TypeScript type checking on typings\n- `make test` - Runs unit tests via Vitest\n- `make test-browser` - Runs browser tests via Puppeteer (requires build first)\n- `make test-all` - Runs both unit and browser tests\n- `make dev` - Starts webpack dev server with example page at localhost:8080\n\nYarn scripts:\n- `yarn test` - Run unit tests (jsdom environment)\n- `yarn test:watch` - Run unit tests in watch mode\n- `yarn test -- path/to/test.jsx` - Run a single test file\n- `yarn test -- -t \"test name\"` - Run tests matching a pattern\n- `yarn test:browser` - Build and run browser tests (Puppeteer)\n- `yarn test:all` - Run all tests\n- `yarn test:coverage` - Run tests with coverage report\n\nPre-commit hooks run `make lint` and `make test` automatically.\n\n## Test Architecture\n\nTests are split into two categories:\n\n1. **Unit tests** (`test/*.test.{js,jsx}`) - Run in jsdom via Vitest\n   - Fast, no browser required\n   - Test component logic, callbacks, prop handling\n   - Some coordinate-based tests skipped (require real browser)\n\n2. **Browser tests** (`test/browser/*.test.js`) - Run in headless Chrome via Puppeteer\n   - Test actual drag behavior with real coordinate calculations\n   - Test transforms, axis constraints, bounds, scale\n\n### Test Utilities\n\n`test/testUtils.js` provides helpers for simulating drag events:\n- `simulateDrag(element, {from, to})` - Complete drag operation\n- `startDrag(element, {x, y})` / `moveDrag({x, y})` / `endDrag(element, {x, y})` - Step-by-step drag\n- `simulateTouchDrag(element, {from, to})` - Touch event drag\n\n## Architecture\n\n### Component Hierarchy\n\n**DraggableCore** (`lib/DraggableCore.js`) - Low-level component that handles raw drag events\n- Maintains minimal internal state (just `dragging`, `lastX`, `lastY`)\n- Manages mouse/touch event binding and cleanup\n- Provides `onStart`, `onDrag`, `onStop` callbacks with position data\n- Use this when you need full control over positioning\n\n**Draggable** (`lib/Draggable.js`) - High-level wrapper around DraggableCore\n- Manages position state, bounds checking, axis constraints\n- Applies CSS transforms or SVG transform attributes\n- Supports controlled (`position` prop) and uncontrolled (`defaultPosition`) modes\n- Adds dragging-related CSS classes\n\n### Key Utilities\n\n- `lib/utils/domFns.js` - DOM helpers: event binding, CSS transforms, user-select hacks\n- `lib/utils/positionFns.js` - Position calculations: bounds checking, grid snapping, delta computations\n- `lib/utils/getPrefix.js` - Browser prefix detection for CSS transforms\n\n### Build Outputs\n\n- `build/cjs/` - CommonJS build (Babel)\n- `build/web/react-draggable.min.js` - UMD browser bundle (Webpack)\n\n### Type Systems\n\nThe codebase uses Flow for internal type checking (`// @flow` annotations) and ships TypeScript definitions in `typings/index.d.ts`. Both must stay in sync when modifying component props or types.\n\n## Key Patterns\n\n### nodeRef Pattern\nTo avoid ReactDOM.findDOMNode deprecation warnings in Strict Mode, components accept a `nodeRef` prop:\n```jsx\nconst nodeRef = React.useRef(null);\n<Draggable nodeRef={nodeRef}>\n  <div ref={nodeRef}>Content</div>\n</Draggable>\n```\n\n### Callback Return Values\nReturning `false` from `onStart`, `onDrag`, or `onStop` cancels the drag operation.\n\n### CSS Transform Approach\nDragging uses CSS transforms (`translate`) rather than absolute positioning, allowing draggable elements to work regardless of their CSS position value.\n\n## Release Process\n\n- Update CHANGELOG.md\n- `make release-patch`, `make release-minor`, or `make release-major`\n- `make publish`\n"
  },
  {
    "path": "LICENSE",
    "content": "(MIT License)\n\nCopyright (c) 2014-2016 Matt Zabriskie. All rights reserved.\n \nPermission is hereby granted, free of charge, to any person obtaining a\ncopy of this software and associated documentation files (the \"Software\"), \nto deal in the Software without restriction, including without limitation \nthe rights to use, copy, modify, merge, publish, distribute, sublicense, \nand/or sell copies of the Software, and to permit persons to whom the \nSoftware is furnished to do so, subject to the following conditions:\n \nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n \nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING \nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER \nDEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "# Mostly lifted from https://andreypopp.com/posts/2013-05-16-makefile-recipes-for-node-js.html\n# Thanks @andreypopp\n\n# Make it parallel\nMAKEFLAGS += j4\nexport BIN := $(shell yarn bin)\n.PHONY: test dev lint build build-cjs build-esm build-web clean install link publish\n.DEFAULT_GOAL := build\n\nclean:\n\trm -rf build\n\tmkdir -p build\n\nlint:\n\t@$(BIN)/flow\n\t@$(BIN)/eslint lib/* lib/utils/*\n\t@$(BIN)/tsc -p typings\n\nbuild: clean build-cjs build-esm build-web\n\nbuild-cjs: $(BIN)\n\t$(BIN)/babel --out-dir ./build/cjs ./lib\n\nbuild-web: $(BIN)\n\t$(BIN)/webpack --mode=production\n\n# Allows usage of `make install`, `make link`\ninstall link:\n\t@yarn $@\n\ntest: $(BIN)\n\t@$(BIN)/vitest run\n\ntest-browser: build $(BIN)\n\t@$(BIN)/vitest run --config vitest.browser.config.js\n\ntest-all: test test-browser\n\ndev: $(BIN) clean\n\tenv DRAGGABLE_DEBUG=1 $(BIN)/webpack serve --mode=development\n\nnode_modules/.bin: install\n\ndefine release\n\tVERSION=`node -pe \"require('./package.json').version\"` && \\\n\tNEXT_VERSION=`node -pe \"require('semver').inc(\\\"$$VERSION\\\", '$(1)')\"` && \\\n\tnode -e \"\\\n\t\t['./package.json'].forEach(function(fileName) {\\\n\t\t\tvar j = require(fileName);\\\n\t\t\tj.version = \\\"$$NEXT_VERSION\\\";\\\n\t\t\tvar s = JSON.stringify(j, null, 2);\\\n\t\t\trequire('fs').writeFileSync(fileName, s);\\\n\t\t});\" && \\\n\tgit add package.json CHANGELOG.md && \\\n\tgit commit -m \"release v$$NEXT_VERSION\" && \\\n\tgit tag \"v$$NEXT_VERSION\" -m \"release v$$NEXT_VERSION\"\nendef\n\nrelease-patch: test\n\t@$(call release,patch)\n\nrelease-minor: test\n\t@$(call release,minor)\n\nrelease-major: test\n\t@$(call release,major)\n\npublish: build\n\tgit push --tags origin HEAD:master\n\tyarn publish\n"
  },
  {
    "path": "README.md",
    "content": "# React-Draggable\n\n[![CI](https://github.com/react-grid-layout/react-draggable/actions/workflows/ci.yml/badge.svg)](https://github.com/react-grid-layout/react-draggable/actions/workflows/ci.yml)\n[![npm version](https://img.shields.io/npm/v/react-draggable.svg)](https://www.npmjs.com/package/react-draggable)\n[![npm downloads](https://img.shields.io/npm/dt/react-draggable.svg)](https://www.npmjs.com/package/react-draggable)\n[![gzip size](https://img.badgesize.io/https://npmcdn.com/react-draggable/build/web/react-draggable.min.js?compression=gzip)](https://npmcdn.com/react-draggable/build/web/react-draggable.min.js)\n\nA simple component for making elements draggable.\n\n[**[Demo](https://react-grid-layout.github.io/react-draggable/) | [Changelog](CHANGELOG.md)**]\n\n<p align=\"center\">\n  <img src=\"https://user-images.githubusercontent.com/6365230/95649276-f3a02480-0b06-11eb-8504-e0614a780ba4.gif\" />\n</p>\n\n```jsx\n<Draggable>\n  <div>I can now be moved around!</div>\n</Draggable>\n```\n\n## Table of Contents\n\n- [Installation](#installation)\n- [Compatibility](#compatibility)\n- [Quick Start](#quick-start)\n- [API](#api)\n  - [Draggable](#draggable)\n  - [DraggableCore](#draggablecore)\n- [Using nodeRef](#using-noderef)\n- [Controlled vs. Uncontrolled](#controlled-vs-uncontrolled)\n- [Contributing](#contributing)\n\n## Installation\n\n```bash\nnpm install react-draggable\n# or\nyarn add react-draggable\n```\n\n```js\n// ES Modules\nimport Draggable from 'react-draggable';\nimport { DraggableCore } from 'react-draggable';\n\n// CommonJS\nconst Draggable = require('react-draggable');\nconst { DraggableCore } = require('react-draggable');\n```\n\nTypeScript types are included.\n\n## Compatibility\n\n| Version | React Version |\n|---------|---------------|\n| 4.x     | 16.3+         |\n| 3.x     | 15 - 16       |\n| 2.x     | 0.14 - 15     |\n\n## Quick Start\n\n```jsx\nimport React, { useRef } from 'react';\nimport Draggable from 'react-draggable';\n\nfunction App() {\n  const nodeRef = useRef(null);\n\n  return (\n    <Draggable nodeRef={nodeRef}>\n      <div ref={nodeRef}>Drag me!</div>\n    </Draggable>\n  );\n}\n```\n\nView the [Demo](https://react-grid-layout.github.io/react-draggable/) and its [source](/example/example.js) for more examples.\n\n## API\n\n### `<Draggable>`\n\nA `<Draggable>` element wraps an existing element and extends it with new event handlers and styles. It does not create a wrapper element in the DOM.\n\nDraggable items are moved using CSS Transforms. This allows items to be dragged regardless of their current positioning (relative, absolute, or static). Elements can also be moved between drags without incident.\n\nIf the item you are dragging already has a CSS Transform applied, it will be overwritten by `<Draggable>`. Use an intermediate wrapper (`<Draggable><span>...</span></Draggable>`) in this case.\n\n#### Props\n\n```ts\ntype DraggableEventHandler = (e: Event, data: DraggableData) => void | false;\n\ntype DraggableData = {\n  node: HTMLElement,\n  x: number, y: number,\n  deltaX: number, deltaY: number,\n  lastX: number, lastY: number,\n};\n```\n\n| Prop | Type | Default | Description |\n|------|------|---------|-------------|\n| `allowAnyClick` | `boolean` | `false` | Allow dragging on non-left-button clicks |\n| `allowMobileScroll` | `boolean` | `false` | Don't prevent `touchstart`, allowing scrolling inside containers |\n| `axis` | `'both' \\| 'x' \\| 'y' \\| 'none'` | `'both'` | Axis to allow dragging on |\n| `bounds` | `object \\| string` | - | Restrict movement. Use `'parent'`, a CSS selector, or `{left, top, right, bottom}` |\n| `cancel` | `string` | - | CSS selector for elements that should not initiate drag |\n| `defaultClassName` | `string` | `'react-draggable'` | Class name applied to the element |\n| `defaultClassNameDragging` | `string` | `'react-draggable-dragging'` | Class name applied while dragging |\n| `defaultClassNameDragged` | `string` | `'react-draggable-dragged'` | Class name applied after drag |\n| `defaultPosition` | `{x: number, y: number}` | `{x: 0, y: 0}` | Starting position |\n| `disabled` | `boolean` | `false` | Disable dragging |\n| `enableUserSelectHack` | `boolean` | `true` | Add `user-select: none` while dragging |\n| `grid` | `[number, number]` | - | Snap to grid `[x, y]` |\n| `handle` | `string` | - | CSS selector for the drag handle |\n| `nodeRef` | `React.RefObject` | - | Ref to the DOM element. Required for React Strict Mode |\n| `offsetParent` | `HTMLElement` | - | Custom offsetParent for drag calculations |\n| `onDrag` | `DraggableEventHandler` | - | Called while dragging |\n| `onMouseDown` | `(e: MouseEvent) => void` | - | Called on mouse down |\n| `onStart` | `DraggableEventHandler` | - | Called when dragging starts. Return `false` to cancel |\n| `onStop` | `DraggableEventHandler` | - | Called when dragging stops |\n| `position` | `{x: number, y: number}` | - | Controlled position |\n| `positionOffset` | `{x: number \\| string, y: number \\| string}` | - | Position offset (supports percentages) |\n| `scale` | `number` | `1` | Scale factor for dragging inside transformed parents |\n\n**Note:** Setting `className`, `style`, or `transform` on `<Draggable>` will error. Set them on the child element.\n\n### `<DraggableCore>`\n\nFor users that require full control, `<DraggableCore>` provides drag callbacks without managing state or styles. It does not set any transforms; you must handle positioning yourself.\n\nSee [React-Resizable](https://github.com/react-grid-layout/react-resizable) and [React-Grid-Layout](https://github.com/react-grid-layout/react-grid-layout) for usage examples.\n\n#### Props\n\n`<DraggableCore>` accepts a subset of `<Draggable>` props:\n\n- `allowAnyClick`\n- `allowMobileScroll`\n- `cancel`\n- `disabled`\n- `enableUserSelectHack`\n- `grid`\n- `handle`\n- `nodeRef`\n- `offsetParent`\n- `onDrag`\n- `onMouseDown`\n- `onStart`\n- `onStop`\n- `scale`\n\n## Using nodeRef\n\nTo avoid `ReactDOM.findDOMNode()` deprecation warnings in React Strict Mode, pass a `nodeRef` prop:\n\n```jsx\nfunction App() {\n  const nodeRef = useRef(null);\n\n  return (\n    <Draggable nodeRef={nodeRef}>\n      <div ref={nodeRef}>Drag me!</div>\n    </Draggable>\n  );\n}\n```\n\nFor custom components, forward both the ref and props:\n\n```jsx\nconst MyComponent = forwardRef((props, ref) => (\n  <div {...props} ref={ref}>Draggable content</div>\n));\n\nfunction App() {\n  const nodeRef = useRef(null);\n\n  return (\n    <Draggable nodeRef={nodeRef}>\n      <MyComponent ref={nodeRef} />\n    </Draggable>\n  );\n}\n```\n\n## Controlled vs. Uncontrolled\n\n`<Draggable>` is a 'batteries-included' component that manages its own state. For complete control, use `<DraggableCore>`.\n\nFor programmatic repositioning while using `<Draggable>`'s state management, pass the `position` prop:\n\n```jsx\nfunction ControlledDraggable() {\n  const nodeRef = useRef(null);\n  const [position, setPosition] = useState({ x: 0, y: 0 });\n\n  const handleDrag = (e, data) => {\n    setPosition({ x: data.x, y: data.y });\n  };\n\n  const resetPosition = () => setPosition({ x: 0, y: 0 });\n\n  return (\n    <>\n      <button onClick={resetPosition}>Reset</button>\n      <Draggable nodeRef={nodeRef} position={position} onDrag={handleDrag}>\n        <div ref={nodeRef}>Drag me or reset!</div>\n      </Draggable>\n    </>\n  );\n}\n```\n\n## Contributing\n\n- Fork the project\n- Run `yarn dev` to start the development server\n- Make changes and add tests\n- Run `yarn test` to ensure tests pass\n- Submit a PR\n\n### Release Checklist\n\n1. Update CHANGELOG.md\n2. Run `make release-patch`, `make release-minor`, or `make release-major`\n3. Run `make publish`\n\n## License\n\nMIT\n"
  },
  {
    "path": "appveyor.yml",
    "content": "# https://www.appveyor.com/docs/appveyor-yml/\n\nenvironment:\n  matrix:\n    - node_version: \"12\"\n    - node_version: \"10\"\n    - node_version: \"8\"\n  IE_BIN: \"%PROGRAMFILES%\\\\Internet Explorer\\\\iexplore.exe\"\n\ncache:\n  - node_modules\n  - \"%LOCALAPPDATA%/Yarn\"\n\nbuild_script:\n  - cmd: yarn install\n\ntest_script:\n  - cmd: yarn run test-ie\n"
  },
  {
    "path": "eslint.config.mjs",
    "content": "import { defineConfig, globalIgnores } from \"eslint/config\";\nimport react from \"eslint-plugin-react\";\nimport globals from \"globals\";\nimport babelParser from \"@babel/eslint-parser\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport js from \"@eslint/js\";\nimport { FlatCompat } from \"@eslint/eslintrc\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\nconst compat = new FlatCompat({\n    baseDirectory: __dirname,\n    recommendedConfig: js.configs.recommended,\n    allConfig: js.configs.all\n});\n\nexport default defineConfig([globalIgnores([\"build/**/*.js\"]), {\n    extends: compat.extends(\"eslint:recommended\"),\n\n    plugins: {\n        react,\n    },\n\n    languageOptions: {\n        globals: {\n            ...globals.browser,\n            ...globals.node,\n            ReactElement: null,\n            ReactClass: null,\n            $Exact: null,\n            Partial: null,\n            $Keys: null,\n            MouseTouchEvent: null,\n        },\n\n        parser: babelParser,\n    },\n\n    rules: {\n        strict: 0,\n        quotes: [1, \"single\"],\n        curly: [1, \"multi-line\"],\n        camelcase: 0,\n        \"comma-dangle\": 0,\n        \"no-console\": 2,\n        \"no-use-before-define\": [1, \"nofunc\"],\n        \"no-underscore-dangle\": 0,\n\n        \"no-unused-vars\": [1, {\n            ignoreRestSiblings: true,\n        }],\n\n        \"new-cap\": 0,\n        \"prefer-const\": 1,\n        semi: 1,\n    },\n}]);"
  },
  {
    "path": "example/example-styles.css",
    "content": "/* === CYBERPUNK THEME === */\n@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Orbitron:wght@400;700;900&display=swap');\n\n:root {\n  --cyber-bg: #0a0a0f;\n  --cyber-surface: #12121a;\n  --cyber-cyan: #00f0ff;\n  --cyber-magenta: #ff00aa;\n  --cyber-purple: #a855f7;\n  --cyber-yellow: #facc15;\n  --cyber-grid: rgba(0, 240, 255, 0.03);\n  --glow-cyan: 0 0 20px rgba(0, 240, 255, 0.5), 0 0 40px rgba(0, 240, 255, 0.2);\n  --glow-magenta: 0 0 20px rgba(255, 0, 170, 0.5), 0 0 40px rgba(255, 0, 170, 0.2);\n}\n\n* {\n  box-sizing: border-box;\n}\n\nbody {\n  margin: 0;\n  padding: 0;\n  background: var(--cyber-bg);\n  background-image:\n    linear-gradient(var(--cyber-grid) 1px, transparent 1px),\n    linear-gradient(90deg, var(--cyber-grid) 1px, transparent 1px);\n  background-size: 50px 50px;\n  min-height: 100vh;\n  font-family: 'JetBrains Mono', monospace;\n  color: #e0e0e0;\n}\n\n/* === MAIN CONTENT === */\n.main-content {\n  padding: 20px 30px;\n  min-height: 100vh;\n  max-width: 1400px;\n  margin: 0 auto;\n}\n\n#container {\n  width: 100%;\n}\n\n/* === HEADER === */\n.main-header {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  margin-bottom: 20px;\n  padding-bottom: 15px;\n  border-bottom: 1px solid rgba(0, 240, 255, 0.3);\n}\n\nh1 {\n  font-family: 'Orbitron', sans-serif;\n  font-size: 24px;\n  font-weight: 700;\n  color: var(--cyber-cyan);\n  text-shadow: var(--glow-cyan);\n  letter-spacing: 1px;\n  margin: 0;\n}\n\nh3 {\n  font-family: 'Orbitron', sans-serif;\n  font-size: 18px;\n  font-weight: 700;\n  color: var(--cyber-cyan);\n  text-shadow: var(--glow-cyan);\n  letter-spacing: 1px;\n  margin: 0 0 15px 0;\n}\n\n.version-badge {\n  font-size: 13px;\n  font-family: 'JetBrains Mono', monospace;\n  color: var(--cyber-magenta);\n  background: rgba(255, 0, 170, 0.15);\n  padding: 6px 12px;\n  border: 1px solid rgba(255, 0, 170, 0.4);\n  text-decoration: none;\n  text-shadow: 0 0 10px rgba(255, 0, 170, 0.5);\n  transition: all 0.2s ease;\n  flex-shrink: 0;\n}\n\n.version-badge:hover {\n  color: var(--cyber-yellow);\n  border-color: var(--cyber-yellow);\n  background: rgba(250, 204, 21, 0.15);\n  text-shadow: 0 0 15px rgba(250, 204, 21, 0.7);\n}\n\n.header-links {\n  display: flex;\n  gap: 12px;\n  align-items: center;\n}\n\n.lib-link {\n  font-size: 13px;\n  font-family: 'JetBrains Mono', monospace;\n  color: var(--cyber-cyan);\n  background: rgba(0, 240, 255, 0.1);\n  padding: 6px 12px;\n  border: 1px solid rgba(0, 240, 255, 0.4);\n  text-decoration: none;\n  text-shadow: 0 0 10px rgba(0, 240, 255, 0.5);\n  transition: all 0.2s ease;\n}\n\n.lib-link:hover {\n  color: var(--cyber-magenta);\n  border-color: var(--cyber-magenta);\n  background: rgba(255, 0, 170, 0.15);\n  text-shadow: 0 0 15px rgba(255, 0, 170, 0.7);\n}\n\n/* === NAVIGATION LINKS === */\n.nav-links {\n  list-style: none;\n  padding: 0;\n  margin: 0 0 25px 0;\n  display: flex;\n  flex-wrap: wrap;\n  gap: 8px;\n}\n\n.nav-links li {\n  background: linear-gradient(135deg, rgba(18, 18, 26, 0.9) 0%, rgba(10, 10, 15, 0.95) 100%);\n  border: 1px solid rgba(0, 240, 255, 0.3);\n  border-left: 3px solid var(--cyber-cyan);\n  padding: 8px 14px;\n  font-size: 13px;\n  transition: all 0.2s ease;\n}\n\n.nav-links li:hover {\n  border-color: var(--cyber-magenta);\n  border-left-color: var(--cyber-magenta);\n  box-shadow: var(--glow-magenta);\n}\n\n.nav-links li a {\n  color: #c0c0c0;\n  text-decoration: none;\n  transition: color 0.2s ease;\n}\n\n.nav-links li a:hover {\n  color: var(--cyber-magenta);\n}\n\n/* === PARAGRAPHS === */\np {\n  color: #a0a0a0;\n  line-height: 1.6;\n  margin-bottom: 15px;\n  padding-left: 12px;\n  border-left: 2px solid rgba(168, 85, 247, 0.4);\n}\n\np code {\n  background: rgba(168, 85, 247, 0.2);\n  border: 1px solid var(--cyber-purple);\n  padding: 2px 8px;\n  font-family: 'JetBrains Mono', monospace;\n  font-size: 12px;\n  color: var(--cyber-purple);\n}\n\np a {\n  color: var(--cyber-cyan);\n  text-decoration: none;\n  transition: color 0.2s ease;\n}\n\np a:hover {\n  color: var(--cyber-magenta);\n}\n\n/* === DRAGGABLE BOXES === */\n.box {\n  background: linear-gradient(145deg, rgba(18, 18, 26, 0.9) 0%, rgba(30, 30, 45, 0.9) 100%);\n  border: 1px solid var(--cyber-cyan);\n  width: 180px;\n  height: 180px;\n  margin: 10px;\n  padding: 15px;\n  float: left;\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  align-items: center;\n  text-align: center;\n  font-size: 13px;\n  color: #c0c0c0;\n  box-shadow:\n    inset 0 1px 0 rgba(255, 255, 255, 0.05),\n    0 4px 20px rgba(0, 0, 0, 0.5);\n  transition: border-color 0.2s ease, box-shadow 0.2s ease;\n  overflow: hidden;\n}\n\n.box:hover {\n  border-color: var(--cyber-magenta);\n  box-shadow:\n    var(--glow-magenta),\n    inset 0 1px 0 rgba(255, 255, 255, 0.05);\n}\n\n/* === CURSORS === */\n.react-draggable, .cursor {\n  cursor: move;\n}\n.no-cursor {\n  cursor: auto;\n}\n.cursor-y {\n  cursor: ns-resize;\n}\n.cursor-x {\n  cursor: ew-resize;\n}\n\n/* === HANDLE === */\n.react-draggable strong {\n  background: linear-gradient(135deg, rgba(0, 240, 255, 0.2) 0%, rgba(0, 240, 255, 0.1) 100%);\n  border: 1px solid var(--cyber-cyan);\n  display: block;\n  margin-bottom: 10px;\n  padding: 6px 10px;\n  text-align: center;\n  font-weight: 500;\n  color: var(--cyber-cyan);\n  text-shadow: 0 0 8px rgba(0, 240, 255, 0.4);\n  transition: all 0.2s ease;\n}\n\n.react-draggable strong:hover {\n  background: linear-gradient(135deg, rgba(255, 0, 170, 0.2) 0%, rgba(255, 0, 170, 0.1) 100%);\n  border-color: var(--cyber-magenta);\n  color: var(--cyber-magenta);\n  text-shadow: 0 0 10px rgba(255, 0, 170, 0.5);\n}\n\n/* === SPECIAL STATES === */\n.no-pointer-events {\n  pointer-events: none;\n}\n\n.hovered {\n  background: linear-gradient(145deg, rgba(255, 0, 170, 0.2) 0%, rgba(30, 30, 45, 0.9) 100%) !important;\n  border-color: var(--cyber-magenta) !important;\n}\n\n.drop-target {\n  border-style: dashed;\n}\n\n/* === REM POSITION FIX === */\n.rem-position-fix {\n  position: static !important;\n}\n\n/* === BOUNDED CONTAINER === */\n.bounded-container {\n  height: 500px;\n  width: 500px;\n  position: relative;\n  overflow: auto;\n  padding: 0;\n  background: var(--cyber-surface);\n  border: 1px solid rgba(0, 240, 255, 0.2);\n  background-image:\n    linear-gradient(rgba(0, 240, 255, 0.05) 1px, transparent 1px),\n    linear-gradient(90deg, rgba(0, 240, 255, 0.05) 1px, transparent 1px);\n  background-size: 20px 20px;\n  float: left;\n  margin: 10px;\n}\n\n.bounded-inner {\n  height: 1000px;\n  width: 1000px;\n  padding: 10px;\n}\n\n/* === ACTIVE DRAGS COUNTER === */\n.active-drags {\n  font-family: 'Orbitron', sans-serif;\n  color: var(--cyber-yellow);\n  background: rgba(250, 204, 21, 0.1);\n  border: 1px solid rgba(250, 204, 21, 0.3);\n  padding: 8px 15px;\n  display: inline-block;\n  margin-bottom: 20px;\n  font-size: 12px;\n}\n\n/* === SCROLLBAR === */\n::-webkit-scrollbar {\n  width: 8px;\n  height: 8px;\n}\n\n::-webkit-scrollbar-track {\n  background: var(--cyber-bg);\n}\n\n::-webkit-scrollbar-thumb {\n  background: linear-gradient(180deg, var(--cyber-cyan), var(--cyber-magenta));\n  border-radius: 4px;\n}\n\n::-webkit-scrollbar-thumb:hover {\n  background: linear-gradient(180deg, var(--cyber-magenta), var(--cyber-cyan));\n}\n\n/* === PREVENT TEXT SELECTION DURING DRAG === */\nbody:has(.react-draggable-dragging) {\n  user-select: none;\n  -webkit-user-select: none;\n}\n\n/* === RESPONSIVE === */\n@media (max-width: 768px) {\n  .main-content {\n    padding: 15px;\n  }\n\n  .main-header {\n    flex-direction: column;\n    gap: 10px;\n    align-items: flex-start;\n  }\n\n  h1 {\n    font-size: 18px;\n  }\n\n  .box {\n    width: 150px;\n    height: 150px;\n  }\n}\n"
  },
  {
    "path": "example/example.js",
    "content": "const {ReactDraggable: Draggable, React, ReactDOM} = window;\n\nclass App extends React.Component {\n  state = {\n    activeDrags: 0,\n    deltaPosition: {\n      x: 0, y: 0\n    },\n    controlledPosition: {\n      x: -400, y: 200\n    }\n  };\n\n  handleDrag = (e, ui) => {\n    const {x, y} = this.state.deltaPosition;\n    this.setState({\n      deltaPosition: {\n        x: x + ui.deltaX,\n        y: y + ui.deltaY,\n      }\n    });\n  };\n\n  onStart = () => {\n    this.setState({activeDrags: ++this.state.activeDrags});\n  };\n\n  onStop = () => {\n    this.setState({activeDrags: --this.state.activeDrags});\n  };\n  onDrop = (e) => {\n    this.setState({activeDrags: --this.state.activeDrags});\n    if (e.target.classList.contains(\"drop-target\")) {\n      alert(\"Dropped!\");\n      e.target.classList.remove('hovered');\n    }\n  };\n  onDropAreaMouseEnter = (e) => {\n    if (this.state.activeDrags) {\n      e.target.classList.add('hovered');\n    }\n  }\n  onDropAreaMouseLeave = (e) => {\n    e.target.classList.remove('hovered');\n  }\n\n  // For controlled component\n  adjustXPos = (e) => {\n    e.preventDefault();\n    e.stopPropagation();\n    const {x, y} = this.state.controlledPosition;\n    this.setState({controlledPosition: {x: x - 10, y}});\n  };\n\n  adjustYPos = (e) => {\n    e.preventDefault();\n    e.stopPropagation();\n    const {controlledPosition} = this.state;\n    const {x, y} = controlledPosition;\n    this.setState({controlledPosition: {x, y: y - 10}});\n  };\n\n  onControlledDrag = (e, position) => {\n    const {x, y} = position;\n    this.setState({controlledPosition: {x, y}});\n  };\n\n  onControlledDragStop = (e, position) => {\n    this.onControlledDrag(e, position);\n    this.onStop();\n  };\n\n  render() {\n    const dragHandlers = {onStart: this.onStart, onStop: this.onStop};\n    const {deltaPosition, controlledPosition} = this.state;\n    return (\n      <div>\n        <div className=\"active-drags\">Active DragHandlers: {this.state.activeDrags}</div>\n        <ul className=\"nav-links\">\n          <li><a href=\"https://github.com/react-grid-layout/react-draggable/blob/master/example/example.js\">Demo Source</a></li>\n          <li><a href=\"https://github.com/react-grid-layout/react-draggable\">GitHub</a></li>\n          <li><a href=\"https://www.npmjs.com/package/react-draggable\">npm</a></li>\n        </ul>\n        <Draggable {...dragHandlers}>\n          <div className=\"box\">I can be dragged anywhere</div>\n        </Draggable>\n        <Draggable axis=\"x\" {...dragHandlers}>\n          <div className=\"box cursor-x\">I can only be dragged horizonally (x axis)</div>\n        </Draggable>\n        <Draggable axis=\"y\" {...dragHandlers}>\n          <div className=\"box cursor-y\">I can only be dragged vertically (y axis)</div>\n        </Draggable>\n        <Draggable onStart={() => false}>\n          <div className=\"box\">I don't want to be dragged</div>\n        </Draggable>\n        <Draggable onDrag={this.handleDrag} {...dragHandlers}>\n          <div className=\"box\">\n            <div>I track my deltas</div>\n            <div>x: {deltaPosition.x.toFixed(0)}, y: {deltaPosition.y.toFixed(0)}</div>\n          </div>\n        </Draggable>\n        <Draggable handle=\"strong\" {...dragHandlers}>\n          <div className=\"box no-cursor\">\n            <strong className=\"cursor\"><div>Drag here</div></strong>\n            <div>You must click my handle to drag me</div>\n          </div>\n        </Draggable>\n        <Draggable handle=\"strong\">\n          <div className=\"box no-cursor\" style={{display: 'flex', flexDirection: 'column'}}>\n            <strong className=\"cursor\"><div>Drag here</div></strong>\n            <div style={{overflow: 'scroll', flex: 1}}>\n              <div style={{background: 'rgba(250, 204, 21, 0.2)', border: '1px solid rgba(250, 204, 21, 0.4)', padding: '8px', whiteSpace: 'pre-wrap', color: 'var(--cyber-yellow)'}}>\n                I have long scrollable content with a handle\n                {'\\n' + Array(40).fill('x').join('\\n')}\n              </div>\n            </div>\n          </div>\n        </Draggable>\n        <Draggable cancel=\"strong\" {...dragHandlers}>\n          <div className=\"box\">\n            <strong className=\"no-cursor\">Can't drag here</strong>\n            <div>Dragging here works</div>\n          </div>\n        </Draggable>\n        <Draggable grid={[25, 25]} {...dragHandlers}>\n          <div className=\"box\">I snap to a 25 x 25 grid</div>\n        </Draggable>\n        <Draggable grid={[50, 50]} {...dragHandlers}>\n          <div className=\"box\">I snap to a 50 x 50 grid</div>\n        </Draggable>\n        <Draggable bounds={{top: -100, left: -100, right: 100, bottom: 100}} {...dragHandlers}>\n          <div className=\"box\">I can only be moved 100px in any direction.</div>\n        </Draggable>\n        <Draggable {...dragHandlers}>\n          <div className=\"box drop-target\" onMouseEnter={this.onDropAreaMouseEnter} onMouseLeave={this.onDropAreaMouseLeave}>I can detect drops from the next box.</div>\n        </Draggable>\n        <Draggable {...dragHandlers} onStop={this.onDrop}>\n          <div className={`box ${this.state.activeDrags ? \"no-pointer-events\" : \"\"}`}>I can be dropped onto another box.</div>\n        </Draggable>\n        <div className=\"bounded-container\">\n          <div className=\"bounded-inner\">\n            <Draggable bounds=\"parent\" {...dragHandlers}>\n              <div className=\"box\">\n                I can only be moved within my offsetParent.<br /><br />\n                Both parent padding and child margin work properly.\n              </div>\n            </Draggable>\n            <Draggable bounds=\"parent\" {...dragHandlers}>\n              <div className=\"box\">\n                I also can only be moved within my offsetParent.<br /><br />\n                Both parent padding and child margin work properly.\n              </div>\n            </Draggable>\n          </div>\n        </div>\n        <Draggable bounds=\"body\" {...dragHandlers}>\n          <div className=\"box\">\n            I can only be moved within the confines of the body element.\n          </div>\n        </Draggable>\n        <Draggable {...dragHandlers}>\n          <div className=\"box\" style={{position: 'absolute', bottom: '100px', right: '100px'}}>\n            I already have an absolute position.\n          </div>\n        </Draggable>\n        <Draggable {...dragHandlers}>\n          <RemWrapper>\n            <div className=\"box rem-position-fix\" style={{position: 'absolute', bottom: '6.25rem', right: '18rem'}}>\n              I use <span style={{ fontWeight: 700 }}>rem</span> instead of <span style={{ fontWeight: 700 }}>px</span> for my transforms. I also have absolute positioning.\n\n              <br /><br />\n              I depend on a CSS hack to avoid double absolute positioning.\n            </div>\n          </RemWrapper>\n        </Draggable>\n        <Draggable defaultPosition={{x: 25, y: 25}} {...dragHandlers}>\n          <div className=\"box\">\n            {\"I have a default position of {x: 25, y: 25}, so I'm slightly offset.\"}\n          </div>\n        </Draggable>\n        <Draggable positionOffset={{x: '-10%', y: '-10%'}} {...dragHandlers}>\n          <div className=\"box\">\n            {'I have a default position based on percents {x: \\'-10%\\', y: \\'-10%\\'}, so I\\'m slightly offset.'}\n          </div>\n        </Draggable>\n        <Draggable position={controlledPosition} {...dragHandlers} onDrag={this.onControlledDrag}>\n          <div className=\"box\">\n            My position can be changed programmatically. <br />\n            I have a drag handler to sync state.\n            <div>\n              <a href=\"#\" onClick={this.adjustXPos}>Adjust x ({controlledPosition.x})</a>\n            </div>\n            <div>\n              <a href=\"#\" onClick={this.adjustYPos}>Adjust y ({controlledPosition.y})</a>\n            </div>\n          </div>\n        </Draggable>\n        <Draggable position={controlledPosition} {...dragHandlers} onStop={this.onControlledDragStop}>\n          <div className=\"box\">\n            My position can be changed programmatically. <br />\n            I have a dragStop handler to sync state.\n            <div>\n              <a href=\"#\" onClick={this.adjustXPos}>Adjust x ({controlledPosition.x})</a>\n            </div>\n            <div>\n              <a href=\"#\" onClick={this.adjustYPos}>Adjust y ({controlledPosition.y})</a>\n            </div>\n          </div>\n        </Draggable>\n\n      </div>\n    );\n  }\n}\n\nclass RemWrapper extends React.Component {\n  // PropTypes is not available in this environment, but here they are.\n  // static propTypes = {\n  //   style: PropTypes.shape({\n  //     transform: PropTypes.string.isRequired\n  //   }),\n  //   children: PropTypes.node.isRequired,\n  //   remBaseline: PropTypes.number,\n  // }\n\n  translateTransformToRem(transform, remBaseline = 16) {\n    const convertedValues = transform.replace('translate(', '').replace(')', '')\n      .split(',')\n      .map(px => px.replace('px', ''))\n      .map(px => parseInt(px, 10) / remBaseline)\n      .map(x => `${x}rem`)\n    const [x, y] = convertedValues\n\n    return `translate(${x}, ${y})`\n  }\n\n  render() {\n    const { children, remBaseline = 16, style } = this.props\n    const child = React.Children.only(children)\n\n    const editedStyle = {\n      ...child.props.style,\n      ...style,\n      transform: this.translateTransformToRem(style.transform, remBaseline),\n    }\n\n    return React.cloneElement(child, {\n       ...child.props,\n       ...this.props,\n       style: editedStyle\n    })\n  }\n}\n\n\nReactDOM.render(<App/>, document.getElementById('container'));\n"
  },
  {
    "path": "example/index.html",
    "content": "<!doctype html>\n<html>\n<head>\n  <meta charset=\"utf-8\"/>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"/>\n  <title>React Draggable</title>\n  <link rel=\"stylesheet\" href=\"example-styles.css\"/>\n</head>\n<body>\n<div class=\"main-content\">\n  <header class=\"main-header\">\n    <h1>React-Draggable</h1>\n    <div class=\"header-links\">\n      <a class=\"lib-link\" href=\"https://react-grid-layout.github.io/react-grid-layout/examples/00-showcase.html\">React-Grid-Layout</a>\n      <a class=\"lib-link\" href=\"https://react-grid-layout.github.io/react-resizable/\">React-Resizable</a>\n      <a class=\"version-badge\" href=\"https://github.com/react-grid-layout/react-draggable\">v4.5.0</a>\n    </div>\n  </header>\n  <div id=\"container\"></div>\n</div>\n<script src=\"//unpkg.com/react@18/umd/react.development.js\"></script>\n<script src=\"//unpkg.com/react-dom@18/umd/react-dom.development.js\"></script>\n<script src=\"../build/web/react-draggable.min.js\"></script>\n<!-- for imported script -->\n<script src=\"//unpkg.com/@babel/standalone/babel.min.js\"></script>\n<script type=\"text/babel\" src=\"example.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "lib/Draggable.js",
    "content": "// @flow\nimport * as React from 'react';\nimport PropTypes from 'prop-types';\nimport ReactDOM from 'react-dom';\nimport { clsx } from 'clsx';\nimport {createCSSTransform, createSVGTransform} from './utils/domFns';\nimport {canDragX, canDragY, createDraggableData, getBoundPosition} from './utils/positionFns';\nimport {dontSetMe} from './utils/shims';\nimport DraggableCore from './DraggableCore';\nimport type {ControlPosition, PositionOffsetControlPosition, DraggableCoreProps, DraggableCoreDefaultProps} from './DraggableCore';\nimport log from './utils/log';\nimport type {Bounds, DraggableEventHandler} from './utils/types';\nimport type {Element as ReactElement} from 'react';\n\ntype DraggableState = {\n  dragging: boolean,\n  dragged: boolean,\n  x: number, y: number,\n  slackX: number, slackY: number,\n  isElementSVG: boolean,\n  prevPropsPosition: ?ControlPosition,\n};\n\nexport type DraggableDefaultProps = {\n  ...DraggableCoreDefaultProps,\n  axis: 'both' | 'x' | 'y' | 'none',\n  bounds: Bounds | string | false,\n  defaultClassName: string,\n  defaultClassNameDragging: string,\n  defaultClassNameDragged: string,\n  defaultPosition: ControlPosition,\n  scale: number,\n};\n\nexport type DraggableProps = {\n  ...DraggableCoreProps,\n  ...DraggableDefaultProps,\n  positionOffset: PositionOffsetControlPosition,\n  position: ControlPosition,\n};\n\n//\n// Define <Draggable>\n//\n\nclass Draggable extends React.Component<DraggableProps, DraggableState> {\n\n  static displayName: ?string = 'Draggable';\n\n  static propTypes: DraggableProps = {\n    // Accepts all props <DraggableCore> accepts.\n    ...DraggableCore.propTypes,\n\n    /**\n     * `axis` determines which axis the draggable can move.\n     *\n     *  Note that all callbacks will still return data as normal. This only\n     *  controls flushing to the DOM.\n     *\n     * 'both' allows movement horizontally and vertically.\n     * 'x' limits movement to horizontal axis.\n     * 'y' limits movement to vertical axis.\n     * 'none' limits all movement.\n     *\n     * Defaults to 'both'.\n     */\n    axis: PropTypes.oneOf(['both', 'x', 'y', 'none']),\n\n    /**\n     * `bounds` determines the range of movement available to the element.\n     * Available values are:\n     *\n     * 'parent' restricts movement within the Draggable's parent node.\n     *\n     * Alternatively, pass an object with the following properties, all of which are optional:\n     *\n     * {left: LEFT_BOUND, right: RIGHT_BOUND, bottom: BOTTOM_BOUND, top: TOP_BOUND}\n     *\n     * All values are in px.\n     *\n     * Example:\n     *\n     * ```jsx\n     *   let App = React.createClass({\n     *       render: function () {\n     *         return (\n     *            <Draggable bounds={{right: 300, bottom: 300}}>\n     *              <div>Content</div>\n     *           </Draggable>\n     *         );\n     *       }\n     *   });\n     * ```\n     */\n    bounds: PropTypes.oneOfType([\n      PropTypes.shape({\n        left: PropTypes.number,\n        right: PropTypes.number,\n        top: PropTypes.number,\n        bottom: PropTypes.number\n      }),\n      PropTypes.string,\n      PropTypes.oneOf([false])\n    ]),\n\n    defaultClassName: PropTypes.string,\n    defaultClassNameDragging: PropTypes.string,\n    defaultClassNameDragged: PropTypes.string,\n\n    /**\n     * `defaultPosition` specifies the x and y that the dragged item should start at\n     *\n     * Example:\n     *\n     * ```jsx\n     *      let App = React.createClass({\n     *          render: function () {\n     *              return (\n     *                  <Draggable defaultPosition={{x: 25, y: 25}}>\n     *                      <div>I start with transformX: 25px and transformY: 25px;</div>\n     *                  </Draggable>\n     *              );\n     *          }\n     *      });\n     * ```\n     */\n    defaultPosition: PropTypes.shape({\n      x: PropTypes.number,\n      y: PropTypes.number\n    }),\n    positionOffset: PropTypes.shape({\n      x: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),\n      y: PropTypes.oneOfType([PropTypes.number, PropTypes.string])\n    }),\n\n    /**\n     * `position`, if present, defines the current position of the element.\n     *\n     *  This is similar to how form elements in React work - if no `position` is supplied, the component\n     *  is uncontrolled.\n     *\n     * Example:\n     *\n     * ```jsx\n     *      let App = React.createClass({\n     *          render: function () {\n     *              return (\n     *                  <Draggable position={{x: 25, y: 25}}>\n     *                      <div>I start with transformX: 25px and transformY: 25px;</div>\n     *                  </Draggable>\n     *              );\n     *          }\n     *      });\n     * ```\n     */\n    position: PropTypes.shape({\n      x: PropTypes.number,\n      y: PropTypes.number\n    }),\n\n    /**\n     * These properties should be defined on the child, not here.\n     */\n    className: dontSetMe,\n    style: dontSetMe,\n    transform: dontSetMe\n  };\n\n  static defaultProps: DraggableDefaultProps = {\n    ...DraggableCore.defaultProps,\n    axis: 'both',\n    bounds: false,\n    defaultClassName: 'react-draggable',\n    defaultClassNameDragging: 'react-draggable-dragging',\n    defaultClassNameDragged: 'react-draggable-dragged',\n    defaultPosition: {x: 0, y: 0},\n    scale: 1\n  };\n\n  // React 16.3+\n  // Arity (props, state)\n  static getDerivedStateFromProps({position}: DraggableProps, {prevPropsPosition}: DraggableState): ?Partial<DraggableState> {\n    // Set x/y if a new position is provided in props that is different than the previous.\n    if (\n      position &&\n      (!prevPropsPosition ||\n        position.x !== prevPropsPosition.x || position.y !== prevPropsPosition.y\n      )\n    ) {\n      log('Draggable: getDerivedStateFromProps %j', {position, prevPropsPosition});\n      return {\n        x: position.x,\n        y: position.y,\n        prevPropsPosition: {...position}\n      };\n    }\n    return null;\n  }\n\n  constructor(props: DraggableProps) {\n    super(props);\n\n    this.state = {\n      // Whether or not we are currently dragging.\n      dragging: false,\n\n      // Whether or not we have been dragged before.\n      dragged: false,\n\n      // Current transform x and y.\n      x: props.position ? props.position.x : props.defaultPosition.x,\n      y: props.position ? props.position.y : props.defaultPosition.y,\n\n      prevPropsPosition: {...props.position},\n\n      // Used for compensating for out-of-bounds drags\n      slackX: 0, slackY: 0,\n\n      // Can only determine if SVG after mounting\n      isElementSVG: false\n    };\n\n    if (props.position && !(props.onDrag || props.onStop)) {\n      // eslint-disable-next-line no-console\n      console.warn('A `position` was applied to this <Draggable>, without drag handlers. This will make this ' +\n        'component effectively undraggable. Please attach `onDrag` or `onStop` handlers so you can adjust the ' +\n        '`position` of this element.');\n    }\n  }\n\n  componentDidMount() {\n    // Check to see if the element passed is an instanceof SVGElement\n    if(typeof window.SVGElement !== 'undefined' && this.findDOMNode() instanceof window.SVGElement) {\n      this.setState({isElementSVG: true});\n    }\n  }\n\n  componentWillUnmount() {\n    if (this.state.dragging) {\n      this.setState({dragging: false}); // prevents invariant if unmounted while dragging\n    }\n  }\n\n  // React 19 removed ReactDOM.findDOMNode, so nodeRef is now required.\n  // For backward compatibility with React 18 and earlier, we still support findDOMNode if available.\n  findDOMNode(): ?HTMLElement {\n    if (this.props?.nodeRef) {\n      return this.props.nodeRef.current;\n    }\n    // ReactDOM.findDOMNode was removed in React 19\n    if (typeof ReactDOM.findDOMNode === 'function') {\n      return ReactDOM.findDOMNode(this);\n    }\n    return null;\n  }\n\n  onDragStart: DraggableEventHandler = (e, coreData) => {\n    log('Draggable: onDragStart: %j', coreData);\n\n    // Short-circuit if user's callback killed it.\n    const shouldStart = this.props.onStart(e, createDraggableData(this, coreData));\n    // Kills start event on core as well, so move handlers are never bound.\n    if (shouldStart === false) return false;\n\n    this.setState({dragging: true, dragged: true});\n  };\n\n  onDrag: DraggableEventHandler = (e, coreData) => {\n    if (!this.state.dragging) return false;\n    log('Draggable: onDrag: %j', coreData);\n\n    const uiData = createDraggableData(this, coreData);\n\n    const newState = {\n      x: uiData.x,\n      y: uiData.y,\n      slackX: 0,\n      slackY: 0,\n    };\n\n    // Keep within bounds.\n    if (this.props.bounds) {\n      // Save original x and y.\n      const {x, y} = newState;\n\n      // Add slack to the values used to calculate bound position. This will ensure that if\n      // we start removing slack, the element won't react to it right away until it's been\n      // completely removed.\n      newState.x += this.state.slackX;\n      newState.y += this.state.slackY;\n\n      // Get bound position. This will ceil/floor the x and y within the boundaries.\n      const [newStateX, newStateY] = getBoundPosition(this, newState.x, newState.y);\n      newState.x = newStateX;\n      newState.y = newStateY;\n\n      // Recalculate slack by noting how much was shaved by the boundPosition handler.\n      newState.slackX = this.state.slackX + (x - newState.x);\n      newState.slackY = this.state.slackY + (y - newState.y);\n\n      // Update the event we fire to reflect what really happened after bounds took effect.\n      uiData.x = newState.x;\n      uiData.y = newState.y;\n      uiData.deltaX = newState.x - this.state.x;\n      uiData.deltaY = newState.y - this.state.y;\n    }\n\n    // Short-circuit if user's callback killed it.\n    const shouldUpdate = this.props.onDrag(e, uiData);\n    if (shouldUpdate === false) return false;\n\n    this.setState(newState);\n  };\n\n  onDragStop: DraggableEventHandler = (e, coreData) => {\n    if (!this.state.dragging) return false;\n\n    // Short-circuit if user's callback killed it.\n    const shouldContinue = this.props.onStop(e, createDraggableData(this, coreData));\n    if (shouldContinue === false) return false;\n\n    log('Draggable: onDragStop: %j', coreData);\n\n    const newState: Partial<DraggableState> = {\n      dragging: false,\n      slackX: 0,\n      slackY: 0\n    };\n\n    // If this is a controlled component, the result of this operation will be to\n    // revert back to the old position. We expect a handler on `onDragStop`, at the least.\n    const controlled = Boolean(this.props.position);\n    if (controlled) {\n      const {x, y} = this.props.position;\n      newState.x = x;\n      newState.y = y;\n    }\n\n    this.setState(newState);\n  };\n\n  render(): ReactElement<any> {\n    const {\n      axis,\n      bounds,\n      children,\n      defaultPosition,\n      defaultClassName,\n      defaultClassNameDragging,\n      defaultClassNameDragged,\n      position,\n      positionOffset,\n      scale,\n      ...draggableCoreProps\n    } = this.props;\n\n    let style = {};\n    let svgTransform = null;\n\n    // If this is controlled, we don't want to move it - unless it's dragging.\n    const controlled = Boolean(position);\n    const draggable = !controlled || this.state.dragging;\n\n    const validPosition = position || defaultPosition;\n    const transformOpts = {\n      // Set left if horizontal drag is enabled\n      x: canDragX(this) && draggable ?\n        this.state.x :\n        validPosition.x,\n\n      // Set top if vertical drag is enabled\n      y: canDragY(this) && draggable ?\n        this.state.y :\n        validPosition.y\n    };\n\n    // If this element was SVG, we use the `transform` attribute.\n    if (this.state.isElementSVG) {\n      svgTransform = createSVGTransform(transformOpts, positionOffset);\n    } else {\n      // Add a CSS transform to move the element around. This allows us to move the element around\n      // without worrying about whether or not it is relatively or absolutely positioned.\n      // If the item you are dragging already has a transform set, wrap it in a <span> so <Draggable>\n      // has a clean slate.\n      style = createCSSTransform(transformOpts, positionOffset);\n    }\n\n    // Mark with class while dragging\n    const className = clsx((children.props.className || ''), defaultClassName, {\n      [defaultClassNameDragging]: this.state.dragging,\n      [defaultClassNameDragged]: this.state.dragged\n    });\n\n    // Reuse the child provided\n    // This makes it flexible to use whatever element is wanted (div, ul, etc)\n    return (\n      <DraggableCore {...draggableCoreProps} onStart={this.onDragStart} onDrag={this.onDrag} onStop={this.onDragStop}>\n        {React.cloneElement(React.Children.only(children), {\n          className: className,\n          style: {...children.props.style, ...style},\n          transform: svgTransform\n        })}\n      </DraggableCore>\n    );\n  }\n}\n\nexport {Draggable as default, DraggableCore};\n"
  },
  {
    "path": "lib/DraggableCore.js",
    "content": "// @flow\nimport * as React from 'react';\nimport PropTypes from 'prop-types';\nimport ReactDOM from 'react-dom';\nimport {matchesSelectorAndParentsTo, addEvent, removeEvent, addUserSelectStyles, getTouchIdentifier,\n        scheduleRemoveUserSelectStyles} from './utils/domFns';\nimport {createCoreData, getControlPosition, snapToGrid} from './utils/positionFns';\nimport {dontSetMe} from './utils/shims';\nimport log from './utils/log';\n\nimport type {EventHandler, MouseTouchEvent} from './utils/types';\nimport type {Element as ReactElement} from 'react';\n\n// Simple abstraction for dragging events names.\nconst eventsFor = {\n  touch: {\n    start: 'touchstart',\n    move: 'touchmove',\n    stop: 'touchend'\n  },\n  mouse: {\n    start: 'mousedown',\n    move: 'mousemove',\n    stop: 'mouseup'\n  }\n};\n\n// Default to mouse events.\nlet dragEventFor = eventsFor.mouse;\n\nexport type DraggableData = {\n  node: HTMLElement,\n  x: number, y: number,\n  deltaX: number, deltaY: number,\n  lastX: number, lastY: number,\n};\n\nexport type DraggableEventHandler = (e: MouseEvent, data: DraggableData) => void | false;\n\nexport type ControlPosition = {x: number, y: number};\nexport type PositionOffsetControlPosition = {x: number|string, y: number|string};\n\nexport type DraggableCoreDefaultProps = {\n  allowAnyClick: boolean,\n  allowMobileScroll: boolean,\n  disabled: boolean,\n  enableUserSelectHack: boolean,\n  onStart: DraggableEventHandler,\n  onDrag: DraggableEventHandler,\n  onStop: DraggableEventHandler,\n  onMouseDown: (e: MouseEvent) => void,\n  scale: number,\n};\n\nexport type DraggableCoreProps = {\n  ...DraggableCoreDefaultProps,\n  cancel: string,\n  children: ReactElement<any>,\n  offsetParent: HTMLElement,\n  grid: [number, number],\n  handle: string,\n  nodeRef?: ?React.ElementRef<any>,\n};\n\n//\n// Define <DraggableCore>.\n//\n// <DraggableCore> is for advanced usage of <Draggable>. It maintains minimal internal state so it can\n// work well with libraries that require more control over the element.\n//\n\nexport default class DraggableCore extends React.Component<DraggableCoreProps> {\n\n  static displayName: ?string = 'DraggableCore';\n\n  static propTypes: Object = {\n    /**\n     * `allowAnyClick` allows dragging using any mouse button.\n     * By default, we only accept the left button.\n     *\n     * Defaults to `false`.\n     */\n    allowAnyClick: PropTypes.bool,\n\n    /**\n     * `allowMobileScroll` turns off cancellation of the 'touchstart' event\n     * on mobile devices. Only enable this if you are having trouble with click\n     * events. Prefer using 'handle' / 'cancel' instead.\n     *\n     * Defaults to `false`.\n     */\n    allowMobileScroll: PropTypes.bool,\n\n    children: PropTypes.node.isRequired,\n\n    /**\n     * `disabled`, if true, stops the <Draggable> from dragging. All handlers,\n     * with the exception of `onMouseDown`, will not fire.\n     */\n    disabled: PropTypes.bool,\n\n    /**\n     * By default, we add 'user-select:none' attributes to the document body\n     * to prevent ugly text selection during drag. If this is causing problems\n     * for your app, set this to `false`.\n     */\n    enableUserSelectHack: PropTypes.bool,\n\n    /**\n     * `offsetParent`, if set, uses the passed DOM node to compute drag offsets\n     * instead of using the parent node.\n     */\n    offsetParent: function(props: DraggableCoreProps, propName: $Keys<DraggableCoreProps>) {\n      if (props[propName] && props[propName].nodeType !== 1) {\n        throw new Error('Draggable\\'s offsetParent must be a DOM Node.');\n      }\n    },\n\n    /**\n     * `grid` specifies the x and y that dragging should snap to.\n     */\n    grid: PropTypes.arrayOf(PropTypes.number),\n\n    /**\n     * `handle` specifies a selector to be used as the handle that initiates drag.\n     *\n     * Example:\n     *\n     * ```jsx\n     *   let App = React.createClass({\n     *       render: function () {\n     *         return (\n     *            <Draggable handle=\".handle\">\n     *              <div>\n     *                  <div className=\"handle\">Click me to drag</div>\n     *                  <div>This is some other content</div>\n     *              </div>\n     *           </Draggable>\n     *         );\n     *       }\n     *   });\n     * ```\n     */\n    handle: PropTypes.string,\n\n    /**\n     * `cancel` specifies a selector to be used to prevent drag initialization.\n     *\n     * Example:\n     *\n     * ```jsx\n     *   let App = React.createClass({\n     *       render: function () {\n     *           return(\n     *               <Draggable cancel=\".cancel\">\n     *                   <div>\n     *                     <div className=\"cancel\">You can't drag from here</div>\n     *                     <div>Dragging here works fine</div>\n     *                   </div>\n     *               </Draggable>\n     *           );\n     *       }\n     *   });\n     * ```\n     */\n    cancel: PropTypes.string,\n\n    /* If running in React Strict mode, ReactDOM.findDOMNode() is deprecated.\n     * Unfortunately, in order for <Draggable> to work properly, we need raw access\n     * to the underlying DOM node. If you want to avoid the warning, pass a `nodeRef`\n     * as in this example:\n     *\n     * function MyComponent() {\n     *   const nodeRef = React.useRef(null);\n     *   return (\n     *     <Draggable nodeRef={nodeRef}>\n     *       <div ref={nodeRef}>Example Target</div>\n     *     </Draggable>\n     *   );\n     * }\n     *\n     * This can be used for arbitrarily nested components, so long as the ref ends up\n     * pointing to the actual child DOM node and not a custom component.\n     */\n    nodeRef: PropTypes.object,\n\n    /**\n     * Called when dragging starts.\n     * If this function returns the boolean false, dragging will be canceled.\n     */\n    onStart: PropTypes.func,\n\n    /**\n     * Called while dragging.\n     * If this function returns the boolean false, dragging will be canceled.\n     */\n    onDrag: PropTypes.func,\n\n    /**\n     * Called when dragging stops.\n     * If this function returns the boolean false, the drag will remain active.\n     */\n    onStop: PropTypes.func,\n\n    /**\n     * A workaround option which can be passed if onMouseDown needs to be accessed,\n     * since it'll always be blocked (as there is internal use of onMouseDown)\n     */\n    onMouseDown: PropTypes.func,\n\n    /**\n     * `scale`, if set, applies scaling while dragging an element\n     */\n    scale: PropTypes.number,\n\n    /**\n     * These properties should be defined on the child, not here.\n     */\n    className: dontSetMe,\n    style: dontSetMe,\n    transform: dontSetMe\n  };\n\n  static defaultProps: DraggableCoreDefaultProps = {\n    allowAnyClick: false, // by default only accept left click\n    allowMobileScroll: false,\n    disabled: false,\n    enableUserSelectHack: true,\n    onStart: function(){},\n    onDrag: function(){},\n    onStop: function(){},\n    onMouseDown: function(){},\n    scale: 1,\n  };\n\n  dragging: boolean = false;\n\n  // Used while dragging to determine deltas.\n  lastX: number = NaN;\n  lastY: number = NaN;\n\n  touchIdentifier: ?number = null;\n\n  mounted: boolean = false;\n\n  componentDidMount() {\n    this.mounted = true;\n    // Touch handlers must be added with {passive: false} to be cancelable.\n    // https://developers.google.com/web/updates/2017/01/scrolling-intervention\n    const thisNode = this.findDOMNode();\n    if (thisNode) {\n      addEvent(thisNode, eventsFor.touch.start, this.onTouchStart, {passive: false});\n    }\n  }\n\n  componentWillUnmount() {\n    this.mounted = false;\n    // Remove any leftover event handlers. Remove both touch and mouse handlers in case\n    // some browser quirk caused a touch event to fire during a mouse move, or vice versa.\n    const thisNode = this.findDOMNode();\n    if (thisNode) {\n      const {ownerDocument} = thisNode;\n      removeEvent(ownerDocument, eventsFor.mouse.move, this.handleDrag);\n      removeEvent(ownerDocument, eventsFor.touch.move, this.handleDrag);\n      removeEvent(ownerDocument, eventsFor.mouse.stop, this.handleDragStop);\n      removeEvent(ownerDocument, eventsFor.touch.stop, this.handleDragStop);\n      removeEvent(thisNode, eventsFor.touch.start, this.onTouchStart, {passive: false});\n      if (this.props.enableUserSelectHack) scheduleRemoveUserSelectStyles(ownerDocument);\n    }\n  }\n\n  // React 19 removed ReactDOM.findDOMNode, so nodeRef is now required.\n  // For backward compatibility with React 18 and earlier, we still support findDOMNode if available.\n  findDOMNode(): ?HTMLElement {\n    if (this.props?.nodeRef) {\n      return this.props.nodeRef.current;\n    }\n    // ReactDOM.findDOMNode was removed in React 19\n    if (typeof ReactDOM.findDOMNode === 'function') {\n      return ReactDOM.findDOMNode(this);\n    }\n    // In React 19+, nodeRef is required - log a warning via our log utility\n    log(\n      'react-draggable: ReactDOM.findDOMNode is not available in React 19+. ' +\n      'You must provide a nodeRef prop. See: https://github.com/react-grid-layout/react-draggable#noderef'\n    );\n    return null;\n  }\n\n  handleDragStart: EventHandler<MouseTouchEvent> = (e) => {\n    // Make it possible to attach event handlers on top of this one.\n    this.props.onMouseDown(e);\n\n    // Only accept left-clicks. On macOS, ctrl+click is equivalent to right-click.\n    if (!this.props.allowAnyClick && ((typeof e.button === 'number' && e.button !== 0) || e.ctrlKey)) return false;\n\n    // Get nodes. Be sure to grab relative document (could be iframed)\n    const thisNode = this.findDOMNode();\n    if (!thisNode || !thisNode.ownerDocument || !thisNode.ownerDocument.body) {\n      throw new Error('<DraggableCore> not mounted on DragStart!');\n    }\n    const {ownerDocument} = thisNode;\n\n    // Short circuit if handle or cancel prop was provided and selector doesn't match.\n    if (this.props.disabled ||\n      (!(e.target instanceof ownerDocument.defaultView.Node)) ||\n      (this.props.handle && !matchesSelectorAndParentsTo(e.target, this.props.handle, thisNode)) ||\n      (this.props.cancel && matchesSelectorAndParentsTo(e.target, this.props.cancel, thisNode))) {\n      return;\n    }\n\n    // Prevent scrolling on mobile devices, like ipad/iphone.\n    // Important that this is after handle/cancel.\n    if (e.type === 'touchstart' && !this.props.allowMobileScroll) e.preventDefault();\n\n    // Set touch identifier in component state if this is a touch event. This allows us to\n    // distinguish between individual touches on multitouch screens by identifying which\n    // touchpoint was set to this element.\n    const touchIdentifier = getTouchIdentifier(e);\n    this.touchIdentifier = touchIdentifier;\n\n    // Get the current drag point from the event. This is used as the offset.\n    const position = getControlPosition(e, touchIdentifier, this);\n    if (position == null) return; // not possible but satisfies flow\n    const {x, y} = position;\n\n    // Create an event object with all the data parents need to make a decision here.\n    const coreEvent = createCoreData(this, x, y);\n\n    log('DraggableCore: handleDragStart: %j', coreEvent);\n\n    // Call event handler. If it returns explicit false, cancel.\n    log('calling', this.props.onStart);\n    const shouldUpdate = this.props.onStart(e, coreEvent);\n    if (shouldUpdate === false || this.mounted === false) return;\n\n    // Add a style to the body to disable user-select. This prevents text from\n    // being selected all over the page.\n    if (this.props.enableUserSelectHack) addUserSelectStyles(ownerDocument);\n\n    // Initiate dragging. Set the current x and y as offsets\n    // so we know how much we've moved during the drag. This allows us\n    // to drag elements around even if they have been moved, without issue.\n    this.dragging = true;\n    this.lastX = x;\n    this.lastY = y;\n\n    // Add events to the document directly so we catch when the user's mouse/touch moves outside of\n    // this element. We use different events depending on whether or not we have detected that this\n    // is a touch-capable device.\n    addEvent(ownerDocument, dragEventFor.move, this.handleDrag);\n    addEvent(ownerDocument, dragEventFor.stop, this.handleDragStop);\n  };\n\n  handleDrag: EventHandler<MouseTouchEvent> = (e) => {\n\n    // Get the current drag point from the event. This is used as the offset.\n    const position = getControlPosition(e, this.touchIdentifier, this);\n    if (position == null) return;\n    let {x, y} = position;\n\n    // Snap to grid if prop has been provided\n    if (Array.isArray(this.props.grid)) {\n      let deltaX = x - this.lastX, deltaY = y - this.lastY;\n      [deltaX, deltaY] = snapToGrid(this.props.grid, deltaX, deltaY);\n      if (!deltaX && !deltaY) return; // skip useless drag\n      x = this.lastX + deltaX, y = this.lastY + deltaY;\n    }\n\n    const coreEvent = createCoreData(this, x, y);\n\n    log('DraggableCore: handleDrag: %j', coreEvent);\n\n    // Call event handler. If it returns explicit false, trigger end.\n    const shouldUpdate = this.props.onDrag(e, coreEvent);\n    if (shouldUpdate === false || this.mounted === false) {\n      try {\n        // $FlowIgnore\n        this.handleDragStop(new MouseEvent('mouseup'));\n      } catch (err) {\n        // Old browsers\n        const event = ((document.createEvent('MouseEvents'): any): MouseTouchEvent);\n        // I see why this insanity was deprecated\n        // $FlowIgnore\n        event.initMouseEvent('mouseup', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);\n        this.handleDragStop(event);\n      }\n      return;\n    }\n\n    this.lastX = x;\n    this.lastY = y;\n  };\n\n  handleDragStop: EventHandler<MouseTouchEvent> = (e) => {\n    if (!this.dragging) return;\n\n    const position = getControlPosition(e, this.touchIdentifier, this);\n    if (position == null) return;\n    let {x, y} = position;\n\n    // Snap to grid if prop has been provided\n    if (Array.isArray(this.props.grid)) {\n      let deltaX = x - this.lastX || 0;\n      let deltaY = y - this.lastY || 0;\n      [deltaX, deltaY] = snapToGrid(this.props.grid, deltaX, deltaY);\n      x = this.lastX + deltaX, y = this.lastY + deltaY;\n    }\n\n    const coreEvent = createCoreData(this, x, y);\n\n    // Call event handler\n    const shouldContinue = this.props.onStop(e, coreEvent);\n    if (shouldContinue === false || this.mounted === false) return false;\n\n    const thisNode = this.findDOMNode();\n    if (thisNode) {\n      // Remove user-select hack\n      if (this.props.enableUserSelectHack) scheduleRemoveUserSelectStyles(thisNode.ownerDocument);\n    }\n\n    log('DraggableCore: handleDragStop: %j', coreEvent);\n\n    // Reset the el.\n    this.dragging = false;\n    this.lastX = NaN;\n    this.lastY = NaN;\n\n    if (thisNode) {\n      // Remove event handlers\n      log('DraggableCore: Removing handlers');\n      removeEvent(thisNode.ownerDocument, dragEventFor.move, this.handleDrag);\n      removeEvent(thisNode.ownerDocument, dragEventFor.stop, this.handleDragStop);\n    }\n  };\n\n  onMouseDown: EventHandler<MouseTouchEvent> = (e) => {\n    dragEventFor = eventsFor.mouse; // on touchscreen laptops we could switch back to mouse\n\n    return this.handleDragStart(e);\n  };\n\n  onMouseUp: EventHandler<MouseTouchEvent> = (e) => {\n    dragEventFor = eventsFor.mouse;\n\n    return this.handleDragStop(e);\n  };\n\n  // Same as onMouseDown (start drag), but now consider this a touch device.\n  onTouchStart: EventHandler<MouseTouchEvent> = (e) => {\n    // We're on a touch device now, so change the event handlers\n    dragEventFor = eventsFor.touch;\n\n    return this.handleDragStart(e);\n  };\n\n  onTouchEnd: EventHandler<MouseTouchEvent> = (e) => {\n    // We're on a touch device now, so change the event handlers\n    dragEventFor = eventsFor.touch;\n\n    return this.handleDragStop(e);\n  };\n\n  render(): React.Element<any> {\n    // Reuse the child provided\n    // This makes it flexible to use whatever element is wanted (div, ul, etc)\n    return React.cloneElement(React.Children.only(this.props.children), {\n      // Note: mouseMove handler is attached to document so it will still function\n      // when the user drags quickly and leaves the bounds of the element.\n      onMouseDown: this.onMouseDown,\n      onMouseUp: this.onMouseUp,\n      // onTouchStart is added on `componentDidMount` so they can be added with\n      // {passive: false}, which allows it to cancel. See\n      // https://developers.google.com/web/updates/2017/01/scrolling-intervention\n      onTouchEnd: this.onTouchEnd\n    });\n  }\n}\n"
  },
  {
    "path": "lib/cjs.js",
    "content": "const {default: Draggable, DraggableCore} = require('./Draggable');\n\n// Previous versions of this lib exported <Draggable> as the root export. As to no-// them, or TypeScript, we export *both* as the root and as 'default'.\n// See https://github.com/mzabriskie/react-draggable/pull/254\n// and https://github.com/mzabriskie/react-draggable/issues/266\nmodule.exports = Draggable;\nmodule.exports.default = Draggable;\nmodule.exports.DraggableCore = DraggableCore;\n"
  },
  {
    "path": "lib/utils/domFns.js",
    "content": "// @flow\nimport {findInArray, isFunction, int} from './shims';\nimport browserPrefix, {browserPrefixToKey} from './getPrefix';\n\nimport type {ControlPosition, PositionOffsetControlPosition, MouseTouchEvent} from './types';\n\nlet matchesSelectorFunc = '';\nexport function matchesSelector(el: Node, selector: string): boolean {\n  if (!matchesSelectorFunc) {\n    matchesSelectorFunc = findInArray([\n      'matches',\n      'webkitMatchesSelector',\n      'mozMatchesSelector',\n      'msMatchesSelector',\n      'oMatchesSelector'\n    ], function(method){\n      // $FlowIgnore: Doesn't think elements are indexable\n      return isFunction(el[method]);\n    });\n  }\n\n  // Might not be found entirely (not an Element?) - in that case, bail\n  // $FlowIgnore: Doesn't think elements are indexable\n  if (!isFunction(el[matchesSelectorFunc])) return false;\n\n  // $FlowIgnore: Doesn't think elements are indexable\n  return el[matchesSelectorFunc](selector);\n}\n\n// Works up the tree to the draggable itself attempting to match selector.\nexport function matchesSelectorAndParentsTo(el: Node, selector: string, baseNode: Node): boolean {\n  let node = el;\n  do {\n    if (matchesSelector(node, selector)) return true;\n    if (node === baseNode) return false;\n    // $FlowIgnore[incompatible-type]\n    node = node.parentNode;\n  } while (node);\n\n  return false;\n}\n\nexport function addEvent(el: ?Node, event: string, handler: Function, inputOptions?: Object): void {\n  if (!el) return;\n  const options = {capture: true, ...inputOptions};\n  // $FlowIgnore[method-unbinding]\n  if (el.addEventListener) {\n    el.addEventListener(event, handler, options);\n  } else if (el.attachEvent) {\n    el.attachEvent('on' + event, handler);\n  } else {\n    // $FlowIgnore: Doesn't think elements are indexable\n    el['on' + event] = handler;\n  }\n}\n\nexport function removeEvent(el: ?Node, event: string, handler: Function, inputOptions?: Object): void {\n  if (!el) return;\n  const options = {capture: true, ...inputOptions};\n  // $FlowIgnore[method-unbinding]\n  if (el.removeEventListener) {\n    el.removeEventListener(event, handler, options);\n  } else if (el.detachEvent) {\n    el.detachEvent('on' + event, handler);\n  } else {\n    // $FlowIgnore: Doesn't think elements are indexable\n    el['on' + event] = null;\n  }\n}\n\nexport function outerHeight(node: HTMLElement): number {\n  // This is deliberately excluding margin for our calculations, since we are using\n  // offsetTop which is including margin. See getBoundPosition\n  let height = node.clientHeight;\n  const computedStyle = node.ownerDocument.defaultView.getComputedStyle(node);\n  height += int(computedStyle.borderTopWidth);\n  height += int(computedStyle.borderBottomWidth);\n  return height;\n}\n\nexport function outerWidth(node: HTMLElement): number {\n  // This is deliberately excluding margin for our calculations, since we are using\n  // offsetLeft which is including margin. See getBoundPosition\n  let width = node.clientWidth;\n  const computedStyle = node.ownerDocument.defaultView.getComputedStyle(node);\n  width += int(computedStyle.borderLeftWidth);\n  width += int(computedStyle.borderRightWidth);\n  return width;\n}\nexport function innerHeight(node: HTMLElement): number {\n  let height = node.clientHeight;\n  const computedStyle = node.ownerDocument.defaultView.getComputedStyle(node);\n  height -= int(computedStyle.paddingTop);\n  height -= int(computedStyle.paddingBottom);\n  return height;\n}\n\nexport function innerWidth(node: HTMLElement): number {\n  let width = node.clientWidth;\n  const computedStyle = node.ownerDocument.defaultView.getComputedStyle(node);\n  width -= int(computedStyle.paddingLeft);\n  width -= int(computedStyle.paddingRight);\n  return width;\n}\n\ninterface EventWithOffset {\n  clientX: number, clientY: number\n}\n\n// Get from offsetParent\nexport function offsetXYFromParent(evt: EventWithOffset, offsetParent: HTMLElement, scale: number): ControlPosition {\n  const isBody = offsetParent === offsetParent.ownerDocument.body;\n  const offsetParentRect = isBody ? {left: 0, top: 0} : offsetParent.getBoundingClientRect();\n\n  const x = (evt.clientX + offsetParent.scrollLeft - offsetParentRect.left) / scale;\n  const y = (evt.clientY + offsetParent.scrollTop - offsetParentRect.top) / scale;\n\n  return {x, y};\n}\n\nexport function createCSSTransform(controlPos: ControlPosition, positionOffset: PositionOffsetControlPosition): Object {\n  const translation = getTranslation(controlPos, positionOffset, 'px');\n  return {[browserPrefixToKey('transform', browserPrefix)]: translation };\n}\n\nexport function createSVGTransform(controlPos: ControlPosition, positionOffset: PositionOffsetControlPosition): string {\n  const translation = getTranslation(controlPos, positionOffset, '');\n  return translation;\n}\nexport function getTranslation({x, y}: ControlPosition, positionOffset: PositionOffsetControlPosition, unitSuffix: string): string {\n  let translation = `translate(${x}${unitSuffix},${y}${unitSuffix})`;\n  if (positionOffset) {\n    const defaultX = `${(typeof positionOffset.x === 'string') ? positionOffset.x : positionOffset.x + unitSuffix}`;\n    const defaultY = `${(typeof positionOffset.y === 'string') ? positionOffset.y : positionOffset.y + unitSuffix}`;\n    translation = `translate(${defaultX}, ${defaultY})` + translation;\n  }\n  return translation;\n}\n\nexport function getTouch(e: MouseTouchEvent, identifier: number): ?{clientX: number, clientY: number} {\n  return (e.targetTouches && findInArray(e.targetTouches, t => identifier === t.identifier)) ||\n         (e.changedTouches && findInArray(e.changedTouches, t => identifier === t.identifier));\n}\n\nexport function getTouchIdentifier(e: MouseTouchEvent): ?number {\n  if (e.targetTouches && e.targetTouches[0]) return e.targetTouches[0].identifier;\n  if (e.changedTouches && e.changedTouches[0]) return e.changedTouches[0].identifier;\n}\n\n// User-select Hacks:\n//\n// Useful for preventing blue highlights all over everything when dragging.\n\n// Note we're passing `document` b/c we could be iframed\nexport function addUserSelectStyles(doc: ?Document) {\n  if (!doc) return;\n  let styleEl = doc.getElementById('react-draggable-style-el');\n  if (!styleEl) {\n    styleEl = doc.createElement('style');\n    styleEl.type = 'text/css';\n    styleEl.id = 'react-draggable-style-el';\n    styleEl.innerHTML = '.react-draggable-transparent-selection *::-moz-selection {all: inherit;}\\n';\n    styleEl.innerHTML += '.react-draggable-transparent-selection *::selection {all: inherit;}\\n';\n    doc.getElementsByTagName('head')[0].appendChild(styleEl);\n  }\n  if (doc.body) addClassName(doc.body, 'react-draggable-transparent-selection');\n}\n\nexport function scheduleRemoveUserSelectStyles(doc: ?Document) {\n  // Prevent a possible \"forced reflow\"\n  if (window.requestAnimationFrame) {\n    window.requestAnimationFrame(() => {\n      removeUserSelectStyles(doc);\n    });\n  } else {\n    removeUserSelectStyles(doc);\n  }\n}\n\nfunction removeUserSelectStyles(doc: ?Document) {\n  if (!doc) return;\n  try {\n    if (doc.body) removeClassName(doc.body, 'react-draggable-transparent-selection');\n    // $FlowIgnore: IE\n    if (doc.selection) {\n      // $FlowIgnore: IE\n      doc.selection.empty();\n    } else {\n      // Remove selection caused by scroll, unless it's a focused input\n      // (we use doc.defaultView in case we're in an iframe)\n      const selection = (doc.defaultView || window).getSelection();\n      if (selection && selection.type !== 'Caret') {\n        selection.removeAllRanges();\n      }\n    }\n  } catch (e) {\n    // probably IE\n  }\n}\n\nexport function addClassName(el: HTMLElement, className: string) {\n  if (el.classList) {\n    el.classList.add(className);\n  } else {\n    if (!el.className.match(new RegExp(`(?:^|\\\\s)${className}(?!\\\\S)`))) {\n      el.className += ` ${className}`;\n    }\n  }\n}\n\nexport function removeClassName(el: HTMLElement, className: string) {\n  if (el.classList) {\n    el.classList.remove(className);\n  } else {\n    el.className = el.className.replace(new RegExp(`(?:^|\\\\s)${className}(?!\\\\S)`, 'g'), '');\n  }\n}\n"
  },
  {
    "path": "lib/utils/getPrefix.js",
    "content": "// @flow\nconst prefixes = ['Moz', 'Webkit', 'O', 'ms'];\nexport function getPrefix(prop: string='transform'): string {\n  // Ensure we're running in an environment where there is actually a global\n  // `window` obj\n  if (typeof window === 'undefined') return '';\n\n  // If we're in a pseudo-browser server-side environment, this access\n  // path may not exist, so bail out if it doesn't.\n  const style = window.document?.documentElement?.style;\n  if (!style) return '';\n\n  if (prop in style) return '';\n\n  for (let i = 0; i < prefixes.length; i++) {\n    if (browserPrefixToKey(prop, prefixes[i]) in style) return prefixes[i];\n  }\n\n  return '';\n}\n\nexport function browserPrefixToKey(prop: string, prefix: string): string {\n  return prefix ? `${prefix}${kebabToTitleCase(prop)}` : prop;\n}\n\nexport function browserPrefixToStyle(prop: string, prefix: string): string {\n  return prefix ? `-${prefix.toLowerCase()}-${prop}` : prop;\n}\n\nfunction kebabToTitleCase(str: string): string {\n  let out = '';\n  let shouldCapitalize = true;\n  for (let i = 0; i < str.length; i++) {\n    if (shouldCapitalize) {\n      out += str[i].toUpperCase();\n      shouldCapitalize = false;\n    } else if (str[i] === '-') {\n      shouldCapitalize = true;\n    } else {\n      out += str[i];\n    }\n  }\n  return out;\n}\n\n// Default export is the prefix itself, like 'Moz', 'Webkit', etc\n// Note that you may have to re-test for certain things; for instance, Chrome 50\n// can handle unprefixed `transform`, but not unprefixed `user-select`\nexport default (getPrefix(): string);\n"
  },
  {
    "path": "lib/utils/log.js",
    "content": "// @flow\n/*eslint no-console:0*/\nexport default function log(...args: any) {\n  if (process.env.DRAGGABLE_DEBUG) console.log(...args);\n}\n"
  },
  {
    "path": "lib/utils/positionFns.js",
    "content": "// @flow\nimport {isNum, int} from './shims';\nimport {getTouch, innerWidth, innerHeight, offsetXYFromParent, outerWidth, outerHeight} from './domFns';\n\nimport type Draggable from '../Draggable';\nimport type {Bounds, ControlPosition, DraggableData, MouseTouchEvent} from './types';\nimport type DraggableCore from '../DraggableCore';\n\nexport function getBoundPosition(draggable: Draggable, x: number, y: number): [number, number] {\n  // If no bounds, short-circuit and move on\n  if (!draggable.props.bounds) return [x, y];\n\n  // Clone new bounds\n  let {bounds} = draggable.props;\n  bounds = typeof bounds === 'string' ? bounds : cloneBounds(bounds);\n  const node = findDOMNode(draggable);\n\n  if (typeof bounds === 'string') {\n    const {ownerDocument} = node;\n    const ownerWindow = ownerDocument.defaultView;\n    let boundNode;\n    if (bounds === 'parent') {\n      boundNode = node.parentNode;\n    } else {\n      // Flow assigns the wrong return type (Node) for getRootNode(),\n      // so we cast it to one of the correct types (Element).\n      // The others are Document and ShadowRoot.\n      // All three implement querySelector() so it's safe to call.\n      const rootNode = (((node.getRootNode()): any): Element);\n      boundNode = rootNode.querySelector(bounds);\n    }\n\n    if (!(boundNode instanceof ownerWindow.HTMLElement)) {\n      throw new Error('Bounds selector \"' + bounds + '\" could not find an element.');\n    }\n    const boundNodeEl: HTMLElement = boundNode; // for Flow, can't seem to refine correctly\n    const nodeStyle = ownerWindow.getComputedStyle(node);\n    const boundNodeStyle = ownerWindow.getComputedStyle(boundNodeEl);\n    // Compute bounds. This is a pain with padding and offsets but this gets it exactly right.\n    bounds = {\n      left: -node.offsetLeft + int(boundNodeStyle.paddingLeft) + int(nodeStyle.marginLeft),\n      top: -node.offsetTop + int(boundNodeStyle.paddingTop) + int(nodeStyle.marginTop),\n      right: innerWidth(boundNodeEl) - outerWidth(node) - node.offsetLeft +\n        int(boundNodeStyle.paddingRight) - int(nodeStyle.marginRight),\n      bottom: innerHeight(boundNodeEl) - outerHeight(node) - node.offsetTop +\n        int(boundNodeStyle.paddingBottom) - int(nodeStyle.marginBottom)\n    };\n  }\n\n  // Keep x and y below right and bottom limits...\n  if (isNum(bounds.right)) x = Math.min(x, bounds.right);\n  if (isNum(bounds.bottom)) y = Math.min(y, bounds.bottom);\n\n  // But above left and top limits.\n  if (isNum(bounds.left)) x = Math.max(x, bounds.left);\n  if (isNum(bounds.top)) y = Math.max(y, bounds.top);\n\n  return [x, y];\n}\n\nexport function snapToGrid(grid: [number, number], pendingX: number, pendingY: number): [number, number] {\n  const x = Math.round(pendingX / grid[0]) * grid[0];\n  const y = Math.round(pendingY / grid[1]) * grid[1];\n  return [x, y];\n}\n\nexport function canDragX(draggable: Draggable): boolean {\n  return draggable.props.axis === 'both' || draggable.props.axis === 'x';\n}\n\nexport function canDragY(draggable: Draggable): boolean {\n  return draggable.props.axis === 'both' || draggable.props.axis === 'y';\n}\n\n// Get {x, y} positions from event.\nexport function getControlPosition(e: MouseTouchEvent, touchIdentifier: ?number, draggableCore: DraggableCore): ?ControlPosition {\n  const touchObj = typeof touchIdentifier === 'number' ? getTouch(e, touchIdentifier) : null;\n  if (typeof touchIdentifier === 'number' && !touchObj) return null; // not the right touch\n  const node = findDOMNode(draggableCore);\n  // User can provide an offsetParent if desired.\n  const offsetParent = draggableCore.props.offsetParent || node.offsetParent || node.ownerDocument.body;\n  return offsetXYFromParent(touchObj || e, offsetParent, draggableCore.props.scale);\n}\n\n// Create an data object exposed by <DraggableCore>'s events\nexport function createCoreData(draggable: DraggableCore, x: number, y: number): DraggableData {\n  const isStart = !isNum(draggable.lastX);\n  const node = findDOMNode(draggable);\n\n  if (isStart) {\n    // If this is our first move, use the x and y as last coords.\n    return {\n      node,\n      deltaX: 0, deltaY: 0,\n      lastX: x, lastY: y,\n      x, y,\n    };\n  } else {\n    // Otherwise calculate proper values.\n    return {\n      node,\n      deltaX: x - draggable.lastX, deltaY: y - draggable.lastY,\n      lastX: draggable.lastX, lastY: draggable.lastY,\n      x, y,\n    };\n  }\n}\n\n// Create an data exposed by <Draggable>'s events\nexport function createDraggableData(draggable: Draggable, coreData: DraggableData): DraggableData {\n  const scale = draggable.props.scale;\n  return {\n    node: coreData.node,\n    x: draggable.state.x + (coreData.deltaX / scale),\n    y: draggable.state.y + (coreData.deltaY / scale),\n    deltaX: (coreData.deltaX / scale),\n    deltaY: (coreData.deltaY / scale),\n    lastX: draggable.state.x,\n    lastY: draggable.state.y\n  };\n}\n\n// A lot faster than stringify/parse\nfunction cloneBounds(bounds: Bounds): Bounds {\n  return {\n    left: bounds.left,\n    top: bounds.top,\n    right: bounds.right,\n    bottom: bounds.bottom\n  };\n}\n\nfunction findDOMNode(draggable: Draggable | DraggableCore): HTMLElement {\n  const node = draggable.findDOMNode();\n  if (!node) {\n    throw new Error('<DraggableCore>: Unmounted during event!');\n  }\n  // $FlowIgnore we can't assert on HTMLElement due to tests... FIXME\n  return node;\n}\n"
  },
  {
    "path": "lib/utils/shims.js",
    "content": "// @flow\n// @credits https://gist.github.com/rogozhnikoff/a43cfed27c41e4e68cdc\nexport function findInArray(array: Array<any> | TouchList, callback: Function): any {\n  for (let i = 0, length = array.length; i < length; i++) {\n    if (callback.apply(callback, [array[i], i, array])) return array[i];\n  }\n}\n\nexport function isFunction(func: any): boolean %checks {\n  // $FlowIgnore[method-unbinding]\n  return typeof func === 'function' || Object.prototype.toString.call(func) === '[object Function]';\n}\n\nexport function isNum(num: any): boolean %checks {\n  return typeof num === 'number' && !isNaN(num);\n}\n\nexport function int(a: string): number {\n  return parseInt(a, 10);\n}\n\nexport function dontSetMe(props: Object, propName: string, componentName: string): ?Error {\n  if (props[propName]) {\n    return new Error(`Invalid prop ${propName} passed to ${componentName} - do not set this, set it on the child.`);\n  }\n}\n"
  },
  {
    "path": "lib/utils/types.js",
    "content": "// @flow\n\n// eslint-disable-next-line no-use-before-define\nexport type DraggableEventHandler = (e: MouseEvent, data: DraggableData) => void | false;\n\nexport type DraggableData = {\n  node: HTMLElement,\n  x: number, y: number,\n  deltaX: number, deltaY: number,\n  lastX: number, lastY: number\n};\n\nexport type Bounds = {\n  left?: number, top?: number, right?: number, bottom?: number\n};\nexport type ControlPosition = {x: number, y: number};\nexport type PositionOffsetControlPosition = {x: number|string, y: number|string};\nexport type EventHandler<T> = (e: T) => void | false;\n\n// Missing in Flow\nexport class SVGElement extends HTMLElement {\n}\n\n// Missing targetTouches\nexport class TouchEvent2 extends TouchEvent {\n  changedTouches: TouchList;\n  targetTouches: TouchList;\n}\n\nexport type MouseTouchEvent = MouseEvent & TouchEvent2;\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"react-draggable\",\n  \"version\": \"4.5.0\",\n  \"description\": \"React draggable component\",\n  \"main\": \"build/cjs/cjs.js\",\n  \"unpkg\": \"build/web/react-draggable.min.js\",\n  \"scripts\": {\n    \"test\": \"vitest run --reporter=verbose\",\n    \"test:watch\": \"vitest\",\n    \"test:coverage\": \"vitest run --coverage\",\n    \"test:ui\": \"vitest --ui\",\n    \"test:browser\": \"yarn build && vitest run --config vitest.browser.config.js\",\n    \"test:all\": \"yarn test && yarn test:browser\",\n    \"dev\": \"make dev\",\n    \"build\": \"make clean build\",\n    \"build-example\": \"mkdir -p example/build/web && cp build/web/react-draggable.min.js example/build/web/ && sed -i.bak 's|../build/web/react-draggable|build/web/react-draggable|g' example/index.html && rm -f example/index.html.bak\",\n    \"lint\": \"make lint\",\n    \"flow\": \"flow\"\n  },\n  \"files\": [\n    \"build\",\n    \"typings\"\n  ],\n  \"typings\": \"./typings/index.d.ts\",\n  \"types\": \"./typings/index.d.ts\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/react-grid-layout/react-draggable.git\"\n  },\n  \"keywords\": [\n    \"react\",\n    \"draggable\",\n    \"react-component\"\n  ],\n  \"author\": \"Matt Zabriskie\",\n  \"contributors\": [\n    \"Samuel Reed <samuel.trace.reed@gmail.com> (http://strml.net/)\"\n  ],\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/react-grid-layout/react-draggable/issues\"\n  },\n  \"homepage\": \"https://github.com/react-grid-layout/react-draggable\",\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.28.3\",\n    \"@babel/core\": \"^7.28.5\",\n    \"@babel/eslint-parser\": \"^7.28.5\",\n    \"@babel/plugin-transform-class-properties\": \"^7.27.1\",\n    \"@babel/plugin-transform-flow-comments\": \"^7.28.5\",\n    \"@babel/preset-env\": \"^7.28.5\",\n    \"@babel/preset-flow\": \"^7.27.1\",\n    \"@babel/preset-react\": \"^7.28.5\",\n    \"@eslint/eslintrc\": \"^3.3.3\",\n    \"@eslint/js\": \"^9.39.2\",\n    \"@testing-library/dom\": \"^10.4.1\",\n    \"@testing-library/react\": \"^16.3.1\",\n    \"@types/node\": \"^25.0.3\",\n    \"@types/react\": \"^19.2.7\",\n    \"@types/react-dom\": \"^19.2.3\",\n    \"@vitejs/plugin-react\": \"^5.1.2\",\n    \"@vitest/coverage-v8\": \"^4.0.16\",\n    \"babel-loader\": \"^10.0.0\",\n    \"babel-plugin-transform-inline-environment-variables\": \"^0.4.4\",\n    \"eslint\": \"^9.39.2\",\n    \"eslint-plugin-react\": \"^7.37.5\",\n    \"flow-bin\": \"^0.217.0\",\n    \"globals\": \"^16.5.0\",\n    \"jsdom\": \"^27.4.0\",\n    \"pre-commit\": \"^1.2.2\",\n    \"process\": \"^0.11.10\",\n    \"puppeteer\": \"^24.34.0\",\n    \"react\": \"19\",\n    \"react-dom\": \"19\",\n    \"react-frame-component\": \"^5.2.7\",\n    \"semver\": \"^7.7.3\",\n    \"typescript\": \"^5.9.3\",\n    \"vitest\": \"^4.0.16\",\n    \"webpack\": \"^5.104.1\",\n    \"webpack-cli\": \"^6.0.1\",\n    \"webpack-dev-server\": \"^5.2.2\"\n  },\n  \"resolutions\": {\n    \"minimist\": \"^1.2.5\"\n  },\n  \"precommit\": [\n    \"lint\",\n    \"test\"\n  ],\n  \"dependencies\": {\n    \"clsx\": \"^2.1.1\",\n    \"prop-types\": \"^15.8.1\"\n  },\n  \"peerDependencies\": {\n    \"react\": \">= 16.3.0\",\n    \"react-dom\": \">= 16.3.0\"\n  }\n}\n"
  },
  {
    "path": "test/Draggable.test.jsx",
    "content": "import React, { useRef, useState } from 'react';\nimport { describe, it, expect, vi, afterEach } from 'vitest';\nimport { render, cleanup, act } from '@testing-library/react';\nimport Draggable, { DraggableCore } from '../lib/Draggable';\nimport { simulateDrag, startDrag, moveDrag, endDrag } from './testUtils';\n\n// Helper wrapper components that provide nodeRef (required in React 19)\nfunction DraggableWrapper({ children, draggableRef, ...props }) {\n  const nodeRef = useRef(null);\n  return (\n    <Draggable ref={draggableRef} nodeRef={nodeRef} {...props}>\n      {React.cloneElement(children, { ref: nodeRef })}\n    </Draggable>\n  );\n}\n\nfunction DraggableCorWrapper({ children, coreRef, ...props }) {\n  const nodeRef = useRef(null);\n  return (\n    <DraggableCore ref={coreRef} nodeRef={nodeRef} {...props}>\n      {React.cloneElement(children, { ref: nodeRef })}\n    </DraggableCore>\n  );\n}\n\ndescribe('Draggable', () => {\n  afterEach(() => {\n    cleanup();\n    // Clean up any leftover classes on body\n    document.body.className = '';\n  });\n\n  describe('default props', () => {\n    it('should have sensible defaults', () => {\n      const draggableRef = React.createRef();\n      render(\n        <DraggableWrapper draggableRef={draggableRef}>\n          <div />\n        </DraggableWrapper>\n      );\n\n      expect(draggableRef.current.props.axis).toBe('both');\n      expect(draggableRef.current.props.bounds).toBe(false);\n      expect(draggableRef.current.props.defaultClassName).toBe('react-draggable');\n      expect(draggableRef.current.props.defaultClassNameDragging).toBe('react-draggable-dragging');\n      expect(draggableRef.current.props.defaultClassNameDragged).toBe('react-draggable-dragged');\n      expect(draggableRef.current.props.scale).toBe(1);\n    });\n  });\n\n  describe('rendering', () => {\n    it('should render with default class', () => {\n      const { container } = render(\n        <DraggableWrapper>\n          <div />\n        </DraggableWrapper>\n      );\n\n      expect(container.firstChild.classList.contains('react-draggable')).toBe(true);\n    });\n\n    it('should preserve child className', () => {\n      const { container } = render(\n        <DraggableWrapper>\n          <div className=\"my-class\" />\n        </DraggableWrapper>\n      );\n\n      expect(container.firstChild.classList.contains('my-class')).toBe(true);\n      expect(container.firstChild.classList.contains('react-draggable')).toBe(true);\n    });\n\n    it('should preserve child styles', () => {\n      const { container } = render(\n        <DraggableWrapper>\n          <div style={{ color: 'red' }} />\n        </DraggableWrapper>\n      );\n\n      expect(container.firstChild.style.color).toBe('red');\n    });\n\n    it('should apply transform style', () => {\n      const { container } = render(\n        <DraggableWrapper>\n          <div />\n        </DraggableWrapper>\n      );\n\n      expect(container.firstChild.style.transform).toMatch(/translate\\(0px,?\\s*0px\\)/);\n    });\n\n    it('should use custom defaultClassName', () => {\n      const { container } = render(\n        <DraggableWrapper defaultClassName=\"custom-draggable\">\n          <div />\n        </DraggableWrapper>\n      );\n\n      expect(container.firstChild.classList.contains('custom-draggable')).toBe(true);\n      expect(container.firstChild.classList.contains('react-draggable')).toBe(false);\n    });\n  });\n\n  describe('dragging classes', () => {\n    it('should add dragging class during drag', () => {\n      const { container } = render(\n        <DraggableWrapper>\n          <div />\n        </DraggableWrapper>\n      );\n\n      expect(container.firstChild.classList.contains('react-draggable-dragging')).toBe(false);\n      act(() => {\n        startDrag(container.firstChild, { x: 0, y: 0 });\n      });\n      expect(container.firstChild.classList.contains('react-draggable-dragging')).toBe(true);\n      act(() => {\n        endDrag(container.firstChild, { x: 0, y: 0 });\n      });\n      expect(container.firstChild.classList.contains('react-draggable-dragging')).toBe(false);\n    });\n\n    it('should add dragged class after drag', () => {\n      const { container } = render(\n        <DraggableWrapper>\n          <div />\n        </DraggableWrapper>\n      );\n\n      expect(container.firstChild.classList.contains('react-draggable-dragged')).toBe(false);\n      act(() => {\n        simulateDrag(container.firstChild, { from: { x: 0, y: 0 }, to: { x: 100, y: 100 } });\n      });\n      expect(container.firstChild.classList.contains('react-draggable-dragged')).toBe(true);\n    });\n\n    it('should use custom class names', () => {\n      const { container } = render(\n        <DraggableWrapper\n          defaultClassName=\"custom\"\n          defaultClassNameDragging=\"custom-dragging\"\n          defaultClassNameDragged=\"custom-dragged\"\n        >\n          <div />\n        </DraggableWrapper>\n      );\n\n      act(() => {\n        startDrag(container.firstChild, { x: 0, y: 0 });\n      });\n      expect(container.firstChild.classList.contains('custom-dragging')).toBe(true);\n      act(() => {\n        endDrag(container.firstChild, { x: 0, y: 0 });\n      });\n      expect(container.firstChild.classList.contains('custom-dragged')).toBe(true);\n    });\n  });\n\n  describe('position', () => {\n    // Note: Transform updates during drag are tested in browser tests\n    // (test/browser/browser.test.js) because jsdom doesn't support\n    // coordinate calculations. See 'should update transform on drag' test there.\n\n    it('should respect defaultPosition', () => {\n      const { container } = render(\n        <DraggableWrapper defaultPosition={{ x: 50, y: 50 }}>\n          <div />\n        </DraggableWrapper>\n      );\n\n      expect(container.firstChild.style.transform).toMatch(/translate\\(50px,?\\s*50px\\)/);\n    });\n\n    it('should use controlled position', () => {\n      const { container } = render(\n        <DraggableWrapper position={{ x: 100, y: 100 }}>\n          <div />\n        </DraggableWrapper>\n      );\n\n      expect(container.firstChild.style.transform).toMatch(/translate\\(100px,?\\s*100px\\)/);\n    });\n\n    it('should respect positionOffset with numbers', () => {\n      const { container } = render(\n        <DraggableWrapper positionOffset={{ x: 10, y: 20 }}>\n          <div />\n        </DraggableWrapper>\n      );\n\n      expect(container.firstChild.style.transform).toContain('translate(10px, 20px)');\n    });\n\n    it('should respect positionOffset with percentages', () => {\n      const { container } = render(\n        <DraggableWrapper positionOffset={{ x: '50%', y: '50%' }}>\n          <div />\n        </DraggableWrapper>\n      );\n\n      expect(container.firstChild.style.transform).toContain('translate(50%, 50%)');\n    });\n  });\n\n  describe('axis constraint', () => {\n    // Note: Axis constraint tests with actual dragging are in browser tests\n    // (test/browser/browser.test.js) because jsdom doesn't support coordinate\n    // calculations. See 'should honor x axis constraint' and 'should honor y axis constraint'.\n\n    it('should not move when axis=\"none\"', () => {\n      const { container } = render(\n        <DraggableWrapper axis=\"none\">\n          <div />\n        </DraggableWrapper>\n      );\n\n      act(() => {\n        startDrag(container.firstChild, { x: 0, y: 0 });\n        moveDrag({ x: 100, y: 100 });\n        endDrag(container.firstChild, { x: 100, y: 100 });\n      });\n      expect(container.firstChild.style.transform).toMatch(/translate\\(0px,?\\s*0px\\)/);\n    });\n  });\n\n  describe('callbacks', () => {\n    it('should call onStart when drag starts', () => {\n      const onStart = vi.fn();\n      const { container } = render(\n        <DraggableWrapper onStart={onStart}>\n          <div />\n        </DraggableWrapper>\n      );\n\n      act(() => {\n        startDrag(container.firstChild, { x: 0, y: 0 });\n      });\n      expect(onStart).toHaveBeenCalledTimes(1);\n    });\n\n    // Note: onStop callback test is in browser tests (test/browser/browser.test.js)\n    // because jsdom doesn't support coordinate calculations.\n    // See 'should call onStop when drag ends' test there.\n\n    it('should cancel drag when onStart returns false', () => {\n      const onStart = vi.fn(() => false);\n      const onDrag = vi.fn();\n      const { container } = render(\n        <DraggableWrapper onStart={onStart} onDrag={onDrag}>\n          <div />\n        </DraggableWrapper>\n      );\n\n      act(() => {\n        startDrag(container.firstChild, { x: 0, y: 0 });\n        moveDrag({ x: 100, y: 100 });\n      });\n\n      expect(onStart).toHaveBeenCalled();\n      expect(onDrag).not.toHaveBeenCalled();\n    });\n  });\n\n  describe('SVG support', () => {\n    it('should detect SVG elements', () => {\n      const draggableRef = React.createRef();\n      render(\n        <DraggableWrapper draggableRef={draggableRef}>\n          <svg />\n        </DraggableWrapper>\n      );\n\n      expect(draggableRef.current.state.isElementSVG).toBe(true);\n    });\n\n    it('should not set isElementSVG for non-SVG elements', () => {\n      const draggableRef = React.createRef();\n      render(\n        <DraggableWrapper draggableRef={draggableRef}>\n          <div />\n        </DraggableWrapper>\n      );\n\n      expect(draggableRef.current.state.isElementSVG).toBe(false);\n    });\n\n    it('should set initial transform attribute for SVG', () => {\n      const { container } = render(\n        <DraggableWrapper>\n          <svg />\n        </DraggableWrapper>\n      );\n\n      expect(container.firstChild.getAttribute('transform')).toContain('translate(0,0)');\n    });\n  });\n\n  describe('controlled component', () => {\n    it('should respect position prop changes', () => {\n      function ControlledTest() {\n        const [position, setPosition] = useState({ x: 0, y: 0 });\n        return (\n          <>\n            <button onClick={() => setPosition({ x: 100, y: 100 })}>Move</button>\n            <DraggableWrapper position={position}>\n              <div data-testid=\"draggable\" />\n            </DraggableWrapper>\n          </>\n        );\n      }\n\n      const { container, getByRole, getByTestId } = render(<ControlledTest />);\n\n      expect(getByTestId('draggable').style.transform).toMatch(/translate\\(0px,?\\s*0px\\)/);\n\n      act(() => {\n        getByRole('button').click();\n      });\n\n      expect(getByTestId('draggable').style.transform).toMatch(/translate\\(100px,?\\s*100px\\)/);\n    });\n\n    it('should revert to controlled position after drag', () => {\n      const onStop = vi.fn();\n      const { container } = render(\n        <DraggableWrapper position={{ x: 50, y: 50 }} onStop={onStop}>\n          <div />\n        </DraggableWrapper>\n      );\n\n      act(() => {\n        simulateDrag(container.firstChild, { from: { x: 50, y: 50 }, to: { x: 150, y: 150 } });\n      });\n\n      // After drag ends, should revert to controlled position\n      expect(container.firstChild.style.transform).toMatch(/translate\\(50px,?\\s*50px\\)/);\n    });\n  });\n\n  describe('disabled prop', () => {\n    it('should not drag when disabled', () => {\n      const onStart = vi.fn();\n      const { container } = render(\n        <DraggableWrapper disabled onStart={onStart}>\n          <div />\n        </DraggableWrapper>\n      );\n\n      act(() => {\n        startDrag(container.firstChild, { x: 0, y: 0 });\n      });\n      expect(onStart).not.toHaveBeenCalled();\n    });\n  });\n\n  describe('validation', () => {\n    it('should throw when no children provided', () => {\n      const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});\n      const nodeRef = React.createRef();\n\n      let threw = false;\n      try {\n        render(<Draggable nodeRef={nodeRef} />);\n      } catch (e) {\n        threw = true;\n        // Can throw either React.Children.only error or props access error\n        expect(e).toBeDefined();\n      }\n\n      expect(threw).toBe(true);\n      errorSpy.mockRestore();\n    });\n\n    it('should throw when multiple children provided', () => {\n      const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});\n      const nodeRef = React.createRef();\n\n      let threw = false;\n      try {\n        render(\n          <Draggable nodeRef={nodeRef}>\n            <div>One</div>\n            <div>Two</div>\n          </Draggable>\n        );\n      } catch (e) {\n        threw = true;\n        // Can throw either React.Children.only error or className access error\n        expect(e).toBeDefined();\n      }\n\n      expect(threw).toBe(true);\n      errorSpy.mockRestore();\n    });\n\n    // Note: React 19 removed runtime propTypes checking, so these tests now just verify\n    // that the component still renders correctly when invalid props are passed.\n    // The dontSetMe validators are defined but no longer trigger warnings in React 19.\n    it('should still render when className is set on Draggable', () => {\n      const { container } = render(\n        <DraggableWrapper className=\"invalid\">\n          <div />\n        </DraggableWrapper>\n      );\n\n      // Component should still render\n      expect(container.firstChild).toBeTruthy();\n      expect(container.firstChild.classList.contains('react-draggable')).toBe(true);\n    });\n\n    it('should still render when style is set on Draggable', () => {\n      const { container } = render(\n        <DraggableWrapper style={{ color: 'red' }}>\n          <div />\n        </DraggableWrapper>\n      );\n\n      // Component should still render\n      expect(container.firstChild).toBeTruthy();\n    });\n\n    it('should still render when transform is set on Draggable', () => {\n      const { container } = render(\n        <DraggableWrapper transform=\"translate(100px, 100px)\">\n          <div />\n        </DraggableWrapper>\n      );\n\n      // Component should still render\n      expect(container.firstChild).toBeTruthy();\n    });\n  });\n});\n"
  },
  {
    "path": "test/DraggableCore.test.jsx",
    "content": "import React, { useRef } from 'react';\nimport { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';\nimport { render, cleanup } from '@testing-library/react';\nimport { DraggableCore } from '../lib/Draggable';\nimport { simulateDrag, startDrag, moveDrag, endDrag, createTouchEvent } from './testUtils';\n\n// Helper wrapper component that provides nodeRef (required in React 19)\nfunction DraggableCoreWrapper({ children, coreRef, ...props }) {\n  const nodeRef = useRef(null);\n  return (\n    <DraggableCore ref={coreRef} nodeRef={nodeRef} {...props}>\n      {React.cloneElement(children, { ref: nodeRef })}\n    </DraggableCore>\n  );\n}\n\ndescribe('DraggableCore', () => {\n  afterEach(() => {\n    cleanup();\n  });\n\n  describe('rendering', () => {\n    it('should render its child', () => {\n      const { container } = render(\n        <DraggableCoreWrapper>\n          <div data-testid=\"child\">Hello</div>\n        </DraggableCoreWrapper>\n      );\n      expect(container.querySelector('[data-testid=\"child\"]')).toBeTruthy();\n      expect(container.textContent).toBe('Hello');\n    });\n\n    it('should accept a single child only', () => {\n      // Suppress React error boundary logs for this test\n      const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});\n      const nodeRef = React.createRef();\n\n      // Use try/catch instead of expect().toThrow() to avoid stderr output\n      let threw = false;\n      try {\n        render(\n          <DraggableCore nodeRef={nodeRef}>\n            <div>One</div>\n            <div>Two</div>\n          </DraggableCore>\n        );\n      } catch (e) {\n        threw = true;\n        expect(e.message).toContain('React.Children.only');\n      }\n\n      expect(threw).toBe(true);\n      errorSpy.mockRestore();\n    });\n  });\n\n  describe('mouse events', () => {\n    it('should call onStart when mouse down', () => {\n      const onStart = vi.fn();\n      const { container } = render(\n        <DraggableCoreWrapper onStart={onStart}>\n          <div />\n        </DraggableCoreWrapper>\n      );\n\n      startDrag(container.firstChild, { x: 0, y: 0 });\n      expect(onStart).toHaveBeenCalledTimes(1);\n    });\n\n    it('should call onDrag during mouse move', () => {\n      const onDrag = vi.fn();\n      const { container } = render(\n        <DraggableCoreWrapper onDrag={onDrag}>\n          <div />\n        </DraggableCoreWrapper>\n      );\n\n      startDrag(container.firstChild, { x: 0, y: 0 });\n      moveDrag({ x: 100, y: 100 });\n\n      expect(onDrag).toHaveBeenCalled();\n      const callData = onDrag.mock.calls[0][1];\n      expect(callData.deltaX).toBe(100);\n      expect(callData.deltaY).toBe(100);\n    });\n\n    it('should call onStop when mouse up', () => {\n      const onStop = vi.fn();\n      const { container } = render(\n        <DraggableCoreWrapper onStop={onStop}>\n          <div />\n        </DraggableCoreWrapper>\n      );\n\n      simulateDrag(container.firstChild, { from: { x: 0, y: 0 }, to: { x: 100, y: 100 } });\n      expect(onStop).toHaveBeenCalledTimes(1);\n    });\n\n    it('should provide correct data in callbacks', () => {\n      const onDrag = vi.fn();\n      const { container } = render(\n        <DraggableCoreWrapper onDrag={onDrag}>\n          <div />\n        </DraggableCoreWrapper>\n      );\n\n      startDrag(container.firstChild, { x: 50, y: 50 });\n      moveDrag({ x: 150, y: 200 });\n\n      const [event, data] = onDrag.mock.calls[0];\n      expect(data.x).toBe(150);\n      expect(data.y).toBe(200);\n      expect(data.deltaX).toBe(100);\n      expect(data.deltaY).toBe(150);\n      expect(data.lastX).toBe(50);\n      expect(data.lastY).toBe(50);\n      expect(data.node).toBe(container.firstChild);\n    });\n  });\n\n  describe('disabled state', () => {\n    it('should not start dragging when disabled', () => {\n      const onStart = vi.fn();\n      const { container } = render(\n        <DraggableCoreWrapper disabled onStart={onStart}>\n          <div />\n        </DraggableCoreWrapper>\n      );\n\n      startDrag(container.firstChild, { x: 0, y: 0 });\n      expect(onStart).not.toHaveBeenCalled();\n    });\n  });\n\n  describe('cancellation', () => {\n    it('should cancel drag when onStart returns false', () => {\n      const onStart = vi.fn(() => false);\n      const onDrag = vi.fn();\n      const { container } = render(\n        <DraggableCoreWrapper onStart={onStart} onDrag={onDrag}>\n          <div />\n        </DraggableCoreWrapper>\n      );\n\n      startDrag(container.firstChild, { x: 0, y: 0 });\n      moveDrag({ x: 100, y: 100 });\n\n      expect(onStart).toHaveBeenCalled();\n      expect(onDrag).not.toHaveBeenCalled();\n    });\n\n    it('should stop drag when onDrag returns false', () => {\n      const onDrag = vi.fn(() => false);\n      const onStop = vi.fn();\n      const { container } = render(\n        <DraggableCoreWrapper onDrag={onDrag} onStop={onStop}>\n          <div />\n        </DraggableCoreWrapper>\n      );\n\n      startDrag(container.firstChild, { x: 0, y: 0 });\n      moveDrag({ x: 100, y: 100 });\n\n      expect(onDrag).toHaveBeenCalled();\n      expect(onStop).toHaveBeenCalled();\n    });\n  });\n\n  describe('handle prop', () => {\n    it('should only drag from handle', () => {\n      const onStart = vi.fn();\n      const { container } = render(\n        <DraggableCoreWrapper handle=\".handle\" onStart={onStart}>\n          <div>\n            <div className=\"handle\">Handle</div>\n            <div className=\"content\">Content</div>\n          </div>\n        </DraggableCoreWrapper>\n      );\n\n      // Click on content - should not start drag\n      startDrag(container.querySelector('.content'), { x: 0, y: 0 });\n      expect(onStart).not.toHaveBeenCalled();\n\n      // Click on handle - should start drag\n      startDrag(container.querySelector('.handle'), { x: 0, y: 0 });\n      expect(onStart).toHaveBeenCalled();\n    });\n\n    it('should work with nested elements in handle', () => {\n      const onStart = vi.fn();\n      const { container } = render(\n        <DraggableCoreWrapper handle=\".handle\" onStart={onStart}>\n          <div>\n            <div className=\"handle\">\n              <span className=\"nested\">Nested</span>\n            </div>\n          </div>\n        </DraggableCoreWrapper>\n      );\n\n      startDrag(container.querySelector('.nested'), { x: 0, y: 0 });\n      expect(onStart).toHaveBeenCalled();\n    });\n  });\n\n  describe('cancel prop', () => {\n    it('should not drag from cancel elements', () => {\n      const onStart = vi.fn();\n      const { container } = render(\n        <DraggableCoreWrapper cancel=\".cancel\" onStart={onStart}>\n          <div>\n            <div className=\"cancel\">Cancel</div>\n            <div className=\"content\">Content</div>\n          </div>\n        </DraggableCoreWrapper>\n      );\n\n      // Click on cancel - should not start drag\n      startDrag(container.querySelector('.cancel'), { x: 0, y: 0 });\n      expect(onStart).not.toHaveBeenCalled();\n\n      // Click on content - should start drag\n      startDrag(container.querySelector('.content'), { x: 0, y: 0 });\n      expect(onStart).toHaveBeenCalled();\n    });\n  });\n\n  describe('grid prop', () => {\n    it('should snap movement to grid', () => {\n      const onDrag = vi.fn();\n      const { container } = render(\n        <DraggableCoreWrapper grid={[50, 50]} onDrag={onDrag}>\n          <div />\n        </DraggableCoreWrapper>\n      );\n\n      startDrag(container.firstChild, { x: 0, y: 0 });\n      moveDrag({ x: 30, y: 30 }); // Less than half grid, should snap to 50\n\n      expect(onDrag).toHaveBeenCalled();\n      const data = onDrag.mock.calls[0][1];\n      expect(data.x).toBe(50);\n      expect(data.y).toBe(50);\n    });\n\n    it('should not call onDrag if movement is less than grid', () => {\n      const onDrag = vi.fn();\n      const { container } = render(\n        <DraggableCoreWrapper grid={[100, 100]} onDrag={onDrag}>\n          <div />\n        </DraggableCoreWrapper>\n      );\n\n      startDrag(container.firstChild, { x: 0, y: 0 });\n      moveDrag({ x: 20, y: 20 }); // Much less than half grid\n\n      // Should not be called because snapped delta is 0\n      expect(onDrag).not.toHaveBeenCalled();\n    });\n  });\n\n  describe('scale prop', () => {\n    it('should adjust position based on scale', () => {\n      const onDrag = vi.fn();\n      const { container } = render(\n        <DraggableCoreWrapper scale={2} onDrag={onDrag}>\n          <div />\n        </DraggableCoreWrapper>\n      );\n\n      startDrag(container.firstChild, { x: 0, y: 0 });\n      moveDrag({ x: 100, y: 100 });\n\n      const data = onDrag.mock.calls[0][1];\n      // With scale 2, 100px movement = 50 logical units\n      expect(data.deltaX).toBe(50);\n      expect(data.deltaY).toBe(50);\n    });\n\n    it('should work with scale < 1', () => {\n      const onDrag = vi.fn();\n      const { container } = render(\n        <DraggableCoreWrapper scale={0.5} onDrag={onDrag}>\n          <div />\n        </DraggableCoreWrapper>\n      );\n\n      startDrag(container.firstChild, { x: 0, y: 0 });\n      moveDrag({ x: 100, y: 100 });\n\n      const data = onDrag.mock.calls[0][1];\n      // With scale 0.5, 100px movement = 200 logical units\n      expect(data.deltaX).toBe(200);\n      expect(data.deltaY).toBe(200);\n    });\n  });\n\n  describe('allowAnyClick prop', () => {\n    it('should only respond to left click by default', () => {\n      const onStart = vi.fn();\n      const { container } = render(\n        <DraggableCoreWrapper onStart={onStart}>\n          <div />\n        </DraggableCoreWrapper>\n      );\n\n      // Right click (button 2)\n      container.firstChild.dispatchEvent(new MouseEvent('mousedown', {\n        bubbles: true,\n        cancelable: true,\n        button: 2,\n        clientX: 0,\n        clientY: 0,\n      }));\n      expect(onStart).not.toHaveBeenCalled();\n    });\n\n    it('should respond to any click when allowAnyClick is true', () => {\n      const onStart = vi.fn();\n      const { container } = render(\n        <DraggableCoreWrapper allowAnyClick onStart={onStart}>\n          <div />\n        </DraggableCoreWrapper>\n      );\n\n      // Right click (button 2)\n      container.firstChild.dispatchEvent(new MouseEvent('mousedown', {\n        bubbles: true,\n        cancelable: true,\n        button: 2,\n        clientX: 0,\n        clientY: 0,\n      }));\n      expect(onStart).toHaveBeenCalled();\n    });\n  });\n\n  describe('nodeRef prop', () => {\n    it('should use nodeRef instead of findDOMNode', () => {\n      const onDrag = vi.fn();\n\n      function TestComponent() {\n        const nodeRef = useRef(null);\n        return (\n          <DraggableCore nodeRef={nodeRef} onDrag={onDrag}>\n            <div ref={nodeRef} data-testid=\"target\" />\n          </DraggableCore>\n        );\n      }\n\n      const { getByTestId } = render(<TestComponent />);\n      const target = getByTestId('target');\n\n      startDrag(target, { x: 0, y: 0 });\n      moveDrag({ x: 100, y: 100 });\n\n      expect(onDrag).toHaveBeenCalled();\n      expect(onDrag.mock.calls[0][1].node).toBe(target);\n    });\n\n    it('should work with forwardRef components', () => {\n      const onDrag = vi.fn();\n\n      const CustomComponent = React.forwardRef((props, ref) => (\n        <div {...props} ref={ref}>Custom</div>\n      ));\n\n      function TestComponent() {\n        const nodeRef = useRef(null);\n        return (\n          <DraggableCore nodeRef={nodeRef} onDrag={onDrag}>\n            <CustomComponent ref={nodeRef} data-testid=\"target\" />\n          </DraggableCore>\n        );\n      }\n\n      const { getByTestId } = render(<TestComponent />);\n      const target = getByTestId('target');\n\n      startDrag(target, { x: 0, y: 0 });\n      moveDrag({ x: 100, y: 100 });\n\n      expect(onDrag).toHaveBeenCalled();\n      expect(onDrag.mock.calls[0][1].node.textContent).toBe('Custom');\n    });\n  });\n\n  describe('touch events', () => {\n    it('should handle touch start', () => {\n      const onStart = vi.fn();\n      const { container } = render(\n        <DraggableCoreWrapper onStart={onStart}>\n          <div />\n        </DraggableCoreWrapper>\n      );\n\n      container.firstChild.dispatchEvent(createTouchEvent('touchstart', { clientX: 0, clientY: 0 }));\n      expect(onStart).toHaveBeenCalled();\n    });\n\n    it('should call preventDefault on touchstart by default', () => {\n      const { container } = render(\n        <DraggableCoreWrapper>\n          <div />\n        </DraggableCoreWrapper>\n      );\n\n      const event = createTouchEvent('touchstart', { clientX: 0, clientY: 0 });\n      let prevented = false;\n      event.preventDefault = () => { prevented = true; };\n\n      container.firstChild.dispatchEvent(event);\n      expect(prevented).toBe(true);\n    });\n\n    it('should not call preventDefault when allowMobileScroll is true', () => {\n      const { container } = render(\n        <DraggableCoreWrapper allowMobileScroll>\n          <div />\n        </DraggableCoreWrapper>\n      );\n\n      const event = createTouchEvent('touchstart', { clientX: 0, clientY: 0 });\n      let prevented = false;\n      event.preventDefault = () => { prevented = true; };\n\n      container.firstChild.dispatchEvent(event);\n      expect(prevented).toBe(false);\n    });\n  });\n\n  describe('onMouseDown prop', () => {\n    it('should call onMouseDown callback', () => {\n      const onMouseDown = vi.fn();\n      const { container } = render(\n        <DraggableCoreWrapper onMouseDown={onMouseDown}>\n          <div />\n        </DraggableCoreWrapper>\n      );\n\n      startDrag(container.firstChild, { x: 0, y: 0 });\n      expect(onMouseDown).toHaveBeenCalled();\n    });\n\n    it('should call onMouseDown even when disabled', () => {\n      const onMouseDown = vi.fn();\n      const onStart = vi.fn();\n      const { container } = render(\n        <DraggableCoreWrapper disabled onMouseDown={onMouseDown} onStart={onStart}>\n          <div />\n        </DraggableCoreWrapper>\n      );\n\n      startDrag(container.firstChild, { x: 0, y: 0 });\n      expect(onMouseDown).toHaveBeenCalled();\n      expect(onStart).not.toHaveBeenCalled();\n    });\n  });\n\n  describe('enableUserSelectHack prop', () => {\n    it('should add transparent selection class by default', () => {\n      const { container } = render(\n        <DraggableCoreWrapper>\n          <div />\n        </DraggableCoreWrapper>\n      );\n\n      startDrag(container.firstChild, { x: 0, y: 0 });\n      expect(document.body.classList.contains('react-draggable-transparent-selection')).toBe(true);\n\n      endDrag(container.firstChild, { x: 0, y: 0 });\n      // rAF mock immediately calls the callback\n      expect(document.body.classList.contains('react-draggable-transparent-selection')).toBe(false);\n    });\n\n    it('should not add class when enableUserSelectHack is false', () => {\n      const { container } = render(\n        <DraggableCoreWrapper enableUserSelectHack={false}>\n          <div />\n        </DraggableCoreWrapper>\n      );\n\n      startDrag(container.firstChild, { x: 0, y: 0 });\n      expect(document.body.classList.contains('react-draggable-transparent-selection')).toBe(false);\n    });\n\n    it('should not add user-select class when onStart returns false', () => {\n      const onStart = vi.fn(() => false);\n      const { container } = render(\n        <DraggableCoreWrapper onStart={onStart}>\n          <div />\n        </DraggableCoreWrapper>\n      );\n\n      // Ensure class is not present before drag\n      expect(document.body.classList.contains('react-draggable-transparent-selection')).toBe(false);\n\n      startDrag(container.firstChild, { x: 0, y: 0 });\n\n      // onStart returned false, so user-select hack should not be applied\n      expect(onStart).toHaveBeenCalled();\n      expect(document.body.classList.contains('react-draggable-transparent-selection')).toBe(false);\n    });\n  });\n\n  describe('unmount safety', () => {\n    it('should track mounted state correctly', () => {\n      const coreRef = React.createRef();\n      render(\n        <DraggableCoreWrapper coreRef={coreRef}>\n          <div />\n        </DraggableCoreWrapper>\n      );\n\n      // Verify mounted is true after mount\n      expect(coreRef.current.mounted).toBe(true);\n\n      // Store reference before unmount since ref.current becomes null\n      const instance = coreRef.current;\n      cleanup();\n\n      // Verify mounted is false after unmount (using stored reference)\n      expect(instance.mounted).toBe(false);\n    });\n\n    it('should not continue drag if onStart returns false', () => {\n      const onStart = vi.fn(() => false);\n      const onDrag = vi.fn();\n      const { container } = render(\n        <DraggableCoreWrapper onStart={onStart} onDrag={onDrag}>\n          <div />\n        </DraggableCoreWrapper>\n      );\n\n      startDrag(container.firstChild, { x: 0, y: 0 });\n      moveDrag({ x: 100, y: 100 });\n\n      expect(onStart).toHaveBeenCalled();\n      // onDrag should not be called because onStart returned false\n      expect(onDrag).not.toHaveBeenCalled();\n    });\n  });\n\n  describe('touch events with handle', () => {\n    it('should call preventDefault on touchstart when using handle', () => {\n      const { container } = render(\n        <DraggableCoreWrapper handle=\".handle\">\n          <div>\n            <div className=\"handle\">Handle</div>\n          </div>\n        </DraggableCoreWrapper>\n      );\n\n      const handle = container.querySelector('.handle');\n      const event = createTouchEvent('touchstart', { clientX: 0, clientY: 0 });\n      let prevented = false;\n      event.preventDefault = () => { prevented = true; };\n\n      handle.dispatchEvent(event);\n      expect(prevented).toBe(true);\n    });\n\n    it('should not call preventDefault on touchstart for cancel elements', () => {\n      const { container } = render(\n        <DraggableCoreWrapper cancel=\".cancel\">\n          <div>\n            <div className=\"cancel\">Cancel</div>\n            <div className=\"content\">Content</div>\n          </div>\n        </DraggableCoreWrapper>\n      );\n\n      const cancel = container.querySelector('.cancel');\n      const event = createTouchEvent('touchstart', { clientX: 0, clientY: 0 });\n      let prevented = false;\n      event.preventDefault = () => { prevented = true; };\n\n      cancel.dispatchEvent(event);\n      // Should not prevent default since drag was cancelled\n      expect(prevented).toBe(false);\n    });\n  });\n\n  describe('error handling', () => {\n    it('should throw error when no children provided', () => {\n      const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});\n      const nodeRef = React.createRef();\n\n      let threw = false;\n      try {\n        render(<DraggableCore nodeRef={nodeRef} />);\n      } catch (e) {\n        threw = true;\n        expect(e.message).toMatch(/React.Children.only|expected/i);\n      }\n\n      expect(threw).toBe(true);\n      errorSpy.mockRestore();\n    });\n  });\n});\n"
  },
  {
    "path": "test/browser/browser.test.js",
    "content": "/**\n * Browser-based tests using Puppeteer\n * These tests require a real browser for proper coordinate calculations\n */\nimport { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach } from 'vitest';\nimport puppeteer from 'puppeteer';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\ndescribe('Browser Tests', () => {\n  let browser;\n  let page;\n\n  beforeAll(async () => {\n    browser = await puppeteer.launch({\n      headless: true,\n      // Disable sandbox in CI environments (required for GitHub Actions runners)\n      args: process.env.CI ? ['--no-sandbox', '--disable-setuid-sandbox'] : [],\n    });\n  }, 30000);\n\n  afterAll(async () => {\n    if (browser) await browser.close();\n  });\n\n  beforeEach(async () => {\n    page = await browser.newPage();\n    // Load the test page via file protocol\n    const testHtmlPath = path.resolve(__dirname, 'test.html');\n    await page.goto(`file://${testHtmlPath}`);\n    // Wait for React/ReactDOM to be available\n    await page.waitForFunction(() => window.React && window.ReactDOM);\n  }, 30000);\n\n  afterEach(async () => {\n    if (page) await page.close();\n  });\n\n  describe('Dragging position', () => {\n    it('should update transform on drag', async () => {\n      // Render a basic draggable\n      await page.evaluate(() => {\n        const { React, ReactDOM, Draggable } = window;\n        const root = document.getElementById('root');\n        const ref = React.createRef();\n        ReactDOM.createRoot(root).render(\n          React.createElement(Draggable, { nodeRef: ref },\n            React.createElement('div', {\n              ref: ref,\n              id: 'draggable-test',\n              style: { width: '100px', height: '100px', background: 'blue' }\n            })\n          )\n        );\n      });\n\n      await page.waitForSelector('#draggable-test');\n\n      // Get initial transform\n      const initialTransform = await page.$eval('#draggable-test', el => el.style.transform);\n      expect(initialTransform).toMatch(/translate\\(0px,?\\s*0px\\)/);\n\n      // Simulate drag\n      const element = await page.$('#draggable-test');\n      const box = await element.boundingBox();\n\n      await page.mouse.move(box.x + 50, box.y + 50);\n      await page.mouse.down();\n      await page.mouse.move(box.x + 150, box.y + 150);\n      await page.mouse.up();\n\n      // Check final transform\n      const finalTransform = await page.$eval('#draggable-test', el => el.style.transform);\n      expect(finalTransform).toMatch(/translate\\(100px,?\\s*100px\\)/);\n    }, 30000);\n\n    it('should honor x axis constraint', async () => {\n      await page.evaluate(() => {\n        const { React, ReactDOM, Draggable } = window;\n        const root = document.getElementById('root');\n        const ref = React.createRef();\n        ReactDOM.createRoot(root).render(\n          React.createElement(Draggable, { axis: 'x', nodeRef: ref },\n            React.createElement('div', {\n              ref: ref,\n              id: 'draggable-test',\n              style: { width: '100px', height: '100px', background: 'blue' }\n            })\n          )\n        );\n      });\n\n      await page.waitForSelector('#draggable-test');\n\n      const element = await page.$('#draggable-test');\n      const box = await element.boundingBox();\n\n      await page.mouse.move(box.x + 50, box.y + 50);\n      await page.mouse.down();\n      await page.mouse.move(box.x + 150, box.y + 150);\n      await page.mouse.up();\n\n      const finalTransform = await page.$eval('#draggable-test', el => el.style.transform);\n      expect(finalTransform).toMatch(/translate\\(100px,?\\s*0px\\)/);\n    }, 30000);\n\n    it('should honor y axis constraint', async () => {\n      await page.evaluate(() => {\n        const { React, ReactDOM, Draggable } = window;\n        const root = document.getElementById('root');\n        const ref = React.createRef();\n        ReactDOM.createRoot(root).render(\n          React.createElement(Draggable, { axis: 'y', nodeRef: ref },\n            React.createElement('div', {\n              ref: ref,\n              id: 'draggable-test',\n              style: { width: '100px', height: '100px', background: 'blue' }\n            })\n          )\n        );\n      });\n\n      await page.waitForSelector('#draggable-test');\n\n      const element = await page.$('#draggable-test');\n      const box = await element.boundingBox();\n\n      await page.mouse.move(box.x + 50, box.y + 50);\n      await page.mouse.down();\n      await page.mouse.move(box.x + 150, box.y + 150);\n      await page.mouse.up();\n\n      const finalTransform = await page.$eval('#draggable-test', el => el.style.transform);\n      expect(finalTransform).toMatch(/translate\\(0px,?\\s*100px\\)/);\n    }, 30000);\n  });\n\n  describe('Callbacks', () => {\n    it('should call onStop when drag ends', async () => {\n      await page.evaluate(() => {\n        const { React, ReactDOM, Draggable } = window;\n        const root = document.getElementById('root');\n\n        window.stopCalled = false;\n        const ref = React.createRef();\n\n        ReactDOM.createRoot(root).render(\n          React.createElement(Draggable, {\n            nodeRef: ref,\n            onStop: () => { window.stopCalled = true; }\n          },\n            React.createElement('div', {\n              ref: ref,\n              id: 'draggable-test',\n              style: { width: '100px', height: '100px', background: 'blue' }\n            })\n          )\n        );\n      });\n\n      await page.waitForSelector('#draggable-test');\n\n      const element = await page.$('#draggable-test');\n      const box = await element.boundingBox();\n\n      await page.mouse.move(box.x + 50, box.y + 50);\n      await page.mouse.down();\n      await page.mouse.move(box.x + 150, box.y + 150);\n      await page.mouse.up();\n\n      const stopCalled = await page.evaluate(() => window.stopCalled);\n      expect(stopCalled).toBe(true);\n    }, 30000);\n  });\n\n  describe('Bounds', () => {\n    it('should clip dragging to bounds object', async () => {\n      await page.evaluate(() => {\n        const { React, ReactDOM, Draggable } = window;\n        const root = document.getElementById('root');\n        const ref = React.createRef();\n\n        ReactDOM.createRoot(root).render(\n          React.createElement(Draggable, {\n            nodeRef: ref,\n            bounds: { left: 0, right: 50, top: 0, bottom: 50 }\n          },\n            React.createElement('div', {\n              ref: ref,\n              id: 'draggable-test',\n              style: { width: '100px', height: '100px', background: 'blue' }\n            })\n          )\n        );\n      });\n\n      await page.waitForSelector('#draggable-test');\n\n      const element = await page.$('#draggable-test');\n      const box = await element.boundingBox();\n\n      await page.mouse.move(box.x + 50, box.y + 50);\n      await page.mouse.down();\n      await page.mouse.move(box.x + 200, box.y + 200);\n      await page.mouse.up();\n\n      const finalTransform = await page.$eval('#draggable-test', el => el.style.transform);\n      expect(finalTransform).toMatch(/translate\\(50px,?\\s*50px\\)/);\n    }, 30000);\n\n    it('should clip dragging to parent bounds', async () => {\n      await page.evaluate(() => {\n        const { React, ReactDOM, Draggable } = window;\n        const root = document.getElementById('root');\n        const ref = React.createRef();\n\n        ReactDOM.createRoot(root).render(\n          React.createElement('div', {\n            id: 'parent',\n            style: {\n              width: '300px',\n              height: '300px',\n              position: 'relative',\n              background: '#ccc'\n            }\n          },\n            React.createElement(Draggable, { bounds: 'parent', nodeRef: ref },\n              React.createElement('div', {\n                ref: ref,\n                id: 'draggable-test',\n                style: { width: '100px', height: '100px', background: 'blue' }\n              })\n            )\n          )\n        );\n      });\n\n      await page.waitForSelector('#draggable-test');\n\n      const element = await page.$('#draggable-test');\n      const box = await element.boundingBox();\n\n      // Try to drag far beyond parent bounds (300 - 100 = 200px max)\n      await page.mouse.move(box.x + 50, box.y + 50);\n      await page.mouse.down();\n      await page.mouse.move(box.x + 500, box.y + 500);\n      await page.mouse.up();\n\n      const finalTransform = await page.$eval('#draggable-test', el => el.style.transform);\n      // Should be clipped to 200px (parent width 300 - element width 100)\n      expect(finalTransform).toMatch(/translate\\(200px,?\\s*200px\\)/);\n    }, 30000);\n\n    it('should clip to negative bounds', async () => {\n      await page.evaluate(() => {\n        const { React, ReactDOM, Draggable } = window;\n        const root = document.getElementById('root');\n        const ref = React.createRef();\n\n        ReactDOM.createRoot(root).render(\n          React.createElement(Draggable, {\n            nodeRef: ref,\n            bounds: { left: -50, right: 50, top: -50, bottom: 50 }\n          },\n            React.createElement('div', {\n              ref: ref,\n              id: 'draggable-test',\n              style: { width: '100px', height: '100px', background: 'blue' }\n            })\n          )\n        );\n      });\n\n      await page.waitForSelector('#draggable-test');\n\n      const element = await page.$('#draggable-test');\n      const box = await element.boundingBox();\n\n      // Drag in negative direction\n      await page.mouse.move(box.x + 50, box.y + 50);\n      await page.mouse.down();\n      await page.mouse.move(box.x - 100, box.y - 100);\n      await page.mouse.up();\n\n      const finalTransform = await page.$eval('#draggable-test', el => el.style.transform);\n      expect(finalTransform).toMatch(/translate\\(-50px,?\\s*-50px\\)/);\n    }, 30000);\n  });\n\n  describe('Grid snapping', () => {\n    it('should snap movement to grid', async () => {\n      await page.evaluate(() => {\n        const { React, ReactDOM, Draggable } = window;\n        const root = document.getElementById('root');\n        const ref = React.createRef();\n\n        window.lastPosition = null;\n\n        ReactDOM.createRoot(root).render(\n          React.createElement(Draggable, {\n            nodeRef: ref,\n            grid: [25, 25],\n            onDrag: (e, data) => { window.lastPosition = { x: data.x, y: data.y }; }\n          },\n            React.createElement('div', {\n              ref: ref,\n              id: 'draggable-test',\n              style: { width: '100px', height: '100px', background: 'blue' }\n            })\n          )\n        );\n      });\n\n      await page.waitForSelector('#draggable-test');\n\n      const element = await page.$('#draggable-test');\n      const box = await element.boundingBox();\n\n      // Move 30px - should snap to 25px\n      await page.mouse.move(box.x + 50, box.y + 50);\n      await page.mouse.down();\n      await page.mouse.move(box.x + 80, box.y + 80);\n      await page.mouse.up();\n\n      const position = await page.evaluate(() => window.lastPosition);\n      expect(position.x).toBe(25);\n      expect(position.y).toBe(25);\n    }, 30000);\n\n    it('should not trigger onDrag when movement is less than grid', async () => {\n      await page.evaluate(() => {\n        const { React, ReactDOM, Draggable } = window;\n        const root = document.getElementById('root');\n        const ref = React.createRef();\n\n        window.dragCallCount = 0;\n\n        ReactDOM.createRoot(root).render(\n          React.createElement(Draggable, {\n            nodeRef: ref,\n            grid: [50, 50],\n            onDrag: () => { window.dragCallCount++; }\n          },\n            React.createElement('div', {\n              ref: ref,\n              id: 'draggable-test',\n              style: { width: '100px', height: '100px', background: 'blue' }\n            })\n          )\n        );\n      });\n\n      await page.waitForSelector('#draggable-test');\n\n      const element = await page.$('#draggable-test');\n      const box = await element.boundingBox();\n\n      // Move only 20px - less than half the grid size (25px)\n      await page.mouse.move(box.x + 50, box.y + 50);\n      await page.mouse.down();\n      await page.mouse.move(box.x + 70, box.y + 70);\n      await page.mouse.up();\n\n      const dragCallCount = await page.evaluate(() => window.dragCallCount);\n      // onDrag should not be called because movement didn't reach grid threshold\n      expect(dragCallCount).toBe(0);\n    }, 30000);\n\n    it('should snap to larger grid correctly', async () => {\n      await page.evaluate(() => {\n        const { React, ReactDOM, Draggable } = window;\n        const root = document.getElementById('root');\n        const ref = React.createRef();\n\n        window.lastPosition = null;\n\n        ReactDOM.createRoot(root).render(\n          React.createElement(Draggable, {\n            nodeRef: ref,\n            grid: [100, 100],\n            onDrag: (e, data) => { window.lastPosition = { x: data.x, y: data.y }; }\n          },\n            React.createElement('div', {\n              ref: ref,\n              id: 'draggable-test',\n              style: { width: '100px', height: '100px', background: 'blue' }\n            })\n          )\n        );\n      });\n\n      await page.waitForSelector('#draggable-test');\n\n      const element = await page.$('#draggable-test');\n      const box = await element.boundingBox();\n\n      // Move 150px - should snap to 200px (closest grid point rounding)\n      await page.mouse.move(box.x + 50, box.y + 50);\n      await page.mouse.down();\n      await page.mouse.move(box.x + 200, box.y + 200);\n      await page.mouse.up();\n\n      const position = await page.evaluate(() => window.lastPosition);\n      // At 150px movement, snaps to 200px (nearest grid point with rounding)\n      expect(position.x).toBe(200);\n      expect(position.y).toBe(200);\n    }, 30000);\n  });\n\n  describe('Scale support', () => {\n    it('should adjust position when scale is 2x', async () => {\n      await page.evaluate(() => {\n        const { React, ReactDOM, Draggable } = window;\n        const root = document.getElementById('root');\n        const ref = React.createRef();\n\n        window.dragData = null;\n\n        ReactDOM.createRoot(root).render(\n          React.createElement(Draggable, {\n            nodeRef: ref,\n            scale: 2,\n            onDrag: (e, data) => { window.dragData = { x: data.x, y: data.y }; }\n          },\n            React.createElement('div', {\n              ref: ref,\n              id: 'draggable-test',\n              style: { width: '100px', height: '100px', background: 'blue' }\n            })\n          )\n        );\n      });\n\n      await page.waitForSelector('#draggable-test');\n\n      const element = await page.$('#draggable-test');\n      const box = await element.boundingBox();\n\n      await page.mouse.move(box.x + 50, box.y + 50);\n      await page.mouse.down();\n      // Move 100px visually, should be 50px in draggable coords due to 2x scale\n      await page.mouse.move(box.x + 150, box.y + 150);\n      await page.mouse.up();\n\n      const dragData = await page.evaluate(() => window.dragData);\n      expect(dragData.x).toBe(50);\n      expect(dragData.y).toBe(50);\n    }, 30000);\n  });\n\n  describe('Handle and Cancel', () => {\n    it('should only drag from handle', async () => {\n      await page.evaluate(() => {\n        const { React, ReactDOM, Draggable } = window;\n        const root = document.getElementById('root');\n        const ref = React.createRef();\n\n        ReactDOM.createRoot(root).render(\n          React.createElement(Draggable, { handle: '.handle', nodeRef: ref },\n            React.createElement('div', {\n              ref: ref,\n              id: 'draggable-test',\n              style: { width: '200px', height: '100px', background: 'blue' }\n            },\n              React.createElement('div', {\n                className: 'handle',\n                style: { width: '50px', height: '50px', background: 'red' }\n              }),\n              React.createElement('div', {\n                className: 'content',\n                style: { width: '50px', height: '50px', background: 'green' }\n              })\n            )\n          )\n        );\n      });\n\n      await page.waitForSelector('#draggable-test');\n\n      // Try dragging from content (should not work)\n      const content = await page.$('.content');\n      const contentBox = await content.boundingBox();\n\n      await page.mouse.move(contentBox.x + 25, contentBox.y + 25);\n      await page.mouse.down();\n      await page.mouse.move(contentBox.x + 125, contentBox.y + 125);\n      await page.mouse.up();\n\n      let transform = await page.$eval('#draggable-test', el => el.style.transform);\n      expect(transform).toMatch(/translate\\(0px,?\\s*0px\\)/);\n\n      // Try dragging from handle (should work)\n      const handle = await page.$('.handle');\n      const handleBox = await handle.boundingBox();\n\n      await page.mouse.move(handleBox.x + 25, handleBox.y + 25);\n      await page.mouse.down();\n      await page.mouse.move(handleBox.x + 125, handleBox.y + 125);\n      await page.mouse.up();\n\n      transform = await page.$eval('#draggable-test', el => el.style.transform);\n      expect(transform).toMatch(/translate\\(100px,?\\s*100px\\)/);\n    }, 30000);\n\n    it('should drag from deeply nested handle elements', async () => {\n      await page.evaluate(() => {\n        const { React, ReactDOM, Draggable } = window;\n        const root = document.getElementById('root');\n        const ref = React.createRef();\n\n        ReactDOM.createRoot(root).render(\n          React.createElement(Draggable, { handle: '.handle', nodeRef: ref },\n            React.createElement('div', {\n              ref: ref,\n              id: 'draggable-test',\n              style: { width: '200px', height: '100px', background: 'blue' }\n            },\n              React.createElement('div', { className: 'handle' },\n                React.createElement('div', null,\n                  React.createElement('span', null,\n                    React.createElement('div', { className: 'deep', style: { width: '50px', height: '50px', background: 'red' } })\n                  )\n                )\n              ),\n              React.createElement('div', { className: 'content', style: { width: '50px', height: '50px', background: 'green' } })\n            )\n          )\n        );\n      });\n\n      await page.waitForSelector('#draggable-test');\n\n      // Drag from deeply nested element inside handle\n      const deep = await page.$('.deep');\n      const deepBox = await deep.boundingBox();\n\n      await page.mouse.move(deepBox.x + 25, deepBox.y + 25);\n      await page.mouse.down();\n      await page.mouse.move(deepBox.x + 125, deepBox.y + 125);\n      await page.mouse.up();\n\n      const transform = await page.$eval('#draggable-test', el => el.style.transform);\n      expect(transform).toMatch(/translate\\(100px,?\\s*100px\\)/);\n    }, 30000);\n\n    it('should not drag from deeply nested cancel elements', async () => {\n      await page.evaluate(() => {\n        const { React, ReactDOM, Draggable } = window;\n        const root = document.getElementById('root');\n        const ref = React.createRef();\n\n        ReactDOM.createRoot(root).render(\n          React.createElement(Draggable, { cancel: '.cancel', nodeRef: ref },\n            React.createElement('div', {\n              ref: ref,\n              id: 'draggable-test',\n              style: { width: '200px', height: '100px', background: 'blue' }\n            },\n              React.createElement('div', { className: 'cancel' },\n                React.createElement('div', null,\n                  React.createElement('span', null,\n                    React.createElement('div', { className: 'deep', style: { width: '50px', height: '50px', background: 'red' } })\n                  )\n                )\n              ),\n              React.createElement('div', { className: 'content', style: { width: '50px', height: '50px', background: 'green' } })\n            )\n          )\n        );\n      });\n\n      await page.waitForSelector('#draggable-test');\n\n      // Drag from deeply nested element inside cancel (should not work)\n      const deep = await page.$('.deep');\n      const deepBox = await deep.boundingBox();\n\n      await page.mouse.move(deepBox.x + 25, deepBox.y + 25);\n      await page.mouse.down();\n      await page.mouse.move(deepBox.x + 125, deepBox.y + 125);\n      await page.mouse.up();\n\n      const transform = await page.$eval('#draggable-test', el => el.style.transform);\n      expect(transform).toMatch(/translate\\(0px,?\\s*0px\\)/);\n    }, 30000);\n  });\n\n  describe('Iframe support', () => {\n    it('should work correctly inside an iframe', async () => {\n      await page.evaluate(() => {\n        const { React, ReactDOM, Draggable } = window;\n        const root = document.getElementById('root');\n\n        // Create an iframe\n        const iframe = document.createElement('iframe');\n        iframe.id = 'test-iframe';\n        iframe.style.cssText = 'width: 500px; height: 500px; border: 1px solid black;';\n        root.appendChild(iframe);\n\n        // Wait for iframe to load and get its document\n        const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;\n\n        // Write basic HTML structure into iframe\n        iframeDoc.open();\n        iframeDoc.write(`\n          <!DOCTYPE html>\n          <html>\n          <head><style>body { margin: 0; padding: 20px; }</style></head>\n          <body><div id=\"iframe-root\"></div></body>\n          </html>\n        `);\n        iframeDoc.close();\n\n        // Copy React and ReactDOM to iframe window\n        iframe.contentWindow.React = React;\n        iframe.contentWindow.ReactDOM = ReactDOM;\n\n        // Render Draggable into iframe\n        const iframeRoot = iframeDoc.getElementById('iframe-root');\n        const ref = React.createRef();\n        ReactDOM.createRoot(iframeRoot).render(\n          React.createElement(Draggable, { nodeRef: ref },\n            React.createElement('div', {\n              ref: ref,\n              id: 'iframe-draggable',\n              style: { width: '100px', height: '100px', background: 'blue' }\n            })\n          )\n        );\n      });\n\n      // Wait for draggable to render in iframe\n      await page.waitForFunction(() => {\n        const iframe = document.getElementById('test-iframe');\n        return iframe && iframe.contentDocument.getElementById('iframe-draggable');\n      });\n\n      // Get the iframe element\n      const iframeHandle = await page.$('#test-iframe');\n      const iframeBox = await iframeHandle.boundingBox();\n\n      // Get the draggable element inside iframe\n      const frame = await iframeHandle.contentFrame();\n      const draggable = await frame.$('#iframe-draggable');\n      const draggableBox = await draggable.boundingBox();\n\n      // Verify initial transform\n      const initialTransform = await frame.$eval('#iframe-draggable', el => el.style.transform);\n      expect(initialTransform).toMatch(/translate\\(0px,?\\s*0px\\)/);\n\n      // Perform drag inside iframe\n      await page.mouse.move(draggableBox.x + 50, draggableBox.y + 50);\n      await page.mouse.down();\n      await page.mouse.move(draggableBox.x + 150, draggableBox.y + 150);\n      await page.mouse.up();\n\n      // Verify transform was updated\n      const finalTransform = await frame.$eval('#iframe-draggable', el => el.style.transform);\n      expect(finalTransform).toMatch(/translate\\(100px,?\\s*100px\\)/);\n    }, 30000);\n\n    it('should respect bounds inside an iframe', async () => {\n      await page.evaluate(() => {\n        const { React, ReactDOM, Draggable } = window;\n        const root = document.getElementById('root');\n\n        // Create an iframe\n        const iframe = document.createElement('iframe');\n        iframe.id = 'test-iframe-bounds';\n        iframe.style.cssText = 'width: 500px; height: 500px; border: 1px solid black;';\n        root.appendChild(iframe);\n\n        const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;\n\n        iframeDoc.open();\n        iframeDoc.write(`\n          <!DOCTYPE html>\n          <html>\n          <head><style>body { margin: 0; padding: 0; }</style></head>\n          <body>\n            <div id=\"iframe-root\" style=\"position: relative; width: 300px; height: 300px; background: #ccc;\"></div>\n          </body>\n          </html>\n        `);\n        iframeDoc.close();\n\n        iframe.contentWindow.React = React;\n        iframe.contentWindow.ReactDOM = ReactDOM;\n\n        const iframeRoot = iframeDoc.getElementById('iframe-root');\n        const ref = React.createRef();\n        ReactDOM.createRoot(iframeRoot).render(\n          React.createElement(Draggable, { bounds: 'parent', nodeRef: ref },\n            React.createElement('div', {\n              ref: ref,\n              id: 'iframe-draggable-bounds',\n              style: { width: '100px', height: '100px', background: 'blue' }\n            })\n          )\n        );\n      });\n\n      await page.waitForFunction(() => {\n        const iframe = document.getElementById('test-iframe-bounds');\n        return iframe && iframe.contentDocument.getElementById('iframe-draggable-bounds');\n      });\n\n      const iframeHandle = await page.$('#test-iframe-bounds');\n      const frame = await iframeHandle.contentFrame();\n      const draggable = await frame.$('#iframe-draggable-bounds');\n      const draggableBox = await draggable.boundingBox();\n\n      // Try to drag beyond parent bounds\n      await page.mouse.move(draggableBox.x + 50, draggableBox.y + 50);\n      await page.mouse.down();\n      await page.mouse.move(draggableBox.x + 500, draggableBox.y + 500);\n      await page.mouse.up();\n\n      // Should be clipped to parent bounds (300 - 100 = 200 max)\n      const finalTransform = await frame.$eval('#iframe-draggable-bounds', el => el.style.transform);\n      expect(finalTransform).toMatch(/translate\\(200px,?\\s*200px\\)/);\n    }, 30000);\n  });\n\n  describe('Scroll handling', () => {\n    it('should handle dragging in scrollable containers', async () => {\n      await page.evaluate(() => {\n        const { React, ReactDOM, Draggable } = window;\n        const root = document.getElementById('root');\n        const ref = React.createRef();\n\n        window.dragData = null;\n\n        // Create a simple draggable that we can verify drag works\n        ReactDOM.createRoot(root).render(\n          React.createElement(Draggable, {\n            nodeRef: ref,\n            onDrag: (e, data) => { window.dragData = { x: data.x, y: data.y, deltaX: data.deltaX, deltaY: data.deltaY }; }\n          },\n            React.createElement('div', {\n              ref: ref,\n              id: 'draggable-test',\n              style: { width: '100px', height: '100px', background: 'blue' }\n            })\n          )\n        );\n      });\n\n      await page.waitForSelector('#draggable-test');\n\n      const element = await page.$('#draggable-test');\n      const box = await element.boundingBox();\n\n      // Perform drag and verify position calculation\n      await page.mouse.move(box.x + 50, box.y + 50);\n      await page.mouse.down();\n      await page.mouse.move(box.x + 150, box.y + 150);\n      await page.mouse.up();\n\n      const dragData = await page.evaluate(() => window.dragData);\n      expect(dragData).not.toBeNull();\n      expect(dragData.deltaX).toBe(100);\n      expect(dragData.deltaY).toBe(100);\n    }, 30000);\n  });\n\n  describe('Shadow DOM support', () => {\n    it('should clip dragging to parent bounds in shadow DOM', async () => {\n      await page.evaluate(() => {\n        const { React, ReactDOM, Draggable } = window;\n        const root = document.getElementById('root');\n\n        window.dragData = null;\n\n        // Create a shadow host\n        const shadowHost = document.createElement('div');\n        shadowHost.id = 'shadow-host';\n        root.appendChild(shadowHost);\n\n        const shadowRoot = shadowHost.attachShadow({ mode: 'open' });\n\n        // Create container in shadow DOM\n        const container = document.createElement('div');\n        container.id = 'shadow-container';\n        container.style.cssText = 'position: relative; width: 200px; height: 200px; background: #ccc;';\n        shadowRoot.appendChild(container);\n\n        // Render Draggable into shadow DOM\n        const ref = React.createRef();\n        ReactDOM.createRoot(container).render(\n          React.createElement(Draggable, {\n            nodeRef: ref,\n            bounds: 'parent',\n            defaultPosition: { x: 50, y: 50 },\n            onDrag: (e, data) => { window.dragData = { x: data.x, y: data.y }; }\n          },\n            React.createElement('div', {\n              ref: ref,\n              id: 'shadow-draggable',\n              style: { width: '100px', height: '100px', background: 'blue' }\n            })\n          )\n        );\n      });\n\n      // Wait for element in shadow DOM\n      await page.waitForFunction(() => {\n        const host = document.getElementById('shadow-host');\n        return host && host.shadowRoot && host.shadowRoot.getElementById('shadow-draggable');\n      });\n\n      // Get element from shadow DOM\n      const element = await page.evaluateHandle(() => {\n        const host = document.getElementById('shadow-host');\n        return host.shadowRoot.getElementById('shadow-draggable');\n      });\n      const box = await element.boundingBox();\n\n      // Try to drag beyond parent bounds\n      await page.mouse.move(box.x + 50, box.y + 50);\n      await page.mouse.down();\n      await page.mouse.move(box.x + 500, box.y + 500);\n      await page.mouse.up();\n\n      const dragData = await page.evaluate(() => window.dragData);\n      // Should be clipped to parent bounds (200 - 100 = 100 max)\n      expect(dragData.x).toBe(100);\n      expect(dragData.y).toBe(100);\n    }, 30000);\n\n    it('should clip dragging to selector bounds in shadow DOM', async () => {\n      await page.evaluate(() => {\n        const { React, ReactDOM, Draggable } = window;\n        const root = document.getElementById('root');\n\n        window.dragData = null;\n\n        // Create a shadow host\n        const shadowHost = document.createElement('div');\n        shadowHost.id = 'shadow-host-2';\n        root.appendChild(shadowHost);\n\n        const shadowRoot = shadowHost.attachShadow({ mode: 'open' });\n\n        // Create container with ID in shadow DOM\n        const container = document.createElement('div');\n        container.id = 'bounds-container';\n        container.style.cssText = 'position: relative; width: 200px; height: 200px; background: #ccc;';\n        shadowRoot.appendChild(container);\n\n        // Render Draggable into shadow DOM with selector bounds\n        const ref = React.createRef();\n        ReactDOM.createRoot(container).render(\n          React.createElement(Draggable, {\n            nodeRef: ref,\n            bounds: '#bounds-container',\n            defaultPosition: { x: 50, y: 50 },\n            onDrag: (e, data) => { window.dragData = { x: data.x, y: data.y }; }\n          },\n            React.createElement('div', {\n              ref: ref,\n              id: 'shadow-draggable-2',\n              style: { width: '100px', height: '100px', background: 'blue' }\n            })\n          )\n        );\n      });\n\n      // Wait for element in shadow DOM\n      await page.waitForFunction(() => {\n        const host = document.getElementById('shadow-host-2');\n        return host && host.shadowRoot && host.shadowRoot.getElementById('shadow-draggable-2');\n      });\n\n      // Get element from shadow DOM\n      const element = await page.evaluateHandle(() => {\n        const host = document.getElementById('shadow-host-2');\n        return host.shadowRoot.getElementById('shadow-draggable-2');\n      });\n      const box = await element.boundingBox();\n\n      // Try to drag beyond bounds\n      await page.mouse.move(box.x + 50, box.y + 50);\n      await page.mouse.down();\n      await page.mouse.move(box.x + 500, box.y + 500);\n      await page.mouse.up();\n\n      const dragData = await page.evaluate(() => window.dragData);\n      // Should be clipped to bounds\n      expect(dragData.x).toBe(100);\n      expect(dragData.y).toBe(100);\n    }, 30000);\n  });\n\n  describe('Unmount safety', () => {\n    it('should not throw when unmounted during onStop callback', async () => {\n      await page.evaluate(() => {\n        const { React, ReactDOM, Draggable } = window;\n        const root = document.getElementById('root');\n\n        window.errorThrown = false;\n        window.originalError = window.onerror;\n        window.onerror = () => { window.errorThrown = true; };\n\n        function App() {\n          const [visible, setVisible] = React.useState(true);\n          const ref = React.useRef(null);\n          window.setVisible = setVisible;\n\n          if (!visible) return React.createElement('div', { id: 'unmounted' }, 'Unmounted');\n\n          return React.createElement(Draggable, {\n            nodeRef: ref,\n            onStop: () => { setVisible(false); }\n          },\n            React.createElement('div', {\n              ref: ref,\n              id: 'draggable-test',\n              style: { width: '100px', height: '100px', background: 'blue' }\n            })\n          );\n        }\n\n        ReactDOM.createRoot(root).render(React.createElement(App));\n      });\n\n      await page.waitForSelector('#draggable-test');\n\n      const element = await page.$('#draggable-test');\n      const box = await element.boundingBox();\n\n      // Perform drag that will unmount the component in onStop\n      await page.mouse.move(box.x + 50, box.y + 50);\n      await page.mouse.down();\n      await page.mouse.move(box.x + 150, box.y + 150);\n      await page.mouse.up();\n\n      // Verify component unmounted\n      await page.waitForSelector('#unmounted');\n\n      // Verify no error was thrown\n      const errorThrown = await page.evaluate(() => window.errorThrown);\n      expect(errorThrown).toBe(false);\n\n      // Cleanup\n      await page.evaluate(() => { window.onerror = window.originalError; });\n    }, 30000);\n  });\n\n  describe('Input focus preservation', () => {\n    it('should not defocus inputs when draggable unmounts', async () => {\n      await page.evaluate(() => {\n        const { React, ReactDOM, Draggable } = window;\n        const root = document.getElementById('root');\n\n        window.inputBlurred = false;\n\n        function App() {\n          const [showDraggable, setShowDraggable] = React.useState(true);\n          const ref = React.useRef(null);\n          window.setShowDraggable = setShowDraggable;\n\n          return React.createElement('div', null,\n            React.createElement('input', {\n              id: 'test-input',\n              type: 'text',\n              onBlur: () => { window.inputBlurred = true; }\n            }),\n            showDraggable && React.createElement(Draggable, { nodeRef: ref },\n              React.createElement('div', {\n                ref: ref,\n                id: 'draggable-test',\n                style: { width: '100px', height: '100px', background: 'blue' }\n              })\n            )\n          );\n        }\n\n        ReactDOM.createRoot(root).render(React.createElement(App));\n      });\n\n      await page.waitForSelector('#test-input');\n      await page.waitForSelector('#draggable-test');\n\n      // Focus the input\n      await page.focus('#test-input');\n      await page.type('#test-input', 'hello');\n\n      // Verify input is focused\n      const isFocused = await page.evaluate(() =>\n        document.activeElement === document.getElementById('test-input')\n      );\n      expect(isFocused).toBe(true);\n\n      // Reset blur tracking\n      await page.evaluate(() => { window.inputBlurred = false; });\n\n      // Unmount the draggable while input is focused\n      await page.evaluate(() => { window.setShowDraggable(false); });\n\n      // Wait for draggable to be removed\n      await page.waitForFunction(() => !document.getElementById('draggable-test'));\n\n      // Verify input wasn't blurred by the unmount\n      const wasBlurred = await page.evaluate(() => window.inputBlurred);\n      expect(wasBlurred).toBe(false);\n\n      // Verify input is still focused\n      const stillFocused = await page.evaluate(() =>\n        document.activeElement === document.getElementById('test-input')\n      );\n      expect(stillFocused).toBe(true);\n\n      // Verify input value is preserved\n      const inputValue = await page.$eval('#test-input', el => el.value);\n      expect(inputValue).toBe('hello');\n    }, 30000);\n\n    it('should not steal focus from inputs when starting drag', async () => {\n      await page.evaluate(() => {\n        const { React, ReactDOM, Draggable } = window;\n        const root = document.getElementById('root');\n        const ref = React.createRef();\n\n        window.inputBlurred = false;\n\n        ReactDOM.createRoot(root).render(\n          React.createElement('div', null,\n            React.createElement('input', {\n              id: 'test-input-drag',\n              type: 'text',\n              style: { marginBottom: '20px', display: 'block' },\n              onBlur: () => { window.inputBlurred = true; }\n            }),\n            React.createElement(Draggable, { nodeRef: ref },\n              React.createElement('div', {\n                ref: ref,\n                id: 'draggable-focus-test',\n                style: { width: '100px', height: '100px', background: 'blue' }\n              })\n            )\n          )\n        );\n      });\n\n      await page.waitForSelector('#test-input-drag');\n      await page.waitForSelector('#draggable-focus-test');\n\n      // Focus the input and type\n      await page.focus('#test-input-drag');\n      await page.type('#test-input-drag', 'test');\n\n      // Reset blur tracking\n      await page.evaluate(() => { window.inputBlurred = false; });\n\n      // Start dragging the draggable element (but don't click the input)\n      const element = await page.$('#draggable-focus-test');\n      const box = await element.boundingBox();\n\n      await page.mouse.move(box.x + 50, box.y + 50);\n      await page.mouse.down();\n      await page.mouse.move(box.x + 100, box.y + 100);\n      await page.mouse.up();\n\n      // The input will blur because we clicked elsewhere (on the draggable)\n      // This is expected browser behavior - we're testing that the library\n      // doesn't cause unexpected blurs during its internal operations\n      const inputValue = await page.$eval('#test-input-drag', el => el.value);\n      expect(inputValue).toBe('test');\n    }, 30000);\n  });\n\n  describe('Offset calculations', () => {\n    it('should calculate drag with offset position correctly', async () => {\n      await page.evaluate(() => {\n        const { React, ReactDOM, Draggable } = window;\n        const root = document.getElementById('root');\n        const ref = React.createRef();\n\n        window.dragData = null;\n\n        ReactDOM.createRoot(root).render(\n          React.createElement(Draggable, {\n            nodeRef: ref,\n            onDrag: (e, data) => { window.dragData = { x: data.x, y: data.y, deltaX: data.deltaX, deltaY: data.deltaY }; }\n          },\n            React.createElement('div', {\n              ref: ref,\n              id: 'draggable-test',\n              style: { position: 'relative', top: '200px', left: '200px', width: '100px', height: '100px', background: 'blue' }\n            })\n          )\n        );\n      });\n\n      await page.waitForSelector('#draggable-test');\n\n      const element = await page.$('#draggable-test');\n      const box = await element.boundingBox();\n\n      await page.mouse.move(box.x + 50, box.y + 50);\n      await page.mouse.down();\n      await page.mouse.move(box.x + 150, box.y + 150);\n      await page.mouse.up();\n\n      const dragData = await page.evaluate(() => window.dragData);\n      // Should report delta of 100, not accounting for initial CSS position\n      expect(dragData.deltaX).toBe(100);\n      expect(dragData.deltaY).toBe(100);\n    }, 30000);\n  });\n});\n"
  },
  {
    "path": "test/browser/test.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <title>React Draggable Browser Tests</title>\n  <style>\n    body { margin: 0; padding: 20px; }\n    #root { position: relative; }\n  </style>\n</head>\n<body>\n  <div id=\"root\"></div>\n  <!-- Load React 19 from esm.sh CDN (React 19 removed UMD builds) -->\n  <script type=\"importmap\">\n    {\n      \"imports\": {\n        \"react\": \"https://esm.sh/react@19?dev\",\n        \"react-dom\": \"https://esm.sh/react-dom@19?dev\",\n        \"react-dom/client\": \"https://esm.sh/react-dom@19/client?dev\"\n      }\n    }\n  </script>\n  <script type=\"module\">\n    import React from 'react';\n    import ReactDOM from 'react-dom';\n    import * as ReactDOMClient from 'react-dom/client';\n\n    // Make React available globally for the Draggable bundle\n    window.React = React;\n    window.ReactDOM = { ...ReactDOM, ...ReactDOMClient };\n\n    // Dynamically load the built library (it expects React/ReactDOM as globals)\n    const script = document.createElement('script');\n    script.src = '../../build/web/react-draggable.min.js';\n    script.onload = () => {\n      // Make things available globally for tests\n      window.Draggable = ReactDraggable.default;\n      window.DraggableCore = ReactDraggable.DraggableCore;\n    };\n    document.body.appendChild(script);\n  </script>\n</body>\n</html>\n"
  },
  {
    "path": "test/setup.js",
    "content": "import { vi } from 'vitest';\nimport '@testing-library/dom';\n\n// Mock requestAnimationFrame for consistent test behavior\nglobal.requestAnimationFrame = vi.fn((callback) => {\n  callback();\n  return 0;\n});\n\nglobal.cancelAnimationFrame = vi.fn();\n\n// Reset body margin like old tests did\nconst styleEl = document.createElement('style');\nstyleEl.textContent = 'body { margin: 0; }';\ndocument.head.appendChild(styleEl);\n"
  },
  {
    "path": "test/testUtils.js",
    "content": "/**\n * Test utilities for simulating drag events\n */\n\n/**\n * Create a MouseEvent with specified coordinates\n */\nexport function createMouseEvent(type, { clientX = 0, clientY = 0 } = {}) {\n  // Use the modern MouseEvent constructor without view (jsdom is strict about it)\n  return new MouseEvent(type, {\n    bubbles: true,\n    cancelable: true,\n    clientX,\n    clientY,\n    // Don't pass view - jsdom doesn't like it\n  });\n}\n\n/**\n * Create a TouchEvent with specified coordinates\n */\nexport function createTouchEvent(type, { clientX = 0, clientY = 0, identifier = 0 } = {}) {\n  const touch = {\n    identifier,\n    clientX,\n    clientY,\n    target: document.body,\n  };\n\n  const touchEvent = new Event(type, { bubbles: true, cancelable: true });\n  touchEvent.targetTouches = type === 'touchend' ? [] : [touch];\n  touchEvent.changedTouches = [touch];\n  touchEvent.touches = type === 'touchend' ? [] : [touch];\n\n  return touchEvent;\n}\n\n/**\n * Simulate a drag operation from one point to another\n */\nexport function simulateDrag(element, { from = { x: 0, y: 0 }, to = { x: 0, y: 0 } } = {}) {\n  // Mouse down at start position\n  element.dispatchEvent(createMouseEvent('mousedown', { clientX: from.x, clientY: from.y }));\n\n  // Mouse move to end position (dispatched on document)\n  document.dispatchEvent(createMouseEvent('mousemove', { clientX: to.x, clientY: to.y }));\n\n  // Mouse up at end position (dispatched on document where DraggableCore listens)\n  document.dispatchEvent(createMouseEvent('mouseup', { clientX: to.x, clientY: to.y }));\n}\n\n/**\n * Start a drag operation (mousedown only)\n */\nexport function startDrag(element, { x = 0, y = 0 } = {}) {\n  element.dispatchEvent(createMouseEvent('mousedown', { clientX: x, clientY: y }));\n}\n\n/**\n * Move during a drag operation (mousemove on document)\n */\nexport function moveDrag({ x = 0, y = 0 } = {}) {\n  document.dispatchEvent(createMouseEvent('mousemove', { clientX: x, clientY: y }));\n}\n\n/**\n * End a drag operation (mouseup on document where DraggableCore listens)\n */\nexport function endDrag(element, { x = 0, y = 0 } = {}) {\n  // DraggableCore binds mouseup listener to ownerDocument, not the element\n  document.dispatchEvent(createMouseEvent('mouseup', { clientX: x, clientY: y }));\n}\n\n/**\n * Simulate a touch drag operation\n */\nexport function simulateTouchDrag(element, { from = { x: 0, y: 0 }, to = { x: 0, y: 0 } } = {}) {\n  element.dispatchEvent(createTouchEvent('touchstart', { clientX: from.x, clientY: from.y }));\n  document.dispatchEvent(createTouchEvent('touchmove', { clientX: to.x, clientY: to.y }));\n  element.dispatchEvent(createTouchEvent('touchend', { clientX: to.x, clientY: to.y }));\n}\n\n/**\n * Wait for the next tick\n */\nexport function nextTick() {\n  return new Promise((resolve) => setTimeout(resolve, 0));\n}\n"
  },
  {
    "path": "test/utils/domFns.test.js",
    "content": "import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';\nimport {\n  matchesSelector,\n  matchesSelectorAndParentsTo,\n  addEvent,\n  removeEvent,\n  outerHeight,\n  outerWidth,\n  innerHeight,\n  innerWidth,\n  addClassName,\n  removeClassName,\n  getTranslation,\n} from '../../lib/utils/domFns';\n\ndescribe('domFns utilities', () => {\n  let container;\n\n  beforeEach(() => {\n    container = document.createElement('div');\n    document.body.appendChild(container);\n  });\n\n  afterEach(() => {\n    document.body.removeChild(container);\n  });\n\n  describe('matchesSelector', () => {\n    it('should match simple class selectors', () => {\n      const el = document.createElement('div');\n      el.className = 'foo bar';\n      expect(matchesSelector(el, '.foo')).toBe(true);\n      expect(matchesSelector(el, '.bar')).toBe(true);\n      expect(matchesSelector(el, '.baz')).toBe(false);\n    });\n\n    it('should match id selectors', () => {\n      const el = document.createElement('div');\n      el.id = 'test';\n      expect(matchesSelector(el, '#test')).toBe(true);\n      expect(matchesSelector(el, '#other')).toBe(false);\n    });\n\n    it('should match tag selectors', () => {\n      const div = document.createElement('div');\n      const span = document.createElement('span');\n      expect(matchesSelector(div, 'div')).toBe(true);\n      expect(matchesSelector(div, 'span')).toBe(false);\n      expect(matchesSelector(span, 'span')).toBe(true);\n    });\n  });\n\n  describe('matchesSelectorAndParentsTo', () => {\n    it('should match element itself', () => {\n      const el = document.createElement('div');\n      el.className = 'target';\n      container.appendChild(el);\n      expect(matchesSelectorAndParentsTo(el, '.target', container)).toBe(true);\n    });\n\n    it('should match parent elements', () => {\n      const parent = document.createElement('div');\n      parent.className = 'parent';\n      const child = document.createElement('span');\n      parent.appendChild(child);\n      container.appendChild(parent);\n\n      expect(matchesSelectorAndParentsTo(child, '.parent', container)).toBe(true);\n    });\n\n    it('should stop at baseNode', () => {\n      const outer = document.createElement('div');\n      outer.className = 'outer';\n      const inner = document.createElement('div');\n      const target = document.createElement('span');\n      inner.appendChild(target);\n      outer.appendChild(inner);\n      container.appendChild(outer);\n\n      // Should not match outer because inner is the baseNode\n      expect(matchesSelectorAndParentsTo(target, '.outer', inner)).toBe(false);\n      // But should match if container is baseNode\n      expect(matchesSelectorAndParentsTo(target, '.outer', container)).toBe(true);\n    });\n\n    it('should return false when no match', () => {\n      const el = document.createElement('div');\n      container.appendChild(el);\n      expect(matchesSelectorAndParentsTo(el, '.nonexistent', container)).toBe(false);\n    });\n  });\n\n  describe('addEvent / removeEvent', () => {\n    it('should add and remove event listeners', () => {\n      const handler = vi.fn();\n      const el = document.createElement('div');\n\n      addEvent(el, 'click', handler);\n      el.dispatchEvent(new Event('click', { bubbles: true }));\n      expect(handler).toHaveBeenCalledTimes(1);\n\n      removeEvent(el, 'click', handler);\n      el.dispatchEvent(new Event('click', { bubbles: true }));\n      expect(handler).toHaveBeenCalledTimes(1); // Still 1, not called again\n    });\n\n    it('should handle null elements gracefully', () => {\n      const handler = vi.fn();\n      expect(() => addEvent(null, 'click', handler)).not.toThrow();\n      expect(() => removeEvent(null, 'click', handler)).not.toThrow();\n    });\n  });\n\n  describe('outerHeight / outerWidth', () => {\n    it('should calculate outer dimensions including borders', () => {\n      const el = document.createElement('div');\n      el.style.width = '100px';\n      el.style.height = '100px';\n      el.style.borderWidth = '5px';\n      el.style.borderStyle = 'solid';\n      container.appendChild(el);\n\n      // In jsdom, getBoundingClientRect returns 0s, so we test the function exists\n      // Real dimension tests would need a real browser\n      expect(typeof outerHeight(el)).toBe('number');\n      expect(typeof outerWidth(el)).toBe('number');\n    });\n  });\n\n  describe('innerHeight / innerWidth', () => {\n    it('should calculate inner dimensions excluding padding', () => {\n      const el = document.createElement('div');\n      el.style.width = '100px';\n      el.style.height = '100px';\n      el.style.padding = '10px';\n      container.appendChild(el);\n\n      expect(typeof innerHeight(el)).toBe('number');\n      expect(typeof innerWidth(el)).toBe('number');\n    });\n  });\n\n  describe('addClassName / removeClassName', () => {\n    it('should add class names', () => {\n      const el = document.createElement('div');\n      addClassName(el, 'foo');\n      expect(el.classList.contains('foo')).toBe(true);\n    });\n\n    it('should not duplicate class names', () => {\n      const el = document.createElement('div');\n      addClassName(el, 'foo');\n      addClassName(el, 'foo');\n      expect(el.className).toBe('foo');\n    });\n\n    it('should remove class names', () => {\n      const el = document.createElement('div');\n      el.className = 'foo bar baz';\n      removeClassName(el, 'bar');\n      expect(el.classList.contains('foo')).toBe(true);\n      expect(el.classList.contains('bar')).toBe(false);\n      expect(el.classList.contains('baz')).toBe(true);\n    });\n\n    it('should handle removing non-existent classes', () => {\n      const el = document.createElement('div');\n      el.className = 'foo';\n      expect(() => removeClassName(el, 'bar')).not.toThrow();\n      expect(el.className).toBe('foo');\n    });\n  });\n\n  describe('getTranslation', () => {\n    it('should create translate string with px suffix', () => {\n      const result = getTranslation({ x: 100, y: 200 }, null, 'px');\n      expect(result).toBe('translate(100px,200px)');\n    });\n\n    it('should create translate string without suffix (for SVG)', () => {\n      const result = getTranslation({ x: 100, y: 200 }, null, '');\n      expect(result).toBe('translate(100,200)');\n    });\n\n    it('should include position offset when provided', () => {\n      const result = getTranslation({ x: 100, y: 200 }, { x: 10, y: 20 }, 'px');\n      expect(result).toBe('translate(10px, 20px)translate(100px,200px)');\n    });\n\n    it('should handle percentage offsets', () => {\n      const result = getTranslation({ x: 100, y: 200 }, { x: '10%', y: '20%' }, 'px');\n      expect(result).toBe('translate(10%, 20%)translate(100px,200px)');\n    });\n  });\n});\n"
  },
  {
    "path": "test/utils/getPrefix.test.js",
    "content": "import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';\nimport { getPrefix, browserPrefixToKey, browserPrefixToStyle } from '../../lib/utils/getPrefix';\n\ndescribe('getPrefix utilities', () => {\n  describe('browserPrefixToKey', () => {\n    it('should return prop unchanged when no prefix', () => {\n      expect(browserPrefixToKey('transform', '')).toBe('transform');\n    });\n\n    it('should prefix and capitalize for Webkit', () => {\n      expect(browserPrefixToKey('transform', 'Webkit')).toBe('WebkitTransform');\n    });\n\n    it('should prefix and capitalize for Moz', () => {\n      expect(browserPrefixToKey('transform', 'Moz')).toBe('MozTransform');\n    });\n\n    it('should handle kebab-case properties', () => {\n      expect(browserPrefixToKey('user-select', 'Webkit')).toBe('WebkitUserSelect');\n    });\n\n    it('should handle multi-hyphen properties', () => {\n      expect(browserPrefixToKey('border-top-left-radius', 'Webkit')).toBe('WebkitBorderTopLeftRadius');\n    });\n  });\n\n  describe('browserPrefixToStyle', () => {\n    it('should return prop unchanged when no prefix', () => {\n      expect(browserPrefixToStyle('transform', '')).toBe('transform');\n    });\n\n    it('should add CSS prefix for Webkit', () => {\n      expect(browserPrefixToStyle('transform', 'Webkit')).toBe('-webkit-transform');\n    });\n\n    it('should add CSS prefix for Moz', () => {\n      expect(browserPrefixToStyle('transform', 'Moz')).toBe('-moz-transform');\n    });\n\n    it('should add CSS prefix for ms', () => {\n      expect(browserPrefixToStyle('transform', 'ms')).toBe('-ms-transform');\n    });\n  });\n\n  describe('getPrefix', () => {\n    let originalWindow;\n\n    beforeEach(() => {\n      originalWindow = global.window;\n    });\n\n    afterEach(() => {\n      global.window = originalWindow;\n    });\n\n    it('should return empty string when window is undefined', () => {\n      global.window = undefined;\n      expect(getPrefix('transform')).toBe('');\n    });\n\n    it('should return empty string when documentElement.style is unavailable', () => {\n      global.window = { document: {} };\n      expect(getPrefix('transform')).toBe('');\n    });\n\n    it('should return empty string when prop exists unprefixed', () => {\n      global.window = {\n        document: {\n          documentElement: {\n            style: { transform: '' }\n          }\n        }\n      };\n      expect(getPrefix('transform')).toBe('');\n    });\n\n    it('should return Webkit prefix when WebkitTransform exists', () => {\n      global.window = {\n        document: {\n          documentElement: {\n            style: { WebkitTransform: '' }\n          }\n        }\n      };\n      expect(getPrefix('transform')).toBe('Webkit');\n    });\n\n    it('should return Moz prefix when MozTransform exists', () => {\n      global.window = {\n        document: {\n          documentElement: {\n            style: { MozTransform: '' }\n          }\n        }\n      };\n      expect(getPrefix('transform')).toBe('Moz');\n    });\n\n    it('should return empty string when no prefix matches', () => {\n      global.window = {\n        document: {\n          documentElement: {\n            style: {}\n          }\n        }\n      };\n      expect(getPrefix('someUnknownProperty')).toBe('');\n    });\n\n    it('should use transform as default prop', () => {\n      global.window = {\n        document: {\n          documentElement: {\n            style: { transform: '' }\n          }\n        }\n      };\n      expect(getPrefix()).toBe('');\n    });\n  });\n});\n"
  },
  {
    "path": "test/utils/positionFns.test.js",
    "content": "import { describe, it, expect } from 'vitest';\nimport { snapToGrid, canDragX, canDragY, createCoreData, createDraggableData } from '../../lib/utils/positionFns';\n\ndescribe('positionFns utilities', () => {\n  describe('snapToGrid', () => {\n    it('should snap to grid values', () => {\n      expect(snapToGrid([10, 10], 15, 15)).toEqual([20, 20]);\n      expect(snapToGrid([10, 10], 14, 14)).toEqual([10, 10]);\n    });\n\n    it('should handle different x and y grid values', () => {\n      expect(snapToGrid([5, 10], 12, 12)).toEqual([10, 10]);\n      expect(snapToGrid([5, 10], 13, 16)).toEqual([15, 20]);\n    });\n\n    it('should handle zero values', () => {\n      expect(snapToGrid([10, 10], 0, 0)).toEqual([0, 0]);\n    });\n\n    it('should handle negative values', () => {\n      // Math.round(-1.5) = -1 in JavaScript (rounds toward +∞)\n      expect(snapToGrid([10, 10], -15, -15)).toEqual([-10, -10]);\n      expect(snapToGrid([10, 10], -14, -14)).toEqual([-10, -10]);\n      expect(snapToGrid([10, 10], -16, -16)).toEqual([-20, -20]);\n    });\n\n    it('should snap exactly at grid boundaries', () => {\n      expect(snapToGrid([10, 10], 10, 10)).toEqual([10, 10]);\n      expect(snapToGrid([10, 10], 20, 20)).toEqual([20, 20]);\n    });\n\n    it('should round at midpoint (standard Math.round behavior)', () => {\n      // 5 is midpoint for grid of 10, rounds up\n      expect(snapToGrid([10, 10], 5, 5)).toEqual([10, 10]);\n      // 4 rounds down\n      expect(snapToGrid([10, 10], 4, 4)).toEqual([0, 0]);\n    });\n  });\n\n  describe('canDragX', () => {\n    it('should return true for axis \"both\"', () => {\n      const draggable = { props: { axis: 'both' } };\n      expect(canDragX(draggable)).toBe(true);\n    });\n\n    it('should return true for axis \"x\"', () => {\n      const draggable = { props: { axis: 'x' } };\n      expect(canDragX(draggable)).toBe(true);\n    });\n\n    it('should return false for axis \"y\"', () => {\n      const draggable = { props: { axis: 'y' } };\n      expect(canDragX(draggable)).toBe(false);\n    });\n\n    it('should return false for axis \"none\"', () => {\n      const draggable = { props: { axis: 'none' } };\n      expect(canDragX(draggable)).toBe(false);\n    });\n  });\n\n  describe('canDragY', () => {\n    it('should return true for axis \"both\"', () => {\n      const draggable = { props: { axis: 'both' } };\n      expect(canDragY(draggable)).toBe(true);\n    });\n\n    it('should return true for axis \"y\"', () => {\n      const draggable = { props: { axis: 'y' } };\n      expect(canDragY(draggable)).toBe(true);\n    });\n\n    it('should return false for axis \"x\"', () => {\n      const draggable = { props: { axis: 'x' } };\n      expect(canDragY(draggable)).toBe(false);\n    });\n\n    it('should return false for axis \"none\"', () => {\n      const draggable = { props: { axis: 'none' } };\n      expect(canDragY(draggable)).toBe(false);\n    });\n  });\n\n  describe('createCoreData', () => {\n    const mockNode = document.createElement('div');\n\n    it('should create initial data when lastX/lastY are NaN', () => {\n      const draggableCore = {\n        lastX: NaN,\n        lastY: NaN,\n        findDOMNode: () => mockNode\n      };\n      const data = createCoreData(draggableCore, 100, 200);\n      expect(data).toEqual({\n        node: mockNode,\n        deltaX: 0,\n        deltaY: 0,\n        lastX: 100,\n        lastY: 200,\n        x: 100,\n        y: 200\n      });\n    });\n\n    it('should calculate deltas when lastX/lastY are set', () => {\n      const draggableCore = {\n        lastX: 50,\n        lastY: 100,\n        findDOMNode: () => mockNode\n      };\n      const data = createCoreData(draggableCore, 150, 250);\n      expect(data).toEqual({\n        node: mockNode,\n        deltaX: 100,\n        deltaY: 150,\n        lastX: 50,\n        lastY: 100,\n        x: 150,\n        y: 250\n      });\n    });\n\n    it('should throw when node is unmounted', () => {\n      const draggableCore = {\n        lastX: NaN,\n        lastY: NaN,\n        findDOMNode: () => null\n      };\n      expect(() => createCoreData(draggableCore, 0, 0)).toThrow('Unmounted during event');\n    });\n  });\n\n  describe('createDraggableData', () => {\n    const mockNode = document.createElement('div');\n\n    it('should create data with scale of 1', () => {\n      const draggable = {\n        props: { scale: 1 },\n        state: { x: 100, y: 200 }\n      };\n      const coreData = {\n        node: mockNode,\n        deltaX: 10,\n        deltaY: 20,\n        lastX: 100,\n        lastY: 200,\n        x: 110,\n        y: 220\n      };\n      const data = createDraggableData(draggable, coreData);\n      expect(data).toEqual({\n        node: mockNode,\n        x: 110,\n        y: 220,\n        deltaX: 10,\n        deltaY: 20,\n        lastX: 100,\n        lastY: 200\n      });\n    });\n\n    it('should apply scale to delta values', () => {\n      const draggable = {\n        props: { scale: 2 },\n        state: { x: 100, y: 200 }\n      };\n      const coreData = {\n        node: mockNode,\n        deltaX: 20,\n        deltaY: 40,\n        lastX: 100,\n        lastY: 200,\n        x: 120,\n        y: 240\n      };\n      const data = createDraggableData(draggable, coreData);\n      expect(data).toEqual({\n        node: mockNode,\n        x: 110,  // 100 + (20/2)\n        y: 220,  // 200 + (40/2)\n        deltaX: 10,  // 20/2\n        deltaY: 20,  // 40/2\n        lastX: 100,\n        lastY: 200\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "test/utils/shims.test.js",
    "content": "import { describe, it, expect } from 'vitest';\nimport { findInArray, isFunction, isNum, int, dontSetMe } from '../../lib/utils/shims';\n\ndescribe('shims utilities', () => {\n  describe('findInArray', () => {\n    it('should find first element matching callback', () => {\n      const arr = [1, 2, 3, 4, 5];\n      const result = findInArray(arr, (x) => x > 3);\n      expect(result).toBe(4);\n    });\n\n    it('should return undefined if no match found', () => {\n      const arr = [1, 2, 3];\n      const result = findInArray(arr, (x) => x > 10);\n      expect(result).toBeUndefined();\n    });\n\n    it('should work with objects', () => {\n      const arr = [{ id: 1 }, { id: 2 }, { id: 3 }];\n      const result = findInArray(arr, (x) => x.id === 2);\n      expect(result).toEqual({ id: 2 });\n    });\n\n    it('should pass index and array to callback', () => {\n      const arr = ['a', 'b', 'c'];\n      const result = findInArray(arr, (val, idx, array) => idx === 1 && array.length === 3);\n      expect(result).toBe('b');\n    });\n  });\n\n  describe('isFunction', () => {\n    it('should return true for regular functions', () => {\n      expect(isFunction(function() {})).toBe(true);\n    });\n\n    it('should return true for arrow functions', () => {\n      expect(isFunction(() => {})).toBe(true);\n    });\n\n    it('should return true for async functions', () => {\n      expect(isFunction(async () => {})).toBe(true);\n    });\n\n    it('should return false for non-functions', () => {\n      expect(isFunction(null)).toBe(false);\n      expect(isFunction(undefined)).toBe(false);\n      expect(isFunction(42)).toBe(false);\n      expect(isFunction('string')).toBe(false);\n      expect(isFunction({})).toBe(false);\n      expect(isFunction([])).toBe(false);\n    });\n  });\n\n  describe('isNum', () => {\n    it('should return true for valid numbers', () => {\n      expect(isNum(0)).toBe(true);\n      expect(isNum(42)).toBe(true);\n      expect(isNum(-10)).toBe(true);\n      expect(isNum(3.14)).toBe(true);\n      expect(isNum(Infinity)).toBe(true);\n    });\n\n    it('should return false for NaN', () => {\n      expect(isNum(NaN)).toBe(false);\n    });\n\n    it('should return false for non-numbers', () => {\n      expect(isNum(null)).toBe(false);\n      expect(isNum(undefined)).toBe(false);\n      expect(isNum('42')).toBe(false);\n      expect(isNum({})).toBe(false);\n    });\n  });\n\n  describe('int', () => {\n    it('should parse integer strings', () => {\n      expect(int('42')).toBe(42);\n      expect(int('0')).toBe(0);\n      expect(int('-10')).toBe(-10);\n    });\n\n    it('should parse pixel values', () => {\n      expect(int('10px')).toBe(10);\n      expect(int('100em')).toBe(100);\n    });\n\n    it('should handle decimal strings by truncating', () => {\n      expect(int('3.14')).toBe(3);\n      expect(int('99.99')).toBe(99);\n    });\n  });\n\n  describe('dontSetMe', () => {\n    it('should return error when prop is set', () => {\n      const props = { className: 'foo' };\n      const result = dontSetMe(props, 'className', 'Draggable');\n      expect(result).toBeInstanceOf(Error);\n      expect(result.message).toContain('className');\n      expect(result.message).toContain('Draggable');\n    });\n\n    it('should return undefined when prop is not set', () => {\n      const props = {};\n      const result = dontSetMe(props, 'className', 'Draggable');\n      expect(result).toBeUndefined();\n    });\n\n    it('should return undefined for falsy values', () => {\n      expect(dontSetMe({ className: null }, 'className', 'Draggable')).toBeUndefined();\n      expect(dontSetMe({ className: '' }, 'className', 'Draggable')).toBeUndefined();\n      expect(dontSetMe({ className: 0 }, 'className', 'Draggable')).toBeUndefined();\n    });\n  });\n});\n"
  },
  {
    "path": "typings/index.d.ts",
    "content": "import * as React from 'react';\n\nexport interface DraggableBounds {\n  left?: number\n  right?: number\n  top?: number\n  bottom?: number\n}\n\nexport interface DraggableProps extends DraggableCoreProps {\n  axis: 'both' | 'x' | 'y' | 'none',\n  bounds: DraggableBounds | string | false ,\n  defaultClassName: string,\n  defaultClassNameDragging: string,\n  defaultClassNameDragged: string,\n  defaultPosition: ControlPosition,\n  positionOffset: PositionOffsetControlPosition,\n  position: ControlPosition\n}\n\nexport type DraggableEvent = React.MouseEvent<HTMLElement | SVGElement>\n  | React.TouchEvent<HTMLElement | SVGElement>\n  | MouseEvent\n  | TouchEvent\n\nexport type DraggableEventHandler = (\n  e: DraggableEvent,\n  data: DraggableData\n) => void | false;\n\nexport interface DraggableData {\n  node: HTMLElement,\n  x: number, y: number,\n  deltaX: number, deltaY: number,\n  lastX: number, lastY: number\n}\n\nexport type ControlPosition = {x: number, y: number};\n\nexport type PositionOffsetControlPosition = {x: number|string, y: number|string};\n\nexport interface DraggableCoreProps {\n  allowAnyClick: boolean,\n  allowMobileScroll: boolean,\n  cancel: string,\n  children?: React.ReactNode,\n  disabled: boolean,\n  enableUserSelectHack: boolean,\n  offsetParent: HTMLElement,\n  grid: [number, number],\n  handle: string,\n  nodeRef?: React.RefObject<HTMLElement | null>,\n  onStart: DraggableEventHandler,\n  onDrag: DraggableEventHandler,\n  onStop: DraggableEventHandler,\n  onMouseDown: (e: MouseEvent) => void,\n  scale: number\n}\n\nexport default class Draggable extends React.Component<Partial<DraggableProps>, {}> {\n  static defaultProps : DraggableProps;\n}\n\nexport class DraggableCore extends React.Component<Partial<DraggableCoreProps>, {}> {\n  static defaultProps : DraggableCoreProps;\n}\n"
  },
  {
    "path": "typings/test.tsx",
    "content": "import * as React from 'react';\nimport { createRoot } from 'react-dom/client';\nimport Draggable, {DraggableCore} from 'react-draggable';\n\nconst rootElement = document.getElementById('root')!;\nconst root = createRoot(rootElement);\n\nfunction handleStart() {}\nfunction handleDrag() {}\nfunction handleStop() {}\nfunction handleMouseDown() {}\n\nconst nodeRef = React.createRef<HTMLDivElement>();\nroot.render(\n  <Draggable\n    axis=\"y\"\n    handle=\".handle\"\n    cancel=\".cancel\"\n    grid={[10, 10]}\n    onStart={handleStart}\n    onDrag={handleDrag}\n    onStop={handleStop}\n    offsetParent={document.body}\n    onMouseDown={handleMouseDown}\n    allowAnyClick={true}\n    allowMobileScroll={false}\n    disabled={true}\n    enableUserSelectHack={false}\n    bounds={false}\n    defaultClassName={'draggable'}\n    defaultClassNameDragging={'dragging'}\n    defaultClassNameDragged={'dragged'}\n    defaultPosition={{x: 0, y: 0}}\n    nodeRef={nodeRef}\n    positionOffset={{x: 0, y: 0}}\n    position={{x: 50, y: 50}}>\n    <div className=\"foo bar\" ref={nodeRef}>\n      <div className=\"handle\"/>\n      <div className=\"cancel\"/>\n    </div>\n  </Draggable>\n);\n\nconst nodeRefCore = React.createRef<HTMLDivElement>();\nroot.render(\n  <DraggableCore\n    handle=\".handle\"\n    cancel=\".cancel\"\n    allowAnyClick={true}\n    disabled={true}\n    onMouseDown={handleMouseDown}\n    grid={[10, 10]}\n    nodeRef={nodeRefCore}\n    onStart={handleStart}\n    onDrag={handleDrag}\n    onStop={handleStop}\n    offsetParent={document.body}\n    enableUserSelectHack={false}\n    allowMobileScroll={false}>\n    <div className=\"foo bar\" ref={nodeRefCore}>\n      <div className=\"handle\"/>\n      <div className=\"cancel\"/>\n    </div>\n  </DraggableCore>\n);\n\nroot.render(<Draggable><div/></Draggable>);\n\nroot.render(<DraggableCore><div/></DraggableCore>);\n"
  },
  {
    "path": "typings/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"noEmit\": true,\n    \"jsx\": \"preserve\",\n    \"strict\": true,\n    \"moduleResolution\": \"node\",\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"react-draggable\": [\"./index.d.ts\"]\n    }\n  },\n  \"files\": [\n    \"index.d.ts\",\n    \"test.tsx\"\n  ]\n}\n"
  },
  {
    "path": "vitest.browser.config.js",
    "content": "import { defineConfig } from 'vitest/config';\n\nexport default defineConfig({\n  test: {\n    include: ['test/browser/**/*.test.js'],\n    testTimeout: 30000,\n    hookTimeout: 30000,\n  },\n});\n"
  },
  {
    "path": "vitest.config.js",
    "content": "import { defineConfig } from 'vitest/config';\nimport react from '@vitejs/plugin-react';\n\nexport default defineConfig({\n  plugins: [\n    react({\n      // Process all .js and .jsx files through Babel\n      include: [/\\.[jt]sx?$/],\n      babel: {\n        presets: ['@babel/preset-flow', '@babel/preset-react'],\n        plugins: ['@babel/plugin-transform-flow-comments'],\n      },\n    }),\n  ],\n  // Disable esbuild for JS files - let Babel handle them\n  esbuild: false,\n  test: {\n    environment: 'jsdom',\n    globals: true,\n    setupFiles: ['./test/setup.js'],\n    include: ['test/**/*.test.{js,jsx}'],\n    exclude: ['test/browser/**'],\n    // Suppress error stack traces from expected test failures\n    onConsoleLog: () => false,\n    coverage: {\n      provider: 'v8',\n      reporter: ['text', 'html', 'lcov'],\n      include: ['lib/**/*.js'],\n      thresholds: {\n        lines: 70,\n        functions: 70,\n        branches: 60,\n        statements: 70,\n      },\n    },\n  },\n});\n"
  },
  {
    "path": "webpack.config.js",
    "content": "const path = require('path');\nconst webpack = require('webpack');\n\n// Builds web module. Only really used in example code / static site.\nmodule.exports = (env, argv) => ({\n\tentry: {\n    'react-draggable.min': './lib/cjs.js',\n  },\n\toutput: {\n    filename: '[name].js',\n    sourceMapFilename: '[name].js.map',\n    devtoolModuleFilenameTemplate: '../[resource-path]',\n    library: 'ReactDraggable',\n    libraryTarget: 'umd',\n    path: path.resolve(__dirname, 'build', 'web'),\n\t},\n  devServer: {\n    hot: true,\n    open: 'example/index.html',\n    client: {\n      overlay: true,\n    },\n    devMiddleware: {\n      // disableHostCheck: true,\n      writeToDisk: true,\n    },\n    static: {\n      directory: '.',\n    }\n  },\n  devtool: 'source-map',\n  externals: {\n    'react': {\n      'commonjs': 'react',\n      'commonjs2': 'react',\n      'amd': 'react',\n      // React dep should be available as window.React, not window.react\n      'root': 'React'\n    },\n    'react-dom': {\n      'commonjs': 'react-dom',\n      'commonjs2': 'react-dom',\n      'amd': 'react-dom',\n      'root': 'ReactDOM'\n    }\n  },\n\tmodule: {\n\t\trules: [\n\t\t\t{\n        test: /\\.(?:js|es).?$/,\n        loader: 'babel-loader',\n        options: {\n          cacheDirectory: false, // intended, have had bugs with env like DRAGGABLE_DEBUG in the past\n        },\n        exclude: /(node_modules)/\n      }\n\t\t]\n\t},\n  plugins: [\n    new webpack.EnvironmentPlugin({\n      // these are default values\n      DRAGGABLE_DEBUG: false,\n      NODE_ENV: ['development', 'production'].includes(argv.mode) ? argv.mode : 'production'\n    }),\n    // Scope hoisting\n    new webpack.optimize.ModuleConcatenationPlugin(),\n  ],\n  optimization: {\n    minimize: false,\n  },\n  stats: {\n    errorDetails: true,\n  }\n});\n"
  }
]