gravity-dnd 1.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/.eslintignore +3 -0
  2. package/.eslintrc.cjs +21 -0
  3. package/.storybook/main.ts +15 -0
  4. package/.storybook/preview.css +80 -0
  5. package/.storybook/preview.ts +15 -0
  6. package/LICENSE +373 -0
  7. package/README.md +292 -0
  8. package/index.ts +19 -0
  9. package/package.json +64 -0
  10. package/public/.gitkeep +0 -0
  11. package/src/Gravity.stories.ts +207 -0
  12. package/src/components/DragAndDrop/DragAndDrop.scss +4 -0
  13. package/src/components/DragAndDrop/DragAndDrop.stories.ts +787 -0
  14. package/src/components/DragAndDrop/DragAndDrop.visuals.css +1 -0
  15. package/src/components/DragAndDrop/DragAndDrop.vue +23 -0
  16. package/src/components/DragAndDrop.scss +4 -0
  17. package/src/components/DragAndDrop.visuals.css +1 -0
  18. package/src/components/DragAndDrop.vue +23 -0
  19. package/src/components/Draggable/DragDropProvider.scss +4 -0
  20. package/src/components/Draggable/DragDropProvider.visuals.css +1 -0
  21. package/src/components/Draggable/DragDropProvider.vue +11 -0
  22. package/src/components/Draggable/DragPreviewOverlay.scss +21 -0
  23. package/src/components/Draggable/DragPreviewOverlay.visuals.css +3 -0
  24. package/src/components/Draggable/DragPreviewOverlay.vue +41 -0
  25. package/src/components/Draggable/Draggable.scss +86 -0
  26. package/src/components/Draggable/Draggable.stories.ts +232 -0
  27. package/src/components/Draggable/Draggable.visuals.css +8 -0
  28. package/src/components/Draggable/Draggable.vue +292 -0
  29. package/src/components/Draggable/contracts.ts +82 -0
  30. package/src/components/Draggable/internalDropLayer.ts +126 -0
  31. package/src/components/Draggable/useDragDropContext.ts +310 -0
  32. package/src/components/Pool/Pool.scss +107 -0
  33. package/src/components/Pool/Pool.stories.ts +155 -0
  34. package/src/components/Pool/Pool.visuals.css +25 -0
  35. package/src/components/Pool/Pool.vue +198 -0
  36. package/src/components/Slot/Slot.scss +48 -0
  37. package/src/components/Slot/Slot.stories.ts +299 -0
  38. package/src/components/Slot/Slot.visuals.css +15 -0
  39. package/src/components/Slot/Slot.vue +126 -0
  40. package/src/styles.css +15 -0
  41. package/styles.css +1 -0
  42. package/styles.scss +6 -0
  43. package/tsconfig.json +18 -0
  44. package/vite.config.ts +21 -0
