@vc-shell/framework 1.1.0-alpha.3 → 1.1.0-alpha.4

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 (156) hide show
  1. package/core/composables/index.ts +18 -17
  2. package/core/composables/useDashboard/index.ts +19 -0
  3. package/core/composables/{useGlobalSearch.ts → useGlobalSearch/index.ts} +3 -5
  4. package/core/composables/useWidgets/index.ts +19 -18
  5. package/core/plugins/modularity/loader.ts +2 -1
  6. package/core/services/dashboard-service.ts +121 -0
  7. package/core/services/widget-service.ts +1 -4
  8. package/dist/core/composables/index.d.ts +1 -0
  9. package/dist/core/composables/index.d.ts.map +1 -1
  10. package/dist/core/composables/useDashboard/index.d.ts +5 -0
  11. package/dist/core/composables/useDashboard/index.d.ts.map +1 -0
  12. package/dist/core/composables/{useGlobalSearch.d.ts → useGlobalSearch/index.d.ts} +1 -1
  13. package/dist/core/composables/useGlobalSearch/index.d.ts.map +1 -0
  14. package/dist/core/plugins/modularity/loader.d.ts.map +1 -1
  15. package/dist/core/services/dashboard-service.d.ts +33 -0
  16. package/dist/core/services/dashboard-service.d.ts.map +1 -0
  17. package/dist/core/services/widget-service.d.ts.map +1 -1
  18. package/dist/framework.js +235 -225
  19. package/dist/{index-Bu12RZTu.js → index-8LELHzw9.js} +1 -1
  20. package/dist/{index-Bwl2ND2Q.js → index-9lJxZE5w.js} +1 -1
  21. package/dist/{index-CJi-BbTb.js → index-B1YR_MYV.js} +1 -1
  22. package/dist/{index-BhdwVgUw.js → index-BA98L1jI.js} +1 -1
  23. package/dist/{index-NdrUF1u3.js → index-BAeTsi-X.js} +1 -1
  24. package/dist/{index-CbRqPQTw.js → index-BBYyHeYA.js} +1 -1
  25. package/dist/{index-CsaYfhir.js → index-BrUitdDo.js} +1 -1
  26. package/dist/{index-CZ_pj3nW.js → index-BuO5ByG9.js} +1 -1
  27. package/dist/{index-DFPb-jDP.js → index-CJ5I7vTn.js} +1 -1
  28. package/dist/{index-BdoAu2fz.js → index-CWKrD2Cd.js} +1 -1
  29. package/dist/{index-DVaMW7gL.js → index-Cf9Tz1ql.js} +1 -1
  30. package/dist/{index-B89uIUkS.js → index-CrxFDC2b.js} +1 -1
  31. package/dist/{index-BcQiBkO6.js → index-D1JchciU.js} +1 -1
  32. package/dist/{index-CEvuTGIu.js → index-DLtsQ_PJ.js} +31254 -31134
  33. package/dist/{index-COjjAS6v.js → index-DVljTjbf.js} +1 -1
  34. package/dist/{index-DjQ6Ffv8.js → index-RwX3kiZh.js} +28 -28
  35. package/dist/{index-S9Ht7s3i.js → index-xLYzNPa7.js} +1 -1
  36. package/dist/index.css +1 -1
  37. package/dist/injection-keys.d.ts +28 -0
  38. package/dist/injection-keys.d.ts.map +1 -1
  39. package/dist/shared/components/dashboard-widget-card/dashboard-widget-card.vue.d.ts +25 -0
  40. package/dist/shared/components/dashboard-widget-card/dashboard-widget-card.vue.d.ts.map +1 -0
  41. package/dist/shared/components/dashboard-widget-card/index.d.ts +2 -0
  42. package/dist/shared/components/dashboard-widget-card/index.d.ts.map +1 -0
  43. package/dist/shared/components/draggable-dashboard/DraggableDashboard.vue.d.ts +6 -0
  44. package/dist/shared/components/draggable-dashboard/DraggableDashboard.vue.d.ts.map +1 -0
  45. package/dist/shared/components/draggable-dashboard/_internal/DashboardWidget.vue.d.ts +20 -0
  46. package/dist/shared/components/draggable-dashboard/_internal/DashboardWidget.vue.d.ts.map +1 -0
  47. package/dist/shared/components/draggable-dashboard/composables/useDashboardDragAndDrop.d.ts +354 -0
  48. package/dist/shared/components/draggable-dashboard/composables/useDashboardDragAndDrop.d.ts.map +1 -0
  49. package/dist/shared/components/draggable-dashboard/composables/useDashboardGrid.d.ts +12 -0
  50. package/dist/shared/components/draggable-dashboard/composables/useDashboardGrid.d.ts.map +1 -0
  51. package/dist/shared/components/draggable-dashboard/index.d.ts +2 -0
  52. package/dist/shared/components/draggable-dashboard/index.d.ts.map +1 -0
  53. package/dist/shared/components/draggable-dashboard/types.d.ts +80 -0
  54. package/dist/shared/components/draggable-dashboard/types.d.ts.map +1 -0
  55. package/dist/shared/components/index.d.ts +2 -0
  56. package/dist/shared/components/index.d.ts.map +1 -1
  57. package/dist/shared/components/user-dropdown-button/_internal/user-info.vue.d.ts.map +1 -1
  58. package/dist/shared/components/user-dropdown-button/user-dropdown-button.vue.d.ts.map +1 -1
  59. package/dist/shared/composables/useMenuExpanded.d.ts +2 -0
  60. package/dist/shared/composables/useMenuExpanded.d.ts.map +1 -1
  61. package/dist/shared/modules/dynamic/components/fields/storybook/Button.stories.d.ts +0 -41
  62. package/dist/shared/modules/dynamic/components/fields/storybook/Button.stories.d.ts.map +1 -1
  63. package/dist/shared/modules/dynamic/components/fields/storybook/Card.stories.d.ts.map +1 -1
  64. package/dist/shared/modules/dynamic/components/fields/storybook/common/templates.d.ts +1 -1
  65. package/dist/shared/modules/dynamic/components/fields/storybook/common/templates.d.ts.map +1 -1
  66. package/dist/shared/modules/dynamic/pages/dynamic-blade-list.vue.d.ts +2 -25
  67. package/dist/shared/modules/dynamic/pages/dynamic-blade-list.vue.d.ts.map +1 -1
  68. package/dist/tailwind.config.d.ts +1 -81
  69. package/dist/tailwind.config.d.ts.map +1 -1
  70. package/dist/tsconfig.tsbuildinfo +1 -1
  71. package/dist/ui/components/atoms/vc-button/vc-button.stories.d.ts +169 -734
  72. package/dist/ui/components/atoms/vc-button/vc-button.stories.d.ts.map +1 -1
  73. package/dist/ui/components/atoms/vc-button/vc-button.vue.d.ts +18 -2
  74. package/dist/ui/components/atoms/vc-button/vc-button.vue.d.ts.map +1 -1
  75. package/dist/ui/components/atoms/vc-card/index.d.ts +2 -0
  76. package/dist/ui/components/atoms/vc-card/index.d.ts.map +1 -1
  77. package/dist/ui/components/atoms/vc-card/vc-card.stories.d.ts +12 -0
  78. package/dist/ui/components/atoms/vc-card/vc-card.stories.d.ts.map +1 -1
  79. package/dist/ui/components/atoms/vc-card/vc-card.vue.d.ts +2 -0
  80. package/dist/ui/components/atoms/vc-card/vc-card.vue.d.ts.map +1 -1
  81. package/dist/ui/components/atoms/vc-icon/icons/GridDotsIcon.vue.d.ts +18 -0
  82. package/dist/ui/components/atoms/vc-icon/icons/GridDotsIcon.vue.d.ts.map +1 -0
  83. package/dist/ui/components/atoms/vc-icon/icons/ShoppingCardIcon.vue.d.ts +18 -0
  84. package/dist/ui/components/atoms/vc-icon/icons/ShoppingCardIcon.vue.d.ts.map +1 -0
  85. package/dist/ui/components/atoms/vc-icon/icons/index.d.ts +2 -0
  86. package/dist/ui/components/atoms/vc-icon/icons/index.d.ts.map +1 -1
  87. package/dist/ui/components/organisms/vc-app/_internal/composables/useAppMenuState.d.ts +2 -0
  88. package/dist/ui/components/organisms/vc-app/_internal/composables/useAppMenuState.d.ts.map +1 -1
  89. package/dist/ui/components/organisms/vc-app/_internal/vc-app-bar/vc-app-bar.vue.d.ts +0 -1
  90. package/dist/ui/components/organisms/vc-app/_internal/vc-app-bar/vc-app-bar.vue.d.ts.map +1 -1
  91. package/dist/ui/components/organisms/vc-app/_internal/vc-app-menu/_internal/vc-app-menu-item/_internal/vc-app-menu-link.vue.d.ts +2 -1
  92. package/dist/ui/components/organisms/vc-app/_internal/vc-app-menu/_internal/vc-app-menu-item/_internal/vc-app-menu-link.vue.d.ts.map +1 -1
  93. package/dist/ui/components/organisms/vc-app/_internal/vc-app-menu/vc-app-menu.vue.d.ts.map +1 -1
  94. package/dist/ui/components/organisms/vc-app/vc-app.stories.d.ts +13 -67
  95. package/dist/ui/components/organisms/vc-app/vc-app.stories.d.ts.map +1 -1
  96. package/dist/ui/components/organisms/vc-app/vc-app.vue.d.ts +5 -65
  97. package/dist/ui/components/organisms/vc-app/vc-app.vue.d.ts.map +1 -1
  98. package/dist/ui/components/organisms/vc-table/_internal/vc-table-base-header/vc-table-base-header.vue.d.ts.map +1 -1
  99. package/dist/ui/components/organisms/vc-table/_internal/vc-table-desktop-view/_internal/{vc-table-header/vc-table-header.vue.d.ts → vc-table-columns-header/vc-table-columns-header.vue.d.ts} +1 -1
  100. package/dist/ui/components/organisms/vc-table/_internal/vc-table-desktop-view/_internal/vc-table-columns-header/vc-table-columns-header.vue.d.ts.map +1 -0
  101. package/dist/ui/components/organisms/vc-table/_internal/vc-table-header/vc-table-header.vue.d.ts.map +1 -1
  102. package/dist/ui/components/organisms/vc-table/_internal/vc-table-mobile-view/vc-table-mobile-view.vue.d.ts +33 -3
  103. package/dist/ui/components/organisms/vc-table/_internal/vc-table-mobile-view/vc-table-mobile-view.vue.d.ts.map +1 -1
  104. package/dist/ui/components/organisms/vc-table/composables/useTableRowReorder.d.ts.map +1 -1
  105. package/package.json +10 -5
  106. package/shared/components/dashboard-widget-card/dashboard-widget-card.vue +67 -0
  107. package/shared/components/dashboard-widget-card/index.ts +1 -0
  108. package/shared/components/draggable-dashboard/DraggableDashboard.vue +369 -0
  109. package/shared/components/draggable-dashboard/_internal/DashboardWidget.vue +133 -0
  110. package/shared/components/draggable-dashboard/composables/useDashboardDragAndDrop.ts +547 -0
  111. package/shared/components/draggable-dashboard/composables/useDashboardGrid.ts +250 -0
  112. package/shared/components/draggable-dashboard/index.ts +1 -0
  113. package/shared/components/draggable-dashboard/types.ts +91 -0
  114. package/shared/components/index.ts +2 -0
  115. package/shared/components/user-dropdown-button/_internal/user-info.vue +25 -12
  116. package/shared/components/user-dropdown-button/user-dropdown-button.vue +3 -3
  117. package/shared/composables/useMenuExpanded.ts +24 -0
  118. package/shared/modules/assets/components/assets-details/assets-details.vue +1 -1
  119. package/shared/modules/dynamic/components/fields/storybook/Button.stories.ts +186 -247
  120. package/shared/modules/dynamic/components/fields/storybook/Card.stories.ts +175 -176
  121. package/shared/modules/dynamic/components/fields/storybook/common/templates.ts +8 -8
  122. package/shared/modules/dynamic/pages/dynamic-blade-list.vue +153 -187
  123. package/tailwind.config.ts +127 -126
  124. package/ui/components/atoms/vc-button/vc-button.stories.ts +1 -16
  125. package/ui/components/atoms/vc-button/vc-button.vue +74 -63
  126. package/ui/components/atoms/vc-card/vc-card.stories.ts +102 -102
  127. package/ui/components/atoms/vc-card/vc-card.vue +164 -159
  128. package/ui/components/atoms/vc-icon/icons/GridDotsIcon.vue +22 -0
  129. package/ui/components/atoms/vc-icon/icons/ShoppingCardIcon.vue +16 -0
  130. package/ui/components/atoms/vc-icon/icons/index.ts +2 -0
  131. package/ui/components/molecules/vc-field/vc-field.vue +1 -1
  132. package/ui/components/organisms/vc-app/_internal/composables/useAppMenuState.ts +12 -1
  133. package/ui/components/organisms/vc-app/_internal/vc-app-bar/_internal/AppBarContent.vue +1 -2
  134. package/ui/components/organisms/vc-app/_internal/vc-app-bar/_internal/AppBarHeader.vue +1 -1
  135. package/ui/components/organisms/vc-app/_internal/vc-app-bar/_internal/AppBarOverlay.vue +0 -1
  136. package/ui/components/organisms/vc-app/_internal/vc-app-bar/vc-app-bar.vue +274 -112
  137. package/ui/components/organisms/vc-app/_internal/vc-app-menu/_internal/vc-app-menu-item/_internal/vc-app-menu-link.vue +81 -37
  138. package/ui/components/organisms/vc-app/_internal/vc-app-menu/vc-app-menu.vue +7 -5
  139. package/ui/components/organisms/vc-app/vc-app.vue +26 -15
  140. package/ui/components/organisms/vc-table/_internal/vc-table-base-header/vc-table-base-header.vue +5 -7
  141. package/ui/components/organisms/vc-table/_internal/vc-table-desktop-view/_internal/{vc-table-header/vc-table-header.vue → vc-table-columns-header/vc-table-columns-header.vue} +23 -21
  142. package/ui/components/organisms/vc-table/_internal/vc-table-desktop-view/_internal/vc-table-row/vc-table-row.vue +1 -0
  143. package/ui/components/organisms/vc-table/_internal/vc-table-desktop-view/vc-table-desktop-view.vue +1 -1
  144. package/ui/components/organisms/vc-table/_internal/vc-table-header/vc-table-header.vue +12 -1
  145. package/ui/components/organisms/vc-table/_internal/vc-table-mobile-view/vc-table-mobile-view.vue +45 -2
  146. package/ui/components/organisms/vc-table/composables/useTableColumnReorder.ts +5 -5
  147. package/ui/components/organisms/vc-table/composables/useTableColumnResize.ts +1 -1
  148. package/ui/components/organisms/vc-table/composables/useTableRowReorder.ts +1 -0
  149. package/core/services/toolbarbus-service.ts +0 -34
  150. package/dist/core/composables/useGlobalSearch.d.ts.map +0 -1
  151. package/dist/core/services/toolbarbus-service.d.ts +0 -10
  152. package/dist/core/services/toolbarbus-service.d.ts.map +0 -1
  153. package/dist/ui/components/organisms/vc-app/composables/useToolbarSlots.d.ts +0 -5
  154. package/dist/ui/components/organisms/vc-app/composables/useToolbarSlots.d.ts.map +0 -1
  155. package/dist/ui/components/organisms/vc-table/_internal/vc-table-desktop-view/_internal/vc-table-header/vc-table-header.vue.d.ts.map +0 -1
  156. package/ui/components/organisms/vc-app/composables/useToolbarSlots.ts +0 -37
