├── .github
└── workflows
│ └── deploy-docs.yml
├── .gitignore
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── docs
├── .vitepress
│ ├── components
│ │ ├── Container.vue
│ │ ├── DnDDemo.vue
│ │ ├── DnDList.vue
│ │ ├── Draggable.vue
│ │ ├── DraggableList.vue
│ │ ├── Droppable.vue
│ │ ├── ExampleContainer.vue
│ │ ├── Examples
│ │ │ ├── Animation
│ │ │ │ ├── CustomContainer.vue
│ │ │ │ ├── Draggable.vue
│ │ │ │ └── Example.vue
│ │ │ ├── ChangingLayers
│ │ │ │ ├── Draggable.vue
│ │ │ │ ├── Example.vue
│ │ │ │ └── Layer.vue
│ │ │ ├── ChangingOverlay
│ │ │ │ ├── Container.vue
│ │ │ │ └── ExampleOverlay.vue
│ │ │ ├── CustomDragOverlayContainer
│ │ │ │ ├── Container.vue
│ │ │ │ └── Draggable.vue
│ │ │ ├── CustomSensor
│ │ │ │ └── Example.vue
│ │ │ ├── DragHandle.vue
│ │ │ ├── DragToZone.vue
│ │ │ ├── Groups
│ │ │ │ ├── Draggable.vue
│ │ │ │ ├── Droppable.vue
│ │ │ │ └── Example.vue
│ │ │ ├── KeyboardSupport.vue
│ │ │ ├── MorphSvg
│ │ │ │ ├── Draggable.vue
│ │ │ │ └── Example.vue
│ │ │ ├── MultiSelection
│ │ │ │ ├── Draggable.vue
│ │ │ │ └── Example.vue
│ │ │ ├── NestingZones
│ │ │ │ ├── Draggable.vue
│ │ │ │ ├── Example.vue
│ │ │ │ └── Zone.vue
│ │ │ ├── PromiseDrop
│ │ │ │ └── Example.vue
│ │ │ ├── ReorderingItems
│ │ │ │ ├── Draggable.vue
│ │ │ │ └── List.vue
│ │ │ ├── SimpleDrag.vue
│ │ │ ├── SortingLists
│ │ │ │ ├── Draggable.vue
│ │ │ │ └── Example.vue
│ │ │ └── Tree
│ │ │ │ ├── Draggable.vue
│ │ │ │ ├── Droppable.vue
│ │ │ │ ├── Example.vue
│ │ │ │ └── Tree.vue
│ │ └── Skeleton.vue
│ ├── config.mts
│ └── theme
│ │ └── index.ts
├── about.md
├── examples
│ ├── advanced
│ │ ├── changing-layers.md
│ │ ├── changing-overlay.md
│ │ ├── custom-sensor.md
│ │ ├── grouping.md
│ │ └── nesting-zones.md
│ ├── basic
│ │ ├── drag-handle.md
│ │ ├── drag-overlay.md
│ │ ├── drag-to-zone.md
│ │ ├── keyboard-support.md
│ │ ├── multi-selection.md
│ │ └── simple-drag.md
│ └── real-world
│ │ ├── animating.md
│ │ ├── morph-svg.md
│ │ ├── promise-drop.md
│ │ ├── reordering-items.md
│ │ ├── sorting-lists.md
│ │ └── tree.md
├── guide
│ ├── core
│ │ ├── collision-detection.md
│ │ ├── dnd-operations.md
│ │ ├── use-dnd-store.md
│ │ ├── use-drag-container.md
│ │ ├── use-draggable.md
│ │ ├── use-droppable.md
│ │ └── use-selection.md
│ ├── devtools
│ │ └── usage.md
│ ├── installation.md
│ ├── introduction.md
│ └── quick-start.md
└── index.md
├── package.json
├── packages
├── components
│ ├── LICENSE
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ ├── components
│ │ │ ├── Draggable.vue
│ │ │ ├── Droppable.vue
│ │ │ ├── Kanban
│ │ │ │ ├── Kanban.vue
│ │ │ │ ├── KanbanColumn.vue
│ │ │ │ └── KanbanItem.vue
│ │ │ └── Table
│ │ │ │ ├── Table.vue
│ │ │ │ ├── TableBody.vue
│ │ │ │ └── TableHead.vue
│ │ ├── index.ts
│ │ ├── shims-vue.d.ts
│ │ ├── types
│ │ │ └── index.d.ts
│ │ ├── utils
│ │ │ └── classNames.ts
│ │ ├── vite-env.d.ts
│ │ └── vue-shims.d.ts
│ ├── tsconfig.json
│ ├── tsconfig.node.json
│ └── vite.config.ts
├── core
│ ├── LICENSE
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ ├── components
│ │ │ ├── DefaultOverlay.vue
│ │ │ └── DragOverlay.vue
│ │ ├── composables
│ │ │ ├── useDnDStore.ts
│ │ │ ├── useDragContainer.ts
│ │ │ ├── useDraggable.ts
│ │ │ ├── useDroppable.ts
│ │ │ ├── useKeyboard.ts
│ │ │ ├── usePointer.ts
│ │ │ ├── useSelection.ts
│ │ │ └── useSensor.ts
│ │ ├── index.ts
│ │ ├── managers
│ │ │ ├── useElementManager.ts
│ │ │ ├── useEventManager.ts
│ │ │ └── useZoneManager.ts
│ │ ├── plugin.ts
│ │ ├── types
│ │ │ └── index.d.ts
│ │ ├── utils
│ │ │ ├── dom.ts
│ │ │ ├── events.ts
│ │ │ ├── geometry.ts
│ │ │ ├── namespaces.ts
│ │ │ ├── operations.ts
│ │ │ └── sensor.ts
│ │ └── vue-shims.d.ts
│ ├── tsconfig.json
│ └── vite.config.ts
├── devtools
│ ├── README.md
│ ├── package.json
│ ├── rollup.config.js
│ ├── src
│ │ ├── devtools.ts
│ │ ├── global.d.ts
│ │ ├── index.ts
│ │ └── utils
│ │ │ ├── container.ts
│ │ │ ├── elements.ts
│ │ │ ├── hovered.ts
│ │ │ ├── pointer.ts
│ │ │ ├── store.ts
│ │ │ └── zones.ts
│ └── tsconfig.json
└── utilities
│ ├── README.md
│ ├── package.json
│ ├── src
│ ├── composables
│ │ ├── useAutoScroll.ts
│ │ └── useGeometry.ts
│ ├── index.ts
│ ├── types
│ │ └── index.d.ts
│ └── utils
│ │ └── index.ts
│ ├── tsconfig.json
│ └── vite.config.ts
├── playground
├── .gitignore
├── README.md
├── index.html
├── package.json
├── public
│ └── vite.svg
├── src
│ ├── App.vue
│ ├── assets
│ │ └── vue.svg
│ ├── components
│ │ ├── CustomContainer.vue
│ │ ├── Drag.vue
│ │ └── Draggable.vue
│ ├── main.ts
│ ├── pages
│ │ ├── Animation.vue
│ │ ├── Kanban.vue
│ │ ├── LazyDrop.vue
│ │ ├── Promises.vue
│ │ ├── SortableList.vue
│ │ └── Table.vue
│ ├── router
│ │ └── index.ts
│ ├── shims.d.ts
│ ├── style.css
│ └── vite-env.d.ts
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
├── tsconfig.tsbuildinfo
├── vite.config.ts
└── yarn.lock
├── public
├── docs
│ └── images
│ │ └── devtools-screenshot.png
└── logo.svg
└── yarn.lock
/.github/workflows/deploy-docs.yml:
--------------------------------------------------------------------------------
1 | name: Deploy Documentation
2 |
3 | on:
4 | push:
5 | branches: [master]
6 | workflow_dispatch:
7 |
8 | permissions:
9 | contents: write
10 | pages: write
11 |
12 | jobs:
13 | build-and-deploy:
14 | runs-on: ubuntu-latest
15 | steps:
16 | - name: Checkout
17 | uses: actions/checkout@v3
18 |
19 | - name: Setup Node.js
20 | uses: actions/setup-node@v3
21 | with:
22 | node-version: 18
23 | cache: 'yarn'
24 |
25 | - name: Install dependencies
26 | run: yarn install
27 |
28 | - name: Build documentation
29 | run: yarn docs:build
30 |
31 | - name: Deploy to GitHub Pages
32 | uses: JamesIves/github-pages-deploy-action@v4
33 | with:
34 | folder: docs/.vitepress/dist
35 | branch: gh-pages
36 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist-ssr
12 | dist
13 | packages/*/dist
14 |
15 |
16 | *.local
17 | lib
18 | docs/.vitepress/dist
19 | docs/.vitepress/cache
20 | # Editor directories and files
21 | .vscode/*
22 | !.vscode/extensions.json
23 | .idea
24 | .DS_Store
25 | *.suo
26 | *.ntvs*
27 | *.njsproj
28 | *.sln
29 | *.sw?
30 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # CHANGELOG
2 |
3 | All notable changes to the Vue DnD Kit project are documented in this file.
4 |
5 | ## 2025-05-19
6 |
7 | ### Core Package
8 |
9 | - Added lazyAllowed for all composables
10 | - Added promise support for dropzone
11 |
12 | ## 2025-05-13
13 |
14 | ### Core Package
15 |
16 | - Added options for plugin, now u can cutomize default overlay styles
17 |
18 | ## 2025-05-01
19 |
20 | ### Core Package
21 |
22 | - Added DragOverlay transition detection (need to handle it, Example in docs)
23 | - fix bug with isDragging on Draggable element in useDraggable hook
24 |
25 | ## 2025-04-30 Added payload to all DnD Events
26 |
27 | ### Core Package
28 |
29 | - now onDrop, onHover, and other events return payload (it's array DraggingItems array)
30 |
31 | ## 2025-04-28 Added transition support for useDragContainer
32 |
33 | ### Core Package
34 |
35 | - added transition support for usedragcontainer
36 |
37 | ## 2025-04-27 - DevTools Package Release
38 |
39 | ### DevTools Package
40 |
41 | - Initial release of the DevTools package
42 | - Integration with Vue DevTools
43 | - Real-time visualization of drag and drop state
44 | - Store state inspection and monitoring
45 | - Elements and drop zones visualization
46 | - Pointer position tracking
47 | - Container and hover state debugging
48 |
49 | ## 2025-04-19 - Core Package Stable Release
50 |
51 | ### Core Package
52 |
53 | - Stable API release
54 | - Full drag and drop functionality
55 | - Touch and mouse input support
56 | - Keyboard navigation
57 | - Custom drag handles
58 | - Multiple draggable items support
59 | - TypeScript support with complete type definitions
60 | - ARIA attributes for accessibility
61 | - Sortable list implementation
62 | - Grid layout support
63 | - Auto-scrolling when dragging near viewport edges
64 | - Drop zone validation
65 | - Event hooks for all drag phases
66 |
67 | ---
68 |
69 | This CHANGELOG will be updated as new features and improvements are added to the Vue DnD Kit ecosystem.
70 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 ZiZiGY
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | A modern, lightweight drag and drop library for Vue 3 applications
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | ## 📋 About
18 |
19 | This repository contains the source code for Vue DnD Kit - a lightweight, high-performance drag and drop toolkit for Vue 3 applications. Inspired by the popular [React DnD Kit](https://dndkit.com/), but specifically designed for the Vue.js ecosystem.
20 |
21 | ## 🚀 Features
22 |
23 | - **Composable API**: Simple composables like `useDraggable` and `useDroppable` for creating drag and drop interfaces
24 | - **High Performance**: Built with performance in mind using efficient algorithms and optimizations
25 | - **TypeScript Support**: Written in TypeScript for better developer experience and code reliability
26 | - **Accessibility**: Designed with accessibility in mind, supporting keyboard navigation and screen readers
27 | - **Flexible**: Create custom drag overlays, handles, and complex nested interfaces
28 |
29 | ## 📦 Repository Structure
30 |
31 | The repository is organized into several packages:
32 |
33 | - **packages/core**: The main package with core drag and drop functionality
34 | - **packages/components**: (Coming soon) Ready-to-use components built on top of the core library
35 | - **packages/utils**: (Coming soon) Utility functions for advanced customization
36 | - **packages/devtools**: (Coming soon) Developer tools for debugging and optimizing
37 | - **docs**: Documentation site built with VitePress
38 |
39 | ## 📖 Documentation
40 |
41 | Visit our [documentation site](https://zizigy.github.io/vue-dnd-kit/) for guides, API references, and examples.
42 |
43 | ## 🔧 Dependencies
44 |
45 | - Vue 3
46 | - VueUse
47 |
48 | ## 🤝 Contributing
49 |
50 | Contributions are welcome! Please feel free to submit a Pull Request.
51 |
52 | ## 📄 License
53 |
54 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
55 |
56 | Made with ❤️ for the Vue.js community
57 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Container.vue:
--------------------------------------------------------------------------------
1 |
23 |
24 |
25 |
26 |
31 |
40 |
41 |
42 |
43 |
44 |
55 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/DnDDemo.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
17 | drop me in zone
18 |
19 |
20 |
21 |
22 |
23 | drop zone
24 |
25 |
29 | drop me from zone
30 |
31 |
32 |
33 |
34 |
35 |
36 |
54 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/DnDList.vue:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 |
30 |
31 |
37 | {{ item.content }}
38 |
39 |
40 |
41 |
42 |
43 |
54 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Draggable.vue:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 |
32 |
33 |
34 |
35 |
36 |
65 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/DraggableList.vue:
--------------------------------------------------------------------------------
1 |
25 |
26 |
27 |
36 |
37 |
38 |
39 |
40 |
60 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Droppable.vue:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
20 |
21 |
22 |
23 |
24 |
75 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Examples/Animation/CustomContainer.vue:
--------------------------------------------------------------------------------
1 |
72 |
73 |
74 |
91 |
92 |
93 |
116 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Examples/Animation/Draggable.vue:
--------------------------------------------------------------------------------
1 |
26 |
27 |
28 |
29 |
39 | Drag me
40 |
41 |
42 |
43 |
44 |
86 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Examples/Animation/Example.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
21 |
26 | Drag me to zone
27 |
28 |
29 |
34 |
Drop here
35 |
36 |
37 |
41 |
42 |
43 |
48 | Drag me back
49 |
50 |
51 |
52 |
53 |
54 |
55 |
117 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Examples/ChangingLayers/Draggable.vue:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
20 |
21 |
28 | ⋮⋮
29 |
30 |
35 |
36 |
37 |
38 |
39 |
40 |
63 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Examples/ChangingLayers/Example.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 | im default
11 | im with custom layer
12 |
13 |
14 |
15 |
16 |
23 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Examples/ChangingLayers/Layer.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
13 | {{ props.id }}
14 |
15 |
16 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Examples/ChangingOverlay/Container.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
26 | Move me to drop zone
27 |
28 |
29 |
34 | Drop zone
35 |
36 |
37 |
38 |
39 |
87 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Examples/CustomDragOverlayContainer/Container.vue:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 |
26 | im working with transition under the hood
27 |
28 |
29 |
30 |
49 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Examples/CustomDragOverlayContainer/Draggable.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
19 | Drag me to see custom drag overlay
20 |
21 |
22 |
23 |
24 |
48 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Examples/CustomSensor/Example.vue:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 |
27 |
28 |
34 | drag me
35 |
36 |
37 |
42 | drop zone
43 |
44 |
45 |
46 |
47 |
48 |
85 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Examples/DragHandle.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
17 |
24 | ⋮⋮
25 |
26 |
Drag using the handle
27 |
28 |
29 |
30 |
31 |
81 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Examples/DragToZone.vue:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 |
21 |
22 |
29 | Drag me!
30 |
31 |
32 |
37 | {{ dropped ? 'Dropped!' : 'Drop here' }}
38 |
39 |
40 |
41 |
42 |
43 |
82 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Examples/Groups/Draggable.vue:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
27 | drag me
28 |
29 |
30 |
31 |
42 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Examples/Groups/Droppable.vue:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
29 |
30 | Group
31 |
35 | {{ group }}
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
71 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Examples/Groups/Example.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
17 |
18 |
19 |
23 |
28 |
29 |
30 |
34 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
57 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Examples/KeyboardSupport.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
22 |
Space to drag
23 |
24 | W A S D to move
25 |
26 |
Press Space to drag me
27 |
28 |
29 |
30 |
31 |
84 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Examples/MorphSvg/Draggable.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
35 |
36 |
37 |
50 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Examples/MorphSvg/Example.vue:
--------------------------------------------------------------------------------
1 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
morph me
54 |
55 |
56 |
57 |
58 |
78 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Examples/MultiSelection/Draggable.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
15 |
22 | ⋮⋮
23 |
24 |
29 |
Drag using the handle
30 |
31 |
32 |
33 |
83 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Examples/MultiSelection/Example.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
23 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Examples/NestingZones/Draggable.vue:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
22 | drop me in any zone
23 |
24 |
25 |
26 |
37 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Examples/NestingZones/Example.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
17 |
18 |
19 | first zone
20 |
24 |
25 |
26 | second zone
27 |
28 |
32 |
33 |
34 | third zone
35 |
36 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
54 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Examples/NestingZones/Zone.vue:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
21 |
22 |
23 |
24 |
25 |
39 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Examples/PromiseDrop/Example.vue:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 |
24 |
25 |
31 | Draggable
32 |
33 |
34 |
39 | Droppable
40 |
41 |
42 |
43 |
44 |
45 |
75 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Examples/ReorderingItems/Draggable.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
24 |
28 | ⋮⋮
29 |
30 |
31 |
32 |
33 |
34 |
96 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Examples/ReorderingItems/List.vue:
--------------------------------------------------------------------------------
1 |
32 |
33 |
34 |
35 | Add Item
40 |
44 |
45 |
51 |
52 | {{ item.content }}
53 | Remove
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
102 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Examples/SimpleDrag.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
17 | Drag me!
18 |
19 |
20 |
21 |
22 |
31 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Examples/SortingLists/Draggable.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
28 |
29 |
30 |
31 |
32 |
69 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Examples/SortingLists/Example.vue:
--------------------------------------------------------------------------------
1 |
36 |
37 |
38 |
39 |
40 |
44 |
45 |
51 | {{ item.name }}
52 |
53 |
54 |
55 |
56 |
60 |
61 |
67 | {{ item.name }}
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
119 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Examples/Tree/Draggable.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
24 |
25 |
30 | ⋮⋮
31 |
32 |
33 |
34 |
35 |
36 |
37 |
61 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Examples/Tree/Droppable.vue:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 |
25 |
26 |
27 |
28 |
29 |
45 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Examples/Tree/Example.vue:
--------------------------------------------------------------------------------
1 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Examples/Tree/Tree.vue:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
18 |
24 | Draggable - {{ item.id }}
25 |
29 |
30 |
31 |
32 |
33 |
34 |
50 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Skeleton.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
22 |
23 |
57 |
--------------------------------------------------------------------------------
/docs/.vitepress/theme/index.ts:
--------------------------------------------------------------------------------
1 | import DefaultTheme from 'vitepress/theme';
2 | import VueDnDKitDevtools from '@vue-dnd-kit/devtools';
3 | import VueDnDKitPlugin from '@vue-dnd-kit/core';
4 |
5 | export default {
6 | ...DefaultTheme,
7 | enhanceApp({ app }) {
8 | app.use(VueDnDKitPlugin);
9 | app.use(VueDnDKitDevtools);
10 | },
11 | };
12 |
--------------------------------------------------------------------------------
/docs/about.md:
--------------------------------------------------------------------------------
1 | # About Vue DnD Kit
2 |
3 | ## Project Origin
4 |
5 | Vue DnD Kit was born out of necessity when I needed to build a WYSIWYG editor with advanced drag and drop functionality. After searching for existing solutions, I couldn't find any library that met all my requirements:
6 |
7 | - Support for keyboard-based drag and drop for accessibility
8 | - Multi-drag capability to move multiple items simultaneously
9 | - Dynamic layer/z-index changes when hovering over drop zones
10 | - Custom animations during drag operations
11 | - Lightweight yet powerful API out of the box
12 |
13 | Facing these challenges, I decided to create my own comprehensive solution from scratch. This project is the evolution of my previous work with Vue DnD Hooks, completely reimagined for Vue 3's Composition API with a focus on modularity and extensibility.
14 |
15 | ## Vision
16 |
17 | My vision for Vue DnD Kit is to create the most developer-friendly drag and drop ecosystem for Vue applications. I believe that:
18 |
19 | 1. **Developers shouldn't have to compromise** between ease of use and flexibility
20 | 2. **Performance should be a priority**, not an afterthought
21 | 3. **Accessibility should be built-in**, not added later
22 | 4. **Documentation matters** as much as the code itself
23 |
24 | ## Roadmap
25 |
26 | Here's what I'm planning for future releases:
27 |
28 | ### Short-term
29 |
30 | - Expand component library with more specialized components
31 | - Add more examples and recipes for common patterns
32 | - Improve testing coverage and performance benchmarks
33 | - Create comprehensive documentation with interactive examples
34 |
35 | ### Mid-term
36 |
37 | - Add specialized modules for specific use cases (form builders, page builders)
38 | - Create ecosystem plugins for popular UI frameworks
39 | - Implement advanced animation capabilities
40 | - Add support for extremely large datasets
41 |
42 | ### Long-term
43 |
44 | - Explore Vue DnD Kit Plus with premium features for enterprise users
45 | - Create a visual builder for drag and drop interfaces
46 | - Expand the ecosystem based on community feedback
47 |
48 | ## Contributing
49 |
50 | While Vue DnD Kit is currently a solo project, I welcome contributions from the community! Whether you're fixing a bug, improving documentation, or proposing a new feature, your help is appreciated.
51 |
52 | If you're interested in contributing, please feel free to open an issue or submit a pull request on GitHub.
53 |
54 | ## About the Creator
55 |
56 | Vue DnD Kit is created and maintained by [ZIZIGY](https://github.com/zizigy).
57 |
58 | I built this library to solve real-world problems I encountered in my own projects, and I'm sharing it with the community in hopes that it helps others facing similar challenges.
59 |
60 | ## License
61 |
62 | Vue DnD Kit is released under the [MIT License](https://github.com/zizigy/vue-dnd-kit/blob/main/LICENSE).
63 |
64 | ## Support the Project
65 |
66 | If you find Vue DnD Kit useful, please consider:
67 |
68 | - Starring the [GitHub repository](https://github.com/zizigy/vue-dnd-kit)
69 | - Sharing your experience on social media
70 | - Contributing to the codebase or documentation
71 | - Reporting bugs or suggesting features
72 |
73 | Your support helps me continue improving and maintaining this library!
74 |
--------------------------------------------------------------------------------
/docs/examples/basic/drag-handle.md:
--------------------------------------------------------------------------------
1 |
4 |
5 | # Drag Handle
6 |
7 | This example demonstrates how to create elements with drag handles - specific parts of an element that can be used to initiate dragging.
8 |
9 | ## Overview
10 |
11 | The Drag Handle example shows how to use the `useDraggable` composable with accessibility features. Instead of making the entire element draggable, we designate a specific "handle" that users can interact with to initiate drag operations.
12 |
13 |
14 |
15 | ## Key Concepts
16 |
17 | - Creating draggable elements with dedicated drag handles
18 | - Styling drag handles to provide visual cues
19 |
20 | ## Basic Usage
21 |
22 | ```vue
23 |
30 |
31 |
32 |
37 |
44 | ⋮⋮
45 |
46 |
Drag using the handle
47 |
48 |
49 |
50 |
101 | ```
102 |
103 | ## Accessibility Considerations
104 |
105 | When implementing drag handles:
106 |
107 | - Use `role="button"` for the handle
108 | - Add appropriate `aria-label` to describe the purpose of the handle
109 |
--------------------------------------------------------------------------------
/docs/examples/basic/drag-to-zone.md:
--------------------------------------------------------------------------------
1 |
4 |
5 | # Drag to Zone
6 |
7 | This example demonstrates how to create an element that can be dragged into a designated drop zone.
8 |
9 | ## Overview
10 |
11 | The Drag to Zone example shows how to use both `useDraggable` and `useDroppable` composables together to create drag and drop interactions. When the draggable element is dropped onto the drop zone, a visual confirmation appears.
12 |
13 |
14 |
15 | ## Key Concepts
16 |
17 | - Creating a draggable element with `useDraggable`
18 | - Defining a drop zone with `useDroppable`
19 | - Handling drop events
20 | - Providing visual feedback for user interactions
21 |
22 | ## Basic Usage
23 |
24 | ```vue
25 |
41 |
42 |
43 |
44 |
51 | Drag me!
52 |
53 |
54 |
59 | {{ dropped ? 'Dropped!' : 'Drop here' }}
60 |
61 |
62 |
63 |
64 |
104 | ```
105 |
106 | ## Next Steps
107 |
108 | After mastering how to drag to a drop zone, you can explore:
109 |
110 | - Creating sortable lists
111 | - Multiple drop zones
112 | - Conditional drop acceptance
113 | - Custom drag overlays
114 |
--------------------------------------------------------------------------------
/docs/examples/basic/keyboard-support.md:
--------------------------------------------------------------------------------
1 |
4 |
5 | # Keyboard Support
6 |
7 | This example demonstrates how to make your drag and drop interface accessible using keyboard controls, making it usable for people who cannot or prefer not to use a mouse.
8 |
9 | ## Overview
10 |
11 | The Keyboard Support example shows how to enhance the `useDraggable` composable with keyboard event handlers, allowing users to initiate and control drag operations using only their keyboard.
12 |
13 |
14 |
15 | ## Key Concepts
16 |
17 | - Making drag and drop operations accessible via keyboard
18 | - Adding proper keyboard event handlers
19 | - Using ARIA attributes to enhance screen reader compatibility
20 | - Providing visual feedback for keyboard focus states
21 |
22 | ## Basic Usage
23 |
24 | ```vue
25 |
32 |
33 |
34 |
44 |
Space to drag
45 |
46 | W A S D to move
47 |
48 |
Press Space to drag me
49 |
50 |
51 |
52 |
80 | ```
81 |
82 | ## Accessibility Best Practices
83 |
84 | When implementing keyboard support for drag and drop:
85 |
86 | 1. Make draggable elements focusable with `tabindex="0"`
87 | 2. Use appropriate ARIA roles and labels
88 | 3. Add keyboard event handler for Space key to start dragging (Note: Enter is reserved for drop operations)
89 | 4. Once in drag mode, use WASD keys for directional movement
90 | 5. Provide clear visual focus indicators
91 | 6. Include instructions or visual cues about keyboard commands
92 |
93 | Supporting keyboard interactions ensures your drag and drop interface is accessible to all users, including those with motor disabilities or those who prefer keyboard navigation.
94 |
--------------------------------------------------------------------------------
/docs/examples/basic/simple-drag.md:
--------------------------------------------------------------------------------
1 |
4 |
5 | # Simple Drag
6 |
7 | This example demonstrates the most basic usage of Vue DnD Kit - making an element draggable.
8 |
9 | ## Overview
10 |
11 | The Simple Drag example shows how to create draggable elements using the `useDraggable` composable. This is the foundation for all drag and drop interactions in Vue DnD Kit.
12 |
13 |
14 |
15 | ## Key Concepts
16 |
17 | - Using the `useDraggable` composable
18 | - Handling drag start events
19 | - Tracking the dragging state
20 | - Styling draggable elements
21 | - Supporting mobile devices with proper touch handling
22 |
23 | ## Basic Usage
24 |
25 | ```vue
26 |
33 |
34 |
35 |
41 | Drag me!
42 |
43 |
44 |
45 |
54 | ```
55 |
56 | ## Mobile Support
57 |
58 | To ensure proper drag behavior on mobile devices, it's crucial to add `touch-action: none` to your draggable elements. This CSS property prevents the browser's default touch actions (like scrolling) from interfering with your drag operations.
59 |
60 | ```css
61 | .draggable {
62 | touch-action: none;
63 | }
64 | ```
65 |
66 | Without this property, mobile users might experience issues where attempting to drag an element results in page scrolling instead of the expected drag behavior.
67 |
68 | ## Next Steps
69 |
70 | Once you understand how to make elements draggable, you can:
71 |
72 | - Create drop zones with `useDroppable`
73 | - Implement drag-to-reorder functionality
74 | - Add custom drag handles
75 | - Create a drag overlay
76 |
--------------------------------------------------------------------------------
/docs/guide/core/use-dnd-store.md:
--------------------------------------------------------------------------------
1 | # useDnDStore
2 |
3 | `useDnDStore` provides access to the global drag and drop state store. This is a singleton store (similar to Pinia but lightweight and specialized) that manages all draggable elements, drop zones, and the current drag state.
4 |
5 | While the basic drag and drop functionality works without directly accessing this store, it's useful for advanced customization scenarios, such as implementing custom animations, creating complex drag visualizations, or adding specialized behaviors.
6 |
7 | ## Usage
8 |
9 | ```ts
10 | import { useDnDStore } from '@vue-dnd-kit/core';
11 |
12 | const store = useDnDStore();
13 | ```
14 |
15 | ## API Reference
16 |
17 | ### Store Properties
18 |
19 | | Property | Type | Description |
20 | | ---------------- | ------------------------------------- | ------------------------------------------------------- |
21 | | draggingElements | `Ref>` | Map of currently dragging elements |
22 | | isDragging | `ComputedRef` | Whether any element is currently being dragged |
23 | | elementsMap | `Ref>` | Map of all registered draggable elements |
24 | | selectedElements | `Ref` | Map of all selected elements (for multi-drag) |
25 | | zonesMap | `Ref>` | Map of all registered drop zones |
26 | | visibleZones | `Ref>` | Set of drop zones currently visible in viewport |
27 | | visibleElements | `Ref>` | Set of draggable elements currently visible in viewport |
28 | | pointerPosition | `Object` | Current and initial pointer positions |
29 | | keyboard | `Object` | State of keyboard keys (using VueUse's useMagicKeys) |
30 | | hovered | `Object` | Currently hovered zone and element |
31 | | activeContainer | `Object` | Active overlay container component and reference |
32 |
33 | ### Pointer Position Object
34 |
35 | ```ts
36 | pointerPosition: {
37 | start: Ref, // Initial position when drag started
38 | current: Ref, // Current position during drag
39 | offset: {
40 | percent: Ref, // Offset as percentage of element size
41 | pixel: Ref, // Offset in pixels
42 | }
43 | }
44 | ```
45 |
46 | ### Hovered Object
47 |
48 | ```ts
49 | hovered: {
50 | zone: Ref, // Currently hovered drop zone
51 | element: Ref // Currently hovered draggable element
52 | }
53 | ```
54 |
55 | ### Keyboard Object
56 |
57 | ```ts
58 | keyboard: {
59 | w: Ref, // Whether 'w' key is pressed
60 | s: Ref, // Whether 's' key is pressed
61 | a: Ref, // Whether 'a' key is pressed
62 | d: Ref, // Whether 'd' key is pressed
63 | ctrl: Ref, // Whether Control key is pressed
64 | shift: Ref, // Whether Shift key is pressed
65 | alt: Ref, // Whether Alt key is pressed
66 | meta: Ref // Whether Meta key is pressed
67 | }
68 | ```
69 |
70 | ## Important Notes
71 |
72 | 1. The store is automatically used by `useDraggable` and `useDroppable` - you don't need to access it directly for basic drag and drop operations.
73 | 2. The store follows a singleton pattern - all calls to `useDnDStore()` return the same instance.
74 | 3. All properties are reactive, allowing you to create computed properties or watch for changes.
75 | 4. The store uses IntersectionObserver internally to track which elements are visible in the viewport.
76 | 5. You can access the store from any component without additional setup or provider components.
77 | 6. The store is primarily intended for advanced customization and extension of the drag and drop system.
78 | 7. Internal methods like `handleDragElementIntersection` and `handleDropZoneIntersection` are not recommended for direct use as they're part of the library's internal infrastructure.
79 |
--------------------------------------------------------------------------------
/docs/guide/core/use-drag-container.md:
--------------------------------------------------------------------------------
1 | # useDragContainer
2 |
3 | `useDragContainer` is a core composable that allows registering a custom container where dragged elements will be displayed. This composable gives users complete freedom to customize how dragged elements appear during drag operations.
4 |
5 | ## API Reference
6 |
7 | ### Usage
8 |
9 | ```ts
10 | const result = useDragContainer();
11 | ```
12 |
13 | The `useDragContainer` composable doesn't accept any parameters.
14 |
15 | ### Return Value
16 |
17 | `useDragContainer` returns an object with the following properties:
18 |
19 | | Property | Type | Description |
20 | | ---------------- | -------------------------- | ----------------------------------------------------- |
21 | | elementRef | `Ref` | Template ref to attach to the container element |
22 | | draggingElements | `Map` | Map of all currently dragged elements |
23 | | pointerPosition | `Object` | Current and initial pointer positions during dragging |
24 | | isDragging | `ComputedRef` | Whether a drag operation is currently active |
25 |
26 | #### pointerPosition Object
27 |
28 | The `pointerPosition` object includes:
29 |
30 | | Property | Type | Description |
31 | | -------------- | --------------------- | ------------------------------------------ |
32 | | start | `Ref` | Starting position when drag began |
33 | | current | `Ref` | Current pointer position during dragging |
34 | | offset.percent | `Ref` | Offset as percentage of element dimensions |
35 | | offset.pixel | `Ref` | Offset in pixels |
36 |
37 | ## Important Notes
38 |
39 | 1. The `elementRef` must be bound to your container element in the template.
40 |
41 | 2. The container is automatically registered with the DnD system on mount and unregistered on unmount.
42 |
43 | 3. For proper positioning of dragged elements, use the values from `pointerPosition`.
44 |
45 | 4. If you create multiple drag containers, the most recently mounted one will be active.
46 |
47 | 5. Containers typically use `pointer-events: none` to prevent interfering with normal interactions.
48 |
49 | 6. To handle multi-select effectively, check `draggingElements.size` to determine how many elements are being dragged.
50 |
51 | 7. The container should typically be positioned fixed or absolute and cover the entire viewport to ensure dragged elements are visible anywhere.
52 |
53 | 8. For performance reasons, apply `will-change: transform` to elements that will move during dragging.
54 |
55 | 9. You may want to conditionally show the container only when `isDragging` is true to improve performance.
56 |
--------------------------------------------------------------------------------
/docs/guide/core/use-selection.md:
--------------------------------------------------------------------------------
1 | # useSelection
2 |
3 | `useSelection` is a specialized composable for managing multi-select and multi-drag operations. It's extracted into a separate utility to keep the core `useDraggable` implementation lean while providing powerful selection management when needed.
4 |
5 | ## Usage
6 |
7 | ```ts
8 | import { useDraggable, useSelection } from '@vue-dnd-kit/core';
9 | import { ref } from 'vue';
10 |
11 | // First create a draggable element
12 | const { elementRef } = useDraggable({
13 | /* options */
14 | });
15 |
16 | // Then add selection functionality to it
17 | const { isSelected, handleSelect, handleToggleSelect } =
18 | useSelection(elementRef);
19 | ```
20 |
21 | ## API Reference
22 |
23 | ### Parameters
24 |
25 | ```ts
26 | useSelection(elementRef: Ref)
27 | ```
28 |
29 | | Parameter | Type | Description |
30 | | ---------- | -------------------------- | ----------------------------------------- |
31 | | elementRef | `Ref` | Reference to the element to be selectable |
32 |
33 | ### Return Value
34 |
35 | `useSelection` returns an object with the following properties:
36 |
37 | | Property | Type | Description |
38 | | ------------------ | ---------------------- | ----------------------------------------- |
39 | | isSelected | `ComputedRef` | Whether the element is currently selected |
40 | | isParentOfSelected | `ComputedRef` | Whether any child elements are selected |
41 | | handleSelect | `() => void` | Function to select this element |
42 | | handleUnselect | `() => void` | Function to unselect this element |
43 | | handleToggleSelect | `() => void` | Function to toggle selection state |
44 |
45 | ## Selection Behavior
46 |
47 | `useSelection` implements smart selection logic that handles parent-child relationships:
48 |
49 | 1. When selecting an element that has selected children, the children are automatically unselected
50 | 2. When selecting an element that has a selected parent, the parent is automatically unselected
51 | 3. This prevents conflicting selection states within the same DOM hierarchy
52 |
53 | ## Important Notes
54 |
55 | 1. `useSelection` provides only the basic selection management functions and does not handle keyboard modifiers by itself.
56 |
57 | 2. For implementing advanced selection behaviors that respond to keyboard modifiers (Shift, Ctrl, etc.), you'll need to combine `useSelection` with `useDnDStore`:
58 |
59 | ```ts
60 | import { useDnDStore } from '@vue-dnd-kit/core';
61 |
62 | const store = useDnDStore();
63 |
64 | // Now you can check keyboard states in your event handlers
65 | // store.keyboard.ctrl.value, store.keyboard.shift.value, etc.
66 | ```
67 |
68 | 3. Selected elements can be dragged together when using the appropriate drag operations.
69 |
70 | 4. Selection state is preserved between drag operations.
71 |
72 | 5. The composable automatically handles parent-child selection conflicts to maintain a consistent selection state.
73 |
--------------------------------------------------------------------------------
/docs/guide/devtools/usage.md:
--------------------------------------------------------------------------------
1 | # DevTools Usage
2 |
3 | Vue DnD Kit includes a DevTools package that helps you monitor your drag and drop state in real-time. This integration works directly with Vue DevTools to provide visibility into your application's DnD state.
4 |
5 | ## Installation
6 |
7 | To use DevTools, first install the package:
8 |
9 | ```bash
10 | #yarn
11 | yarn add @vue-dnd-kit/devtools
12 |
13 | #npm
14 | npm install @vue-dnd-kit/devtools
15 |
16 | #pnpm
17 | pnpm add @vue-dnd-kit/devtools
18 | ```
19 |
20 | If you haven't installed the core package yet, you'll need both:
21 |
22 | ```bash
23 | yarn add @vueuse/core @vue-dnd-kit/core @vue-dnd-kit/devtools
24 | ```
25 |
26 | ## Integration
27 |
28 | Add the DevTools to your Vue application:
29 |
30 | ```js
31 | // main.js or main.ts
32 | import { createApp } from 'vue';
33 | import App from './App.vue';
34 | import VueDnDKitPlugin from '@vue-dnd-kit/core';
35 | import VueDnDKitDevtools from '@vue-dnd-kit/devtools';
36 |
37 | const app = createApp(App);
38 |
39 | // Register the core plugin
40 | app.use(VueDnDKitPlugin);
41 |
42 | // Register DevTools
43 | app.use(VueDnDKitDevtools);
44 |
45 | app.mount('#app');
46 | ```
47 |
48 | The DevTools integration automatically works only in development mode.
49 |
50 | ## Using DevTools
51 |
52 | After installing and registering the DevTools plugin, follow these steps to access the state viewer:
53 |
54 | 1. Open your application in a development environment
55 | 2. Open browser DevTools (F12 or right-click → Inspect)
56 | 3. Navigate to the Vue panel in DevTools
57 | 4. Look for the "DnD Store" section in the Vue DevTools sidebar
58 |
59 | 
60 |
61 | ## Available Information
62 |
63 | The DevTools panel shows the current state of your DnD system:
64 |
65 | - **Elements**: All registered draggable elements
66 | - **Zones**: All registered drop zones
67 | - **Pointer**: Current pointer position and state
68 | - **Container**: Container information
69 | - **Hovered**: Currently hovered elements and zones
70 |
71 | This information updates in real-time as you interact with your drag and drop interface, making it easier to debug issues and understand the current state of your application.
72 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-dnd-kit",
3 | "private": true,
4 | "description": "Drag and Drop toolkit for Vue 3",
5 | "scripts": {
6 | "update-submodules": "git submodule update --init --recursive",
7 | "build-all": "npm run build --workspaces",
8 | "dev": "npm run dev --workspaces",
9 | "test": "npm run test --workspaces",
10 | "docs:dev": "vitepress dev docs",
11 | "docs:build": "vitepress build docs",
12 | "docs:preview": "vitepress preview docs"
13 | },
14 | "workspaces": [
15 | "packages/*",
16 | "playground"
17 | ],
18 | "keywords": [
19 | "vue",
20 | "vue3",
21 | "drag-and-drop",
22 | "dnd",
23 | "draggable"
24 | ],
25 | "author": "ZiZIGY",
26 | "license": "MIT",
27 | "devDependencies": {
28 | "typescript": "^4.9.5",
29 | "vue": "^3.2.47"
30 | },
31 | "dependencies": {
32 | "gsap": "^3.13.0"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/packages/components/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 ZiZiGY
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/components/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vue-dnd-kit/components",
3 | "version": "0.2.22",
4 | "description": "Components for Vue DnD Kit",
5 | "author": "ZiZIGY",
6 | "license": "MIT",
7 | "keywords": [
8 | "vue",
9 | "dnd",
10 | "drag",
11 | "drop",
12 | "drag and drop",
13 | "drag and drop library",
14 | "vue dnd",
15 | "vue drag and drop",
16 | "vue drag and drop library",
17 | "vue dnd kit",
18 | "vue dnd kit components",
19 | "dnd-kit",
20 | "dnd-kit components",
21 | "vue-dnd-kit",
22 | "vue-dnd-kit components",
23 | "@vue-dnd-kit/components"
24 | ],
25 | "repository": {
26 | "type": "git",
27 | "url": "git+https://github.com/zizigy/vue-dnd-kit.git",
28 | "directory": "packages/components"
29 | },
30 | "scripts": {
31 | "dev": "vite build --watch",
32 | "start": "vite build --watch",
33 | "build": "rm -rf dist && vite build",
34 | "test": "vitest run",
35 | "lint": "eslint src --ext .ts,.vue",
36 | "prepublishOnly": "npm run build"
37 | },
38 | "main": "./dist/vue-dnd-kit-components.umd.js",
39 | "module": "./dist/vue-dnd-kit-components.es.js",
40 | "unpkg": "./dist/vue-dnd-kit-components.umd.js",
41 | "types": "./dist/types/index.d.ts",
42 | "browser": {
43 | "./sfc": "./src/components/"
44 | },
45 | "files": [
46 | "README.md",
47 | "CHANGELOG.md",
48 | "LICENSE",
49 | "dist",
50 | "src/components/*.vue"
51 | ],
52 | "exports": {
53 | ".": {
54 | "types": "./dist/types/index.d.ts",
55 | "import": "./dist/vue-dnd-kit-components.es.js",
56 | "require": "./dist/vue-dnd-kit-components.umd.js"
57 | },
58 | "./sfc": "./src/components/",
59 | "./styles": "./dist/style.css"
60 | },
61 | "peerDependencies": {
62 | "@vue-dnd-kit/core": "^0.5.0",
63 | "@vueuse/core": "^10.0.0 || ^13.0.0",
64 | "vue": "^3.3.0 || ^3.4.0 || ^3.5.0"
65 | },
66 | "devDependencies": {
67 | "@types/node": "^22.13.14",
68 | "@vitejs/plugin-vue": "^4.2.3",
69 | "@vueuse/core": "^13.1.0",
70 | "eslint": "^8.38.0",
71 | "typescript": "^4.9.5",
72 | "vite": "^6.0.5",
73 | "vite-plugin-dts": "^4.5.0",
74 | "vitepress": "^1.6.3",
75 | "vue": "^3.5.13",
76 | "vue-tsc": "^1.8.27"
77 | },
78 | "publishConfig": {
79 | "access": "public",
80 | "registry": "https://registry.npmjs.org/"
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/packages/components/src/components/Draggable.vue:
--------------------------------------------------------------------------------
1 |
64 |
65 |
66 | !preventRootDrag && handleDragStart(event)"
78 | @keydown.space.self="
79 | (event: KeyboardEvent) => !preventRootDrag && handleDragStart(event)
80 | "
81 | >
82 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/packages/components/src/components/Droppable.vue:
--------------------------------------------------------------------------------
1 |
35 |
36 |
37 |
46 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/packages/components/src/components/Kanban/Kanban.vue:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/packages/components/src/components/Kanban/KanbanColumn.vue:
--------------------------------------------------------------------------------
1 |
40 |
41 |
42 |
73 |
74 |
88 | {{ title }} {{ index }}
89 |
90 |
91 |
98 |
110 |
111 |
112 |
125 |
126 |
127 |
--------------------------------------------------------------------------------
/packages/components/src/components/Kanban/KanbanItem.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
39 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/packages/components/src/components/Table/Table.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/packages/components/src/components/Table/TableBody.vue:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 |
30 |
39 |
44 |
50 | {{ row[column.key] }}
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/packages/components/src/components/Table/TableHead.vue:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
22 |
23 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/packages/components/src/index.ts:
--------------------------------------------------------------------------------
1 | // Экспортируем утилиты
2 | import * as classNames from './utils/classNames';
3 |
4 | // Экспортируем компоненты по отдельности
5 | import Draggable from './components/Draggable.vue';
6 | import Droppable from './components/Droppable.vue';
7 | import Kanban from './components/Kanban/Kanban.vue';
8 | import KanbanColumn from './components/Kanban/KanbanColumn.vue';
9 | import KanbanItem from './components/Kanban/KanbanItem.vue';
10 | import Table from './components/Table/Table.vue';
11 | import TableBody from './components/Table/TableBody.vue';
12 | import TableHead from './components/Table/TableHead.vue';
13 |
14 | export {
15 | Draggable,
16 | Droppable,
17 | Kanban,
18 | KanbanColumn,
19 | KanbanItem,
20 | Table,
21 | TableBody,
22 | TableHead,
23 | classNames,
24 | };
25 |
--------------------------------------------------------------------------------
/packages/components/src/shims-vue.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.vue' {
2 | import type { DefineComponent } from 'vue';
3 | const component: DefineComponent<{}, {}, any>;
4 | export default component;
5 | }
6 |
--------------------------------------------------------------------------------
/packages/components/src/types/index.d.ts:
--------------------------------------------------------------------------------
1 | export interface IKanbanColumn {
2 | title?: string;
3 | [key: string]: any;
4 | items: any[];
5 | }
6 |
7 | export interface ITableColumn {
8 | key: string;
9 | name: string;
10 | }
11 |
--------------------------------------------------------------------------------
/packages/components/src/utils/classNames.ts:
--------------------------------------------------------------------------------
1 | export enum DraggableClassNames {
2 | DRAGGABLE = 'dnd-kit-draggable',
3 | DRAGGABLE_DISABLED = 'dnd-kit-draggable-disabled',
4 | DRAGGABLE_ACTIVE = 'dnd-kit-draggable-active',
5 | DRAGGABLE_SELECTED = 'dnd-kit-draggable-selected',
6 | DRAGGABLE_ALLOWED = 'dnd-kit-draggable-allowed',
7 | DRAGGABLE_OVERED = 'dnd-kit-draggable-overed',
8 | }
9 |
10 | export enum DroppableClassNames {
11 | DROPPABLE = 'dnd-kit-droppable',
12 | DROPPABLE_DISABLED = 'dnd-kit-droppable-disabled',
13 | DROPPABLE_ACTIVE = 'dnd-kit-droppable-active',
14 | }
15 |
--------------------------------------------------------------------------------
/packages/components/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | declare module '*.vue' {
4 | import type { DefineComponent } from 'vue';
5 |
6 | const component: DefineComponent<{}, {}, any>;
7 | export default component;
8 | }
9 |
--------------------------------------------------------------------------------
/packages/components/src/vue-shims.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.vue' {
2 | import type { DefineComponent } from 'vue';
3 | const component: DefineComponent<{}, {}, any>;
4 | export default component;
5 | }
6 |
--------------------------------------------------------------------------------
/packages/components/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "module": "ESNext",
6 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "node",
11 | "resolveJsonModule": true,
12 | "isolatedModules": true,
13 |
14 | /* Важно: добавляем настройки для генерации .d.ts файлов */
15 | "declaration": true,
16 | "declarationDir": "./dist/types",
17 | "emitDeclarationOnly": true,
18 |
19 | "jsx": "preserve",
20 | "esModuleInterop": true,
21 |
22 | /* Linting */
23 | "strict": true,
24 | "noUnusedLocals": true,
25 | "noUnusedParameters": true,
26 | "noFallthroughCasesInSwitch": true
27 | },
28 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue"],
29 | "exclude": ["node_modules", "dist", "**/*.test.ts"],
30 | "references": [{ "path": "./tsconfig.node.json" }]
31 | }
32 |
--------------------------------------------------------------------------------
/packages/components/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "ESNext",
5 | "moduleResolution": "Node",
6 | "allowSyntheticDefaultImports": true
7 | },
8 | "include": ["vite.config.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/components/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 | import dts from 'vite-plugin-dts';
3 | import { resolve } from 'path';
4 | import vue from '@vitejs/plugin-vue';
5 |
6 | export default defineConfig({
7 | plugins: [
8 | vue(),
9 | dts({
10 | include: ['src/**/*.ts', 'src/**/*.vue'],
11 | outDir: 'dist/types',
12 | cleanVueFileName: true,
13 | staticImport: true,
14 | insertTypesEntry: true,
15 | copyDtsFiles: false,
16 | beforeWriteFile: (filePath, content) => {
17 | let newPath = filePath
18 | .replace('/src/components/', '/components/')
19 | .replace('/src/', '/');
20 |
21 | return {
22 | filePath: newPath,
23 | content: content,
24 | };
25 | },
26 |
27 | }),
28 | ],
29 | build: {
30 | emptyOutDir: true,
31 | lib: {
32 | entry: resolve(__dirname, 'src/index.ts'),
33 | name: 'VueDndKitComponents',
34 | formats: ['es', 'umd'],
35 | fileName: (format) => `vue-dnd-kit-components.${format}.js`,
36 | },
37 | rollupOptions: {
38 | external: ['vue', '@vue-dnd-kit/core', '@vueuse/core'],
39 | output: {
40 | globals: {
41 | vue: 'Vue',
42 | '@vue-dnd-kit/core': 'VueDndKitCore',
43 | '@vueuse/core': 'VueUse',
44 | },
45 | exports: 'named',
46 | },
47 | },
48 | cssCodeSplit: false,
49 | cssMinify: true,
50 | sourcemap: true,
51 | },
52 | resolve: {
53 | alias: {
54 | '@vue-dnd-kit/core': resolve(__dirname, '../core/src'),
55 | '@vue-dnd-kit/components': resolve(__dirname, './src'),
56 | '@': resolve(__dirname, './src'),
57 | },
58 | dedupe: ['vue'],
59 | },
60 | optimizeDeps: {
61 | include: ['vue'],
62 | exclude: ['@vue-dnd-kit/core'],
63 | },
64 | });
65 |
66 | // export default defineConfig({
67 | // build: {
68 | // emptyOutDir: false,
69 | // lib: {
70 | // entry: path.resolve(__dirname, 'src/main.ts'),
71 | // formats: ['es'],
72 | // name: 'UiLib',
73 | // },
74 | // rollupOptions: {
75 | // external: ['vue'],
76 | // output: {
77 | // globals: {
78 | // Vue: 'vue',
79 | // },
80 | // },
81 | // },
82 | // },
83 | // plugins: [vue(), dts()],
84 | // });
85 |
--------------------------------------------------------------------------------
/packages/core/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 ZiZiGY
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vue-dnd-kit/core",
3 | "version": "1.5.0",
4 | "description": "Core functionality for Vue DnD Kit - a lightweight Vue 3 library for building performant and accessible drag and drop interfaces",
5 | "author": "ZiZIGY",
6 | "license": "MIT",
7 | "keywords": [
8 | "vue",
9 | "dnd",
10 | "drag",
11 | "drop",
12 | "drag and drop",
13 | "drag and drop library",
14 | "vue dnd",
15 | "vue drag and drop",
16 | "vue drag and drop library",
17 | "vue dnd kit",
18 | "vue dnd kit core",
19 | "dnd-kit",
20 | "dnd-kit core",
21 | "vue-dnd-kit",
22 | "vue-dnd-kit core",
23 | "@vue-dnd-kit/core"
24 | ],
25 | "repository": {
26 | "type": "git",
27 | "url": "git+https://github.com/zizigy/vue-dnd-kit.git",
28 | "directory": "packages/core"
29 | },
30 | "scripts": {
31 | "dev": "yarn build --watch",
32 | "start": "vite build --watch",
33 | "build": "vite build",
34 | "test": "vitest run",
35 | "lint": "eslint src --ext .ts,.vue",
36 | "prepublishOnly": "vite build"
37 | },
38 | "main": "dist/vue-dnd-kit-core.cjs.js",
39 | "module": "dist/vue-dnd-kit-core.es.js",
40 | "types": "dist/index.d.ts",
41 | "exports": {
42 | ".": {
43 | "types": "./dist/index.d.ts",
44 | "import": "./dist/vue-dnd-kit-core.es.js",
45 | "require": "./dist/vue-dnd-kit-core.cjs.js"
46 | }
47 | },
48 | "files": [
49 | "README.md",
50 | "CHANGELOG.md",
51 | "LICENSE",
52 | "dist"
53 | ],
54 | "peerDependencies": {
55 | "@vueuse/core": "^13.1.0",
56 | "vue": "^3.5.13"
57 | },
58 | "devDependencies": {
59 | "@types/node": "^22.13.14",
60 | "@vitejs/plugin-vue": "^4.2.3",
61 | "@vueuse/core": "^13.1.0",
62 | "eslint": "^8.38.0",
63 | "typescript": "^4.9.5",
64 | "vite": "^6.0.5",
65 | "vite-plugin-dts": "^4.5.0",
66 | "vitepress": "^1.6.3",
67 | "vue": "^3.5.13"
68 | },
69 | "publishConfig": {
70 | "access": "public",
71 | "registry": "https://registry.npmjs.org/"
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/packages/core/src/components/DefaultOverlay.vue:
--------------------------------------------------------------------------------
1 |
28 |
29 |
30 |
57 |
58 |
--------------------------------------------------------------------------------
/packages/core/src/components/DragOverlay.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
46 |
50 |
51 |
52 |
57 |
58 |
--------------------------------------------------------------------------------
/packages/core/src/composables/useDnDStore.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | IDragElement,
3 | IDraggingElement,
4 | IDropZone,
5 | IPoint,
6 | } from '../types';
7 | import {
8 | computed,
9 | ref,
10 | shallowRef,
11 | type Component,
12 | type TransitionProps,
13 | } from 'vue';
14 | import { createGlobalState, useMagicKeys } from '@vueuse/core';
15 |
16 | export const useDnDStore = createGlobalState(() => {
17 | const isPending = shallowRef(false);
18 | const draggingElements = ref>(
19 | new Map()
20 | );
21 | const isDragging = computed(() => draggingElements.value.size > 0);
22 |
23 | const activeContainer = {
24 | component: ref(null),
25 | ref: shallowRef(null),
26 | options: shallowRef(null),
27 | animating: {
28 | enter: shallowRef(false),
29 | leave: shallowRef(false),
30 | appear: shallowRef(false),
31 | },
32 | };
33 |
34 | const elementsMap = ref>(new Map());
35 | const selectedElements = ref>(new Set());
36 | const zonesMap = ref>(new Map());
37 | const visibleZones = shallowRef>(new Set());
38 | const visibleElements = shallowRef>(new Set());
39 |
40 | const hasIntersectionObserver =
41 | typeof window !== 'undefined' && 'IntersectionObserver' in window;
42 |
43 | const elementObserver = hasIntersectionObserver
44 | ? new IntersectionObserver((entries) => {
45 | entries.forEach((entry) =>
46 | visibleElements.value[entry.isIntersecting ? 'add' : 'delete'](
47 | entry.target
48 | )
49 | );
50 | })
51 | : null;
52 |
53 | const zoneObserver = hasIntersectionObserver
54 | ? new IntersectionObserver((entries) => {
55 | entries.forEach((entry) =>
56 | visibleZones.value[entry.isIntersecting ? 'add' : 'delete'](
57 | entry.target
58 | )
59 | );
60 | })
61 | : null;
62 |
63 | const handleDragElementIntersection = (
64 | action: 'add' | 'remove',
65 | element: HTMLElement | Element
66 | ) => {
67 | if (!elementObserver) return;
68 |
69 | if (action === 'add') {
70 | elementObserver.observe(element);
71 | } else {
72 | elementObserver.unobserve(element);
73 | visibleElements.value.delete(element);
74 | }
75 | };
76 |
77 | const handleDropZoneIntersection = (
78 | action: 'add' | 'remove',
79 | element: HTMLElement | Element
80 | ) => {
81 | if (!zoneObserver) return;
82 |
83 | if (action === 'add') {
84 | zoneObserver.observe(element);
85 | } else {
86 | zoneObserver.unobserve(element);
87 | visibleZones.value.delete(element);
88 | }
89 | };
90 |
91 | const pointerPosition = {
92 | start: shallowRef(null),
93 | current: shallowRef(null),
94 | offset: {
95 | percent: shallowRef(null),
96 | pixel: shallowRef(null),
97 | },
98 | };
99 |
100 | const { w, s, a, d, ctrl, shift, alt, meta } = useMagicKeys();
101 |
102 | const hovered = {
103 | zone: shallowRef(null),
104 | element: shallowRef(null),
105 | };
106 |
107 | return {
108 | draggingElements,
109 | isDragging,
110 | activeContainer,
111 | elementsMap,
112 | selectedElements,
113 | zonesMap,
114 | visibleZones,
115 | visibleElements,
116 | pointerPosition,
117 | keyboard: {
118 | w,
119 | s,
120 | a,
121 | d,
122 | ctrl,
123 | shift,
124 | alt,
125 | meta,
126 | },
127 | hovered,
128 | isPending,
129 | handleDragElementIntersection,
130 | handleDropZoneIntersection,
131 | };
132 | });
133 |
--------------------------------------------------------------------------------
/packages/core/src/composables/useDragContainer.ts:
--------------------------------------------------------------------------------
1 | import { onUnmounted, onMounted, ref, type TransitionProps } from 'vue';
2 |
3 | import { useDnDStore } from './useDnDStore';
4 |
5 | export const useDragContainer = (options?: Omit) => {
6 | const elementRef = ref(null);
7 |
8 | const { draggingElements, pointerPosition, isDragging, activeContainer } =
9 | useDnDStore();
10 |
11 | onMounted(() => {
12 | activeContainer.ref = elementRef;
13 | activeContainer.options.value = options ?? null;
14 | });
15 |
16 | onUnmounted(() => {
17 | activeContainer.ref.value = null;
18 | activeContainer.options.value = null;
19 | });
20 |
21 | return {
22 | elementRef,
23 | draggingElements,
24 | pointerPosition,
25 | isDragging,
26 | animating: activeContainer.animating,
27 | };
28 | };
29 |
--------------------------------------------------------------------------------
/packages/core/src/composables/useDraggable.ts:
--------------------------------------------------------------------------------
1 | import { onBeforeUnmount, onMounted } from 'vue';
2 |
3 | import type { IUseDragOptions } from '../types';
4 | import { useDnDStore } from './useDnDStore';
5 | import { useElementManager } from '../managers/useElementManager';
6 | import { useEventManager } from '../managers/useEventManager';
7 |
8 | export const useDraggable = (options?: IUseDragOptions) => {
9 | const {
10 | id,
11 | elementRef,
12 | isDragging,
13 | isOvered,
14 | isAllowed,
15 | isLazyAllowed,
16 | registerElement,
17 | unregisterElement,
18 | } = useElementManager(options);
19 |
20 | const { pointerPosition } = useDnDStore();
21 | const { handleDragStart: start } = useEventManager();
22 |
23 | const handleDragStart = (event: PointerEvent | KeyboardEvent) =>
24 | start(event, elementRef, options);
25 |
26 | onMounted(registerElement);
27 | onBeforeUnmount(unregisterElement);
28 |
29 | return {
30 | pointerPosition,
31 | elementRef,
32 | isDragging,
33 | isOvered,
34 | isAllowed,
35 | isLazyAllowed,
36 | handleDragStart,
37 | id,
38 | };
39 | };
40 |
--------------------------------------------------------------------------------
/packages/core/src/composables/useDroppable.ts:
--------------------------------------------------------------------------------
1 | import { onBeforeUnmount, onMounted } from 'vue';
2 |
3 | import type { IUseDropOptions } from '../types';
4 | import { useZoneManager } from '../managers/useZoneManager';
5 |
6 | export const useDroppable = (options?: IUseDropOptions) => {
7 | const {
8 | elementRef,
9 | registerZone,
10 | unregisterZone,
11 | isOvered,
12 | isAllowed,
13 | isLazyAllowed,
14 | } = useZoneManager(options);
15 |
16 | // Register/unregister drop zone with store
17 | onMounted(registerZone);
18 | onBeforeUnmount(unregisterZone);
19 |
20 | return { elementRef, isOvered, isAllowed, isLazyAllowed };
21 | };
22 |
--------------------------------------------------------------------------------
/packages/core/src/composables/useKeyboard.ts:
--------------------------------------------------------------------------------
1 | import type { Ref } from 'vue';
2 | import { getBoundingBox } from '../utils/geometry';
3 | import { preventEvent } from '../utils/events';
4 | import { useDnDStore } from './useDnDStore';
5 |
6 | export interface IKeyboardOptions {
7 | moveStep?: number;
8 | }
9 |
10 | export const useKeyboard = (
11 | elementRef: Ref,
12 | options?: IKeyboardOptions
13 | ) => {
14 | const { pointerPosition, keyboard } = useDnDStore();
15 | const moveStep = options?.moveStep || 10;
16 |
17 | const onKeyboardStart = (event: KeyboardEvent) => {
18 | preventEvent(event);
19 |
20 | const rect = getBoundingBox(elementRef.value);
21 |
22 | elementRef.value?.blur();
23 |
24 | const centerX = rect.x + rect.width / 2;
25 | const centerY = rect.y + rect.height / 2;
26 |
27 | pointerPosition.start.value = {
28 | x: centerX,
29 | y: centerY,
30 | };
31 |
32 | pointerPosition.current.value = {
33 | x: centerX,
34 | y: centerY,
35 | };
36 |
37 | pointerPosition.offset.pixel.value = {
38 | x: rect.width / 2,
39 | y: rect.height / 2,
40 | };
41 |
42 | pointerPosition.offset.percent.value = {
43 | x: 50,
44 | y: 50,
45 | };
46 | };
47 |
48 | const onKeyboardMove = () => {
49 | if (!pointerPosition.current.value) return;
50 |
51 | const currentX = pointerPosition.current.value.x;
52 | const currentY = pointerPosition.current.value.y;
53 |
54 | let newX = currentX;
55 | let newY = currentY;
56 |
57 | if (keyboard.w.value) newY -= moveStep;
58 | if (keyboard.s.value) newY += moveStep;
59 | if (keyboard.a.value) newX -= moveStep;
60 | if (keyboard.d.value) newX += moveStep;
61 |
62 | pointerPosition.current.value = {
63 | x: newX,
64 | y: newY,
65 | };
66 | };
67 |
68 | const onKeyboardEnd = () => {
69 | pointerPosition.current.value = null;
70 | pointerPosition.start.value = null;
71 | pointerPosition.offset.pixel.value = null;
72 | pointerPosition.offset.percent.value = null;
73 | };
74 |
75 | return {
76 | onKeyboardStart,
77 | onKeyboardMove,
78 | onKeyboardEnd,
79 | };
80 | };
81 |
--------------------------------------------------------------------------------
/packages/core/src/composables/usePointer.ts:
--------------------------------------------------------------------------------
1 | import type { Ref } from 'vue';
2 | import { getOffset } from '../utils/geometry';
3 | import { useDnDStore } from './useDnDStore';
4 |
5 | export const usePointer = (elementRef: Ref) => {
6 | const store = useDnDStore();
7 |
8 | const onPointerStart = (event: PointerEvent) => {
9 | store.pointerPosition.start.value = { x: event.clientX, y: event.clientY };
10 | store.pointerPosition.current.value = {
11 | x: event.clientX,
12 | y: event.clientY,
13 | };
14 |
15 | const { pixel, percent } = getOffset(elementRef.value, {
16 | x: event.clientX,
17 | y: event.clientY,
18 | });
19 |
20 | store.pointerPosition.offset.pixel.value = pixel;
21 | store.pointerPosition.offset.percent.value = percent;
22 | };
23 |
24 | const onPointerMove = (event: PointerEvent | WheelEvent) => {
25 | if (store.isPending.value) return;
26 |
27 | store.pointerPosition.current.value = {
28 | x: event.clientX,
29 | y: event.clientY,
30 | };
31 | };
32 |
33 | const onPointerEnd = () => {
34 | store.pointerPosition.current.value = null;
35 | store.pointerPosition.start.value = null;
36 | store.pointerPosition.offset.pixel.value = null;
37 | store.pointerPosition.offset.percent.value = null;
38 | };
39 |
40 | return {
41 | onPointerStart,
42 | onPointerMove,
43 | onPointerEnd,
44 | };
45 | };
46 |
--------------------------------------------------------------------------------
/packages/core/src/composables/useSelection.ts:
--------------------------------------------------------------------------------
1 | import { computed, type Ref } from 'vue';
2 | import { useDnDStore } from './useDnDStore';
3 | import { isDescendant } from '../utils/dom';
4 |
5 | export const useSelection = (elementRef: Ref) => {
6 | const { selectedElements, elementsMap } = useDnDStore();
7 |
8 | const isSelected = computed(() =>
9 | elementRef.value ? selectedElements.value.has(elementRef.value) : false
10 | );
11 |
12 | const isParentOfSelected = computed(() => {
13 | if (!elementRef.value) return false;
14 |
15 | for (const node of selectedElements.value) {
16 | if (
17 | node &&
18 | isDescendant(node as HTMLElement, elementRef.value as HTMLElement)
19 | ) {
20 | return true;
21 | }
22 | }
23 | return false;
24 | });
25 |
26 | const hasSelectedParent = computed(() => {
27 | if (!elementRef.value) return false;
28 |
29 | for (const node of selectedElements.value) {
30 | if (
31 | node &&
32 | isDescendant(elementRef.value as HTMLElement, node as HTMLElement)
33 | ) {
34 | return true;
35 | }
36 | }
37 | return false;
38 | });
39 |
40 | const handleUnselect = () => {
41 | if (!elementRef.value) return;
42 | selectedElements.value.delete(elementRef.value);
43 | };
44 |
45 | const handleSelect = () => {
46 | if (!elementRef.value) return;
47 | const element = elementsMap.value.get(elementRef.value);
48 | if (!element) return;
49 |
50 | if (isParentOfSelected.value) {
51 | // Удаляем все дочерние выбранные элементы
52 | for (const node of selectedElements.value) {
53 | if (
54 | node &&
55 | isDescendant(node as HTMLElement, elementRef.value as HTMLElement)
56 | ) {
57 | selectedElements.value.delete(node);
58 | }
59 | }
60 | }
61 |
62 | if (hasSelectedParent.value) {
63 | // Удаляем все родительские выбранные элементы
64 | for (const node of selectedElements.value) {
65 | if (
66 | node &&
67 | isDescendant(elementRef.value as HTMLElement, node as HTMLElement)
68 | ) {
69 | selectedElements.value.delete(node);
70 | }
71 | }
72 | }
73 |
74 | selectedElements.value.add(elementRef.value);
75 | };
76 |
77 | const handleToggleSelect = () => {
78 | if (!elementRef.value) return;
79 |
80 | selectedElements.value.has(elementRef.value)
81 | ? handleUnselect()
82 | : handleSelect();
83 | };
84 |
85 | return {
86 | handleUnselect,
87 | handleSelect,
88 | handleToggleSelect,
89 | isSelected,
90 | isParentOfSelected,
91 | };
92 | };
93 |
--------------------------------------------------------------------------------
/packages/core/src/index.ts:
--------------------------------------------------------------------------------
1 | import { DnDOperations } from './utils/operations';
2 | import { VueDndKitPlugin } from './plugin';
3 | import { getBoundingBox } from './utils/geometry';
4 | import { useDnDStore } from './composables/useDnDStore';
5 | import { useDragContainer } from './composables/useDragContainer';
6 | import { useDraggable } from './composables/useDraggable';
7 | import { useDroppable } from './composables/useDroppable';
8 | import { useSelection } from './composables/useSelection';
9 |
10 | export default VueDndKitPlugin;
11 |
12 | export {
13 | useDraggable,
14 | useDroppable,
15 | useDnDStore,
16 | useSelection,
17 | getBoundingBox,
18 | DnDOperations,
19 | useDragContainer,
20 | };
21 |
22 | export type {
23 | IDnDStore,
24 | IActiveContainer,
25 | IDragElement,
26 | IDraggingElement,
27 | IDropZone,
28 | TLayerProps,
29 | IPluginOptions,
30 | } from './types';
31 |
--------------------------------------------------------------------------------
/packages/core/src/managers/useElementManager.ts:
--------------------------------------------------------------------------------
1 | import { computed, ref, shallowRef, useId } from 'vue';
2 |
3 | import type { IUseDragOptions } from '../types';
4 | import { draggableDataName } from '../utils/namespaces';
5 | import { preventEvent } from '../utils/events';
6 | import { useDnDStore } from '../composables/useDnDStore';
7 |
8 | export const useElementManager = (options?: IUseDragOptions) => {
9 | const {
10 | elementsMap,
11 | draggingElements,
12 | hovered,
13 | selectedElements,
14 | isDragging: isDragStarted,
15 | visibleElements,
16 | handleDragElementIntersection,
17 | } = useDnDStore();
18 |
19 | const elementRef = ref(null);
20 | const isOvered = computed(
21 | () =>
22 | visibleElements.value.has(elementRef.value as HTMLElement) &&
23 | hovered.element.value === elementRef.value
24 | );
25 |
26 | const id = shallowRef(options?.id || useId());
27 |
28 | const isDragging = computed(() => {
29 | if (!elementRef.value) return false;
30 | if (!elementsMap.value.has(elementRef.value)) return false;
31 | return draggingElements.value.has(elementRef.value);
32 | });
33 |
34 | const isAllowed = computed(() => {
35 | if (!elementRef.value || !isDragStarted.value) return false;
36 | if (!visibleElements.value.has(elementRef.value)) return false;
37 |
38 | const currentElement = elementsMap.value.get(elementRef.value);
39 | if (!currentElement?.groups.length) return true;
40 |
41 | return !Array.from(draggingElements.value.entries()).some(
42 | ([_, draggingElement]) => {
43 | if (!draggingElement.groups.length) return false;
44 | return !draggingElement.groups.some((group) =>
45 | currentElement.groups.includes(group)
46 | );
47 | }
48 | );
49 | });
50 |
51 | const isLazyAllowed = computed(() => {
52 | if (!elementRef.value || !isDragStarted.value) return false;
53 | if (!visibleElements.value.has(elementRef.value)) return false;
54 | if (hovered.element.value !== elementRef.value) return false;
55 |
56 | const currentElement = elementsMap.value.get(elementRef.value);
57 | if (!currentElement?.groups.length) return true;
58 |
59 | return !Array.from(draggingElements.value.entries()).some(
60 | ([_, draggingElement]) => {
61 | if (!draggingElement.groups.length) return false;
62 | return !draggingElement.groups.some((group) =>
63 | currentElement.groups.includes(group)
64 | );
65 | }
66 | );
67 | });
68 |
69 | const registerElement = () => {
70 | if (!elementRef.value) throw new Error('ElementRef is not set');
71 |
72 | elementsMap.value.set(elementRef.value, {
73 | node: elementRef.value,
74 | groups: options?.groups ?? [],
75 | layer: options?.layer ?? null,
76 | defaultLayer: options?.layer ?? null,
77 | events: options?.events ?? {},
78 | data: options?.data ?? null,
79 | id: id.value,
80 | });
81 |
82 | handleDragElementIntersection('add', elementRef.value);
83 |
84 | elementRef.value.addEventListener('dragstart', preventEvent);
85 | elementRef.value.addEventListener('drag', preventEvent);
86 | elementRef.value.setAttribute(draggableDataName, 'true');
87 | elementRef.value.setAttribute('draggable', 'false');
88 | };
89 |
90 | const unregisterElement = () => {
91 | if (!elementRef.value) return;
92 |
93 | elementsMap.value.delete(elementRef.value);
94 | selectedElements.value.delete(elementRef.value);
95 |
96 | elementRef.value.removeEventListener('dragstart', preventEvent);
97 | elementRef.value.removeEventListener('drag', preventEvent);
98 | elementRef.value.removeAttribute(draggableDataName);
99 | elementRef.value.removeAttribute('draggable');
100 | };
101 |
102 | return {
103 | elementRef,
104 | registerElement,
105 | unregisterElement,
106 | isDragging,
107 | isOvered,
108 | isAllowed,
109 | isLazyAllowed,
110 | id,
111 | };
112 | };
113 |
--------------------------------------------------------------------------------
/packages/core/src/managers/useZoneManager.ts:
--------------------------------------------------------------------------------
1 | import { computed, ref } from 'vue';
2 |
3 | import type { IUseDropOptions } from '../types';
4 | import { useDnDStore } from '../composables/useDnDStore';
5 |
6 | export const useZoneManager = (options?: IUseDropOptions) => {
7 | const {
8 | zonesMap,
9 | hovered,
10 | draggingElements,
11 | isDragging,
12 | handleDropZoneIntersection,
13 | } = useDnDStore();
14 |
15 | const elementRef = ref(null);
16 |
17 | const isOvered = computed(
18 | () => hovered.zone.value === elementRef.value
19 | );
20 |
21 | const isAllowed = computed(() => {
22 | if (!elementRef.value || !isDragging.value) return false;
23 |
24 | const currentZone = zonesMap.value.get(elementRef.value);
25 | if (!currentZone?.groups.length) return true;
26 |
27 | return !Array.from(draggingElements.value.values()).some((element) => {
28 | if (!element.groups.length) return false;
29 | return !element.groups.some((group) =>
30 | currentZone.groups.includes(group)
31 | );
32 | });
33 | });
34 |
35 | const isLazyAllowed = computed(() => {
36 | if (!elementRef.value || !isDragging.value) return false;
37 | if (hovered.zone.value !== elementRef.value) return false;
38 |
39 | const currentZone = zonesMap.value.get(elementRef.value);
40 | if (!currentZone?.groups.length) return true;
41 |
42 | return !Array.from(draggingElements.value.values()).some((element) => {
43 | return !element.groups.some((group) =>
44 | currentZone.groups.includes(group)
45 | );
46 | });
47 | });
48 |
49 | const registerZone = () => {
50 | if (!elementRef.value) throw new Error('elementRef is not set');
51 |
52 | handleDropZoneIntersection('add', elementRef.value);
53 |
54 | zonesMap.value.set(elementRef.value, {
55 | node: elementRef.value,
56 | groups: options?.groups ?? [],
57 | events: options?.events ?? {},
58 | data: options?.data ?? undefined,
59 | });
60 |
61 | elementRef.value.setAttribute('data-dnd-droppable', 'true');
62 | };
63 |
64 | const unregisterZone = () => {
65 | if (!elementRef.value) return;
66 |
67 | handleDropZoneIntersection('remove', elementRef.value);
68 |
69 | zonesMap.value.delete(elementRef.value);
70 | };
71 |
72 | return {
73 | elementRef,
74 | registerZone,
75 | unregisterZone,
76 | isOvered,
77 | isAllowed,
78 | isLazyAllowed,
79 | };
80 | };
81 |
--------------------------------------------------------------------------------
/packages/core/src/plugin.ts:
--------------------------------------------------------------------------------
1 | import { createVNode, render } from 'vue';
2 |
3 | import type { App } from 'vue';
4 | import DragOverlay from './components/DragOverlay.vue';
5 | import { IPluginOptions } from './types';
6 | import { useDnDStore } from './composables/useDnDStore';
7 |
8 | export const VueDndKitPlugin = {
9 | install(app: App, options?: IPluginOptions) {
10 | app.component('DragOverlay', DragOverlay);
11 | const originalMount = app.mount;
12 | app.mount = function (rootContainer) {
13 | const instance = originalMount.call(this, rootContainer);
14 |
15 | const rootEl =
16 | typeof rootContainer === 'string'
17 | ? document.querySelector(rootContainer)
18 | : rootContainer;
19 |
20 | if (rootEl && rootEl instanceof Element) {
21 | if (!rootEl.querySelector('#vue-dnd-kit-overlay')) {
22 | const overlayContainer = document.createElement('div');
23 |
24 | overlayContainer.id = 'vue-dnd-kit-overlay';
25 | overlayContainer.style.pointerEvents = 'none';
26 |
27 | rootEl.appendChild(overlayContainer);
28 |
29 | // Передаем опции напрямую в компонент
30 | const vnode = createVNode(DragOverlay, {
31 | styles: options?.defaultOverlay?.styles,
32 | });
33 | render(vnode, overlayContainer);
34 |
35 | app.__VUE_DND_KIT_OVERLAY__ = {
36 | container: overlayContainer,
37 | vnode,
38 | options: options?.defaultOverlay || {},
39 | };
40 |
41 | const store = useDnDStore();
42 | app.__VUE_DND_KIT_STORE__ = store;
43 | }
44 | }
45 |
46 | return instance;
47 | };
48 |
49 | // Модифицируем метод unmount для очистки
50 | const originalUnmount = app.unmount;
51 | app.unmount = function () {
52 | if (app.__VUE_DND_KIT_OVERLAY__) {
53 | render(null, app.__VUE_DND_KIT_OVERLAY__.container);
54 | delete app.__VUE_DND_KIT_OVERLAY__;
55 | }
56 | return originalUnmount.call(this);
57 | };
58 | },
59 | };
60 |
61 | declare module '@vue/runtime-core' {
62 | export interface App {
63 | __VUE_DND_KIT_OVERLAY__?: {
64 | container: HTMLElement;
65 | vnode: any;
66 | options: IPluginOptions['defaultOverlay'];
67 | };
68 | __VUE_DND_KIT_STORE__?: ReturnType;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/packages/core/src/types/index.d.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | Component,
3 | ComputedRef,
4 | CSSProperties,
5 | Ref,
6 | ShallowRef,
7 | } from 'vue';
8 | import { useDnDStore } from '../composables/useDnDStore';
9 |
10 | export interface IDnDStore extends ReturnType {}
11 |
12 | export interface IActiveContainer {
13 | component: Ref;
14 | ref: Ref;
15 | }
16 |
17 | export interface IPluginOptions {
18 | defaultOverlay?: {
19 | styles?: CSSProperties;
20 | };
21 | }
22 |
23 | export interface IPointerPosition {
24 | start: Ref;
25 | current: Ref;
26 | offset: {
27 | percent: Ref;
28 | pixel: Ref;
29 | };
30 | }
31 |
32 | export interface IDragElement {
33 | id: string | number;
34 | node: HTMLElement | Element | null;
35 | groups: string[];
36 | layer: Component | null;
37 | defaultLayer: Component | null;
38 | data: {
39 | source?: any[];
40 | index?: number;
41 | [key: string]: any;
42 | } | null;
43 | events: {
44 | onHover?: (store: IDnDStore, payload: IDnDPayload) => void;
45 | onLeave?: (store: IDnDStore, payload: IDnDPayload) => void;
46 | onEnd?: (store: IDnDStore, payload: IDnDPayload) => void;
47 | onStart?: (store: IDnDStore, payload: IDnDPayload) => void;
48 | onMove?: (store: IDnDStore, payload: IDnDPayload) => void;
49 | };
50 | }
51 |
52 | export type TLayerProps = Pick;
53 |
54 | export interface IDnDPayload {
55 | items: IDraggingElement[];
56 | }
57 |
58 | export interface IDraggingElement extends IDragElement {
59 | initialHTML: string;
60 | initialRect?: DOMRect;
61 | }
62 |
63 | export interface IDropZone {
64 | node: HTMLElement | Element | null;
65 | groups: string[];
66 | data?: {
67 | source?: any[];
68 | [key: string]: any;
69 | };
70 | events: {
71 | onHover?: (store: IDnDStore, payload: IDnDPayload) => void;
72 | onLeave?: (store: IDnDStore, payload: IDnDPayload) => void;
73 | onDrop?: (
74 | store: IDnDStore,
75 | payload: IDnDPayload
76 | ) => void | Promise;
77 | };
78 | }
79 |
80 | export interface IPoint {
81 | x: number;
82 | y: number;
83 | }
84 |
85 | export interface IUseDropOptions {
86 | groups?: string[];
87 | events?: {
88 | onDrop?: (
89 | store: IDnDStore,
90 | payload: IDnDPayload
91 | ) => void | Promise;
92 | onHover?: (store: IDnDStore, payload: IDnDPayload) => void;
93 | onLeave?: (store: IDnDStore, payload: IDnDPayload) => void;
94 | };
95 | data?: {
96 | source?: any[];
97 | [key: string]: any;
98 | };
99 | }
100 | export interface IUseDragOptions extends IUseSensorOptions {
101 | id?: string | number;
102 | groups?: string[];
103 | events?: {
104 | onEnd?: (store: IDnDStore, payload: IDnDPayload) => void;
105 | onStart?: (store: IDnDStore, payload: IDnDPayload) => void;
106 | onMove?: (store: IDnDStore, payload: IDnDPayload) => void;
107 | onHover?: (store: IDnDStore, payload: IDnDPayload) => void;
108 | onLeave?: (store: IDnDStore, payload: IDnDPayload) => void;
109 | };
110 | keyboard?: {
111 | moveStep?: number;
112 | };
113 | data?: {
114 | source?: any[];
115 | index?: number;
116 | [key: string]: any;
117 | };
118 | layer?: Component | null;
119 | container?: Component;
120 | }
121 |
122 | export type ISensor = (
123 | store: IDnDStore
124 | ) => HTMLElement | HTMLElement[] | Element | Element[] | null;
125 |
126 | export interface IBoundingBox {
127 | x: number;
128 | y: number;
129 | width: number;
130 | height: number;
131 | bottom: number;
132 | left: number;
133 | right: number;
134 | top: number;
135 | }
136 |
137 | export interface IUseSensorOptions {
138 | sensor?: {
139 | throttle?: number;
140 | setup?: ISensor;
141 | };
142 | }
143 |
144 | export interface ICollisionDetectionResult {
145 | element: HTMLElement | Element | null;
146 | zone: HTMLElement | Element | null;
147 | }
148 |
--------------------------------------------------------------------------------
/packages/core/src/utils/dom.ts:
--------------------------------------------------------------------------------
1 | export const isDescendant = (
2 | element: HTMLElement | Element | null,
3 | container: HTMLElement | Element
4 | ): boolean => {
5 | if (!element) return false;
6 | return container.contains(element);
7 | };
8 |
--------------------------------------------------------------------------------
/packages/core/src/utils/events.ts:
--------------------------------------------------------------------------------
1 | import { IDnDPayload, IDnDStore } from '../types';
2 |
3 | export const preventEvent = (event: Event) => {
4 | event.preventDefault();
5 | };
6 |
7 | export const createPayload = (store: IDnDStore): IDnDPayload => {
8 | return {
9 | items: Array.from(store.draggingElements.value.values()),
10 | };
11 | };
12 |
--------------------------------------------------------------------------------
/packages/core/src/utils/geometry.ts:
--------------------------------------------------------------------------------
1 | import type { IBoundingBox, IPoint } from '../types';
2 |
3 | export const checkCollision = (
4 | boxA: IBoundingBox,
5 | boxB: IBoundingBox
6 | ): boolean => {
7 | return (
8 | boxA.x < boxB.x + boxB.width &&
9 | boxA.x + boxA.width > boxB.x &&
10 | boxA.y < boxB.y + boxB.height &&
11 | boxA.y + boxA.height > boxB.y
12 | );
13 | };
14 |
15 | export const getBoundingBox = (element: HTMLElement | null): IBoundingBox => {
16 | if (!element)
17 | return {
18 | x: 0,
19 | y: 0,
20 | width: 0,
21 | height: 0,
22 | bottom: 0,
23 | left: 0,
24 | right: 0,
25 | top: 0,
26 | };
27 |
28 | const rect = element.getBoundingClientRect();
29 |
30 | return {
31 | bottom: rect.bottom,
32 | left: rect.left,
33 | right: rect.right,
34 | top: rect.top,
35 | x: rect.x,
36 | y: rect.y,
37 | width: rect.width,
38 | height: rect.height,
39 | };
40 | };
41 |
42 | export const getCenter = (box: IBoundingBox): IPoint => ({
43 | x: box.x + box.width / 2,
44 | y: box.y + box.height / 2,
45 | });
46 |
47 | export const getOffset = (element: HTMLElement | null, pointer: IPoint) => {
48 | const rect = getBoundingBox(element);
49 | return {
50 | pixel: {
51 | x: pointer.x - rect.x,
52 | y: pointer.y - rect.y,
53 | },
54 | percent: {
55 | x: ((pointer.x - rect.x) / rect.width) * 100,
56 | y: ((pointer.y - rect.y) / rect.height) * 100,
57 | },
58 | };
59 | };
60 |
61 | export const getDistance = (pointA: IPoint, pointB: IPoint): number => {
62 | const dx = pointB.x - pointA.x;
63 | const dy = pointB.y - pointA.y;
64 | return Math.sqrt(dx * dx + dy * dy);
65 | };
66 |
67 | export const getOverlapPercent = (
68 | boxA: IBoundingBox,
69 | boxB: IBoundingBox
70 | ): number => {
71 | const xOverlap = Math.max(
72 | 0,
73 | Math.min(boxA.x + boxA.width, boxB.x + boxB.width) -
74 | Math.max(boxA.x, boxB.x)
75 | );
76 | const yOverlap = Math.max(
77 | 0,
78 | Math.min(boxA.y + boxA.height, boxB.y + boxB.height) -
79 | Math.max(boxA.y, boxB.y)
80 | );
81 |
82 | const overlapArea = xOverlap * yOverlap;
83 |
84 | const boxAArea = boxA.width * boxA.height;
85 | const boxBArea = boxB.width * boxB.height;
86 |
87 | // Возвращаем среднее значение процентов перекрытия относительно обоих элементов
88 | return ((overlapArea / boxAArea) * 100 + (overlapArea / boxBArea) * 100) / 2;
89 | };
90 |
--------------------------------------------------------------------------------
/packages/core/src/utils/namespaces.ts:
--------------------------------------------------------------------------------
1 | export const draggableDataName = 'data-vue-dnd-kit-draggable';
2 | export const droppableDataName = 'data-vue-dnd-kit-droppable';
3 |
4 | export const VUE_DND_KIT_OPTIONS = Symbol('vue-dnd-kit-options');
5 |
--------------------------------------------------------------------------------
/packages/core/src/vue-shims.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.vue' {
2 | import type { DefineComponent } from 'vue';
3 | const component: DefineComponent<{}, {}, any>;
4 | export default component;
5 | }
6 |
--------------------------------------------------------------------------------
/packages/core/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "module": "ESNext",
6 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
7 | "skipLibCheck": true,
8 | "moduleResolution": "node",
9 | "allowSyntheticDefaultImports": true,
10 | "resolveJsonModule": true,
11 | "isolatedModules": true,
12 | "strict": true,
13 | "noUnusedLocals": true,
14 | "noUnusedParameters": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "declaration": true,
17 | "declarationDir": "./dist",
18 | "outDir": "./dist"
19 | },
20 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue"],
21 | "exclude": ["node_modules", "dist", "**/*.test.ts"]
22 | }
23 |
--------------------------------------------------------------------------------
/packages/core/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 | import dts from 'vite-plugin-dts';
3 | import { resolve } from 'path';
4 | import vue from '@vitejs/plugin-vue';
5 |
6 | export default defineConfig({
7 | plugins: [
8 | vue(),
9 | dts({
10 | include: ['src/**/*.ts', 'src/**/*.vue', 'src/types/**/*.ts'],
11 | exclude: ['node_modules/**', 'src/**/*.spec.ts'],
12 | rollupTypes: true,
13 | insertTypesEntry: true,
14 | // Оставляем copyDtsFiles: true для копирования .d.ts файлов
15 | copyDtsFiles: true,
16 | }),
17 | ],
18 | build: {
19 | lib: {
20 | entry: resolve(__dirname, 'src/index.ts'),
21 | name: 'VueDndKitCore',
22 | fileName: (format) => `vue-dnd-kit-core.${format}.js`,
23 | formats: ['es', 'cjs'],
24 | },
25 | rollupOptions: {
26 | external: ['vue', '@vueuse/core'],
27 | output: {
28 | globals: {
29 | vue: 'Vue',
30 | '@vueuse/core': 'VueUse',
31 | },
32 | // Удаляем preserveModules для создания единого бандла
33 | // preserveModules: true,
34 | // preserveModulesRoot: 'src',
35 | },
36 | },
37 | sourcemap: true,
38 | emptyOutDir: true,
39 | },
40 | resolve: {
41 | alias: {
42 | '@vue-dnd-kit/core': resolve(__dirname, './src'),
43 | '@': resolve(__dirname, './src'),
44 | './types': resolve(__dirname, './src/types'),
45 | },
46 | dedupe: ['vue'],
47 | },
48 | optimizeDeps: {
49 | include: ['vue'],
50 | exclude: ['@vueuse/core'],
51 | },
52 | });
53 |
--------------------------------------------------------------------------------
/packages/devtools/README.md:
--------------------------------------------------------------------------------
1 | # Vue Drag & Drop Library - DevTools Package
2 |
3 | [](https://github.com/zizigy/vue-dnd-kit)
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | DevTools package of the Vue Drag & Drop library with enhanced debugging capabilities.
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Extends Vue DevTools with specialized panels for debugging drag and drop operations
23 |
24 |
25 | ## Enhanced Debugging
26 |
27 | Vue DnD Kit DevTools provides specialized debugging tools for your drag and drop applications:
28 |
29 | - 🔍 **Store state visualization** - inspect the complete state of your DnD system
30 | - 📊 **Visual feedback** - see your drag elements, drop zones, and their relationships
31 | - 🔄 **Real-time updates** - observe state changes as they happen during interactions
32 | - 🧪 **Testing aid** - quickly identify issues in complex drag and drop scenarios
33 |
34 | ## Powerful Inspector Panels
35 |
36 | The DevTools package extends Vue DevTools with multiple specialized panels:
37 |
38 | - 📱 **Full Store Overview**
39 |
40 | - Complete visibility into the DnD store
41 | - Access to all internal state values
42 | - Reactive updates as operations occur
43 |
44 | - 🖱️ **Pointer Tracking**
45 |
46 | - Monitor cursor/touch position
47 | - View start and current positions
48 | - Inspect pixel and percentage offsets
49 |
50 | - 🧩 **Elements Inspector**
51 |
52 | - View all registered draggable elements
53 | - Track visibility status
54 | - Monitor selection state
55 |
56 | - 🎯 **Drop Zones Panel**
57 |
58 | - Inspect registered drop zones
59 | - Check visibility and hover states
60 | - Debug zone constraints
61 |
62 | - 📦 **Container Tools**
63 |
64 | - View active containers
65 | - Monitor component references
66 | - Debug container relationships
67 |
68 | - 👆 **Hover States**
69 |
70 | - Track elements under the cursor
71 | - Monitor intersection states
72 | - Debug hover-related issues
73 |
74 | ## Installation
75 |
76 | Choose your preferred package manager:
77 |
78 | ```bash
79 | npm install @vue-dnd-kit/devtools @vue-dnd-kit/core @vueuse/core
80 | ```
81 |
82 | ```bash
83 | yarn add @vue-dnd-kit/devtools @vue-dnd-kit/core @vueuse/core
84 | ```
85 |
86 | ```bash
87 | pnpm install @vue-dnd-kit/devtools @vue-dnd-kit/core @vueuse/core
88 | ```
89 |
90 | ## Setup
91 |
92 | Register the DevTools extension in your main.ts/js file:
93 |
94 | ```typescript
95 | import { createApp } from 'vue';
96 | import App from './App.vue';
97 | import VueDnDKitPlugin from '@vue-dnd-kit/core';
98 | import VueDnDKitDevtools from '@vue-dnd-kit/devtools';
99 |
100 | const app = createApp(App);
101 | app.use(VueDnDKitPlugin);
102 | app.use(VueDnDKitDevtools);
103 | app.mount('#app');
104 | ```
105 |
106 | ## Usage
107 |
108 | Once installed and registered, the DevTools extension will automatically:
109 |
110 | 1. Register with Vue DevTools
111 | 2. Add a "DnD Kit" panel to the inspector
112 | 3. Display real-time state information as you interact with your application
113 |
114 | No additional code is required in your components to enable the debugging features.
115 |
116 | ## 📄 License
117 |
118 | [MIT](LICENSE) © [ZiZiGY](https://github.com/ZiZiGY)
119 |
120 | ---
121 |
122 | 🔍 Enhance your debugging experience!
123 | Made with ❤️ for the Vue.js community
124 |
--------------------------------------------------------------------------------
/packages/devtools/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vue-dnd-kit/devtools",
3 | "version": "1.1.0",
4 | "description": "Vue DnD Kit DevTools Plugin",
5 | "main": "dist/vue-dnd-kit-devtools.cjs.js",
6 | "module": "dist/vue-dnd-kit-devtools.esm.js",
7 | "types": "dist/types/index.d.ts",
8 | "files": [
9 | "dist"
10 | ],
11 | "scripts": {
12 | "build": "rollup -c",
13 | "dev": "rollup -c -w"
14 | },
15 | "peerDependencies": {
16 | "vue": "^3.0.0",
17 | "@vue-dnd-kit/core": "workspace:*"
18 | },
19 | "dependencies": {
20 | "@rollup/plugin-terser": "^0.4.4",
21 | "@vue/devtools-api": "^6.0.0"
22 | },
23 | "devDependencies": {
24 | "@rollup/plugin-commonjs": "^28.0.3",
25 | "@rollup/plugin-node-resolve": "^16.0.1",
26 | "@rollup/plugin-terser": "^0.4.4",
27 | "@vue/devtools-api": "^6.0.0",
28 | "rollup": "^4.40.0",
29 | "rollup-plugin-terser": "^7.0.2",
30 | "rollup-plugin-typescript2": "^0.36.0",
31 | "typescript": "^4.5.2",
32 | "vue": "^3.2.0"
33 | },
34 | "publishConfig": {
35 | "access": "public",
36 | "registry": "https://registry.npmjs.org/"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/packages/devtools/rollup.config.js:
--------------------------------------------------------------------------------
1 | const typescript = require('rollup-plugin-typescript2');
2 | const terser = require('@rollup/plugin-terser');
3 | const resolve = require('@rollup/plugin-node-resolve');
4 | const commonjs = require('@rollup/plugin-commonjs');
5 |
6 | const pkg = require('./package.json');
7 |
8 | module.exports = [
9 | {
10 | input: 'src/index.ts',
11 | output: [
12 | {
13 | file: pkg.main,
14 | format: 'cjs',
15 | exports: 'named',
16 | sourcemap: true,
17 | },
18 | {
19 | file: pkg.module,
20 | format: 'esm',
21 | exports: 'named',
22 | sourcemap: true,
23 | },
24 | {
25 | file: 'dist/vue-dnd-kit-devtools.global.js',
26 | format: 'iife',
27 | name: 'VueDndKitDevtools',
28 | sourcemap: true,
29 | globals: {
30 | vue: 'Vue',
31 | '@vue/devtools-api': 'VueDevtoolsApi',
32 | },
33 | },
34 | {
35 | file: 'dist/vue-dnd-kit-devtools.global.min.js',
36 | format: 'iife',
37 | name: 'VueDndKitDevtools',
38 | sourcemap: true,
39 | globals: {
40 | vue: 'Vue',
41 | '@vue/devtools-api': 'VueDevtoolsApi',
42 | },
43 | plugins: [terser()],
44 | },
45 | ],
46 | external: ['vue', '@vue/devtools-api'],
47 | plugins: [
48 | typescript({
49 | tsconfigOverride: {
50 | compilerOptions: {
51 | declaration: true,
52 | declarationDir: 'dist/types',
53 | },
54 | include: ['src/**/*.ts'],
55 | exclude: ['node_modules', 'dist'],
56 | },
57 | abortOnError: false,
58 | }),
59 | resolve(),
60 | commonjs(),
61 | ],
62 | },
63 | ];
64 |
--------------------------------------------------------------------------------
/packages/devtools/src/global.d.ts:
--------------------------------------------------------------------------------
1 | interface Window {
2 | __VUE__?: any;
3 | __VUE_DEVTOOLS_GLOBAL_HOOK__?: {
4 | Vue?: any;
5 | enabled?: boolean;
6 | plugins?: Array;
7 | emit?: (event: string, ...args: any[]) => void;
8 | on?: (event: string, fn: Function) => void;
9 | once?: (event: string, fn: Function) => void;
10 | off?: (event: string, fn: Function) => void;
11 | };
12 | }
13 |
--------------------------------------------------------------------------------
/packages/devtools/src/index.ts:
--------------------------------------------------------------------------------
1 | import { App } from 'vue';
2 | import { setupDevtools } from './devtools';
3 |
4 | function setupDndKitDevtools(app: App) {
5 | if (process.env.NODE_ENV === 'development' || __VUE_PROD_DEVTOOLS__) {
6 | return setupDevtools(app);
7 | }
8 | return {};
9 | }
10 |
11 | export default setupDndKitDevtools;
12 |
13 | declare global {
14 | var __VUE_PROD_DEVTOOLS__: boolean;
15 | }
16 |
--------------------------------------------------------------------------------
/packages/devtools/src/utils/container.ts:
--------------------------------------------------------------------------------
1 | import { CustomInspectorState } from '@vue/devtools-api';
2 | import { useDnDStore } from '@vue-dnd-kit/core';
3 |
4 | export const createContainer = (
5 | store: ReturnType
6 | ): CustomInspectorState => ({
7 | state: [
8 | {
9 | key: 'component',
10 | value: store.activeContainer.component.value,
11 | objectType: 'ref',
12 | },
13 | {
14 | key: 'ref',
15 | value: store.activeContainer.ref.value,
16 | objectType: 'ref',
17 | },
18 | {
19 | key: 'animating',
20 | value: {
21 | appear: store.activeContainer.animating.appear.value,
22 | enter: store.activeContainer.animating.enter.value,
23 | leave: store.activeContainer.animating.leave.value,
24 | },
25 | objectType: 'ref',
26 | },
27 | {
28 | key: 'options',
29 | value: store.activeContainer.options.value,
30 | objectType: 'ref',
31 | },
32 | ],
33 | });
34 |
--------------------------------------------------------------------------------
/packages/devtools/src/utils/elements.ts:
--------------------------------------------------------------------------------
1 | import { CustomInspectorState } from '@vue/devtools-api';
2 | import { useDnDStore } from '@vue-dnd-kit/core';
3 |
4 | export const createElements = (
5 | store: ReturnType
6 | ): CustomInspectorState => ({
7 | state: [
8 | {
9 | key: 'elementsMap',
10 | value: store.elementsMap.value,
11 | objectType: 'ref',
12 | },
13 | {
14 | key: 'visibleElements',
15 | value: store.visibleElements.value,
16 | objectType: 'ref',
17 | },
18 | {
19 | key: 'selectedElements',
20 | value: store.selectedElements.value,
21 | objectType: 'ref',
22 | },
23 | {
24 | key: 'draggingElements',
25 | value: store.draggingElements.value,
26 | objectType: 'ref',
27 | },
28 | ],
29 | });
30 |
--------------------------------------------------------------------------------
/packages/devtools/src/utils/hovered.ts:
--------------------------------------------------------------------------------
1 | import { CustomInspectorState } from '@vue/devtools-api';
2 | import { useDnDStore } from '@vue-dnd-kit/core';
3 |
4 | export const createHovered = (
5 | store: ReturnType
6 | ): CustomInspectorState => ({
7 | state: [
8 | {
9 | key: 'element',
10 | value: store.hovered.element.value,
11 | },
12 | {
13 | key: 'zone',
14 | value: store.hovered.zone.value,
15 | },
16 | ],
17 | });
18 |
--------------------------------------------------------------------------------
/packages/devtools/src/utils/pointer.ts:
--------------------------------------------------------------------------------
1 | import type { CustomInspectorState } from '@vue/devtools-api';
2 | import { useDnDStore } from '@vue-dnd-kit/core';
3 |
4 | export const createPointer = (
5 | store: ReturnType
6 | ): CustomInspectorState => ({
7 | state: [
8 | {
9 | key: 'current',
10 | value: store.pointerPosition.current.value,
11 | objectType: 'ref',
12 | },
13 | {
14 | key: 'start',
15 | value: store.pointerPosition.start.value,
16 | objectType: 'ref',
17 | },
18 | {
19 | key: 'offset',
20 | value: {
21 | percent: store.pointerPosition.offset.percent.value,
22 | pixel: store.pointerPosition.offset.pixel.value,
23 | },
24 | },
25 | ],
26 | });
27 |
--------------------------------------------------------------------------------
/packages/devtools/src/utils/store.ts:
--------------------------------------------------------------------------------
1 | import { CustomInspectorState } from '@vue/devtools-api';
2 | import { useDnDStore } from '@vue-dnd-kit/core';
3 |
4 | export const createStore = (
5 | store: ReturnType
6 | ): CustomInspectorState => ({
7 | state: [
8 | {
9 | key: 'isDragging',
10 | value: store.isDragging.value,
11 | objectType: 'computed',
12 | },
13 | {
14 | key: 'elementsMap',
15 | value: store.elementsMap.value,
16 | objectType: 'ref',
17 | },
18 | {
19 | key: 'zonesMap',
20 | value: store.zonesMap.value,
21 | objectType: 'ref',
22 | },
23 | {
24 | key: 'visibleElements',
25 | value: store.visibleElements.value,
26 | objectType: 'ref',
27 | },
28 | {
29 | key: 'activeContainer',
30 | value: {
31 | component: store.activeContainer.component.value,
32 | ref: store.activeContainer.ref.value,
33 | animating: {
34 | appear: store.activeContainer.animating.appear.value,
35 | enter: store.activeContainer.animating.enter.value,
36 | leave: store.activeContainer.animating.leave.value,
37 | },
38 | options: store.activeContainer.options.value,
39 | },
40 | },
41 | {
42 | key: 'visibleZones',
43 | value: store.visibleZones.value,
44 | objectType: 'ref',
45 | },
46 | {
47 | key: 'draggingElements',
48 | value: store.draggingElements.value,
49 | objectType: 'ref',
50 | },
51 | {
52 | key: 'pointerPosition',
53 | value: {
54 | start: store.pointerPosition.start.value,
55 | current: store.pointerPosition.current.value,
56 | offset: {
57 | percent: store.pointerPosition.offset.percent.value,
58 | pixel: store.pointerPosition.offset.pixel.value,
59 | },
60 | },
61 | },
62 |
63 | {
64 | key: 'selectedElements',
65 | value: store.selectedElements.value,
66 | objectType: 'ref',
67 | },
68 | {
69 | key: 'keyboard',
70 | editable: true,
71 | value: {
72 | w: store.keyboard.w.value,
73 | s: store.keyboard.s.value,
74 | a: store.keyboard.a.value,
75 | d: store.keyboard.d.value,
76 | },
77 | },
78 | {
79 | key: 'hovered',
80 | value: {
81 | zone: store.hovered.zone.value,
82 | element: store.hovered.element.value,
83 | },
84 | },
85 | ],
86 | });
87 |
--------------------------------------------------------------------------------
/packages/devtools/src/utils/zones.ts:
--------------------------------------------------------------------------------
1 | import { CustomInspectorState } from '@vue/devtools-api';
2 | import { useDnDStore } from '@vue-dnd-kit/core';
3 |
4 | export const createZones = (
5 | store: ReturnType
6 | ): CustomInspectorState => ({
7 | state: [
8 | {
9 | key: 'zonesMap',
10 | value: store.zonesMap.value,
11 | objectType: 'ref',
12 | },
13 | {
14 | key: 'visibleZones',
15 | value: store.visibleZones.value,
16 | objectType: 'ref',
17 | },
18 | ],
19 | });
20 |
--------------------------------------------------------------------------------
/packages/devtools/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src/global.d.ts", "src/**/*.ts", "__tests__/**/*.ts"],
3 | "compilerOptions": {
4 | "outDir": "dist",
5 | "sourceMap": false,
6 |
7 | "target": "esnext",
8 | "module": "esnext",
9 | "moduleResolution": "node",
10 | "allowJs": false,
11 | "skipLibCheck": true,
12 |
13 | "noUnusedLocals": true,
14 | "strictNullChecks": true,
15 | "noImplicitAny": true,
16 | "noImplicitThis": true,
17 | "noImplicitReturns": false,
18 | "strict": true,
19 | "isolatedModules": true,
20 |
21 | "experimentalDecorators": true,
22 | "resolveJsonModule": true,
23 | "esModuleInterop": true,
24 | "removeComments": false,
25 | "jsx": "preserve",
26 | "lib": ["esnext", "dom"],
27 | "types": ["node"]
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/packages/utilities/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vue-dnd-kit/utilities",
3 | "version": "0.0.7",
4 | "description": "Utilities for Vue DnD Kit - a lightweight Vue 3 library for building performant and accessible drag and drop interfaces",
5 | "author": "ZiZIGY",
6 | "license": "MIT",
7 | "keywords": [
8 | "vue",
9 | "dnd",
10 | "drag",
11 | "drop",
12 | "drag and drop",
13 | "drag and drop library",
14 | "drag and drop component",
15 | "vue dnd",
16 | "vue drag and drop",
17 | "vue drag and drop library",
18 | "vue drag and drop component",
19 | "vue dnd kit",
20 | "vue dnd kit utilities",
21 | "dnd-kit",
22 | "dnd-kit utilities"
23 | ],
24 | "repository": {
25 | "type": "git",
26 | "url": "git+https://github.com/zizigy/vue-dnd-kit.git",
27 | "directory": "packages/core"
28 | },
29 | "scripts": {
30 | "start": "vite build --watch",
31 | "build": "vite build",
32 | "test": "vitest run",
33 | "lint": "eslint src --ext .ts,.vue",
34 | "prepublish": "npm run build"
35 | },
36 | "main": "dist/vue-dnd-kit-utilities.cjs.js",
37 | "module": "dist/vue-dnd-kit-utilities.es.js",
38 | "types": "dist/index.d.ts",
39 | "exports": {
40 | ".": {
41 | "types": "./dist/index.d.ts",
42 | "import": "./dist/vue-dnd-kit-utilities.es.js",
43 | "require": "./dist/vue-dnd-kit-utilities.cjs.js"
44 | }
45 | },
46 | "files": [
47 | "README.md",
48 | "CHANGELOG.md",
49 | "LICENSE",
50 | "dist"
51 | ],
52 | "peerDependencies": {
53 | "vue": "^3.5.13",
54 | "@vue-dnd-kit/core": "workspace:*"
55 | },
56 | "devDependencies": {
57 | "@vitejs/plugin-vue": "^4.2.3",
58 | "eslint": "^8.38.0",
59 | "typescript": "^4.9.5",
60 | "vite": "^6.0.5",
61 | "vite-plugin-dts": "^4.5.0",
62 | "vitepress": "^1.6.3",
63 | "vue": "^3.5.13"
64 | },
65 | "publishConfig": {
66 | "access": "public",
67 | "registry": "https://registry.npmjs.org/"
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/packages/utilities/src/composables/useGeometry.ts:
--------------------------------------------------------------------------------
1 | import { computed, type Ref } from 'vue';
2 | import type { IPoint } from '@vue-dnd-kit/core';
3 | import { getDirection as _getDirection } from '../utils';
4 |
5 | /**
6 | * Hook for calculating geometric relationships between two points.
7 | * Independent utility that can be used for any geometric calculations,
8 | * commonly used in drag and drop but not limited to it.
9 | *
10 | * @param pointA - Reference to the first point coordinates
11 | * @param pointB - Reference to the second point coordinates
12 | * @returns Object containing geometric calculations:
13 | * - delta: difference between points
14 | * - direction: cardinal direction from pointA to pointB
15 | * - distance: euclidean distance between points
16 | * - angle: angle in degrees between points
17 | *
18 | * @example
19 | * ```ts
20 | * const start = ref({ x: 0, y: 0 });
21 | * const end = ref({ x: 100, y: 100 });
22 | * const { delta, direction, distance, angle } = useGeometry(start, end);
23 | * ```
24 | */
25 | export const useGeometry = (
26 | pointA: Ref,
27 | pointB: Ref
28 | ) => {
29 | /** Vector between two points */
30 | const delta = computed(() => ({
31 | x: (pointB.value?.x ?? 0) - (pointA.value?.x ?? 0),
32 | y: (pointB.value?.y ?? 0) - (pointA.value?.y ?? 0),
33 | }));
34 |
35 | /** Cardinal direction from pointA to pointB */
36 | const direction = computed(() => _getDirection(delta.value));
37 |
38 | /** Euclidean distance between points */
39 | const distance = computed(() => {
40 | const dx = (pointB.value?.x ?? 0) - (pointA.value?.x ?? 0);
41 | const dy = (pointB.value?.y ?? 0) - (pointA.value?.y ?? 0);
42 | return Math.sqrt(dx * dx + dy * dy);
43 | });
44 |
45 | /** Angle in degrees between points (0° is right, 90° is down) */
46 | const angle = computed(() => {
47 | const dx = (pointB.value?.x ?? 0) - (pointA.value?.x ?? 0);
48 | const dy = (pointB.value?.y ?? 0) - (pointA.value?.y ?? 0);
49 | return Math.atan2(dy, dx) * (180 / Math.PI);
50 | });
51 |
52 | return {
53 | delta,
54 | direction,
55 | distance,
56 | angle,
57 | };
58 | };
59 |
--------------------------------------------------------------------------------
/packages/utilities/src/index.ts:
--------------------------------------------------------------------------------
1 | export { useGeometry } from './composables/useGeometry';
2 | export { useAutoScroll } from './composables/useAutoScroll';
3 |
4 | export type { IAutoScrollOptions } from './types';
5 |
--------------------------------------------------------------------------------
/packages/utilities/src/types/index.d.ts:
--------------------------------------------------------------------------------
1 | /** Auto-scroll configuration options */
2 | export interface IAutoScrollOptions {
3 | /** Distance from edge to start auto-scrolling (px) */
4 | threshold?: number;
5 | /** Scrolling speed (px/frame) */
6 | speed?: number;
7 | /** Whether auto-scroll is disabled */
8 | disabled?: boolean;
9 | }
10 |
--------------------------------------------------------------------------------
/packages/utilities/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | import type { IPoint } from '@vue-dnd-kit/core';
2 |
3 | export const getDelta = (pointA: IPoint, pointB: IPoint): IPoint => ({
4 | x: pointB.x - pointA.x,
5 | y: pointB.y - pointA.y,
6 | });
7 |
8 | export const getDirection = (
9 | delta: IPoint
10 | ): 'up' | 'right' | 'down' | 'left' => {
11 | const angle = Math.atan2(delta.y, delta.x);
12 | const deg = angle * (180 / Math.PI);
13 |
14 | if (deg >= -45 && deg <= 45) return 'right';
15 | if (deg > 45 && deg < 135) return 'down';
16 | if (deg >= 135 || deg <= -135) return 'left';
17 | return 'up';
18 | };
19 |
20 | export const getAngle = (pointA: IPoint, pointB: IPoint): number => {
21 | const dx = pointB.x - pointA.x;
22 | const dy = pointB.y - pointA.y;
23 | return Math.atan2(dy, dx) * (180 / Math.PI);
24 | };
25 |
--------------------------------------------------------------------------------
/packages/utilities/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "module": "ESNext",
6 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
7 | "skipLibCheck": true,
8 | "moduleResolution": "node",
9 | "allowSyntheticDefaultImports": true,
10 | "resolveJsonModule": true,
11 | "isolatedModules": true,
12 | "strict": true,
13 | "noUnusedLocals": true,
14 | "noUnusedParameters": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "declaration": true,
17 | "declarationDir": "./dist",
18 | "outDir": "./dist",
19 | "baseUrl": ".",
20 | "paths": {
21 | "@vue-dnd-kit/core": ["../core/src"],
22 | "@vue-dnd-kit/core/*": ["../core/src/*"]
23 | }
24 | },
25 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue"],
26 | "exclude": ["node_modules", "dist", "**/*.test.ts"]
27 | }
28 |
--------------------------------------------------------------------------------
/packages/utilities/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 | import dts from 'vite-plugin-dts';
3 | import { resolve } from 'path';
4 | import vue from '@vitejs/plugin-vue';
5 |
6 | export default defineConfig({
7 | plugins: [
8 | vue(),
9 | dts({
10 | include: ['src/**/*.ts', 'src/**/*.vue'],
11 | beforeWriteFile: (filePath, content) => {
12 | const fixedContent = content.replace(
13 | /from ['"]\.\.\/types['"];/g,
14 | 'from "../types";'
15 | );
16 | return {
17 | filePath: filePath.replace('/src/', '/'),
18 | content: fixedContent,
19 | };
20 | },
21 | copyDtsFiles: true,
22 | insertTypesEntry: true,
23 | cleanVueFileName: true,
24 | }),
25 | ],
26 | build: {
27 | lib: {
28 | entry: resolve(__dirname, 'src/index.ts'),
29 | name: 'VueDndKitUtilities',
30 | formats: ['es', 'cjs'],
31 | fileName: (format) => `vue-dnd-kit-utilities.${format}.js`,
32 | },
33 | rollupOptions: {
34 | external: ['vue', '@vue-dnd-kit/core'],
35 | output: {
36 | globals: {
37 | vue: 'Vue',
38 | '@vue-dnd-kit/core': 'VueDndKitCore',
39 | },
40 | exports: 'named',
41 | },
42 | },
43 | cssCodeSplit: false,
44 | cssMinify: true,
45 | },
46 | css: {
47 | modules: {
48 | localsConvention: 'camelCase',
49 | },
50 | },
51 | resolve: {
52 | alias: {
53 | '@vue-dnd-kit/core': resolve(__dirname, '../core/src'),
54 | },
55 | dedupe: ['vue'],
56 | },
57 | optimizeDeps: {
58 | include: ['vue'],
59 | exclude: ['@vue-dnd-kit/core'],
60 | },
61 | server: {
62 | fs: {
63 | allow: ['..'],
64 | },
65 | },
66 | });
67 |
--------------------------------------------------------------------------------
/playground/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/playground/README.md:
--------------------------------------------------------------------------------
1 | # Vue 3 + TypeScript + Vite
2 |
3 | This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `
12 |