@vc-shell/framework 1.1.0-alpha.4 → 1.1.0-alpha.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 (119) hide show
  1. package/core/composables/useMenuService/index.ts +20 -110
  2. package/core/composables/useWidgets/index.ts +2 -1
  3. package/core/plugins/modularity/index.ts +3 -3
  4. package/core/services/menu-service.ts +195 -0
  5. package/core/services/widget-service.ts +20 -0
  6. package/core/types/index.ts +1 -1
  7. package/dist/core/composables/useMenuService/index.d.ts +4 -10
  8. package/dist/core/composables/useMenuService/index.d.ts.map +1 -1
  9. package/dist/core/composables/useWidgets/index.d.ts +2 -1
  10. package/dist/core/composables/useWidgets/index.d.ts.map +1 -1
  11. package/dist/core/plugins/modularity/index.d.ts.map +1 -1
  12. package/dist/core/services/menu-service.d.ts +17 -0
  13. package/dist/core/services/menu-service.d.ts.map +1 -0
  14. package/dist/core/services/widget-service.d.ts +4 -0
  15. package/dist/core/services/widget-service.d.ts.map +1 -1
  16. package/dist/core/types/index.d.ts +1 -1
  17. package/dist/core/types/index.d.ts.map +1 -1
  18. package/dist/framework.js +220 -210
  19. package/dist/{index-CrxFDC2b.js → index-3ySdd-mG.js} +1 -1
  20. package/dist/{index-B1YR_MYV.js → index-B-nvqNbp.js} +1 -1
  21. package/dist/{index-xLYzNPa7.js → index-BQF2-UMe.js} +1 -1
  22. package/dist/{index-BBYyHeYA.js → index-BXlxP2d2.js} +1 -1
  23. package/dist/{index-Cf9Tz1ql.js → index-C7P-aBjd.js} +1 -1
  24. package/dist/{index-8LELHzw9.js → index-CO_2IshF.js} +1 -1
  25. package/dist/{index-BA98L1jI.js → index-CfyFpaKq.js} +1 -1
  26. package/dist/{index-DVljTjbf.js → index-Ci23AX3j.js} +1 -1
  27. package/dist/{index-D1JchciU.js → index-CyuFXG83.js} +1 -1
  28. package/dist/{index-CWKrD2Cd.js → index-D1rpRTKf.js} +1 -1
  29. package/dist/{index-BAeTsi-X.js → index-DLxTAT7x.js} +1 -1
  30. package/dist/{index-BuO5ByG9.js → index-DOVhosAY.js} +1 -1
  31. package/dist/{index-DLtsQ_PJ.js → index-DZAq0B3U.js} +23780 -23339
  32. package/dist/{index-BrUitdDo.js → index-DtkJ7xTB.js} +1 -1
  33. package/dist/{index-9lJxZE5w.js → index-DvGVm1rK.js} +1 -1
  34. package/dist/{index-RwX3kiZh.js → index-EDF1MDtU.js} +1 -1
  35. package/dist/{index-CJ5I7vTn.js → index-LjqdX6jw.js} +1 -1
  36. package/dist/index.css +1 -1
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/injection-keys.d.ts +2 -0
  39. package/dist/injection-keys.d.ts.map +1 -1
  40. package/dist/shared/components/draggable-dashboard/DraggableDashboard.vue.d.ts +2 -0
  41. package/dist/shared/components/draggable-dashboard/DraggableDashboard.vue.d.ts.map +1 -1
  42. package/dist/shared/components/draggable-dashboard/composables/useCellSizeCalculator.d.ts +25 -0
  43. package/dist/shared/components/draggable-dashboard/composables/useCellSizeCalculator.d.ts.map +1 -0
  44. package/dist/shared/components/draggable-dashboard/composables/useCollisionDetection.d.ts +27 -0
  45. package/dist/shared/components/draggable-dashboard/composables/useCollisionDetection.d.ts.map +1 -0
  46. package/dist/shared/components/draggable-dashboard/composables/useDashboardDragAndDrop.d.ts +22 -0
  47. package/dist/shared/components/draggable-dashboard/composables/useDashboardDragAndDrop.d.ts.map +1 -1
  48. package/dist/shared/components/draggable-dashboard/composables/useDashboardGrid.d.ts +12 -4
  49. package/dist/shared/components/draggable-dashboard/composables/useDashboardGrid.d.ts.map +1 -1
  50. package/dist/shared/components/draggable-dashboard/composables/useDragClone.d.ts +15 -0
  51. package/dist/shared/components/draggable-dashboard/composables/useDragClone.d.ts.map +1 -0
  52. package/dist/shared/components/draggable-dashboard/composables/useEventCoordinates.d.ts +33 -0
  53. package/dist/shared/components/draggable-dashboard/composables/useEventCoordinates.d.ts.map +1 -0
  54. package/dist/shared/components/draggable-dashboard/composables/useGridPosition.d.ts +57 -0
  55. package/dist/shared/components/draggable-dashboard/composables/useGridPosition.d.ts.map +1 -0
  56. package/dist/shared/components/draggable-dashboard/composables/useGridSystem.d.ts +22 -0
  57. package/dist/shared/components/draggable-dashboard/composables/useGridSystem.d.ts.map +1 -0
  58. package/dist/shared/components/draggable-dashboard/composables/useLayoutPersistence.d.ts +19 -0
  59. package/dist/shared/components/draggable-dashboard/composables/useLayoutPersistence.d.ts.map +1 -0
  60. package/dist/shared/components/draggable-dashboard/composables/useResizeObserver.d.ts +18 -0
  61. package/dist/shared/components/draggable-dashboard/composables/useResizeObserver.d.ts.map +1 -0
  62. package/dist/shared/components/draggable-dashboard/composables/useWidgetLayout.d.ts +14 -0
  63. package/dist/shared/components/draggable-dashboard/composables/useWidgetLayout.d.ts.map +1 -0
  64. package/dist/shared/components/draggable-dashboard/composables/useWidgetStyles.d.ts +21 -0
  65. package/dist/shared/components/draggable-dashboard/composables/useWidgetStyles.d.ts.map +1 -0
  66. package/dist/shared/components/draggable-dashboard/types.d.ts +5 -1
  67. package/dist/shared/components/draggable-dashboard/types.d.ts.map +1 -1
  68. package/dist/tsconfig.tsbuildinfo +1 -1
  69. package/dist/ui/components/atoms/vc-icon/icons/FulfillmentCentersIcon.vue.d.ts +18 -0
  70. package/dist/ui/components/atoms/vc-icon/icons/FulfillmentCentersIcon.vue.d.ts.map +1 -0
  71. package/dist/ui/components/atoms/vc-icon/icons/OffersIcon.vue.d.ts +18 -0
  72. package/dist/ui/components/atoms/vc-icon/icons/OffersIcon.vue.d.ts.map +1 -0
  73. package/dist/ui/components/atoms/vc-icon/icons/OrdersIcon.vue.d.ts +18 -0
  74. package/dist/ui/components/atoms/vc-icon/icons/OrdersIcon.vue.d.ts.map +1 -0
  75. package/dist/ui/components/atoms/vc-icon/icons/PeopleIcon.vue.d.ts +18 -0
  76. package/dist/ui/components/atoms/vc-icon/icons/PeopleIcon.vue.d.ts.map +1 -0
  77. package/dist/ui/components/atoms/vc-icon/icons/ProductsIcon.vue.d.ts +18 -0
  78. package/dist/ui/components/atoms/vc-icon/icons/ProductsIcon.vue.d.ts.map +1 -0
  79. package/dist/ui/components/atoms/vc-icon/icons/ProfileIcon.vue.d.ts +18 -0
  80. package/dist/ui/components/atoms/vc-icon/icons/ProfileIcon.vue.d.ts.map +1 -0
  81. package/dist/ui/components/atoms/vc-icon/icons/index.d.ts +6 -0
  82. package/dist/ui/components/atoms/vc-icon/icons/index.d.ts.map +1 -1
  83. package/dist/ui/components/atoms/vc-icon/vc-icon.vue.d.ts.map +1 -1
  84. package/dist/ui/components/organisms/vc-app/_internal/vc-app-bar/components/app-bar-button/app-bar-button.vue.d.ts +1 -0
  85. package/dist/ui/components/organisms/vc-app/_internal/vc-app-bar/components/app-bar-button/app-bar-button.vue.d.ts.map +1 -1
  86. package/dist/ui/components/organisms/vc-app/_internal/vc-app-menu/_internal/vc-app-menu-item/vc-app-menu-item.vue.d.ts +2 -1
  87. package/dist/ui/components/organisms/vc-app/_internal/vc-app-menu/_internal/vc-app-menu-item/vc-app-menu-item.vue.d.ts.map +1 -1
  88. package/dist/ui/components/organisms/vc-app/vc-app.vue.d.ts.map +1 -1
  89. package/dist/ui/components/organisms/vc-table/vc-table.vue.d.ts.map +1 -1
  90. package/package.json +4 -4
  91. package/shared/components/draggable-dashboard/DraggableDashboard.vue +114 -148
  92. package/shared/components/draggable-dashboard/composables/useCellSizeCalculator.ts +121 -0
  93. package/shared/components/draggable-dashboard/composables/useCollisionDetection.ts +219 -0
  94. package/shared/components/draggable-dashboard/composables/useDashboardDragAndDrop.ts +126 -331
  95. package/shared/components/draggable-dashboard/composables/useDashboardGrid.ts +74 -220
  96. package/shared/components/draggable-dashboard/composables/useDragClone.ts +97 -0
  97. package/shared/components/draggable-dashboard/composables/useEventCoordinates.ts +91 -0
  98. package/shared/components/draggable-dashboard/composables/useGridPosition.ts +150 -0
  99. package/shared/components/draggable-dashboard/composables/useGridSystem.ts +169 -0
  100. package/shared/components/draggable-dashboard/composables/useLayoutPersistence.ts +89 -0
  101. package/shared/components/draggable-dashboard/composables/useResizeObserver.ts +105 -0
  102. package/shared/components/draggable-dashboard/composables/useWidgetLayout.ts +264 -0
  103. package/shared/components/draggable-dashboard/composables/useWidgetStyles.ts +120 -0
  104. package/shared/components/draggable-dashboard/types.ts +6 -1
  105. package/shared/components/notification-dropdown/notification-dropdown.vue +1 -0
  106. package/ui/components/atoms/vc-icon/icons/FulfillmentCentersIcon.vue +27 -0
  107. package/ui/components/atoms/vc-icon/icons/OffersIcon.vue +23 -0
  108. package/ui/components/atoms/vc-icon/icons/OrdersIcon.vue +19 -0
  109. package/ui/components/atoms/vc-icon/icons/PeopleIcon.vue +21 -0
  110. package/ui/components/atoms/vc-icon/icons/ProductsIcon.vue +23 -0
  111. package/ui/components/atoms/vc-icon/icons/ProfileIcon.vue +18 -0
  112. package/ui/components/atoms/vc-icon/icons/index.ts +6 -0
  113. package/ui/components/atoms/vc-icon/vc-icon.vue +101 -82
  114. package/ui/components/organisms/vc-app/_internal/vc-app-bar/components/app-bar-button/app-bar-button.vue +10 -3
  115. package/ui/components/organisms/vc-app/_internal/vc-app-bar/vc-app-bar.vue +3 -4
  116. package/ui/components/organisms/vc-app/_internal/vc-app-menu/_internal/vc-app-menu-item/vc-app-menu-item.vue +2 -2
  117. package/ui/components/organisms/vc-app/_internal/vc-app-menu/vc-app-menu.vue +1 -0
  118. package/ui/components/organisms/vc-app/vc-app.vue +2 -3
  119. package/ui/components/organisms/vc-table/vc-table.vue +4 -1
