[
  {
    "path": ".editorconfig",
    "content": "# Editor configuration, see https://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.ts]\nquote_type = single\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/Bug_report.md",
    "content": "---\r\nname: Bug report\r\nabout: Create a report to help us improve\r\n\r\n---\r\n\r\n**Describe the bug**\r\nA clear and concise description of what the bug is.\r\n\r\n**To Reproduce**\r\nIf possible please provide a StackBlitz based on this template\r\nhttps://stackblitz.com/edit/ngx-drag-drop-issue-template\r\n\r\nSteps to reproduce the behavior:\r\n1. Go to '...'\r\n2. Click on '....'\r\n3. Scroll down to '....'\r\n4. See error\r\n\r\n**Expected behavior**\r\nA clear and concise description of what you expected to happen.\r\n\r\n**Screenshots**\r\nIf applicable, add screenshots to help explain your problem.\r\n\r\n**Desktop (please complete the following information):**\r\n - OS: [e.g. iOS]\r\n - Browser [e.g. chrome, safari]\r\n - Version [e.g. 22]\r\n\r\n**Smartphone (please complete the following information):**\r\n - Device: [e.g. iPhone6]\r\n - OS: [e.g. iOS8.1]\r\n - Browser [e.g. stock browser, safari]\r\n - Version [e.g. 22]\r\n\r\n**Additional context**\r\nAdd any other context about the problem here.\r\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/Feature_request.md",
    "content": "---\r\nname: Feature request\r\nabout: Suggest an idea for this project\r\n\r\n---\r\n\r\n**Is your feature request related to a problem? Please describe.**\r\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\r\n\r\n**Describe the solution you'd like**\r\nA clear and concise description of what you want to happen.\r\n\r\n**Describe alternatives you've considered**\r\nA clear and concise description of any alternative solutions or features you've considered.\r\n\r\n**Additional context**\r\nAdd any other context or screenshots about the feature request here.\r\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  pull_request:\n    branches: [master]\n\njobs:\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6\n      - uses: pnpm/action-setup@71c92474e7e4f5bca283fb17ef80fba9cdb2b4b1 # v6\n      - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6\n        with:\n          node-version: 24\n          cache: pnpm\n      - run: pnpm install --frozen-lockfile\n      - run: pnpm exec prettier --check .\n\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6\n      - uses: pnpm/action-setup@71c92474e7e4f5bca283fb17ef80fba9cdb2b4b1 # v6\n      - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6\n        with:\n          node-version: 24\n          cache: pnpm\n      - run: pnpm install --frozen-lockfile\n      - run: pnpm run build:lib\n      - name: Run tests with coverage\n        run: |\n          echo '## Test Coverage' >> $GITHUB_STEP_SUMMARY\n          echo '' >> $GITHUB_STEP_SUMMARY\n          pnpm run test:coverage 2>&1 | tee /dev/stderr | sed -n '/^-.*|/,/^-.*|$/p' >> $GITHUB_STEP_SUMMARY\n\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6\n      - uses: pnpm/action-setup@71c92474e7e4f5bca283fb17ef80fba9cdb2b4b1 # v6\n      - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6\n        with:\n          node-version: 24\n          cache: pnpm\n      - run: pnpm install --frozen-lockfile\n      - run: pnpm run build:lib\n      - run: pnpm run build:docs\n"
  },
  {
    "path": ".github/workflows/deploy-pages.yml",
    "content": "name: Deploy Demo to GitHub Pages\n\non:\n  push:\n    branches: [master]\n  workflow_dispatch:\n\npermissions:\n  contents: read\n  pages: write\n  id-token: write\n\nconcurrency:\n  group: pages\n  cancel-in-progress: true\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6\n      - uses: pnpm/action-setup@71c92474e7e4f5bca283fb17ef80fba9cdb2b4b1 # v6\n      - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6\n        with:\n          node-version: 24\n          cache: pnpm\n\n      - run: pnpm install --frozen-lockfile\n      - run: pnpm run build:lib\n      - run: pnpm run build:docs\n      - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5\n        with:\n          path: docs\n\n  deploy:\n    needs: build\n    runs-on: ubuntu-latest\n    environment:\n      name: github-pages\n      url: ${{ steps.deployment.outputs.page_url }}\n    steps:\n      - id: deployment\n        uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5\n"
  },
  {
    "path": ".github/workflows/publish.yml",
    "content": "name: Publish to npm\n\non:\n  release:\n    types: [published]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6\n      - uses: pnpm/action-setup@71c92474e7e4f5bca283fb17ef80fba9cdb2b4b1 # v6\n      - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6\n        with:\n          node-version: 24\n          cache: pnpm\n      - run: pnpm install --frozen-lockfile\n      - run: pnpm run build:lib\n      - name: Run tests with coverage\n        run: |\n          echo '## Test Coverage' >> $GITHUB_STEP_SUMMARY\n          echo '' >> $GITHUB_STEP_SUMMARY\n          echo '```' >> $GITHUB_STEP_SUMMARY\n          pnpm run test:coverage 2>&1 | tee /dev/stderr | sed -n '/^-.*|/,/^-.*|$/p' >> $GITHUB_STEP_SUMMARY\n          echo '```' >> $GITHUB_STEP_SUMMARY\n\n  publish:\n    needs: test\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      id-token: write\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6\n      - uses: pnpm/action-setup@71c92474e7e4f5bca283fb17ef80fba9cdb2b4b1 # v6\n      - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6\n        with:\n          node-version: 24\n          cache: pnpm\n          registry-url: https://registry.npmjs.org/\n      - run: pnpm install --frozen-lockfile\n      - run: pnpm run build:lib\n      - run: pnpm publish ./dist/ngx-drag-drop --provenance --access public --no-git-checks\n"
  },
  {
    "path": ".gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# compiled output\n/dist\n/docs\n/tmp\n/out-tsc\n# Only exists if Bazel was run\n/bazel-out\n/.ng_build\n\n# dependencies\n/node_modules\n\n# profiling files\nchrome-profiler-events*.json\n\n# IDEs and editors\n/.idea\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# IDE - VSCode\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# misc\n/.angular/cache\n/.sass-cache\n/connect.lock\n/coverage\n/libpeerconnection.log\nnpm-debug.log\npnpm-debug.log\npackage-lock.json\nyarn-error.log\ntestem.log\n/typings\n\n# System Files\n.DS_Store\nThumbs.db\n\nnode_modules\n"
  },
  {
    "path": ".prettierignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# compiled output\n/dist\n/docs\n/tmp\n/out-tsc\n# Only exists if Bazel was run\n/bazel-out\n/.ng_build\n/.github\n\n# dependencies\n/node_modules\n\n# profiling files\nchrome-profiler-events*.json\n\n# IDEs and editors\n/.idea\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# IDE - VSCode\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# misc\n/.angular/cache\n/.sass-cache\n/connect.lock\n/coverage\n/libpeerconnection.log\nnpm-debug.log\npnpm-debug.log\npnpm-lock.yaml\nyarn-error.log\ntestem.log\n/typings\n\n# System Files\n.DS_Store\nThumbs.db\n\nnode_modules\n"
  },
  {
    "path": ".prettierrc.json",
    "content": "{\n  \"tabWidth\": 2,\n  \"useTabs\": false,\n  \"singleQuote\": true,\n  \"semi\": true,\n  \"bracketSpacing\": true,\n  \"arrowParens\": \"avoid\",\n  \"trailingComma\": \"es5\",\n  \"bracketSameLine\": true,\n  \"printWidth\": 80\n}\n"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846\n  \"recommendations\": [\"angular.ng-template\"]\n}\n"
  },
  {
    "path": ".vscode/launch.json",
    "content": "{\n  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387\n  \"version\": \"0.2.0\",\n  \"configurations\": [\n    {\n      \"name\": \"ng serve\",\n      \"type\": \"pwa-chrome\",\n      \"request\": \"launch\",\n      \"preLaunchTask\": \"npm: start\",\n      \"url\": \"http://localhost:4200/\"\n    },\n    {\n      \"name\": \"ng test\",\n      \"type\": \"chrome\",\n      \"request\": \"launch\",\n      \"preLaunchTask\": \"npm: test\",\n      \"url\": \"http://localhost:9876/debug.html\"\n    }\n  ]\n}\n"
  },
  {
    "path": ".vscode/tasks.json",
    "content": "{\n  // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558\n  \"version\": \"2.0.0\",\n  \"tasks\": [\n    {\n      \"type\": \"npm\",\n      \"script\": \"start\",\n      \"isBackground\": true,\n      \"problemMatcher\": {\n        \"owner\": \"typescript\",\n        \"pattern\": \"$tsc\",\n        \"background\": {\n          \"activeOnStart\": true,\n          \"beginsPattern\": {\n            \"regexp\": \"(.*?)\"\n          },\n          \"endsPattern\": {\n            \"regexp\": \"bundle generation complete\"\n          }\n        }\n      }\n    },\n    {\n      \"type\": \"npm\",\n      \"script\": \"test\",\n      \"isBackground\": true,\n      \"problemMatcher\": {\n        \"owner\": \"typescript\",\n        \"pattern\": \"$tsc\",\n        \"background\": {\n          \"activeOnStart\": true,\n          \"beginsPattern\": {\n            \"regexp\": \"(.*?)\"\n          },\n          \"endsPattern\": {\n            \"regexp\": \"bundle generation complete\"\n          }\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "BSD 3-Clause License\n\nCopyright (c) 2021, Stefan Steinhart\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this\n  list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice,\n  this list of conditions and the following disclaimer in the documentation\n  and/or other materials provided with the distribution.\n\n* Neither the name of the copyright holder nor the names of its\n  contributors may be used to endorse or promote products derived from\n  this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "README.md",
    "content": "[![npm](https://img.shields.io/npm/v/ngx-drag-drop.svg)](https://www.npmjs.com/package/ngx-drag-drop)\n[![npm (next)](https://img.shields.io/npm/v/ngx-drag-drop/next.svg)](https://www.npmjs.com/package/ngx-drag-drop)\n[![NpmLicense](https://img.shields.io/npm/l/ngx-drag-drop.svg)](https://www.npmjs.com/package/ngx-drag-drop)\n[![GitHub issues](https://img.shields.io/github/issues/ChristofFritz/ngx-drag-drop.svg)](https://github.com/ChristofFritz/ngx-drag-drop/issues)\n[![Twitter](https://img.shields.io/twitter/url/https/github.com/ChristofFritz/ngx-drag-drop.svg?style=social)](https://twitter.com/intent/tweet?text=Angular%20drag%20and%20drop%20with%20ease:&url=https://github.com/ChristofFritz/ngx-drag-drop)\n\n# NgxDragDrop\n\n[_Demo_](https://christoffritz.github.io/ngx-drag-drop/) / [_StackBlitz Issue Template_](https://stackblitz.com/edit/ngx-drag-drop-issue-template)\n\n```sh\nnpm install ngx-drag-drop\n# or\npnpm add ngx-drag-drop\n```\n\n**Angular directives for declarative drag and drop using the HTML5 Drag-And-Drop API**\n\n- sortable lists by using placeholder element (vertical and horizontal)\n- nestable\n- dropzones optionally support external/native draggables (img, txt, file)\n- conditional drag/drop\n- typed drag/drop\n- utilize [EffectAllowed](https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/effectAllowed)\n- custom CSS classes\n- touch support by using a [polyfill](#touch-support)\n- [AOT](https://angular.io/guide/aot-compiler) compatible\n\nPort of [angular-drag-drop-lists](https://github.com/marceljuenemann/angular-drag-and-drop-lists) but without the lists :wink:\n\nThis has `dropzones` though :+1:\nThe idea is that the directive does not handle lists internally so the `dndDropzone` can be general purpose.\n\n## Angular Version Compatibility\n\nStarting with v13, the library major version matches the Angular major version.\n\n| Angular | ngx-drag-drop |\n| ------- | ------------- |\n| 21.x    | 21.x          |\n| 20.x    | 20.x          |\n| 19.x    | 19.x          |\n| 18.x    | 18.x          |\n| 17.x    | 17.x          |\n| 16.x    | 16.x          |\n| 15.x    | 15.x          |\n| 14.x    | 14.x          |\n| 13.x    | 13.x          |\n\nFor older Angular versions (v4–v12), use ngx-drag-drop v2.x.\n\n## Usage\n\n`app.component.html`\n\n```HTML\n<!--a draggable element-->\n<div [dndDraggable]=\"draggable.data\"\n     [dndEffectAllowed]=\"draggable.effectAllowed\"\n     [dndDisableIf]=\"draggable.disable\"\n     (dndStart)=\"onDragStart($event)\"\n     (dndCopied)=\"onDraggableCopied($event)\"\n     (dndLinked)=\"onDraggableLinked($event)\"\n     (dndMoved)=\"onDraggableMoved($event)\"\n     (dndCanceled)=\"onDragCanceled($event)\"\n     (dndEnd)=\"onDragEnd($event)\">\n\n    <!--if [dndHandle] is used inside dndDraggable drag can only start from the handle-->\n    <div *ngIf=\"draggable.handle\"\n         dndHandle>HANDLE\n    </div>\n\n    draggable ({{draggable.effectAllowed}}) <span [hidden]=\"!draggable.disable\">DISABLED</span>\n\n    <!--optionally select a child element as drag image-->\n    <div dndDragImageRef>DRAG_IMAGE</div>\n\n</div>\n\n<!--a dropzone-->\n<!--to allow dropping content that is not [dndDraggable] set dndAllowExternal to true-->\n<section dndDropzone\n         (dndDragover)=\"onDragover($event)\"\n         (dndDrop)=\"onDrop($event)\">\n\n    dropzone\n\n    <!--optional placeholder element for dropzone-->\n    <!--will be removed from DOM on init-->\n    <div style=\"border: 1px orangered solid; border-radius: 5px; padding: 15px;\"\n         dndPlaceholderRef>\n        placeholder\n    </div>\n\n</section>\n```\n\n`app.component`\n\n```JS\nimport { Component } from '@angular/core';\n\nimport { DndDropEvent } from 'ngx-drag-drop';\n\n@Component()\nexport class AppComponent {\n\n  draggable = {\n    // note that data is handled with JSON.stringify/JSON.parse\n    // only set simple data or POJO's as methods will be lost\n    data: \"myDragData\",\n    effectAllowed: \"all\",\n    disable: false,\n    handle: false\n  };\n\n  onDragStart(event:DragEvent) {\n\n    console.log(\"drag started\", JSON.stringify(event, null, 2));\n  }\n\n  onDragEnd(event:DragEvent) {\n\n    console.log(\"drag ended\", JSON.stringify(event, null, 2));\n  }\n\n  onDraggableCopied(event:DragEvent) {\n\n    console.log(\"draggable copied\", JSON.stringify(event, null, 2));\n  }\n\n  onDraggableLinked(event:DragEvent) {\n\n    console.log(\"draggable linked\", JSON.stringify(event, null, 2));\n  }\n\n  onDraggableMoved(event:DragEvent) {\n\n    console.log(\"draggable moved\", JSON.stringify(event, null, 2));\n  }\n\n  onDragCanceled(event:DragEvent) {\n\n    console.log(\"drag cancelled\", JSON.stringify(event, null, 2));\n  }\n\n  onDragover(event:DragEvent) {\n\n    console.log(\"dragover\", JSON.stringify(event, null, 2));\n  }\n\n  onDrop(event:DndDropEvent) {\n\n    console.log(\"dropped\", JSON.stringify(event, null, 2));\n  }\n}\n```\n\n`app.module`\n\n```JS\nimport { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\n\nimport { DndModule } from 'ngx-drag-drop';\n\nimport { AppComponent } from './app.component';\n\n@NgModule({\n  declarations: [\n    AppComponent\n  ],\n  imports: [\n    BrowserModule,\n    DndModule\n  ],\n  providers: [],\n  bootstrap: [AppComponent]\n})\nexport class AppModule {\n}\n```\n\n## API\n\n```TS\n// https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/dropEffect\nexport type DropEffect = \"move\" | \"copy\" | \"link\" | \"none\";\n\n// https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/effectAllowed\nexport type EffectAllowed = DropEffect | \"copyMove\" | \"copyLink\" | \"linkMove\" | \"all\";\n```\n\n```TS\nexport type DndDragImageOffsetFunction = ( event:DragEvent, dragImage:Element ) => { x:number, y:number };\n\n@Directive( {\n  selector: \"[dndDraggable]\"\n} )\nexport declare class DndDraggableDirective {\n\n    // the data attached to the drag\n    dndDraggable: any;\n\n    // the allowed drop effect\n    dndEffectAllowed: EffectAllowed;\n\n    // optionally set the type of dragged data to restrict dropping on compatible dropzones\n    dndType?: string;\n\n    // conditionally disable the draggability\n    dndDisableIf: boolean;\n    dndDisableDragIf: boolean;\n\n    // set a custom class that is applied while dragging\n    dndDraggingClass: string = \"dndDragging\";\n\n    // set a custom class that is applied to only the src element while dragging\n    dndDraggingSourceClass: string = \"dndDraggingSource\";\n\n    // set the class that is applied when draggable is disabled by [dndDisableIf]\n    dndDraggableDisabledClass = \"dndDraggableDisabled\";\n\n    // enables to set a function for calculating custom dragimage offset\n    dndDragImageOffsetFunction:DndDragImageOffsetFunction = calculateDragImageOffset;\n\n    // emits on drag start\n    readonly dndStart: EventEmitter<DragEvent>;\n\n    // emits on drag\n    readonly dndDrag: EventEmitter<DragEvent>;\n\n    // emits on drag end\n    readonly dndEnd: EventEmitter<DragEvent>;\n\n    // emits when the dragged item has been dropped with effect \"move\"\n    readonly dndMoved: EventEmitter<DragEvent>;\n\n    // emits when the dragged item has been dropped with effect \"copy\"\n    readonly dndCopied: EventEmitter<DragEvent>;\n\n    // emits when the dragged item has been dropped with effect \"link\"\n    readonly dndLinked: EventEmitter<DragEvent>;\n\n    // emits when the drag is canceled\n    readonly dndCanceled: EventEmitter<DragEvent>;\n}\n```\n\n```TS\nexport interface DndDropEvent {\n\n    // the original drag event\n    event: DragEvent;\n\n    // the actual drop effect\n    dropEffect: DropEffect;\n\n    // true if the drag did not origin from a [dndDraggable]\n    isExternal:boolean;\n\n    // the data set on the [dndDraggable] that started the drag\n    // for external drags use the event property which contains the original drop event as this will be undefined\n    data?: any;\n\n    // the index where the draggable was dropped in a dropzone\n    // set only when using a placeholder\n    index?: number;\n\n    // if the dndType input on dndDraggable was set\n    // it will be transported here\n    type?: any;\n}\n\n@Directive( {\n  selector: \"[dndDropzone]\"\n} )\nexport declare class DndDropzoneDirective {\n\n    // optionally restrict the allowed types\n    dndDropzone?: string[];\n\n    // set the allowed drop effect\n    dndEffectAllowed: EffectAllowed;\n\n    // conditionally disable the dropzone\n    dndDisableIf: boolean;\n    dndDisableDropIf: boolean;\n\n    // if draggables that are not [dndDraggable] are allowed to be dropped\n    // set to true if dragged text, images or files should be handled\n    dndAllowExternal: boolean;\n\n    // if its a horizontal list this influences how the placeholder position\n    // is calculated\n    dndHorizontal: boolean;\n\n    // set the class applied to the dropzone\n    // when a draggable is dragged over it\n    dndDragoverClass: string = \"dndDragover\";\n\n    // set the class applied to the dropzone\n    // when the dropzone is disabled by [dndDisableIf]\n    dndDropzoneDisabledClass = \"dndDropzoneDisabled\";\n\n    // emits when a draggable is dragged over the dropzone\n    readonly dndDragover: EventEmitter<DragEvent>;\n\n    // emits on successful drop\n    readonly dndDrop: EventEmitter<DndDropEvent>;\n}\n```\n\n## Touch support (experimental)\n\nThis library uses the native HTML5 Drag and Drop API, which is **not supported on most mobile browsers** (including iOS Safari). Touch support requires a polyfill that translates touch events into drag events. This approach has known limitations and the experience may not be reliable on all devices.\n\nTo enable basic touch support, install the `@dragdroptouch/drag-drop-touch` polyfill:\n\n```JS\nimport { enableDragDropTouch } from \"@dragdroptouch/drag-drop-touch\";\n\nenableDragDropTouch();\n```\n\nFor more info on the polyfill check it out on GitHub\nhttps://github.com/drag-drop-touch-js/dragdroptouch\n\n## Known issues\n\n### Firefox\n\n- Beware that Firefox does not support dragging on `<button>` elements.\n  - `<button [dndDraggable]>` and `<button [dndHandler]>` won't work.\n  - See https://bugzilla.mozilla.org/show_bug.cgi?id=568313\n\n## Why?\n\nHTML Drag-And-Drop API implementations are not behaving the same way across browsers.\n\nThe directives contained in this module enable declarative drag and drop that \"just works\" across browsers in a consistent way.\n\nCredits go to the author and contributors of [angular-drag-drop-lists](https://github.com/marceljuenemann/angular-drag-and-drop-lists).\n\n## Maintenance\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli).\n\nSee https://angular.io/guide/creating-libraries\n\n#### Edit Library\n\n- run `pnpm run watch:lib` for hacking on library\n\n#### Release Library\n\n- assure correct version is set in `projects/dnd/package.json`\n- build library with `pnpm run build:lib`\n- publish library with `pnpm run publish:stable` (use `pnpm run publish:next` for pre-releases)\n\n#### Edit Docs\n\n- initially and on lib changes run `pnpm run build:lib` to current version of lib available to the demo\n- run `pnpm run start:docs`\n\n#### Release Docs\n\nThe demo site is automatically deployed to GitHub Pages on every push to `master` via GitHub Actions.\n\n---\n\n<p align=\"center\">\nMade with :heart: & \n<a href=\"https://www.jetbrains.com/?from=ngx-drag-drop\">\n  <img align=\"center\" alt=\"jetbrains\" src=\"jetbrains.svg\" width=\"120px\">\n</a>\n& :coffee:\n</p>\n"
  },
  {
    "path": "angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"cli\": {\n    \"analytics\": false\n  },\n  \"version\": 1,\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"dnd\": {\n      \"projectType\": \"library\",\n      \"root\": \"projects/dnd\",\n      \"sourceRoot\": \"projects/dnd/src\",\n      \"prefix\": \"dnd\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular/build:ng-packagr\",\n          \"options\": {\n            \"project\": \"projects/dnd/ng-package.json\"\n          },\n          \"configurations\": {\n            \"production\": {\n              \"tsConfig\": \"projects/dnd/tsconfig.lib.prod.json\"\n            },\n            \"development\": {\n              \"tsConfig\": \"projects/dnd/tsconfig.lib.json\"\n            }\n          },\n          \"defaultConfiguration\": \"production\"\n        }\n      }\n    },\n    \"demo\": {\n      \"projectType\": \"application\",\n      \"schematics\": {\n        \"@schematics/angular:component\": {\n          \"style\": \"scss\"\n        },\n        \"@schematics/angular:application\": {\n          \"strict\": true\n        }\n      },\n      \"root\": \"projects/demo\",\n      \"sourceRoot\": \"projects/demo/src\",\n      \"prefix\": \"dnd\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular/build:application\",\n          \"options\": {\n            \"outputPath\": {\n              \"base\": \"docs\",\n              \"browser\": \"\"\n            },\n            \"index\": \"projects/demo/src/index.html\",\n            \"polyfills\": [\"projects/demo/src/polyfills.ts\"],\n            \"tsConfig\": \"projects/demo/tsconfig.app.json\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"projects/demo/src/favicon.ico\",\n              \"projects/demo/src/assets\"\n            ],\n            \"styles\": [\n              \"./node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css\",\n              \"projects/demo/src/styles.scss\"\n            ],\n            \"scripts\": [],\n            \"browser\": \"projects/demo/src/main.ts\"\n          },\n          \"configurations\": {\n            \"production\": {\n              \"budgets\": [\n                {\n                  \"type\": \"initial\",\n                  \"maximumWarning\": \"2mb\",\n                  \"maximumError\": \"2mb\"\n                },\n                {\n                  \"type\": \"anyComponentStyle\",\n                  \"maximumWarning\": \"2kb\",\n                  \"maximumError\": \"4kb\"\n                }\n              ],\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"projects/demo/src/environments/environment.ts\",\n                  \"with\": \"projects/demo/src/environments/environment.prod.ts\"\n                }\n              ],\n              \"outputHashing\": \"all\"\n            },\n            \"development\": {\n              \"optimization\": false,\n              \"extractLicenses\": false,\n              \"sourceMap\": true,\n              \"namedChunks\": true\n            }\n          },\n          \"defaultConfiguration\": \"production\"\n        },\n        \"serve\": {\n          \"builder\": \"@angular/build:dev-server\",\n          \"configurations\": {\n            \"production\": {\n              \"buildTarget\": \"demo:build:production\"\n            },\n            \"development\": {\n              \"buildTarget\": \"demo:build:development\"\n            }\n          },\n          \"defaultConfiguration\": \"development\"\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular/build:extract-i18n\",\n          \"options\": {\n            \"buildTarget\": \"demo:build\"\n          }\n        }\n      }\n    }\n  },\n  \"schematics\": {\n    \"@schematics/angular:component\": {\n      \"type\": \"component\"\n    },\n    \"@schematics/angular:directive\": {\n      \"type\": \"directive\"\n    },\n    \"@schematics/angular:service\": {\n      \"type\": \"service\"\n    },\n    \"@schematics/angular:guard\": {\n      \"typeSeparator\": \".\"\n    },\n    \"@schematics/angular:interceptor\": {\n      \"typeSeparator\": \".\"\n    },\n    \"@schematics/angular:module\": {\n      \"typeSeparator\": \".\"\n    },\n    \"@schematics/angular:pipe\": {\n      \"typeSeparator\": \".\"\n    },\n    \"@schematics/angular:resolver\": {\n      \"typeSeparator\": \".\"\n    }\n  }\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"ngx-drag-drop-workspace\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"pnpm run build:lib && pnpm run start:docs\",\n    \"start:docs\": \"ng serve demo\",\n    \"build:lib\": \"ng build dnd\",\n    \"watch:lib\": \"ng build dnd --watch --configuration development\",\n    \"build:docs\": \"ng build demo --configuration production --base-href /ngx-drag-drop/ && cp docs/index.html docs/404.html\",\n    \"watch:docs\": \"ng build demo --watch --configuration development\",\n    \"publish:stable\": \"pnpm publish ./dist/ngx-drag-drop\",\n    \"publish:next\": \"pnpm publish ./dist/ngx-drag-drop --tag next\",\n    \"test\": \"vitest run\",\n    \"test:watch\": \"vitest\",\n    \"test:coverage\": \"vitest run --coverage\",\n    \"prettier\": \"pnpm exec prettier --write .\"\n  },\n  \"private\": true,\n  \"packageManager\": \"pnpm@10.33.0\",\n  \"dependencies\": {\n    \"@angular/animations\": \"^21.2.6\",\n    \"@angular/cdk\": \"^21.2.4\",\n    \"@angular/common\": \"^21.2.6\",\n    \"@angular/compiler\": \"^21.2.6\",\n    \"@angular/core\": \"^21.2.6\",\n    \"@angular/forms\": \"^21.2.6\",\n    \"@angular/material\": \"^21.2.4\",\n    \"@angular/platform-browser\": \"^21.2.6\",\n    \"@angular/platform-browser-dynamic\": \"^21.2.6\",\n    \"@angular/router\": \"^21.2.6\",\n    \"@dragdroptouch/drag-drop-touch\": \"^2.0.3\",\n    \"bootstrap\": \"^5.3.8\",\n    \"rxjs\": \"~7.8.2\",\n    \"tslib\": \"^2.3.0\",\n    \"zone.js\": \"~0.16.1\"\n  },\n  \"devDependencies\": {\n    \"@analogjs/vite-plugin-angular\": \"2.4.8\",\n    \"@angular/build\": \"21.2.7\",\n    \"@angular/cli\": \"21.2.7\",\n    \"@angular/compiler-cli\": \"21.2.9\",\n    \"@types/node\": \"24.12.2\",\n    \"@vitest/coverage-v8\": \"4.1.4\",\n    \"jsdom\": \"29.0.2\",\n    \"ng-packagr\": \"21.2.2\",\n    \"prettier\": \"3.8.3\",\n    \"typescript\": \"6.0.2\",\n    \"vitest\": \"4.1.4\"\n  }\n}\n"
  },
  {
    "path": "pnpm-workspace.yaml",
    "content": "allowBuilds:\n  '@parcel/watcher': true\n  esbuild: true\n  lmdb: true\n  msgpackr-extract: true\n"
  },
  {
    "path": "projects/demo/.browserslistrc",
    "content": "# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n\n# For the full list of supported browsers by the Angular framework, please see:\n# https://angular.io/guide/browser-support\n\n# You can see what browsers were selected by your queries by running:\n#   npx browserslist\n\nlast 1 Chrome version\nlast 1 Firefox version\nlast 2 Edge major versions\nlast 2 Safari major versions\nlast 2 iOS major versions\nFirefox ESR\n"
  },
  {
    "path": "projects/demo/src/app/app.component.html",
    "content": "<mat-toolbar class=\"mat-elevation-z4 flex-shrink-0\" color=\"primary\">\n  <span>{{ title }}</span>\n\n  <span class=\"flex-grow-1\"></span>\n\n  <button [matMenuTriggerFor]=\"issueMenu\" mat-button>\n    <mat-icon fontIcon=\"bug_report\"></mat-icon>\n    Issue Demos\n  </button>\n  <mat-menu #issueMenu=\"matMenu\">\n    @for (demo of issueDemos; track demo.issue) {\n      <button (click)=\"onIssueDemoClick(demo.issue)\" mat-menu-item>\n        {{ demo.label }}\n      </button>\n    }\n  </mat-menu>\n\n  <a href=\"https://github.com/ChristofFritz/ngx-drag-drop\" mat-icon-button>\n    <mat-icon svgIcon=\"github\"></mat-icon>\n  </a>\n</mat-toolbar>\n\n@if (activeTab$ | async; as activeTab) {\n  <nav [tabPanel]=\"tabPanel\" mat-tab-nav-bar>\n    @for (tab of tabs; track tab) {\n      <a\n        [active]=\"activeTab === tab\"\n        (click)=\"onTabLinkClick(tab)\"\n        mat-tab-link>\n        {{ tab }}\n      </a>\n    }\n  </nav>\n}\n\n<mat-tab-nav-panel #tabPanel class=\"flex-grow-1 pt-3 overflow-auto\">\n  <router-outlet></router-outlet>\n</mat-tab-nav-panel>\n\n<mat-divider></mat-divider>\n\n<dnd-demo-link [name]=\"activeTab$ | async\" style=\"margin: 8px\"> </dnd-demo-link>\n"
  },
  {
    "path": "projects/demo/src/app/app.component.scss",
    "content": ":host {\n  display: flex;\n  flex: 1 1 auto;\n  flex-direction: column;\n  max-height: 100vh;\n  max-width: 100vw;\n  height: 100%;\n  width: 100%;\n}\n\n.container-padding {\n  padding: 8px;\n}\n\n.scrollable {\n  overflow: auto;\n  -webkit-overflow-scrolling: touch;\n}\n\n/* workaround for flex tab content */\n/* TODO(mdc-migration): The following rule targets internal classes of tabs that may no longer apply for the MDC version. */\nmat-tab-group ::ng-deep .mat-tab-body-wrapper {\n  height: 100%;\n  flex: 1 1 auto;\n}\n\n/* TODO(mdc-migration): The following rule targets internal classes of tabs that may no longer apply for the MDC version. */\nmat-tab-group ::ng-deep .mat-tab-body-wrapper .mat-tab-body-content {\n  display: flex;\n  height: 100%;\n  flex: 1 1 auto;\n}\n"
  },
  {
    "path": "projects/demo/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { MatIconRegistry } from '@angular/material/icon';\nimport { DomSanitizer } from '@angular/platform-browser';\nimport { ActivatedRoute, ActivationEnd, Router } from '@angular/router';\nimport { filter, map, Observable, shareReplay, startWith } from 'rxjs';\n\nconst TABS: string[] = [\n  'simple',\n  'list',\n  'nested',\n  'tree',\n  'native',\n  'typed',\n  'shadow-dom',\n];\n\nconst ISSUE_DEMOS: { issue: number; label: string }[] = [\n  { issue: 195, label: '#195 — dropEffect ignores dropzone effectAllowed' },\n];\n\nconst DEFAULT_TAB = TABS[0];\n\n@Component({\n  selector: 'dnd-root',\n  templateUrl: './app.component.html',\n  styleUrls: ['./app.component.scss'],\n  standalone: false,\n})\nexport class AppComponent {\n  readonly title = 'NgxDragDrop Demo';\n\n  readonly tabs: string[] = TABS;\n  readonly issueDemos = ISSUE_DEMOS;\n  readonly activeTab$: Observable<string>;\n\n  constructor(\n    sanitizer: DomSanitizer,\n    iconRegistry: MatIconRegistry,\n    activatedRoute: ActivatedRoute,\n    private router: Router\n  ) {\n    iconRegistry.addSvgIcon(\n      'github',\n      sanitizer.bypassSecurityTrustResourceUrl('assets/github.svg')\n    );\n\n    this.activeTab$ = this.router.events.pipe(\n      filter(event => event instanceof ActivationEnd),\n      map(event => {\n        const activationEnd = event as ActivationEnd;\n        if (!!activationEnd?.snapshot?.url?.length) {\n          return activationEnd?.snapshot?.url[0]?.path ?? DEFAULT_TAB;\n        }\n        return DEFAULT_TAB;\n      }),\n      startWith(DEFAULT_TAB),\n      shareReplay(1)\n    );\n  }\n\n  onTabLinkClick(tab: string) {\n    this.router.navigate([tab]);\n  }\n\n  onIssueDemoClick(issue: number) {\n    this.router.navigate(['issue', issue]);\n  }\n}\n"
  },
  {
    "path": "projects/demo/src/app/app.module.ts",
    "content": "import {\n  provideHttpClient,\n  withInterceptorsFromDi,\n} from '@angular/common/http';\nimport { NgModule } from '@angular/core';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatLineModule } from '@angular/material/core';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\nimport { MatSlideToggleModule } from '@angular/material/slide-toggle';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport { MatTabsModule } from '@angular/material/tabs';\nimport { MatToolbarModule } from '@angular/material/toolbar';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { RouterModule, Routes } from '@angular/router';\nimport { DndModule } from 'ngx-drag-drop';\nimport { AppComponent } from './app.component';\nimport { DemoLinkComponent } from './demo-link/demo-link.component';\n\nconst routes: Routes = [\n  {\n    path: 'simple',\n    loadComponent: () => import('./simple/simple.component'),\n  },\n  {\n    path: 'list',\n    loadComponent: () => import('./list/list.component'),\n  },\n  {\n    path: 'nested',\n    loadComponent: () => import('./nested/nested.component'),\n  },\n  {\n    path: 'tree',\n    loadComponent: () => import('./tree/tree.component'),\n  },\n  {\n    path: 'native',\n    loadComponent: () => import('./native/native.component'),\n  },\n  {\n    path: 'typed',\n    loadComponent: () => import('./typed/typed.component'),\n  },\n  {\n    path: 'shadow-dom',\n    loadComponent: () => import('./shadow-dom/shadow-dom.component'),\n  },\n  {\n    path: 'issue/195',\n    loadComponent: () => import('./issue-195/issue-195.component'),\n  },\n  {\n    path: '**',\n    pathMatch: 'full',\n    redirectTo: 'simple',\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forRoot(routes)],\n  exports: [RouterModule],\n})\nexport class AppRoutingModule {}\n\n@NgModule({\n  declarations: [AppComponent],\n  bootstrap: [AppComponent],\n  imports: [\n    BrowserModule,\n    BrowserAnimationsModule,\n    DndModule,\n    MatButtonModule,\n    MatInputModule,\n    MatToolbarModule,\n    MatCardModule,\n    MatSnackBarModule,\n    MatSlideToggleModule,\n    MatIconModule,\n    MatMenuModule,\n    MatTabsModule,\n    AppRoutingModule,\n    MatLineModule,\n    MatListModule,\n    DemoLinkComponent,\n  ],\n  providers: [provideHttpClient(withInterceptorsFromDi())],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "projects/demo/src/app/demo-link/demo-link.component.html",
    "content": "<a [href]=\"url\" color=\"accent\" mat-raised-button>go to sources</a>\n"
  },
  {
    "path": "projects/demo/src/app/demo-link/demo-link.component.scss",
    "content": ":host {\n  margin: 8px 0;\n}\n"
  },
  {
    "path": "projects/demo/src/app/demo-link/demo-link.component.ts",
    "content": "import { Component, Input } from '@angular/core';\nimport { MatButtonModule } from '@angular/material/button';\n\n@Component({\n  selector: 'dnd-demo-link',\n  templateUrl: './demo-link.component.html',\n  styleUrls: ['./demo-link.component.scss'],\n  standalone: true,\n  imports: [MatButtonModule],\n})\nexport class DemoLinkComponent {\n  @Input()\n  name: string | null = null;\n\n  public get url(): string {\n    return `https://github.com/ChristofFritz/ngx-drag-drop/tree/master/projects/demo/src/app/${this.name}`;\n  }\n}\n"
  },
  {
    "path": "projects/demo/src/app/indirect-dnd-handle/indirect-dnd-handle.component.html",
    "content": "<mat-icon dndHandle fontIcon=\"drag_handle\"></mat-icon>\n"
  },
  {
    "path": "projects/demo/src/app/indirect-dnd-handle/indirect-dnd-handle.component.scss",
    "content": ""
  },
  {
    "path": "projects/demo/src/app/indirect-dnd-handle/indirect-dnd-handle.component.ts",
    "content": "import { Component, HostBinding } from '@angular/core';\nimport { MatIconModule } from '@angular/material/icon';\nimport { DndHandleDirective } from 'ngx-drag-drop';\n\n@Component({\n  selector: 'dnd-indirect-handle',\n  templateUrl: './indirect-dnd-handle.component.html',\n  styleUrls: ['./indirect-dnd-handle.component.scss'],\n  standalone: true,\n  imports: [MatIconModule, DndHandleDirective],\n})\nexport class IndirectDndHandleComponent {\n  @HostBinding('class.drag-handle') get dragHandle() {\n    return true;\n  }\n}\n"
  },
  {
    "path": "projects/demo/src/app/indirect-drag-image/indirect-drag-image.component.html",
    "content": "<div dndDragImageRef>\n  <ng-content></ng-content>\n</div>\n"
  },
  {
    "path": "projects/demo/src/app/indirect-drag-image/indirect-drag-image.component.scss",
    "content": ""
  },
  {
    "path": "projects/demo/src/app/indirect-drag-image/indirect-drag-image.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { DndDragImageRefDirective } from 'ngx-drag-drop';\n\n@Component({\n  selector: 'dnd-indirect-drag-image',\n  templateUrl: './indirect-drag-image.component.html',\n  styleUrls: ['./indirect-drag-image.component.scss'],\n  standalone: true,\n  imports: [DndDragImageRefDirective],\n})\nexport class IndirectDragImageComponent {}\n"
  },
  {
    "path": "projects/demo/src/app/issue-195/issue-195.component.html",
    "content": "<h3>Issue #195: dropEffect ignores dropzone's dndEffectAllowed</h3>\n<p>\n  Draggable has <code>dndEffectAllowed=\"all\"</code>. Dropzone has\n  <code>dndEffectAllowed=\"link\"</code>. On drop, the\n  <code>dropEffect</code> should be <code>\"link\"</code>, not\n  <code>\"move\"</code>.\n</p>\n\n<div class=\"demo\">\n  <div\n    [dndDraggable]=\"'test-data'\"\n    dndEffectAllowed=\"all\"\n    (dndMoved)=\"dragEffect = 'move'\"\n    (dndCopied)=\"dragEffect = 'copy'\"\n    (dndLinked)=\"dragEffect = 'link'\"\n    (dndCanceled)=\"dragEffect = 'none'\"\n    class=\"draggable\">\n    Drag me (effectAllowed: all)\n  </div>\n\n  <div\n    (dndDrop)=\"onDrop($event)\"\n    class=\"dropzone\"\n    dndDropzone\n    dndEffectAllowed=\"link\">\n    <div class=\"placeholder\" dndPlaceholderRef></div>\n    Drop here (effectAllowed: link)\n  </div>\n</div>\n\n@if (lastDropEvent) {\n  <div class=\"result\">\n    <div>\n      <strong\n        >Dropzone received dropEffect: {{ lastDropEvent.dropEffect }}</strong\n      >\n      <span\n        [class.pass]=\"lastDropEvent.dropEffect === 'link'\"\n        [class.fail]=\"lastDropEvent.dropEffect !== 'link'\">\n        {{\n          lastDropEvent.dropEffect === 'link'\n            ? 'PASS'\n            : 'FAIL — expected \"link\"'\n        }}\n      </span>\n    </div>\n    <div>\n      <strong>Draggable fired effect: {{ dragEffect }}</strong>\n      <span\n        [class.pass]=\"dragEffect === 'link'\"\n        [class.fail]=\"dragEffect !== 'link'\">\n        {{ dragEffect === 'link' ? 'PASS' : 'FAIL — expected \"link\"' }}\n      </span>\n    </div>\n  </div>\n}\n"
  },
  {
    "path": "projects/demo/src/app/issue-195/issue-195.component.scss",
    "content": ".demo {\n  display: flex;\n  gap: 32px;\n  padding: 16px;\n  align-items: flex-start;\n}\n\n.draggable {\n  padding: 16px 24px;\n  background: white;\n  border: 2px solid #3f51b5;\n  border-radius: 4px;\n  cursor: move;\n}\n\n.dropzone {\n  padding: 24px;\n  min-height: 80px;\n  min-width: 200px;\n  background: #f5f5f5;\n  border: 2px dashed #999;\n  border-radius: 4px;\n}\n\n.placeholder {\n  padding: 16px;\n  border: 2px dashed orangered;\n  border-radius: 4px;\n}\n\n.result {\n  margin: 16px;\n  padding: 12px 16px;\n  background: #fafafa;\n  border-radius: 4px;\n  display: flex;\n  gap: 16px;\n  align-items: center;\n}\n\n.pass {\n  color: green;\n  font-weight: bold;\n}\n\n.fail {\n  color: red;\n  font-weight: bold;\n}\n"
  },
  {
    "path": "projects/demo/src/app/issue-195/issue-195.component.ts",
    "content": "import { JsonPipe } from '@angular/common';\nimport { Component } from '@angular/core';\nimport {\n  DndDraggableDirective,\n  DndDropEvent,\n  DndDropzoneDirective,\n  DndPlaceholderRefDirective,\n} from 'ngx-drag-drop';\n\n@Component({\n  selector: 'dnd-issue-195',\n  templateUrl: './issue-195.component.html',\n  styleUrls: ['./issue-195.component.scss'],\n  standalone: true,\n  imports: [\n    DndDraggableDirective,\n    DndDropzoneDirective,\n    DndPlaceholderRefDirective,\n    JsonPipe,\n  ],\n})\nexport default class Issue195Component {\n  lastDropEvent: DndDropEvent | null = null;\n  dragEffect: string = '';\n\n  onDrop(event: DndDropEvent) {\n    this.lastDropEvent = event;\n  }\n}\n"
  },
  {
    "path": "projects/demo/src/app/list/list.component.html",
    "content": "<div class=\"container-fluid\">\n  <div class=\"row\">\n    <div class=\"col\">\n      <pre>dndEffectAllowed=\"copyMove\"</pre>\n\n      <mat-list\n        [class.horizontal]=\"layout.dndHorizontal\"\n        [dndHorizontal]=\"layout.dndHorizontal\"\n        (dndDrop)=\"onDrop($event, draggableListLeft)\"\n        class=\"dndList gap-1 d-flex flex-column bg-light rounded-1 shadow-sm\"\n        dndDropzone\n        dndEffectAllowed=\"copyMove\">\n        <mat-list-item\n          class=\"dndPlaceholder border rounded-1 bg-opacity-25\"\n          dndPlaceholderRef>\n        </mat-list-item>\n\n        @for (item of draggableListLeft; track item) {\n          <mat-list-item\n            [dndDisableIf]=\"item.disable\"\n            [dndDraggable]=\"item\"\n            [dndEffectAllowed]=\"item.effectAllowed\"\n            (dndCanceled)=\"onDragged(item, draggableListLeft, 'none')\"\n            (dndCopied)=\"onDragged(item, draggableListLeft, 'copy')\"\n            (dndEnd)=\"onDragEnd($event)\"\n            (dndLinked)=\"onDragged(item, draggableListLeft, 'link')\"\n            (dndMoved)=\"onDragged(item, draggableListLeft, 'move')\"\n            (dndStart)=\"onDragStart($event)\"\n            class=\"border rounded-1 bg-white\">\n            @if (item.handle) {\n              <div\n                class=\"drag-handle align-self-center mx-3 my-0\"\n                dndHandle\n                matListItemIcon>\n                <mat-icon fontIcon=\"drag_handle\"></mat-icon>\n              </div>\n            }\n            <span matListItemTitle>{{ item.content }}</span>\n            <span matListItemLine>effectAllowed: {{ item.effectAllowed }}</span>\n          </mat-list-item>\n        }\n      </mat-list>\n    </div>\n\n    <div class=\"col d-flex flex-column\">\n      <pre>dndEffectAllowed=\"copyMove\"</pre>\n\n      <mat-list\n        [class.horizontal]=\"layout.dndHorizontal\"\n        [dndHorizontal]=\"layout.dndHorizontal\"\n        (dndDrop)=\"onDrop($event, draggableListRight)\"\n        class=\"dndList gap-1 flex-grow-1 d-flex flex-column bg-light rounded-1 shadow-sm\"\n        dndDropzone\n        dndEffectAllowed=\"copyMove\">\n        <mat-list-item\n          class=\"dndPlaceholder border rounded-1 bg-white bg-opacity-25\"\n          dndPlaceholderRef>\n        </mat-list-item>\n        @for (item of draggableListRight; track item) {\n          <mat-list-item\n            [dndDisableIf]=\"item.disable\"\n            [dndDraggable]=\"item\"\n            [dndEffectAllowed]=\"item.effectAllowed\"\n            (dndCanceled)=\"onDragged(item, draggableListRight, 'none')\"\n            (dndCopied)=\"onDragged(item, draggableListRight, 'copy')\"\n            (dndEnd)=\"onDragEnd($event)\"\n            (dndLinked)=\"onDragged(item, draggableListRight, 'link')\"\n            (dndMoved)=\"onDragged(item, draggableListRight, 'move')\"\n            (dndStart)=\"onDragStart($event)\"\n            class=\"border rounded-1 bg-white\">\n            @if (item.handle) {\n              <div\n                class=\"drag-handle align-self-center mx-3 my-0\"\n                dndHandle\n                matListItemIcon>\n                <mat-icon fontIcon=\"drag_handle\"></mat-icon>\n              </div>\n            }\n            <span matListItemTitle>{{ item.content }}</span>\n            <span matListItemLine>effectAllowed: {{ item.effectAllowed }}</span>\n          </mat-list-item>\n        }\n      </mat-list>\n    </div>\n\n    <div class=\"col d-flex flex-column\">\n      <pre>trash (dndEffectAllowed=\"move\")</pre>\n\n      <mat-list\n        [dndHorizontal]=\"layout.dndHorizontal\"\n        (dndDrop)=\"onDrop($event)\"\n        class=\"dndList gap-1 flex-grow-1 d-flex flex-column bg-light rounded-1 shadow-sm\"\n        dndDropzone\n        dndEffectAllowed=\"move\">\n      </mat-list>\n    </div>\n  </div>\n</div>\n\n<!-- <div class=\"layout-padding\"> -->\n<!--   <mat-slide-toggle [checked]=\"horizontalLayoutActive\" -->\n<!--                     (change)=\"horizontalLayoutActive = $event.checked; setHorizontalLayout($event.checked)\">horizontal -->\n<!--   </mat-slide-toggle> -->\n<!-- </div> -->\n"
  },
  {
    "path": "projects/demo/src/app/list/list.component.scss",
    "content": ":host {\n  display: block;\n}\n\npre {\n  text-align: center;\n  padding: 8px;\n}\n\n.dndList {\n  transition: all 300ms ease;\n  padding: 5px;\n\n  &:not(.horizontal).dndDragover {\n    padding-top: 12px;\n    padding-bottom: 12px;\n  }\n\n  &.horizontal {\n    &.dndDragover {\n      padding-left: 12px;\n      padding-right: 12px;\n      padding-bottom: 12px;\n    }\n\n    .mat-mdc-list-item {\n      max-width: 120px;\n      margin: 2px;\n    }\n  }\n}\n\n.dndDragging {\n  border: 1px solid green;\n}\n\n.dndDraggingSource {\n  display: none;\n}\n\n.dndPlaceholder {\n  min-height: 72px;\n}\n"
  },
  {
    "path": "projects/demo/src/app/list/list.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatSnackBar } from '@angular/material/snack-bar';\nimport {\n  DndDraggableDirective,\n  DndDropEvent,\n  DndDropzoneDirective,\n  DndHandleDirective,\n  DndPlaceholderRefDirective,\n  DropEffect,\n  EffectAllowed,\n} from 'ngx-drag-drop';\n\ninterface DraggableItem {\n  content: string;\n  effectAllowed: EffectAllowed;\n  disable: boolean;\n  handle: boolean;\n}\n\ninterface DropzoneLayout {\n  container: string;\n  list: string;\n  dndHorizontal: boolean;\n}\n\n@Component({\n  selector: 'dnd-list',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n  standalone: true,\n  imports: [\n    MatListModule,\n    DndDropzoneDirective,\n    DndPlaceholderRefDirective,\n    DndDraggableDirective,\n    DndHandleDirective,\n    MatIconModule,\n  ],\n})\nexport default class ListComponent {\n  draggableListLeft: DraggableItem[] = [\n    {\n      content: 'Left',\n      effectAllowed: 'move',\n      disable: false,\n      handle: false,\n    },\n    {\n      content: 'Lefter',\n      effectAllowed: 'move',\n      disable: false,\n      handle: false,\n    },\n    {\n      content: 'Leftest',\n      effectAllowed: 'copyMove',\n      disable: false,\n      handle: false,\n    },\n    {\n      content: 'Lefty',\n      effectAllowed: 'move',\n      disable: false,\n      handle: true,\n    },\n    {\n      content: 'Left',\n      effectAllowed: 'move',\n      disable: false,\n      handle: false,\n    },\n    {\n      content: 'Lefter',\n      effectAllowed: 'move',\n      disable: false,\n      handle: false,\n    },\n    {\n      content: 'Leftest',\n      effectAllowed: 'copyMove',\n      disable: false,\n      handle: false,\n    },\n    {\n      content: 'Lefty',\n      effectAllowed: 'move',\n      disable: false,\n      handle: true,\n    },\n    {\n      content: 'Left',\n      effectAllowed: 'move',\n      disable: false,\n      handle: false,\n    },\n    {\n      content: 'Lefter',\n      effectAllowed: 'move',\n      disable: false,\n      handle: false,\n    },\n    {\n      content: 'Leftest',\n      effectAllowed: 'copyMove',\n      disable: false,\n      handle: false,\n    },\n    {\n      content: 'Lefty',\n      effectAllowed: 'move',\n      disable: false,\n      handle: true,\n    },\n  ];\n\n  draggableListRight: DraggableItem[] = [\n    {\n      content: 'I was originally right',\n      effectAllowed: 'move',\n      disable: false,\n      handle: false,\n    },\n  ];\n  horizontalLayoutActive: boolean = false;\n  private currentDraggableEvent?: DragEvent;\n  private currentDragEffectMsg?: string;\n  private readonly verticalLayout: DropzoneLayout = {\n    container: 'row',\n    list: 'column',\n    dndHorizontal: false,\n  };\n  layout: DropzoneLayout = this.verticalLayout;\n  private readonly horizontalLayout: DropzoneLayout = {\n    container: 'row',\n    list: 'row wrap',\n    dndHorizontal: true,\n  };\n\n  constructor(private snackBarService: MatSnackBar) {}\n\n  setHorizontalLayout(horizontalLayoutActive: boolean) {\n    this.layout = horizontalLayoutActive\n      ? this.horizontalLayout\n      : this.verticalLayout;\n  }\n\n  onDragStart(event: DragEvent) {\n    this.currentDragEffectMsg = '';\n    this.currentDraggableEvent = event;\n\n    this.snackBarService.dismiss();\n    this.snackBarService.open('Drag started!', undefined, { duration: 2000 });\n  }\n\n  onDragged(item: any, list: any[], effect: DropEffect) {\n    this.currentDragEffectMsg = `Drag ended with effect \"${effect}\"!`;\n\n    if (effect === 'move') {\n      const index = list.indexOf(item);\n      list.splice(index, 1);\n    }\n  }\n\n  onDragEnd(event: DragEvent) {\n    this.currentDraggableEvent = event;\n    this.snackBarService.dismiss();\n    this.snackBarService.open(\n      this.currentDragEffectMsg || `Drag ended!`,\n      undefined,\n      { duration: 2000 }\n    );\n  }\n\n  onDrop(event: DndDropEvent, list?: any[]) {\n    if (list && (event.dropEffect === 'copy' || event.dropEffect === 'move')) {\n      let index = event.index;\n\n      if (typeof index === 'undefined') {\n        index = list.length;\n      }\n\n      list.splice(index, 0, event.data);\n    }\n  }\n}\n"
  },
  {
    "path": "projects/demo/src/app/native/native.component.html",
    "content": "<div class=\"container-fluid\">\n  <div class=\"row\">\n    <div class=\"col\">\n      <mat-card appearance=\"raised\">\n        <mat-card-header>\n          <mat-card-title>Draggable stuff</mat-card-title>\n          <mat-card-subtitle>\n            Drag the image or a text selection onto the dropzone and see what\n            happens\n          </mat-card-subtitle>\n        </mat-card-header>\n        <mat-card-content class=\"d-flex flex-column gap-3\">\n          <div>\n            <img src=\"https://i.imgflip.com/1quv8v.jpg\" />\n          </div>\n\n          <div>\n            <a href=\"https://imgflip.com/i/1quv8v\">Made with imgflip</a>\n          </div>\n\n          <a href=\"#\">\n            Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam\n            nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam\n            erat, sed diam voluptua. At vero eos et accusam et justo duo dolores\n            et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est\n            Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur\n            sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore\n            et dolore magna aliquyam erat, sed diam voluptua. At vero eos et\n            accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren,\n            no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum\n            dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod\n            tempor invidunt ut labore et dolore magna aliquyam erat, sed diam\n            voluptua. At vero eos et accusam et justo duo dolores et ea rebum.\n            Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum\n            dolor sit amet.\n          </a>\n        </mat-card-content>\n      </mat-card>\n    </div>\n\n    <div class=\"col\">\n      <mat-card appearance=\"raised\">\n        <mat-card-header>\n          <mat-card-title>Dropzone</mat-card-title>\n        </mat-card-header>\n        <mat-card-content>\n          <div\n            [dndAllowExternal]=\"true\"\n            (dndDrop)=\"onDrop($event)\"\n            class=\"mt-2 p-2 border rounded\"\n            dndDropzone>\n            <div>\n              @if (lastDropEvent) {\n                <pre>Event: {{ lastDropEvent | json }}</pre>\n              }\n              @if (lastDropTypes && lastDropTypes.length) {\n                <pre>Types: {{ lastDropTypes | json }}</pre>\n              }\n              @if (lastDropFiles && lastDropFiles.length) {\n                <pre>Files: {{ lastDropFiles | json }}</pre>\n              }\n              @if (lastDropItems && lastDropItems.length) {\n                <pre>Items: {{ lastDropItems | json }}</pre>\n              }\n            </div>\n          </div>\n        </mat-card-content>\n      </mat-card>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "projects/demo/src/app/native/native.component.scss",
    "content": ":host {\n  display: block;\n  box-sizing: border-box;\n}\n\npre {\n  white-space: pre-wrap;\n  word-wrap: break-word;\n}\n"
  },
  {
    "path": "projects/demo/src/app/native/native.component.ts",
    "content": "import { JsonPipe } from '@angular/common';\nimport { Component } from '@angular/core';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatSnackBar } from '@angular/material/snack-bar';\nimport { DndDropEvent, DndDropzoneDirective } from 'ngx-drag-drop';\n\n@Component({\n  selector: 'dnd-native',\n  templateUrl: './native.component.html',\n  styleUrls: ['./native.component.scss'],\n  standalone: true,\n  imports: [MatCardModule, DndDropzoneDirective, JsonPipe],\n})\nexport default class NativeComponent {\n  public lastDropEvent: DndDropEvent | null = null;\n\n  public lastDropTypes?: ReadonlyArray<string>;\n  public lastDropFiles?: object[];\n  public lastDropItems?: object[];\n\n  constructor(private snackBarService: MatSnackBar) {}\n\n  onDrop(event: DndDropEvent) {\n    this.snackBarService.dismiss();\n    this.snackBarService.open(`Something dropped O.O`, undefined, {\n      duration: 2000,\n    });\n\n    this.lastDropEvent = event;\n\n    this.lastDropTypes = this.lastDropEvent.event.dataTransfer?.types;\n\n    this.lastDropFiles = [];\n    this.lastDropItems = [];\n\n    if (this.lastDropEvent.event.dataTransfer?.files) {\n      for (\n        let i: number = 0;\n        i < this.lastDropEvent.event.dataTransfer?.files.length;\n        i++\n      ) {\n        const file = this.lastDropEvent.event.dataTransfer?.files[i];\n        this.lastDropFiles.push({\n          lastModifiedDate: file.lastModified,\n          name: file.name,\n          type: file.type,\n          size: file.size,\n        });\n      }\n\n      for (\n        let i: number = 0;\n        i < this.lastDropEvent.event.dataTransfer.items.length;\n        i++\n      ) {\n        const item = this.lastDropEvent.event.dataTransfer.items[i];\n        this.lastDropItems.push({\n          type: item.type,\n          kind: item.kind,\n          data: this.lastDropEvent.event.dataTransfer.getData(item.type),\n        });\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "projects/demo/src/app/nested/nested.component.html",
    "content": "<div class=\"container-fluid\">\n  <ng-template #recursiveList let-list>\n    <mat-card appearance=\"raised\" class=\"dndPlaceholder\" dndPlaceholderRef>\n    </mat-card>\n\n    @for (item of list; track item) {\n      <mat-card\n        appearance=\"raised\"\n        [dndDisableIf]=\"!!item.disable\"\n        [dndDraggable]=\"item\"\n        (dndCanceled)=\"onDragged(item, list, 'none')\"\n        (dndCopied)=\"onDragged(item, list, 'copy')\"\n        (dndEnd)=\"onDragEnd($event)\"\n        (dndLinked)=\"onDragged(item, list, 'link')\"\n        (dndMoved)=\"onDragged(item, list, 'move')\"\n        (dndStart)=\"onDragStart($event)\"\n        dndEffectAllowed=\"move\">\n        <mat-card-header class=\"p-2\">\n          @if (item.handle) {\n            <div class=\"drag-handle me-2 text-muted\">\n              <mat-icon dndHandle mat-list-icon>drag_handle </mat-icon>\n            </div>\n          }\n          {{ item.content }}\n        </mat-card-header>\n        <mat-card-content\n          class=\"d-flex align-items-start gap-2 flex-column p-2\">\n          @if (!!item.customDragImage) {\n            <div dndDragImageRef>MY_CUSTOM_DRAG_IMAGE</div>\n          }\n          @if (item.children) {\n            <div\n              (dndDrop)=\"onDrop($event, item.children)\"\n              class=\"flex-column p-2 gap-2 rounded-2\"\n              dndDropzone>\n              <ng-container\n                *ngTemplateOutlet=\"\n                  recursiveList;\n                  context: { $implicit: item.children }\n                \"></ng-container>\n            </div>\n          }\n        </mat-card-content>\n      </mat-card>\n    }\n  </ng-template>\n\n  <div\n    (dndDrop)=\"onDrop($event, nestableList)\"\n    class=\"d-flex gap-3 p-3 rounded-2\"\n    dndDropzone>\n    <ng-container\n      *ngTemplateOutlet=\"\n        recursiveList;\n        context: { $implicit: nestableList }\n      \"></ng-container>\n  </div>\n</div>\n"
  },
  {
    "path": "projects/demo/src/app/nested/nested.component.scss",
    "content": ":host {\n  display: block;\n  box-sizing: border-box;\n}\n\nmat-card {\n  transition:\n    transform 200ms,\n    opacity 200ms;\n}\n\nmat-card-header {\n  border-bottom: 2px solid rgba(0, 0, 0, 0.04);\n  align-items: center;\n  min-height: 46px;\n}\n\n.dndDraggingSource {\n  opacity: 0.5;\n  transform: scale(0.98);\n}\n\n.dndPlaceholder {\n  background: #fff;\n  min-height: 46px;\n  min-width: 46px;\n}\n\n[dnddropzone] {\n  background: #fafafa;\n  min-height: 60px;\n  display: flex;\n  outline: 2px solid rgba(0, 0, 0, 0.04);\n}\n"
  },
  {
    "path": "projects/demo/src/app/nested/nested.component.ts",
    "content": "import { NgTemplateOutlet } from '@angular/common';\nimport { Component } from '@angular/core';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatSnackBar } from '@angular/material/snack-bar';\nimport {\n  DndDraggableDirective,\n  DndDragImageRefDirective,\n  DndDropEvent,\n  DndDropzoneDirective,\n  DndHandleDirective,\n  DndPlaceholderRefDirective,\n  DropEffect,\n} from 'ngx-drag-drop';\n\ninterface NestableListItem {\n  content: string;\n  disable?: boolean;\n  handle?: boolean;\n  customDragImage?: boolean;\n  children?: NestableListItem[];\n}\n\n@Component({\n  selector: 'dnd-nested',\n  templateUrl: './nested.component.html',\n  styleUrls: ['./nested.component.scss'],\n  standalone: true,\n  imports: [\n    MatCardModule,\n    DndPlaceholderRefDirective,\n    DndDraggableDirective,\n    MatIconModule,\n    DndHandleDirective,\n    DndDragImageRefDirective,\n    DndDropzoneDirective,\n    NgTemplateOutlet,\n  ],\n})\nexport default class NestedComponent {\n  nestableList: NestableListItem[] = [\n    {\n      content: 'Got something nested',\n      children: [\n        {\n          content: 'Nested',\n          customDragImage: true,\n          children: [],\n        },\n      ],\n    },\n    {\n      content: 'No nested dropping here',\n    },\n    {\n      content: 'Got more than one',\n      children: [\n        {\n          content: 'Nested 1',\n          handle: true,\n          children: [],\n        },\n        {\n          content: 'Nested 2',\n          children: [],\n        },\n        {\n          content: 'Nested 3',\n          children: [],\n        },\n      ],\n    },\n    {\n      content: \"Drop something, I'll catch!\",\n      children: [],\n    },\n  ];\n\n  private currentDraggableEvent?: DragEvent;\n  private currentDragEffectMsg?: string;\n\n  constructor(private snackBarService: MatSnackBar) {}\n\n  onDragStart(event: DragEvent) {\n    this.currentDragEffectMsg = '';\n    this.currentDraggableEvent = event;\n\n    this.snackBarService.dismiss();\n    this.snackBarService.open('Drag started!', undefined, { duration: 2000 });\n  }\n\n  onDragged(item: any, list: any[], effect: DropEffect) {\n    this.currentDragEffectMsg = `Drag ended with effect \"${effect}\"!`;\n\n    if (effect === 'move') {\n      const index = list.indexOf(item);\n      list.splice(index, 1);\n    }\n  }\n\n  onDragEnd(event: DragEvent) {\n    this.currentDraggableEvent = event;\n    this.snackBarService.dismiss();\n    this.snackBarService.open(\n      this.currentDragEffectMsg || `Drag ended!`,\n      undefined,\n      { duration: 2000 }\n    );\n  }\n\n  onDrop(event: DndDropEvent, list?: any[]) {\n    if (list && (event.dropEffect === 'copy' || event.dropEffect === 'move')) {\n      let index = event.index;\n\n      if (typeof index === 'undefined') {\n        index = list.length;\n      }\n\n      list.splice(index, 0, event.data);\n    }\n  }\n}\n"
  },
  {
    "path": "projects/demo/src/app/shadow-dom/shadow-dom.component.html",
    "content": "<ng-template #recursiveList let-list>\n  <div class=\"placeholder\" dndPlaceholderRef></div>\n\n  @for (item of list; track item) {\n    <div\n      [dndDraggable]=\"item\"\n      dndEffectAllowed=\"move\"\n      (dndMoved)=\"onDragged(item, list, 'move')\"\n      class=\"drag-item\">\n      <div class=\"drag-item-header\">{{ item.content }}</div>\n      @if (item.children) {\n        <div\n          (dndDrop)=\"onDrop($event, item.children)\"\n          class=\"nested-dropzone\"\n          dndDropzone>\n          <ng-container\n            *ngTemplateOutlet=\"\n              recursiveList;\n              context: { $implicit: item.children }\n            \"></ng-container>\n        </div>\n      }\n    </div>\n  }\n</ng-template>\n\n<div (dndDrop)=\"onDrop($event, nestableList)\" class=\"root-dropzone\" dndDropzone>\n  <ng-container\n    *ngTemplateOutlet=\"\n      recursiveList;\n      context: { $implicit: nestableList }\n    \"></ng-container>\n</div>\n"
  },
  {
    "path": "projects/demo/src/app/shadow-dom/shadow-dom.component.scss",
    "content": ":host {\n  display: block;\n  padding: 16px;\n}\n\n.root-dropzone,\n.nested-dropzone {\n  display: flex;\n  flex-direction: column;\n  gap: 8px;\n  padding: 8px;\n  min-height: 40px;\n  background: #f5f5f5;\n  border-radius: 4px;\n}\n\n.nested-dropzone {\n  margin-top: 8px;\n  background: #ede7f6;\n}\n\n.drag-item {\n  padding: 12px 16px;\n  background: white;\n  border: 1px solid #ddd;\n  border-radius: 4px;\n  cursor: move;\n}\n\n.drag-item-header {\n  font-weight: 500;\n}\n\n.placeholder {\n  padding: 12px 16px;\n  border: 2px dashed orangered;\n  border-radius: 4px;\n  background: rgba(255, 69, 0, 0.05);\n}\n\n.dndDragover {\n  outline: 2px solid #3f51b5;\n}\n"
  },
  {
    "path": "projects/demo/src/app/shadow-dom/shadow-dom.component.ts",
    "content": "import { NgTemplateOutlet } from '@angular/common';\nimport { Component, ViewEncapsulation } from '@angular/core';\nimport {\n  DndDraggableDirective,\n  DndDropEvent,\n  DndDropzoneDirective,\n  DndPlaceholderRefDirective,\n  DropEffect,\n} from 'ngx-drag-drop';\n\ninterface NestableListItem {\n  content: string;\n  children?: NestableListItem[];\n}\n\n@Component({\n  selector: 'dnd-shadow-dom',\n  templateUrl: './shadow-dom.component.html',\n  styleUrls: ['./shadow-dom.component.scss'],\n  standalone: true,\n  encapsulation: ViewEncapsulation.ShadowDom,\n  imports: [\n    DndDropzoneDirective,\n    DndPlaceholderRefDirective,\n    DndDraggableDirective,\n    NgTemplateOutlet,\n  ],\n})\nexport default class ShadowDomComponent {\n  nestableList: NestableListItem[] = [\n    {\n      content: 'Parent A',\n      children: [\n        { content: 'Child A.1', children: [] },\n        { content: 'Child A.2', children: [] },\n      ],\n    },\n    {\n      content: 'Parent B (no children)',\n    },\n    {\n      content: 'Parent C',\n      children: [\n        { content: 'Child C.1', children: [] },\n        { content: 'Child C.2', children: [] },\n        { content: 'Child C.3', children: [] },\n      ],\n    },\n    {\n      content: 'Parent D (empty)',\n      children: [],\n    },\n  ];\n\n  onDragged(\n    item: NestableListItem,\n    list: NestableListItem[],\n    effect: DropEffect\n  ) {\n    if (effect === 'move') {\n      const index = list.indexOf(item);\n      list.splice(index, 1);\n    }\n  }\n\n  onDrop(event: DndDropEvent, list?: NestableListItem[]) {\n    if (list && (event.dropEffect === 'copy' || event.dropEffect === 'move')) {\n      let index = event.index;\n      if (typeof index === 'undefined') {\n        index = list.length;\n      }\n      list.splice(index, 0, event.data);\n    }\n  }\n}\n"
  },
  {
    "path": "projects/demo/src/app/simple/simple.component.html",
    "content": "<div class=\"container-fluid\">\n  <div class=\"row gap-3 gap-sm-0\">\n    <div class=\"d-flex flex-column col gap-2\">\n      @for (draggable of draggables; track draggable) {\n        <mat-card\n          appearance=\"raised\"\n          [dndDisableIf]=\"draggable.disable\"\n          [dndDraggable]=\"draggable.content\"\n          [dndEffectAllowed]=\"draggable.effectAllowed\"\n          (dndCanceled)=\"onDragged($event, 'none')\"\n          (dndCopied)=\"onDragged($event, 'copy')\"\n          (dndEnd)=\"onDragEnd($event)\"\n          (dndLinked)=\"onDragged($event, 'link')\"\n          (dndMoved)=\"onDragged($event, 'move')\"\n          (dndStart)=\"onDragStart($event)\">\n          <mat-card-content class=\"d-flex flex-column gap-2\">\n            @if (draggable.handle) {\n              <div class=\"drag-handle\" dndHandle>\n                <mat-icon fontIcon=\"drag_handle\" mat-list-icon> </mat-icon>\n              </div>\n            }\n            draggable ({{ draggable.effectAllowed }})\n            <span [hidden]=\"!draggable.handle\"> only with handle</span>\n            <span [hidden]=\"!draggable.disable\"> DISABLED</span>\n          </mat-card-content>\n        </mat-card>\n      }\n\n      <mat-card\n        appearance=\"raised\"\n        [dndDisableIf]=\"draggableWithDragImage.disable\"\n        [dndDraggable]=\"draggableWithDragImage\"\n        [dndEffectAllowed]=\"draggableWithDragImage.effectAllowed\"\n        (dndCanceled)=\"onDragged($event, 'none')\"\n        (dndCopied)=\"onDragged($event, 'copy')\"\n        (dndEnd)=\"onDragEnd($event)\"\n        (dndLinked)=\"onDragged($event, 'link')\"\n        (dndMoved)=\"onDragged($event, 'move')\"\n        (dndStart)=\"onDragStart($event)\">\n        <mat-card-content class=\"d-flex flex-column gap-2\">\n          @if (draggableWithDragImage.handle) {\n            <div class=\"drag-handle\">\n              <mat-icon dndHandle fontIcon=\"drag_handle\" mat-list-icon>\n              </mat-icon>\n            </div>\n          }\n          draggable ({{ draggableWithDragImage.effectAllowed }})\n          <span [hidden]=\"!draggableWithDragImage.handle\"\n            >only with handle</span\n          >\n          <span [hidden]=\"!draggableWithDragImage.disable\"> DISABLED</span>\n\n          <div dndDragImageRef>MY_CUSTOM_DRAG_IMAGE</div>\n        </mat-card-content>\n      </mat-card>\n\n      <mat-card\n        appearance=\"raised\"\n        [dndDisableIf]=\"draggableWithDragImage.disable\"\n        [dndDraggable]=\"draggableWithDragImage\"\n        [dndEffectAllowed]=\"draggableWithDragImage.effectAllowed\"\n        (dndCanceled)=\"onDragged($event, 'none')\"\n        (dndCopied)=\"onDragged($event, 'copy')\"\n        (dndEnd)=\"onDragEnd($event)\"\n        (dndLinked)=\"onDragged($event, 'link')\"\n        (dndMoved)=\"onDragged($event, 'move')\"\n        (dndStart)=\"onDragStart($event)\">\n        <mat-card-content class=\"d-flex flex-column gap-2\">\n          <dnd-indirect-handle></dnd-indirect-handle>\n\n          draggable ({{ draggableWithDragImage.effectAllowed }})\n\n          <dnd-indirect-drag-image\n            >I'm the drag image but pssst</dnd-indirect-drag-image\n          >\n        </mat-card-content>\n      </mat-card>\n\n      <mat-card\n        appearance=\"raised\"\n        [dndDisableIf]=\"draggableWithDragImage.disable\"\n        [dndDragImageOffsetFunction]=\"dragImageOffsetRight\"\n        [dndDraggable]=\"draggableWithDragImage\"\n        [dndEffectAllowed]=\"draggableWithDragImage.effectAllowed\"\n        (dndCanceled)=\"onDragged($event, 'none')\"\n        (dndCopied)=\"onDragged($event, 'copy')\"\n        (dndEnd)=\"onDragEnd($event)\"\n        (dndLinked)=\"onDragged($event, 'link')\"\n        (dndMoved)=\"onDragged($event, 'move')\"\n        (dndStart)=\"onDragStart($event)\">\n        <mat-card-content class=\"d-flex flex-column gap-2\">\n          <div>draggable ({{ draggableWithDragImage.effectAllowed }})</div>\n\n          <dnd-indirect-handle></dnd-indirect-handle>\n        </mat-card-content>\n      </mat-card>\n    </div>\n\n    <div class=\"col\">\n      <mat-card appearance=\"raised\">\n        <mat-card-header>\n          <mat-card-title>Dropzone</mat-card-title>\n          <mat-card-subtitle>\n            <mat-slide-toggle\n              [checked]=\"dropzoneEnabled\"\n              (change)=\"dropzoneEnabled = $event.checked\">\n              Enabled\n            </mat-slide-toggle>\n          </mat-card-subtitle>\n        </mat-card-header>\n        <mat-card-content>\n          <section\n            [dndDisableIf]=\"!dropzoneEnabled\"\n            (dndDrop)=\"onDrop($event)\"\n            class=\"my-dropzone mt-3 p-2 border rounded\"\n            dndDragoverClass=\"custom-drag-over\"\n            dndDropzone>\n            @if (lastDropEvent) {\n              <pre>{{ lastDropEvent | json }}</pre>\n            }\n          </section>\n        </mat-card-content>\n      </mat-card>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "projects/demo/src/app/simple/simple.component.scss",
    "content": ":host {\n  display: block;\n  height: 100%;\n}\n\n.my-dropzone {\n  transition: all 300ms ease;\n}\n\n.custom-drag-over {\n  background-color: rgba(0, 0, 0, 0.06);\n}\n\npre {\n  white-space: pre-wrap;\n  word-wrap: break-word;\n}\n"
  },
  {
    "path": "projects/demo/src/app/simple/simple.component.ts",
    "content": "import { JsonPipe } from '@angular/common';\nimport { Component } from '@angular/core';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatSlideToggleModule } from '@angular/material/slide-toggle';\nimport { MatSnackBar } from '@angular/material/snack-bar';\nimport {\n  DndDraggableDirective,\n  DndDragImageOffsetFunction,\n  DndDragImageRefDirective,\n  DndDropEvent,\n  DndDropzoneDirective,\n  DndHandleDirective,\n  EffectAllowed,\n} from 'ngx-drag-drop';\nimport { IndirectDndHandleComponent } from '../indirect-dnd-handle/indirect-dnd-handle.component';\nimport { IndirectDragImageComponent } from '../indirect-drag-image/indirect-drag-image.component';\n\ninterface DraggableItem {\n  content: string;\n  effectAllowed: EffectAllowed;\n  disable: boolean;\n  handle: boolean;\n}\n\n@Component({\n  selector: 'dnd-simple',\n  templateUrl: './simple.component.html',\n  styleUrls: ['./simple.component.scss'],\n  standalone: true,\n  imports: [\n    MatCardModule,\n    DndDraggableDirective,\n    DndHandleDirective,\n    MatIconModule,\n    DndDragImageRefDirective,\n    IndirectDndHandleComponent,\n    IndirectDragImageComponent,\n    MatSlideToggleModule,\n    DndDropzoneDirective,\n    JsonPipe,\n  ],\n})\nexport default class SimpleComponent {\n  draggables: DraggableItem[] = [\n    {\n      content: 'testdata',\n      effectAllowed: 'copy',\n      disable: false,\n      handle: false,\n    },\n    {\n      content: 'testdata2',\n      effectAllowed: 'move',\n      disable: false,\n      handle: false,\n    },\n    {\n      content: 'testdata3',\n      effectAllowed: 'link',\n      disable: false,\n      handle: false,\n    },\n    {\n      content: 'testdata4',\n      effectAllowed: 'copy',\n      disable: true,\n      handle: false,\n    },\n    {\n      content: 'testdata5',\n      effectAllowed: 'copy',\n      disable: false,\n      handle: true,\n    },\n  ];\n\n  draggableWithDragImage: DraggableItem = {\n    content: 'testdata6',\n    effectAllowed: 'copy',\n    disable: false,\n    handle: true,\n  };\n\n  public dropzoneEnabled: boolean = true;\n  public lastDropEvent: DndDropEvent | null = null;\n\n  private currentDraggableEvent?: DragEvent;\n  private currentDragEffectMsg?: string;\n\n  constructor(private snackBarService: MatSnackBar) {}\n\n  dragImageOffsetRight: DndDragImageOffsetFunction = (\n    event: DragEvent,\n    dragImage: Element\n  ) => {\n    const dragImageComputedStyle = window.getComputedStyle(dragImage);\n    const paddingTop = parseFloat(dragImageComputedStyle.paddingTop) || 0;\n    const paddingLeft = parseFloat(dragImageComputedStyle.paddingLeft) || 0;\n    const borderTop = parseFloat(dragImageComputedStyle.borderTopWidth) || 0;\n    const borderLeft = parseFloat(dragImageComputedStyle.borderLeftWidth) || 0;\n\n    const x =\n      dragImage.clientWidth - (event.offsetX + paddingLeft + borderLeft);\n    return {\n      x: x,\n      y: event.offsetY + paddingTop + borderTop,\n    };\n  };\n\n  onDragStart(event: DragEvent) {\n    this.lastDropEvent = null;\n\n    this.currentDragEffectMsg = '';\n    this.currentDraggableEvent = event;\n\n    this.snackBarService.dismiss();\n    this.snackBarService.open('Drag started!', undefined, { duration: 2000 });\n  }\n\n  onDragged($event: DragEvent, effect: string) {\n    this.currentDragEffectMsg = `Drag ended with effect \"${effect}\"!`;\n  }\n\n  onDragEnd(event: DragEvent) {\n    this.currentDraggableEvent = event;\n    this.snackBarService.dismiss();\n    this.snackBarService.open(\n      this.currentDragEffectMsg || `Drag ended!`,\n      undefined,\n      { duration: 2000 }\n    );\n  }\n\n  onDrop(event: DndDropEvent) {\n    this.lastDropEvent = event;\n  }\n}\n"
  },
  {
    "path": "projects/demo/src/app/tree/tree.component.html",
    "content": "<div class=\"container-fluid\">\n  <ng-template #recursiveList let-list>\n    <mat-list-item\n      class=\"dndPlaceholder border bg-opacity-25 mb-1\"\n      dndPlaceholderRef />\n\n    @for (item of list; track item) {\n      <div class=\"\">\n        <mat-list-item\n          [dndDraggable]=\"item\"\n          dndEffectAllowed=\"move\"\n          (dndMoved)=\"onDragged(item, list, 'move')\"\n          class=\"border bg-white ms-n3\">\n          <span matListItemTitle>{{ item.content }}</span>\n        </mat-list-item>\n        @if (item.children) {\n          <mat-list\n            (dndDrop)=\"onDrop($event, item.children)\"\n            class=\"d-flex flex-column bg-light pt-2 pb-0 ps-2\"\n            style=\"min-height: unset\"\n            dndDropzone\n            dndEffectAllowed=\"move\">\n            <ng-container\n              *ngTemplateOutlet=\"\n                recursiveList;\n                context: { $implicit: item.children }\n              \" />\n          </mat-list>\n        }\n      </div>\n    }\n  </ng-template>\n\n  <div class=\"row\">\n    <div class=\"col-12 offset-lg-2 col-lg-4\">\n      <mat-list\n        (dndDrop)=\"onDrop($event, draggableList)\"\n        class=\"d-flex flex-column bg-light gap-1\"\n        dndDropzone\n        dndEffectAllowed=\"move\">\n        <ng-container\n          *ngTemplateOutlet=\"\n            recursiveList;\n            context: { $implicit: draggableList }\n          \" />\n      </mat-list>\n    </div>\n    <div class=\"col-12 col-lg-4\">\n      <pre>{{ draggableList | json }}</pre>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "projects/demo/src/app/tree/tree.component.scss",
    "content": ".dndDraggingSource {\n  opacity: 0.5;\n  transform: scale(0.98);\n  pointer-events: none;\n\n  & > * {\n    pointer-events: none;\n  }\n}\n"
  },
  {
    "path": "projects/demo/src/app/tree/tree.component.ts",
    "content": "import { CommonModule } from '@angular/common';\nimport { Component } from '@angular/core';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport {\n  DndDraggableDirective,\n  DndDropEvent,\n  DndDropzoneDirective,\n  DndPlaceholderRefDirective,\n  DropEffect,\n} from 'ngx-drag-drop';\n\ninterface DraggableItem {\n  content: string;\n  children: DraggableItem[];\n}\n\n@Component({\n  selector: 'dnd-tree',\n  standalone: true,\n  imports: [\n    CommonModule,\n    DndDropzoneDirective,\n    DndPlaceholderRefDirective,\n    MatIconModule,\n    MatListModule,\n    DndDraggableDirective,\n  ],\n  templateUrl: './tree.component.html',\n  styleUrls: ['./tree.component.scss'],\n})\nexport default class TreeComponent {\n  draggableList: DraggableItem[] = [\n    {\n      content: 'Demo 1',\n      children: [\n        {\n          content: 'Nested 1',\n          children: [],\n        },\n        {\n          content: 'Nested 2',\n          children: [],\n        },\n        {\n          content: 'Nested 3',\n          children: [],\n        },\n      ],\n    },\n    {\n      content: 'Demo 2',\n      children: [],\n    },\n    {\n      content: 'Demo 3',\n      children: [],\n    },\n    {\n      content: 'Demo 4',\n      children: [],\n    },\n    {\n      content: 'Demo 5',\n      children: [],\n    },\n    {\n      content: 'Demo 6',\n      children: [],\n    },\n    {\n      content: 'Demo 7',\n      children: [],\n    },\n    {\n      content: 'Demo 8',\n      children: [],\n    },\n    {\n      content: 'Demo 9',\n      children: [],\n    },\n    {\n      content: 'Demo 10',\n      children: [\n        {\n          content: 'Nested 1',\n          children: [],\n        },\n        {\n          content: 'Nested 2',\n          children: [],\n        },\n        {\n          content: 'Nested 3',\n          children: [],\n        },\n      ],\n    },\n  ];\n\n  onDragged(item: any, list: any[], effect: DropEffect) {\n    if (effect === 'move') {\n      const index = list.indexOf(item);\n      list.splice(index, 1);\n    }\n  }\n\n  onDrop(event: DndDropEvent, list?: any[]) {\n    console.log('onDrop', event, list);\n\n    if (list && (event.dropEffect === 'copy' || event.dropEffect === 'move')) {\n      let index = event.index;\n\n      if (typeof index === 'undefined') {\n        index = list.length;\n      }\n\n      list.splice(index, 0, event.data);\n    }\n  }\n}\n"
  },
  {
    "path": "projects/demo/src/app/typed/typed.component.html",
    "content": "<div class=\"container-fluid\">\n  <div class=\"row\">\n    <div class=\"col\">\n      <pre>Fruits</pre>\n      <mat-list\n        (dndDrop)=\"onDrop($event, fruits)\"\n        class=\"dndList gap-1 flex-grow-1 d-flex flex-column bg-light rounded-1 shadow-sm\"\n        dndDropzone\n        dndEffectAllowed=\"move\">\n        <mat-list-item\n          class=\"dndPlaceholder border rounded-1 bg-danger bg-gradient\"\n          dndPlaceholderRef>\n        </mat-list-item>\n        @for (fruit of fruits; track trackByFruit(i, fruit); let i = $index) {\n          <mat-list-item\n            [dndDraggable]=\"fruit\"\n            [dndType]=\"fruit.type\"\n            (dndMoved)=\"onDragged(i, fruit, fruits)\"\n            class=\"border rounded-1 bg-white\"\n            dndEffectAllowed=\"move\">\n            <span matListItemTitle>{{ fruit.type }} {{ fruit.id }}</span>\n          </mat-list-item>\n        }\n      </mat-list>\n    </div>\n\n    <div class=\"col\">\n      <pre>Apples</pre>\n      <mat-list\n        [dndDropzone]=\"['apple']\"\n        (dndDrop)=\"onDrop($event, apples)\"\n        class=\"dndList gap-1 flex-grow-1 d-flex flex-column bg-light rounded-1 shadow-sm\"\n        dndEffectAllowed=\"move\">\n        <mat-list-item\n          class=\"dndPlaceholder border rounded-1 bg-success bg-gradient\"\n          dndPlaceholderRef>\n        </mat-list-item>\n        @for (apple of apples; track trackByFruit(i, apple); let i = $index) {\n          <mat-list-item\n            [dndDraggable]=\"apple\"\n            [dndType]=\"apple.type\"\n            (dndMoved)=\"onDragged(i, apple, apples)\"\n            class=\"border rounded-1 bg-white\"\n            dndEffectAllowed=\"move\">\n            <span matListItemTitle>{{ apple.type }} {{ apple.id }}</span>\n          </mat-list-item>\n        }\n      </mat-list>\n    </div>\n\n    <div class=\"col\">\n      <pre>Bananas</pre>\n      <mat-list\n        [dndDropzone]=\"['banana']\"\n        (dndDrop)=\"onDrop($event, bananas)\"\n        class=\"dndList gap-1 flex-grow-1 d-flex flex-column bg-light rounded-1 shadow-sm\"\n        dndEffectAllowed=\"move\">\n        <mat-list-item\n          class=\"dndPlaceholder border rounded-1 bg-warning bg-gradient\"\n          dndPlaceholderRef>\n        </mat-list-item>\n        @for (\n          banana of bananas;\n          track trackByFruit(i, banana);\n          let i = $index\n        ) {\n          <mat-list-item\n            [dndDraggable]=\"banana\"\n            [dndType]=\"banana.type\"\n            (dndMoved)=\"onDragged(i, banana, bananas)\"\n            class=\"border rounded-1 bg-white\"\n            dndEffectAllowed=\"move\">\n            <span matListItemTitle>{{ banana.type }} {{ banana.id }}</span>\n          </mat-list-item>\n        }\n      </mat-list>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "projects/demo/src/app/typed/typed.component.scss",
    "content": ":host {\n  display: block;\n  box-sizing: border-box;\n}\n\npre {\n  text-align: center;\n  padding: 8px;\n}\n\n.mat-mdc-list-item {\n  margin: 2px 0;\n  border: 1px solid gray;\n}\n\n.dndList {\n  transition: all 300ms ease;\n\n  padding: 8px;\n\n  overflow-y: auto;\n\n  &.dndDragover {\n    padding-top: 12px;\n    padding-bottom: 12px;\n\n    border-color: green;\n  }\n}\n\n.dndDragging {\n  border: 1px solid green;\n}\n\n.dndDraggingSource {\n  display: none;\n}\n\n.dndPlaceholder {\n  min-height: 48px;\n  border: 1px dashed green;\n  background-color: rgba(0, 0, 0, 0.1);\n}\n\npre {\n  white-space: pre-wrap;\n  word-wrap: break-word;\n}\n"
  },
  {
    "path": "projects/demo/src/app/typed/typed.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { MatListModule } from '@angular/material/list';\nimport {\n  DndDraggableDirective,\n  DndDropEvent,\n  DndDropzoneDirective,\n  DndPlaceholderRefDirective,\n} from 'ngx-drag-drop';\n\ntype Apple = 'apple';\ntype Banana = 'banana';\ntype FruitType = Apple | Banana;\n\ninterface Fruit {\n  id: number;\n  type: FruitType;\n}\n\nlet id = 0;\n\nfunction createFruit(type: FruitType) {\n  return {\n    id: id++,\n    type: type,\n  };\n}\n\nfunction range(start: number, end: number) {\n  return Array.from({ length: end - start + 1 }, (_, i) => i);\n}\n\n@Component({\n  selector: 'dnd-typed',\n  templateUrl: './typed.component.html',\n  styleUrls: ['./typed.component.scss'],\n  standalone: true,\n  imports: [\n    MatListModule,\n    DndDropzoneDirective,\n    DndPlaceholderRefDirective,\n    DndDraggableDirective,\n  ],\n})\nexport default class TypedComponent {\n  public fruits: Fruit[] = range(0, 100).map(_ => {\n    const randomFruitType: FruitType = Math.random() < 0.5 ? 'apple' : 'banana';\n    return createFruit(randomFruitType);\n  });\n\n  public apples: Fruit[] = range(0, 12).map(_ => {\n    return createFruit('apple');\n  });\n\n  public bananas: Fruit[] = range(0, 10).map(_ => {\n    return createFruit('banana');\n  });\n\n  trackByFruit(index: number, fruit: Fruit) {\n    return fruit;\n  }\n\n  onDragged(index: number, fruit: Fruit, list: Fruit[]) {\n    const removeIndex = list.indexOf(fruit);\n    console.log(\n      `onDragged ngFor-index=${index}, item=${fruit}, removeIndex=${removeIndex}, list=${list.length}`\n    );\n    list.splice(removeIndex, 1);\n  }\n\n  onDrop(event: DndDropEvent, list: Fruit[]) {\n    console.log('onDrop', event, list.length);\n    let index = event.index;\n    if (typeof index === 'undefined') {\n      index = list.length;\n    }\n    list.splice(index, 0, event.data);\n  }\n}\n"
  },
  {
    "path": "projects/demo/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "projects/demo/src/dragdroptouch.d.ts",
    "content": "declare module '@dragdroptouch/drag-drop-touch' {\n  export function enableDragDropTouch(\n    dragRoot?: Element,\n    dropRoot?: Element,\n    options?: Record<string, unknown>\n  ): void;\n}\n"
  },
  {
    "path": "projects/demo/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true,\n};\n"
  },
  {
    "path": "projects/demo/src/environments/environment.ts",
    "content": "// This file can be replaced during build by using the `fileReplacements` array.\n// `ng build` replaces `environment.ts` with `environment.prod.ts`.\n// The list of file replacements can be found in `angular.json`.\n\nexport const environment = {\n  production: false,\n};\n\n/*\n * For easier debugging in development mode, you can import the following file\n * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.\n *\n * This import should be commented out in production mode because it will have a negative impact\n * on performance if an error is thrown.\n */\n// import 'zone.js/plugins/zone-error';  // Included with Angular CLI.\n"
  },
  {
    "path": "projects/demo/src/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>NgxDragDrop</title>\n    <base href=\"/\" />\n    <meta content=\"width=device-width, initial-scale=1\" name=\"viewport\" />\n    <link href=\"favicon.ico\" rel=\"icon\" type=\"image/x-icon\" />\n    <link href=\"https://fonts.gstatic.com\" rel=\"preconnect\" />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap\"\n      rel=\"stylesheet\" />\n    <link\n      href=\"https://fonts.googleapis.com/icon?family=Material+Icons\"\n      rel=\"stylesheet\" />\n  </head>\n  <body class=\"mat-typography\">\n    <dnd-root></dnd-root>\n  </body>\n</html>\n"
  },
  {
    "path": "projects/demo/src/main.ts",
    "content": "import { enableProdMode, provideZoneChangeDetection } from '@angular/core';\nimport { platformBrowser } from '@angular/platform-browser';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowser()\n  .bootstrapModule(AppModule, {\n    applicationProviders: [provideZoneChangeDetection()],\n  })\n  .catch(err => console.error(err));\n"
  },
  {
    "path": "projects/demo/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes recent versions of Safari, Chrome (including\n * Opera), Edge on the desktop, and iOS and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\nimport { enableDragDropTouch } from '@dragdroptouch/drag-drop-touch';\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js'; // Included with Angular CLI.\n\nenableDragDropTouch();\n"
  },
  {
    "path": "projects/demo/src/styles.scss",
    "content": "@import 'bootstrap/scss/bootstrap.scss';\n\n.drag-handle {\n  border: 1px solid #ddd;\n  border-radius: 50%;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n\n  // Important because the material design would overwrite it in the mat-list\n  width: 28px !important;\n  height: 28px !important;\n}\n\n/* You can add global styles to this file, and also import other style files */\nhtml,\nbody {\n  margin: 0;\n\n  height: 100%;\n  width: 100%;\n\n  min-height: 100vh;\n\n  display: flex;\n  flex: 1;\n\n  background: #fafafa;\n}\n\n.layout-padding {\n  padding: 8px;\n}\n\n.layout-padding > * {\n  margin: 8px;\n}\n\n.scrollable {\n  -webkit-overflow-scrolling: touch;\n  overflow-x: auto;\n  overflow-y: auto;\n}\n\n[dndHandle],\n[draggable='true']:not(:has([dndHandle])) {\n  cursor: pointer;\n}\n\n[draggable].dndDraggableDisabled {\n  cursor: not-allowed;\n  opacity: 0.7;\n}\n\n[dndDropzone] {\n  // border: 1px red solid;\n  min-width: 200px;\n  min-height: 200px;\n}\n\n[dndDropzone].dndDropzoneDisabled {\n  cursor: no-drop;\n  opacity: 0.7;\n  border-color: gray;\n}\n\n[dndDropzone].dndDragover {\n  border-color: green;\n}\n\nhtml,\nbody {\n  height: 100%;\n}\n\nbody {\n  margin: 0;\n  font-family: Roboto, 'Helvetica Neue', sans-serif;\n}\n"
  },
  {
    "path": "projects/demo/tsconfig.app.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../out-tsc/app\",\n    \"types\": []\n  },\n  \"files\": [\"src/main.ts\", \"src/polyfills.ts\"],\n  \"include\": [\"src/**/*.d.ts\"]\n}\n"
  },
  {
    "path": "projects/demo/tsconfig.spec.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../out-tsc/spec\"\n  },\n  \"files\": [\"src/test.ts\", \"src/polyfills.ts\"],\n  \"include\": [\"src/**/*.spec.ts\", \"src/**/*.d.ts\"]\n}\n"
  },
  {
    "path": "projects/dnd/.browserslistrc",
    "content": "# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n\n# For the full list of supported browsers by the Angular framework, please see:\n# https://angular.io/guide/browser-support\n\n# You can see what browsers were selected by your queries by running:\n#   npx browserslist\n\nlast 1 Chrome version\nlast 1 Firefox version\nlast 2 Edge major versions\nlast 2 Safari major versions\nlast 2 iOS major versions\nFirefox ESR\n"
  },
  {
    "path": "projects/dnd/README.md",
    "content": "[![npm](https://img.shields.io/npm/v/ngx-drag-drop.svg)](https://www.npmjs.com/package/ngx-drag-drop)\n[![npm (next)](https://img.shields.io/npm/v/ngx-drag-drop/next.svg)](https://www.npmjs.com/package/ngx-drag-drop)\n[![NpmLicense](https://img.shields.io/npm/l/ngx-drag-drop.svg)](https://www.npmjs.com/package/ngx-drag-drop)\n[![GitHub issues](https://img.shields.io/github/issues/ChristofFritz/ngx-drag-drop.svg)](https://github.com/ChristofFritz/ngx-drag-drop/issues)\n[![Twitter](https://img.shields.io/twitter/url/https/github.com/ChristofFritz/ngx-drag-drop.svg?style=social)](https://twitter.com/intent/tweet?text=Angular%20drag%20and%20drop%20with%20ease:&url=https://github.com/ChristofFritz/ngx-drag-drop)\n\n# NgxDragDrop\n\n[_Demo_](https://christoffritz.github.io/ngx-drag-drop/) / [_StackBlitz Issue\nTemplate_](https://stackblitz.com/edit/ngx-drag-drop-issue-template)\n\n```sh\nnpm install ngx-drag-drop\n# or\npnpm add ngx-drag-drop\n```\n\n**Angular directives for declarative drag and drop using the HTML5 Drag-And-Drop API**\n\n- sortable lists by using placeholder element (vertical and horizontal)\n- nestable\n- dropzones optionally support external/native draggables (img, txt, file)\n- conditional drag/drop\n- typed drag/drop\n- utilize [EffectAllowed](https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/effectAllowed)\n- custom CSS classes\n- touch support by using a [polyfill](#touch-support)\n- [AOT](https://angular.io/guide/aot-compiler) compatible\n\nPort of [angular-drag-drop-lists](https://github.com/marceljuenemann/angular-drag-and-drop-lists) but without the lists :wink:\n\nThis has `dropzones` though :+1:\nThe idea is that the directive does not handle lists internally so the `dndDropzone` can be general purpose.\n\n## Angular Version Compatibility\n\nStarting with v13, the library major version matches the Angular major version.\n\n| Angular | ngx-drag-drop |\n| ------- | ------------- |\n| 21.x    | 21.x          |\n| 20.x    | 20.x          |\n| 19.x    | 19.x          |\n| 18.x    | 18.x          |\n| 17.x    | 17.x          |\n| 16.x    | 16.x          |\n| 15.x    | 15.x          |\n| 14.x    | 14.x          |\n| 13.x    | 13.x          |\n\nFor older Angular versions (v4–v12), use ngx-drag-drop v2.x.\n\n## Usage\n\n`app.component.html`\n\n```HTML\n<!--a draggable element-->\n<div\n  [dndDraggable]=\"draggable.data\"\n  [dndEffectAllowed]=\"draggable.effectAllowed\"\n  [dndDisableIf]=\"draggable.disable\"\n  (dndStart)=\"onDragStart($event)\"\n  (dndCopied)=\"onDraggableCopied($event)\"\n  (dndLinked)=\"onDraggableLinked($event)\"\n  (dndMoved)=\"onDraggableMoved($event)\"\n  (dndCanceled)=\"onDragCanceled($event)\"\n  (dndEnd)=\"onDragEnd($event)\">\n\n  <!--if [dndHandle] is used inside dndDraggable drag can only start from the handle-->\n  <div\n    *ngIf=\"draggable.handle\"\n    dndHandle>HANDLE\n  </div>\n\n  draggable ({{draggable.effectAllowed}}) <span [hidden]=\"!draggable.disable\">DISABLED</span>\n\n  <!--optionally select a child element as drag image-->\n  <div dndDragImageRef>DRAG_IMAGE</div>\n\n</div>\n\n<!--a dropzone-->\n<!--to allow dropping content that is not [dndDraggable] set dndAllowExternal to true-->\n<section\n  dndDropzone\n  (dndDragover)=\"onDragover($event)\"\n  (dndDrop)=\"onDrop($event)\">\n\n  dropzone\n\n  <!--optional placeholder element for dropzone-->\n  <!--will be removed from DOM on init-->\n  <div\n    style=\"border: 1px orangered solid; border-radius: 5px; padding: 15px;\"\n    dndPlaceholderRef>\n    placeholder\n  </div>\n\n</section>\n```\n\n`app.component`\n\n```JS\nimport {Component} from '@angular/core';\n\nimport {DndDropEvent} from 'ngx-drag-drop';\n\n@Component()\nexport class AppComponent {\n\n  draggable = {\n    // note that data is handled with JSON.stringify/JSON.parse\n    // only set simple data or POJO's as methods will be lost\n    data: \"myDragData\",\n    effectAllowed: \"all\",\n    disable: false,\n    handle: false\n  };\n\n  onDragStart(event: DragEvent) {\n\n    console.log(\"drag started\", JSON.stringify(event, null, 2));\n  }\n\n  onDragEnd(event: DragEvent) {\n\n    console.log(\"drag ended\", JSON.stringify(event, null, 2));\n  }\n\n  onDraggableCopied(event: DragEvent) {\n\n    console.log(\"draggable copied\", JSON.stringify(event, null, 2));\n  }\n\n  onDraggableLinked(event: DragEvent) {\n\n    console.log(\"draggable linked\", JSON.stringify(event, null, 2));\n  }\n\n  onDraggableMoved(event: DragEvent) {\n\n    console.log(\"draggable moved\", JSON.stringify(event, null, 2));\n  }\n\n  onDragCanceled(event: DragEvent) {\n\n    console.log(\"drag cancelled\", JSON.stringify(event, null, 2));\n  }\n\n  onDragover(event: DragEvent) {\n\n    console.log(\"dragover\", JSON.stringify(event, null, 2));\n  }\n\n  onDrop(event: DndDropEvent) {\n\n    console.log(\"dropped\", JSON.stringify(event, null, 2));\n  }\n}\n```\n\n`app.module`\n\n```JS\nimport {BrowserModule} from '@angular/platform-browser';\nimport {NgModule} from '@angular/core';\n\nimport {DndModule} from 'ngx-drag-drop';\n\nimport {AppComponent} from './app.component';\n\n@NgModule({\n  declarations: [\n    AppComponent\n  ],\n  imports: [\n    BrowserModule,\n    DndModule\n  ],\n  providers: [],\n  bootstrap: [AppComponent]\n})\nexport class AppModule {\n}\n```\n\n## API\n\n```TS\n// https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/dropEffect\nexport type DropEffect = \"move\" | \"copy\" | \"link\" | \"none\";\n\n// https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/effectAllowed\nexport type EffectAllowed = DropEffect | \"copyMove\" | \"copyLink\" | \"linkMove\" | \"all\";\n```\n\n```TS\nexport type DndDragImageOffsetFunction = ( event:DragEvent, dragImage:Element ) => { x:number, y:number };\n\n@Directive( {\n  selector: \"[dndDraggable]\"\n} )\nexport declare class DndDraggableDirective {\n\n    // the data attached to the drag\n    dndDraggable: any;\n\n    // the allowed drop effect\n    dndEffectAllowed: EffectAllowed;\n\n    // optionally set the type of dragged data to restrict dropping on compatible dropzones\n    dndType?: string;\n\n    // conditionally disable the draggability\n    dndDisableIf: boolean;\n    dndDisableDragIf: boolean;\n\n    // set a custom class that is applied while dragging\n    dndDraggingClass: string = \"dndDragging\";\n\n    // set a custom class that is applied to only the src element while dragging\n    dndDraggingSourceClass: string = \"dndDraggingSource\";\n\n    // set the class that is applied when draggable is disabled by [dndDisableIf]\n    dndDraggableDisabledClass = \"dndDraggableDisabled\";\n\n    // enables to set a function for calculating custom dragimage offset\n    dndDragImageOffsetFunction:DndDragImageOffsetFunction = calculateDragImageOffset;\n\n    // emits on drag start\n    readonly dndStart: EventEmitter<DragEvent>;\n\n    // emits on drag\n    readonly dndDrag: EventEmitter<DragEvent>;\n\n    // emits on drag end\n    readonly dndEnd: EventEmitter<DragEvent>;\n\n    // emits when the dragged item has been dropped with effect \"move\"\n    readonly dndMoved: EventEmitter<DragEvent>;\n\n    // emits when the dragged item has been dropped with effect \"copy\"\n    readonly dndCopied: EventEmitter<DragEvent>;\n\n    // emits when the dragged item has been dropped with effect \"link\"\n    readonly dndLinked: EventEmitter<DragEvent>;\n\n    // emits when the drag is canceled\n    readonly dndCanceled: EventEmitter<DragEvent>;\n}\n```\n\n```TS\nexport interface DndDropEvent {\n\n    // the original drag event\n    event: DragEvent;\n\n    // the actual drop effect\n    dropEffect: DropEffect;\n\n    // true if the drag did not origin from a [dndDraggable]\n    isExternal:boolean;\n\n    // the data set on the [dndDraggable] that started the drag\n    // for external drags use the event property which contains the original drop event as this will be undefined\n    data?: any;\n\n    // the index where the draggable was dropped in a dropzone\n    // set only when using a placeholder\n    index?: number;\n\n    // if the dndType input on dndDraggable was set\n    // it will be transported here\n    type?: any;\n}\n\n@Directive( {\n  selector: \"[dndDropzone]\"\n} )\nexport declare class DndDropzoneDirective {\n\n    // optionally restrict the allowed types\n    dndDropzone?: string[];\n\n    // set the allowed drop effect\n    dndEffectAllowed: EffectAllowed;\n\n    // conditionally disable the dropzone\n    dndDisableIf: boolean;\n    dndDisableDropIf: boolean;\n\n    // if draggables that are not [dndDraggable] are allowed to be dropped\n    // set to true if dragged text, images or files should be handled\n    dndAllowExternal: boolean;\n\n    // if its a horizontal list this influences how the placeholder position\n    // is calculated\n    dndHorizontal: boolean;\n\n    // set the class applied to the dropzone\n    // when a draggable is dragged over it\n    dndDragoverClass: string = \"dndDragover\";\n\n    // set the class applied to the dropzone\n    // when the dropzone is disabled by [dndDisableIf]\n    dndDropzoneDisabledClass = \"dndDropzoneDisabled\";\n\n    // emits when a draggable is dragged over the dropzone\n    readonly dndDragover: EventEmitter<DragEvent>;\n\n    // emits on successful drop\n    readonly dndDrop: EventEmitter<DndDropEvent>;\n}\n```\n\n## Touch support\n\nInstall the `mobile-drag-drop` module available on npm.\n\nAdd the following lines to your js code\n\n```JS\nimport { polyfill } from 'mobile-drag-drop';\n// optional import of scroll behaviour\nimport { scrollBehaviourDragImageTranslateOverride } from \"mobile-drag-drop/scroll-behaviour\";\n\npolyfill( {\n  // use this to make use of the scroll behaviour\n  dragImageTranslateOverride: scrollBehaviourDragImageTranslateOverride\n} );\n\n// workaround to make scroll prevent work in iOS Safari > 10\ntry {\n  window.addEventListener( \"touchmove\", function() { }, { passive: false } );\n}\ncatch(e){}\n```\n\nFor more info on the polyfill check it out on GitHub\nhttps://github.com/timruffles/mobile-drag-drop\n\n## Known issues\n\n### Firefox\n\n- Beware that Firefox does not support dragging on `<button>` elements.\n  - `<button [dndDraggable]>` and `<button [dndHandler]>` won't work.\n  - See https://bugzilla.mozilla.org/show_bug.cgi?id=568313\n\n## Why?\n\nHTML Drag-And-Drop API implementations are not behaving the same way across browsers.\n\nThe directives contained in this module enable declarative drag and drop that \"just works\" across browsers in a consistent way.\n\nCredits go to the author and contributors of [angular-drag-drop-lists](https://github.com/marceljuenemann/angular-drag-and-drop-lists).\n"
  },
  {
    "path": "projects/dnd/ng-package.json",
    "content": "{\n  \"$schema\": \"../../node_modules/ng-packagr/ng-package.schema.json\",\n  \"dest\": \"../../dist/ngx-drag-drop\",\n  \"lib\": {\n    \"entryFile\": \"src/public-api.ts\"\n  }\n}\n"
  },
  {
    "path": "projects/dnd/package.json",
    "content": "{\n  \"name\": \"ngx-drag-drop\",\n  \"version\": \"21.0.6\",\n  \"description\": \"Angular directives using the native HTML Drag And Drop API\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/ChristofFritz/ngx-drag-drop.git\"\n  },\n  \"homepage\": \"https://christoffritz.github.io/ngx-drag-drop/\",\n  \"author\": {\n    \"name\": \"Christof Fritz\",\n    \"email\": \"npm@christof.me\"\n  },\n  \"license\": \"BSD-3-Clause\",\n  \"keywords\": [\n    \"angular\",\n    \"html\",\n    \"drag\",\n    \"drop\",\n    \"dragdrop\",\n    \"dragndrop\",\n    \"dnd\",\n    \"directive\",\n    \"touch\"\n  ],\n  \"peerDependencies\": {\n    \"@angular/common\": \">=21.0.0\",\n    \"@angular/core\": \">=21.0.0\"\n  },\n  \"dependencies\": {\n    \"tslib\": \"^2.3.0\"\n  }\n}\n"
  },
  {
    "path": "projects/dnd/src/lib/dnd-draggable.directive.spec.ts",
    "content": "import { Component, DebugElement } from '@angular/core';\nimport { ComponentFixture, TestBed } from '@angular/core/testing';\nimport { By } from '@angular/platform-browser';\nimport { describe, it, expect, beforeEach } from 'vitest';\nimport {\n  DndDraggableDirective,\n  DndDragImageRefDirective,\n} from './dnd-draggable.directive';\nimport { DndHandleDirective } from './dnd-handle.directive';\nimport { endDrag, dndState } from './dnd-state';\n\n@Component({\n  standalone: true,\n  imports: [DndDraggableDirective],\n  template: `<div [dndDraggable]=\"'testData'\" [dndEffectAllowed]=\"'copyMove'\">\n    drag me\n  </div>`,\n})\nclass BasicDraggableHost {}\n\n@Component({\n  standalone: true,\n  imports: [DndDraggableDirective],\n  template: `<div [dndDraggable]=\"'data'\">drag me</div>`,\n})\nclass DisabledDraggableHost {}\n\n@Component({\n  standalone: true,\n  imports: [DndDraggableDirective, DndHandleDirective],\n  template: `\n    <div [dndDraggable]=\"'data'\">\n      <div dndHandle>handle</div>\n      content\n    </div>\n  `,\n})\nclass HandleDraggableHost {}\n\n@Component({\n  standalone: true,\n  imports: [DndDraggableDirective, DndDragImageRefDirective],\n  template: `\n    <div [dndDraggable]=\"'data'\">\n      <div dndDragImageRef>custom image</div>\n      content\n    </div>\n  `,\n})\nclass DragImageHost {}\n\ndescribe('DndDraggableDirective', () => {\n  let fixture: ComponentFixture<BasicDraggableHost>;\n  let draggableEl: DebugElement;\n\n  beforeEach(async () => {\n    endDrag();\n    await TestBed.configureTestingModule({\n      imports: [BasicDraggableHost],\n    }).compileComponents();\n\n    fixture = TestBed.createComponent(BasicDraggableHost);\n    fixture.detectChanges();\n    draggableEl = fixture.debugElement.query(\n      By.directive(DndDraggableDirective)\n    );\n  });\n\n  it('should set draggable attribute to true', () => {\n    expect(draggableEl.nativeElement.getAttribute('draggable')).toBe('true');\n  });\n\n  it('should have the directive instance', () => {\n    const directive = draggableEl.injector.get(DndDraggableDirective);\n    expect(directive).toBeTruthy();\n    expect(directive.dndEffectAllowed).toBe('copyMove');\n  });\n});\n\ndescribe('DndDraggableDirective - disabled', () => {\n  let fixture: ComponentFixture<DisabledDraggableHost>;\n  let directive: DndDraggableDirective;\n  let draggableEl: DebugElement;\n\n  beforeEach(async () => {\n    endDrag();\n    await TestBed.configureTestingModule({\n      imports: [DisabledDraggableHost],\n    }).compileComponents();\n\n    fixture = TestBed.createComponent(DisabledDraggableHost);\n    fixture.detectChanges();\n    draggableEl = fixture.debugElement.query(\n      By.directive(DndDraggableDirective)\n    );\n    directive = draggableEl.injector.get(DndDraggableDirective);\n  });\n\n  it('should set draggable to false when disabled', () => {\n    directive.dndDisableIf = true;\n    expect(directive.draggable).toBe(false);\n    expect(\n      draggableEl.nativeElement.classList.contains('dndDraggableDisabled')\n    ).toBe(true);\n  });\n\n  it('should add disabled class when disabled', () => {\n    directive.dndDisableIf = true;\n    expect(\n      draggableEl.nativeElement.classList.contains('dndDraggableDisabled')\n    ).toBe(true);\n  });\n\n  it('should remove disabled class when re-enabled', () => {\n    directive.dndDisableIf = true;\n    directive.dndDisableIf = false;\n    expect(directive.draggable).toBe(true);\n    expect(\n      draggableEl.nativeElement.classList.contains('dndDraggableDisabled')\n    ).toBe(false);\n  });\n});\n\ndescribe('DndDraggableDirective - handle', () => {\n  let fixture: ComponentFixture<HandleDraggableHost>;\n\n  beforeEach(async () => {\n    endDrag();\n    await TestBed.configureTestingModule({\n      imports: [HandleDraggableHost],\n    }).compileComponents();\n\n    fixture = TestBed.createComponent(HandleDraggableHost);\n    fixture.detectChanges();\n  });\n\n  it('should register the handle', () => {\n    const draggableEl = fixture.debugElement.query(\n      By.directive(DndDraggableDirective)\n    );\n    const directive = draggableEl.injector.get(DndDraggableDirective);\n    // The handle registers itself on init — verify via the private field\n    expect((directive as any).dndHandle).toBeTruthy();\n  });\n});\n\ndescribe('DndDraggableDirective - drag image', () => {\n  let fixture: ComponentFixture<DragImageHost>;\n\n  beforeEach(async () => {\n    endDrag();\n    await TestBed.configureTestingModule({\n      imports: [DragImageHost],\n    }).compileComponents();\n\n    fixture = TestBed.createComponent(DragImageHost);\n    fixture.detectChanges();\n  });\n\n  it('should register the drag image element', () => {\n    const draggableEl = fixture.debugElement.query(\n      By.directive(DndDraggableDirective)\n    );\n    const directive = draggableEl.injector.get(DndDraggableDirective);\n    expect((directive as any).dndDragImageElementRef).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "projects/dnd/src/lib/dnd-draggable.directive.ts",
    "content": "import {\n  AfterViewInit,\n  Directive,\n  ElementRef,\n  EventEmitter,\n  forwardRef,\n  HostBinding,\n  HostListener,\n  inject,\n  Input,\n  NgZone,\n  OnDestroy,\n  OnInit,\n  Output,\n  Renderer2,\n} from '@angular/core';\nimport { DndHandleDirective } from './dnd-handle.directive';\nimport { dndState, endDrag, startDrag } from './dnd-state';\nimport { EffectAllowed } from './dnd-types';\nimport {\n  calculateDragImageOffset,\n  DndDragImageOffsetFunction,\n  DndEvent,\n  setDragData,\n  setDragImage,\n} from './dnd-utils';\n\n@Directive({ selector: '[dndDragImageRef]', standalone: true })\nexport class DndDragImageRefDirective implements OnInit {\n  dndDraggableDirective = inject(forwardRef(() => DndDraggableDirective));\n  elementRef: ElementRef<HTMLElement> = inject(ElementRef);\n\n  ngOnInit() {\n    this.dndDraggableDirective.registerDragImage(this.elementRef);\n  }\n}\n\n@Directive({ selector: '[dndDraggable]', standalone: true })\nexport class DndDraggableDirective implements AfterViewInit, OnDestroy {\n  @Input() dndDraggable: any;\n  @Input() dndEffectAllowed: EffectAllowed = 'copy';\n  @Input() dndType?: string;\n  @Input() dndDraggingClass = 'dndDragging';\n  @Input() dndDraggingSourceClass = 'dndDraggingSource';\n  @Input() dndDraggableDisabledClass = 'dndDraggableDisabled';\n  @Input() dndDragImageOffsetFunction: DndDragImageOffsetFunction =\n    calculateDragImageOffset;\n\n  @Output() readonly dndStart: EventEmitter<DragEvent> =\n    new EventEmitter<DragEvent>();\n  @Output() readonly dndDrag: EventEmitter<DragEvent> =\n    new EventEmitter<DragEvent>();\n  @Output() readonly dndEnd: EventEmitter<DragEvent> =\n    new EventEmitter<DragEvent>();\n  @Output() readonly dndMoved: EventEmitter<DragEvent> =\n    new EventEmitter<DragEvent>();\n  @Output() readonly dndCopied: EventEmitter<DragEvent> =\n    new EventEmitter<DragEvent>();\n  @Output() readonly dndLinked: EventEmitter<DragEvent> =\n    new EventEmitter<DragEvent>();\n  @Output() readonly dndCanceled: EventEmitter<DragEvent> =\n    new EventEmitter<DragEvent>();\n\n  @HostBinding('attr.draggable') draggable = true;\n\n  private dndHandle?: DndHandleDirective;\n  private dndDragImageElementRef?: ElementRef;\n  private dragImage: Element | undefined;\n  private isDragStarted: boolean = false;\n\n  private elementRef: ElementRef<HTMLElement> = inject(ElementRef);\n  private renderer = inject(Renderer2);\n  private ngZone = inject(NgZone);\n\n  @Input() set dndDisableIf(value: boolean) {\n    this.draggable = !value;\n\n    if (this.draggable) {\n      this.renderer.removeClass(\n        this.elementRef.nativeElement,\n        this.dndDraggableDisabledClass\n      );\n    } else {\n      this.renderer.addClass(\n        this.elementRef.nativeElement,\n        this.dndDraggableDisabledClass\n      );\n    }\n  }\n\n  @Input() set dndDisableDragIf(value: boolean) {\n    this.dndDisableIf = value;\n  }\n\n  ngAfterViewInit(): void {\n    this.ngZone.runOutsideAngular(() => {\n      this.elementRef.nativeElement.addEventListener(\n        'drag',\n        this.dragEventHandler\n      );\n    });\n  }\n\n  ngOnDestroy(): void {\n    this.elementRef.nativeElement.removeEventListener(\n      'drag',\n      this.dragEventHandler\n    );\n    if (this.isDragStarted) {\n      endDrag();\n    }\n  }\n\n  @HostListener('dragstart', ['$event']) onDragStart(event: DndEvent): boolean {\n    if (!this.draggable) {\n      return false;\n    }\n\n    // check if there is dnd handle and if the dnd handle was used to start the drag\n    if (this.dndHandle != null && event._dndUsingHandle == null) {\n      event.stopPropagation();\n      return false;\n    }\n\n    // initialize global state\n    startDrag(event, this.dndEffectAllowed, this.dndType);\n\n    this.isDragStarted = true;\n\n    setDragData(\n      event,\n      { data: this.dndDraggable, type: this.dndType },\n      dndState.effectAllowed!\n    );\n\n    this.dragImage = this.determineDragImage();\n\n    // set dragging css class prior to setDragImage so styles are applied before\n    // TODO breaking change: add class to elementRef rather than drag image which could be another element\n    this.renderer.addClass(this.dragImage, this.dndDraggingClass);\n\n    // set custom dragimage if present\n    // set dragimage if drag is started from dndHandle\n    if (this.dndDragImageElementRef != null || event._dndUsingHandle != null) {\n      setDragImage(event, this.dragImage, this.dndDragImageOffsetFunction);\n    }\n\n    // add dragging source css class on first drag event\n    const unregister = this.renderer.listen(\n      this.elementRef.nativeElement,\n      'drag',\n      () => {\n        this.renderer.addClass(\n          this.elementRef.nativeElement,\n          this.dndDraggingSourceClass\n        );\n        unregister();\n      }\n    );\n\n    this.dndStart.emit(event);\n\n    event.stopPropagation();\n\n    setTimeout(() => {\n      if (this.isDragStarted) {\n        this.renderer.setStyle(this.dragImage, 'pointer-events', 'none');\n      }\n    }, 100);\n\n    return true;\n  }\n\n  onDrag(event: DragEvent) {\n    this.dndDrag.emit(event);\n  }\n\n  @HostListener('dragend', ['$event']) onDragEnd(event: DragEvent) {\n    if (!this.draggable || !this.isDragStarted) {\n      return;\n    }\n    // get drop effect from custom stored state as its not reliable across browsers\n    const dropEffect = dndState.dropEffect;\n\n    this.renderer.setStyle(this.dragImage, 'pointer-events', 'unset');\n\n    let dropEffectEmitter: EventEmitter<DragEvent>;\n\n    switch (dropEffect) {\n      case 'copy':\n        dropEffectEmitter = this.dndCopied;\n        break;\n\n      case 'link':\n        dropEffectEmitter = this.dndLinked;\n        break;\n\n      case 'move':\n        dropEffectEmitter = this.dndMoved;\n        break;\n\n      default:\n        dropEffectEmitter = this.dndCanceled;\n        break;\n    }\n\n    dropEffectEmitter.emit(event);\n    this.dndEnd.emit(event);\n\n    // reset global state\n    endDrag();\n\n    this.isDragStarted = false;\n\n    this.renderer.removeClass(this.dragImage, this.dndDraggingClass);\n\n    // IE9 special hammering\n    window.setTimeout(() => {\n      this.renderer.removeClass(\n        this.elementRef.nativeElement,\n        this.dndDraggingSourceClass\n      );\n    }, 0);\n\n    event.stopPropagation();\n  }\n\n  registerDragHandle(handle: DndHandleDirective | undefined) {\n    this.dndHandle = handle;\n  }\n\n  registerDragImage(elementRef: ElementRef | undefined) {\n    this.dndDragImageElementRef = elementRef;\n  }\n\n  private readonly dragEventHandler: (event: DragEvent) => void = (\n    event: DragEvent\n  ) => this.onDrag(event);\n\n  private determineDragImage(): Element {\n    // evaluate custom drag image existence\n    if (typeof this.dndDragImageElementRef !== 'undefined') {\n      return this.dndDragImageElementRef.nativeElement as Element;\n    } else {\n      return this.elementRef.nativeElement;\n    }\n  }\n}\n"
  },
  {
    "path": "projects/dnd/src/lib/dnd-dropzone.directive.spec.ts",
    "content": "import { Component, DebugElement } from '@angular/core';\nimport { ComponentFixture, TestBed } from '@angular/core/testing';\nimport { By } from '@angular/platform-browser';\nimport { describe, it, expect, beforeEach } from 'vitest';\nimport {\n  DndDropzoneDirective,\n  DndPlaceholderRefDirective,\n} from './dnd-dropzone.directive';\nimport { endDrag } from './dnd-state';\n\n@Component({\n  standalone: true,\n  imports: [DndDropzoneDirective],\n  template: `<div dndDropzone>drop here</div>`,\n})\nclass BasicDropzoneHost {}\n\n@Component({\n  standalone: true,\n  imports: [DndDropzoneDirective],\n  template: `<div dndDropzone>drop here</div>`,\n})\nclass DisabledDropzoneHost {}\n\n@Component({\n  standalone: true,\n  imports: [DndDropzoneDirective, DndPlaceholderRefDirective],\n  template: `\n    <div dndDropzone>\n      <div dndPlaceholderRef>placeholder</div>\n      <div class=\"item\">item 1</div>\n    </div>\n  `,\n})\nclass PlaceholderDropzoneHost {}\n\n@Component({\n  standalone: true,\n  imports: [DndDropzoneDirective],\n  template: `<div [dndDropzone]=\"['typeA', 'typeB']\">typed drop</div>`,\n})\nclass TypedDropzoneHost {}\n\ndescribe('DndDropzoneDirective', () => {\n  let fixture: ComponentFixture<BasicDropzoneHost>;\n  let dropzoneEl: DebugElement;\n\n  beforeEach(async () => {\n    endDrag();\n    await TestBed.configureTestingModule({\n      imports: [BasicDropzoneHost],\n    }).compileComponents();\n\n    fixture = TestBed.createComponent(BasicDropzoneHost);\n    fixture.detectChanges();\n    dropzoneEl = fixture.debugElement.query(By.directive(DndDropzoneDirective));\n  });\n\n  it('should have the directive instance', () => {\n    const directive = dropzoneEl.injector.get(DndDropzoneDirective);\n    expect(directive).toBeTruthy();\n  });\n\n  it('should have default dndEffectAllowed', () => {\n    const directive = dropzoneEl.injector.get(DndDropzoneDirective);\n    expect(directive.dndEffectAllowed).toBe('uninitialized');\n  });\n\n  it('should have default dndDragoverClass', () => {\n    const directive = dropzoneEl.injector.get(DndDropzoneDirective);\n    expect(directive.dndDragoverClass).toBe('dndDragover');\n  });\n});\n\ndescribe('DndDropzoneDirective - disabled', () => {\n  let fixture: ComponentFixture<DisabledDropzoneHost>;\n  let directive: DndDropzoneDirective;\n  let dropzoneEl: DebugElement;\n\n  beforeEach(async () => {\n    endDrag();\n    await TestBed.configureTestingModule({\n      imports: [DisabledDropzoneHost],\n    }).compileComponents();\n\n    fixture = TestBed.createComponent(DisabledDropzoneHost);\n    fixture.detectChanges();\n    dropzoneEl = fixture.debugElement.query(By.directive(DndDropzoneDirective));\n    directive = dropzoneEl.injector.get(DndDropzoneDirective);\n  });\n\n  it('should add disabled class when disabled', () => {\n    directive.dndDisableIf = true;\n    fixture.detectChanges();\n    expect(\n      dropzoneEl.nativeElement.classList.contains('dndDropzoneDisabled')\n    ).toBe(true);\n  });\n\n  it('should remove disabled class when re-enabled', () => {\n    directive.dndDisableIf = true;\n    fixture.detectChanges();\n    directive.dndDisableIf = false;\n    fixture.detectChanges();\n    expect(\n      dropzoneEl.nativeElement.classList.contains('dndDropzoneDisabled')\n    ).toBe(false);\n  });\n});\n\ndescribe('DndDropzoneDirective - placeholder', () => {\n  let fixture: ComponentFixture<PlaceholderDropzoneHost>;\n  let dropzoneEl: DebugElement;\n\n  beforeEach(async () => {\n    endDrag();\n    await TestBed.configureTestingModule({\n      imports: [PlaceholderDropzoneHost],\n    }).compileComponents();\n\n    fixture = TestBed.createComponent(PlaceholderDropzoneHost);\n    fixture.detectChanges();\n    dropzoneEl = fixture.debugElement.query(By.directive(DndDropzoneDirective));\n  });\n\n  it('should remove placeholder from DOM on init', () => {\n    const placeholder = dropzoneEl.nativeElement.querySelector(\n      '[dndPlaceholderRef]'\n    );\n    expect(placeholder).toBeNull();\n  });\n});\n\ndescribe('DndDropzoneDirective - typed', () => {\n  let fixture: ComponentFixture<TypedDropzoneHost>;\n  let dropzoneEl: DebugElement;\n\n  beforeEach(async () => {\n    endDrag();\n    await TestBed.configureTestingModule({\n      imports: [TypedDropzoneHost],\n    }).compileComponents();\n\n    fixture = TestBed.createComponent(TypedDropzoneHost);\n    fixture.detectChanges();\n    dropzoneEl = fixture.debugElement.query(By.directive(DndDropzoneDirective));\n  });\n\n  it('should accept typed dropzone input', () => {\n    const directive = dropzoneEl.injector.get(DndDropzoneDirective);\n    expect(directive.dndDropzone).toEqual(['typeA', 'typeB']);\n  });\n});\n"
  },
  {
    "path": "projects/dnd/src/lib/dnd-dropzone.directive.ts",
    "content": "import {\n  AfterViewInit,\n  ContentChild,\n  Directive,\n  ElementRef,\n  EventEmitter,\n  HostListener,\n  Input,\n  NgZone,\n  OnDestroy,\n  OnInit,\n  Output,\n  Renderer2,\n} from '@angular/core';\nimport {\n  getDndType,\n  getDropEffect,\n  isExternalDrag,\n  setDropEffect,\n} from './dnd-state';\nimport { DropEffect, EffectAllowed } from './dnd-types';\nimport {\n  DndEvent,\n  DragDropData,\n  getDirectChildElement,\n  getDropData,\n  shouldPositionPlaceholderBeforeElement,\n} from './dnd-utils';\n\nexport interface DndDropEvent {\n  event: DragEvent;\n  dropEffect: DropEffect;\n  isExternal: boolean;\n  data?: any;\n  index?: number;\n  type?: any;\n}\n\n@Directive({ selector: '[dndPlaceholderRef]', standalone: true })\nexport class DndPlaceholderRefDirective implements OnInit {\n  constructor(public readonly elementRef: ElementRef<HTMLElement>) {}\n\n  ngOnInit() {\n    // placeholder has to be \"invisible\" to the cursor, or it would interfere with the dragover detection for the same dropzone\n    this.elementRef.nativeElement.style.pointerEvents = 'none';\n  }\n}\n\n@Directive({ selector: '[dndDropzone]', standalone: true })\nexport class DndDropzoneDirective implements AfterViewInit, OnDestroy {\n  @Input() dndDropzone?: string[] | '' = '';\n\n  @Input() dndEffectAllowed: EffectAllowed = 'uninitialized';\n\n  @Input() dndAllowExternal: boolean = false;\n\n  @Input() dndHorizontal: boolean = false;\n\n  @Input() dndDragoverClass: string = 'dndDragover';\n\n  @Input() dndDropzoneDisabledClass = 'dndDropzoneDisabled';\n\n  @Output() readonly dndDragover: EventEmitter<DragEvent> =\n    new EventEmitter<DragEvent>();\n\n  @Output() readonly dndDrop: EventEmitter<DndDropEvent> =\n    new EventEmitter<DndDropEvent>();\n\n  @ContentChild(DndPlaceholderRefDirective)\n  private readonly dndPlaceholderRef?: DndPlaceholderRefDirective;\n\n  private placeholder: Element | null = null;\n\n  private disabled: boolean = false;\n\n  private enterCount: number = 0;\n\n  constructor(\n    private ngZone: NgZone,\n    private elementRef: ElementRef,\n    private renderer: Renderer2\n  ) {}\n\n  @Input() set dndDisableIf(value: boolean) {\n    this.disabled = value;\n\n    if (this.disabled) {\n      this.renderer.addClass(\n        this.elementRef.nativeElement,\n        this.dndDropzoneDisabledClass\n      );\n    } else {\n      this.renderer.removeClass(\n        this.elementRef.nativeElement,\n        this.dndDropzoneDisabledClass\n      );\n    }\n  }\n\n  @Input() set dndDisableDropIf(value: boolean) {\n    this.dndDisableIf = value;\n  }\n\n  ngAfterViewInit(): void {\n    this.placeholder = this.tryGetPlaceholder();\n\n    this.removePlaceholderFromDOM();\n\n    this.ngZone.runOutsideAngular(() => {\n      this.elementRef.nativeElement.addEventListener(\n        'dragenter',\n        this.dragEnterEventHandler\n      );\n      this.elementRef.nativeElement.addEventListener(\n        'dragover',\n        this.dragOverEventHandler\n      );\n      this.elementRef.nativeElement.addEventListener(\n        'dragleave',\n        this.dragLeaveEventHandler\n      );\n    });\n  }\n\n  ngOnDestroy(): void {\n    this.elementRef.nativeElement.removeEventListener(\n      'dragenter',\n      this.dragEnterEventHandler\n    );\n    this.elementRef.nativeElement.removeEventListener(\n      'dragover',\n      this.dragOverEventHandler\n    );\n    this.elementRef.nativeElement.removeEventListener(\n      'dragleave',\n      this.dragLeaveEventHandler\n    );\n  }\n\n  onDragEnter(event: DndEvent) {\n    this.enterCount++;\n\n    // check if another dropzone is activated\n    if (event._dndDropzoneActive === true) {\n      this.removePlaceholderFromDOM();\n      this.renderer.removeClass(\n        this.elementRef.nativeElement,\n        this.dndDragoverClass\n      );\n      return;\n    }\n\n    // set as active if the target element is inside this dropzone\n    if (event._dndDropzoneActive == null) {\n      if (this.elementRef.nativeElement.contains(event.target)) {\n        event._dndDropzoneActive = true;\n      }\n    }\n\n    // check if this drag event is allowed to drop on this dropzone\n    const type = getDndType(event);\n    if (!this.isDropAllowed(type)) {\n      return;\n    }\n\n    // allow the dragenter\n    event.preventDefault();\n  }\n\n  onDragOver(event: DragEvent) {\n    // With nested dropzones, we want to ignore this event if a child dropzone\n    // has already handled a dragover.  Historically, event.stopPropagation() was\n    // used to prevent this bubbling, but that prevents any dragovers outside the\n    // ngx-drag-drop component, and stops other use cases such as scrolling on drag.\n    // Instead, we can check if the event was already prevented by a child and bail early.\n    if (event.defaultPrevented) {\n      return;\n    }\n\n    // check if this drag event is allowed to drop on this dropzone\n    const type = getDndType(event);\n    if (!this.isDropAllowed(type)) {\n      return;\n    }\n\n    this.checkAndUpdatePlaceholderPosition(event);\n\n    const dropEffect = getDropEffect(event, this.dndEffectAllowed);\n\n    if (dropEffect === 'none') {\n      this.cleanupDragoverState();\n      return;\n    }\n\n    // allow the dragover\n    event.preventDefault();\n\n    // set the drop effect\n    setDropEffect(event, dropEffect);\n\n    this.dndDragover.emit(event);\n\n    this.renderer.addClass(\n      this.elementRef.nativeElement,\n      this.dndDragoverClass\n    );\n  }\n\n  @HostListener('drop', ['$event']) onDrop(event: DragEvent) {\n    try {\n      // check if this drag event is allowed to drop on this dropzone\n      const type = getDndType(event);\n      if (!this.isDropAllowed(type)) {\n        return;\n      }\n\n      const data: DragDropData = getDropData(event, isExternalDrag());\n\n      if (!this.isDropAllowed(data.type)) {\n        return;\n      }\n\n      // signal custom drop handling\n      event.preventDefault();\n\n      const dropEffect = getDropEffect(event, this.dndEffectAllowed);\n\n      setDropEffect(event, dropEffect);\n\n      if (dropEffect === 'none') {\n        return;\n      }\n\n      const dropIndex = this.getPlaceholderIndex();\n\n      // if for whatever reason the placeholder is not present in the DOM but it should be there\n      // we don't allow/emit the drop event since it breaks the contract\n      // seems to only happen if drag and drop is executed faster than the DOM updates\n      if (dropIndex === -1) {\n        return;\n      }\n\n      this.dndDrop.emit({\n        event: event,\n        dropEffect: dropEffect,\n        isExternal: isExternalDrag(),\n        data: data.data,\n        index: dropIndex,\n        type: type,\n      });\n\n      event.stopPropagation();\n    } finally {\n      this.cleanupDragoverState();\n    }\n  }\n\n  onDragLeave(event: DndEvent) {\n    this.enterCount--;\n\n    // only clean up when all enter/leave pairs are balanced (cursor has truly left)\n    if (this.enterCount > 0) {\n      return;\n    }\n\n    this.cleanupDragoverState();\n\n    // cleanup drop effect when leaving dropzone\n    setDropEffect(event, 'none');\n  }\n\n  private readonly dragEnterEventHandler: (event: DragEvent) => void = (\n    event: DragEvent\n  ) => this.onDragEnter(event);\n\n  private readonly dragOverEventHandler: (event: DragEvent) => void = (\n    event: DragEvent\n  ) => this.onDragOver(event);\n\n  private readonly dragLeaveEventHandler: (event: DragEvent) => void = (\n    event: DragEvent\n  ) => this.onDragLeave(event);\n\n  private isDropAllowed(type?: string): boolean {\n    // dropzone is disabled -> deny it\n    if (this.disabled) {\n      return false;\n    }\n\n    // if drag did not start from our directive\n    // and external drag sources are not allowed -> deny it\n    if (isExternalDrag() && !this.dndAllowExternal) {\n      return false;\n    }\n\n    // no filtering by types -> allow it\n    if (!this.dndDropzone) {\n      return true;\n    }\n\n    // no type set -> allow it\n    if (!type) {\n      return true;\n    }\n\n    if (!Array.isArray(this.dndDropzone)) {\n      throw new Error(\n        'dndDropzone: bound value to [dndDropzone] must be an array!'\n      );\n    }\n\n    // if dropzone contains type -> allow it\n    return this.dndDropzone.indexOf(type) !== -1;\n  }\n\n  private tryGetPlaceholder(): Element | null {\n    if (typeof this.dndPlaceholderRef !== 'undefined') {\n      return this.dndPlaceholderRef.elementRef.nativeElement as Element;\n    }\n\n    // TODO nasty workaround needed because if ng-container / template is used @ContentChild() or DI will fail because\n    // of wrong context see angular bug https://github.com/angular/angular/issues/13517\n    return this.elementRef.nativeElement.querySelector('[dndPlaceholderRef]');\n  }\n\n  private removePlaceholderFromDOM() {\n    if (this.placeholder !== null && this.placeholder.parentNode !== null) {\n      this.placeholder.parentNode.removeChild(this.placeholder);\n    }\n  }\n\n  private checkAndUpdatePlaceholderPosition(event: DragEvent): void {\n    if (this.placeholder === null) {\n      return;\n    }\n\n    // make sure the placeholder is in the DOM\n    if (this.placeholder.parentNode !== this.elementRef.nativeElement) {\n      this.renderer.appendChild(\n        this.elementRef.nativeElement,\n        this.placeholder\n      );\n    }\n\n    // update the position if the event originates from a child element of the dropzone\n    const directChild = getDirectChildElement(\n      this.elementRef.nativeElement,\n      event.target as Element\n    );\n\n    // early exit if no direct child or direct child is placeholder\n    if (directChild === null || directChild === this.placeholder) {\n      return;\n    }\n\n    const positionPlaceholderBeforeDirectChild =\n      shouldPositionPlaceholderBeforeElement(\n        event,\n        directChild,\n        this.dndHorizontal\n      );\n\n    if (positionPlaceholderBeforeDirectChild) {\n      // do insert before only if necessary\n      if (directChild.previousSibling !== this.placeholder) {\n        this.renderer.insertBefore(\n          this.elementRef.nativeElement,\n          this.placeholder,\n          directChild\n        );\n      }\n    } else {\n      // do insert after only if necessary\n      if (directChild.nextSibling !== this.placeholder) {\n        this.renderer.insertBefore(\n          this.elementRef.nativeElement,\n          this.placeholder,\n          directChild.nextSibling\n        );\n      }\n    }\n  }\n\n  private getPlaceholderIndex(): number | undefined {\n    if (this.placeholder === null) {\n      return undefined;\n    }\n\n    const element = this.elementRef.nativeElement as HTMLElement;\n\n    return Array.prototype.indexOf.call(element.children, this.placeholder);\n  }\n\n  private cleanupDragoverState() {\n    this.renderer.removeClass(\n      this.elementRef.nativeElement,\n      this.dndDragoverClass\n    );\n\n    this.enterCount = 0;\n    this.removePlaceholderFromDOM();\n  }\n}\n"
  },
  {
    "path": "projects/dnd/src/lib/dnd-handle.directive.spec.ts",
    "content": "import { Component, DebugElement } from '@angular/core';\nimport { ComponentFixture, TestBed } from '@angular/core/testing';\nimport { By } from '@angular/platform-browser';\nimport { describe, it, expect, beforeEach } from 'vitest';\nimport { DndDraggableDirective } from './dnd-draggable.directive';\nimport { DndHandleDirective } from './dnd-handle.directive';\nimport { endDrag } from './dnd-state';\n\n@Component({\n  standalone: true,\n  imports: [DndDraggableDirective, DndHandleDirective],\n  template: `\n    <div [dndDraggable]=\"'data'\">\n      <div dndHandle class=\"handle\">handle</div>\n      content\n    </div>\n  `,\n})\nclass HandleHost {}\n\ndescribe('DndHandleDirective', () => {\n  let fixture: ComponentFixture<HandleHost>;\n  let handleEl: DebugElement;\n\n  beforeEach(async () => {\n    endDrag();\n    await TestBed.configureTestingModule({\n      imports: [HandleHost],\n    }).compileComponents();\n\n    fixture = TestBed.createComponent(HandleHost);\n    fixture.detectChanges();\n    handleEl = fixture.debugElement.query(By.directive(DndHandleDirective));\n  });\n\n  it('should set draggable attribute on the handle', () => {\n    expect(handleEl.nativeElement.getAttribute('draggable')).toBe('true');\n  });\n\n  it('should set _dndUsingHandle on dragstart event', () => {\n    const directive = handleEl.injector.get(DndHandleDirective);\n    const event = {} as any;\n    directive.onDragEvent(event);\n    expect(event._dndUsingHandle).toBe(true);\n  });\n\n  it('should set _dndUsingHandle on dragend event', () => {\n    const directive = handleEl.injector.get(DndHandleDirective);\n    const event = {} as any;\n    directive.onDragEvent(event);\n    expect(event._dndUsingHandle).toBe(true);\n  });\n\n  it('should unregister handle on destroy', () => {\n    const draggableEl = fixture.debugElement.query(\n      By.directive(DndDraggableDirective)\n    );\n    const directive = draggableEl.injector.get(DndDraggableDirective);\n    expect((directive as any).dndHandle).toBeTruthy();\n\n    fixture.destroy();\n    expect((directive as any).dndHandle).toBeUndefined();\n  });\n});\n"
  },
  {
    "path": "projects/dnd/src/lib/dnd-handle.directive.ts",
    "content": "import {\n  Directive,\n  HostBinding,\n  HostListener,\n  inject,\n  OnDestroy,\n  OnInit,\n} from '@angular/core';\nimport { DndDraggableDirective } from './dnd-draggable.directive';\nimport { DndEvent } from './dnd-utils';\n\n@Directive({ selector: '[dndHandle]', standalone: true })\nexport class DndHandleDirective implements OnInit, OnDestroy {\n  @HostBinding('attr.draggable') draggable = true;\n\n  dndDraggableDirective = inject(DndDraggableDirective);\n\n  ngOnInit() {\n    this.dndDraggableDirective.registerDragHandle(this);\n  }\n\n  ngOnDestroy(): void {\n    this.dndDraggableDirective.registerDragHandle(undefined);\n  }\n\n  @HostListener('dragstart', ['$event'])\n  @HostListener('dragend', ['$event'])\n  onDragEvent(event: DndEvent) {\n    event._dndUsingHandle = true;\n  }\n}\n"
  },
  {
    "path": "projects/dnd/src/lib/dnd-state.spec.ts",
    "content": "import { describe, it, expect, beforeEach, vi } from 'vitest';\nimport {\n  startDrag,\n  endDrag,\n  setDropEffect,\n  getDropEffect,\n  getDndType,\n  isExternalDrag,\n  dndState,\n} from './dnd-state';\nimport { CUSTOM_MIME_TYPE } from './dnd-utils';\n\nfunction createMockDragEvent(overrides: Partial<DragEvent> = {}): DragEvent {\n  return {\n    dataTransfer: {\n      effectAllowed: 'all',\n      dropEffect: 'none',\n      types: [],\n    },\n    ctrlKey: false,\n    altKey: false,\n    ...overrides,\n  } as unknown as DragEvent;\n}\n\ndescribe('dnd-state', () => {\n  beforeEach(() => {\n    endDrag();\n  });\n\n  describe('startDrag', () => {\n    it('should set isDragging to true', () => {\n      const event = createMockDragEvent();\n      startDrag(event, 'all', undefined);\n      expect(dndState.isDragging).toBe(true);\n    });\n\n    it('should set effectAllowed on state and dataTransfer', () => {\n      const event = createMockDragEvent();\n      startDrag(event, 'copyMove', undefined);\n      expect(dndState.effectAllowed).toBe('copyMove');\n      expect(event.dataTransfer!.effectAllowed).toBe('copyMove');\n    });\n\n    it('should set type', () => {\n      const event = createMockDragEvent();\n      startDrag(event, 'all', 'myType');\n      expect(dndState.type).toBe('myType');\n    });\n\n    it('should reset dropEffect to none', () => {\n      const event = createMockDragEvent();\n      startDrag(event, 'all', undefined);\n      expect(dndState.dropEffect).toBe('none');\n    });\n  });\n\n  describe('endDrag', () => {\n    it('should reset all state', () => {\n      const event = createMockDragEvent();\n      startDrag(event, 'copyMove', 'test');\n      endDrag();\n\n      expect(dndState.isDragging).toBe(false);\n      expect(dndState.dropEffect).toBeUndefined();\n      expect(dndState.effectAllowed).toBeUndefined();\n      expect(dndState.type).toBeUndefined();\n    });\n  });\n\n  describe('setDropEffect', () => {\n    it('should update state when dragging', () => {\n      const event = createMockDragEvent();\n      startDrag(event, 'all', undefined);\n\n      setDropEffect(event, 'copy');\n      expect(dndState.dropEffect).toBe('copy');\n      expect(event.dataTransfer!.dropEffect).toBe('copy');\n    });\n\n    it('should not update state when not dragging', () => {\n      const event = createMockDragEvent();\n      setDropEffect(event, 'copy');\n      expect(dndState.dropEffect).toBeUndefined();\n      expect(event.dataTransfer!.dropEffect).toBe('copy');\n    });\n  });\n\n  describe('getDropEffect', () => {\n    it('should return first available effect', () => {\n      const event = createMockDragEvent();\n      expect(getDropEffect(event)).toBe('move');\n    });\n\n    it('should return \"none\" when no effects available', () => {\n      const event = createMockDragEvent({\n        dataTransfer: {\n          effectAllowed: 'none',\n        } as DataTransfer,\n      });\n      expect(getDropEffect(event)).toBe('none');\n    });\n\n    it('should filter by dndState effectAllowed when dragging', () => {\n      const startEvent = createMockDragEvent();\n      startDrag(startEvent, 'copy', undefined);\n\n      const event = createMockDragEvent();\n      expect(getDropEffect(event)).toBe('copy');\n    });\n\n    it('should filter by provided effectAllowed', () => {\n      const event = createMockDragEvent();\n      expect(getDropEffect(event, 'link')).toBe('link');\n    });\n\n    it('should return \"copy\" when ctrlKey is held and copy is available', () => {\n      const event = createMockDragEvent({ ctrlKey: true } as any);\n      expect(getDropEffect(event)).toBe('copy');\n    });\n\n    it('should return \"link\" when altKey is held and link is available', () => {\n      const event = createMockDragEvent({ altKey: true } as any);\n      expect(getDropEffect(event)).toBe('link');\n    });\n\n    it('should handle missing dataTransfer', () => {\n      const event = {\n        dataTransfer: null,\n        ctrlKey: false,\n        altKey: false,\n      } as unknown as DragEvent;\n      expect(getDropEffect(event)).toBe('move');\n    });\n  });\n\n  describe('getDndType', () => {\n    it('should return type from state when dragging', () => {\n      const event = createMockDragEvent();\n      startDrag(event, 'all', 'myType');\n      expect(getDndType(event)).toBe('myType');\n    });\n\n    it('should return undefined from state when dragging with no type', () => {\n      const event = createMockDragEvent();\n      startDrag(event, 'all', undefined);\n      expect(getDndType(event)).toBeUndefined();\n    });\n\n    it('should extract type from custom MIME type for external drag', () => {\n      const event = {\n        dataTransfer: {\n          types: [CUSTOM_MIME_TYPE + '-myType'],\n        },\n      } as unknown as DragEvent;\n      expect(getDndType(event)).toBe('myType');\n    });\n\n    it('should return undefined when no known MIME type on external drag', () => {\n      const event = {\n        dataTransfer: {\n          types: ['text/plain'],\n        },\n      } as unknown as DragEvent;\n      expect(getDndType(event)).toBeUndefined();\n    });\n  });\n\n  describe('isExternalDrag', () => {\n    it('should return true when not dragging', () => {\n      expect(isExternalDrag()).toBe(true);\n    });\n\n    it('should return false when dragging', () => {\n      const event = createMockDragEvent();\n      startDrag(event, 'all', undefined);\n      expect(isExternalDrag()).toBe(false);\n    });\n  });\n});\n"
  },
  {
    "path": "projects/dnd/src/lib/dnd-state.ts",
    "content": "import { DropEffect, EffectAllowed } from './dnd-types';\nimport {\n  CUSTOM_MIME_TYPE,\n  DROP_EFFECTS,\n  filterEffects,\n  getWellKnownMimeType,\n} from './dnd-utils';\n\nexport interface DndState {\n  isDragging: boolean;\n  dropEffect?: DropEffect;\n  effectAllowed?: EffectAllowed;\n  type?: string;\n}\n\nconst _dndState: DndState = {\n  isDragging: false,\n  dropEffect: 'none',\n  effectAllowed: 'all',\n  type: undefined,\n};\n\nexport function startDrag(\n  event: DragEvent,\n  effectAllowed: EffectAllowed,\n  type: string | undefined\n) {\n  _dndState.isDragging = true;\n  _dndState.dropEffect = 'none';\n  _dndState.effectAllowed = effectAllowed;\n  _dndState.type = type;\n\n  if (event.dataTransfer) {\n    event.dataTransfer.effectAllowed = effectAllowed;\n  }\n}\n\nexport function endDrag() {\n  _dndState.isDragging = false;\n  _dndState.dropEffect = undefined;\n  _dndState.effectAllowed = undefined;\n  _dndState.type = undefined;\n}\n\nexport function setDropEffect(event: DragEvent, dropEffect: DropEffect) {\n  if (_dndState.isDragging === true) {\n    _dndState.dropEffect = dropEffect;\n  }\n\n  if (event.dataTransfer) {\n    event.dataTransfer.dropEffect = dropEffect;\n  }\n}\n\nexport function getDropEffect(\n  event: DragEvent,\n  effectAllowed?: EffectAllowed | DropEffect\n): DropEffect {\n  const dataTransferEffectAllowed: EffectAllowed = event.dataTransfer\n    ? (event.dataTransfer.effectAllowed as EffectAllowed)\n    : 'uninitialized';\n\n  let effects = filterEffects(DROP_EFFECTS, dataTransferEffectAllowed);\n\n  if (_dndState.isDragging === true) {\n    effects = filterEffects(effects, _dndState.effectAllowed!);\n  }\n\n  if (effectAllowed) {\n    effects = filterEffects(effects, effectAllowed);\n  }\n\n  // MacOS automatically filters dataTransfer.effectAllowed depending on the modifier keys,\n  // therefore the following modifier keys will only affect other operating systems.\n  if (effects.length === 0) {\n    return 'none';\n  }\n\n  if (event.ctrlKey && effects.indexOf('copy') !== -1) {\n    return 'copy';\n  }\n\n  if (event.altKey && effects.indexOf('link') !== -1) {\n    return 'link';\n  }\n\n  return effects[0] as DropEffect;\n}\n\nexport function getDndType(event: DragEvent): string | undefined {\n  if (_dndState.isDragging === true) {\n    return _dndState.type;\n  }\n\n  const mimeType = getWellKnownMimeType(event);\n\n  if (mimeType === null) {\n    return undefined;\n  }\n\n  return mimeType.substring(CUSTOM_MIME_TYPE.length + 1) || undefined;\n}\n\nexport function isExternalDrag(): boolean {\n  return _dndState.isDragging === false;\n}\n\nexport const dndState: Readonly<DndState> = _dndState as Readonly<DndState>;\n"
  },
  {
    "path": "projects/dnd/src/lib/dnd-types.ts",
    "content": "// https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/dropEffect\nexport type DropEffect = 'move' | 'copy' | 'link' | 'none';\n\n// https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/effectAllowed\nexport type EffectAllowed =\n  | DropEffect\n  | 'copyMove'\n  | 'copyLink'\n  | 'linkMove'\n  | 'all'\n  | 'uninitialized';\n"
  },
  {
    "path": "projects/dnd/src/lib/dnd-utils.spec.ts",
    "content": "import { describe, it, expect, vi } from 'vitest';\nimport {\n  filterEffects,\n  getWellKnownMimeType,\n  setDragData,\n  getDropData,\n  getDirectChildElement,\n  shouldPositionPlaceholderBeforeElement,\n  calculateDragImageOffset,\n  setDragImage,\n  CUSTOM_MIME_TYPE,\n} from './dnd-utils';\n\nfunction createMockDragEvent(overrides: Partial<DragEvent> = {}): DragEvent {\n  const dataStore = new Map<string, string>();\n  return {\n    dataTransfer: {\n      types: [] as string[],\n      effectAllowed: 'all',\n      setData: vi.fn((type: string, data: string) => {\n        dataStore.set(type, data);\n        (overrides.dataTransfer as any)?.types?.push?.(type);\n      }),\n      getData: vi.fn((type: string) => dataStore.get(type) ?? ''),\n      setDragImage: vi.fn(),\n    },\n    ...overrides,\n  } as unknown as DragEvent;\n}\n\ndescribe('filterEffects', () => {\n  const allEffects = ['move', 'copy', 'link'] as any;\n\n  it('should return all effects for \"all\"', () => {\n    expect(filterEffects(allEffects, 'all')).toEqual(['move', 'copy', 'link']);\n  });\n\n  it('should return all effects for \"uninitialized\"', () => {\n    expect(filterEffects(allEffects, 'uninitialized')).toEqual([\n      'move',\n      'copy',\n      'link',\n    ]);\n  });\n\n  it('should filter to \"copyMove\"', () => {\n    expect(filterEffects(allEffects, 'copyMove')).toEqual(['move', 'copy']);\n  });\n\n  it('should filter to \"copyLink\"', () => {\n    expect(filterEffects(allEffects, 'copyLink')).toEqual(['copy', 'link']);\n  });\n\n  it('should filter to \"linkMove\"', () => {\n    expect(filterEffects(allEffects, 'linkMove')).toEqual(['move', 'link']);\n  });\n\n  it('should return single effect for exact match', () => {\n    expect(filterEffects(allEffects, 'copy')).toEqual(['copy']);\n  });\n\n  it('should return empty for \"none\"', () => {\n    expect(filterEffects(allEffects, 'none')).toEqual([]);\n  });\n});\n\ndescribe('getWellKnownMimeType', () => {\n  it('should return null when no dataTransfer', () => {\n    const event = { dataTransfer: null } as DragEvent;\n    expect(getWellKnownMimeType(event)).toBeNull();\n  });\n\n  it('should return custom MIME type when present', () => {\n    const customType = CUSTOM_MIME_TYPE + '-mytype';\n    const event = {\n      dataTransfer: { types: [customType] },\n    } as unknown as DragEvent;\n    expect(getWellKnownMimeType(event)).toBe(customType);\n  });\n\n  it('should return null for unknown MIME types', () => {\n    const event = {\n      dataTransfer: { types: ['text/plain'] },\n    } as unknown as DragEvent;\n    expect(getWellKnownMimeType(event)).toBeNull();\n  });\n\n  it('should return null for non-custom known MIME types', () => {\n    const event = {\n      dataTransfer: { types: ['application/json'] },\n    } as unknown as DragEvent;\n    expect(getWellKnownMimeType(event)).toBeNull();\n  });\n});\n\ndescribe('setDragData', () => {\n  it('should set data with custom MIME type', () => {\n    const event = createMockDragEvent();\n    const data = { data: 'test', type: 'mytype' };\n    setDragData(event, data, 'all');\n    expect(event.dataTransfer!.setData).toHaveBeenCalledWith(\n      CUSTOM_MIME_TYPE + '-mytype',\n      JSON.stringify(data)\n    );\n  });\n\n  it('should set data without type suffix when no type', () => {\n    const event = createMockDragEvent();\n    const data = { data: 'test' };\n    setDragData(event, data, 'all');\n    expect(event.dataTransfer!.setData).toHaveBeenCalledWith(\n      CUSTOM_MIME_TYPE,\n      JSON.stringify(data)\n    );\n  });\n});\n\ndescribe('getDropData', () => {\n  it('should parse data from custom MIME type for internal drag', () => {\n    const payload = { data: 'hello', type: 'mytype' };\n    const mimeType = CUSTOM_MIME_TYPE + '-mytype';\n    const event = {\n      dataTransfer: {\n        types: [mimeType],\n        getData: vi.fn(() => JSON.stringify(payload)),\n      },\n    } as unknown as DragEvent;\n\n    expect(getDropData(event, false)).toEqual(payload);\n  });\n\n  it('should return empty object for unknown MIME type on internal drag', () => {\n    const event = {\n      dataTransfer: {\n        types: ['text/plain'],\n        getData: vi.fn(() => ''),\n      },\n    } as unknown as DragEvent;\n\n    expect(getDropData(event, false)).toEqual({});\n  });\n\n  it('should return empty object for external drag with unknown MIME type', () => {\n    const event = {\n      dataTransfer: {\n        types: ['text/plain'],\n        getData: vi.fn(() => ''),\n      },\n    } as unknown as DragEvent;\n\n    expect(getDropData(event, true)).toEqual({});\n  });\n\n  it('should parse data for external drag with custom MIME type', () => {\n    const payload = { data: 'ext' };\n    const mimeType = CUSTOM_MIME_TYPE;\n    const event = {\n      dataTransfer: {\n        types: [mimeType],\n        getData: vi.fn(() => JSON.stringify(payload)),\n      },\n    } as unknown as DragEvent;\n\n    expect(getDropData(event, true)).toEqual(payload);\n  });\n});\n\ndescribe('getDirectChildElement', () => {\n  it('should return the direct child', () => {\n    const parent = document.createElement('div');\n    const child = document.createElement('span');\n    const grandchild = document.createElement('a');\n    parent.appendChild(child);\n    child.appendChild(grandchild);\n\n    expect(getDirectChildElement(parent, grandchild)).toBe(child);\n  });\n\n  it('should return the element itself when it is a direct child', () => {\n    const parent = document.createElement('div');\n    const child = document.createElement('span');\n    parent.appendChild(child);\n\n    expect(getDirectChildElement(parent, child)).toBe(child);\n  });\n\n  it('should return null when parent is not an ancestor', () => {\n    const parent = document.createElement('div');\n    const unrelated = document.createElement('span');\n\n    expect(getDirectChildElement(parent, unrelated)).toBeNull();\n  });\n});\n\ndescribe('shouldPositionPlaceholderBeforeElement', () => {\n  it('should return true when cursor is in upper half (vertical)', () => {\n    const element = document.createElement('div');\n    vi.spyOn(element, 'getBoundingClientRect').mockReturnValue({\n      top: 100,\n      height: 50,\n      left: 0,\n      width: 100,\n    } as DOMRect);\n\n    const event = { clientY: 110, clientX: 0 } as DragEvent;\n    expect(shouldPositionPlaceholderBeforeElement(event, element, false)).toBe(\n      true\n    );\n  });\n\n  it('should return false when cursor is in lower half (vertical)', () => {\n    const element = document.createElement('div');\n    vi.spyOn(element, 'getBoundingClientRect').mockReturnValue({\n      top: 100,\n      height: 50,\n      left: 0,\n      width: 100,\n    } as DOMRect);\n\n    const event = { clientY: 140, clientX: 0 } as DragEvent;\n    expect(shouldPositionPlaceholderBeforeElement(event, element, false)).toBe(\n      false\n    );\n  });\n\n  it('should return true when cursor is in left half (horizontal)', () => {\n    const element = document.createElement('div');\n    vi.spyOn(element, 'getBoundingClientRect').mockReturnValue({\n      top: 0,\n      height: 50,\n      left: 100,\n      width: 100,\n    } as DOMRect);\n\n    const event = { clientX: 120, clientY: 0 } as DragEvent;\n    expect(shouldPositionPlaceholderBeforeElement(event, element, true)).toBe(\n      true\n    );\n  });\n\n  it('should return false when cursor is in right half (horizontal)', () => {\n    const element = document.createElement('div');\n    vi.spyOn(element, 'getBoundingClientRect').mockReturnValue({\n      top: 0,\n      height: 50,\n      left: 100,\n      width: 100,\n    } as DOMRect);\n\n    const event = { clientX: 180, clientY: 0 } as DragEvent;\n    expect(shouldPositionPlaceholderBeforeElement(event, element, true)).toBe(\n      false\n    );\n  });\n});\n\ndescribe('calculateDragImageOffset', () => {\n  it('should calculate offset with padding and border', () => {\n    const element = document.createElement('div');\n    vi.spyOn(window, 'getComputedStyle').mockReturnValue({\n      paddingTop: '10px',\n      paddingLeft: '5px',\n      borderTopWidth: '2px',\n      borderLeftWidth: '3px',\n    } as CSSStyleDeclaration);\n\n    const event = { offsetX: 20, offsetY: 30 } as DragEvent;\n    const result = calculateDragImageOffset(event, element);\n\n    expect(result).toEqual({ x: 28, y: 42 });\n  });\n});\n\ndescribe('setDragImage', () => {\n  it('should call setDragImage on dataTransfer with offset', () => {\n    const setDragImageFn = vi.fn();\n    const event = {\n      dataTransfer: { setDragImage: setDragImageFn },\n    } as unknown as DragEvent;\n    const element = document.createElement('div');\n    const offsetFn = vi.fn().mockReturnValue({ x: 10, y: 20 });\n\n    setDragImage(event, element, offsetFn);\n\n    expect(offsetFn).toHaveBeenCalledWith(event, element);\n    expect(setDragImageFn).toHaveBeenCalledWith(element, 10, 20);\n  });\n});\n"
  },
  {
    "path": "projects/dnd/src/lib/dnd-utils.ts",
    "content": "import { DropEffect, EffectAllowed } from './dnd-types';\n\nexport interface DragDropData {\n  data?: any;\n  type?: string;\n}\n\nexport interface DndEvent extends DragEvent {\n  _dndUsingHandle?: boolean;\n  _dndDropzoneActive?: true;\n}\n\nexport type DndDragImageOffsetFunction = (\n  event: DragEvent,\n  dragImage: Element\n) => { x: number; y: number };\n\nexport const DROP_EFFECTS = ['move', 'copy', 'link'] as DropEffect[];\n\nexport const CUSTOM_MIME_TYPE = 'application/x-dnd';\n\nfunction mimeTypeIsCustom(mimeType: string) {\n  return mimeType.substring(0, CUSTOM_MIME_TYPE.length) === CUSTOM_MIME_TYPE;\n}\n\nexport function getWellKnownMimeType(event: DragEvent): string | null {\n  if (event.dataTransfer) {\n    const types = event.dataTransfer.types;\n\n    for (let i = 0; i < types.length; i++) {\n      if (mimeTypeIsCustom(types[i])) {\n        return types[i];\n      }\n    }\n  }\n\n  return null;\n}\n\nexport function setDragData(\n  event: DragEvent,\n  data: DragDropData,\n  effectAllowed: EffectAllowed\n): void {\n  const mimeType = CUSTOM_MIME_TYPE + (data.type ? '-' + data.type : '');\n\n  const dataString = JSON.stringify(data);\n\n  event.dataTransfer?.setData(mimeType, dataString);\n}\n\nexport function getDropData(\n  event: DragEvent,\n  dragIsExternal: boolean\n): DragDropData {\n  const mimeType = getWellKnownMimeType(event);\n\n  if (dragIsExternal === true) {\n    if (mimeType !== null && mimeTypeIsCustom(mimeType)) {\n      return JSON.parse(event.dataTransfer?.getData(mimeType) ?? '{}');\n    }\n\n    return {};\n  }\n\n  if (mimeType !== null) {\n    return JSON.parse(event.dataTransfer?.getData(mimeType) ?? '{}');\n  }\n\n  return {};\n}\n\nexport function filterEffects(\n  effects: DropEffect[],\n  allowed: EffectAllowed | DropEffect\n): DropEffect[] {\n  if (allowed === 'all' || allowed === 'uninitialized') {\n    return effects;\n  }\n\n  return effects.filter(function (effect) {\n    return allowed.toLowerCase().indexOf(effect) !== -1;\n  });\n}\n\nexport function getDirectChildElement(\n  parentElement: Element,\n  childElement: Element\n): Element | null {\n  let directChild: Node = childElement;\n\n  while (directChild.parentNode !== parentElement) {\n    // reached root node without finding given parent\n    if (!directChild.parentNode) {\n      return null;\n    }\n\n    directChild = directChild.parentNode;\n  }\n\n  return directChild as Element;\n}\n\nexport function shouldPositionPlaceholderBeforeElement(\n  event: DragEvent,\n  element: Element,\n  horizontal: boolean\n) {\n  const bounds = element.getBoundingClientRect();\n\n  // If the pointer is in the upper half of the list item element,\n  // we position the placeholder before the list item, otherwise after it.\n  if (horizontal) {\n    return event.clientX < bounds.left + bounds.width / 2;\n  }\n\n  return event.clientY < bounds.top + bounds.height / 2;\n}\n\nexport function calculateDragImageOffset(\n  event: DragEvent,\n  dragImage: Element\n): { x: number; y: number } {\n  const dragImageComputedStyle = window.getComputedStyle(dragImage);\n  const paddingTop = parseFloat(dragImageComputedStyle.paddingTop) || 0;\n  const paddingLeft = parseFloat(dragImageComputedStyle.paddingLeft) || 0;\n  const borderTop = parseFloat(dragImageComputedStyle.borderTopWidth) || 0;\n  const borderLeft = parseFloat(dragImageComputedStyle.borderLeftWidth) || 0;\n\n  return {\n    x: event.offsetX + paddingLeft + borderLeft,\n    y: event.offsetY + paddingTop + borderTop,\n  };\n}\n\nexport function setDragImage(\n  event: DragEvent,\n  dragImage: Element,\n  offsetFunction: DndDragImageOffsetFunction\n): void {\n  const offset = offsetFunction(event, dragImage) || { x: 0, y: 0 };\n\n  (event.dataTransfer as any).setDragImage(dragImage, offset.x, offset.y);\n}\n"
  },
  {
    "path": "projects/dnd/src/lib/dnd.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport {\n  DndDraggableDirective,\n  DndDragImageRefDirective,\n} from './dnd-draggable.directive';\nimport {\n  DndDropzoneDirective,\n  DndPlaceholderRefDirective,\n} from './dnd-dropzone.directive';\nimport { DndHandleDirective } from './dnd-handle.directive';\n\n@NgModule({\n  exports: [\n    DndDraggableDirective,\n    DndDropzoneDirective,\n    DndHandleDirective,\n    DndPlaceholderRefDirective,\n    DndDragImageRefDirective,\n  ],\n  imports: [\n    DndDragImageRefDirective,\n    DndDropzoneDirective,\n    DndHandleDirective,\n    DndPlaceholderRefDirective,\n    DndDraggableDirective,\n  ],\n})\nexport class DndModule {}\n"
  },
  {
    "path": "projects/dnd/src/public-api.ts",
    "content": "/*\n * Public API Surface of dnd\n */\n\nexport * from './lib/dnd-draggable.directive';\nexport * from './lib/dnd-dropzone.directive';\nexport * from './lib/dnd-handle.directive';\nexport * from './lib/dnd-types';\nexport { DndDragImageOffsetFunction } from './lib/dnd-utils';\nexport * from './lib/dnd.module';\n"
  },
  {
    "path": "projects/dnd/src/test-setup.ts",
    "content": "import 'zone.js';\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting,\n} from '@angular/platform-browser-dynamic/testing';\n\n// jsdom does not implement DragEvent\nif (typeof globalThis.DragEvent === 'undefined') {\n  (globalThis as any).DragEvent = class DragEvent extends MouseEvent {\n    public dataTransfer: DataTransfer | null;\n    constructor(type: string, eventInitDict?: DragEventInit) {\n      super(type, eventInitDict);\n      this.dataTransfer = eventInitDict?.dataTransfer ?? null;\n    }\n  };\n}\n\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting()\n);\n"
  },
  {
    "path": "projects/dnd/tsconfig.lib.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../out-tsc/lib\",\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"inlineSources\": true,\n    \"types\": []\n  },\n  \"exclude\": [\"src/test.ts\", \"**/*.spec.ts\"]\n}\n"
  },
  {
    "path": "projects/dnd/tsconfig.lib.prod.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.lib.json\",\n  \"compilerOptions\": {\n    \"declarationMap\": false\n  },\n  \"angularCompilerOptions\": {\n    \"compilationMode\": \"partial\"\n  }\n}\n"
  },
  {
    "path": "projects/dnd/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../out-tsc/spec\",\n    \"types\": [\"vitest/globals\"]\n  },\n  \"files\": [\"src/test-setup.ts\"],\n  \"include\": [\"**/*.spec.ts\", \"**/*.d.ts\"]\n}\n"
  },
  {
    "path": "renovate.json",
    "content": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"extends\": [\"config:best-practices\"]\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"outDir\": \"./dist/out-tsc\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"esModuleInterop\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"sourceMap\": true,\n    \"paths\": {\n      \"ngx-drag-drop\": [\n        \"./dist/ngx-drag-drop/ngx-drag-drop\",\n        \"./dist/ngx-drag-drop\"\n      ]\n    },\n    \"declaration\": false,\n    \"experimentalDecorators\": true,\n    \"moduleResolution\": \"bundler\",\n    \"importHelpers\": true,\n    \"target\": \"es2022\",\n    \"module\": \"es2022\"\n  },\n  \"angularCompilerOptions\": {\n    \"enableI18nLegacyMessageIdFormat\": false,\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": true\n  }\n}\n"
  },
  {
    "path": "vitest.config.ts",
    "content": "/// <reference types=\"vitest\" />\nimport angular from '@analogjs/vite-plugin-angular';\nimport { defineConfig } from 'vitest/config';\n\nexport default defineConfig({\n  plugins: [\n    angular({\n      tsconfig: 'projects/dnd/tsconfig.spec.json',\n    }),\n  ],\n  test: {\n    globals: true,\n    environment: 'jsdom',\n    setupFiles: ['projects/dnd/src/test-setup.ts'],\n    include: ['projects/dnd/src/**/*.spec.ts'],\n    coverage: {\n      provider: 'v8',\n      include: ['projects/dnd/src/lib/**/*.ts'],\n      exclude: ['**/*.spec.ts'],\n      reporter: ['text', 'json-summary', 'html'],\n    },\n  },\n});\n"
  }
]