package/README.md ADDED
@@ -0,0 +1,292 @@
1
+ # Gravity - Stardust UI's drag-n-drop component.
2
+
3
+ ![Vercel Deploy](https://deploy-badge.vercel.app/vercel/gravity-dnd) ![Dynamic JSON Badge](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fandromika%2Fgravity-dnd%2Frefs%2Fheads%2Fmain%2Fpackage.json&query=%24.version&label=version)
4
+ **[>> Check the Demo](https://gravity-dnd.vercel.app/?path=/docs/gravity-librarydemo--docs)**
5
+
6
+ Originally created back in 2019 for [Pollux.gg](https://pollux.gg)'s Medal Picker from the profile
7
+ editor dashboard ([Original source](https://github.com/PolestarLabs/dashboard/blob/96cdfd6a8b49612c807a38230ab909aaf75ca944/src/views/dashboard/pages/profile_edit.pug)). Original was done in pure frontend Vue2
8
+ and Pug using [vuedraggable](https://www.npmjs.com/package/vuedraggable).
9
+
10
+ This package ports it over for more general use as a lightweight, composable
11
+ drag-and-drop system for Vue 3.
12
+ It has been redesigned for use in component libraries and applications where you want
13
+ customizable drop targets, drag sources, and flexible collision behavior.
14
+
15
+ Component structural styles are bundled with the Vue components. Visual color and border treatments are opt-in and come from the package stylesheet.
16
+
17
+ ```ts
18
+ import 'gravity-dnd/styles.css';
19
+ ```
20
+
21
+ *Renamed to **Gravity** to keep on-theme with all of other Pollux's side-libs and elements. Mainly its parent component lib: **Stardust UI***
22
+
23
+ ---
24
+
25
+ ## Core Concepts
26
+
27
+ ### GravityProvider
28
+ Wrap any part of your app that uses drag/drop in a single `GravityProvider`.
29
+ It provides context for all draggables, slots, and pools.
30
+
31
+ ```html
32
+ <template>
33
+ <GravityProvider>
34
+ <!-- drag/drop components here -->
35
+ </GravityProvider>
36
+ </template>
37
+
38
+ <script setup>
39
+ import 'gravity-dnd/styles.css';
40
+ import { GravityProvider } from '@/ui/gravity';
41
+ </script>
42
+ ```
43
+
44
+ ### GravityDraggable
45
+ Represents a draggable item.
46
+
47
+ Key props:
48
+ - `draggable-id` (string): unique id for the drag instance
49
+ - `item` (any): the data payload carried during drag
50
+ - `source-id` (string): identifies the container the drag originates from
51
+ - `source-kind` (`'pool' | 'slot' | 'custom'`): used for identifying what type of container a drag comes from
52
+ - `source-index` (number): index within the container
53
+ - `drop-mode` (`'target' | 'floating'`): controls whether drop is evaluated by hover targets or by pointer release
54
+
55
+ Example:
56
+
57
+ ```html
58
+ <GravityDraggable
59
+ draggable-id="draggable-1"
60
+ :item="item"
61
+ source-id="my-pool"
62
+ source-kind="pool"
63
+ :source-index="index"
64
+ >
65
+ <template #default="{ dragging }">
66
+ <div :style="{ opacity: dragging ? .9 : 1 }">{{ item.label }}</div>
67
+ </template>
68
+ </GravityDraggable>
69
+ ```
70
+
71
+ ### GravitySlot
72
+ A drop target that accepts one item at a time.
73
+ It emits `drop` events when an item is dropped.
74
+
75
+ Key props:
76
+ - `slot-id` (string): unique identifier for the slot
77
+ - `item` (any): current item in the slot (optional)
78
+ - `onDropCollision` (`'replace' | 'swap' | 'reject'`): how to handle collisions when slot already contains an item
79
+ - `accepts` (function): optional predicate to allow/reject drops based on item + source
80
+
81
+ ```html
82
+ <GravitySlot
83
+ slot-id="my-slot"
84
+ :item="currentItem"
85
+ onDropCollision="swap"
86
+ :accepts="(item) => item.type === 'allowed'"
87
+ @drop="handleDrop"
88
+ >
89
+ <template #default="{ hovering, accepting }">
90
+ <div :class="{ hover: hovering, accept: accepting }">
91
+ {{ currentItem ? currentItem.label : 'Drop here' }}
92
+ </div>
93
+ </template>
94
+ </GravitySlot>
95
+ ```
96
+
97
+ ### GravityPool
98
+ A container that supports reordering items within the pool and receiving items from other sources.
99
+
100
+ Key events:
101
+ - `@reorder` when items inside the pool are reordered
102
+ - `@receive` when an item is dropped into the pool from another source
103
+
104
+ ```html
105
+ <GravityPool pool-id="my-pool" :items="items" @reorder="onReorder" @receive="onReceive">
106
+ <template #item="{ item, index }">
107
+ <GravityDraggable
108
+ :draggable-id="`pool-${item.id}`"
109
+ :item="item"
110
+ source-id="my-pool"
111
+ source-kind="pool"
112
+ :source-index="index"
113
+ >
114
+ <template #default="{ dragging }">
115
+ <div :style="{ opacity: dragging ? .9 : 1 }">{{ item.label }}</div>
116
+ </template>
117
+ </GravityDraggable>
118
+ </template>
119
+ </GravityPool>
120
+ ```
121
+
122
+ ---
123
+
124
+ ## Collision Modes (`onDropCollision`)
125
+
126
+ `GravitySlot` supports three collision behaviors when a drop occurs and the slot is already occupied:
127
+
128
+ | Mode | Behavior |
129
+ |---------|----------|
130
+ | `replace` | Overwrites the current slot item with the dropped item. The existing item is discarded. |
131
+ | `swap` | Swaps the dropped item with the existing slot item. The existing item is returned to the drag source. |
132
+ | `reject` | Prevents the drop. The dragged item is returned to its source container. |
133
+
134
+ If `onDropCollision` is not configured, the component uses the older `swap` behavior when `swap=true`, otherwise `replace`.
135
+
136
+ ---
137
+
138
+ ## Drop event payload (for slots)
139
+
140
+ Drop handlers receive a `GravitySlotDropEvent<TItem>` with the following shape:
141
+
142
+ ```ts
143
+ interface GravitySlotDropEvent<TItem> {
144
+ draggableId: string;
145
+ item: TItem;
146
+ source: {
147
+ containerId: string;
148
+ kind: 'slot' | 'pool' | 'custom';
149
+ index: number;
150
+ };
151
+ target: {
152
+ kind: 'slot' | 'pool' | 'floating';
153
+ containerId: string | null;
154
+ index: number;
155
+ };
156
+ slotId: string;
157
+ swap: boolean;
158
+ collision: 'replace' | 'swap' | 'reject';
159
+ replacedItem?: TItem;
160
+ }
161
+ ```
162
+
163
+ - `collision`: the configured collision rule in effect.
164
+ - `replacedItem`: the item that was in the slot prior to the drop (available for `swap`/`replace`).
165
+
166
+ ---
167
+
168
+ ## Extending / Customizing Behavior
169
+
170
+ ### Custom Accept Logic
171
+ Use `accepts` to fine-tune which items can be dropped into a slot.
172
+
173
+ ```html
174
+ <GravitySlot
175
+ slot-id="custom-slot"
176
+ :accepts="(item, { sourceContainerId }) => sourceContainerId === 'trusted-pool'"
177
+ @drop="onDrop"
178
+ />
179
+ ```
180
+
181
+ ### Custom Slot Rendering
182
+ Use the slot scope values to render feedback:
183
+
184
+ - `hovering`: whether the pointer is hovering the slot
185
+ - `accepting`: whether the current drag is accepted
186
+
187
+ ```html
188
+ <GravitySlot slot-id="styled-slot" @drop="onDrop">
189
+ <template #default="{ hovering, accepting }">
190
+ <div :class="{ 'hovering': hovering, 'accepting': accepting }">
191
+ <!-- custom status UI -->
192
+ </div>
193
+ </template>
194
+ </GravitySlot>
195
+ ```
196
+
197
+ ### Extending drop logic in parent components
198
+ Use event handlers to implement complex behavior (e.g., persistence, undo, analytics).
199
+
200
+ ```ts
201
+ function handleSlotDrop(event: GravitySlotDropEvent<MyItem>) {
202
+ // apply app-specific rules
203
+ if (event.collision === 'swap') {
204
+ // maybe persist both items
205
+ }
206
+ updateLocalState(event);
207
+ }
208
+ ```
209
+
210
+ ---
211
+
212
+ ## Use Cases
213
+
214
+ ### 1) Drag-to-slot with swap behavior
215
+ - Use case: a single slot that accepts an item, but dropping another item should return the original back to its source.
216
+ - Configure:
217
+ - `onDropCollision="swap"`
218
+
219
+ ### 2) Drag-to-slot with replace behavior
220
+ - Use case: a slot that always accepts the newest item and discards the previous.
221
+ - Configure:
222
+ - `onDropCollision="replace"` (or `swap=false`)
223
+
224
+ ### 3) Drop rejection based on slot fullness
225
+ - Use case: only allow a drop when the slot is empty.
226
+ - Configure:
227
+ - `onDropCollision="reject"`
228
+
229
+ ### 4) Ordered pool with receive/reorder
230
+ - Use case: a list of draggable items where items can be reordered and new items can be dropped in.
231
+ - Use `GravityPool` + `@reorder` + `@receive`.
232
+
233
+ ---
234
+
235
+ ## Quick Start (smallest sample)
236
+
237
+ ```html
238
+ <template>
239
+ <GravityProvider>
240
+ <GravitySlot slot-id="target" onDropCollision="swap" @drop="onDrop">
241
+ <template #default="{ hovering, accepting }">
242
+ <div :style="{ background: hovering ? (accepting ? '#e0f7ff' : '#ffeaea') : '#fff' }">
243
+ Drop an item here
244
+ </div>
245
+ </template>
246
+ </GravitySlot>
247
+
248
+ <GravityPool pool-id="pool" :items="items" @receive="onReceive" @reorder="onReorder">
249
+ <template #item="{ item, index }">
250
+ <GravityDraggable
251
+ :draggable-id="`item-${item.id}`"
252
+ :item="item"
253
+ source-id="pool"
254
+ source-kind="pool"
255
+ :source-index="index"
256
+ >
257
+ <template #default="{ dragging }">
258
+ <div :style="{ opacity: dragging ? 0.3 : 1 }">{{ item.label }}</div>
259
+ </template>
260
+ </GravityDraggable>
261
+ </template>
262
+ </GravityPool>
263
+ </GravityProvider>
264
+ </template>
265
+
266
+ <script setup lang="ts">
267
+ import 'gravity-dnd/styles.css';
268
+ import { ref } from 'vue';
269
+ import { GravityProvider, GravityPool, GravitySlot, GravityDraggable } from '@/ui/gravity';
270
+
271
+ const items = ref([{ id: 'a', label: 'A' }, { id: 'b', label: 'B' }]);
272
+
273
+ function onDrop(event) {
274
+ // handle drop
275
+ }
276
+ function onReceive(event) {
277
+ // handle received item
278
+ }
279
+ function onReorder(event) {
280
+ // handle reorder
281
+ }
282
+ </script>
283
+ ```
284
+
285
+ ---
286
+
287
+ ## Notes
288
+ - This drag/drop system is intentionally lightweight and does not have built-in keyboard accessibility.
289
+ - For production use, wrap drag-drop logic in safe state updates (avoid mutating arrays directly in complex UIs).
290
+ - The `onDropCollision` API is designed for specific use-cases from Pollux.gg's dashboard, its implementation might be a bit stiff.
291
+ - Better mobile support is planned for future iterations
292
+ - React support is being considered.
package/index.ts ADDED
@@ -0,0 +1,19 @@
1
+ export { default as GravityProvider } from './src/components/Draggable/DragDropProvider.vue';
2
+ export { default as GravityDraggable } from './src/components/Draggable/Draggable.vue';
3
+ export { default as GravitySlot } from './src/components/Slot/Slot.vue';
4
+ export { default as GravityPool } from './src/components/Pool/Pool.vue';
5
+
6
+ export type {
7
+ GravityBoundary,
8
+ GravityCanDragContext,
9
+ GravityContainerKind,
10
+ GravityDropEvent,
11
+ GravityDropTarget,
12
+ GravityDraggableDropEvent,
13
+ GravityHoverTarget,
14
+ GravityMode,
15
+ GravityPoolReceiveEvent,
16
+ GravityPoolReorderEvent,
17
+ GravitySlotDropEvent,
18
+ GravitySource,
19
+ } from './src/components/Draggable/contracts';
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "gravity-dnd",
3
+ "private": false,
4
+ "version": "1.1.6",
5
+ "type": "module",
6
+ "authors": [
7
+ {
8
+ "name": "L Reis / Flicky / Andromika",
9
+ "email": "github@amarok.com"
10
+ },
11
+ {
12
+ "name": "Polestar Labs",
13
+ "email": "hello@pollux.gg"
14
+ }
15
+ ],
16
+ "description": "A Vue 3 drag-and-drop component library.",
17
+ "exports": {
18
+ ".": "./index.ts",
19
+ "./styles.css": "./styles.css",
20
+ "./styles.scss": "./styles.scss"
21
+ },
22
+ "scripts": {
23
+ "start": "npm run storybook -- --ci --port 6007",
24
+ "build": "npm run build:storybook",
25
+ "build-storybook": "npm run build:storybook",
26
+ "storybook": "storybook dev -p 6007 --ci",
27
+ "build:storybook": "storybook build --output-dir dist",
28
+ "lint": "eslint . --ext .ts,.tsx,.vue",
29
+ "preview": "vite preview"
30
+ },
31
+ "keywords": [
32
+ "vue",
33
+ "dnd",
34
+ "drag-and-drop",
35
+ "component-library"
36
+ ],
37
+ "author": "",
38
+ "license": "MIT",
39
+ "dependencies": {
40
+ "vue": "^3.4.0"
41
+ },
42
+ "devDependencies": {
43
+ "@storybook/addon-a11y": "^10.2.19",
44
+ "@storybook/addon-docs": "^10.2.19",
45
+ "@storybook/builder-vite": "^10.2.19",
46
+ "@storybook/vue3-vite": "^10.2.19",
47
+ "@vitejs/plugin-vue": "^6.0.5",
48
+ "@vue/compiler-sfc": "^3.4.0",
49
+ "chromatic": "^15.3.0",
50
+ "eslint": "^8.0.0",
51
+ "eslint-plugin-storybook": "10.2.19",
52
+ "eslint-plugin-vue": "^9.0.0",
53
+ "prismjs": "^1.29.0",
54
+ "pug": "^3.0.4",
55
+ "pug-plain-loader": "^1.1.0",
56
+ "react": "^18.3.1",
57
+ "react-dom": "^18.3.1",
58
+ "sass-embedded": "^1.98.0",
59
+ "storybook": "^10.2.19",
60
+ "typescript": "^5.0.0",
61
+ "vite": "^8.0.0",
62
+ "vite-plugin-dts": "^4.0.0"
63
+ }
64
+ }
File without changes
@@ -0,0 +1,207 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3-vite';
2
+ import { ref } from 'vue';
3
+ import docs from '../README.md?raw';
4
+ import {
5
+ GravityDraggable,
6
+ GravityPool,
7
+ GravityProvider,
8
+ GravitySlot,
9
+ type GravityPoolReceiveEvent,
10
+ type GravityPoolReorderEvent,
11
+ type GravitySlotDropEvent,
12
+ } from '../index';
13
+
14
+ interface DemoItem {
15
+ id: string;
16
+ label: string;
17
+ }
18
+
19
+ const meta: Meta = {
20
+ title: 'Gravity/LibraryDemo',
21
+ tags: ['autodocs'],
22
+ parameters: {
23
+ layout: 'centered',
24
+ docs: {
25
+ description: {
26
+ component: docs,
27
+ },
28
+ },
29
+ },
30
+ };
31
+
32
+ export default meta;
33
+ type Story = StoryObj<typeof meta>;
34
+
35
+ export const Playground: Story = {
36
+ render: () => ({
37
+ setup() {
38
+ const loose = ref<DemoItem | null>({ id: 'loose', label: 'Loose Token' });
39
+ const slotItem = ref<DemoItem | null>(null);
40
+ const pool = ref<DemoItem[]>([
41
+ { id: 'pool-a', label: 'A' },
42
+ { id: 'pool-b', label: 'B' },
43
+ { id: 'pool-c', label: 'C' },
44
+ ]);
45
+
46
+ function takeFromSource(sourceContainerId: string, sourceIndex: number): DemoItem | null {
47
+ if (sourceContainerId === 'pool-main') {
48
+ const [moved] = pool.value.splice(sourceIndex, 1);
49
+ return moved || null;
50
+ }
51
+ if (sourceContainerId === 'slot-main') {
52
+ const moved = slotItem.value;
53
+ slotItem.value = null;
54
+ return moved;
55
+ }
56
+ if (sourceContainerId === 'loose-main') {
57
+ const moved = loose.value;
58
+ loose.value = null;
59
+ return moved;
60
+ }
61
+ return null;
62
+ }
63
+
64
+ function putIntoSource(sourceContainerId: string, sourceIndex: number, item: DemoItem) {
65
+ if (sourceContainerId === 'pool-main') {
66
+ const insertIndex = Math.max(0, Math.min(sourceIndex, pool.value.length));
67
+ pool.value.splice(insertIndex, 0, item);
68
+ return;
69
+ }
70
+ if (sourceContainerId === 'slot-main') {
71
+ slotItem.value = item;
72
+ return;
73
+ }
74
+ if (sourceContainerId === 'loose-main') {
75
+ loose.value = item;
76
+ return;
77
+ }
78
+ }
79
+
80
+ function onSlotDrop(event: GravitySlotDropEvent<unknown>) {
81
+ const moved = takeFromSource(event.source.containerId, event.source.index);
82
+ if (!moved) return;
83
+
84
+ // Handle collision rules (replace / swap / reject)
85
+ if (event.collision === 'reject') {
86
+ // Put the moved item back into the source
87
+ putIntoSource(event.source.containerId, event.source.index, moved);
88
+ return;
89
+ }
90
+
91
+ const existing = slotItem.value;
92
+ slotItem.value = moved;
93
+
94
+ if (event.collision === 'swap' && event.replacedItem) {
95
+ putIntoSource(event.source.containerId, event.source.index, event.replacedItem as DemoItem);
96
+ }
97
+ }
98
+
99
+ function onPoolReorder(event: GravityPoolReorderEvent<unknown>) {
100
+ if (event.fromIndex === event.toIndex) return;
101
+ const [moved] = pool.value.splice(event.fromIndex, 1);
102
+ if (!moved) return;
103
+ let insertIndex = event.toIndex;
104
+ if (event.fromIndex < event.toIndex) insertIndex -= 1;
105
+ pool.value.splice(Math.max(0, Math.min(insertIndex, pool.value.length)), 0, moved as DemoItem);
106
+ }
107
+
108
+ function onPoolReceive(event: GravityPoolReceiveEvent<unknown>) {
109
+ const moved = takeFromSource(event.source.containerId, event.source.index);
110
+ if (!moved) return;
111
+ const insertIndex = Math.max(0, Math.min(event.insertIndex, pool.value.length));
112
+ pool.value.splice(insertIndex, 0, moved);
113
+ }
114
+
115
+ return {
116
+ loose,
117
+ slotItem,
118
+ pool,
119
+ onSlotDrop,
120
+ onPoolReorder,
121
+ onPoolReceive,
122
+ };
123
+ },
124
+ components: { GravityProvider, GravityDraggable, GravitySlot, GravityPool },
125
+ template: `
126
+ <div style="width: 640px; max-width: 96vw; font-family: system-ui, sans-serif;">
127
+ <GravityProvider>
128
+ <div style="display: grid; gap: 16px;">
129
+ <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px;">
130
+ <div>
131
+ <div style="font-size: 13px; font-weight: 600; margin-bottom: 8px;">Loose Draggable</div>
132
+ <GravityDraggable
133
+ v-if="loose"
134
+ draggable-id="gravity-loose-item"
135
+ :item="loose"
136
+ source-id="loose-main"
137
+ source-kind="custom"
138
+ :source-index="0"
139
+ >
140
+ <template #default="{ dragging }">
141
+ <div
142
+ :style="{
143
+ display: 'inline-flex',
144
+ padding: '8px 12px',
145
+ borderRadius: '8px',
146
+ border: '1px solid #d1d5db',
147
+ background: '#f9fafb',
148
+ opacity: dragging ? .9 : 1
149
+ }"
150
+ >
151
+ {{ loose.label }}
152
+ </div>
153
+ </template>
154
+ </GravityDraggable>
155
+ </div>
156
+
157
+ <div>
158
+ <div style="font-size: 13px; font-weight: 600; margin-bottom: 8px;">Slot</div>
159
+ <GravitySlot slot-id="slot-main" :item="slotItem" onDropCollision="swap" @drop="onSlotDrop">
160
+ <template #default="{ hovering, accepting }">
161
+ <div
162
+ style="min-height: 54px; border-radius: 8px; border: 1px solid #d1d5db; display: flex; align-items: center; justify-content: center;"
163
+ :style="{ background: hovering ? (accepting ? '#ecfeff' : '#fff1f2') : '#fff' }"
164
+ >
165
+ {{ slotItem ? slotItem.label : 'Drop here' }}
166
+ </div>
167
+ </template>
168
+ </GravitySlot>
169
+ </div>
170
+ </div>
171
+
172
+ <div>
173
+ <div style="font-size: 13px; font-weight: 600; margin-bottom: 8px;">Pool (reorder + receive)</div>
174
+ <GravityPool pool-id="pool-main" :items="pool" @reorder="onPoolReorder" @receive="onPoolReceive">
175
+ <template #item="{ item, index }">
176
+ <GravityDraggable
177
+ :draggable-id="'gravity-pool-item-' + item.id"
178
+ :item="item"
179
+ source-id="pool-main"
180
+ source-kind="pool"
181
+ :source-index="index"
182
+ >
183
+ <template #default="{ dragging }">
184
+ <div
185
+ :style="{
186
+ padding: '6px 10px',
187
+ minWidth: '56px',
188
+ textAlign: 'center',
189
+ borderRadius: '6px',
190
+ border: '1px solid #d1d5db',
191
+ background: '#f9fafb',
192
+ opacity: dragging ? .9 : 1
193
+ }"
194
+ >
195
+ {{ item.label }}
196
+ </div>
197
+ </template>
198
+ </GravityDraggable>
199
+ </template>
200
+ </GravityPool>
201
+ </div>
202
+ </div>
203
+ </GravityProvider>
204
+ </div>
205
+ `,
206
+ }),
207
+ };
@@ -0,0 +1,4 @@
1
+ .st-profile-medalpicker2-dnd {
2
+ position: relative;
3
+ user-select: none;
4
+ }