[
  {
    "path": ".babelrc",
    "content": "{\n  \"presets\": [\n    \"@babel/preset-env\",\n    \"@babel/preset-react\",\n    \"@babel/preset-typescript\"\n  ],\n  \"plugins\": [\n    \"@babel/plugin-transform-runtime\",\n    [\n      \"add-module-exports\",\n      {\n        \"addDefaultProperty\": true\n      }\n    ]\n  ]\n}\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\nindent_style = space\nindent_size = 2\n"
  },
  {
    "path": ".eslintignore",
    "content": "/example/dist\n/dist\n*.js.flow\n"
  },
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n  env: {\n    browser: true,\n    jest: true,\n    node: true,\n    es6: true,\n  },\n  extends: [\n    'eslint:recommended',\n    'plugin:@typescript-eslint/eslint-recommended',\n    'plugin:@typescript-eslint/recommended',\n    'plugin:react/recommended',\n  ],\n  rules: {\n    'react/no-children-prop': ['off'],\n\n    '@typescript-eslint/no-non-null-assertion': ['off'],\n    '@typescript-eslint/no-explicit-any': ['off'],\n    '@typescript-eslint/explicit-function-return-type': ['off'],\n    '@typescript-eslint/no-unused-vars': [\n      'error',\n      { argsIgnorePattern: '[iI]gnored' },\n    ],\n    '@typescript-eslint/ban-types': ['off'],\n    '@typescript-eslint/explicit-module-boundary-types': ['off'],\n\n    indent: ['off'],\n    'react/jsx-indent': ['error', 2],\n    'react/jsx-indent-props': ['error', 2],\n    'linebreak-style': ['error', 'unix'],\n    quotes: ['off'],\n    semi: ['error', 'always'],\n    'no-var': ['error'],\n    'brace-style': ['error'],\n    'array-bracket-spacing': ['error', 'never'],\n    'block-spacing': ['error', 'always'],\n    'no-spaced-func': ['error'],\n    'no-whitespace-before-property': ['error'],\n    'space-before-blocks': ['error', 'always'],\n    'keyword-spacing': ['error'],\n\n    // We use Typescript for this\n    'react/prop-types': ['off'],\n  },\n  settings: {\n    react: {\n      version: '16.6',\n    },\n  },\n};\n"
  },
  {
    "path": ".flowconfig",
    "content": "[ignore]\n.*/node_modules/.*/\\(test\\|lib\\|example\\|samplejson\\)/.*\\.json\n<PROJECT_ROOT>/dist/\n\n[include]\n\n[libs]\n\n[options]\n"
  },
  {
    "path": ".gitattributes",
    "content": "* text=auto eol=lf\n"
  },
  {
    "path": ".github/workflows/node.js.yml",
    "content": "name: Node.js CI\n\non: [push]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 20\n          cache: 'npm'\n      - run: npm ci --force\n      - run: npm test\n"
  },
  {
    "path": ".gitignore",
    "content": "*~\n.DS_Store\n/node_modules\nnpm-debug.log\n/dist\n/example/dist\n/.cache\n"
  },
  {
    "path": ".prettierignore",
    "content": "/example/dist\n/dist\n.cache\n"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"singleQuote\": true\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"javascript.validate.enable\": false,\n  \"flow.useNPMPackagedFlow\": true,\n  \"eslint.validate\": [\n    \"javascript\",\n    \"javascriptreact\",\n    \"typescript\",\n    \"typescriptreact\"\n  ]\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "## 4.2.1 (2024-05-23)\n\n- Reupload package to work around minor file metadata issue caused by Yarn (https://github.com/yarnpkg/yarn/issues/8109). This version contains no other changes.\n\n## 4.2.0 (2024-02-19)\n\n- Added `draggedItem` parameter to the `onDragStart` and `onDragEnd` callback props.\n\n## 4.1.0 (2022-08-12)\n\n- Implemented `onDragStart` and `onDragEnd` props. ([@JayHales](https://github.com/JayHales) in [#52](https://github.com/StreakYC/react-draggable-list/pull/52))\n\n## 4.0.4 (2021-09-22)\n\n- Updated peerDependencies list to mark compatibility with React 17.\n\n## 4.0.3 (2019-06-05)\n\n- Updated to use immutability-helper 3.0.\n\n## 4.0.2 (2019-04-05)\n\n- Fixed issue where the DraggableList could have an incorrect height during drag depending on the styling of its parents.\n\n## 4.0.0 (2018-11-28)\n\n### Breaking Changes\n\n- React v16.6+ is now required.\n- The `dragHandle` function prop was removed. Now the Template component is instead given a prop `dragHandleProps` which is an object that must be spread as props on the HTML element to be used as the drag handle.\n\nReactDraggableList v3:\n\n```js\n<div>\n  {this.props.dragHandle(<div>drag me</div>)}\n  <div>content</div>\n</div>\n```\n\nReactDraggableList v4:\n\n```js\n<div>\n  <div {...this.props.dragHandleProps}>drag me</div>\n  <div>content</div>\n</div>\n```\n\n### Improvements\n\n- No longer uses any deprecated APIs (lifecycle methods and ReactDOM.findDOMNode).\n- Fixed bug where the `oldIndex` parameter passed to `onMoveEnd` was incorrect if the `list` prop was updated while the user was dragging an item.\n\n## 3.7.0 (2018-11-05)\n\n- Added TypeScript type definitions.\n- Changed Flow type definitions to use `$ReadOnlyArray` where applicable. Users may need to change the type annotations on the function they give to the `onMoveEnd` prop to keep Flow's type-check passing.\n\n## 3.6.0 (2018-08-24)\n\n- Added `constrainDrag` prop. [#30](https://github.com/StreakYC/react-draggable-list/pull/30)\n\n## 3.5.3 (2018-05-15)\n\n- Updated for compatibility with Flow v0.72.\n\n## 3.5.2 (2018-04-13)\n\n- Improved Flow type definitions to cover the proper return type of `getItemInstance`.\n\n## 3.5.1 (2018-04-13)\n\n- Fixed accidental usage of `event` global variable. This didn't cause any user-visible bugs to my knowledge.\n\n## 3.5.0 (2018-04-13)\n\n- Flow types for DraggableList now include a type parameter representing the list item's type, enabling fuller type-checking coverage.\n\n## 3.4.1 (2017-10-02)\n\n- Updated package.json to mark compatibility with React v16.\n- Internal: tests now use React v16.\n\n## 3.4.0 (2017-09-11)\n\n- Updated for compatibility with Flow v0.54.1. This made it incompatible with older versions of Flow, so I'm making this update be a semver-minor change so users still on an older Flow version can pin to the previous minor version. Because of its frequent changes, I'm not considering incompatibilities with old Flow versions as semver-major breaking changes.\n\n## 3.3.1 (2017-07-07)\n\n- Updated for compatibility with Flow v0.49.1.\n\n## 3.3.0 (2017-04-25)\n\n- Stop using the newly deprecated `React.PropTypes` and now use the separate prop-types module.\n\n## 3.2.1 (2017-03-24)\n\n- Fixed issue where components didn't re-render when the value of the `commonProps` prop changed. [#20](https://github.com/StreakYC/react-draggable-list/pull/20)\n\n## 3.2.0 (2017-03-06)\n\n- Added `autoScrollMaxSpeed` and `autoScrollRegionSize` props to DraggableList. [#15](https://github.com/StreakYC/react-draggable-list/pull/15)\n- Added `commonProps` prop to DraggableList. [#18](https://github.com/StreakYC/react-draggable-list/pull/18)\n\n## 3.1.4 (2017-03-06)\n\n- Updated for compatibility with Flow v0.41.\n\n## 3.1.3 (2017-01-24)\n\n- Updated for compatibility with Flow v0.38.\n\n## 3.1.2 (2016-10-26)\n\n- Fixed issue with the dragged item itself being removed during the post-drag animation.\n\n## 3.1.1 (2016-10-25)\n\n- Fixed handling of items being removed from list during the post-drag animation.\n\n## 3.1.0 (2016-10-25)\n\n- Added `getItemInstance` method.\n\n## 3.0.4 (2016-09-26)\n\n- Fixed Flow type-checking issue when used with newer version of react-motion.\n\n## 3.0.3 (2016-09-13)\n\n- Updated for compatibility with Flow v0.32.\n\n## 3.0.2 (2016-08-05)\n\n- Updated for compatibility with Flow v0.30.\n\n## 3.0.1 (2016-06-27)\n\n- Fixed handling of props being changed while the user is dragging an item.\n- Fixed issue where DraggableList could prevent elements from having a natural layout applied after being dragged when the container prop was not used.\n\n## 3.0.0 (2016-04-07)\n\n### Breaking Changes\n\n- React v15 is now required.\n\n## 2.1.0 (2016-03-02)\n\n- Added `unsetZIndex` prop.\n\n## 2.0.0 (2016-02-29)\n\n### Breaking Changes\n\n- If the `getDragHeight` method isn't present on the template component, the drag height now defaults to the element's natural height instead of the arbitrary height of 30px.\n\n## 1.0.6 (2016-02-29)\n\n- Fixed the DraggableList element changing height when the last item is grabbed.\n\n## 1.0.5 (2016-02-25)\n\n- Re-use dragHandle prop value given to component to reduce amount of re-renders.\n\n## 1.0.3 (2016-02-25)\n\n- Fixed a scroll animation glitch when DraggableList was not in a scrollable container.\n\n## 1.0.2 (2016-02-25)\n\n- Fixed an accuracy issue with the scroll animation on drop.\n\n## 1.0.1 (2016-02-25)\n\n- Fixed animation glitch if you pick up an item while it's still animating.\n- Efficiency improvements: minimize amount of re-renders needed during dragging.\n\n## 1.0.0 (2016-02-24)\n\nInitial stable release.\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2018 Rewardly, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# react-draggable-list\n\n[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/StreakYC/react-draggable-list/blob/master/LICENSE.txt) [![npm version](https://img.shields.io/npm/v/react-draggable-list.svg?style=flat)](https://www.npmjs.com/package/react-draggable-list) [![Node.js CI](https://github.com/StreakYC/react-draggable-list/actions/workflows/node.js.yml/badge.svg)](https://github.com/StreakYC/react-draggable-list/actions/workflows/node.js.yml)\n\nThis component lets you make a user re-orderable list that animates nicely so\nthat the user can easily move large items:\n\n![Example](https://streakyc.github.io/react-draggable-list/video/dragitem.gif)\n\nThe above example can be tried here:\n\nhttps://streakyc.github.io/react-draggable-list/example/\n\nYou can find its code in the `example` directory. The example may be compiled\nby running:\n\n```\nnpm i --force\nnpm run example-build\n# or use this to auto-rebuild on changes:\nnpm run example-watch\n```\n\n## DraggableList\n\nThis module exports the `DraggableList` React component, which takes the\nfollowing props:\n\n- `list` must be an array of objects representing your list's items.\n- `itemKey` must be the name of a property of the list's objects to use as a\n  key to identify the objects, or it must be a function that takes an object as\n  an argument and returns a key.\n- `template` must be a React component used to render the list items. This must\n  not be a stateless-functional component. If possible, don't pass a new\n  class instance on every render. See the next section for more information\n  on the template including a description of the props passed to the component.\n- `onMoveEnd` may be a function which will be called when the user drags and\n  drops an item to a new position in the list. The arguments to the function\n  will be `(newList: Array<Object>, movedItem: Object, oldIndex: number, newIndex: number)`. A component using DraggableList should immediately store\n  the newList into its state and then pass the new list (or an equivalent list)\n  as the `list` prop to DraggableList.\n- `container`: If the DraggableList is inside a scrollable element, then this\n  property should be set to a function which returns a reference to it. When the\n  user moves an item in the list, the container will be scrolled to keep the\n  item in view. If the DraggableList is in no scrollable elements besides the\n  page itself, then a function returning a reference to `document.body` should\n  be given.\n- `springConfig` is an optional object which sets the [SpringHelperConfig\n  object passed to\n  React-Motion](https://github.com/chenglou/react-motion/tree/85ca75c6de9ed85937d1c95646b6044a66981eee#--spring-val-number-config-springhelperconfig--opaqueconfig)\n  for animations. This prop defaults to `{stiffness: 300, damping: 50}`.\n- `padding` is an optional number of pixels to leave between items. Defaults to 10.\n- `unsetZIndex` is an optional property that defaults to false. If set to true,\n  then the z-index of all of the list items will be set to \"auto\" when the list\n  isn't animating. This may have a small performance cost when the list starts\n  and stops animating. Use this if you need to avoid having the list item create\n  a stacking context when it's not being animated.\n- `constrainDrag` is an option property that defaults to false. If it is set to\n  true, then the y-coordinate of a dragged item will be constrained vertically to\n  the bounds of the list.\n- `autoScrollMaxSpeed` is an optional number that allows the scroll speed when\n  the user drags to the top or bottom of the list to be overridden.\n- `autoScrollRegionSize` is an optional number that allows the height of the\n  region that triggers auto-scrolling when dragged onto to be overridden.\n- `commonProps` is an optional value that will be passed as the `commonProps`\n  prop to every template component instance.\n- `onDragStart` is an optional function which is called once a list item starts\n  being dragged. Receives the dragged item as an argument.\n- `onDragEnd` is an optional function which is called once a list item is no longer being dragged. Receives the dragged item as an argument. It differs from `onMoveEnd` in that it's called even if the user does not reorder any items in the lists, like when an item is just picked up and then dropped.\n\nA DraggableList instance has the following methods:\n\n- `getItemInstance(key)` will return a reference to the mounted instance of the\n  template for a given key.\n\n## Template\n\nThe template component is passed the following props:\n\n- `item` is an object from the list prop passed to DraggableList.\n- `itemSelected` is a number from 0 to 1. It starts at 0, and quickly increases\n  to 1 when the item is picked up by the user. This may be used to animate the\n  item when the user picks it up or drops it.\n- `anySelected` is a number from 0 to 1. It starts at 0, and quickly increases\n  to 1 when any item is picked up by the user.\n- `dragHandleProps` is an object which should be spread as props on the HTML\n  element to be used as the drag handle. The whole item will be draggable by the\n  wrapped element. See the\n  [example](https://github.com/StreakYC/react-draggable-list/blob/master/example/Example.tsx)\n  to see how it should be used.\n- `commonProps` will be set to the same value passed as the `commonProps` prop\n  to the DraggableList component.\n\nThe template component should be styled with max-height set to \"100%\" for best\nresults.\n\nThe template component will have its props updated many times quickly during\nthe animation, so implementing `shouldComponentUpdate` in its children is\nhighly recommended.\n\nThe template component may have a `getDragHeight` method which may return a\nnumber to set the height in pixels of the item while the user is dragging it.\nIf the method returns null or is not present, then the drag height will be\nequal to the element's natural height.\n\n## Bundling Note\n\nTo use this module in browsers, a CommonJS bundler such as Parcel, Browserify, or\nWebpack should be used.\n\nThis project relies on the javascript Map object being available globally. A\nglobal polyfill such as [Babel's polyfill](https://babeljs.io/docs/usage/polyfill/)\nis required to support [older browsers that don't implement these](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map#Browser_compatibility).\n\n## Types\n\nBoth [TypeScript](https://www.typescriptlang.org/) and\n[Flow](https://flowtype.org/) type definitions for this module are included!\nThe type definitions won't require any configuration to use.\n"
  },
  {
    "path": "example/Example.tsx",
    "content": "/* eslint-disable no-console */\n\nimport * as React from 'react';\nimport cx from 'classnames';\nimport DraggableList from '../src';\n\ninterface PlanetListItem {\n  name: string;\n  subtitle?: boolean;\n}\n\ninterface PlanetProps {\n  item: PlanetListItem;\n  itemSelected: number;\n  dragHandleProps: object;\n}\ninterface PlanetState {\n  value: number;\n}\nclass PlanetItem extends React.Component<PlanetProps, PlanetState> {\n  state = {\n    value: 0,\n  };\n\n  _inc() {\n    this.setState({\n      value: this.state.value + 1,\n    });\n  }\n\n  getDragHeight() {\n    return this.props.item.subtitle ? 47 : 28;\n  }\n\n  render() {\n    const { item, itemSelected, dragHandleProps } = this.props;\n    const { value } = this.state;\n    const scale = itemSelected * 0.05 + 1;\n    const shadow = itemSelected * 15 + 1;\n    const dragged = itemSelected !== 0;\n\n    return (\n      <div\n        className={cx('item', { dragged })}\n        style={{\n          transform: `scale(${scale})`,\n          boxShadow: `rgba(0, 0, 0, 0.3) 0px ${shadow}px ${2 * shadow}px 0px`,\n        }}\n      >\n        <div className=\"dragHandle\" {...dragHandleProps} />\n        <h2>{item.name}</h2>\n        {item.subtitle && (\n          <div className=\"subtitle\">\n            This item has a subtitle visible while dragging\n          </div>\n        )}\n        <div>\n          some description here\n          <br />\n          this planet orbits the sun\n          <br />\n          this planet is mostly round\n        </div>\n        {item.subtitle && (\n          <div>\n            subtitled planets are better\n            <br />\n            and have longer descriptions\n          </div>\n        )}\n        <div>\n          State works and is retained during movement:{' '}\n          <input type=\"button\" value={value} onClick={() => this._inc()} />\n        </div>\n      </div>\n    );\n  }\n}\n\ntype ExampleState = {\n  useContainer: boolean;\n  list: ReadonlyArray<PlanetListItem>;\n};\nexport default class Example extends React.Component<{}, ExampleState> {\n  private _container = React.createRef<HTMLDivElement>();\n\n  state = {\n    useContainer: false,\n    list: [\n      { name: 'Mercury' },\n      { name: 'Venus' },\n      { name: 'Earth', subtitle: true },\n      { name: 'Mars' },\n      { name: 'Jupiter' },\n      { name: 'Saturn', subtitle: true },\n      { name: 'Uranus', subtitle: true },\n      { name: 'Neptune' },\n    ],\n  };\n\n  private _togglePluto() {\n    const noPluto = this.state.list.filter((item) => item.name !== 'Pluto');\n    if (noPluto.length !== this.state.list.length) {\n      this.setState({ list: noPluto });\n    } else {\n      this.setState({ list: this.state.list.concat([{ name: 'Pluto' }]) });\n    }\n  }\n\n  private _toggleContainer() {\n    this.setState({ useContainer: !this.state.useContainer });\n  }\n\n  private _onListChange(newList: ReadonlyArray<PlanetListItem>) {\n    this.setState({ list: newList });\n  }\n\n  render() {\n    const { useContainer } = this.state;\n\n    return (\n      <div className=\"main\">\n        <div className=\"intro\">\n          <p>\n            This is a demonstration of the{' '}\n            <a href=\"https://github.com/StreakYC/react-draggable-list\">\n              react-draggable-list\n            </a>{' '}\n            library.\n          </p>\n          <p>\n            Each item has a drag handle visible when the user hovers over them.\n            The items may have any height, and can each define their own height\n            to use while being dragged.\n          </p>\n          <p>\n            When the list is reordered, the page will be scrolled if possible to\n            keep the moved item visible and on the same part of the screen.\n          </p>\n          <div>\n            <input\n              type=\"button\"\n              value=\"Toggle Pluto\"\n              onClick={() => this._togglePluto()}\n            />\n            <input\n              type=\"button\"\n              value=\"Toggle Container\"\n              onClick={() => this._toggleContainer()}\n            />\n          </div>\n        </div>\n        <div\n          className=\"list\"\n          ref={this._container}\n          style={{\n            overflow: useContainer ? 'auto' : '',\n            height: useContainer ? '200px' : '',\n            border: useContainer ? '1px solid gray' : '',\n          }}\n        >\n          <DraggableList<PlanetListItem, void, PlanetItem>\n            itemKey=\"name\"\n            template={PlanetItem}\n            list={this.state.list}\n            onMoveEnd={(newList) => this._onListChange(newList)}\n            container={() =>\n              useContainer ? this._container.current! : document.body\n            }\n          />\n        </div>\n        <footer>Footer here.</footer>\n      </div>\n    );\n  }\n}\n"
  },
  {
    "path": "example/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta\n      http-equiv=\"Content-Security-Policy\"\n      content=\"script-src 'self' 'unsafe-eval'\"\n    />\n    <title>React Draggable List Test Page</title>\n    <script src=\"dist/main.js\"></script>\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"main.css\" />\n  </head>\n  <body>\n    <div id=\"main\"></div>\n  </body>\n</html>\n"
  },
  {
    "path": "example/main.css",
    "content": ".intro,\nfooter {\n  width: 400px;\n  margin: auto;\n}\n\n.list {\n  width: 430px;\n  margin: 10px auto;\n  padding: 15px;\n}\n.item {\n  border: 1px solid black;\n  overflow: hidden;\n  transform-origin: 30% 50% 0px;\n  padding-left: 20px;\n  background: white;\n  max-height: 100%;\n}\n.item .dragHandle {\n  visibility: hidden;\n  position: absolute;\n  top: 0;\n  left: 0;\n  cursor: move;\n  width: 20px;\n  height: 16px;\n  background: url('grippy.png') 30% 50% no-repeat no-repeat;\n}\n.item:hover .dragHandle,\n.item.dragged .dragHandle {\n  visibility: visible;\n}\n.item h2 {\n  margin: 0;\n}\n.item .subtitle {\n  font-weight: bold;\n}\n\n::-webkit-scrollbar {\n  -webkit-appearance: none;\n  width: 7px;\n}\n::-webkit-scrollbar-thumb {\n  border-radius: 4px;\n  background-color: rgba(0, 0, 0, 0.5);\n  -webkit-box-shadow: 0 0 1px rgba(255, 255, 255, 0.5);\n}\n"
  },
  {
    "path": "example/main.tsx",
    "content": "import * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport Example from './Example';\n\nconst onReady = new Promise((resolve) => {\n  if (document.readyState === 'complete') {\n    resolve(undefined);\n  } else {\n    document.addEventListener('DOMContentLoaded', resolve, false);\n    window.addEventListener('load', resolve, false);\n  }\n});\n\nfunction main() {\n  const mainDiv = document.getElementById('main');\n  if (!mainDiv) throw new Error();\n  ReactDOM.render(<Example />, mainDiv);\n}\n\nonReady.then(main).catch((e) => {\n  console.error(e, e.stack); // eslint-disable-line no-console\n});\n"
  },
  {
    "path": "index.js.flow",
    "content": "/* @flow */\n\nimport * as React from 'react';\n\nexport type TemplateProps<I, C> = {\n  item: I,\n  itemSelected: number,\n  anySelected: number,\n  dragHandleProps: Object,\n  commonProps: C,\n};\n\nexport type Props<I, C, T> = {\n  itemKey: string | ((item: I) => string),\n  template: Class<T>,\n  list: $ReadOnlyArray<I>,\n  onMoveEnd?: ?(\n    newList: $ReadOnlyArray<I>,\n    movedItem: I,\n    oldIndex: number,\n    newIndex: number\n  ) => void,\n  container?: ?() => ?HTMLElement,\n  constrainDrag: boolean,\n  springConfig: Object,\n  padding: number,\n  unsetZIndex: boolean,\n  autoScrollMaxSpeed: number,\n  autoScrollRegionSize: number,\n  commonProps?: C,\n};\n\ndeclare export default class DraggableList<\n    I,\n    C = *,\n    T: React.Component<$Shape<TemplateProps<I, C>>, *> = *\n  >\n  extends React.Component<Props<I, C, T>>\n{\n  getItemInstance(key: string): T;\n}\n"
  },
  {
    "path": "jest.config.js",
    "content": "module.exports = {\n  testEnvironment: 'jsdom',\n  modulePathIgnorePatterns: ['<rootDir>/dist/']\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"react-draggable-list\",\n  \"version\": \"4.2.1\",\n  \"description\": \"Make a list of draggable collapsible items.\",\n  \"main\": \"dist/src/index.js\",\n  \"sideEffects\": false,\n  \"scripts\": {\n    \"prepare\": \"rimraf dist && babel -s true -d dist/src/ src/ -x .ts,.tsx --ignore '**/*.test.tsx' && tsc\",\n    \"example-build\": \"esbuild example/main.tsx --bundle --outdir=example/dist --public-path=.\",\n    \"example-watch\": \"npm example-build --watch\",\n    \"test\": \"npm run lint && flow check && jest && tsc --noEmit\",\n    \"lint\": \"eslint . --ext js,jsx,ts,tsx\",\n    \"lint-fix\": \"eslint . --ext js,jsx,ts,tsx --fix\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/StreakYC/react-draggable-list.git\"\n  },\n  \"keywords\": [\n    \"react\",\n    \"react-component\",\n    \"animation\",\n    \"reorder\",\n    \"move\",\n    \"drag-and-drop\",\n    \"draggable\"\n  ],\n  \"files\": [\n    \"dist\",\n    \"index.js.flow\"\n  ],\n  \"author\": \"Chris Cowan <agentme49@gmail.com>\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/StreakYC/react-draggable-list/issues\"\n  },\n  \"homepage\": \"https://github.com/StreakYC/react-draggable-list#readme\",\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.0.0\",\n    \"@babel/core\": \"^7.0.0\",\n    \"@babel/plugin-transform-runtime\": \"^7.0.0\",\n    \"@babel/preset-env\": \"^7.0.0\",\n    \"@babel/preset-react\": \"^7.0.0\",\n    \"@babel/preset-typescript\": \"^7.6.0\",\n    \"@types/classnames\": \"^2.2.9\",\n    \"@types/jest\": \"^29.5.12\",\n    \"@types/react\": \"^17.0.24\",\n    \"@types/react-dom\": \"^17.0.9\",\n    \"@types/react-motion\": \"^0.0.33\",\n    \"@typescript-eslint/eslint-plugin\": \"^7.0.2\",\n    \"@typescript-eslint/parser\": \"^7.0.2\",\n    \"babel-jest\": \"^29.7.0\",\n    \"babel-plugin-add-module-exports\": \"^1.0.0\",\n    \"classnames\": \"^2.2.3\",\n    \"esbuild\": \"^0.19.10\",\n    \"eslint\": \"^8.21.0\",\n    \"eslint-plugin-react\": \"^7.4.0\",\n    \"flow-bin\": \"^0.184.0\",\n    \"jest\": \"^29.7.0\",\n    \"jest-environment-jsdom\": \"^29.7.0\",\n    \"pdelay\": \"^2.0.0\",\n    \"prettier\": \"~2.4.1\",\n    \"react\": \"^17.0.2\",\n    \"react-dom\": \"^17.0.2\",\n    \"rimraf\": \"^5.0.5\",\n    \"typescript\": \"^5.8.3\"\n  },\n  \"resolutions\": {\n    \"@types/react\": \"^17.0.0\",\n    \"@types/react-dom\": \"^17.0.0\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.0.0\",\n    \"@types/prop-types\": \"^15.7.3\",\n    \"immutability-helper\": \"^3.0.0\",\n    \"prop-types\": \"^15.6.0\",\n    \"react-motion\": \"^0.5.2\",\n    \"react-multi-ref\": \"^1.0.0\"\n  },\n  \"peerDependencies\": {\n    \"react\": \"^16.6.0 || ^17.0.0\"\n  }\n}\n"
  },
  {
    "path": "src/MoveContainer.tsx",
    "content": "import * as React from 'react';\nimport TemplateContainer from './TemplateContainer';\n\nexport interface HeightData {\n  natural: number;\n  drag: number;\n}\n\ninterface Props<I, C, T> {\n  item: I;\n  template: new (props: any, context?: any) => T;\n  padding: number;\n  y: number | undefined;\n  itemSelected: number;\n  anySelected: number;\n  height: HeightData;\n  zIndex: React.CSSProperties['zIndex'];\n  makeDragHandleProps: (getY: () => number | undefined) => object;\n  commonProps: C;\n}\n\nexport default class MoveContainer<\n  I,\n  C,\n  T extends React.Component<any, any>\n> extends React.Component<Props<I, C, T>> {\n  private readonly _templateContainer =\n    React.createRef<TemplateContainer<I, C, T>>();\n  private readonly _el = React.createRef<HTMLDivElement>();\n\n  getDOMNode(): HTMLElement {\n    return this._el.current!;\n  }\n\n  getTemplate(): T {\n    return this._templateContainer.current!.getTemplate();\n  }\n\n  shouldComponentUpdate(nextProps: Props<I, C, T>): boolean {\n    return (\n      this.props.anySelected !== nextProps.anySelected ||\n      this.props.itemSelected !== nextProps.itemSelected ||\n      this.props.item !== nextProps.item ||\n      this.props.template !== nextProps.template ||\n      this.props.y !== nextProps.y ||\n      this.props.height !== nextProps.height ||\n      this.props.zIndex !== nextProps.zIndex ||\n      this.props.commonProps !== nextProps.commonProps\n    );\n  }\n\n  private _dragHandleProps = this.props.makeDragHandleProps(() => this.props.y);\n\n  render() {\n    const {\n      item,\n      y,\n      padding,\n      itemSelected,\n      anySelected,\n      height,\n      zIndex,\n      template,\n      commonProps,\n    } = this.props;\n\n    return (\n      <div\n        ref={this._el}\n        style={{\n          position: y == null ? ('relative' as const) : ('absolute' as const),\n          boxSizing: 'border-box' as const,\n          left: '0px',\n          right: '0px',\n          top: y == null ? '0px' : `${y}px`,\n          marginBottom: `${padding}px`,\n          height:\n            y == null\n              ? 'auto'\n              : `${\n                  anySelected * (height.drag - height.natural) + height.natural\n                }px`,\n          zIndex,\n        }}\n      >\n        <TemplateContainer\n          ref={this._templateContainer}\n          item={item}\n          template={template}\n          itemSelected={itemSelected}\n          anySelected={anySelected}\n          dragHandleProps={this._dragHandleProps}\n          commonProps={commonProps}\n        />\n      </div>\n    );\n  }\n}\n"
  },
  {
    "path": "src/OnUpdate.tsx",
    "content": "import * as React from 'react';\n\ninterface Props {\n  cb: () => void;\n}\n\nexport default class OnUpdate extends React.Component<Props> {\n  public componentDidUpdate() {\n    this.props.cb();\n  }\n\n  public render() {\n    return null;\n  }\n}\n"
  },
  {
    "path": "src/TemplateContainer.tsx",
    "content": "import * as React from 'react';\n\ninterface Props<I, C, T> {\n  item: I;\n  template: new (props: any, context?: any) => T;\n  itemSelected: number;\n  anySelected: number;\n  dragHandleProps: object;\n  commonProps: C;\n}\n\nexport default class TemplateContainer<\n  I,\n  C,\n  T extends React.Component<any, any>\n> extends React.Component<Props<I, C, T>> {\n  private _template: T | undefined;\n  private readonly _templateSetter = (cmp: any) => {\n    this._template = cmp;\n  };\n\n  public shouldComponentUpdate(nextProps: Props<I, C, T>): boolean {\n    return (\n      this.props.anySelected !== nextProps.anySelected ||\n      this.props.itemSelected !== nextProps.itemSelected ||\n      this.props.item !== nextProps.item ||\n      this.props.template !== nextProps.template ||\n      this.props.commonProps !== nextProps.commonProps\n    );\n  }\n\n  public getTemplate(): T {\n    return this._template!;\n  }\n\n  public render() {\n    const { item, itemSelected, anySelected, dragHandleProps, commonProps } =\n      this.props;\n    const Template = this.props.template;\n\n    return (\n      <Template\n        ref={this._templateSetter}\n        item={item}\n        itemSelected={itemSelected}\n        anySelected={anySelected}\n        dragHandleProps={dragHandleProps}\n        commonProps={commonProps}\n      />\n    );\n  }\n}\n"
  },
  {
    "path": "src/index.test.tsx",
    "content": "/**\n * @jest-environment jsdom\n */\n/* eslint-disable @typescript-eslint/no-use-before-define, @typescript-eslint/no-empty-function */\n\nimport delay from 'pdelay';\nimport * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport * as TestUtils from 'react-dom/test-utils';\nimport DraggableList from '../src';\n\ninterface Item {\n  name: string;\n  extra?: number;\n}\n\ninterface TestTemplateProps {\n  item: Item;\n  dragHandleProps: any;\n  commonProps: any;\n}\n\nclass TestTemplate extends React.Component<TestTemplateProps> {\n  private readonly _elRef = React.createRef<HTMLDivElement>();\n\n  render() {\n    const { item, dragHandleProps } = this.props;\n    return (\n      <div ref={this._elRef} className=\"item\" {...dragHandleProps}>\n        {item.name}\n      </div>\n    );\n  }\n\n  getName() {\n    return this.props.item.name;\n  }\n\n  getDragHeight() {\n    return 30;\n  }\n\n  getDOMNode(): HTMLDivElement {\n    return this._elRef.current!;\n  }\n\n  shouldComponentUpdate(nextProps: TestTemplateProps) {\n    return this.props.item !== nextProps.item;\n  }\n\n  componentDidMount() {\n    const el = this._elRef.current!;\n    Object.defineProperty(el, 'offsetHeight', {\n      get: () => 115,\n    });\n  }\n}\n\nconst springConfig = { stiffness: 1500, damping: 50 };\n\ntest('drag works', async () => {\n  let _scrollTop = 0;\n  const containerEl: any = {\n    get scrollTop() {\n      return _scrollTop;\n    },\n    set scrollTop(x) {\n      _scrollTop = x;\n    },\n  };\n\n  let list: Item[] = [\n    { name: 'caboose' },\n    { name: 'tucker' },\n    { name: 'church' },\n    { name: 'simmons' },\n    { name: 'sarge' },\n    { name: 'grif' },\n    { name: 'donut' },\n  ];\n  const commonProps = { a: 'foo' };\n\n  const div = document.createElement('div');\n\n  const onMoveEnd = jest.fn((newList) => {\n    list = newList;\n    render();\n  });\n\n  const rootRef = React.createRef<DraggableList<Item, any, TestTemplate>>();\n  function render() {\n    ReactDOM.render(\n      <DraggableList\n        ref={rootRef}\n        itemKey=\"name\"\n        list={list}\n        template={TestTemplate}\n        onMoveEnd={onMoveEnd}\n        springConfig={springConfig}\n        container={() => containerEl}\n        commonProps={commonProps}\n      />,\n      div\n    );\n  }\n  render();\n  const root = rootRef.current!;\n\n  expect(\n    TestUtils.scryRenderedComponentsWithType(root, TestTemplate).map(\n      (e) => e.props.item\n    )\n  ).toEqual(list);\n\n  expect(root.getItemInstance('grif').getName()).toBe('grif');\n  expect(root.getItemInstance('grif').getDragHeight()).toBe(30);\n  expect(root.getItemInstance('grif').props.commonProps).toBe(commonProps);\n\n  const renderedHandles: Array<TestTemplate> =\n    TestUtils.scryRenderedComponentsWithType(root, TestTemplate) as any;\n  expect(root.state.dragging).toBe(false);\n  renderedHandles[0].props.dragHandleProps.onMouseDown({\n    pageY: 500,\n    preventDefault() {},\n  });\n  expect(root.state.dragging).toBe(true);\n\n  (root as any)._handleMouseMove({ pageY: 600 });\n  await delay(30);\n\n  (root as any)._handleMouseMove({ pageY: 650 });\n\n  expect(root.state.dragging).toBe(true);\n  expect(onMoveEnd).toHaveBeenCalledTimes(0);\n  (root as any)._handleMouseUp();\n  expect(root.state.dragging).toBe(false);\n  expect(onMoveEnd).toHaveBeenCalledTimes(1);\n\n  const reorderedList = [\n    { name: 'tucker' },\n    { name: 'church' },\n    { name: 'simmons' },\n    { name: 'sarge' },\n    { name: 'caboose' },\n    { name: 'grif' },\n    { name: 'donut' },\n  ];\n\n  expect(onMoveEnd.mock.calls[0]).toEqual([\n    reorderedList,\n    { name: 'caboose' },\n    0,\n    4,\n  ]);\n\n  expect(\n    TestUtils.scryRenderedComponentsWithType(root, TestTemplate).map(\n      (e) => e.props.item\n    )\n  ).toEqual(reorderedList);\n\n  expect(_scrollTop).toBe(0);\n  await delay(30);\n  expect(_scrollTop).toBeGreaterThan(20);\n});\n\ntest('two drags work', async () => {\n  let _scrollTop = 0;\n  const containerEl: any = {\n    get scrollTop() {\n      return _scrollTop;\n    },\n    set scrollTop(x) {\n      _scrollTop = x;\n    },\n  };\n\n  let list: Item[] = [\n    { name: 'caboose' },\n    { name: 'tucker' },\n    { name: 'church' },\n    { name: 'simmons' },\n    { name: 'sarge' },\n    { name: 'grif' },\n    { name: 'donut' },\n  ];\n\n  const div = document.createElement('div');\n\n  const onMoveEnd = jest.fn((newList) => {\n    list = newList;\n    render();\n  });\n\n  const rootRef = React.createRef<DraggableList<Item, any, TestTemplate>>();\n  function render() {\n    ReactDOM.render(\n      <DraggableList\n        ref={rootRef}\n        itemKey=\"name\"\n        list={list}\n        onMoveEnd={onMoveEnd}\n        template={TestTemplate}\n        springConfig={springConfig}\n        container={() => containerEl}\n      />,\n      div\n    );\n  }\n  render();\n  const root = rootRef.current!;\n\n  expect(\n    TestUtils.scryRenderedComponentsWithType(root, TestTemplate).map(\n      (e) => e.props.item\n    )\n  ).toEqual(list);\n\n  const renderedHandles: Array<TestTemplate> =\n    TestUtils.scryRenderedComponentsWithType(root, TestTemplate) as any;\n  expect(root.state.dragging).toBe(false);\n  renderedHandles[0].props.dragHandleProps.onMouseDown({\n    pageY: 500,\n    preventDefault() {},\n  });\n  expect(root.state.dragging).toBe(true);\n\n  (root as any)._handleMouseMove({ pageY: 600 });\n  await delay(30);\n  expect(root.state.dragging).toBe(true);\n  expect(onMoveEnd).toHaveBeenCalledTimes(0);\n  (root as any)._handleMouseUp();\n  expect(root.state.dragging).toBe(false);\n  expect(onMoveEnd).toHaveBeenCalledTimes(1);\n\n  const reorderedList = [\n    { name: 'tucker' },\n    { name: 'church' },\n    { name: 'caboose' },\n    { name: 'simmons' },\n    { name: 'sarge' },\n    { name: 'grif' },\n    { name: 'donut' },\n  ];\n  expect(onMoveEnd.mock.calls[0]).toEqual([\n    reorderedList,\n    { name: 'caboose' },\n    0,\n    2,\n  ]);\n\n  expect(\n    TestUtils.scryRenderedComponentsWithType(root, TestTemplate).map(\n      (e) => e.props.item\n    )\n  ).toEqual(reorderedList);\n\n  expect(root.state.dragging).toBe(false);\n  renderedHandles[0].props.dragHandleProps.onMouseDown({\n    pageY: 600,\n    preventDefault() {},\n  });\n  expect(root.state.dragging).toBe(true);\n  (root as any)._handleMouseMove({ pageY: 650 });\n  expect(root.state.dragging).toBe(true);\n  expect(onMoveEnd).toHaveBeenCalledTimes(1);\n  (root as any)._handleMouseUp();\n  expect(root.state.dragging).toBe(false);\n  expect(onMoveEnd).toHaveBeenCalledTimes(2);\n\n  const reorderedList2 = [\n    { name: 'tucker' },\n    { name: 'church' },\n    { name: 'simmons' },\n    { name: 'caboose' },\n    { name: 'sarge' },\n    { name: 'grif' },\n    { name: 'donut' },\n  ];\n  expect(onMoveEnd.mock.calls[1]).toEqual([\n    reorderedList2,\n    { name: 'caboose' },\n    2,\n    3,\n  ]);\n\n  expect(\n    TestUtils.scryRenderedComponentsWithType(root, TestTemplate).map(\n      (e) => e.props.item\n    )\n  ).toEqual(reorderedList2);\n\n  expect(_scrollTop).toBe(0);\n  await delay(30);\n  expect(_scrollTop).toBeGreaterThan(20);\n});\n\ntest('props reordered during drag works', () => {\n  let list: Item[] = [\n    { name: 'caboose' },\n    { name: 'tucker' },\n    { name: 'church' },\n    { name: 'simmons' },\n    { name: 'sarge' },\n    { name: 'grif' },\n    { name: 'donut' },\n  ];\n  const div = document.createElement('div');\n\n  const onMoveEnd = jest.fn((newList) => {\n    list = newList;\n    render();\n  });\n\n  const rootRef = React.createRef<DraggableList<Item, any, TestTemplate>>();\n  function render() {\n    ReactDOM.render(\n      <DraggableList\n        ref={rootRef}\n        itemKey=\"name\"\n        list={list}\n        template={TestTemplate}\n        onMoveEnd={onMoveEnd}\n        springConfig={springConfig}\n      />,\n      div\n    );\n  }\n  render();\n  const root = rootRef.current!;\n\n  expect(\n    TestUtils.scryRenderedComponentsWithType(root, TestTemplate).map(\n      (e) => e.props.item\n    )\n  ).toEqual(list);\n\n  const renderedHandles: Array<TestTemplate> =\n    TestUtils.scryRenderedComponentsWithType(root, TestTemplate) as any;\n  renderedHandles[0].props.dragHandleProps.onMouseDown({\n    pageY: 500,\n    preventDefault() {},\n  });\n\n  list = [\n    { name: 'tucker' },\n    { name: 'church' },\n    { name: 'simmons' },\n    { name: 'sarge' },\n    { name: 'grif' },\n    { name: 'caboose', extra: 1 },\n    { name: 'donut' },\n  ];\n  render();\n\n  (root as any)._handleMouseMove({ pageY: 450 });\n  expect(root.state.dragging).toBe(true);\n  expect(onMoveEnd).toHaveBeenCalledTimes(0);\n  (root as any)._handleMouseUp();\n  expect(root.state.dragging).toBe(false);\n  expect(onMoveEnd).toHaveBeenCalledTimes(1);\n\n  const reorderedList = [\n    { name: 'tucker' },\n    { name: 'church' },\n    { name: 'simmons' },\n    { name: 'sarge' },\n    { name: 'caboose', extra: 1 },\n    { name: 'grif' },\n    { name: 'donut' },\n  ];\n  expect(onMoveEnd.mock.calls[0]).toEqual([\n    reorderedList,\n    { name: 'caboose', extra: 1 },\n    5,\n    4,\n  ]);\n\n  expect(\n    TestUtils.scryRenderedComponentsWithType(root, TestTemplate).map(\n      (e) => e.props.item\n    )\n  ).toEqual(reorderedList);\n});\n\ntest('item removed during drag works', () => {\n  let list: Item[] = [\n    { name: 'caboose' },\n    { name: 'tucker' },\n    { name: 'church' },\n    { name: 'simmons' },\n    { name: 'sarge' },\n    { name: 'grif' },\n    { name: 'donut' },\n  ];\n  const div = document.createElement('div');\n\n  const onMoveEnd = jest.fn((newList) => {\n    list = newList;\n    render();\n  });\n\n  const rootRef = React.createRef<DraggableList<Item, any, TestTemplate>>();\n  function render() {\n    ReactDOM.render(\n      <DraggableList\n        ref={rootRef}\n        itemKey=\"name\"\n        list={list}\n        template={TestTemplate}\n        onMoveEnd={onMoveEnd}\n        springConfig={springConfig}\n      />,\n      div\n    );\n  }\n  render();\n  const root = rootRef.current!;\n\n  expect(\n    TestUtils.scryRenderedComponentsWithType(root, TestTemplate).map(\n      (e) => e.props.item\n    )\n  ).toEqual(list);\n\n  const renderedHandles: Array<TestTemplate> =\n    TestUtils.scryRenderedComponentsWithType(root, TestTemplate) as any;\n  renderedHandles[0].props.dragHandleProps.onMouseDown({\n    pageY: 500,\n    preventDefault() {},\n  });\n\n  list = [\n    { name: 'tucker' },\n    { name: 'church' },\n    { name: 'simmons' },\n    { name: 'sarge' },\n    { name: 'grif', extra: 2 },\n    { name: 'donut' },\n  ];\n  render();\n\n  (root as any)._handleMouseMove({ pageY: 650 });\n  const reorderedList = [\n    { name: 'tucker' },\n    { name: 'church' },\n    { name: 'simmons' },\n    { name: 'sarge' },\n    { name: 'grif', extra: 2 },\n    { name: 'donut' },\n  ];\n  expect(\n    TestUtils.scryRenderedComponentsWithType(root, TestTemplate).map(\n      (e) => e.props.item\n    )\n  ).toEqual(reorderedList);\n\n  expect(root.state.dragging).toBe(false);\n  expect(onMoveEnd).toHaveBeenCalledTimes(0);\n  (root as any)._handleMouseUp();\n  expect(root.state.dragging).toBe(false);\n  expect(onMoveEnd).toHaveBeenCalledTimes(0);\n\n  expect(\n    TestUtils.scryRenderedComponentsWithType(root, TestTemplate).map(\n      (e) => e.props.item\n    )\n  ).toEqual(reorderedList);\n});\n\ntest('item removed before drag end works', async () => {\n  let list: Item[] = [\n    { name: 'caboose' },\n    { name: 'tucker' },\n    { name: 'church' },\n    { name: 'simmons' },\n    { name: 'sarge' },\n    { name: 'grif' },\n    { name: 'donut' },\n  ];\n  const div = document.createElement('div');\n\n  const onMoveEnd = jest.fn((newList) => {\n    list = newList;\n    render();\n  });\n\n  const rootRef = React.createRef<DraggableList<Item, any, TestTemplate>>();\n  function render() {\n    ReactDOM.render(\n      <DraggableList\n        ref={rootRef}\n        itemKey=\"name\"\n        list={list}\n        template={TestTemplate}\n        onMoveEnd={onMoveEnd}\n        springConfig={springConfig}\n      />,\n      div\n    );\n  }\n  render();\n  const root = rootRef.current!;\n\n  expect(\n    TestUtils.scryRenderedComponentsWithType(root, TestTemplate).map(\n      (e) => e.props.item\n    )\n  ).toEqual(list);\n\n  const renderedHandles: Array<TestTemplate> =\n    TestUtils.scryRenderedComponentsWithType(root, TestTemplate) as any;\n  renderedHandles[0].props.dragHandleProps.onMouseDown({\n    pageY: 500,\n    preventDefault() {},\n  });\n  (root as any)._handleMouseMove({ pageY: 650 });\n  await delay(100);\n\n  // eslint-disable-next-line require-atomic-updates\n  list = [\n    { name: 'caboose', extra: 3 },\n    { name: 'tucker' },\n    { name: 'church' },\n    { name: 'simmons' },\n    { name: 'sarge' },\n    { name: 'grif', extra: 2 },\n  ];\n  render();\n  expect(root.state.dragging).toBe(true);\n  expect(onMoveEnd).toHaveBeenCalledTimes(0);\n  (root as any)._handleMouseUp();\n  expect(root.state.dragging).toBe(false);\n  expect(onMoveEnd).toHaveBeenCalledTimes(1);\n\n  const reorderedList = [\n    { name: 'tucker' },\n    { name: 'church' },\n    { name: 'simmons' },\n    { name: 'sarge' },\n    { name: 'caboose', extra: 3 },\n    { name: 'grif', extra: 2 },\n  ];\n  expect(\n    TestUtils.scryRenderedComponentsWithType(root, TestTemplate).map(\n      (e) => e.props.item\n    )\n  ).toEqual(reorderedList);\n});\n\ntest('dragged item removed after drag during animation works', () => {\n  let list: Item[] = [\n    { name: 'caboose' },\n    { name: 'tucker' },\n    { name: 'church' },\n    { name: 'simmons' },\n    { name: 'sarge' },\n    { name: 'grif' },\n    { name: 'donut' },\n  ];\n  const div = document.createElement('div');\n\n  const onMoveEnd = jest.fn((newList) => {\n    list = newList;\n    render();\n  });\n\n  const rootRef = React.createRef<DraggableList<Item, any, TestTemplate>>();\n  function render() {\n    ReactDOM.render(\n      <DraggableList\n        ref={rootRef}\n        itemKey=\"name\"\n        list={list}\n        template={TestTemplate}\n        onMoveEnd={onMoveEnd}\n        springConfig={springConfig}\n      />,\n      div\n    );\n  }\n  render();\n  const root = rootRef.current!;\n\n  expect(\n    TestUtils.scryRenderedComponentsWithType(root, TestTemplate).map(\n      (e) => e.props.item\n    )\n  ).toEqual(list);\n\n  const renderedHandles: Array<TestTemplate> =\n    TestUtils.scryRenderedComponentsWithType(root, TestTemplate) as any;\n  renderedHandles[0].props.dragHandleProps.onMouseDown({\n    pageY: 500,\n    preventDefault() {},\n  });\n  (root as any)._handleMouseMove({ pageY: 650 });\n\n  expect(root.state.dragging).toBe(true);\n  expect(onMoveEnd).toHaveBeenCalledTimes(0);\n  (root as any)._handleMouseUp();\n  expect(root.state.dragging).toBe(false);\n  expect(onMoveEnd).toHaveBeenCalledTimes(1);\n\n  const listMinusOne = [\n    { name: 'tucker' },\n    { name: 'church' },\n    { name: 'simmons' },\n    { name: 'sarge' },\n    { name: 'grif' },\n    { name: 'donut' },\n  ];\n  list = listMinusOne;\n  render();\n\n  expect(\n    TestUtils.scryRenderedComponentsWithType(root, TestTemplate).map(\n      (e) => e.props.item\n    )\n  ).toEqual(listMinusOne);\n});\n\ntest('list is shown with correct positions after being fully changed during animation', async () => {\n  let list: Item[] = [\n    { name: 'caboose' },\n    { name: 'tucker' },\n    { name: 'church' },\n    { name: 'simmons' },\n    { name: 'sarge' },\n    { name: 'grif' },\n    { name: 'donut' },\n  ];\n\n  const div = document.createElement('div');\n\n  const onMoveEnd = jest.fn((newList) => {\n    list = newList;\n    render();\n  });\n\n  const rootRef = React.createRef<DraggableList<Item, any, TestTemplate>>();\n  function render() {\n    ReactDOM.render(\n      <DraggableList\n        ref={rootRef}\n        itemKey=\"name\"\n        list={list}\n        template={TestTemplate}\n        onMoveEnd={onMoveEnd}\n        springConfig={springConfig}\n      />,\n      div\n    );\n  }\n  render();\n  const root = rootRef.current!;\n\n  const renderedHandles: Array<TestTemplate> =\n    TestUtils.scryRenderedComponentsWithType(root, TestTemplate) as any;\n  renderedHandles[0].props.dragHandleProps.onMouseDown({\n    pageY: 500,\n    preventDefault() {},\n  });\n\n  await delay(100);\n\n  (root as any)._handleMouseUp();\n  await delay(1);\n\n  expect(\n    root.getItemInstance('caboose').getDOMNode().parentElement!.style.position\n  ).toBe('absolute');\n\n  list = [{ name: 'lopez' }, { name: \"o'malley\" }];\n  render();\n  while (\n    root.getItemInstance('lopez').getDOMNode().parentElement!.style.position ===\n    'absolute'\n  ) {\n    await delay(10);\n  }\n  expect(\n    root.getItemInstance('lopez').getDOMNode().parentElement!.style.position\n  ).toBe('relative');\n});\n\ntest('updating commonProps works', () => {\n  let list: Item[] = [{ name: 'caboose' }, { name: 'donut' }];\n  let commonProps: any = { a: 5 };\n  const div = document.createElement('div');\n\n  const onMoveEnd = jest.fn((newList) => {\n    list = newList;\n    render();\n  });\n\n  const rootRef = React.createRef<DraggableList<Item, any, TestTemplate>>();\n  function render() {\n    ReactDOM.render(\n      <DraggableList\n        ref={rootRef}\n        itemKey=\"name\"\n        list={list}\n        template={TestTemplate}\n        onMoveEnd={onMoveEnd}\n        springConfig={springConfig}\n        commonProps={commonProps}\n      />,\n      div\n    );\n  }\n  render();\n  const root = rootRef.current!;\n\n  expect(\n    TestUtils.scryRenderedComponentsWithType(root, TestTemplate).map(\n      (e) => e.props.item\n    )\n  ).toEqual(list);\n  expect(\n    TestUtils.scryRenderedComponentsWithType(root, TestTemplate).map(\n      (e) => e.props.commonProps\n    )\n  ).toEqual(list.map(() => ({ a: 5 })));\n\n  commonProps = { b: 6 };\n  render();\n\n  expect(\n    TestUtils.scryRenderedComponentsWithType(root, TestTemplate).map(\n      (e) => e.props.commonProps\n    )\n  ).toEqual(list.map(() => ({ b: 6 })));\n});\n\ntest('onDragEnd and onDragStart callbacks are correctly called', () => {\n  let _scrollTop = 0;\n  const containerEl: any = {\n    get scrollTop() {\n      return _scrollTop;\n    },\n    set scrollTop(x) {\n      _scrollTop = x;\n    },\n  };\n\n  let list: Item[] = [\n    { name: 'alice' },\n    { name: 'bob' },\n    { name: 'charlie' },\n    { name: 'deb' },\n    { name: 'ethan' },\n  ];\n\n  const div = document.createElement('div');\n\n  const onMoveEnd = jest.fn((newList) => {\n    list = newList;\n    render();\n  });\n\n  const onDragStart = jest.fn(() => {\n\n  });\n\n  const onDragEnd = jest.fn(() => {\n\n  });\n\n  const rootRef = React.createRef<DraggableList<Item, any, TestTemplate>>();\n  function render() {\n    ReactDOM.render(\n      <DraggableList\n        ref={rootRef}\n        itemKey=\"name\"\n        list={list}\n        template={TestTemplate}\n        onMoveEnd={onMoveEnd}\n        springConfig={springConfig}\n        container={() => containerEl}\n        onDragStart={onDragStart}\n        onDragEnd={onDragEnd}\n      />,\n      div\n    );\n  }\n  render();\n  const root = rootRef.current!;\n\n  const renderedHandles: Array<TestTemplate> =\n    TestUtils.scryRenderedComponentsWithType(root, TestTemplate) as any;\n\n  expect(onDragStart).toHaveBeenCalledTimes(0);\n\n  renderedHandles[0].props.dragHandleProps.onMouseDown({\n    pageY: 500,\n    preventDefault() {},\n  });\n\n  expect(onDragStart).toHaveBeenCalledTimes(1);\n  expect(onDragStart).toHaveBeenLastCalledWith({name: 'alice'});\n  expect(onDragEnd).toHaveBeenCalledTimes(0);\n\n  (root as any)._handleMouseMove({ pageY: 600 });\n\n  (root as any)._handleMouseUp();\n\n  expect(onDragEnd).toHaveBeenCalledTimes(1);\n  expect(onDragEnd).toHaveBeenLastCalledWith({name: 'alice'});\n\n});\n"
  },
  {
    "path": "src/index.tsx",
    "content": "/* eslint react/prop-types: \"error\" */\n\nimport * as React from 'react';\nimport * as PropTypes from 'prop-types';\nimport { Motion, spring, OpaqueConfig } from 'react-motion';\nimport update from 'immutability-helper';\nimport MultiRef from 'react-multi-ref';\nimport OnUpdate from './OnUpdate';\nimport MoveContainer, { HeightData } from './MoveContainer';\n\nconst DEFAULT_HEIGHT: HeightData = { natural: 200, drag: 30 };\n\nfunction getScrollSpeed(distance: number, speed: number, size: number): number {\n  // If distance is zero, then the result is the max speed. Otherwise,\n  // the result tapers toward zero as it gets closer to the opposite\n  // edge of the region.\n  return Math.round(speed - (speed / size) * distance);\n}\n\ninterface Drag {\n  itemKey: string;\n\n  // The y position of the dragged item when the drag started. This will be\n  // equal to the initial mouseY value. The items not being dragged will be\n  // positioned so that the dragged item's original position lines up with\n  // startY.\n  startY: number;\n\n  // The y-position that corresponds to the mouse's current location. The\n  // dragged item will be rendered with this as its y-position.\n  mouseY: number;\n\n  // This is the difference between the raw mouse y value and startY. For\n  // example, if a user clicks the drag handle at a point 5 px below the item's\n  // top, then mouseOffset will be set to 5. As the user moves their mouse, we\n  // update mouseY to be the raw mouse y value minus mouseOffset.\n  mouseOffset: number;\n}\n\nexport interface TemplateProps<I, C> {\n  item: I;\n  itemSelected: number;\n  anySelected: number;\n  dragHandleProps: object;\n  commonProps: C;\n}\n\nexport interface Props<I, C, T> {\n  itemKey: string | ((item: I) => string);\n  template: new (props: any, context?: any) => T;\n  list: ReadonlyArray<I>;\n  onMoveEnd?: (\n    newList: ReadonlyArray<I>,\n    movedItem: I,\n    oldIndex: number,\n    newIndex: number\n  ) => void;\n  container?: () => HTMLElement | null | undefined;\n  constrainDrag?: boolean;\n  springConfig?: object;\n  padding?: number;\n  unsetZIndex?: boolean;\n  autoScrollMaxSpeed?: number;\n  autoScrollRegionSize?: number;\n  commonProps?: C;\n  onDragStart?: (draggedItem: I) => void;\n  onDragEnd?: (draggedItem: I) => void;\n}\ninterface State {\n  useAbsolutePositioning: boolean;\n  dragging: boolean;\n  lastDrag: Drag | null;\n  heights: { [key: string]: HeightData } | null;\n}\nexport default class DraggableList<\n  I,\n  C,\n  T extends React.Component<Partial<TemplateProps<I, C>>>\n> extends React.Component<Props<I, C, T>, State> {\n  public static propTypes = {\n    itemKey: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,\n    template: PropTypes.func,\n    list: PropTypes.array.isRequired,\n    onMoveEnd: PropTypes.func,\n    container: PropTypes.func,\n    springConfig: PropTypes.object,\n    constrainDrag: PropTypes.bool,\n    padding: PropTypes.number,\n    unsetZIndex: PropTypes.bool,\n    autoScrollMaxSpeed: PropTypes.number.isRequired,\n    autoScrollRegionSize: PropTypes.number.isRequired,\n    commonProps: PropTypes.object,\n  };\n  public static defaultProps: Partial<Props<any, any, any>> = {\n    springConfig: { stiffness: 300, damping: 50 },\n    padding: 10,\n    unsetZIndex: false,\n    constrainDrag: false,\n    autoScrollMaxSpeed: 15,\n    autoScrollRegionSize: 30,\n  };\n  private readonly _itemRefs: MultiRef<string, MoveContainer<I, any, T>> =\n    new MultiRef();\n  private _autoScrollerTimer: any;\n\n  private _listRef = React.createRef<HTMLDivElement>();\n\n  public state: State = {\n    useAbsolutePositioning: false,\n    dragging: false,\n    lastDrag: null,\n    heights: null,\n  };\n\n  public getItemInstance(key: string): T {\n    const ref = this._itemRefs.map.get(key);\n    if (!ref) throw new Error('key not found');\n    return ref.getTemplate();\n  }\n\n  public static getDerivedStateFromProps<I, C, T>(\n    newProps: Props<I, C, T>,\n    state: State\n  ): Partial<State> | null {\n    const { list } = newProps;\n    const { lastDrag } = state;\n\n    if (lastDrag) {\n      const keyFn = DraggableList._getKeyFn<I>(newProps.itemKey);\n\n      try {\n        DraggableList._getIndexOfItemWithKey<I>(keyFn, list, lastDrag.itemKey);\n      } catch (err) {\n        // If the dragged item was removed from the list, this block will get hit.\n        // Cancel the drag.\n        return { dragging: false, lastDrag: null };\n      }\n    }\n\n    return null;\n  }\n\n  public componentWillUnmount() {\n    this._handleMouseUp();\n  }\n\n  private _handleTouchStart(\n    itemKey: string,\n    pressY: number | undefined,\n    event: TouchEvent | React.TouchEvent\n  ) {\n    event.stopPropagation();\n    this._handleStartDrag(itemKey, pressY, event.touches[0].pageY);\n  }\n\n  private _handleMouseDown(\n    itemKey: string,\n    pressY: number | undefined,\n    event: MouseEvent | React.MouseEvent\n  ) {\n    event.preventDefault();\n    this._handleStartDrag(itemKey, pressY, event.pageY);\n  }\n\n  private _handleStartDrag(\n    itemKey: string,\n    pressY: number | undefined,\n    pageY: number\n  ) {\n    if (document.documentElement)\n      document.documentElement.style.cursor = 'move';\n    window.addEventListener('mouseup', this._handleMouseUp);\n    window.addEventListener('touchend', this._handleMouseUp);\n    window.addEventListener('touchmove', this._handleTouchMove);\n    window.addEventListener('mousemove', this._handleMouseMove);\n\n    const keyFn = this._getKeyFn();\n\n    if (this.props.onDragStart) {\n      const {list} = this.props;\n      const draggedItem = list[DraggableList._getIndexOfItemWithKey(keyFn, list, itemKey)];\n      this.props.onDragStart(draggedItem);\n    }\n\n    // If an element has focus while we drag around the parent, some browsers\n    // try to scroll the parent element to keep the focused element in view.\n    // Prevent that by unfocusing the element if it is focused.\n    {\n      const listEl = this._listRef.current;\n      if (!listEl) throw new Error('Should not happen');\n      if (\n        listEl.contains &&\n        document.activeElement &&\n        listEl.contains(document.activeElement) &&\n        document.activeElement instanceof HTMLElement\n      ) {\n        document.activeElement.blur();\n      }\n    }\n\n    let newHeights = null;\n    if (this.state.heights == null) {\n      const _newHeights: { [key: string]: HeightData } = Object.create(null);\n\n      this.props.list.forEach((item) => {\n        const key = keyFn(item);\n        const containerRef = this._itemRefs.map.get(key);\n        const refEl = containerRef\n          ? containerRef.getDOMNode().firstElementChild\n          : null;\n        const ref = containerRef ? containerRef.getTemplate() : null;\n        const natural =\n          refEl instanceof HTMLElement\n            ? refEl.offsetHeight\n            : DEFAULT_HEIGHT.natural;\n        const drag =\n          (ref &&\n            typeof (ref as any).getDragHeight === 'function' &&\n            (ref as any).getDragHeight()) ||\n          natural;\n\n        _newHeights[key] = { natural, drag };\n      });\n\n      newHeights = _newHeights;\n    }\n\n    // Need to re-render once before we start dragging so that the `y` values\n    // are set using the correct state.heights and then can animate from there.\n\n    const afterHeights = () => {\n      const itemIndex = DraggableList._getIndexOfItemWithKey(keyFn, this.props.list, itemKey);\n\n      // pressY will be non-null if the list is currently animating (because the\n      // clicked item has its `y` prop set). pressY will be null if the list is\n      // not currently animating (because the clicked item will be at its\n      // natural position, which is calculatable using _getDistance).\n      const startY =\n        pressY == null ? this._getDistance(0, itemIndex, false) : pressY;\n\n      const containerEl = this._getContainer();\n      const containerScroll =\n        !containerEl || containerEl === document.body\n          ? 0\n          : containerEl.scrollTop;\n\n      this.setState({\n        useAbsolutePositioning: true,\n        dragging: true,\n        lastDrag: {\n          itemKey,\n          startY,\n          mouseY: startY,\n          mouseOffset: pageY - startY + containerScroll,\n        },\n      });\n    };\n\n    if (newHeights) {\n      this.setState({ heights: newHeights }, afterHeights);\n    } else {\n      afterHeights();\n    }\n  }\n\n  private _handleTouchMove = (e: TouchEvent) => {\n    e.preventDefault();\n    this._handleMouseMove(e.touches[0]);\n  };\n\n  private _handleMouseMove = ({\n    pageY,\n    clientY,\n  }: MouseEvent | Touch | { pageY: number; clientY: number }) => {\n    const { list } = this.props;\n    const autoScrollMaxSpeed = this.props.autoScrollMaxSpeed!;\n    const autoScrollRegionSize = this.props.autoScrollRegionSize!;\n    const { dragging, lastDrag } = this.state;\n    if (!dragging || !lastDrag) return;\n\n    const containerEl = this._getContainer();\n    const dragVisualIndex = this._getDragVisualIndex();\n    const keyFn = this._getKeyFn();\n\n    clearInterval(this._autoScrollerTimer);\n\n    // If the user has the mouse near the top or bottom of the container and\n    // not at the end of the list, then autoscroll.\n    if (dragVisualIndex !== 0 && dragVisualIndex !== list.length - 1) {\n      let scrollSpeed = 0;\n\n      const containerRect =\n        containerEl &&\n        containerEl !== document.body &&\n        containerEl.getBoundingClientRect\n          ? containerEl.getBoundingClientRect()\n          : { top: 0, bottom: Infinity };\n\n      // Get the lowest of the screen top and the container top.\n      const top = Math.max(0, containerRect.top);\n\n      const distanceFromTop = clientY - top;\n      if (distanceFromTop > 0 && distanceFromTop < autoScrollRegionSize) {\n        scrollSpeed =\n          -1 *\n          getScrollSpeed(\n            distanceFromTop,\n            autoScrollMaxSpeed,\n            autoScrollRegionSize\n          );\n      } else {\n        // Get the lowest of the screen bottom and the container bottom.\n        const bottom = Math.min(window.innerHeight, containerRect.bottom);\n        const distanceFromBottom = bottom - clientY;\n        if (\n          distanceFromBottom > 0 &&\n          distanceFromBottom < autoScrollRegionSize\n        ) {\n          scrollSpeed = getScrollSpeed(\n            distanceFromBottom,\n            autoScrollMaxSpeed,\n            autoScrollRegionSize\n          );\n        }\n      }\n\n      if (scrollSpeed !== 0) {\n        this._scrollContainer(scrollSpeed);\n        this._autoScrollerTimer = setTimeout(() => {\n          this._handleMouseMove({\n            pageY: pageY + (containerEl === document.body ? scrollSpeed : 0),\n            clientY,\n          });\n        }, 16);\n      }\n    }\n\n    const containerScroll =\n      !containerEl || containerEl === document.body ? 0 : containerEl.scrollTop;\n    let mouseY = pageY - lastDrag.mouseOffset + containerScroll;\n    if (this.props.constrainDrag) {\n      const visualList = this._getVisualListDuringDrag();\n\n      mouseY = Math.max(\n        mouseY,\n        this._getDistanceFromTopDuringDrag(\n          lastDrag,\n          keyFn(visualList[0]),\n          visualList\n        )\n      );\n      mouseY = Math.min(\n        mouseY,\n        this._getDistanceFromTopDuringDrag(\n          lastDrag,\n          keyFn(visualList[visualList.length - 1]),\n          visualList\n        )\n      );\n    }\n\n    this.setState({ lastDrag: { ...lastDrag, mouseY } });\n  };\n\n  private _handleMouseUp = () => {\n    clearInterval(this._autoScrollerTimer);\n    window.removeEventListener('mouseup', this._handleMouseUp);\n    window.removeEventListener('touchend', this._handleMouseUp);\n    window.removeEventListener('touchmove', this._handleTouchMove);\n    window.removeEventListener('mousemove', this._handleMouseMove);\n\n    if (document.documentElement) document.documentElement.style.cursor = '';\n    this._lastScrollDelta = 0;\n\n    const { list, onMoveEnd, onDragEnd } = this.props;\n    const { dragging, lastDrag } = this.state;\n\n    if (dragging && lastDrag && onMoveEnd) {\n      const dragIndex = this._getDragListIndex();\n      const newIndex = this._getDragVisualIndex();\n\n      if (onDragEnd) {\n        onDragEnd(list[dragIndex]);\n      }\n\n      if (dragIndex !== newIndex) {\n        const newList = this._getVisualListDuringDrag();\n\n        onMoveEnd(newList, list[dragIndex], dragIndex, newIndex);\n      }\n\n      this.setState({ dragging: false });\n    }\n  };\n\n  private _scrollContainer(delta: number) {\n    const containerEl = this._getContainer();\n    if (!containerEl) return;\n    if (window.scrollBy && containerEl === document.body) {\n      window.scrollBy(0, delta);\n    } else {\n      containerEl.scrollTop += delta;\n    }\n  }\n\n  private _lastScrollDelta = 0;\n  private _adjustScrollAtEnd(delta: number) {\n    const frameDelta = Math.round(delta - this._lastScrollDelta);\n    this._scrollContainer(frameDelta);\n    this._lastScrollDelta += frameDelta;\n  }\n\n  private static _getIndexOfItemWithKey<I>(\n    keyFn: (item: I) => string,\n    list: ReadonlyArray<I>,\n    itemKey: string\n  ): number {\n    for (let i = 0, len = list.length; i < len; i++) {\n      if (keyFn(list[i]) === itemKey) {\n        return i;\n      }\n    }\n    throw new Error('Failed to find drag index');\n  }\n\n  private _getDragListIndex(): number {\n    const { list } = this.props;\n    const { lastDrag } = this.state;\n    if (!lastDrag) {\n      throw new Error('No drag happened');\n    }\n    const keyFn = this._getKeyFn();\n    return DraggableList._getIndexOfItemWithKey(keyFn, list, lastDrag.itemKey);\n  }\n\n  private _getDragVisualIndex(): number {\n    const { list } = this.props;\n    const padding = this.props.padding!;\n    const { dragging, lastDrag } = this.state;\n    if (!dragging || !lastDrag) throw new Error('Should not happen');\n\n    const dragListIndex = this._getDragListIndex();\n\n    const { mouseY, startY } = lastDrag;\n\n    const movementFromNatural = mouseY - startY;\n    // 1 down, -1 up, 0 neither\n    const direction =\n      movementFromNatural > 0 ? 1 : movementFromNatural < 0 ? -1 : 0;\n    let newIndex = dragListIndex;\n    if (direction !== 0) {\n      const keyFn = this._getKeyFn();\n      let reach = Math.abs(movementFromNatural);\n      for (\n        let i = dragListIndex + direction;\n        i < list.length && i >= 0;\n        i += direction\n      ) {\n        const iDragHeight = this._getItemHeight(keyFn(list[i])).drag;\n        if (reach < iDragHeight / 2 + padding) break;\n        reach -= iDragHeight + padding;\n        newIndex = i;\n      }\n    }\n\n    return newIndex;\n  }\n\n  private _getVisualListDuringDrag(): ReadonlyArray<I> {\n    const { list } = this.props;\n    const { dragging, lastDrag } = this.state;\n    if (!dragging || !lastDrag)\n      throw new Error(\n        'Should not happen: _getVisualListDuringDrag called outside of drag'\n      );\n\n    const dragListIndex = this._getDragListIndex();\n    const dragVisualIndex = this._getDragVisualIndex();\n\n    return update(list, {\n      $splice: [\n        [dragListIndex, 1],\n        [dragVisualIndex, 0, list[dragListIndex]],\n      ],\n    });\n  }\n\n  private _getItemHeight(key: string): HeightData {\n    return this.state.heights != null && key in this.state.heights\n      ? this.state.heights[key]\n      : DEFAULT_HEIGHT;\n  }\n\n  // Get the distance between the tops of two items in the list.\n  // Does not consider how the dragged item may be rendered in a different position\n  // unless you pass in the re-ordered list as a parameter.\n  private _getDistance(\n    start: number,\n    end: number,\n    dragging: boolean,\n    list: ReadonlyArray<I> = this.props.list\n  ): number {\n    if (end < start) {\n      return -this._getDistance(end, start, dragging, list);\n    }\n\n    const padding = this.props.padding!;\n    const keyFn = this._getKeyFn();\n    let distance = 0;\n    for (let i = start; i < end; i++) {\n      const height = this._getItemHeight(keyFn(list[i]));\n      distance += (dragging ? height.drag : height.natural) + padding;\n    }\n    return distance;\n  }\n\n  private _getDistanceFromTopDuringDrag(\n    lastDrag: Drag,\n    itemKey: string,\n    visualList: ReadonlyArray<I>\n  ): number {\n    const keyFn = this._getKeyFn();\n    const index = DraggableList._getIndexOfItemWithKey(\n      keyFn,\n      visualList,\n      itemKey\n    );\n    const dragListIndex = this._getDragListIndex();\n    const dragVisualIndex = DraggableList._getIndexOfItemWithKey(\n      keyFn,\n      visualList,\n      lastDrag.itemKey\n    );\n\n    let offset = 0;\n    if (dragVisualIndex < dragListIndex) {\n      const dragItemHeight = this._getItemHeight(lastDrag.itemKey);\n      const newCenterHeight = this._getItemHeight(\n        keyFn(visualList[dragListIndex])\n      );\n      offset = dragItemHeight.drag - newCenterHeight.drag;\n    }\n\n    return (\n      lastDrag.startY +\n      offset +\n      this._getDistance(dragListIndex, index, true, visualList)\n    );\n  }\n\n  private _getContainer(): HTMLElement | null | undefined {\n    const { container } = this.props;\n    return container ? container() : null;\n  }\n\n  private static _getKeyFn<I>(\n    itemKey: string | ((item: I) => string)\n  ): (item: I) => string {\n    return typeof itemKey === 'function' ? itemKey : (x) => (x as any)[itemKey];\n  }\n\n  private _getKeyFn(): (item: I) => string {\n    return DraggableList._getKeyFn<I>(this.props.itemKey);\n  }\n\n  render() {\n    const padding = this.props.padding!;\n    const {\n      list,\n      springConfig,\n      container,\n      template,\n      unsetZIndex,\n      commonProps,\n    } = this.props;\n    const { dragging, lastDrag, useAbsolutePositioning } = this.state;\n\n    const keyFn = this._getKeyFn();\n    const anySelected = spring(dragging ? 1 : 0, springConfig);\n\n    const visualList = dragging ? this._getVisualListDuringDrag() : list;\n\n    const children = list.map((item, i) => {\n      const key = keyFn(item);\n      const selectedStyle =\n        dragging && lastDrag && lastDrag.itemKey === key\n          ? {\n              itemSelected: spring(1, springConfig),\n              y: lastDrag.mouseY,\n            }\n          : {\n              itemSelected: spring(0, springConfig),\n              y: (useAbsolutePositioning ? spring : (x: number) => x)(\n                dragging && lastDrag\n                  ? this._getDistanceFromTopDuringDrag(\n                      lastDrag,\n                      key,\n                      visualList\n                    )\n                  : this._getDistance(0, i, false),\n                springConfig\n              ),\n            };\n      const style = {\n        anySelected,\n        ...selectedStyle,\n      };\n      const makeDragHandleProps = (getY: () => number | undefined): object => ({\n        onMouseDown: (e: React.MouseEvent) =>\n          this._handleMouseDown(key, getY(), e),\n        onTouchStart: (e: React.TouchEvent) =>\n          this._handleTouchStart(key, getY(), e),\n      });\n      const height = this._getItemHeight(key);\n      return (\n        <Motion\n          style={style}\n          key={key}\n          children={({ itemSelected, anySelected, y }) => (\n            <MoveContainer\n              ref={this._itemRefs.ref(key)}\n              y={useAbsolutePositioning ? y : undefined}\n              template={template}\n              padding={padding}\n              item={item}\n              itemSelected={itemSelected}\n              anySelected={anySelected}\n              height={height}\n              zIndex={\n                unsetZIndex && !useAbsolutePositioning\n                  ? 'auto'\n                  : lastDrag && lastDrag.itemKey === key\n                  ? list.length\n                  : i\n              }\n              makeDragHandleProps={makeDragHandleProps}\n              commonProps={commonProps}\n            />\n          )}\n        />\n      );\n    });\n\n    let adjustScroll: number | OpaqueConfig = 0;\n    if (!dragging && lastDrag && useAbsolutePositioning) {\n      const dragListIndex = this._getDragListIndex();\n      adjustScroll = spring(\n        this._getDistance(0, dragListIndex, false) - lastDrag.mouseY,\n        springConfig\n      );\n    }\n\n    let heightReserverHeight = 0;\n    let heightReserverMarginBottom = 0;\n    if (list.length) {\n      heightReserverMarginBottom = padding;\n      if (useAbsolutePositioning) {\n        heightReserverHeight =\n          this._getDistance(0, list.length, false) - padding;\n      }\n    }\n    return (\n      <div style={{ position: 'relative' }} ref={this._listRef}>\n        <Motion\n          style={{ adjustScroll, anySelected }}\n          onRest={() => {\n            if (!dragging) {\n              this.setState({\n                heights: null,\n                useAbsolutePositioning: false,\n              });\n            }\n          }}\n          children={({ adjustScroll }) => (\n            <div\n              style={{\n                display: useAbsolutePositioning ? 'block' : 'none',\n                height: `${heightReserverHeight}px`,\n                marginBottom: `${heightReserverMarginBottom}px`,\n              }}\n            >\n              {container && (\n                <OnUpdate\n                  cb={() => {\n                    if (!dragging && lastDrag && useAbsolutePositioning) {\n                      this._adjustScrollAtEnd(adjustScroll);\n                    }\n                  }}\n                />\n              )}\n            </div>\n          )}\n        />\n        {children}\n      </div>\n    );\n  }\n}\n"
  },
  {
    "path": "test-types/typescript.tsx",
    "content": "import * as React from 'react';\nimport DraggableList from '../src';\n\n// This file isn't meant to be executed. It's just a test for the type definitions.\n\ninterface Item {\n  a: number;\n  b: string;\n}\n\ninterface MyTempProps {\n  item: Item;\n  itemSelected: number;\n  // purposefully omit anySelected here\n  dragHandleProps: object;\n}\ninterface MyTempState {\n  foo: number;\n}\nclass MyTemp extends React.Component<MyTempProps, MyTempState> {\n  render() {\n    return <div {...this.props.dragHandleProps} />;\n  }\n}\n\nconst list: Array<Item> = [{ a: 123, b: 'xyz' }];\nconst x = (\n  <DraggableList<Item, void, MyTemp>\n    itemKey=\"foo\"\n    list={list}\n    template={MyTemp}\n    onMoveEnd={(ignoredNewList) => {\n      // ignore\n    }}\n  />\n);\nx;\nconst renderedDL: DraggableList<Item, void, MyTemp> = null as any;\nconst renderedItem = renderedDL.getItemInstance('foo');\nrenderedItem as MyTemp;\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"emitDeclarationOnly\": true,\n    \"outDir\": \"dist\",\n    \"rootDir\": \".\",\n    \"declaration\": true,\n    \"strict\": true,\n    \"jsx\": \"react\",\n    \"module\": \"commonjs\",\n    \"target\": \"ES2017\",\n    \"moduleResolution\": \"node\",\n    \"moduleDetection\": \"force\"\n  }\n}\n"
  }
]