@alaarab/ogrid-angular 2.0.3 → 2.0.5
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/dist/esm/components/grid-context-menu.component.js +4 -6
- package/dist/esm/components/marching-ants-overlay.component.js +2 -0
- package/dist/esm/services/datagrid-state.service.js +22 -8
- package/dist/esm/services/ogrid.service.js +58 -0
- package/dist/types/components/marching-ants-overlay.component.d.ts +1 -0
- package/dist/types/services/datagrid-state.service.d.ts +4 -1
- package/dist/types/services/ogrid.service.d.ts +26 -0
- package/dist/types/types/dataGridTypes.d.ts +3 -0
- package/package.json +2 -2
|
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { Component, input, output,
|
|
7
|
+
import { Component, input, output, viewChild, DestroyRef, inject } from '@angular/core';
|
|
8
8
|
import { CommonModule } from '@angular/common';
|
|
9
9
|
import { GRID_CONTEXT_MENU_ITEMS, formatShortcut } from '@alaarab/ogrid-core';
|
|
10
10
|
let GridContextMenuComponent = class GridContextMenuComponent {
|
|
@@ -35,11 +35,9 @@ let GridContextMenuComponent = class GridContextMenuComponent {
|
|
|
35
35
|
if (e.key === 'Escape')
|
|
36
36
|
this.close.emit();
|
|
37
37
|
};
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
document.addEventListener('keydown', this.keyDownHandler, true);
|
|
42
|
-
});
|
|
38
|
+
// Register listeners once on init (no signal dependencies needed)
|
|
39
|
+
document.addEventListener('mousedown', this.clickOutsideHandler, true);
|
|
40
|
+
document.addEventListener('keydown', this.keyDownHandler, true);
|
|
43
41
|
this.destroyRef.onDestroy(() => {
|
|
44
42
|
document.removeEventListener('mousedown', this.clickOutsideHandler, true);
|
|
45
43
|
document.removeEventListener('keydown', this.keyDownHandler, true);
|
|
@@ -41,6 +41,7 @@ let MarchingAntsOverlayComponent = class MarchingAntsOverlayComponent {
|
|
|
41
41
|
this.copyRange = input(null);
|
|
42
42
|
this.cutRange = input(null);
|
|
43
43
|
this.colOffset = input(0);
|
|
44
|
+
this.columnSizingVersion = input(0);
|
|
44
45
|
this.selRect = signal(null);
|
|
45
46
|
this.clipRect = signal(null);
|
|
46
47
|
this.rafId = 0;
|
|
@@ -51,6 +52,7 @@ let MarchingAntsOverlayComponent = class MarchingAntsOverlayComponent {
|
|
|
51
52
|
const selRange = this.selectionRange();
|
|
52
53
|
const clipRange = this.copyRange() ?? this.cutRange();
|
|
53
54
|
const colOff = this.colOffset();
|
|
55
|
+
const _version = this.columnSizingVersion(); // Track column resize changes
|
|
54
56
|
if (this.resizeObserver) {
|
|
55
57
|
this.resizeObserver.disconnect();
|
|
56
58
|
this.resizeObserver = null;
|
|
@@ -108,9 +108,14 @@ let DataGridStateService = class DataGridStateService {
|
|
|
108
108
|
const order = p.columnOrder;
|
|
109
109
|
if (!order?.length)
|
|
110
110
|
return filtered;
|
|
111
|
+
// Build index map for O(1) lookup instead of repeated O(n) indexOf
|
|
112
|
+
const orderMap = new Map();
|
|
113
|
+
for (let i = 0; i < order.length; i++) {
|
|
114
|
+
orderMap.set(order[i], i);
|
|
115
|
+
}
|
|
111
116
|
return [...filtered].sort((a, b) => {
|
|
112
|
-
const ia =
|
|
113
|
-
const ib =
|
|
117
|
+
const ia = orderMap.get(a.columnId) ?? -1;
|
|
118
|
+
const ib = orderMap.get(b.columnId) ?? -1;
|
|
114
119
|
if (ia === -1 && ib === -1)
|
|
115
120
|
return 0;
|
|
116
121
|
if (ia === -1)
|
|
@@ -122,8 +127,10 @@ let DataGridStateService = class DataGridStateService {
|
|
|
122
127
|
});
|
|
123
128
|
this.visibleColumnCount = computed(() => this.visibleCols().length);
|
|
124
129
|
this.hasCheckboxCol = computed(() => (this.props()?.rowSelection ?? 'none') === 'multiple');
|
|
125
|
-
this.
|
|
126
|
-
this.
|
|
130
|
+
this.hasRowNumbersCol = computed(() => !!this.props()?.showRowNumbers);
|
|
131
|
+
this.specialColsCount = computed(() => (this.hasCheckboxCol() ? 1 : 0) + (this.hasRowNumbersCol() ? 1 : 0));
|
|
132
|
+
this.totalColCount = computed(() => this.visibleColumnCount() + this.specialColsCount());
|
|
133
|
+
this.colOffset = computed(() => this.specialColsCount());
|
|
127
134
|
this.rowIndexByRowId = computed(() => {
|
|
128
135
|
const p = this.props();
|
|
129
136
|
if (!p)
|
|
@@ -239,14 +246,20 @@ let DataGridStateService = class DataGridStateService {
|
|
|
239
246
|
this.resizeObserver.observe(el);
|
|
240
247
|
measure();
|
|
241
248
|
});
|
|
242
|
-
// Cleanup on destroy
|
|
249
|
+
// Cleanup on destroy — null out refs to prevent accidental reuse after teardown
|
|
243
250
|
this.destroyRef.onDestroy(() => {
|
|
244
|
-
if (this.rafId)
|
|
251
|
+
if (this.rafId) {
|
|
245
252
|
cancelAnimationFrame(this.rafId);
|
|
246
|
-
|
|
253
|
+
this.rafId = 0;
|
|
254
|
+
}
|
|
255
|
+
if (this.autoScrollInterval) {
|
|
247
256
|
clearInterval(this.autoScrollInterval);
|
|
248
|
-
|
|
257
|
+
this.autoScrollInterval = null;
|
|
258
|
+
}
|
|
259
|
+
if (this.resizeObserver) {
|
|
249
260
|
this.resizeObserver.disconnect();
|
|
261
|
+
this.resizeObserver = null;
|
|
262
|
+
}
|
|
250
263
|
});
|
|
251
264
|
// Clean up column sizing overrides for removed columns
|
|
252
265
|
effect(() => {
|
|
@@ -947,6 +960,7 @@ let DataGridStateService = class DataGridStateService {
|
|
|
947
960
|
totalColCount: this.totalColCount(),
|
|
948
961
|
colOffset: this.colOffset(),
|
|
949
962
|
hasCheckboxCol: this.hasCheckboxCol(),
|
|
963
|
+
hasRowNumbersCol: this.hasRowNumbersCol(),
|
|
950
964
|
rowIndexByRowId: this.rowIndexByRowId(),
|
|
951
965
|
containerWidth: this.containerWidthSig(),
|
|
952
966
|
minTableWidth: this.minTableWidth(),
|
|
@@ -529,6 +529,64 @@ let OGridService = class OGridService {
|
|
|
529
529
|
this.ariaLabelledBy.set(props['aria-labelledby']);
|
|
530
530
|
}
|
|
531
531
|
// --- API ---
|
|
532
|
+
// --- Column Pinning Methods ---
|
|
533
|
+
/**
|
|
534
|
+
* Pin a column to the left or right edge.
|
|
535
|
+
*/
|
|
536
|
+
pinColumn(columnId, side) {
|
|
537
|
+
this.pinnedOverrides.update((prev) => ({ ...prev, [columnId]: side }));
|
|
538
|
+
this.onColumnPinned()?.(columnId, side);
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Unpin a column (remove sticky positioning).
|
|
542
|
+
*/
|
|
543
|
+
unpinColumn(columnId) {
|
|
544
|
+
this.pinnedOverrides.update((prev) => {
|
|
545
|
+
const next = { ...prev };
|
|
546
|
+
delete next[columnId];
|
|
547
|
+
return next;
|
|
548
|
+
});
|
|
549
|
+
this.onColumnPinned()?.(columnId, null);
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Check if a column is pinned and which side.
|
|
553
|
+
*/
|
|
554
|
+
isPinned(columnId) {
|
|
555
|
+
return this.pinnedOverrides()[columnId];
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Compute sticky left offsets for left-pinned columns.
|
|
559
|
+
* Returns a map of columnId -> left offset in pixels.
|
|
560
|
+
*/
|
|
561
|
+
computeLeftOffsets(visibleCols, columnWidths, defaultWidth, hasCheckboxColumn, checkboxColumnWidth) {
|
|
562
|
+
const offsets = {};
|
|
563
|
+
const pinned = this.pinnedOverrides();
|
|
564
|
+
let left = hasCheckboxColumn ? checkboxColumnWidth : 0;
|
|
565
|
+
for (const col of visibleCols) {
|
|
566
|
+
if (pinned[col.columnId] === 'left') {
|
|
567
|
+
offsets[col.columnId] = left;
|
|
568
|
+
left += columnWidths[col.columnId] ?? defaultWidth;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
return offsets;
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Compute sticky right offsets for right-pinned columns.
|
|
575
|
+
* Returns a map of columnId -> right offset in pixels.
|
|
576
|
+
*/
|
|
577
|
+
computeRightOffsets(visibleCols, columnWidths, defaultWidth) {
|
|
578
|
+
const offsets = {};
|
|
579
|
+
const pinned = this.pinnedOverrides();
|
|
580
|
+
let right = 0;
|
|
581
|
+
for (let i = visibleCols.length - 1; i >= 0; i--) {
|
|
582
|
+
const col = visibleCols[i];
|
|
583
|
+
if (pinned[col.columnId] === 'right') {
|
|
584
|
+
offsets[col.columnId] = right;
|
|
585
|
+
right += columnWidths[col.columnId] ?? defaultWidth;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
return offsets;
|
|
589
|
+
}
|
|
532
590
|
getApi() {
|
|
533
591
|
return {
|
|
534
592
|
setRowData: (d) => {
|
|
@@ -12,6 +12,7 @@ export declare class MarchingAntsOverlayComponent {
|
|
|
12
12
|
readonly copyRange: import("@angular/core").InputSignal<ISelectionRange | null>;
|
|
13
13
|
readonly cutRange: import("@angular/core").InputSignal<ISelectionRange | null>;
|
|
14
14
|
readonly colOffset: import("@angular/core").InputSignal<number>;
|
|
15
|
+
readonly columnSizingVersion: import("@angular/core").InputSignal<number>;
|
|
15
16
|
readonly selRect: import("@angular/core").WritableSignal<OverlayRect | null>;
|
|
16
17
|
readonly clipRect: import("@angular/core").WritableSignal<OverlayRect | null>;
|
|
17
18
|
private rafId;
|
|
@@ -9,6 +9,7 @@ export interface DataGridLayoutState<T> {
|
|
|
9
9
|
totalColCount: number;
|
|
10
10
|
colOffset: number;
|
|
11
11
|
hasCheckboxCol: boolean;
|
|
12
|
+
hasRowNumbersCol: boolean;
|
|
12
13
|
rowIndexByRowId: Map<RowId, number>;
|
|
13
14
|
containerWidth: number;
|
|
14
15
|
minTableWidth: number;
|
|
@@ -168,8 +169,10 @@ export declare class DataGridStateService<T> {
|
|
|
168
169
|
readonly visibleCols: import("@angular/core").Signal<IColumnDef<T>[]>;
|
|
169
170
|
readonly visibleColumnCount: import("@angular/core").Signal<number>;
|
|
170
171
|
readonly hasCheckboxCol: import("@angular/core").Signal<boolean>;
|
|
172
|
+
readonly hasRowNumbersCol: import("@angular/core").Signal<boolean>;
|
|
173
|
+
readonly specialColsCount: import("@angular/core").Signal<number>;
|
|
171
174
|
readonly totalColCount: import("@angular/core").Signal<number>;
|
|
172
|
-
readonly colOffset: import("@angular/core").Signal<
|
|
175
|
+
readonly colOffset: import("@angular/core").Signal<number>;
|
|
173
176
|
readonly rowIndexByRowId: import("@angular/core").Signal<Map<RowId, number>>;
|
|
174
177
|
readonly selectedRowIds: import("@angular/core").Signal<Set<RowId>>;
|
|
175
178
|
readonly allSelected: import("@angular/core").Signal<boolean>;
|
|
@@ -184,5 +184,31 @@ export declare class OGridService<T> {
|
|
|
184
184
|
handleColumnResized(columnId: string, width: number): void;
|
|
185
185
|
handleColumnPinned(columnId: string, pinned: 'left' | 'right' | null): void;
|
|
186
186
|
configure(props: IOGridProps<T>): void;
|
|
187
|
+
/**
|
|
188
|
+
* Pin a column to the left or right edge.
|
|
189
|
+
*/
|
|
190
|
+
pinColumn(columnId: string, side: 'left' | 'right'): void;
|
|
191
|
+
/**
|
|
192
|
+
* Unpin a column (remove sticky positioning).
|
|
193
|
+
*/
|
|
194
|
+
unpinColumn(columnId: string): void;
|
|
195
|
+
/**
|
|
196
|
+
* Check if a column is pinned and which side.
|
|
197
|
+
*/
|
|
198
|
+
isPinned(columnId: string): 'left' | 'right' | undefined;
|
|
199
|
+
/**
|
|
200
|
+
* Compute sticky left offsets for left-pinned columns.
|
|
201
|
+
* Returns a map of columnId -> left offset in pixels.
|
|
202
|
+
*/
|
|
203
|
+
computeLeftOffsets(visibleCols: {
|
|
204
|
+
columnId: string;
|
|
205
|
+
}[], columnWidths: Record<string, number>, defaultWidth: number, hasCheckboxColumn: boolean, checkboxColumnWidth: number): Record<string, number>;
|
|
206
|
+
/**
|
|
207
|
+
* Compute sticky right offsets for right-pinned columns.
|
|
208
|
+
* Returns a map of columnId -> right offset in pixels.
|
|
209
|
+
*/
|
|
210
|
+
computeRightOffsets(visibleCols: {
|
|
211
|
+
columnId: string;
|
|
212
|
+
}[], columnWidths: Record<string, number>, defaultWidth: number): Record<string, number>;
|
|
187
213
|
getApi(): IOGridApi<T>;
|
|
188
214
|
}
|
|
@@ -107,6 +107,9 @@ export interface IOGridDataGridProps<T> {
|
|
|
107
107
|
rowSelection?: RowSelectionMode;
|
|
108
108
|
selectedRows?: Set<RowId>;
|
|
109
109
|
onSelectionChange?: (event: IRowSelectionChangeEvent<T>) => void;
|
|
110
|
+
showRowNumbers?: boolean;
|
|
111
|
+
currentPage?: number;
|
|
112
|
+
pageSize?: number;
|
|
110
113
|
statusBar?: IStatusBarProps;
|
|
111
114
|
filters: IFilters;
|
|
112
115
|
onFilterChange: (key: string, value: FilterValue | undefined) => void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alaarab/ogrid-angular",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.5",
|
|
4
4
|
"description": "OGrid Angular – Angular services, signals, and headless components for OGrid data grids.",
|
|
5
5
|
"main": "dist/esm/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"files": ["dist", "README.md", "LICENSE"],
|
|
23
23
|
"engines": { "node": ">=18" },
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@alaarab/ogrid-core": "2.0.
|
|
25
|
+
"@alaarab/ogrid-core": "2.0.5"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
28
|
"@angular/core": "^21.0.0",
|