@@ -1,250 +1,104 @@
1
- import { computed, ref, watch } from "vue";
2
- import type { IDashboardWidget, DashboardWidgetPosition, DashboardGridConfig } from "../types";
1
+ import { computed } from "vue";
2
+ import type { IDashboardWidget, DashboardWidgetPosition } from "../types";
3
3
  import { useDashboard } from "../../../../core/composables/useDashboard";
4
-
5
- // Constants for grid
6
- export const GRID_COLUMNS = 12;
7
- // Initial rows, but now it's the minimum value
8
- export const MIN_GRID_ROWS = 12;
9
- // Increase height buffer for "infinite" scroll
10
- const ROWS_BUFFER = 10;
11
- // Dynamically calculated number of rows
12
- const dynamicRows = ref(MIN_GRID_ROWS);
13
-
14
- // Constants for position search optimization
15
- const SEARCH_DIRECTIONS = [
16
- { dx: 0, dy: 0 }, // Current position
17
- { dx: 0, dy: 1 }, // Down
18
- { dx: 1, dy: 0 }, // Right
19
- { dx: 0, dy: -1 }, // Up
20
- { dx: -1, dy: 0 }, // Left
21
- { dx: 1, dy: 1 }, // Right-down
22
- { dx: -1, dy: 1 }, // Left-down
23
- { dx: 1, dy: -1 }, // Right-up
24
- { dx: -1, dy: -1 }, // Left-up
25
- ];
26
-
4
+ import { useLayoutPersistence } from "./useLayoutPersistence";
5
+ import { useGridSystem } from "./useGridSystem";
6
+ import { useWidgetLayout } from "./useWidgetLayout";
7
+
8
+ // LocalStorage key for dashboard layout
9
+ const DASHBOARD_LAYOUT_KEY = "vc-dashboard-layout";
10
+
11
+ /**
12
+ * Main composable for managing the dashboard
13
+ *
14
+ * Combines functionality for working with the grid, widget placement, and layout persistence
15
+ *
16
+ * @returns An object with functions for working with the dashboard
17
+ */
27
18
  export function useDashboardGrid() {
19
+ // Dashboard service
28
20
  const dashboard = useDashboard();
21
+
22
+ // Reactive properties of widgets and layout
29
23
  const widgets = computed(() => dashboard.getWidgets());
30
24
  const layout = computed(() => dashboard.getLayout());
31
25
 
32
- // Calculate current maximum number of rows based on existing widgets
33
- const currentRows = computed(() => {
34
- if (widgets.value.length === 0) return MIN_GRID_ROWS;
26
+ // Initialize subsystems
27
+ const grid = useGridSystem();
35
28
 
36
- const maxY = Math.max(
37
- ...Array.from(layout.value.values()).map((pos: DashboardWidgetPosition, index) => {
38
- const widget = widgets.value.find((w) => w.id === Array.from(layout.value.keys())[index]);
39
- return widget ? pos.y + widget.size.height : pos.y + 1;
40
- }),
41
- 0,
42
- );
29
+ const widgetLayout = useWidgetLayout(dashboard.updateWidgetPosition);
43
30
 
44
- // Update dynamic number of rows with large buffer
45
- dynamicRows.value = Math.max(maxY + ROWS_BUFFER, MIN_GRID_ROWS);
46
- return dynamicRows.value;
47
- });
48
-
49
- // Get current number of rows
50
- const getGridRows = () => currentRows.value;
51
-
52
- // Improved collision detection function
53
- const hasCollision = (
54
- widget: IDashboardWidget,
55
- position: DashboardWidgetPosition,
56
- excludeWidgetId?: string,
57
- ): boolean => {
58
- // Check only bottom grid boundary
59
- if (position.x < 0 || position.x + widget.size.width > GRID_COLUMNS || position.y < 0) {
60
- return true;
61
- }
62
-
63
- // Create occupied cells map for optimization
64
- const occupiedCells = new Set<string>();
65
-
66
- // Fill occupied cells map
67
- widgets.value.forEach((other) => {
68
- if (other.id === widget.id || (excludeWidgetId && other.id === excludeWidgetId)) {
69
- return;
70
- }
71
-
72
- const otherPos = layout.value.get(other.id);
73
- if (!otherPos) return;
74
-
75
- for (let x = otherPos.x; x < otherPos.x + other.size.width; x++) {
76
- for (let y = otherPos.y; y < otherPos.y + other.size.height; y++) {
77
- occupiedCells.add(`${x},${y}`);
78
- }
79
- }
80
- });
31
+ // Hook for working with localStorage
32
+ const persistence = useLayoutPersistence(DASHBOARD_LAYOUT_KEY, dashboard.updateWidgetPosition);
81
33
 
82
- // Check collisions for each widget cell
83
- for (let x = position.x; x < position.x + widget.size.width; x++) {
84
- for (let y = position.y; y < position.y + widget.size.height; y++) {
85
- if (occupiedCells.has(`${x},${y}`)) {
86
- return true;
87
- }
88
- }
89
- }
90
-
91
- return false;
34
+ /**
35
+ * Gets the current number of rows in the grid
36
+ */
37
+ const getGridRows = (): number => {
38
+ return grid.updateGridRows(widgets.value, layout.value);
92
39
  };
93
40
 
94
- // Improved free position search function
95
- const findFreePosition = (widget: IDashboardWidget): DashboardWidgetPosition => {
96
- const currentPos = layout.value.get(widget.id);
97
-
98
- // If current position is free, use it
99
- if (currentPos && !hasCollision(widget, currentPos, widget.id)) {
100
- return currentPos;
101
- }
102
-
103
- // Start search from top-left corner or current position
104
- const startX = currentPos?.x || 0;
105
- const startY = currentPos?.y || 0;
106
-
107
- // Spiral search for free position
108
- let layer = 0;
109
- const maxLayers = Math.max(GRID_COLUMNS, currentRows.value);
110
-
111
- while (layer < maxLayers) {
112
- // Check all directions in current layer
113
- for (const { dx, dy } of SEARCH_DIRECTIONS) {
114
- const x = startX + dx * layer;
115
- const y = startY + dy * layer;
116
-
117
- const position = { x, y };
118
-
119
- // Check position
120
- if (!hasCollision(widget, position, widget.id)) {
121
- return position;
122
- }
123
- }
124
-
125
- layer++;
126
- }
41
+ /**
42
+ * Saves the current layout to localStorage
43
+ */
44
+ const saveLayoutToLocalStorage = (): void => {
45
+ persistence.saveLayout(layout.value);
46
+ };
127
47
 
128
- // If no position found, add to the end
129
- return {
130
- x: 0,
131
- y: currentRows.value,
132
- };
48
+ /**
49
+ * Loads the layout from localStorage
50
+ *
51
+ * @returns true if the layout was successfully loaded
52
+ */
53
+ const loadLayoutFromLocalStorage = (): boolean => {
54
+ return persistence.loadLayout(widgets.value);
133
55
  };
134
56
 
135
- // Arrange widgets in rows, minimizing empty spaces
57
+ /**
58
+ * Arranges widgets in rows
59
+ *
60
+ * @param widgetsToArrange An array of widgets to arrange
61
+ */
136
62
  const arrangeWidgetsInRows = (widgetsToArrange: IDashboardWidget[]): void => {
137
- if (widgetsToArrange.length === 0) return;
138
-
139
- const sortedWidgets = [...widgetsToArrange].sort((a, b) => {
140
- // Sort by size (largest first)
141
- const aSize = a.size.width * a.size.height;
142
- const bSize = b.size.width * b.size.height;
143
- return bSize - aSize;
144
- });
145
-
146
- // Current position for placement
147
- let currentX = 0;
148
- let currentY = 0;
149
- let rowHeight = 0;
150
-
151
- sortedWidgets.forEach((widget) => {
152
- // Check if widget fits in current row
153
- if (currentX + widget.size.width > GRID_COLUMNS) {
154
- // If it doesn't fit, move to the next row
155
- currentX = 0;
156
- currentY += rowHeight;
157
- rowHeight = 0;
158
- }
159
-
160
- // Place widget on current position
161
- const position = { x: currentX, y: currentY };
162
- dashboard.updateWidgetPosition(widget.id, position);
163
-
164
- // Update current position
165
- currentX += widget.size.width;
166
-
167
- // Update maximum height for current row
168
- rowHeight = Math.max(rowHeight, widget.size.height);
169
- });
63
+ widgetLayout.arrangeWidgetsInRows(widgetsToArrange, widgets.value, layout.value);
170
64
  };
171
65
 
172
- // Initialize initial widget positions
173
- const initializeLayout = () => {
174
- // Check if layout rebuild is needed
175
-
176
- // Check if not all widgets have positioned yet
177
- if (widgets.value.length !== layout.value.size) {
178
- arrangeWidgetsInRows(widgets.value);
179
- return;
180
- }
181
-
182
- // Check if all widgets have a position
183
- const hasUnpositionedWidgets = widgets.value.some((widget: IDashboardWidget) => !layout.value.has(widget.id));
184
-
185
- // If there are widgets without a position, then rearrange
186
- if (hasUnpositionedWidgets) {
187
- arrangeWidgetsInRows(widgets.value);
188
- return;
189
- }
190
-
191
- // Check for collisions
192
- const hasCollisions = widgets.value.some((widget: IDashboardWidget) => {
193
- const pos = layout.value.get(widget.id);
194
- return pos && hasCollision(widget, pos, widget.id);
195
- });
196
-
197
- // If there are collisions, then rearrange
198
- if (hasCollisions) {
199
- arrangeWidgetsInRows(widgets.value);
200
- return;
201
- }
202
-
203
- // Check if widgets are compact
204
- const maxY = Math.max(...Array.from(layout.value.values()).map((pos: DashboardWidgetPosition) => pos.y + 1), 0);
205
-
206
- // If widgets occupy many rows but are not fully occupied, then consider this placement suboptimal
207
- const totalCells = GRID_COLUMNS * maxY;
208
- const occupiedCells = widgets.value.reduce((sum: number, widget: IDashboardWidget) => {
209
- return sum + widget.size.width * widget.size.height;
210
- }, 0);
66
+ /**
67
+ * Initializes widgets with their built-in positions
68
+ *
69
+ * @returns true if at least one widget had a built-in position
70
+ */
71
+ const initializeWithBuiltInPositions = (): boolean => {
72
+ return widgetLayout.initializeWithBuiltInPositions(widgets.value, layout.value);
73
+ };
211
74
 
212
- // If less than 70% of the grid is occupied and widgets occupy many rows, then rearrange
213
- const fillRatio = occupiedCells / totalCells;
214
- if (maxY > 1 && fillRatio < 0.7) {
215
- arrangeWidgetsInRows(widgets.value);
216
- return;
217
- }
75
+ /**
76
+ * Initializes the initial positions of widgets
77
+ */
78
+ const initializeLayout = (): void => {
79
+ // Priority 1: Loading from localStorage
80
+ const layoutLoadedFromStorage = loadLayoutFromLocalStorage();
218
81
 
219
- // For widgets with positions, check and eliminate only collisions
220
- const sortedWidgets = [...widgets.value].sort((a, b) => {
221
- const posA = layout.value.get(a.id);
222
- const posB = layout.value.get(b.id);
223
- if (!posA && !posB) return 0;
224
- if (!posA) return 1;
225
- if (!posB) return -1;
226
- return posA.y - posB.y || posA.x - posB.x;
227
- });
82
+ // Priority 2: Using built-in widget positions
83
+ if (!layoutLoadedFromStorage) {
84
+ const usedBuiltInPositions = initializeWithBuiltInPositions();
228
85
 
229
- // Rearrange widgets, avoiding collisions
230
- sortedWidgets.forEach((widget) => {
231
- const currentPos = layout.value.get(widget.id);
232
- if (!currentPos) {
233
- const freePosition = findFreePosition(widget);
234
- dashboard.updateWidgetPosition(widget.id, freePosition);
235
- } else if (hasCollision(widget, currentPos, widget.id)) {
236
- const freePosition = findFreePosition(widget);
237
- dashboard.updateWidgetPosition(widget.id, freePosition);
86
+ // Priority 3: Standard initialization
87
+ if (!usedBuiltInPositions) {
88
+ arrangeWidgetsInRows(widgets.value);
238
89
  }
239
- });
90
+ }
240
91
  };
241
92
 
242
93
  return {
243
94
  widgets,
244
95
  layout,
96
+ GRID_COLUMNS: grid.GRID_COLUMNS,
97
+ getGridRows,
98
+ saveLayoutToLocalStorage,
99
+ loadLayoutFromLocalStorage,
245
100
  arrangeWidgetsInRows,
101
+ initializeWithBuiltInPositions,
246
102
  initializeLayout,
247
- GRID_COLUMNS,
248
- getGridRows,
249
103
  };
250
104
  }
@@ -0,0 +1,97 @@
1
+ import { ref } from "vue";
2
+
3
+ /**
4
+ * Composible for managing a widget clone during dragging
5
+ *
6
+ * Provides functions for creating, updating, and deleting a widget clone
7
+ * during dragging, as well as for animating the transition
8
+ *
9
+ * @returns An object with functions for working with the widget clone
10
+ */
11
+ export function useDragClone() {
12
+ // Reference to the DOM element of the clone
13
+ const dragClone = ref<HTMLElement | null>(null);
14
+
15
+ /**
16
+ * Creates a widget clone for dragging
17
+ *
18
+ * @param element The HTML element from which dragging starts
19
+ * @returns The DOM element of the clone or null if the clone was not created
20
+ */
21
+ const createDragClone = (element: HTMLElement) => {
22
+ // Find the parent widget element
23
+ const widgetElement = element.closest(".dashboard-widget") as HTMLElement;
24
+ if (!widgetElement) return null;
25
+
26
+ const clone = widgetElement.cloneNode(true) as HTMLElement;
27
+ const rect = widgetElement.getBoundingClientRect();
28
+
29
+ // Consider the page scroll
30
+ const scrollX = window.scrollX || window.pageXOffset;
31
+ const scrollY = window.scrollY || window.pageYOffset;
32
+
33
+ // Copy all computed styles from the original
34
+ const computedStyle = window.getComputedStyle(widgetElement);
35
+
36
+ clone.style.position = "fixed";
37
+ clone.style.width = `${rect.width}px`;
38
+ clone.style.height = `${rect.height}px`;
39
+ clone.style.left = `${rect.left + scrollX}px`;
40
+ clone.style.top = `${rect.top + scrollY}px`;
41
+ clone.style.zIndex = "9999";
42
+ clone.style.pointerEvents = "none";
43
+ clone.style.opacity = "0.95";
44
+ clone.style.transform = "scale(1.02)";
45
+ clone.style.transition = "transform 0.1s ease, opacity 0.2s ease";
46
+ clone.style.boxShadow = "0 10px 25px rgba(0,0,0,0.15)";
47
+ clone.style.willChange = "transform";
48
+
49
+ // Copy important styles from the original
50
+ clone.style.backgroundColor = computedStyle.backgroundColor;
51
+ clone.style.borderRadius = computedStyle.borderRadius;
52
+
53
+ // Add a class for styling the clone
54
+ clone.classList.add("dashboard-widget-clone");
55
+
56
+ // Animation of appearance
57
+ requestAnimationFrame(() => {
58
+ clone.style.transform = "scale(1.02)";
59
+ clone.style.opacity = "0.92";
60
+ });
61
+
62
+ document.body.appendChild(clone);
63
+ dragClone.value = clone;
64
+ return clone;
65
+ };
66
+
67
+ /**
68
+ * Updates the position of the clone during dragging
69
+ *
70
+ * @param deltaX The offset along the X axis
71
+ * @param deltaY The offset along the Y axis
72
+ */
73
+ const updateDragClonePosition = (deltaX: number, deltaY: number) => {
74
+ if (!dragClone.value) return;
75
+
76
+ dragClone.value.style.transform = `translate(${deltaX}px, ${deltaY}px)`;
77
+ };
78
+
79
+ /**
80
+ * Removes the widget clone
81
+ */
82
+ const removeDragClone = () => {
83
+ if (!dragClone.value) return;
84
+
85
+ if (dragClone.value.parentNode) {
86
+ document.body.removeChild(dragClone.value);
87
+ }
88
+ dragClone.value = null;
89
+ };
90
+
91
+ return {
92
+ dragClone,
93
+ createDragClone,
94
+ updateDragClonePosition,
95
+ removeDragClone,
96
+ };
97
+ }
@@ -0,0 +1,91 @@
1
+ import { ref } from "vue";
2
+
3
+ /**
4
+ * Interface for event coordinates
5
+ */
6
+ export interface EventCoordinates {
7
+ clientX: number;
8
+ clientY: number;
9
+ }
10
+
11
+ /**
12
+ * Composible for processing event coordinates
13
+ *
14
+ * Provides functions for getting coordinates from different types of events,
15
+ * tracking the initial position, and calculating the offset
16
+ *
17
+ * @returns An object with functions for working with event coordinates
18
+ */
19
+ export function useEventCoordinates() {
20
+ // Initial position of the mouse/touch
21
+ const initialPosition = ref<EventCoordinates>({ clientX: 0, clientY: 0 });
22
+
23
+ // Flag indicating whether the device is touch-enabled
24
+ const isTouchDevice = ref(false);
25
+
26
+ /**
27
+ * Gets coordinates from a mouse or touch event
28
+ *
29
+ * @param event The mouse or touch event
30
+ * @returns An object with coordinates clientX and clientY
31
+ */
32
+ const getEventCoordinates = (event: MouseEvent | TouchEvent): EventCoordinates => {
33
+ if ("touches" in event) {
34
+ // Touch event
35
+ isTouchDevice.value = true;
36
+ return {
37
+ clientX: event.touches[0].clientX,
38
+ clientY: event.touches[0].clientY,
39
+ };
40
+ }
41
+ // Mouse event
42
+ isTouchDevice.value = false;
43
+ return {
44
+ clientX: (event as MouseEvent).clientX,
45
+ clientY: (event as MouseEvent).clientY,
46
+ };
47
+ };
48
+
49
+ /**
50
+ * Saves the initial position of the event
51
+ *
52
+ * @param event The mouse or touch event
53
+ */
54
+ const saveInitialPosition = (event: MouseEvent | TouchEvent) => {
55
+ initialPosition.value = getEventCoordinates(event);
56
+ };
57
+
58
+ /**
59
+ * Calculates the offset from the initial position
60
+ *
61
+ * @param currentCoordinates The current coordinates
62
+ * @returns An object with the offsets along the X and Y axes
63
+ */
64
+ const calculateDelta = (currentCoordinates: EventCoordinates) => {
65
+ return {
66
+ deltaX: currentCoordinates.clientX - initialPosition.value.clientX,
67
+ deltaY: currentCoordinates.clientY - initialPosition.value.clientY,
68
+ };
69
+ };
70
+
71
+ /**
72
+ * Checks if the offset from the initial position exceeds the specified threshold
73
+ *
74
+ * @param currentCoordinates The current coordinates
75
+ * @param threshold The threshold of the offset in pixels
76
+ * @returns true if the offset exceeds the threshold
77
+ */
78
+ const hasMovedBeyondThreshold = (currentCoordinates: EventCoordinates, threshold = 3) => {
79
+ const { deltaX, deltaY } = calculateDelta(currentCoordinates);
80
+ return Math.abs(deltaX) > threshold || Math.abs(deltaY) > threshold;
81
+ };
82
+
83
+ return {
84
+ initialPosition,
85
+ isTouchDevice,
86
+ getEventCoordinates,
87
+ saveInitialPosition,
88
+ calculateDelta,
89
+ hasMovedBeyondThreshold,
90
+ };
91
+ }
@@ -0,0 +1,150 @@
1
+ import { ref } from "vue";
2
+ import type { IDashboardWidget, DashboardWidgetPosition } from "../types";
3
+ import type { EventCoordinates } from "./useEventCoordinates";
4
+
5
+ /**
6
+ * Interface for the cell size
7
+ */
8
+ export interface CellSize {
9
+ cellWidth: number;
10
+ cellHeight: number;
11
+ }
12
+
13
+ /**
14
+ * Interface for the drag offset
15
+ */
16
+ export interface DragOffset {
17
+ x: number;
18
+ y: number;
19
+ }
20
+
21
+ /**
22
+ * Composible for calculating the position of a widget in the grid
23
+ *
24
+ * Provides functions for converting mouse coordinates to grid coordinates,
25
+ * considering the cell sizes, drag offset, and grid boundaries
26
+ *
27
+ * @param gridColumns The number of columns in the grid
28
+ * @param getGridRows The function for getting the number of rows in the grid
29
+ * @returns An object with functions for working with the grid position
30
+ */
31
+ export function useGridPosition(gridColumns: number, getGridRows?: () => number) {
32
+ // Current preview position
33
+ const previewPosition = ref<DashboardWidgetPosition | null>(null);
34
+
35
+ // Drag offset (relative to the top left corner of the widget)
36
+ const dragOffset = ref<DragOffset>({ x: 0, y: 0 });
37
+
38
+ /**
39
+ * Calculates the drag offset
40
+ *
41
+ * @param event The mouse or touch event
42
+ * @param element The HTML element of the widget
43
+ * @param cellSize The cell sizes
44
+ */
45
+ const calculateDragOffset = (event: MouseEvent | TouchEvent, element: HTMLElement, cellSize: CellSize) => {
46
+ const rect = element.getBoundingClientRect();
47
+
48
+ // Get the coordinates of the event
49
+ let clientX: number, clientY: number;
50
+
51
+ if ("touches" in event) {
52
+ clientX = event.touches[0].clientX;
53
+ clientY = event.touches[0].clientY;
54
+ } else {
55
+ clientX = event.clientX;
56
+ clientY = event.clientY;
57
+ }
58
+
59
+ // Calculate the offset relative to the cell size
60
+ dragOffset.value = {
61
+ x: (clientX - rect.left) / cellSize.cellWidth,
62
+ y: (clientY - rect.top) / cellSize.cellHeight,
63
+ };
64
+ };
65
+
66
+ /**
67
+ * Converts mouse coordinates to grid coordinates
68
+ *
69
+ * @param coords The mouse coordinates
70
+ * @param containerRect The sizes and position of the grid container
71
+ * @param cellSize The cell sizes
72
+ * @param scrollOffset The scroll offset of the container
73
+ * @returns The coordinates in the grid
74
+ */
75
+ const mouseToGridCoordinates = (
76
+ coords: EventCoordinates,
77
+ containerRect: DOMRect,
78
+ cellSize: CellSize,
79
+ scrollOffset: { left: number; top: number },
80
+ ): { gridX: number; gridY: number } => {
81
+ // Calculate the position of the mouse relative to the container with the scroll offset
82
+ const mouseX = coords.clientX - containerRect.left + scrollOffset.left;
83
+ const mouseY = coords.clientY - containerRect.top + scrollOffset.top;
84
+
85
+ // Convert the mouse coordinates to grid coordinates with the drag offset
86
+ const gridX = Math.round(mouseX / cellSize.cellWidth - dragOffset.value.x);
87
+ const gridY = Math.round(mouseY / cellSize.cellHeight - dragOffset.value.y);
88
+
89
+ return { gridX, gridY };
90
+ };
91
+
92
+ /**
93
+ * Constrains the position of the widget to the grid boundaries
94
+ *
95
+ * @param gridX The X-coordinate in the grid
96
+ * @param gridY The Y-coordinate in the grid
97
+ * @param widgetWidth The width of the widget in cells
98
+ * @param widgetHeight The height of the widget in cells
99
+ * @returns The constrained coordinates in the grid
100
+ */
101
+ const constrainToGrid = (
102
+ gridX: number,
103
+ gridY: number,
104
+ widgetWidth: number,
105
+ widgetHeight: number,
106
+ ): { gridX: number; gridY: number } => {
107
+ // Constrain the minimum values (not less than 0)
108
+ gridX = Math.max(0, gridX);
109
+ gridY = Math.max(0, gridY);
110
+
111
+ // The right and bottom boundaries depend on the size of the widget and the size of the grid
112
+ const gridWidth = gridColumns;
113
+ const gridHeight = getGridRows?.() || 100; // Use a large value if getGridRows is not defined
114
+
115
+ // Constrain the position so that the widget remains within the grid
116
+ gridX = Math.min(gridX, gridWidth - widgetWidth);
117
+ gridY = Math.min(gridY, gridHeight - widgetHeight);
118
+
119
+ return { gridX, gridY };
120
+ };
121
+
122
+ /**
123
+ * Updates the preview position
124
+ *
125
+ * @param position The new position
126
+ * @returns The updated preview position
127
+ */
128
+ const updatePreviewPosition = (position: DashboardWidgetPosition): DashboardWidgetPosition => {
129
+ previewPosition.value = position;
130
+ return position;
131
+ };
132
+
133
+ /**
134
+ * Resets the preview position and drag offset
135
+ */
136
+ const resetPosition = () => {
137
+ previewPosition.value = null;
138
+ dragOffset.value = { x: 0, y: 0 };
139
+ };
140
+
141
+ return {
142
+ previewPosition,
143
+ dragOffset,
144
+ calculateDragOffset,
145
+ mouseToGridCoordinates,
146
+ constrainToGrid,
147
+ updatePreviewPosition,
148
+ resetPosition,
149
+ };
150
+ }