@@ -0,0 +1,547 @@
1
+ import { ref, computed, onUnmounted } from "vue";
2
+ import type { IDashboardWidget, DashboardWidgetPosition } from "../types";
3
+ import { GRID_COLUMNS } from "./useDashboardGrid";
4
+ import { useDashboard } from "../../../../core/composables/useDashboard";
5
+
6
+ // Cell height constant, corresponds to --dashboard-cell-height CSS variable
7
+ // Value of 80px is defined in DraggableDashboard.vue
8
+ const CELL_HEIGHT = 80;
9
+
10
+ export function useDashboardDragAndDrop(
11
+ updateWidgetPosition: (widgetId: string, position: DashboardWidgetPosition) => void,
12
+ getGridRows?: () => number,
13
+ ) {
14
+ const dashboard = useDashboard();
15
+ const widgets = computed(() => dashboard.getWidgets());
16
+ const gridContainer = ref<HTMLElement | null>(null);
17
+
18
+ // Drag state
19
+ const draggedWidget = ref<IDashboardWidget | null>(null);
20
+ const previewPosition = ref<DashboardWidgetPosition | null>(null);
21
+ const isDragging = ref(false);
22
+ const dragOffset = ref({ x: 0, y: 0 });
23
+ const displacedWidgets = ref<Map<string, DashboardWidgetPosition>>(new Map());
24
+
25
+ // Drag clone
26
+ const dragClone = ref<HTMLElement | null>(null);
27
+ const initialMousePosition = ref({ x: 0, y: 0 });
28
+ const isTouchDevice = ref(false);
29
+
30
+ // Get cell size
31
+ const calculateCellSize = () => {
32
+ if (!gridContainer.value) return { cellWidth: 0, cellHeight: CELL_HEIGHT };
33
+ const rect = gridContainer.value.getBoundingClientRect();
34
+ return {
35
+ cellWidth: rect.width / GRID_COLUMNS,
36
+ cellHeight: CELL_HEIGHT,
37
+ };
38
+ };
39
+
40
+ // Create widget clone for dragging
41
+ const createDragClone = (element: HTMLElement) => {
42
+ // Find parent widget element
43
+ const widgetElement = element.closest(".dashboard-widget") as HTMLElement;
44
+ if (!widgetElement) return null;
45
+
46
+ const clone = widgetElement.cloneNode(true) as HTMLElement;
47
+ const rect = widgetElement.getBoundingClientRect();
48
+
49
+ // Учитываем прокрутку страницы
50
+ const scrollX = window.scrollX || window.pageXOffset;
51
+ const scrollY = window.scrollY || window.pageYOffset;
52
+
53
+ // Копируем все вычисленные стили с оригинала
54
+ const computedStyle = window.getComputedStyle(widgetElement);
55
+
56
+ clone.style.position = "fixed";
57
+ clone.style.width = `${rect.width}px`;
58
+ clone.style.height = `${rect.height}px`;
59
+ clone.style.left = `${rect.left + scrollX}px`;
60
+ clone.style.top = `${rect.top + scrollY}px`;
61
+ clone.style.zIndex = "9999";
62
+ clone.style.pointerEvents = "none";
63
+ clone.style.opacity = "0.95";
64
+ clone.style.transform = "scale(1.02)";
65
+ clone.style.transition = "transform 0.1s ease, opacity 0.2s ease";
66
+ clone.style.boxShadow = "0 10px 25px rgba(0,0,0,0.15)";
67
+ clone.style.willChange = "transform";
68
+
69
+ // Копируем важные стили с оригинала
70
+ clone.style.backgroundColor = computedStyle.backgroundColor;
71
+ clone.style.borderRadius = computedStyle.borderRadius;
72
+
73
+ // Добавляем класс для стилизации клона
74
+ clone.classList.add("dashboard-widget-clone");
75
+
76
+ // Анимация появления
77
+ requestAnimationFrame(() => {
78
+ clone.style.transform = "scale(1.02)";
79
+ clone.style.opacity = "0.92";
80
+ });
81
+
82
+ document.body.appendChild(clone);
83
+ return clone;
84
+ };
85
+
86
+ // Optimized intersection check function
87
+ const intersect = (
88
+ widgetA: IDashboardWidget,
89
+ posA: DashboardWidgetPosition,
90
+ widgetB: IDashboardWidget,
91
+ posB: DashboardWidgetPosition,
92
+ ) => {
93
+ // Early Y-axis check (if difference is greater than sum of heights, no intersection)
94
+ const verticalDistance = Math.abs(posA.y - posB.y);
95
+ if (verticalDistance > widgetA.size.height + widgetB.size.height) {
96
+ return false;
97
+ }
98
+
99
+ // Early X-axis check (if difference is greater than sum of widths, no intersection)
100
+ const horizontalDistance = Math.abs(posA.x - posB.x);
101
+ if (horizontalDistance > widgetA.size.width + widgetB.size.width) {
102
+ return false;
103
+ }
104
+
105
+ // Standard rectangle intersection check
106
+ const aLeft = posA.x;
107
+ const aRight = posA.x + widgetA.size.width;
108
+ const aTop = posA.y;
109
+ const aBottom = posA.y + widgetA.size.height;
110
+
111
+ const bLeft = posB.x;
112
+ const bRight = posB.x + widgetB.size.width;
113
+ const bTop = posB.y;
114
+ const bBottom = posB.y + widgetB.size.height;
115
+
116
+ return !(aRight <= bLeft || aLeft >= bRight || aBottom <= bTop || aTop >= bBottom);
117
+ };
118
+
119
+ // Optimized function for updating displaced widgets
120
+ const updateDisplacedWidgets = () => {
121
+ if (!draggedWidget.value || !previewPosition.value || !isDragging.value) {
122
+ displacedWidgets.value.clear();
123
+ return;
124
+ }
125
+
126
+ const layout = new Map(dashboard.getLayout());
127
+ const newDisplacements = new Map<string, DashboardWidgetPosition>();
128
+ const processedWidgets = new Set<string>();
129
+
130
+ // Cache for initial collision checks
131
+ const directCollisionWidgets = new Set<string>();
132
+
133
+ // Check collisions with other widgets
134
+ for (const widget of widgets.value) {
135
+ if (widget.id === draggedWidget.value.id) continue;
136
+
137
+ const pos = layout.get(widget.id);
138
+ if (!pos) continue;
139
+
140
+ if (intersect(draggedWidget.value, previewPosition.value, widget, pos)) {
141
+ directCollisionWidgets.add(widget.id);
142
+ }
143
+ }
144
+
145
+ // If no direct collisions, skip additional displacement calculations
146
+ if (directCollisionWidgets.size === 0) {
147
+ displacedWidgets.value = newDisplacements;
148
+ return;
149
+ }
150
+
151
+ // Sort widgets top to bottom for more natural rearrangement
152
+ const sortedWidgets = [...widgets.value]
153
+ .filter((w) => w.id !== draggedWidget.value?.id)
154
+ .sort((a, b) => {
155
+ const posA = layout.get(a.id);
156
+ const posB = layout.get(b.id);
157
+ if (!posA || !posB) return 0;
158
+ // Sort by Y first, then by X for more stable results
159
+ return posA.y === posB.y ? posA.x - posB.x : posA.y - posB.y;
160
+ });
161
+
162
+ // Function for checking collisions with caching
163
+ const collisionCache = new Map<string, boolean>();
164
+
165
+ const checkCollision = (widget: IDashboardWidget, position: DashboardWidgetPosition) => {
166
+ const cacheKey = `${widget.id}_${position.x}_${position.y}`;
167
+
168
+ if (collisionCache.has(cacheKey)) {
169
+ return collisionCache.get(cacheKey);
170
+ }
171
+
172
+ // Check intersection with dragged widget
173
+ let hasCollision = intersect(draggedWidget.value!, previewPosition.value!, widget, position);
174
+
175
+ // Check intersections with already displaced widgets if no collision with dragged widget
176
+ if (!hasCollision) {
177
+ for (const [id, pos] of newDisplacements) {
178
+ const w = widgets.value.find((w) => w.id === id);
179
+ if (w && intersect(widget, position, w, pos)) {
180
+ hasCollision = true;
181
+ break;
182
+ }
183
+ }
184
+ }
185
+
186
+ collisionCache.set(cacheKey, hasCollision);
187
+ return hasCollision;
188
+ };
189
+
190
+ // Process widgets with direct collisions first
191
+ for (const widget of sortedWidgets) {
192
+ if (processedWidgets.has(widget.id)) continue;
193
+
194
+ const currentPos = layout.get(widget.id);
195
+ if (!currentPos) continue;
196
+
197
+ // Focus on widgets with direct collisions first
198
+ if (!directCollisionWidgets.has(widget.id) && newDisplacements.size === 0) continue;
199
+
200
+ // Check collisions at current position
201
+ const hasCollision = checkCollision(widget, currentPos);
202
+
203
+ if (hasCollision) {
204
+ // Find new position below dragged widget
205
+ const newY = Math.max(previewPosition.value.y + draggedWidget.value.size.height, currentPos.y + 1);
206
+ let finalY = newY;
207
+
208
+ // Find nearest position without collisions, optimized
209
+ const maxIterations = 50; // Protection against infinite loop
210
+ let iterations = 0;
211
+
212
+ while (checkCollision(widget, { x: currentPos.x, y: finalY }) && iterations < maxIterations) {
213
+ finalY++;
214
+ iterations++;
215
+ }
216
+
217
+ newDisplacements.set(widget.id, { x: currentPos.x, y: finalY });
218
+ processedWidgets.add(widget.id);
219
+
220
+ // If we displaced a widget with direct collision, check if we created new collisions
221
+ for (const w of sortedWidgets) {
222
+ if (w.id !== widget.id && !processedWidgets.has(w.id)) {
223
+ const pos = layout.get(w.id);
224
+ if (pos && intersect(widget, { x: currentPos.x, y: finalY }, w, pos)) {
225
+ directCollisionWidgets.add(w.id);
226
+ }
227
+ }
228
+ }
229
+ }
230
+ }
231
+
232
+ displacedWidgets.value = newDisplacements;
233
+ };
234
+
235
+ // Get event coordinates for either mouse or touch events
236
+ const getEventCoordinates = (event: MouseEvent | TouchEvent) => {
237
+ if ("touches" in event) {
238
+ // Touch event
239
+ return {
240
+ clientX: event.touches[0].clientX,
241
+ clientY: event.touches[0].clientY,
242
+ };
243
+ }
244
+ // Mouse event
245
+ return {
246
+ clientX: (event as MouseEvent).clientX,
247
+ clientY: (event as MouseEvent).clientY,
248
+ };
249
+ };
250
+
251
+ // Common logic for mouse and touch move events
252
+ const updateDragPosition = (event: MouseEvent | TouchEvent) => {
253
+ if (!gridContainer.value || !draggedWidget.value || !dragClone.value) return;
254
+
255
+ // Save event data as it might be unavailable in requestAnimationFrame
256
+ const coords = getEventCoordinates(event);
257
+
258
+ // Use requestAnimationFrame for UI update optimization
259
+ requestAnimationFrame(() => {
260
+ if (!gridContainer.value || !draggedWidget.value || !dragClone.value) return;
261
+
262
+ const rect = gridContainer.value.getBoundingClientRect();
263
+ const { cellWidth, cellHeight } = calculateCellSize();
264
+
265
+ // Update clone position relative to initial mouse position
266
+ const deltaX = coords.clientX - initialMousePosition.value.x;
267
+ const deltaY = coords.clientY - initialMousePosition.value.y;
268
+
269
+ dragClone.value.style.transform = `translate(${deltaX}px, ${deltaY}px)`;
270
+
271
+ // Calculate container scroll
272
+ const gridScrollLeft = gridContainer.value.scrollLeft;
273
+ const gridScrollTop = gridContainer.value.scrollTop;
274
+
275
+ // Calculate container offset in window
276
+ const gridRectLeft = rect.left;
277
+ const gridRectTop = rect.top;
278
+
279
+ // Calculate mouse position relative to grid with scroll
280
+ const mouseX = coords.clientX - gridRectLeft + gridScrollLeft;
281
+ const mouseY = coords.clientY - gridRectTop + gridScrollTop;
282
+
283
+ // Convert mouse coordinates to grid coordinates with drag offset
284
+ let gridX = Math.round(mouseX / cellWidth - dragOffset.value.x);
285
+ let gridY = Math.round(mouseY / cellHeight - dragOffset.value.y);
286
+
287
+ // Limit boundaries (minimum 0)
288
+ gridX = Math.max(0, gridX);
289
+ gridY = Math.max(0, gridY);
290
+
291
+ // Right and bottom boundaries depend on widget size and grid dimensions
292
+ const gridWidth = GRID_COLUMNS;
293
+ const gridHeight = getGridRows?.() || 100; // Use large value if getGridRows is not defined
294
+
295
+ // Limit position to keep widget within grid boundaries
296
+ gridX = Math.min(gridX, gridWidth - draggedWidget.value.size.width);
297
+ gridY = Math.min(gridY, gridHeight - draggedWidget.value.size.height);
298
+
299
+ // Update preview position only if it changed
300
+ if (!previewPosition.value || previewPosition.value.x !== gridX || previewPosition.value.y !== gridY) {
301
+ previewPosition.value = { x: gridX, y: gridY };
302
+
303
+ // Recalculate displaced widgets on position change
304
+ updateDisplacedWidgets();
305
+ }
306
+ });
307
+ };
308
+
309
+ // Enhanced mouse event handler
310
+ const handleMouseMove = (event: MouseEvent) => {
311
+ if (!isDragging.value || !draggedWidget.value || !gridContainer.value || !dragClone.value) return;
312
+ updateDragPosition(event);
313
+ };
314
+
315
+ // Enhanced touch event handler
316
+ const handleTouchMove = (event: TouchEvent) => {
317
+ if (!isDragging.value || !draggedWidget.value || !gridContainer.value || !dragClone.value) return;
318
+
319
+ // Prevent page scrolling during drag
320
+ event.preventDefault();
321
+
322
+ updateDragPosition(event);
323
+ };
324
+
325
+ // Enhanced drag end handlers
326
+ const handleMouseUp = () => {
327
+ finishDrag();
328
+ };
329
+
330
+ const handleTouchEnd = () => {
331
+ finishDrag();
332
+ };
333
+
334
+ // Common drag finish function
335
+ const finishDrag = () => {
336
+ if (!isDragging.value || !draggedWidget.value || !previewPosition.value) return;
337
+
338
+ // Get necessary data
339
+ const draggedWidgetId = draggedWidget.value.id;
340
+ const finalPosition = previewPosition.value;
341
+
342
+ // When there's a clone, finish dragging smoothly
343
+ if (dragClone.value) {
344
+ // Get DOM element of original widget
345
+ const originalWidget = document.querySelector(`.dashboard-widget[data-id="${draggedWidgetId}"]`) as HTMLElement;
346
+
347
+ if (originalWidget) {
348
+ const { cellWidth, cellHeight } = calculateCellSize();
349
+ const widgetGap = 20; // Widget gap in pixels
350
+
351
+ // Get current clone position (where user released mouse)
352
+ const cloneRect = dragClone.value.getBoundingClientRect();
353
+
354
+ // Get page scroll
355
+ const scrollX = window.scrollX || window.pageXOffset;
356
+ const scrollY = window.scrollY || window.pageYOffset;
357
+
358
+ // Get grid container position with scroll
359
+ const gridRect = gridContainer.value!.getBoundingClientRect();
360
+ const gridScrollLeft = gridContainer.value!.scrollLeft;
361
+ const gridScrollTop = gridContainer.value!.scrollTop;
362
+
363
+ // Calculate relative clone position inside container with all scrolls
364
+ const relX = cloneRect.left + scrollX - (gridRect.left + scrollX) + gridScrollLeft;
365
+ const relY = cloneRect.top + scrollY - (gridRect.top + scrollY) + gridScrollTop;
366
+
367
+ // Calculate final coordinates where widget should move
368
+ const finalX = finalPosition.x * cellWidth + widgetGap / 2;
369
+ const finalY = finalPosition.y * cellHeight + widgetGap / 2;
370
+
371
+ // First place original widget exactly at clone's position
372
+ originalWidget.style.transition = "none";
373
+ originalWidget.style.transform = `translate(${relX}px, ${relY}px)`;
374
+ originalWidget.style.opacity = "1";
375
+ originalWidget.style.zIndex = "1000";
376
+
377
+ // Remove clone immediately since original is in its place
378
+ if (dragClone.value.parentNode) {
379
+ document.body.removeChild(dragClone.value);
380
+ }
381
+ dragClone.value = null;
382
+
383
+ // Force reflow to apply styles
384
+ originalWidget.offsetHeight; // eslint-disable-line
385
+
386
+ // Update position in data store
387
+ updateWidgetPosition(draggedWidgetId, finalPosition);
388
+
389
+ // Update positions of all displaced widgets
390
+ for (const [widgetId, position] of displacedWidgets.value.entries()) {
391
+ updateWidgetPosition(widgetId, position);
392
+ }
393
+
394
+ // Now smoothly animate widget to its final position
395
+ setTimeout(() => {
396
+ originalWidget.style.transition = `transform var(--dashboard-transition-duration) var(--dashboard-transition-timing)`;
397
+ originalWidget.style.transform = `translate(${finalX}px, ${finalY}px)`;
398
+ }, 20); // Small delay to ensure style changes apply correctly
399
+ } else {
400
+ // If original not found, just remove clone and update data
401
+ if (dragClone.value.parentNode) {
402
+ document.body.removeChild(dragClone.value);
403
+ }
404
+ dragClone.value = null;
405
+
406
+ // Update position in store
407
+ updateWidgetPosition(draggedWidgetId, finalPosition);
408
+
409
+ // Update positions of all displaced widgets
410
+ for (const [widgetId, position] of displacedWidgets.value.entries()) {
411
+ updateWidgetPosition(widgetId, position);
412
+ }
413
+ }
414
+ } else {
415
+ // If no clone, just update data
416
+ updateWidgetPosition(draggedWidgetId, finalPosition);
417
+
418
+ // Update positions of all displaced widgets
419
+ for (const [widgetId, position] of displacedWidgets.value.entries()) {
420
+ updateWidgetPosition(widgetId, position);
421
+ }
422
+ }
423
+
424
+ // Remove event handlers
425
+ document.removeEventListener("mousemove", handleMouseMove);
426
+ document.removeEventListener("mouseup", handleMouseUp);
427
+ document.removeEventListener("touchmove", handleTouchMove, { passive: false } as EventListenerOptions);
428
+ document.removeEventListener("touchend", handleTouchEnd);
429
+ document.removeEventListener("touchcancel", handleTouchEnd);
430
+
431
+ // Reset state with delay to allow animation to complete
432
+ setTimeout(() => {
433
+ isDragging.value = false;
434
+ draggedWidget.value = null;
435
+ previewPosition.value = null;
436
+ displacedWidgets.value.clear();
437
+ dragOffset.value = { x: 0, y: 0 };
438
+ }, 50);
439
+ };
440
+
441
+ // Enhanced mousedown handler with passive listeners
442
+ const handleMouseDown = (event: MouseEvent | TouchEvent, widget: IDashboardWidget, element: HTMLElement) => {
443
+ if (!gridContainer.value) return;
444
+
445
+ // Check event type
446
+ isTouchDevice.value = "touches" in event;
447
+
448
+ // Use preventDefault only for mouse events, use passive: false for touch
449
+ if (!isTouchDevice.value) {
450
+ event.preventDefault();
451
+ }
452
+ event.stopPropagation();
453
+
454
+ // Store initial position for drag detection
455
+ const initialEvent = getEventCoordinates(event);
456
+ let hasDragStarted = false;
457
+
458
+ // Find parent widget element
459
+ const widgetElement = element.closest(".dashboard-widget") as HTMLElement;
460
+ if (!widgetElement) return;
461
+
462
+ const rect = widgetElement.getBoundingClientRect();
463
+ const { cellWidth, cellHeight } = calculateCellSize();
464
+
465
+ // Temporary move handler to detect actual drag start
466
+ const tempMoveHandler = (moveEvent: MouseEvent | TouchEvent) => {
467
+ if (hasDragStarted) return;
468
+
469
+ const currentCoords = getEventCoordinates(moveEvent);
470
+ const deltaX = Math.abs(currentCoords.clientX - initialEvent.clientX);
471
+ const deltaY = Math.abs(currentCoords.clientY - initialEvent.clientY);
472
+
473
+ // Start drag only if mouse moved more than 3 pixels
474
+ if (deltaX > 3 || deltaY > 3) {
475
+ hasDragStarted = true;
476
+ isDragging.value = true;
477
+ draggedWidget.value = widget;
478
+
479
+ // Save exact coordinate offset without rounding
480
+ dragOffset.value = {
481
+ x: (initialEvent.clientX - rect.left) / cellWidth,
482
+ y: (initialEvent.clientY - rect.top) / cellHeight,
483
+ };
484
+
485
+ initialMousePosition.value = {
486
+ x: initialEvent.clientX,
487
+ y: initialEvent.clientY,
488
+ };
489
+
490
+ dragClone.value = createDragClone(element);
491
+
492
+ // Remove temporary handlers
493
+ document.removeEventListener("mousemove", tempMoveHandler);
494
+ document.removeEventListener("touchmove", tempMoveHandler);
495
+ document.removeEventListener("mouseup", tempUpHandler);
496
+ document.removeEventListener("touchend", tempUpHandler);
497
+
498
+ // Add actual drag handlers
499
+ if (isTouchDevice.value) {
500
+ document.addEventListener("touchmove", handleTouchMove, { passive: false } as EventListenerOptions);
501
+ document.addEventListener("touchend", handleTouchEnd);
502
+ document.addEventListener("touchcancel", handleTouchEnd);
503
+ } else {
504
+ document.addEventListener("mousemove", handleMouseMove, { passive: true });
505
+ document.addEventListener("mouseup", handleMouseUp);
506
+ }
507
+ }
508
+ };
509
+
510
+ // Temporary up handler to cancel drag detection
511
+ const tempUpHandler = () => {
512
+ if (!hasDragStarted) {
513
+ document.removeEventListener("mousemove", tempMoveHandler);
514
+ document.removeEventListener("touchmove", tempMoveHandler);
515
+ document.removeEventListener("mouseup", tempUpHandler);
516
+ document.removeEventListener("touchend", tempUpHandler);
517
+ }
518
+ };
519
+
520
+ // Add temporary handlers to detect drag start
521
+ if (isTouchDevice.value) {
522
+ document.addEventListener("touchmove", tempMoveHandler, { passive: true });
523
+ document.addEventListener("touchend", tempUpHandler);
524
+ } else {
525
+ document.addEventListener("mousemove", tempMoveHandler, { passive: true });
526
+ document.addEventListener("mouseup", tempUpHandler);
527
+ }
528
+ };
529
+
530
+ const setGridContainer = (container: HTMLElement) => {
531
+ gridContainer.value = container;
532
+ };
533
+
534
+ // Clean up event handlers when the component is unmounted
535
+ onUnmounted(() => {
536
+ finishDrag();
537
+ });
538
+
539
+ return {
540
+ draggedWidget,
541
+ previewPosition,
542
+ displacedWidgets,
543
+ isDragging,
544
+ handleMouseDown, // Now handles both mouse and touch events
545
+ setGridContainer,
546
+ };
547
+ }