@dragonworks/ngx-dashboard 20.0.6 → 20.1.0
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.
- package/fesm2022/dragonworks-ngx-dashboard.mjs +2250 -0
- package/fesm2022/dragonworks-ngx-dashboard.mjs.map +1 -0
- package/index.d.ts +727 -0
- package/package.json +45 -34
- package/ng-package.json +0 -7
- package/src/lib/__tests__/dashboard-component-widget-state-integration.spec.ts +0 -537
- package/src/lib/cell/__tests__/cell-resize.component.spec.ts +0 -442
- package/src/lib/cell/__tests__/cell.component.spec.ts +0 -541
- package/src/lib/cell/cell-context-menu.component.ts +0 -138
- package/src/lib/cell/cell-context-menu.service.ts +0 -36
- package/src/lib/cell/cell.component.html +0 -37
- package/src/lib/cell/cell.component.scss +0 -198
- package/src/lib/cell/cell.component.ts +0 -375
- package/src/lib/dashboard/dashboard.component.html +0 -18
- package/src/lib/dashboard/dashboard.component.scss +0 -17
- package/src/lib/dashboard/dashboard.component.ts +0 -187
- package/src/lib/dashboard-editor/dashboard-editor.component.html +0 -57
- package/src/lib/dashboard-editor/dashboard-editor.component.scss +0 -87
- package/src/lib/dashboard-editor/dashboard-editor.component.ts +0 -219
- package/src/lib/dashboard-viewer/__tests__/dashboard-viewer.component.spec.ts +0 -258
- package/src/lib/dashboard-viewer/dashboard-viewer.component.html +0 -20
- package/src/lib/dashboard-viewer/dashboard-viewer.component.scss +0 -50
- package/src/lib/dashboard-viewer/dashboard-viewer.component.ts +0 -70
- package/src/lib/drop-zone/__tests__/drop-zone.component.spec.ts +0 -465
- package/src/lib/drop-zone/drop-zone.component.html +0 -20
- package/src/lib/drop-zone/drop-zone.component.scss +0 -67
- package/src/lib/drop-zone/drop-zone.component.ts +0 -122
- package/src/lib/internal-widgets/unknown-widget/unknown-widget.component.ts +0 -72
- package/src/lib/models/cell-data.ts +0 -13
- package/src/lib/models/cell-dialog.ts +0 -7
- package/src/lib/models/cell-id.ts +0 -85
- package/src/lib/models/cell-position.ts +0 -15
- package/src/lib/models/dashboard-data.dto.ts +0 -44
- package/src/lib/models/dashboard-data.utils.ts +0 -49
- package/src/lib/models/drag-data.ts +0 -6
- package/src/lib/models/index.ts +0 -11
- package/src/lib/models/reserved-space.ts +0 -24
- package/src/lib/models/widget-factory.ts +0 -33
- package/src/lib/models/widget-id.ts +0 -70
- package/src/lib/models/widget.ts +0 -21
- package/src/lib/providers/cell-settings-dialog/cell-settings-dialog.component.ts +0 -127
- package/src/lib/providers/cell-settings-dialog/cell-settings-dialog.provider.ts +0 -15
- package/src/lib/providers/cell-settings-dialog/cell-settings-dialog.tokens.ts +0 -20
- package/src/lib/providers/cell-settings-dialog/default-cell-settings-dialog.provider.ts +0 -32
- package/src/lib/providers/cell-settings-dialog/index.ts +0 -3
- package/src/lib/providers/index.ts +0 -1
- package/src/lib/services/__tests__/dashboard-bridge.service.spec.ts +0 -220
- package/src/lib/services/__tests__/dashboard-viewport.service.spec.ts +0 -362
- package/src/lib/services/dashboard-bridge.service.ts +0 -155
- package/src/lib/services/dashboard-viewport.service.ts +0 -148
- package/src/lib/services/dashboard.service.ts +0 -62
- package/src/lib/store/__tests__/dashboard-store-collision-detection.spec.ts +0 -756
- package/src/lib/store/__tests__/dashboard-store-computed-properties.spec.ts +0 -974
- package/src/lib/store/__tests__/dashboard-store-drag-drop.spec.ts +0 -279
- package/src/lib/store/__tests__/dashboard-store-export-import.spec.ts +0 -780
- package/src/lib/store/__tests__/dashboard-store-grid-config.spec.ts +0 -128
- package/src/lib/store/__tests__/dashboard-store-query-methods.spec.ts +0 -229
- package/src/lib/store/__tests__/dashboard-store-resize-operations.spec.ts +0 -652
- package/src/lib/store/__tests__/dashboard-store-widget-management.spec.ts +0 -461
- package/src/lib/store/__tests__/dashboard-store-widget-state-preservation.spec.ts +0 -369
- package/src/lib/store/dashboard-store.ts +0 -239
- package/src/lib/store/features/drag-drop.feature.ts +0 -140
- package/src/lib/store/features/grid-config.feature.ts +0 -43
- package/src/lib/store/features/resize.feature.ts +0 -140
- package/src/lib/store/features/utils/collision.utils.ts +0 -89
- package/src/lib/store/features/utils/grid-query-internal.utils.ts +0 -37
- package/src/lib/store/features/utils/resize.utils.ts +0 -165
- package/src/lib/store/features/widget-management.feature.ts +0 -158
- package/src/lib/styles/_dashboard-grid-vars.scss +0 -11
- package/src/lib/widget-list/__tests__/widget-list-bridge-integration.spec.ts +0 -137
- package/src/lib/widget-list/widget-list.component.html +0 -22
- package/src/lib/widget-list/widget-list.component.scss +0 -154
- package/src/lib/widget-list/widget-list.component.ts +0 -106
- package/src/public-api.ts +0 -21
- package/src/test-setup.ts +0 -10
- package/tsconfig.lib.json +0 -15
- package/tsconfig.lib.prod.json +0 -11
- package/tsconfig.spec.json +0 -14
|
@@ -1,465 +0,0 @@
|
|
|
1
|
-
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
2
|
-
import { ElementRef } from '@angular/core';
|
|
3
|
-
import { DropZoneComponent } from '../drop-zone.component';
|
|
4
|
-
import { DashboardStore } from '../../store/dashboard-store';
|
|
5
|
-
import { DragData, CellIdUtils, WidgetIdUtils, WidgetMetadata } from '../../models';
|
|
6
|
-
import { DashboardService } from '../../services/dashboard.service';
|
|
7
|
-
|
|
8
|
-
describe('DropZoneComponent - Focused Regression Tests', () => {
|
|
9
|
-
let component: DropZoneComponent;
|
|
10
|
-
let fixture: ComponentFixture<DropZoneComponent>;
|
|
11
|
-
let store: InstanceType<typeof DashboardStore>;
|
|
12
|
-
let dashboardService: jasmine.SpyObj<DashboardService>;
|
|
13
|
-
|
|
14
|
-
const testWidgetMetadata: WidgetMetadata = {
|
|
15
|
-
widgetTypeid: 'test-widget',
|
|
16
|
-
name: 'Test Widget',
|
|
17
|
-
description: 'A test widget',
|
|
18
|
-
svgIcon: '<svg></svg>'
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const cellDragData: DragData = {
|
|
22
|
-
kind: 'cell',
|
|
23
|
-
content: {
|
|
24
|
-
cellId: CellIdUtils.create(2, 2),
|
|
25
|
-
widgetId: WidgetIdUtils.generate(),
|
|
26
|
-
row: 2,
|
|
27
|
-
col: 2,
|
|
28
|
-
rowSpan: 1,
|
|
29
|
-
colSpan: 1
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const widgetDragData: DragData = {
|
|
34
|
-
kind: 'widget',
|
|
35
|
-
content: testWidgetMetadata
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
beforeEach(async () => {
|
|
39
|
-
const spy = jasmine.createSpyObj('DashboardService', ['getFactory']);
|
|
40
|
-
|
|
41
|
-
await TestBed.configureTestingModule({
|
|
42
|
-
imports: [DropZoneComponent],
|
|
43
|
-
providers: [
|
|
44
|
-
DashboardStore,
|
|
45
|
-
{ provide: DashboardService, useValue: spy }
|
|
46
|
-
]
|
|
47
|
-
}).compileComponents();
|
|
48
|
-
|
|
49
|
-
fixture = TestBed.createComponent(DropZoneComponent);
|
|
50
|
-
component = fixture.componentInstance;
|
|
51
|
-
store = TestBed.inject(DashboardStore);
|
|
52
|
-
dashboardService = TestBed.inject(DashboardService) as jasmine.SpyObj<DashboardService>;
|
|
53
|
-
|
|
54
|
-
// Set required inputs
|
|
55
|
-
fixture.componentRef.setInput('row', 3);
|
|
56
|
-
fixture.componentRef.setInput('col', 4);
|
|
57
|
-
fixture.componentRef.setInput('index', 12);
|
|
58
|
-
|
|
59
|
-
fixture.detectChanges();
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
describe('Critical Drag & Drop Event Handling', () => {
|
|
63
|
-
it('should prevent default and stop propagation on dragenter', () => {
|
|
64
|
-
const event = new DragEvent('dragenter', { bubbles: true });
|
|
65
|
-
spyOn(event, 'preventDefault');
|
|
66
|
-
spyOn(event, 'stopPropagation');
|
|
67
|
-
|
|
68
|
-
component.onDragEnter(event);
|
|
69
|
-
|
|
70
|
-
expect(event.preventDefault).toHaveBeenCalled();
|
|
71
|
-
expect(event.stopPropagation).toHaveBeenCalled();
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('should prevent default and stop propagation on dragover', () => {
|
|
75
|
-
const event = new DragEvent('dragover', { bubbles: true });
|
|
76
|
-
Object.defineProperty(event, 'dataTransfer', {
|
|
77
|
-
value: { dropEffect: 'none' }
|
|
78
|
-
});
|
|
79
|
-
spyOn(event, 'preventDefault');
|
|
80
|
-
spyOn(event, 'stopPropagation');
|
|
81
|
-
|
|
82
|
-
component.onDragOver(event);
|
|
83
|
-
|
|
84
|
-
expect(event.preventDefault).toHaveBeenCalled();
|
|
85
|
-
expect(event.stopPropagation).toHaveBeenCalled();
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('should prevent default and stop propagation on dragleave', () => {
|
|
89
|
-
const event = new DragEvent('dragleave', { bubbles: true });
|
|
90
|
-
spyOn(event, 'preventDefault');
|
|
91
|
-
spyOn(event, 'stopPropagation');
|
|
92
|
-
|
|
93
|
-
component.onDragLeave(event);
|
|
94
|
-
|
|
95
|
-
expect(event.preventDefault).toHaveBeenCalled();
|
|
96
|
-
expect(event.stopPropagation).toHaveBeenCalled();
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('should prevent default and stop propagation on drop', () => {
|
|
100
|
-
const event = new DragEvent('drop', { bubbles: true });
|
|
101
|
-
Object.defineProperty(event, 'dataTransfer', {
|
|
102
|
-
value: {}
|
|
103
|
-
});
|
|
104
|
-
spyOn(event, 'preventDefault');
|
|
105
|
-
spyOn(event, 'stopPropagation');
|
|
106
|
-
|
|
107
|
-
component.onDrop(event);
|
|
108
|
-
|
|
109
|
-
expect(event.preventDefault).toHaveBeenCalled();
|
|
110
|
-
expect(event.stopPropagation).toHaveBeenCalled();
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('should return move drop effect for cell drag data', () => {
|
|
114
|
-
store.startDrag(cellDragData);
|
|
115
|
-
fixture.detectChanges();
|
|
116
|
-
|
|
117
|
-
expect(component.dropEffect()).toBe('move');
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it('should return copy drop effect for widget drag data', () => {
|
|
121
|
-
store.startDrag(widgetDragData);
|
|
122
|
-
fixture.detectChanges();
|
|
123
|
-
|
|
124
|
-
expect(component.dropEffect()).toBe('copy');
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it('should return none drop effect for invalid drops', () => {
|
|
128
|
-
store.endDrag();
|
|
129
|
-
fixture.componentRef.setInput('highlightInvalid', true);
|
|
130
|
-
fixture.detectChanges();
|
|
131
|
-
|
|
132
|
-
expect(component.dropEffect()).toBe('none');
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it('should return none drop effect when no drag data', () => {
|
|
136
|
-
store.endDrag();
|
|
137
|
-
fixture.detectChanges();
|
|
138
|
-
|
|
139
|
-
expect(component.dropEffect()).toBe('none');
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it('should detect when mouse leaves element boundaries through dragExit behavior', () => {
|
|
143
|
-
spyOn(component.dragExit, 'emit');
|
|
144
|
-
const mockBoundingRect = {
|
|
145
|
-
left: 100,
|
|
146
|
-
right: 200,
|
|
147
|
-
top: 50,
|
|
148
|
-
bottom: 150
|
|
149
|
-
};
|
|
150
|
-
spyOn(component.nativeElement, 'getBoundingClientRect').and.returnValue(mockBoundingRect as DOMRect);
|
|
151
|
-
|
|
152
|
-
// Mouse outside left boundary - should emit dragExit
|
|
153
|
-
const event1 = new DragEvent('dragleave', { clientX: 90, clientY: 100 });
|
|
154
|
-
component.onDragLeave(event1);
|
|
155
|
-
expect(component.dragExit.emit).toHaveBeenCalled();
|
|
156
|
-
|
|
157
|
-
// Reset spy
|
|
158
|
-
(component.dragExit.emit as jasmine.Spy).calls.reset();
|
|
159
|
-
|
|
160
|
-
// Mouse inside boundaries - should not emit dragExit
|
|
161
|
-
const event2 = new DragEvent('dragleave', { clientX: 150, clientY: 100 });
|
|
162
|
-
component.onDragLeave(event2);
|
|
163
|
-
expect(component.dragExit.emit).not.toHaveBeenCalled();
|
|
164
|
-
});
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
describe('Output Event Emission', () => {
|
|
168
|
-
it('should emit dragEnter with correct row and col data', () => {
|
|
169
|
-
spyOn(component.dragEnter, 'emit');
|
|
170
|
-
const event = new DragEvent('dragenter');
|
|
171
|
-
|
|
172
|
-
component.onDragEnter(event);
|
|
173
|
-
|
|
174
|
-
expect(component.dragEnter.emit).toHaveBeenCalledWith({ row: 3, col: 4 });
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it('should emit dragOver with correct row and col data', () => {
|
|
178
|
-
spyOn(component.dragOver, 'emit');
|
|
179
|
-
const event = new DragEvent('dragover');
|
|
180
|
-
Object.defineProperty(event, 'dataTransfer', {
|
|
181
|
-
value: { dropEffect: 'none' }
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
component.onDragOver(event);
|
|
185
|
-
|
|
186
|
-
expect(component.dragOver.emit).toHaveBeenCalledWith({ row: 3, col: 4 });
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
it('should emit dragDrop with correct data structure when drag data exists', () => {
|
|
190
|
-
spyOn(component.dragDrop, 'emit');
|
|
191
|
-
store.startDrag(cellDragData);
|
|
192
|
-
fixture.detectChanges();
|
|
193
|
-
|
|
194
|
-
const event = new DragEvent('drop');
|
|
195
|
-
Object.defineProperty(event, 'dataTransfer', {
|
|
196
|
-
value: {}
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
component.onDrop(event);
|
|
200
|
-
|
|
201
|
-
expect(component.dragDrop.emit).toHaveBeenCalledWith({
|
|
202
|
-
data: cellDragData,
|
|
203
|
-
target: { row: 3, col: 4 }
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
it('should not emit dragDrop when no drag data exists', () => {
|
|
208
|
-
spyOn(component.dragDrop, 'emit');
|
|
209
|
-
store.endDrag();
|
|
210
|
-
fixture.detectChanges();
|
|
211
|
-
|
|
212
|
-
const event = new DragEvent('drop');
|
|
213
|
-
Object.defineProperty(event, 'dataTransfer', {
|
|
214
|
-
value: {}
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
component.onDrop(event);
|
|
218
|
-
|
|
219
|
-
expect(component.dragDrop.emit).not.toHaveBeenCalled();
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
it('should not emit dragDrop when dataTransfer is missing', () => {
|
|
223
|
-
spyOn(component.dragDrop, 'emit');
|
|
224
|
-
store.startDrag(cellDragData);
|
|
225
|
-
fixture.detectChanges();
|
|
226
|
-
|
|
227
|
-
const event = new DragEvent('drop');
|
|
228
|
-
|
|
229
|
-
component.onDrop(event);
|
|
230
|
-
|
|
231
|
-
expect(component.dragDrop.emit).not.toHaveBeenCalled();
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
it('should emit dragExit only when actually leaving element', () => {
|
|
235
|
-
spyOn(component.dragExit, 'emit');
|
|
236
|
-
const mockBoundingRect = {
|
|
237
|
-
left: 100,
|
|
238
|
-
right: 200,
|
|
239
|
-
top: 50,
|
|
240
|
-
bottom: 150
|
|
241
|
-
};
|
|
242
|
-
spyOn(component.nativeElement, 'getBoundingClientRect').and.returnValue(mockBoundingRect as DOMRect);
|
|
243
|
-
|
|
244
|
-
// Mouse outside boundaries - should emit
|
|
245
|
-
const event = new DragEvent('dragleave', { clientX: 90, clientY: 100 });
|
|
246
|
-
component.onDragLeave(event);
|
|
247
|
-
|
|
248
|
-
expect(component.dragExit.emit).toHaveBeenCalled();
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
it('should not emit dragExit when not leaving element', () => {
|
|
252
|
-
spyOn(component.dragExit, 'emit');
|
|
253
|
-
const mockBoundingRect = {
|
|
254
|
-
left: 100,
|
|
255
|
-
right: 200,
|
|
256
|
-
top: 50,
|
|
257
|
-
bottom: 150
|
|
258
|
-
};
|
|
259
|
-
spyOn(component.nativeElement, 'getBoundingClientRect').and.returnValue(mockBoundingRect as DOMRect);
|
|
260
|
-
|
|
261
|
-
// Mouse inside boundaries - should not emit
|
|
262
|
-
const event = new DragEvent('dragleave', { clientX: 150, clientY: 100 });
|
|
263
|
-
component.onDragLeave(event);
|
|
264
|
-
|
|
265
|
-
expect(component.dragExit.emit).not.toHaveBeenCalled();
|
|
266
|
-
});
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
describe('Visual State Management', () => {
|
|
270
|
-
it('should apply highlight class when highlight is true and highlightInvalid is false', () => {
|
|
271
|
-
fixture.componentRef.setInput('highlight', true);
|
|
272
|
-
fixture.componentRef.setInput('highlightInvalid', false);
|
|
273
|
-
fixture.detectChanges();
|
|
274
|
-
|
|
275
|
-
const dropZone = fixture.nativeElement.querySelector('.drop-zone');
|
|
276
|
-
expect(dropZone.classList).toContain('drop-zone--highlight');
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
it('should not apply highlight class when highlightInvalid is true', () => {
|
|
280
|
-
fixture.componentRef.setInput('highlight', true);
|
|
281
|
-
fixture.componentRef.setInput('highlightInvalid', true);
|
|
282
|
-
fixture.detectChanges();
|
|
283
|
-
|
|
284
|
-
const dropZone = fixture.nativeElement.querySelector('.drop-zone');
|
|
285
|
-
expect(dropZone.classList).not.toContain('drop-zone--highlight');
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
it('should apply invalid class when highlightInvalid is true', () => {
|
|
289
|
-
fixture.componentRef.setInput('highlightInvalid', true);
|
|
290
|
-
fixture.detectChanges();
|
|
291
|
-
|
|
292
|
-
const dropZone = fixture.nativeElement.querySelector('.drop-zone');
|
|
293
|
-
expect(dropZone.classList).toContain('drop-zone--invalid');
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
it('should apply resize class when highlightResize is true', () => {
|
|
297
|
-
fixture.componentRef.setInput('highlightResize', true);
|
|
298
|
-
fixture.detectChanges();
|
|
299
|
-
|
|
300
|
-
const dropZone = fixture.nativeElement.querySelector('.drop-zone');
|
|
301
|
-
expect(dropZone.classList).toContain('drop-zone--resize');
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
it('should display edit mode cell numbers with correct values', () => {
|
|
305
|
-
fixture.componentRef.setInput('editMode', true);
|
|
306
|
-
fixture.detectChanges();
|
|
307
|
-
|
|
308
|
-
const cellNumber = fixture.nativeElement.querySelector('.edit-mode-cell-number');
|
|
309
|
-
expect(cellNumber).toBeTruthy();
|
|
310
|
-
expect(cellNumber.textContent.trim()).toBe('12 3,4');
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
it('should not display edit mode cell numbers when editMode is false', () => {
|
|
314
|
-
fixture.componentRef.setInput('editMode', false);
|
|
315
|
-
fixture.detectChanges();
|
|
316
|
-
|
|
317
|
-
const cellNumber = fixture.nativeElement.querySelector('.edit-mode-cell-number');
|
|
318
|
-
expect(cellNumber).toBeFalsy();
|
|
319
|
-
});
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
describe('Integration & Edge Cases', () => {
|
|
323
|
-
it('should correctly read dragData from store', () => {
|
|
324
|
-
store.startDrag(cellDragData);
|
|
325
|
-
fixture.detectChanges();
|
|
326
|
-
|
|
327
|
-
expect(component.dragData()).toEqual(cellDragData);
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
it('should handle null dragData gracefully', () => {
|
|
331
|
-
store.endDrag();
|
|
332
|
-
fixture.detectChanges();
|
|
333
|
-
|
|
334
|
-
expect(component.dragData()).toBeNull();
|
|
335
|
-
expect(component.dropEffect()).toBe('none');
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
it('should set dataTransfer dropEffect on dragover when drag data exists', () => {
|
|
339
|
-
store.startDrag(cellDragData);
|
|
340
|
-
fixture.detectChanges();
|
|
341
|
-
|
|
342
|
-
const mockDataTransfer = { dropEffect: 'none' };
|
|
343
|
-
const event = new DragEvent('dragover');
|
|
344
|
-
Object.defineProperty(event, 'dataTransfer', {
|
|
345
|
-
value: mockDataTransfer
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
component.onDragOver(event);
|
|
349
|
-
|
|
350
|
-
expect(mockDataTransfer.dropEffect).toBe('move');
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
it('should not set dataTransfer dropEffect when no drag data', () => {
|
|
354
|
-
store.endDrag();
|
|
355
|
-
fixture.detectChanges();
|
|
356
|
-
|
|
357
|
-
const mockDataTransfer = { dropEffect: 'copy' };
|
|
358
|
-
const event = new DragEvent('dragover');
|
|
359
|
-
Object.defineProperty(event, 'dataTransfer', {
|
|
360
|
-
value: mockDataTransfer
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
component.onDragOver(event);
|
|
364
|
-
|
|
365
|
-
expect(mockDataTransfer.dropEffect).toBe('copy'); // Should remain unchanged
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
it('should generate unique dropZoneId', () => {
|
|
369
|
-
expect(component.dropZoneId()).toBe('drop-zone-3-4');
|
|
370
|
-
});
|
|
371
|
-
|
|
372
|
-
it('should provide correct dropData object', () => {
|
|
373
|
-
expect(component.dropData()).toEqual({ row: 3, col: 4 });
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
it('should handle rapid drag events without state corruption', () => {
|
|
377
|
-
spyOn(component.dragEnter, 'emit');
|
|
378
|
-
spyOn(component.dragExit, 'emit');
|
|
379
|
-
|
|
380
|
-
const mockBoundingRect = {
|
|
381
|
-
left: 100,
|
|
382
|
-
right: 200,
|
|
383
|
-
top: 50,
|
|
384
|
-
bottom: 150
|
|
385
|
-
};
|
|
386
|
-
spyOn(component.nativeElement, 'getBoundingClientRect').and.returnValue(mockBoundingRect as DOMRect);
|
|
387
|
-
|
|
388
|
-
const enterEvent = new DragEvent('dragenter');
|
|
389
|
-
const leaveEvent = new DragEvent('dragleave', { clientX: 90, clientY: 100 }); // Outside bounds
|
|
390
|
-
|
|
391
|
-
// Rapid fire events
|
|
392
|
-
component.onDragEnter(enterEvent);
|
|
393
|
-
component.onDragLeave(leaveEvent);
|
|
394
|
-
component.onDragEnter(enterEvent);
|
|
395
|
-
component.onDragLeave(leaveEvent);
|
|
396
|
-
|
|
397
|
-
expect(component.dragEnter.emit).toHaveBeenCalledTimes(2);
|
|
398
|
-
expect(component.dragExit.emit).toHaveBeenCalledTimes(2);
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
it('should handle drag events in correct sequence', () => {
|
|
402
|
-
spyOn(component.dragEnter, 'emit');
|
|
403
|
-
spyOn(component.dragOver, 'emit');
|
|
404
|
-
spyOn(component.dragExit, 'emit');
|
|
405
|
-
spyOn(component.dragDrop, 'emit');
|
|
406
|
-
|
|
407
|
-
const mockBoundingRect = {
|
|
408
|
-
left: 100,
|
|
409
|
-
right: 200,
|
|
410
|
-
top: 50,
|
|
411
|
-
bottom: 150
|
|
412
|
-
};
|
|
413
|
-
spyOn(component.nativeElement, 'getBoundingClientRect').and.returnValue(mockBoundingRect as DOMRect);
|
|
414
|
-
|
|
415
|
-
store.startDrag(cellDragData);
|
|
416
|
-
fixture.detectChanges();
|
|
417
|
-
|
|
418
|
-
const enterEvent = new DragEvent('dragenter');
|
|
419
|
-
const overEvent = new DragEvent('dragover');
|
|
420
|
-
Object.defineProperty(overEvent, 'dataTransfer', {
|
|
421
|
-
value: { dropEffect: 'none' }
|
|
422
|
-
});
|
|
423
|
-
const dropEvent = new DragEvent('drop');
|
|
424
|
-
Object.defineProperty(dropEvent, 'dataTransfer', {
|
|
425
|
-
value: {}
|
|
426
|
-
});
|
|
427
|
-
const leaveEvent = new DragEvent('dragleave', { clientX: 90, clientY: 100 }); // Outside bounds
|
|
428
|
-
|
|
429
|
-
component.onDragEnter(enterEvent);
|
|
430
|
-
component.onDragOver(overEvent);
|
|
431
|
-
component.onDrop(dropEvent);
|
|
432
|
-
component.onDragLeave(leaveEvent);
|
|
433
|
-
|
|
434
|
-
expect(component.dragEnter.emit).toHaveBeenCalledWith({ row: 3, col: 4 });
|
|
435
|
-
expect(component.dragOver.emit).toHaveBeenCalledWith({ row: 3, col: 4 });
|
|
436
|
-
expect(component.dragDrop.emit).toHaveBeenCalledWith({
|
|
437
|
-
data: cellDragData,
|
|
438
|
-
target: { row: 3, col: 4 }
|
|
439
|
-
});
|
|
440
|
-
expect(component.dragExit.emit).toHaveBeenCalled();
|
|
441
|
-
});
|
|
442
|
-
});
|
|
443
|
-
|
|
444
|
-
describe('Grid Positioning', () => {
|
|
445
|
-
it('should apply correct grid positioning styles', () => {
|
|
446
|
-
fixture.detectChanges();
|
|
447
|
-
|
|
448
|
-
const dropZone = fixture.nativeElement.querySelector('.drop-zone');
|
|
449
|
-
expect(dropZone.style.gridRow).toBe('3');
|
|
450
|
-
expect(dropZone.style.gridColumn).toBe('4');
|
|
451
|
-
});
|
|
452
|
-
|
|
453
|
-
it('should update positioning when inputs change', () => {
|
|
454
|
-
fixture.componentRef.setInput('row', 5);
|
|
455
|
-
fixture.componentRef.setInput('col', 6);
|
|
456
|
-
fixture.detectChanges();
|
|
457
|
-
|
|
458
|
-
const dropZone = fixture.nativeElement.querySelector('.drop-zone');
|
|
459
|
-
expect(dropZone.style.gridRow).toBe('5');
|
|
460
|
-
expect(dropZone.style.gridColumn).toBe('6');
|
|
461
|
-
expect(component.dropZoneId()).toBe('drop-zone-5-6');
|
|
462
|
-
expect(component.dropData()).toEqual({ row: 5, col: 6 });
|
|
463
|
-
});
|
|
464
|
-
});
|
|
465
|
-
});
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
<!-- drop-zone.component.html -->
|
|
2
|
-
<div
|
|
3
|
-
class="drop-zone"
|
|
4
|
-
[class.drop-zone--highlight]="highlight() && !highlightInvalid()"
|
|
5
|
-
[class.drop-zone--invalid]="highlightInvalid()"
|
|
6
|
-
[class.drop-zone--resize]="highlightResize()"
|
|
7
|
-
[style.grid-row]="row()"
|
|
8
|
-
[style.grid-column]="col()"
|
|
9
|
-
(dragenter)="onDragEnter($event)"
|
|
10
|
-
(dragover)="onDragOver($event)"
|
|
11
|
-
(dragleave)="onDragLeave($event)"
|
|
12
|
-
(drop)="onDrop($event)"
|
|
13
|
-
>
|
|
14
|
-
@if (editMode()) {
|
|
15
|
-
<div class="edit-mode-cell-number">
|
|
16
|
-
{{ index() }}<br />
|
|
17
|
-
{{ row() }},{{ col() }}
|
|
18
|
-
</div>
|
|
19
|
-
}
|
|
20
|
-
</div>
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
// drop-zone.component.scss
|
|
2
|
-
|
|
3
|
-
.drop-zone {
|
|
4
|
-
width: 100%;
|
|
5
|
-
height: 100%;
|
|
6
|
-
z-index: 0;
|
|
7
|
-
|
|
8
|
-
// Ensure proper alignment when explicitly positioned in CSS Grid
|
|
9
|
-
align-self: stretch;
|
|
10
|
-
justify-self: stretch;
|
|
11
|
-
|
|
12
|
-
// Ensure the drop zone takes up the full grid area
|
|
13
|
-
display: block;
|
|
14
|
-
box-sizing: border-box;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
.drop-zone--active {
|
|
18
|
-
background-color: rgba(128, 128, 128, 0.5);
|
|
19
|
-
// background-color: var(--mat-sys-surface-dim);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
.drop-zone--highlight {
|
|
23
|
-
background-color: rgba(128, 128, 128, 0.5);
|
|
24
|
-
// background-color: var(--mat-sys-surface-dim);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
.drop-zone--invalid {
|
|
28
|
-
// background-color: var(--mat-sys-error);
|
|
29
|
-
// background-color: color-mix(in srgb, var(--mat-sys-error) 50%, transparent);
|
|
30
|
-
//background-color: rgba(255, 0, 0, 0.2);
|
|
31
|
-
|
|
32
|
-
background-color: light-dark(
|
|
33
|
-
color-mix(in srgb, var(--mat-sys-error) 40%, white),
|
|
34
|
-
color-mix(in srgb, var(--mat-sys-error) 80%, black)
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
.drop-zone--resize {
|
|
39
|
-
background-color: rgba(33, 150, 243, 0.3);
|
|
40
|
-
outline: 1px solid rgba(33, 150, 243, 0.6);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Edit mode cell numbers - centered in cell
|
|
44
|
-
.edit-mode-cell-number {
|
|
45
|
-
// Text styling
|
|
46
|
-
font-size: 10px;
|
|
47
|
-
// color: var(-mat-sys-surface-container-highest);
|
|
48
|
-
color: rgba(100, 100, 100, 0.6);
|
|
49
|
-
// color: color-mix(in srgb, var(--mat-sys-surface-on-dim) 40%, transparent);
|
|
50
|
-
|
|
51
|
-
pointer-events: none;
|
|
52
|
-
user-select: none;
|
|
53
|
-
z-index: -1;
|
|
54
|
-
|
|
55
|
-
// Center in cell
|
|
56
|
-
display: flex;
|
|
57
|
-
flex-direction: column;
|
|
58
|
-
align-items: center;
|
|
59
|
-
justify-content: center;
|
|
60
|
-
text-align: center;
|
|
61
|
-
|
|
62
|
-
// Take up entire cell area
|
|
63
|
-
width: 100%;
|
|
64
|
-
height: 100%;
|
|
65
|
-
|
|
66
|
-
// background-color: rgba(255, 255, 255, 0.1);
|
|
67
|
-
}
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
// drop-zone.component.ts
|
|
2
|
-
import {
|
|
3
|
-
Component,
|
|
4
|
-
ElementRef,
|
|
5
|
-
inject,
|
|
6
|
-
input,
|
|
7
|
-
output,
|
|
8
|
-
computed,
|
|
9
|
-
ChangeDetectionStrategy,
|
|
10
|
-
} from '@angular/core';
|
|
11
|
-
import { CommonModule } from '@angular/common';
|
|
12
|
-
import { DashboardStore } from '../store/dashboard-store';
|
|
13
|
-
import { DragData } from '../models';
|
|
14
|
-
|
|
15
|
-
@Component({
|
|
16
|
-
selector: 'lib-drop-zone',
|
|
17
|
-
standalone: true,
|
|
18
|
-
imports: [CommonModule],
|
|
19
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
20
|
-
templateUrl: './drop-zone.component.html',
|
|
21
|
-
styleUrl: './drop-zone.component.scss',
|
|
22
|
-
})
|
|
23
|
-
export class DropZoneComponent {
|
|
24
|
-
// Required inputs
|
|
25
|
-
row = input.required<number>();
|
|
26
|
-
col = input.required<number>();
|
|
27
|
-
index = input.required<number>();
|
|
28
|
-
|
|
29
|
-
// Optional inputs with defaults
|
|
30
|
-
highlight = input(false);
|
|
31
|
-
highlightInvalid = input(false);
|
|
32
|
-
highlightResize = input(false);
|
|
33
|
-
editMode = input(false);
|
|
34
|
-
|
|
35
|
-
// Outputs
|
|
36
|
-
dragEnter = output<{ row: number; col: number }>();
|
|
37
|
-
dragExit = output<void>();
|
|
38
|
-
dragOver = output<{ row: number; col: number }>();
|
|
39
|
-
dragDrop = output<{
|
|
40
|
-
data: DragData;
|
|
41
|
-
target: { row: number; col: number };
|
|
42
|
-
}>();
|
|
43
|
-
|
|
44
|
-
// Computed properties
|
|
45
|
-
dropZoneId = computed(() => `drop-zone-${this.row()}-${this.col()}`);
|
|
46
|
-
|
|
47
|
-
dropData = computed(() => ({
|
|
48
|
-
row: this.row(),
|
|
49
|
-
col: this.col(),
|
|
50
|
-
}));
|
|
51
|
-
|
|
52
|
-
// Abstract drag state from store
|
|
53
|
-
dragData = computed(() => this.#store.dragData());
|
|
54
|
-
|
|
55
|
-
// Computed drop effect based on drag data and validity
|
|
56
|
-
dropEffect = computed(() => {
|
|
57
|
-
const data = this.dragData();
|
|
58
|
-
if (!data || this.highlightInvalid()) {
|
|
59
|
-
return 'none';
|
|
60
|
-
}
|
|
61
|
-
return data.kind === 'cell' ? 'move' : 'copy';
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
readonly #store = inject(DashboardStore);
|
|
65
|
-
readonly #elementRef = inject(ElementRef);
|
|
66
|
-
|
|
67
|
-
get nativeElement(): HTMLElement {
|
|
68
|
-
return this.#elementRef.nativeElement;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
onDragEnter(event: DragEvent): void {
|
|
72
|
-
event.preventDefault();
|
|
73
|
-
event.stopPropagation();
|
|
74
|
-
this.dragEnter.emit({ row: this.row(), col: this.col() });
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
onDragOver(event: DragEvent): void {
|
|
78
|
-
event.preventDefault();
|
|
79
|
-
event.stopPropagation();
|
|
80
|
-
|
|
81
|
-
if (event.dataTransfer && this.dragData()) {
|
|
82
|
-
event.dataTransfer.dropEffect = this.dropEffect();
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
this.dragOver.emit({ row: this.row(), col: this.col() });
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
onDragLeave(event: DragEvent): void {
|
|
89
|
-
event.preventDefault();
|
|
90
|
-
event.stopPropagation();
|
|
91
|
-
|
|
92
|
-
// Only emit if actually leaving the element (not entering a child)
|
|
93
|
-
if (this.#isLeavingElement(event)) {
|
|
94
|
-
this.dragExit.emit();
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
#isLeavingElement(event: DragEvent): boolean {
|
|
99
|
-
const rect = this.#elementRef.nativeElement.getBoundingClientRect();
|
|
100
|
-
return (
|
|
101
|
-
event.clientX <= rect.left ||
|
|
102
|
-
event.clientX >= rect.right ||
|
|
103
|
-
event.clientY <= rect.top ||
|
|
104
|
-
event.clientY >= rect.bottom
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
onDrop(event: DragEvent): void {
|
|
109
|
-
event.preventDefault();
|
|
110
|
-
event.stopPropagation();
|
|
111
|
-
|
|
112
|
-
if (!event.dataTransfer) return;
|
|
113
|
-
|
|
114
|
-
const data = this.dragData();
|
|
115
|
-
if (data) {
|
|
116
|
-
this.dragDrop.emit({
|
|
117
|
-
data,
|
|
118
|
-
target: { row: this.row(), col: this.col() },
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|