ms-data-grid 0.0.16 → 0.0.17
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/CHANGELOG.md +57 -0
- package/DOCUMENTATION.md +243 -0
- package/ng-package.json +10 -0
- package/package.json +33 -45
- package/src/lib/css/bootstrap.css +12043 -0
- package/src/lib/data-grid/data-grid.component.html +4806 -0
- package/src/lib/data-grid/data-grid.component.scss +1502 -0
- package/src/lib/data-grid/data-grid.component.spec.ts +28 -0
- package/src/lib/data-grid/data-grid.component.ts +4216 -0
- package/src/lib/data-grid/statuses.ts +47 -0
- package/src/lib/data-grid.module.ts +20 -0
- package/src/lib/data-grid.service.spec.ts +16 -0
- package/src/lib/data-grid.service.ts +9 -0
- package/src/lib/directives/draggable-header.directive.spec.ts +11 -0
- package/src/lib/directives/draggable-header.directive.ts +172 -0
- package/src/lib/pipes/filter.pipe.spec.ts +11 -0
- package/src/lib/pipes/filter.pipe.ts +16 -0
- package/src/lib/services/cell-selection.service.spec.ts +16 -0
- package/src/lib/services/cell-selection.service.ts +234 -0
- package/src/lib/services/common.service.spec.ts +16 -0
- package/src/lib/services/common.service.ts +239 -0
- package/src/lib/services/copy-service.service.spec.ts +16 -0
- package/src/lib/services/copy-service.service.ts +251 -0
- package/src/lib/services/drag-drp.service.spec.ts +16 -0
- package/src/lib/services/drag-drp.service.ts +58 -0
- package/src/lib/services/split-columns.service.spec.ts +16 -0
- package/src/lib/services/split-columns.service.ts +148 -0
- package/src/lib/services/swap-columns.service.spec.ts +16 -0
- package/src/lib/services/swap-columns.service.ts +162 -0
- package/{public-api.d.ts → src/public-api.ts} +8 -4
- package/tsconfig.lib.json +16 -0
- package/tsconfig.lib.prod.json +10 -0
- package/tsconfig.spec.json +14 -0
- package/esm2022/lib/data-grid/data-grid.component.mjs +0 -3623
- package/esm2022/lib/data-grid/statuses.mjs +0 -44
- package/esm2022/lib/data-grid.module.mjs +0 -26
- package/esm2022/lib/data-grid.service.mjs +0 -14
- package/esm2022/lib/directives/draggable-header.directive.mjs +0 -145
- package/esm2022/lib/pipes/filter.pipe.mjs +0 -22
- package/esm2022/lib/services/common.service.mjs +0 -206
- package/esm2022/lib/services/copy-service.service.mjs +0 -221
- package/esm2022/lib/services/split-columns.service.mjs +0 -143
- package/esm2022/lib/services/swap-columns.service.mjs +0 -118
- package/esm2022/ms-data-grid.mjs +0 -5
- package/esm2022/public-api.mjs +0 -8
- package/fesm2022/ms-data-grid.mjs +0 -4546
- package/fesm2022/ms-data-grid.mjs.map +0 -1
- package/index.d.ts +0 -5
- package/lib/data-grid/data-grid.component.d.ts +0 -468
- package/lib/data-grid/statuses.d.ts +0 -3
- package/lib/data-grid.module.d.ts +0 -14
- package/lib/data-grid.service.d.ts +0 -6
- package/lib/directives/draggable-header.directive.d.ts +0 -31
- package/lib/pipes/filter.pipe.d.ts +0 -7
- package/lib/services/common.service.d.ts +0 -17
- package/lib/services/copy-service.service.d.ts +0 -14
- package/lib/services/split-columns.service.d.ts +0 -9
- package/lib/services/swap-columns.service.d.ts +0 -19
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export const STATUSES_BADGE_MAP: { [key: string]: string } = {
|
|
2
|
+
// Success – Green
|
|
3
|
+
'active': 'badge badge-success',
|
|
4
|
+
'approved': 'badge badge-success',
|
|
5
|
+
'accepted': 'badge badge-success',
|
|
6
|
+
'completed': 'badge badge-success',
|
|
7
|
+
'evaluated': 'badge badge-success',
|
|
8
|
+
'assigned': 'badge badge-success',
|
|
9
|
+
'scrap': 'badge badge-success',
|
|
10
|
+
'move-available': 'badge badge-success',
|
|
11
|
+
'move-assigned': 'badge badge-success',
|
|
12
|
+
|
|
13
|
+
// Warning – Yellow/Amber
|
|
14
|
+
'contract': 'badge badge-warning',
|
|
15
|
+
'warranty': 'badge badge-warning',
|
|
16
|
+
'scheduled': 'badge badge-warning',
|
|
17
|
+
'leased': 'badge badge-warning',
|
|
18
|
+
'disposed': 'badge badge-warning',
|
|
19
|
+
'maintenance': 'badge badge-warning',
|
|
20
|
+
'assigning start': 'badge badge-warning',
|
|
21
|
+
'evaluation start': 'badge badge-warning',
|
|
22
|
+
'to be start assigning': 'badge badge-warning',
|
|
23
|
+
'pending': 'badge badge-warning',
|
|
24
|
+
'leave': 'badge badge-warning',
|
|
25
|
+
|
|
26
|
+
// Danger – Red
|
|
27
|
+
'inactive': 'badge badge-danger',
|
|
28
|
+
'rejected': 'badge badge-danger',
|
|
29
|
+
'unassigned': 'badge badge-danger',
|
|
30
|
+
'trashed': 'badge badge-danger',
|
|
31
|
+
'onhold': 'badge badge-danger',
|
|
32
|
+
'assigning stop': 'badge badge-danger',
|
|
33
|
+
'evaluation stop': 'badge badge-danger',
|
|
34
|
+
'unavailable': 'badge badge-danger',
|
|
35
|
+
'move-error': 'badge badge-danger',
|
|
36
|
+
'failed': 'badge badge-danger',
|
|
37
|
+
'absent': 'badge badge-danger',
|
|
38
|
+
|
|
39
|
+
// Info – Blue
|
|
40
|
+
'insurance': 'badge badge-info',
|
|
41
|
+
'pastdue': 'badge badge-info',
|
|
42
|
+
|
|
43
|
+
// Dark – Neutral/Other
|
|
44
|
+
'expired': 'badge badge-dark',
|
|
45
|
+
'draft': 'badge badge-dark',
|
|
46
|
+
'present': 'badge badge-success'
|
|
47
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { NgModule } from '@angular/core';
|
|
2
|
+
import { DataGridComponent } from './data-grid/data-grid.component';
|
|
3
|
+
import { CommonModule } from '@angular/common';
|
|
4
|
+
import { FormsModule } from '@angular/forms';
|
|
5
|
+
import { FilterPipe } from './pipes/filter.pipe';
|
|
6
|
+
import { DraggableHeaderDirective } from './directives/draggable-header.directive';
|
|
7
|
+
import { DragDropModule } from '@angular/cdk/drag-drop';
|
|
8
|
+
import { InlineSVGModule } from 'ng-inline-svg';
|
|
9
|
+
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
|
10
|
+
import { ScrollingModule } from '@angular/cdk/scrolling';
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@NgModule({
|
|
15
|
+
declarations: [DataGridComponent, FilterPipe, DraggableHeaderDirective],
|
|
16
|
+
imports: [CommonModule, FormsModule, DragDropModule, InlineSVGModule.forRoot(), ScrollingModule],
|
|
17
|
+
exports: [DataGridComponent, DraggableHeaderDirective],
|
|
18
|
+
providers: [],
|
|
19
|
+
})
|
|
20
|
+
export class DataGridModule {}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { TestBed } from '@angular/core/testing';
|
|
2
|
+
|
|
3
|
+
import { DataGridService } from './data-grid.service';
|
|
4
|
+
|
|
5
|
+
describe('DataGridService', () => {
|
|
6
|
+
let service: DataGridService;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
TestBed.configureTestingModule({});
|
|
10
|
+
service = TestBed.inject(DataGridService);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should be created', () => {
|
|
14
|
+
expect(service).toBeTruthy();
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/* tslint:disable:no-unused-variable */
|
|
2
|
+
|
|
3
|
+
import { TestBed, async } from '@angular/core/testing';
|
|
4
|
+
import { DraggableHeaderDirective } from './draggable-header.directive';
|
|
5
|
+
|
|
6
|
+
describe('Directive: DraggableHeader', () => {
|
|
7
|
+
it('should create an instance', () => {
|
|
8
|
+
const directive = new DraggableHeaderDirective();
|
|
9
|
+
expect(directive).toBeTruthy();
|
|
10
|
+
});
|
|
11
|
+
});
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Directive,
|
|
3
|
+
ElementRef,
|
|
4
|
+
EventEmitter,
|
|
5
|
+
HostListener,
|
|
6
|
+
Input,
|
|
7
|
+
Output,
|
|
8
|
+
Renderer2,
|
|
9
|
+
} from '@angular/core';
|
|
10
|
+
|
|
11
|
+
export interface DragMoveEvent {
|
|
12
|
+
event: MouseEvent;
|
|
13
|
+
data: any;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@Directive({
|
|
17
|
+
selector: '[appDraggableHeader]',
|
|
18
|
+
})
|
|
19
|
+
export class DraggableHeaderDirective {
|
|
20
|
+
@Input() column: any;
|
|
21
|
+
@Input() headerName: string = '';
|
|
22
|
+
|
|
23
|
+
@Output() dragStart = new EventEmitter<any>();
|
|
24
|
+
@Output() dragMove = new EventEmitter<any>();
|
|
25
|
+
@Output() dragEnd = new EventEmitter<any>();
|
|
26
|
+
|
|
27
|
+
private isDragging = false;
|
|
28
|
+
private placeholderEl: HTMLElement | null = null;
|
|
29
|
+
private previewEl: HTMLElement | null = null;
|
|
30
|
+
|
|
31
|
+
constructor(private el: ElementRef, private renderer: Renderer2) {}
|
|
32
|
+
|
|
33
|
+
startX = 0;
|
|
34
|
+
startY = 0;
|
|
35
|
+
|
|
36
|
+
@HostListener('mousedown', ['$event'])
|
|
37
|
+
onMouseDown(event: MouseEvent) {
|
|
38
|
+
if (event.button !== 0) return;
|
|
39
|
+
let target = event.target as HTMLElement;
|
|
40
|
+
const classes = target.classList;
|
|
41
|
+
debugger
|
|
42
|
+
if ((event.target as HTMLElement).classList.contains('three-dots')) return;
|
|
43
|
+
this.startX = event.clientX;
|
|
44
|
+
this.startY = event.clientY;
|
|
45
|
+
this.isDragging = false;
|
|
46
|
+
|
|
47
|
+
document.addEventListener('mousemove', this.onMouseMove);
|
|
48
|
+
document.addEventListener('mouseup', this.onMouseUp);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
onMouseMove = (event: MouseEvent) => {
|
|
52
|
+
debugger
|
|
53
|
+
const moveX = Math.abs(event.clientX - this.startX - 10);
|
|
54
|
+
const moveY = Math.abs(event.clientY - this.startY - 10);
|
|
55
|
+
if (!this.isDragging && (moveX > 1 || moveY > 1)) {
|
|
56
|
+
this.startDragging(event);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (this.isDragging && this.previewEl) {
|
|
60
|
+
this.renderer.setStyle(this.previewEl, 'top', `${event.pageY + 10}px`);
|
|
61
|
+
this.renderer.setStyle(this.previewEl, 'left', `${event.pageX + 10}px`);
|
|
62
|
+
|
|
63
|
+
// Find the element under cursor (hovered element)
|
|
64
|
+
const hoveredElement = document.elementFromPoint(
|
|
65
|
+
event.clientX,
|
|
66
|
+
event.clientY
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
this.dragMove.emit({
|
|
70
|
+
event,
|
|
71
|
+
column: this.column,
|
|
72
|
+
hoveredElement,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
onMouseUp = (event: MouseEvent) => {
|
|
78
|
+
if (!this.isDragging) return;
|
|
79
|
+
this.isDragging = false;
|
|
80
|
+
|
|
81
|
+
this.dragEnd.emit({ column: this.column, event });
|
|
82
|
+
|
|
83
|
+
if (this.previewEl) {
|
|
84
|
+
this.renderer.removeChild(document.body, this.previewEl);
|
|
85
|
+
this.previewEl = null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (this.placeholderEl) {
|
|
89
|
+
const parent = this.placeholderEl.parentNode;
|
|
90
|
+
if (parent) {
|
|
91
|
+
parent.replaceChild(this.el.nativeElement, this.placeholderEl);
|
|
92
|
+
}
|
|
93
|
+
this.placeholderEl = null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Reset the cursor
|
|
97
|
+
this.resetCursor();
|
|
98
|
+
|
|
99
|
+
document.removeEventListener('mousemove', this.onMouseMove);
|
|
100
|
+
document.removeEventListener('mouseup', this.onMouseUp);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
private originalCursor: string | null = null;
|
|
104
|
+
|
|
105
|
+
private setCursor(cursorStyle: string) {
|
|
106
|
+
this.renderer.setStyle(document.body, 'cursor', cursorStyle);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private resetCursor() {
|
|
110
|
+
this.renderer.setStyle(document.body, 'cursor', this.originalCursor || '');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private startDragging(event: MouseEvent) {
|
|
114
|
+
this.isDragging = true;
|
|
115
|
+
|
|
116
|
+
// Store original cursor (optional fallback)
|
|
117
|
+
this.originalCursor = getComputedStyle(document.body).cursor;
|
|
118
|
+
|
|
119
|
+
// Set cursor to grabbing
|
|
120
|
+
this.setCursor('move');
|
|
121
|
+
|
|
122
|
+
// --- Clone the element as a placeholder ---
|
|
123
|
+
this.placeholderEl = this.el.nativeElement.cloneNode(true) as HTMLElement;
|
|
124
|
+
this.renderer.setStyle(this.placeholderEl, 'opacity', '0.5');
|
|
125
|
+
this.renderer.setStyle(this.placeholderEl, 'pointer-events', 'none');
|
|
126
|
+
this.renderer.addClass(this.placeholderEl, 'drag-placeholder');
|
|
127
|
+
|
|
128
|
+
const parent = this.el.nativeElement.parentNode;
|
|
129
|
+
parent.replaceChild(this.placeholderEl, this.el.nativeElement);
|
|
130
|
+
|
|
131
|
+
// --- Create floating preview ---
|
|
132
|
+
this.previewEl = this.renderer.createElement('div');
|
|
133
|
+
this.renderer.setStyle(this.previewEl, 'position', 'absolute');
|
|
134
|
+
this.renderer.setStyle(this.previewEl, 'top', `${event.pageY + 10}px`);
|
|
135
|
+
this.renderer.setStyle(this.previewEl, 'left', `${event.pageX + 10}px`);
|
|
136
|
+
this.renderer.setStyle(this.previewEl, 'pointer-events', 'none');
|
|
137
|
+
this.renderer.setStyle(this.previewEl, 'z-index', '1000');
|
|
138
|
+
this.renderer.setStyle(this.previewEl, 'max-width', '200px');
|
|
139
|
+
this.renderer.setStyle(this.previewEl, 'padding', '8px');
|
|
140
|
+
this.renderer.setStyle(this.previewEl, 'border', '1px solid #ccc');
|
|
141
|
+
this.renderer.setStyle(this.previewEl, 'background-color', '#fff');
|
|
142
|
+
this.renderer.setStyle(
|
|
143
|
+
this.previewEl,
|
|
144
|
+
'box-shadow',
|
|
145
|
+
'0 2px 6px rgba(0,0,0,0.2)'
|
|
146
|
+
);
|
|
147
|
+
this.renderer.setStyle(this.previewEl, 'border-radius', '4px');
|
|
148
|
+
this.renderer.setStyle(this.previewEl, 'display', 'flex');
|
|
149
|
+
this.renderer.setStyle(this.previewEl, 'align-items', 'center');
|
|
150
|
+
this.renderer.setStyle(this.previewEl, 'gap', '8px');
|
|
151
|
+
this.renderer.setStyle(this.previewEl, 'font-weight', '500');
|
|
152
|
+
this.renderer.setStyle(this.previewEl, 'white-space', 'nowrap');
|
|
153
|
+
|
|
154
|
+
const icon = this.renderer.createElement('span');
|
|
155
|
+
this.renderer.setStyle(icon, 'font-size', '16px');
|
|
156
|
+
this.renderer.setStyle(icon, 'user-select', 'none');
|
|
157
|
+
this.renderer.setProperty(icon, 'innerText', '≡');
|
|
158
|
+
|
|
159
|
+
const text = this.renderer.createElement('span');
|
|
160
|
+
this.renderer.setProperty(text, 'innerText', this.headerName || 'Dragging');
|
|
161
|
+
|
|
162
|
+
this.renderer.appendChild(this.previewEl, icon);
|
|
163
|
+
this.renderer.appendChild(this.previewEl, text);
|
|
164
|
+
this.renderer.appendChild(document.body, this.previewEl);
|
|
165
|
+
|
|
166
|
+
this.dragStart.emit({ column: this.column, event });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
public overrideCursor(style: string) {
|
|
170
|
+
this.setCursor(style);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/* tslint:disable:no-unused-variable */
|
|
2
|
+
|
|
3
|
+
import { TestBed, async } from '@angular/core/testing';
|
|
4
|
+
import { FilterPipe } from './filter.pipe';
|
|
5
|
+
|
|
6
|
+
describe('Pipe: Filtere', () => {
|
|
7
|
+
it('create an instance', () => {
|
|
8
|
+
let pipe = new FilterPipe();
|
|
9
|
+
expect(pipe).toBeTruthy();
|
|
10
|
+
});
|
|
11
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Pipe, PipeTransform } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
@Pipe({
|
|
4
|
+
name: 'filter',
|
|
5
|
+
})
|
|
6
|
+
export class FilterPipe implements PipeTransform {
|
|
7
|
+
transform(items: any[], searchText: string, key?: string): any[] {
|
|
8
|
+
if (!items || !searchText) return items;
|
|
9
|
+
|
|
10
|
+
searchText = searchText.toLowerCase();
|
|
11
|
+
|
|
12
|
+
return items.filter(item => {
|
|
13
|
+
const value = key ? item?.[key] : item;
|
|
14
|
+
return value?.toString().toLowerCase().includes(searchText);
|
|
15
|
+
});
|
|
16
|
+
}}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/* tslint:disable:no-unused-variable */
|
|
2
|
+
|
|
3
|
+
import { TestBed, async, inject } from '@angular/core/testing';
|
|
4
|
+
import { CellSelectionService } from './cell-selection.service';
|
|
5
|
+
|
|
6
|
+
describe('Service: CellSelection', () => {
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
TestBed.configureTestingModule({
|
|
9
|
+
providers: [CellSelectionService]
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should ...', inject([CellSelectionService], (service: CellSelectionService) => {
|
|
14
|
+
expect(service).toBeTruthy();
|
|
15
|
+
}));
|
|
16
|
+
});
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
// selection.service.ts
|
|
2
|
+
import { Injectable } from '@angular/core';
|
|
3
|
+
|
|
4
|
+
export interface SelectionRange {
|
|
5
|
+
startRow: number;
|
|
6
|
+
startGroupIndex: number;
|
|
7
|
+
startSubColIndex: number;
|
|
8
|
+
startField: string;
|
|
9
|
+
endRow: number;
|
|
10
|
+
endGroupIndex: number;
|
|
11
|
+
endSubColIndex: number;
|
|
12
|
+
endField: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ActiveCell {
|
|
16
|
+
row: number;
|
|
17
|
+
groupIndex: number;
|
|
18
|
+
subColIndex: number;
|
|
19
|
+
field: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@Injectable({
|
|
23
|
+
providedIn: 'root'
|
|
24
|
+
})
|
|
25
|
+
export class CellSelectionService {
|
|
26
|
+
private selectionRange: SelectionRange | null = null;
|
|
27
|
+
private activeCell: ActiveCell | null = null;
|
|
28
|
+
private isSelecting = false;
|
|
29
|
+
|
|
30
|
+
// Get the current selection range
|
|
31
|
+
getSelection(): SelectionRange | null {
|
|
32
|
+
return this.selectionRange;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Get the active cell
|
|
36
|
+
getActiveCell(): ActiveCell | null {
|
|
37
|
+
return this.activeCell;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Check if a cell is selected
|
|
41
|
+
isSelected(row: number, groupIndex: number, subColIndex: number, field: string): boolean {
|
|
42
|
+
if (!this.selectionRange) return false;
|
|
43
|
+
|
|
44
|
+
const {
|
|
45
|
+
startRow,
|
|
46
|
+
startGroupIndex,
|
|
47
|
+
startSubColIndex,
|
|
48
|
+
startField,
|
|
49
|
+
endRow,
|
|
50
|
+
endGroupIndex,
|
|
51
|
+
endSubColIndex,
|
|
52
|
+
endField
|
|
53
|
+
} = this.selectionRange;
|
|
54
|
+
|
|
55
|
+
// Normalize the selection range
|
|
56
|
+
const minRow = Math.min(startRow, endRow);
|
|
57
|
+
const maxRow = Math.max(startRow, endRow);
|
|
58
|
+
|
|
59
|
+
// For group and subcolumn, we need to compare both indices AND field names
|
|
60
|
+
const isSameGroup = startGroupIndex === endGroupIndex;
|
|
61
|
+
const isSameField = startField === endField;
|
|
62
|
+
|
|
63
|
+
if (isSameGroup && isSameField) {
|
|
64
|
+
// Selection is within the same group and same field
|
|
65
|
+
const minSubCol = Math.min(startSubColIndex, endSubColIndex);
|
|
66
|
+
const maxSubCol = Math.max(startSubColIndex, endSubColIndex);
|
|
67
|
+
|
|
68
|
+
return row >= minRow &&
|
|
69
|
+
row <= maxRow &&
|
|
70
|
+
groupIndex === startGroupIndex &&
|
|
71
|
+
field === startField &&
|
|
72
|
+
subColIndex >= minSubCol &&
|
|
73
|
+
subColIndex <= maxSubCol;
|
|
74
|
+
} else if (isSameGroup) {
|
|
75
|
+
// Selection spans multiple fields within the same group
|
|
76
|
+
// We need to check if this field is between start and end fields
|
|
77
|
+
const fieldsInGroup = this.getFieldsInGroup(groupIndex); // You'll need to implement this
|
|
78
|
+
const startFieldIndex = fieldsInGroup.indexOf(startField);
|
|
79
|
+
const endFieldIndex = fieldsInGroup.indexOf(endField);
|
|
80
|
+
const minFieldIndex = Math.min(startFieldIndex, endFieldIndex);
|
|
81
|
+
const maxFieldIndex = Math.max(startFieldIndex, endFieldIndex);
|
|
82
|
+
const currentFieldIndex = fieldsInGroup.indexOf(field);
|
|
83
|
+
|
|
84
|
+
return row >= minRow &&
|
|
85
|
+
row <= maxRow &&
|
|
86
|
+
groupIndex === startGroupIndex &&
|
|
87
|
+
currentFieldIndex >= minFieldIndex &&
|
|
88
|
+
currentFieldIndex <= maxFieldIndex;
|
|
89
|
+
} else {
|
|
90
|
+
// Selection spans multiple groups
|
|
91
|
+
const minGroup = Math.min(startGroupIndex, endGroupIndex);
|
|
92
|
+
const maxGroup = Math.max(startGroupIndex, endGroupIndex);
|
|
93
|
+
|
|
94
|
+
// For cells in the starting group
|
|
95
|
+
if (groupIndex === startGroupIndex) {
|
|
96
|
+
const fieldsInGroup = this.getFieldsInGroup(groupIndex);
|
|
97
|
+
const startFieldIndex = fieldsInGroup.indexOf(startField);
|
|
98
|
+
const currentFieldIndex = fieldsInGroup.indexOf(field);
|
|
99
|
+
|
|
100
|
+
return row >= minRow &&
|
|
101
|
+
row <= maxRow &&
|
|
102
|
+
currentFieldIndex >= (startGroupIndex === minGroup ? startFieldIndex : 0) &&
|
|
103
|
+
currentFieldIndex <= (startGroupIndex === maxGroup ? startFieldIndex : fieldsInGroup.length - 1);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// For cells in the ending group
|
|
107
|
+
if (groupIndex === endGroupIndex) {
|
|
108
|
+
const fieldsInGroup = this.getFieldsInGroup(groupIndex);
|
|
109
|
+
const endFieldIndex = fieldsInGroup.indexOf(endField);
|
|
110
|
+
const currentFieldIndex = fieldsInGroup.indexOf(field);
|
|
111
|
+
|
|
112
|
+
return row >= minRow &&
|
|
113
|
+
row <= maxRow &&
|
|
114
|
+
currentFieldIndex >= (endGroupIndex === minGroup ? endFieldIndex : 0) &&
|
|
115
|
+
currentFieldIndex <= (endGroupIndex === maxGroup ? endFieldIndex : fieldsInGroup.length - 1);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// For cells in between groups
|
|
119
|
+
return row >= minRow &&
|
|
120
|
+
row <= maxRow &&
|
|
121
|
+
groupIndex > minGroup &&
|
|
122
|
+
groupIndex < maxGroup;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Check if a cell is the active cell
|
|
127
|
+
isActiveCell(row: number, groupIndex: number, subColIndex: number, field: string): boolean {
|
|
128
|
+
return this.activeCell?.row === row &&
|
|
129
|
+
this.activeCell?.groupIndex === groupIndex &&
|
|
130
|
+
this.activeCell?.subColIndex === subColIndex &&
|
|
131
|
+
this.activeCell?.field === field;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Start selection
|
|
135
|
+
startSelection(row: number, groupIndex: number, subColIndex: number, field: string): void {
|
|
136
|
+
this.isSelecting = true;
|
|
137
|
+
this.selectionRange = {
|
|
138
|
+
startRow: row,
|
|
139
|
+
startGroupIndex: groupIndex,
|
|
140
|
+
startSubColIndex: subColIndex,
|
|
141
|
+
startField: field,
|
|
142
|
+
endRow: row,
|
|
143
|
+
endGroupIndex: groupIndex,
|
|
144
|
+
endSubColIndex: subColIndex,
|
|
145
|
+
endField: field
|
|
146
|
+
};
|
|
147
|
+
this.activeCell = { row, groupIndex, subColIndex, field };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Extend selection
|
|
151
|
+
extendSelection(row: number, groupIndex: number, subColIndex: number, field: string): void {
|
|
152
|
+
if (this.isSelecting && this.selectionRange) {
|
|
153
|
+
this.selectionRange.endRow = row;
|
|
154
|
+
this.selectionRange.endGroupIndex = groupIndex;
|
|
155
|
+
this.selectionRange.endSubColIndex = subColIndex;
|
|
156
|
+
this.selectionRange.endField = field;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// End selection
|
|
161
|
+
endSelection(): void {
|
|
162
|
+
this.isSelecting = false;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Clear selection
|
|
166
|
+
clearSelection(): void {
|
|
167
|
+
this.selectionRange = null;
|
|
168
|
+
this.activeCell = null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Move selection with keyboard
|
|
172
|
+
moveSelection(direction: 'up' | 'down' | 'left' | 'right', columnStructure: any[]): void {
|
|
173
|
+
if (!this.activeCell) return;
|
|
174
|
+
|
|
175
|
+
let { row, groupIndex, subColIndex, field } = this.activeCell;
|
|
176
|
+
|
|
177
|
+
switch (direction) {
|
|
178
|
+
case 'up':
|
|
179
|
+
row--;
|
|
180
|
+
break;
|
|
181
|
+
case 'down':
|
|
182
|
+
row++;
|
|
183
|
+
break;
|
|
184
|
+
case 'left':
|
|
185
|
+
if (subColIndex > 0) {
|
|
186
|
+
// Move left within the same group
|
|
187
|
+
subColIndex--;
|
|
188
|
+
field = this.getFieldFromIndices(groupIndex, subColIndex, columnStructure);
|
|
189
|
+
} else if (groupIndex > 0) {
|
|
190
|
+
// Move to previous group
|
|
191
|
+
groupIndex--;
|
|
192
|
+
subColIndex = columnStructure[groupIndex].children.length - 1;
|
|
193
|
+
field = this.getFieldFromIndices(groupIndex, subColIndex, columnStructure);
|
|
194
|
+
}
|
|
195
|
+
break;
|
|
196
|
+
case 'right':
|
|
197
|
+
if (subColIndex < columnStructure[groupIndex].children.length - 1) {
|
|
198
|
+
// Move right within the same group
|
|
199
|
+
subColIndex++;
|
|
200
|
+
field = this.getFieldFromIndices(groupIndex, subColIndex, columnStructure);
|
|
201
|
+
} else if (groupIndex < columnStructure.length - 1) {
|
|
202
|
+
// Move to next group
|
|
203
|
+
groupIndex++;
|
|
204
|
+
subColIndex = 0;
|
|
205
|
+
field = this.getFieldFromIndices(groupIndex, subColIndex, columnStructure);
|
|
206
|
+
}
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Boundary checks for rows (you'll need to implement based on your data)
|
|
211
|
+
// if (row >= 0 && row < maxRows) {
|
|
212
|
+
this.startSelection(row, groupIndex, subColIndex, field);
|
|
213
|
+
this.endSelection();
|
|
214
|
+
// }
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Helper method to get field from indices
|
|
218
|
+
private getFieldFromIndices(groupIndex: number, subColIndex: number, columnStructure: any[]): string {
|
|
219
|
+
if (groupIndex >= 0 &&
|
|
220
|
+
groupIndex < columnStructure.length &&
|
|
221
|
+
subColIndex >= 0 &&
|
|
222
|
+
subColIndex < columnStructure[groupIndex].children.length) {
|
|
223
|
+
return columnStructure[groupIndex].children[subColIndex].field;
|
|
224
|
+
}
|
|
225
|
+
return '';
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Helper method to get all fields in a group
|
|
229
|
+
private getFieldsInGroup(groupIndex: number): string[] {
|
|
230
|
+
// You'll need to implement this based on your data structure
|
|
231
|
+
// This should return an array of field names for the specified group
|
|
232
|
+
return [];
|
|
233
|
+
}
|
|
234
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/* tslint:disable:no-unused-variable */
|
|
2
|
+
|
|
3
|
+
import { TestBed, async, inject } from '@angular/core/testing';
|
|
4
|
+
import { CommonService } from './common.service';
|
|
5
|
+
|
|
6
|
+
describe('Service: Common', () => {
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
TestBed.configureTestingModule({
|
|
9
|
+
providers: [CommonService]
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should ...', inject([CommonService], (service: CommonService) => {
|
|
14
|
+
expect(service).toBeTruthy();
|
|
15
|
+
}));
|
|
16
|
+
});
|