{"id":13447158,"url":"https://github.com/schne324/dragon-drop","last_synced_at":"2025-05-16T15:04:53.001Z","repository":{"id":2981656,"uuid":"47157595","full_name":"schne324/dragon-drop","owner":"schne324","description":"Accessible drag and drop list reorder module","archived":false,"fork":false,"pushed_at":"2023-03-01T10:27:42.000Z","size":1346,"stargazers_count":407,"open_issues_count":17,"forks_count":26,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-04-03T13:16:05.481Z","etag":null,"topics":["a11y","accessible","drag-and-drop","dragon-drop"],"latest_commit_sha":null,"homepage":"https://schne324.github.io/dragon-drop/demo/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/schne324.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2015-12-01T01:39:02.000Z","updated_at":"2025-03-26T17:05:28.000Z","dependencies_parsed_at":"2023-07-05T19:34:11.314Z","dependency_job_id":null,"html_url":"https://github.com/schne324/dragon-drop","commit_stats":{"total_commits":100,"total_committers":5,"mean_commits":20.0,"dds":0.27,"last_synced_commit":"bb07bc0c2927bc26511ccfa7a96b18f07c47117f"},"previous_names":[],"tags_count":31,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/schne324%2Fdragon-drop","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/schne324%2Fdragon-drop/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/schne324%2Fdragon-drop/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/schne324%2Fdragon-drop/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/schne324","download_url":"https://codeload.github.com/schne324/dragon-drop/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248573448,"owners_count":21126832,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["a11y","accessible","drag-and-drop","dragon-drop"],"created_at":"2024-07-31T05:01:09.705Z","updated_at":"2025-04-12T13:34:03.800Z","avatar_url":"https://github.com/schne324.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"# Dragon Drop\n\n[![CircleCI](https://circleci.com/gh/schne324/dragon-drop.svg?style=svg)](https://circleci.com/gh/schne324/dragon-drop)\n\nKeyboard/assistive technology accessible drag-and-drop reorder list.\n\n\u003cimg alt=\"Dragon Drop\" src=\"/demo/dragondrop_sticker.png\" width=\"400\" /\u003e\n\n## Demo\n\nhttp://schne324.github.io/dragon-drop/demo/\n\n\u003cimg alt=\"\" role=\"presentation\" src=\"/demo/dragon-drop-screenshot.png\" width=\"400\" /\u003e\n\n## Enter the dragon...\n\nFor an in-depth look at dragon drop see the [smashing magazine article on dragon drop](https://www.smashingmagazine.com/2018/01/dragon-drop-accessible-list-reordering/)\n\n## Installation\n\n## npm\n```bash\n$ npm install drag-on-drop\n```\n\n### bower\n```bash\n$ bower install drag-on-drop\n```\n\n## Usage\n\n### Browserify/Webpack\n\n```js\nimport DragonDrop from 'drag-on-drop';\n\nconst dragon = new DragonDrop(container, options);\n```\n\n### In the browser\n\n```js\nconst DragonDrop = window.DragonDrop;\nconst dragon = new DragonDrop(container, options);\n```\n\n### React\n\nAlthough a DragonDrop react component doesn't exist (yet), it can be used _with_ react:\n\n```js\nclass App extends Component {\n  componentDidMount() {\n    this.setState({\n      dragonDrop: new DragonDrop(this.dragon)\n    });\n  }\n\n  componentDidUpdate() {\n    const { dragonDrop } = this.state;\n    // this public method allows dragon drop to\n    // reassess the updated items and handles\n    dragonDrop.initElements(this.dragon);\n  }\n\n  render() {\n    return (\n      \u003cul className='dragon' ref={el =\u003e this.dragon = el}\u003e\n        \u003cli\u003e\n          \u003cbutton type='button' aria-label='Reorder' /\u003e\n          \u003cspan\u003eItem 1\u003c/span\u003e\n        \u003c/li\u003e\n        \u003cli\u003e\n          \u003cbutton type='button' aria-label='Reorder' /\u003e\n          \u003cspan\u003eItem 2\u003c/span\u003e\n        \u003c/li\u003e\n      \u003c/ul\u003e\n    );\n  }\n}\n```\n[Full example](https://codepen.io/schne324/pen/dZOGeG)\n\n**NOTE** usage with react is not exactly ideal because DragonDrop uses normal DOM events not picked up by react (react doesn't know about the reordering).\n\n### CDN (unpkg)\n\nhttps://unpkg.com/drag-on-drop\n\n## API\n\n### `new DragonDrop(container, [options])`\n\n#### `container` _HTMLElement|Array_ (required)\n\nEither a single container element or an array of container elements.\n\n#### `options` _Object_ (optional)\n\n##### `item` _String_\n\nThe selector for the drag items (qualified within container). Defaults to\n```js\n'li'\n```\n\n##### `handle` _String_\n\nThe selector for the keyboard handle (qualified within the container and the selector provided for `item`). If set to `false`, the entire item will be used as the handle. Defaults to\n```ks\n'button'\n```\n\n##### `activeClass` _String_\n\nThe class to be added to the item being dragged. Defaults to\n\n```js\n'dragon-active'\n```\n\n##### `inactiveClass` _String_\n\nThe class to be added to all of the other items when an item is being dragged. Defaults\n\n```js\n'dragon-inactive'\n```\n\n##### `nested` _Boolean_\n\nSet to true if nested lists are being used (click and keydown events will not bubble up (`e.stopPropagation()` will be applied)). For nested lists, you MUST pass `DragonDrop` an array of containers as the 1st parameter (see example below).\n\n__NOTE:__ *there is a 99% chance that you'll need to use [:scope selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/:scope) to target only a given list's items (because dragon drop would otherwise include the sub list's items for example). Using `:scope` selectors will allow you to target direct descendant children (example: `:scope \u003e li`).*\n\n```js\nconst lists = Array.from(document.querySelectorAll('.dragon-list'));\nconst dragons = new DragonDrop(lists, {\n  nested: true,\n  handle: false,\n  item: ':scope \u003e li' // IMPORTANT! a selector that targets only a single list's items\n});\nconst [ topLevel, sublist1, sublist2 ] = dragons;\n\ntopLevel.on('grabbed', () =\u003e console.log('top-most container item grabbed'));\nsublist1.on('grabbed', () =\u003e console.log('sublist 1 item grabbed'));\nsublist2.on('grabbed', () =\u003e console.log('sublist 1 item grabbed'));\n```\n\n##### `dragulaOptions` _Object_\n\nAn options object passed through to dragula.\n\n__NOTE__: `dragulaOptions.moves` will be ignored given a DragonDrop instance with `nested: false` and a truthy `handle`\n\n__NOTE__: `dragulaOptions.moves` AND `dragulaOptions.accepts` will be ignored given a DragonDrop instance with `nested: true`\n\n##### `announcement` _Object_\n\nThe live region announcement configuration object containing the following properties:\n\n###### `grabbed` _Function_\n\nThe function called when an item is picked up. The currently grabbed element along with an array of all items are passed as arguments respectively. The function should return a string of text to be announced in the live region. Defaults to\n\n```js\nel =\u003e `Item ${el.innerText} grabbed`\n```\n\n###### `dropped` _Function_\n\nThe function called when an item is dropped. The newly dropped item along with an array of all items are passed as arguments respectively. The function should return a string of text to be announced in the live region. Defaults to\n\n```js\nel =\u003e `Item ${el.innerText} dropped`\n```\n\n###### `reorder` _Function_\n\nThe function called when the list has been reordered. The newly dropped item along with an array of items are passed as arguments respectively. The function should return a string of text to be announced in the live region. Defaults to\n\n```js\n(el, items) =\u003e {\n  const pos = items.indexOf(el) + 1;\n  const text = el.innerText;\n  return `The list has been reordered, ${text} is now item ${pos} of ${items.length}`;\n}\n```\n\n###### `cancel` _Function_\n\nThe function called when the reorder is cancelled (via ESC). No arguments passed in. Defaults to\n\n```js\n() =\u003e 'Reordering cancelled'\n```\n\n##### `liveRegion` _Object_\n\nAttributes that can be overridden in on the live region:\n\n###### `ariaLive` _string_\n\nOptional ariaLive attribute to be passed to the live region. Valid values are \"off\", \"polite\", or \"assertive\". Default is \"assertive\".\n\n###### `ariaRelevant` string\n\nOptional ariaRelevant attribute to be passed to the live region. Valid values are \"additions\", \"removals\", \"text\", and \"all\". Default is \"additions\".\n\n###### `ariaAtomic` boolean\n\nOptional ariaAtomic attribute to be passed to the live region. Default is \"true\".\n\n## Properties\n\n```js\nconst dragonDrop = new DragonDrop(container);\n```\n### `dragonDrop.items` _Array_\n\nAn array of each of the sortable item element references.\n\n### `dragonDrop.handles` _Array_\n\nAn array of each of the handle item element references. If instance doesn't have handles, this will be identical to `dragonDrop.items`.\n\n### `dragonDrop.dragula`\n\nA direct handle on the `dragula` instance created by `dragonDrop`\n\n### Example with options\n\n```js\nconst list = document.getElementById('dragon-list');\nconst dragonDrop = new DragonDrop(list, {\n  item: 'li',\n  handle: '.handle',\n  announcement: {\n    grabbed: el =\u003e `The dragon has grabbed ${el.innerText}`,\n    dropped: el =\u003e `The dragon has dropped ${el.innerText}`,\n    reorder: (el, items) =\u003e {\n      const pos = items.indexOf(el) + 1;\n      const text = el.innerText;\n      return `The dragon's list has been reordered, ${text} is now item ${pos} of ${items.length}`;\n    },\n    cancel: 'The dragon cancelled the reorder'\n  }\n});\n```\n\n## Events\n\nDragon drop emit events when important stuff happens.\n\n### `dragonDrop.on('grabbed', callback)`\n\nFires when an item is grabbed (with keyboard or mouse). The callback is passed the container along with the grabbed item.\n\n### `dragonDrop.on('dropped', callback)`\n\nFires when an item is dropped (with keyboard or mouse). The callback is passed the container and the grabbed item.\n\n### `dragonDrop.on('reorder', callback)`\n\nFires when a list is reordered. The callback is passed the container along with the item.\n\n### `dragonDrop.on('cancel', callback)`\n\nFires when a user cancels reordering with the escape key. The callback is passed the keydown event that triggered the cancel.\n\n### Example use of events\n\n```js\ndragonDrop\n  .on('grabbed', (container, item) =\u003e console.log(`Item ${item.innerText} grabbed`))\n  .on('dropped', (container, item) =\u003e console.log(`Item ${item.innerText} dropped`))\n  .on('reorder', (container, item) =\u003e console.log(`Reorder: ${item.innerText} has moved`))\n  .on('cancel', (e) =\u003e {\n    console.log('Reordering cancelled');\n    e.preventDefault();\n  });\n```\n\n**NOTE** for mouse drag/drop event hooks the `dragula` property is exposed for dragula's events\n```js\ndragonDrop.dragula.on('drop', ...);\n```\n\n## Methods\n\n### `dragonDrop.initElements(container)`\n\nReinitialises the list, so that newly added items can be dragged. You can do this automatically with a `MutationObserver`:\n\n```js\nconst observer = new MutationObserver(() =\u003e dragonDrop.initElements(container));\nobserver.observe(container, {childList: true});\n```\n\n## Debugging\n\nSet the following localStorage option to debug dragonDrop\n\n```js\nlocalStorage.debug = 'drag-on-drop:*';\n```\n\n## Notes on accessibility\n\nThere are certain things that are left up to the discretion of the implementer.  This is to keep `DragonDrop` less opinionated on some of the gray areas of a11y. The [demos](http://schne324.github.io/dragon-drop/demo/) show a few different approaches on associating help text with DragonDrop:\n\n1. __Recommended__ `aria-describedby` on each control (item, or if applicable, handle). This is the safest/test approach because it guarantees that AT users will receive the instructions. [Demo of this approach](https://schne324.github.io/dragon-drop/demo/index.html#bands-head)\n1. __Recommended__ `aria-labelledby` on the container/list element.  With this approach, where supported, will announce the instructions whenever the AT users enters the list (which is less verbose than the above). [Demo of this approach](https://schne324.github.io/dragon-drop/demo/index.html#schedule-head)\n1. __Not recommended__`aria-describedby` on the container/list element. This approach, where supported, will only announce the instructions if the screen reader user traverses to the actual list element. [Demo of this approach](https://schne324.github.io/dragon-drop/demo/index.html#nested)\n\nFor more information regarding accessibility you can read an [accessibility review of dragon drop](https://github.com/schne324/dragon-drop/issues/6) initiated by Drupal.\n\n## Thanks!\n\nA special thanks to Aaron Pearlman for the logo.\n\nAnother special thanks to contributors/maintainers of [dragula](https://github.com/bevacqua/dragula) which is used for all of the mouse behavior/interaction for dragon drop!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fschne324%2Fdragon-drop","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fschne324%2Fdragon-drop","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fschne324%2Fdragon-drop/lists"}