@alaarab/ogrid-angular 2.0.6 → 2.0.8
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/base-datagrid-table.component.js +74 -7
- package/dist/esm/components/marching-ants-overlay.component.js +1 -1
- package/dist/esm/index.js +2 -0
- package/dist/esm/services/datagrid-state.service.js +80 -1
- package/dist/esm/services/ogrid.service.js +9 -1
- package/dist/esm/services/virtual-scroll.service.js +1 -1
- package/dist/esm/utils/dataGridViewModel.js +4 -161
- package/dist/esm/utils/debounce.js +2 -39
- package/dist/esm/utils/index.js +1 -1
- package/dist/types/components/base-datagrid-table.component.d.ts +18 -2
- package/dist/types/index.d.ts +2 -2
- package/dist/types/services/datagrid-state.service.d.ts +37 -0
- package/dist/types/services/ogrid.service.d.ts +3 -1
- package/dist/types/services/virtual-scroll.service.d.ts +1 -9
- package/dist/types/types/dataGridTypes.d.ts +6 -2
- package/dist/types/types/index.d.ts +1 -1
- package/dist/types/utils/dataGridViewModel.d.ts +4 -105
- package/dist/types/utils/debounce.d.ts +1 -23
- package/dist/types/utils/index.d.ts +1 -1
- package/package.json +21 -6
|
@@ -3,7 +3,7 @@ import { DataGridStateService } from '../services/datagrid-state.service';
|
|
|
3
3
|
import { ColumnReorderService } from '../services/column-reorder.service';
|
|
4
4
|
import { VirtualScrollService } from '../services/virtual-scroll.service';
|
|
5
5
|
import { buildHeaderRows, DEFAULT_MIN_COLUMN_WIDTH, } from '@alaarab/ogrid-core';
|
|
6
|
-
import { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, } from '../utils';
|
|
6
|
+
import { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, buildPopoverEditorProps, } from '../utils';
|
|
7
7
|
/**
|
|
8
8
|
* Abstract base class containing all shared TypeScript logic for DataGridTable components.
|
|
9
9
|
* Framework-specific UI packages extend this with their templates and style overrides.
|
|
@@ -23,6 +23,7 @@ export class BaseDataGridTableComponent {
|
|
|
23
23
|
// --- Delegated state ---
|
|
24
24
|
this.state = computed(() => this.stateService.getState());
|
|
25
25
|
this.tableContainerEl = computed(() => this.getTableContainerRef()?.nativeElement ?? null);
|
|
26
|
+
this.allItems = computed(() => this.getProps()?.items ?? []);
|
|
26
27
|
this.items = computed(() => this.getProps()?.items ?? []);
|
|
27
28
|
this.getRowId = computed(() => this.getProps()?.getRowId ?? ((item) => item['id']));
|
|
28
29
|
this.isLoading = computed(() => this.getProps()?.isLoading ?? false);
|
|
@@ -63,6 +64,36 @@ export class BaseDataGridTableComponent {
|
|
|
63
64
|
this.showEmptyInGrid = computed(() => this.state().viewModels.showEmptyInGrid);
|
|
64
65
|
this.headerFilterInput = computed(() => this.state().viewModels.headerFilterInput);
|
|
65
66
|
this.cellDescriptorInput = computed(() => this.state().viewModels.cellDescriptorInput);
|
|
67
|
+
// Pinning state
|
|
68
|
+
this.pinnedColumnsMap = computed(() => this.state().pinning.pinnedColumns);
|
|
69
|
+
// Virtual scrolling
|
|
70
|
+
this.vsEnabled = computed(() => this.virtualScrollService.isActive());
|
|
71
|
+
this.vsVisibleRange = computed(() => this.virtualScrollService.visibleRange());
|
|
72
|
+
this.vsTopSpacerHeight = computed(() => {
|
|
73
|
+
if (!this.vsEnabled())
|
|
74
|
+
return 0;
|
|
75
|
+
return this.vsVisibleRange().offsetTop;
|
|
76
|
+
});
|
|
77
|
+
this.vsBottomSpacerHeight = computed(() => {
|
|
78
|
+
if (!this.vsEnabled())
|
|
79
|
+
return 0;
|
|
80
|
+
return this.vsVisibleRange().offsetBottom;
|
|
81
|
+
});
|
|
82
|
+
this.vsVisibleItems = computed(() => {
|
|
83
|
+
const items = this.allItems();
|
|
84
|
+
if (!this.vsEnabled())
|
|
85
|
+
return items;
|
|
86
|
+
const range = this.vsVisibleRange();
|
|
87
|
+
return items.slice(range.startIndex, Math.min(range.endIndex + 1, items.length));
|
|
88
|
+
});
|
|
89
|
+
this.vsStartIndex = computed(() => {
|
|
90
|
+
if (!this.vsEnabled())
|
|
91
|
+
return 0;
|
|
92
|
+
return this.vsVisibleRange().startIndex;
|
|
93
|
+
});
|
|
94
|
+
// Popover editing
|
|
95
|
+
this.popoverAnchorEl = computed(() => this.state().editing.popoverAnchorEl);
|
|
96
|
+
this.pendingEditorValueForPopover = computed(() => this.state().editing.pendingEditorValue);
|
|
66
97
|
this.allowOverflowX = computed(() => {
|
|
67
98
|
const p = this.getProps();
|
|
68
99
|
if (p?.suppressHorizontalScroll)
|
|
@@ -142,6 +173,21 @@ export class BaseDataGridTableComponent {
|
|
|
142
173
|
const p = this.getProps();
|
|
143
174
|
if (p) {
|
|
144
175
|
this.virtualScrollService.totalRows.set(p.items.length);
|
|
176
|
+
if (p.virtualScroll) {
|
|
177
|
+
this.virtualScrollService.updateConfig({
|
|
178
|
+
enabled: p.virtualScroll.enabled,
|
|
179
|
+
rowHeight: p.virtualScroll.rowHeight,
|
|
180
|
+
overscan: p.virtualScroll.overscan,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
// Wire wrapper element to virtual scroll for scroll events + container height
|
|
186
|
+
effect(() => {
|
|
187
|
+
const el = this.getWrapperRef()?.nativeElement;
|
|
188
|
+
if (el) {
|
|
189
|
+
this.virtualScrollService.setContainer(el);
|
|
190
|
+
this.virtualScrollService.containerHeight.set(el.clientHeight);
|
|
145
191
|
}
|
|
146
192
|
});
|
|
147
193
|
}
|
|
@@ -171,6 +217,13 @@ export class BaseDataGridTableComponent {
|
|
|
171
217
|
resolveCellStyleFn(col, item) {
|
|
172
218
|
return resolveCellStyle(col, item);
|
|
173
219
|
}
|
|
220
|
+
buildPopoverEditorProps(item, col, descriptor) {
|
|
221
|
+
return buildPopoverEditorProps(item, col, descriptor, this.pendingEditorValue(), {
|
|
222
|
+
setPendingEditorValue: (value) => this.setPendingEditorValue(value),
|
|
223
|
+
commitCellEdit: (item, columnId, oldValue, newValue, rowIndex, globalColIndex) => this.commitEdit(item, columnId, oldValue, newValue, rowIndex, globalColIndex),
|
|
224
|
+
cancelPopoverEdit: () => this.cancelPopoverEdit(),
|
|
225
|
+
});
|
|
226
|
+
}
|
|
174
227
|
getSelectValues(col) {
|
|
175
228
|
const params = col.cellEditorParams;
|
|
176
229
|
if (params && typeof params === 'object' && 'values' in params) {
|
|
@@ -186,6 +239,23 @@ export class BaseDataGridTableComponent {
|
|
|
186
239
|
return '';
|
|
187
240
|
return d.toISOString().split('T')[0];
|
|
188
241
|
}
|
|
242
|
+
// --- Virtual scroll event handler ---
|
|
243
|
+
onWrapperScroll(event) {
|
|
244
|
+
this.virtualScrollService.onScroll(event);
|
|
245
|
+
}
|
|
246
|
+
// --- Popover editor helpers ---
|
|
247
|
+
setPopoverAnchorEl(el) {
|
|
248
|
+
this.state().editing.setPopoverAnchorEl(el);
|
|
249
|
+
}
|
|
250
|
+
setPendingEditorValue(value) {
|
|
251
|
+
this.state().editing.setPendingEditorValue(value);
|
|
252
|
+
}
|
|
253
|
+
cancelPopoverEdit() {
|
|
254
|
+
this.state().editing.cancelPopoverEdit();
|
|
255
|
+
}
|
|
256
|
+
commitPopoverEdit(item, columnId, oldValue, newValue, rowIndex, globalColIndex) {
|
|
257
|
+
this.state().editing.commitCellEdit(item, columnId, oldValue, newValue, rowIndex, globalColIndex);
|
|
258
|
+
}
|
|
189
259
|
// --- Event handlers ---
|
|
190
260
|
onWrapperMouseDown(event) {
|
|
191
261
|
this.lastMouseShift = event.shiftKey;
|
|
@@ -287,16 +357,13 @@ export class BaseDataGridTableComponent {
|
|
|
287
357
|
}
|
|
288
358
|
// --- Column pinning methods ---
|
|
289
359
|
onPinColumn(columnId, side) {
|
|
290
|
-
|
|
291
|
-
props?.onColumnPinned?.(columnId, side);
|
|
360
|
+
this.state().pinning.pinColumn(columnId, side);
|
|
292
361
|
}
|
|
293
362
|
onUnpinColumn(columnId) {
|
|
294
|
-
|
|
295
|
-
props?.onColumnPinned?.(columnId, null);
|
|
363
|
+
this.state().pinning.unpinColumn(columnId);
|
|
296
364
|
}
|
|
297
365
|
isPinned(columnId) {
|
|
298
|
-
|
|
299
|
-
return props?.pinnedColumns?.[columnId];
|
|
366
|
+
return this.state().pinning.isPinned(columnId);
|
|
300
367
|
}
|
|
301
368
|
getPinState(columnId) {
|
|
302
369
|
const pinned = this.isPinned(columnId);
|
|
@@ -52,7 +52,7 @@ let MarchingAntsOverlayComponent = class MarchingAntsOverlayComponent {
|
|
|
52
52
|
const selRange = this.selectionRange();
|
|
53
53
|
const clipRange = this.copyRange() ?? this.cutRange();
|
|
54
54
|
const colOff = this.colOffset();
|
|
55
|
-
|
|
55
|
+
void this.columnSizingVersion(); // Track column resize changes
|
|
56
56
|
if (this.resizeObserver) {
|
|
57
57
|
this.resizeObserver.disconnect();
|
|
58
58
|
this.resizeObserver = null;
|
package/dist/esm/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// Re-export core types + utils
|
|
2
2
|
export * from '@alaarab/ogrid-core';
|
|
3
|
+
// Explicitly re-export constants for test resolution
|
|
4
|
+
export { CHECKBOX_COLUMN_WIDTH, ROW_NUMBER_COLUMN_WIDTH, DEFAULT_MIN_COLUMN_WIDTH, CELL_PADDING, GRID_BORDER_RADIUS, PEOPLE_SEARCH_DEBOUNCE_MS, DEFAULT_DEBOUNCE_MS, SIDEBAR_TRANSITION_MS, Z_INDEX, } from '@alaarab/ogrid-core';
|
|
3
5
|
export { toUserLike, isInSelectionRange, normalizeSelectionRange } from './types';
|
|
4
6
|
// Services
|
|
5
7
|
export { OGridService } from './services/ogrid.service';
|
|
@@ -53,6 +53,10 @@ let DataGridStateService = class DataGridStateService {
|
|
|
53
53
|
this.autoScrollInterval = null;
|
|
54
54
|
// ResizeObserver
|
|
55
55
|
this.resizeObserver = null;
|
|
56
|
+
// Header menu state (for column pinning UI)
|
|
57
|
+
this.headerMenuIsOpenSig = signal(false);
|
|
58
|
+
this.headerMenuOpenForColumnSig = signal(null);
|
|
59
|
+
this.headerMenuAnchorElementSig = signal(null);
|
|
56
60
|
// --- Derived computed ---
|
|
57
61
|
this.propsResolved = computed(() => this.props());
|
|
58
62
|
this.cellSelection = computed(() => {
|
|
@@ -949,6 +953,59 @@ let DataGridStateService = class DataGridStateService {
|
|
|
949
953
|
this.fillDragStart = { startRow: range.startRow, startCol: range.startCol };
|
|
950
954
|
this.setupFillHandleDrag();
|
|
951
955
|
}
|
|
956
|
+
// --- Column pinning ---
|
|
957
|
+
pinColumn(columnId, side) {
|
|
958
|
+
const props = this.props();
|
|
959
|
+
props?.onColumnPinned?.(columnId, side);
|
|
960
|
+
}
|
|
961
|
+
unpinColumn(columnId) {
|
|
962
|
+
const props = this.props();
|
|
963
|
+
props?.onColumnPinned?.(columnId, null);
|
|
964
|
+
}
|
|
965
|
+
isPinned(columnId) {
|
|
966
|
+
const props = this.props();
|
|
967
|
+
return props?.pinnedColumns?.[columnId];
|
|
968
|
+
}
|
|
969
|
+
getPinState(columnId) {
|
|
970
|
+
const pinned = this.isPinned(columnId);
|
|
971
|
+
return {
|
|
972
|
+
canPinLeft: pinned !== 'left',
|
|
973
|
+
canPinRight: pinned !== 'right',
|
|
974
|
+
canUnpin: !!pinned,
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
// --- Header menu ---
|
|
978
|
+
openHeaderMenu(columnId, anchorEl) {
|
|
979
|
+
this.headerMenuOpenForColumnSig.set(columnId);
|
|
980
|
+
this.headerMenuAnchorElementSig.set(anchorEl);
|
|
981
|
+
this.headerMenuIsOpenSig.set(true);
|
|
982
|
+
}
|
|
983
|
+
closeHeaderMenu() {
|
|
984
|
+
this.headerMenuIsOpenSig.set(false);
|
|
985
|
+
this.headerMenuOpenForColumnSig.set(null);
|
|
986
|
+
this.headerMenuAnchorElementSig.set(null);
|
|
987
|
+
}
|
|
988
|
+
headerMenuPinLeft() {
|
|
989
|
+
const col = this.headerMenuOpenForColumnSig();
|
|
990
|
+
if (col && this.isPinned(col) !== 'left') {
|
|
991
|
+
this.pinColumn(col, 'left');
|
|
992
|
+
this.closeHeaderMenu();
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
headerMenuPinRight() {
|
|
996
|
+
const col = this.headerMenuOpenForColumnSig();
|
|
997
|
+
if (col && this.isPinned(col) !== 'right') {
|
|
998
|
+
this.pinColumn(col, 'right');
|
|
999
|
+
this.closeHeaderMenu();
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
headerMenuUnpin() {
|
|
1003
|
+
const col = this.headerMenuOpenForColumnSig();
|
|
1004
|
+
if (col && this.isPinned(col)) {
|
|
1005
|
+
this.unpinColumn(col);
|
|
1006
|
+
this.closeHeaderMenu();
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
952
1009
|
// --- Get state result ---
|
|
953
1010
|
getState() {
|
|
954
1011
|
const p = this.props();
|
|
@@ -1048,7 +1105,29 @@ let DataGridStateService = class DataGridStateService {
|
|
|
1048
1105
|
showEmptyInGrid: this.showEmptyInGrid(),
|
|
1049
1106
|
onCellError: p?.onCellError,
|
|
1050
1107
|
};
|
|
1051
|
-
|
|
1108
|
+
// --- Pinning ---
|
|
1109
|
+
const openForColumn = this.headerMenuOpenForColumnSig();
|
|
1110
|
+
const currentPinState = openForColumn ? (p?.pinnedColumns?.[openForColumn]) : undefined;
|
|
1111
|
+
const pinning = {
|
|
1112
|
+
pinnedColumns: p?.pinnedColumns ?? {},
|
|
1113
|
+
pinColumn: (columnId, side) => this.pinColumn(columnId, side),
|
|
1114
|
+
unpinColumn: (columnId) => this.unpinColumn(columnId),
|
|
1115
|
+
isPinned: (columnId) => this.isPinned(columnId),
|
|
1116
|
+
headerMenu: {
|
|
1117
|
+
isOpen: this.headerMenuIsOpenSig(),
|
|
1118
|
+
openForColumn,
|
|
1119
|
+
anchorElement: this.headerMenuAnchorElementSig(),
|
|
1120
|
+
open: (columnId, anchorEl) => this.openHeaderMenu(columnId, anchorEl),
|
|
1121
|
+
close: () => this.closeHeaderMenu(),
|
|
1122
|
+
handlePinLeft: () => this.headerMenuPinLeft(),
|
|
1123
|
+
handlePinRight: () => this.headerMenuPinRight(),
|
|
1124
|
+
handleUnpin: () => this.headerMenuUnpin(),
|
|
1125
|
+
canPinLeft: currentPinState !== 'left',
|
|
1126
|
+
canPinRight: currentPinState !== 'right',
|
|
1127
|
+
canUnpin: !!currentPinState,
|
|
1128
|
+
},
|
|
1129
|
+
};
|
|
1130
|
+
return { layout, rowSelection, editing, interaction, contextMenu, viewModels, pinning };
|
|
1052
1131
|
}
|
|
1053
1132
|
// --- Private helpers ---
|
|
1054
1133
|
getEffectiveRange() {
|
|
@@ -52,6 +52,7 @@ let OGridService = class OGridService {
|
|
|
52
52
|
this.suppressHorizontalScroll = signal(undefined);
|
|
53
53
|
this.editable = signal(undefined);
|
|
54
54
|
this.cellSelection = signal(undefined);
|
|
55
|
+
this.density = signal('normal');
|
|
55
56
|
this.onCellValueChanged = signal(undefined);
|
|
56
57
|
this.onUndo = signal(undefined);
|
|
57
58
|
this.onRedo = signal(undefined);
|
|
@@ -66,6 +67,7 @@ let OGridService = class OGridService {
|
|
|
66
67
|
this.onFirstDataRendered = signal(undefined);
|
|
67
68
|
this.onError = signal(undefined);
|
|
68
69
|
this.columnChooserProp = signal(undefined);
|
|
70
|
+
this.virtualScroll = signal(undefined);
|
|
69
71
|
this.ariaLabel = signal(undefined);
|
|
70
72
|
this.ariaLabelledBy = signal(undefined);
|
|
71
73
|
// --- Internal state signals ---
|
|
@@ -225,6 +227,7 @@ let OGridService = class OGridService {
|
|
|
225
227
|
freezeCols: this.freezeCols(),
|
|
226
228
|
editable: this.editable(),
|
|
227
229
|
cellSelection: this.cellSelection(),
|
|
230
|
+
density: this.density(),
|
|
228
231
|
onCellValueChanged: this.onCellValueChanged(),
|
|
229
232
|
onUndo: this.onUndo(),
|
|
230
233
|
onRedo: this.onRedo(),
|
|
@@ -243,6 +246,7 @@ let OGridService = class OGridService {
|
|
|
243
246
|
getUserByEmail: this.dataSource()?.getUserByEmail?.bind(this.dataSource()),
|
|
244
247
|
layoutMode: this.layoutMode(),
|
|
245
248
|
suppressHorizontalScroll: this.suppressHorizontalScroll(),
|
|
249
|
+
virtualScroll: this.virtualScroll(),
|
|
246
250
|
'aria-label': this.ariaLabel(),
|
|
247
251
|
'aria-labelledby': this.ariaLabelledBy(),
|
|
248
252
|
emptyState: {
|
|
@@ -428,7 +432,7 @@ let OGridService = class OGridService {
|
|
|
428
432
|
handleColumnPinned(columnId, pinned) {
|
|
429
433
|
this.pinnedOverrides.update((prev) => {
|
|
430
434
|
if (pinned === null) {
|
|
431
|
-
const { [columnId]:
|
|
435
|
+
const { [columnId]: _removed, ...rest } = prev;
|
|
432
436
|
return rest;
|
|
433
437
|
}
|
|
434
438
|
return { ...prev, [columnId]: pinned };
|
|
@@ -487,6 +491,8 @@ let OGridService = class OGridService {
|
|
|
487
491
|
this.editable.set(props.editable);
|
|
488
492
|
if (props.cellSelection !== undefined)
|
|
489
493
|
this.cellSelection.set(props.cellSelection);
|
|
494
|
+
if (props.density !== undefined)
|
|
495
|
+
this.density.set(props.density);
|
|
490
496
|
if (props.onCellValueChanged)
|
|
491
497
|
this.onCellValueChanged.set(props.onCellValueChanged);
|
|
492
498
|
if (props.onUndo)
|
|
@@ -515,6 +521,8 @@ let OGridService = class OGridService {
|
|
|
515
521
|
this.onError.set(props.onError);
|
|
516
522
|
if (props.columnChooser !== undefined)
|
|
517
523
|
this.columnChooserProp.set(props.columnChooser);
|
|
524
|
+
if (props.virtualScroll !== undefined)
|
|
525
|
+
this.virtualScroll.set(props.virtualScroll);
|
|
518
526
|
if (props.entityLabelPlural !== undefined)
|
|
519
527
|
this.entityLabelPlural.set(props.entityLabelPlural);
|
|
520
528
|
if (props.className !== undefined)
|
|
@@ -28,7 +28,7 @@ let VirtualScrollService = class VirtualScrollService {
|
|
|
28
28
|
// Scrollable container reference for programmatic scrolling
|
|
29
29
|
this.containerEl = null;
|
|
30
30
|
// --- Derived computed signals ---
|
|
31
|
-
this.rowHeight = computed(() => this.config().rowHeight);
|
|
31
|
+
this.rowHeight = computed(() => this.config().rowHeight ?? 36);
|
|
32
32
|
this.overscan = computed(() => this.config().overscan ?? 5);
|
|
33
33
|
this.enabled = computed(() => this.config().enabled !== false);
|
|
34
34
|
/** Whether virtual scrolling is actually active (enabled + enough rows). */
|
|
@@ -1,163 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* View model helpers for Angular DataGridTable.
|
|
3
|
-
*
|
|
2
|
+
* View model helpers for Angular DataGridTable.
|
|
3
|
+
* Pure functions live in @alaarab/ogrid-core. This file re-exports them.
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
* Returns ColumnHeaderFilter props from column def and grid filter/sort state.
|
|
8
|
-
* Use in Angular Material and PrimeNG DataGridTableComponent instead of inline logic.
|
|
9
|
-
*/
|
|
10
|
-
export function getHeaderFilterConfig(col, input) {
|
|
11
|
-
const filterable = col.filterable && typeof col.filterable === 'object' ? col.filterable : null;
|
|
12
|
-
const filterType = (filterable?.type ?? 'none');
|
|
13
|
-
const filterField = filterable?.filterField ?? col.columnId;
|
|
14
|
-
const sortable = col.sortable !== false;
|
|
15
|
-
const filterValue = input.filters[filterField];
|
|
16
|
-
const base = {
|
|
17
|
-
columnKey: col.columnId,
|
|
18
|
-
columnName: col.name,
|
|
19
|
-
filterType,
|
|
20
|
-
isSorted: input.sortBy === col.columnId,
|
|
21
|
-
isSortedDescending: input.sortBy === col.columnId && input.sortDirection === 'desc',
|
|
22
|
-
onSort: sortable ? () => input.onColumnSort(col.columnId) : undefined,
|
|
23
|
-
};
|
|
24
|
-
if (filterType === 'text') {
|
|
25
|
-
return {
|
|
26
|
-
...base,
|
|
27
|
-
textValue: filterValue?.type === 'text' ? filterValue.value : '',
|
|
28
|
-
onTextChange: (v) => input.onFilterChange(filterField, v.trim() ? { type: 'text', value: v } : undefined),
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
if (filterType === 'people') {
|
|
32
|
-
return {
|
|
33
|
-
...base,
|
|
34
|
-
selectedUser: filterValue?.type === 'people' ? filterValue.value : undefined,
|
|
35
|
-
onUserChange: (u) => input.onFilterChange(filterField, u ? { type: 'people', value: u } : undefined),
|
|
36
|
-
peopleSearch: input.peopleSearch,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
if (filterType === 'multiSelect') {
|
|
40
|
-
return {
|
|
41
|
-
...base,
|
|
42
|
-
options: input.filterOptions[filterField] ?? [],
|
|
43
|
-
isLoadingOptions: input.loadingFilterOptions[filterField] ?? false,
|
|
44
|
-
selectedValues: filterValue?.type === 'multiSelect' ? filterValue.value : [],
|
|
45
|
-
onFilterChange: (values) => input.onFilterChange(filterField, values.length ? { type: 'multiSelect', value: values } : undefined),
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
if (filterType === 'date') {
|
|
49
|
-
return {
|
|
50
|
-
...base,
|
|
51
|
-
dateValue: filterValue?.type === 'date' ? filterValue.value : undefined,
|
|
52
|
-
onDateChange: (v) => input.onFilterChange(filterField, v ? { type: 'date', value: v } : undefined),
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
return base;
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Returns a descriptor for rendering a cell. UI uses this to decide editing-inline vs editing-popover vs display
|
|
59
|
-
* and to apply isActive, isInRange, etc. without duplicating the boolean logic.
|
|
60
|
-
*/
|
|
61
|
-
export function getCellRenderDescriptor(item, col, rowIndex, colIdx, input) {
|
|
62
|
-
const rowId = input.getRowId(item);
|
|
63
|
-
const globalColIndex = colIdx + input.colOffset;
|
|
64
|
-
const colEditable = col.editable === true ||
|
|
65
|
-
(typeof col.editable === 'function' && col.editable(item));
|
|
66
|
-
const canEditInline = input.editable !== false &&
|
|
67
|
-
!!colEditable &&
|
|
68
|
-
!!input.onCellValueChanged &&
|
|
69
|
-
typeof col.cellEditor !== 'function';
|
|
70
|
-
const canEditPopup = input.editable !== false &&
|
|
71
|
-
!!colEditable &&
|
|
72
|
-
!!input.onCellValueChanged &&
|
|
73
|
-
typeof col.cellEditor === 'function';
|
|
74
|
-
const canEditAny = canEditInline || canEditPopup;
|
|
75
|
-
const isEditing = input.editingCell?.rowId === rowId &&
|
|
76
|
-
input.editingCell?.columnId === col.columnId;
|
|
77
|
-
const isActive = input.activeCell?.rowIndex === rowIndex &&
|
|
78
|
-
input.activeCell?.columnIndex === globalColIndex;
|
|
79
|
-
const isInRange = input.selectionRange != null &&
|
|
80
|
-
isInSelectionRange(input.selectionRange, rowIndex, colIdx);
|
|
81
|
-
const isInCutRange = input.cutRange != null &&
|
|
82
|
-
isInSelectionRange(input.cutRange, rowIndex, colIdx);
|
|
83
|
-
const isInCopyRange = input.copyRange != null &&
|
|
84
|
-
isInSelectionRange(input.copyRange, rowIndex, colIdx);
|
|
85
|
-
const isSelectionEndCell = !input.isDragging &&
|
|
86
|
-
input.copyRange == null &&
|
|
87
|
-
input.cutRange == null &&
|
|
88
|
-
input.selectionRange != null &&
|
|
89
|
-
rowIndex === input.selectionRange.endRow &&
|
|
90
|
-
colIdx === input.selectionRange.endCol;
|
|
91
|
-
let mode = 'display';
|
|
92
|
-
let editorType;
|
|
93
|
-
const value = getCellValue(item, col);
|
|
94
|
-
if (isEditing && canEditInline) {
|
|
95
|
-
mode = 'editing-inline';
|
|
96
|
-
if (col.cellEditor === 'text' ||
|
|
97
|
-
col.cellEditor === 'select' ||
|
|
98
|
-
col.cellEditor === 'checkbox' ||
|
|
99
|
-
col.cellEditor === 'richSelect' ||
|
|
100
|
-
col.cellEditor === 'date') {
|
|
101
|
-
editorType = col.cellEditor;
|
|
102
|
-
}
|
|
103
|
-
else if (col.type === 'date') {
|
|
104
|
-
editorType = 'date';
|
|
105
|
-
}
|
|
106
|
-
else if (col.type === 'boolean') {
|
|
107
|
-
editorType = 'checkbox';
|
|
108
|
-
}
|
|
109
|
-
else {
|
|
110
|
-
editorType = 'text';
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
else if (isEditing && canEditPopup) {
|
|
114
|
-
mode = 'editing-popover';
|
|
115
|
-
}
|
|
116
|
-
return {
|
|
117
|
-
mode,
|
|
118
|
-
editorType,
|
|
119
|
-
value,
|
|
120
|
-
isActive,
|
|
121
|
-
isInRange,
|
|
122
|
-
isInCutRange,
|
|
123
|
-
isInCopyRange,
|
|
124
|
-
isSelectionEndCell,
|
|
125
|
-
canEditAny,
|
|
126
|
-
globalColIndex,
|
|
127
|
-
rowId,
|
|
128
|
-
rowIndex,
|
|
129
|
-
displayValue: value,
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
// --- Cell rendering helpers (reduce DataGridTable view-layer duplication) ---
|
|
133
|
-
/**
|
|
134
|
-
* Resolves display content for a cell in display mode.
|
|
135
|
-
* Handles the renderCell → valueFormatter → String() fallback chain.
|
|
136
|
-
*/
|
|
137
|
-
export function resolveCellDisplayContent(col, item, displayValue) {
|
|
138
|
-
if (col.renderCell && typeof col.renderCell === 'function') {
|
|
139
|
-
const result = col.renderCell(item);
|
|
140
|
-
return result != null ? String(result) : '';
|
|
141
|
-
}
|
|
142
|
-
if (col.valueFormatter)
|
|
143
|
-
return String(col.valueFormatter(displayValue, item) ?? '');
|
|
144
|
-
if (displayValue == null)
|
|
145
|
-
return '';
|
|
146
|
-
if (col.type === 'date') {
|
|
147
|
-
const d = new Date(String(displayValue));
|
|
148
|
-
if (!Number.isNaN(d.getTime()))
|
|
149
|
-
return d.toLocaleDateString();
|
|
150
|
-
}
|
|
151
|
-
if (col.type === 'boolean') {
|
|
152
|
-
return displayValue ? 'True' : 'False';
|
|
153
|
-
}
|
|
154
|
-
return String(displayValue);
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* Resolves the cellStyle from a column def, handling both function and static values.
|
|
158
|
-
*/
|
|
159
|
-
export function resolveCellStyle(col, item) {
|
|
160
|
-
if (!col.cellStyle)
|
|
161
|
-
return undefined;
|
|
162
|
-
return typeof col.cellStyle === 'function' ? col.cellStyle(item) : col.cellStyle;
|
|
163
|
-
}
|
|
5
|
+
// Re-export everything from core's dataGridViewModel
|
|
6
|
+
export { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, } from '@alaarab/ogrid-core';
|
|
@@ -64,42 +64,5 @@ export function createDebouncedCallback(fn, delayMs) {
|
|
|
64
64
|
}, delayMs);
|
|
65
65
|
});
|
|
66
66
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
* Returns a debounced version of the provided function.
|
|
70
|
-
*
|
|
71
|
-
* @param fn - The function to debounce
|
|
72
|
-
* @param delayMs - Delay in milliseconds
|
|
73
|
-
* @returns A debounced version of the function with a `cancel()` method
|
|
74
|
-
*
|
|
75
|
-
* @example
|
|
76
|
-
* ```typescript
|
|
77
|
-
* const handleResize = debounce(() => {
|
|
78
|
-
* console.log('Window resized');
|
|
79
|
-
* }, 200);
|
|
80
|
-
*
|
|
81
|
-
* window.addEventListener('resize', handleResize);
|
|
82
|
-
*
|
|
83
|
-
* // Later, cancel pending execution
|
|
84
|
-
* handleResize.cancel();
|
|
85
|
-
* ```
|
|
86
|
-
*/
|
|
87
|
-
export function debounce(fn, delayMs) {
|
|
88
|
-
let timeoutId = null;
|
|
89
|
-
const debounced = ((...args) => {
|
|
90
|
-
if (timeoutId !== null) {
|
|
91
|
-
clearTimeout(timeoutId);
|
|
92
|
-
}
|
|
93
|
-
timeoutId = setTimeout(() => {
|
|
94
|
-
fn(...args);
|
|
95
|
-
timeoutId = null;
|
|
96
|
-
}, delayMs);
|
|
97
|
-
});
|
|
98
|
-
debounced.cancel = () => {
|
|
99
|
-
if (timeoutId !== null) {
|
|
100
|
-
clearTimeout(timeoutId);
|
|
101
|
-
timeoutId = null;
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
return debounced;
|
|
105
|
-
}
|
|
67
|
+
// Re-export simple debounce from core
|
|
68
|
+
export { debounce } from '@alaarab/ogrid-core';
|
package/dist/esm/utils/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared utilities for Angular DataGridTable view layer.
|
|
3
3
|
*/
|
|
4
|
-
export { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, } from './dataGridViewModel';
|
|
4
|
+
export { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, } from './dataGridViewModel';
|
|
5
5
|
// Debounce utilities
|
|
6
6
|
export { createDebouncedSignal, createDebouncedCallback, debounce, } from './debounce';
|
|
7
7
|
// Latest ref utilities
|
|
@@ -13,7 +13,7 @@ import type { HeaderFilterConfig, CellRenderDescriptor } from '../utils';
|
|
|
13
13
|
* 2. Call `initBase()` in the constructor (effects require injection context)
|
|
14
14
|
* 3. Implement abstract accessors for propsInput, wrapperRef, and tableContainerRef
|
|
15
15
|
*/
|
|
16
|
-
export declare abstract class BaseDataGridTableComponent<T =
|
|
16
|
+
export declare abstract class BaseDataGridTableComponent<T = unknown> {
|
|
17
17
|
readonly stateService: DataGridStateService<T>;
|
|
18
18
|
readonly columnReorderService: ColumnReorderService<T>;
|
|
19
19
|
readonly virtualScrollService: VirtualScrollService;
|
|
@@ -27,6 +27,7 @@ export declare abstract class BaseDataGridTableComponent<T = any> {
|
|
|
27
27
|
protected abstract getTableContainerRef(): ElementRef<HTMLElement> | undefined;
|
|
28
28
|
readonly state: import("@angular/core").Signal<import("../services/datagrid-state.service").DataGridStateResult<T>>;
|
|
29
29
|
readonly tableContainerEl: import("@angular/core").Signal<HTMLElement | null>;
|
|
30
|
+
readonly allItems: import("@angular/core").Signal<T[]>;
|
|
30
31
|
readonly items: import("@angular/core").Signal<T[]>;
|
|
31
32
|
readonly getRowId: import("@angular/core").Signal<(item: T) => RowId>;
|
|
32
33
|
readonly isLoading: import("@angular/core").Signal<boolean>;
|
|
@@ -103,6 +104,15 @@ export declare abstract class BaseDataGridTableComponent<T = any> {
|
|
|
103
104
|
onCellValueChanged?: ((event: import("@alaarab/ogrid-core").ICellValueChangedEvent<T>) => void) | undefined;
|
|
104
105
|
isDragging: boolean;
|
|
105
106
|
}>;
|
|
107
|
+
readonly pinnedColumnsMap: import("@angular/core").Signal<Record<string, "left" | "right">>;
|
|
108
|
+
readonly vsEnabled: import("@angular/core").Signal<boolean>;
|
|
109
|
+
readonly vsVisibleRange: import("@angular/core").Signal<import("@alaarab/ogrid-core").IVisibleRange>;
|
|
110
|
+
readonly vsTopSpacerHeight: import("@angular/core").Signal<number>;
|
|
111
|
+
readonly vsBottomSpacerHeight: import("@angular/core").Signal<number>;
|
|
112
|
+
readonly vsVisibleItems: import("@angular/core").Signal<T[]>;
|
|
113
|
+
readonly vsStartIndex: import("@angular/core").Signal<number>;
|
|
114
|
+
readonly popoverAnchorEl: import("@angular/core").Signal<HTMLElement | null>;
|
|
115
|
+
readonly pendingEditorValueForPopover: import("@angular/core").Signal<unknown>;
|
|
106
116
|
readonly allowOverflowX: import("@angular/core").Signal<boolean>;
|
|
107
117
|
readonly selectionCellCount: import("@angular/core").Signal<number | undefined>;
|
|
108
118
|
readonly headerRows: import("@angular/core").Signal<import("@alaarab/ogrid-core").HeaderRow<T>[]>;
|
|
@@ -123,10 +133,16 @@ export declare abstract class BaseDataGridTableComponent<T = any> {
|
|
|
123
133
|
getColumnWidth(col: IColumnDef<T>): number;
|
|
124
134
|
getFilterConfig(col: IColumnDef<T>): HeaderFilterConfig;
|
|
125
135
|
getCellDescriptor(item: T, col: IColumnDef<T>, rowIndex: number, colIdx: number): CellRenderDescriptor;
|
|
126
|
-
resolveCellContent(col: IColumnDef<T>, item: T, displayValue: unknown):
|
|
136
|
+
resolveCellContent(col: IColumnDef<T>, item: T, displayValue: unknown): unknown;
|
|
127
137
|
resolveCellStyleFn(col: IColumnDef<T>, item: T): Record<string, string> | undefined;
|
|
138
|
+
buildPopoverEditorProps(item: T, col: IColumnDef<T>, descriptor: CellRenderDescriptor): unknown;
|
|
128
139
|
getSelectValues(col: IColumnDef<T>): string[];
|
|
129
140
|
formatDateForInput(value: unknown): string;
|
|
141
|
+
onWrapperScroll(event: Event): void;
|
|
142
|
+
setPopoverAnchorEl(el: HTMLElement | null): void;
|
|
143
|
+
setPendingEditorValue(value: unknown): void;
|
|
144
|
+
cancelPopoverEdit(): void;
|
|
145
|
+
commitPopoverEdit(item: T, columnId: string, oldValue: unknown, newValue: unknown, rowIndex: number, globalColIndex: number): void;
|
|
130
146
|
onWrapperMouseDown(event: MouseEvent): void;
|
|
131
147
|
onGridKeyDown(event: KeyboardEvent): void;
|
|
132
148
|
onCellMouseDown(event: MouseEvent, rowIndex: number, globalColIndex: number): void;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from '@alaarab/ogrid-core';
|
|
2
|
+
export { CHECKBOX_COLUMN_WIDTH, ROW_NUMBER_COLUMN_WIDTH, DEFAULT_MIN_COLUMN_WIDTH, CELL_PADDING, GRID_BORDER_RADIUS, PEOPLE_SEARCH_DEBOUNCE_MS, DEFAULT_DEBOUNCE_MS, SIDEBAR_TRANSITION_MS, Z_INDEX, } from '@alaarab/ogrid-core';
|
|
2
3
|
export type { IColumnDef, IColumnGroupDef, IColumnDefinition, ICellEditorProps, } from './types';
|
|
3
4
|
export type { IOGridProps, IOGridClientProps, IOGridServerProps, IOGridDataGridProps, } from './types';
|
|
4
5
|
export type { ColumnFilterType, IColumnFilterDef, IColumnMeta, ICellValueChangedEvent, CellEditorParams, IValueParserParams, IDateFilterValue, HeaderCell, HeaderRow, RowId, UserLike, UserLikeInput, FilterValue, IFilters, IFetchParams, IPageResult, IDataSource, IGridColumnState, IOGridApi, RowSelectionMode, IRowSelectionChangeEvent, StatusBarPanel, IStatusBarProps, IActiveCell, ISelectionRange, SideBarPanelId, ISideBarDef, } from './types';
|
|
@@ -6,10 +7,9 @@ export { toUserLike, isInSelectionRange, normalizeSelectionRange } from './types
|
|
|
6
7
|
export { OGridService } from './services/ogrid.service';
|
|
7
8
|
export type { ColumnChooserPlacement, OGridPagination, OGridColumnChooser, OGridFilters, OGridSideBarState, } from './services/ogrid.service';
|
|
8
9
|
export { DataGridStateService } from './services/datagrid-state.service';
|
|
9
|
-
export type { DataGridLayoutState, DataGridRowSelectionState, DataGridEditingState, DataGridCellInteractionState, DataGridContextMenuState, DataGridViewModelState, DataGridStateResult, } from './services/datagrid-state.service';
|
|
10
|
+
export type { DataGridLayoutState, DataGridRowSelectionState, DataGridEditingState, DataGridCellInteractionState, DataGridContextMenuState, DataGridViewModelState, DataGridPinningState, DataGridStateResult, } from './services/datagrid-state.service';
|
|
10
11
|
export { ColumnReorderService } from './services/column-reorder.service';
|
|
11
12
|
export { VirtualScrollService } from './services/virtual-scroll.service';
|
|
12
|
-
export type { IVirtualScrollConfig } from './services/virtual-scroll.service';
|
|
13
13
|
export { OGridLayoutComponent } from './components/ogrid-layout.component';
|
|
14
14
|
export { StatusBarComponent } from './components/status-bar.component';
|
|
15
15
|
export { GridContextMenuComponent } from './components/grid-context-menu.component';
|
|
@@ -115,6 +115,26 @@ export interface DataGridViewModelState<T> {
|
|
|
115
115
|
showEmptyInGrid: boolean;
|
|
116
116
|
onCellError?: (error: Error) => void;
|
|
117
117
|
}
|
|
118
|
+
/** Column pinning state and column header menu. */
|
|
119
|
+
export interface DataGridPinningState {
|
|
120
|
+
pinnedColumns: Record<string, 'left' | 'right'>;
|
|
121
|
+
pinColumn: (columnId: string, side: 'left' | 'right') => void;
|
|
122
|
+
unpinColumn: (columnId: string) => void;
|
|
123
|
+
isPinned: (columnId: string) => 'left' | 'right' | undefined;
|
|
124
|
+
headerMenu: {
|
|
125
|
+
isOpen: boolean;
|
|
126
|
+
openForColumn: string | null;
|
|
127
|
+
anchorElement: HTMLElement | null;
|
|
128
|
+
open: (columnId: string, anchorEl: HTMLElement) => void;
|
|
129
|
+
close: () => void;
|
|
130
|
+
handlePinLeft: () => void;
|
|
131
|
+
handlePinRight: () => void;
|
|
132
|
+
handleUnpin: () => void;
|
|
133
|
+
canPinLeft: boolean;
|
|
134
|
+
canPinRight: boolean;
|
|
135
|
+
canUnpin: boolean;
|
|
136
|
+
};
|
|
137
|
+
}
|
|
118
138
|
export interface DataGridStateResult<T> {
|
|
119
139
|
layout: DataGridLayoutState<T>;
|
|
120
140
|
rowSelection: DataGridRowSelectionState;
|
|
@@ -122,6 +142,7 @@ export interface DataGridStateResult<T> {
|
|
|
122
142
|
interaction: DataGridCellInteractionState;
|
|
123
143
|
contextMenu: DataGridContextMenuState;
|
|
124
144
|
viewModels: DataGridViewModelState<T>;
|
|
145
|
+
pinning: DataGridPinningState;
|
|
125
146
|
}
|
|
126
147
|
/**
|
|
127
148
|
* Single orchestration service for DataGridTable. Takes grid props,
|
|
@@ -161,6 +182,9 @@ export declare class DataGridStateService<T> {
|
|
|
161
182
|
private lastMousePos;
|
|
162
183
|
private autoScrollInterval;
|
|
163
184
|
private resizeObserver;
|
|
185
|
+
private readonly headerMenuIsOpenSig;
|
|
186
|
+
private readonly headerMenuOpenForColumnSig;
|
|
187
|
+
private readonly headerMenuAnchorElementSig;
|
|
164
188
|
private readonly propsResolved;
|
|
165
189
|
readonly cellSelection: import("@angular/core").Signal<boolean>;
|
|
166
190
|
private readonly wrappedOnCellValueChanged;
|
|
@@ -227,6 +251,19 @@ export declare class DataGridStateService<T> {
|
|
|
227
251
|
redo(): void;
|
|
228
252
|
handleGridKeyDown(e: KeyboardEvent): void;
|
|
229
253
|
handleFillHandleMouseDown(e: MouseEvent): void;
|
|
254
|
+
pinColumn(columnId: string, side: 'left' | 'right'): void;
|
|
255
|
+
unpinColumn(columnId: string): void;
|
|
256
|
+
isPinned(columnId: string): 'left' | 'right' | undefined;
|
|
257
|
+
getPinState(columnId: string): {
|
|
258
|
+
canPinLeft: boolean;
|
|
259
|
+
canPinRight: boolean;
|
|
260
|
+
canUnpin: boolean;
|
|
261
|
+
};
|
|
262
|
+
openHeaderMenu(columnId: string, anchorEl: HTMLElement): void;
|
|
263
|
+
closeHeaderMenu(): void;
|
|
264
|
+
headerMenuPinLeft(): void;
|
|
265
|
+
headerMenuPinRight(): void;
|
|
266
|
+
headerMenuUnpin(): void;
|
|
230
267
|
getState(): DataGridStateResult<T>;
|
|
231
268
|
private getEffectiveRange;
|
|
232
269
|
private onWindowMouseMove;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RowId, IOGridApi, IFilters, FilterValue, IRowSelectionChangeEvent, IStatusBarProps, IColumnDefinition, IDataSource, ISideBarDef, SideBarPanelId } from '../types';
|
|
1
|
+
import type { RowId, IOGridApi, IFilters, FilterValue, IRowSelectionChangeEvent, IStatusBarProps, IColumnDefinition, IDataSource, ISideBarDef, IVirtualScrollConfig, SideBarPanelId } from '../types';
|
|
2
2
|
import type { IOGridProps, IOGridDataGridProps } from '../types';
|
|
3
3
|
import type { IColumnDef, IColumnGroupDef, ICellValueChangedEvent } from '../types';
|
|
4
4
|
/** Resolved column chooser placement. */
|
|
@@ -86,6 +86,7 @@ export declare class OGridService<T> {
|
|
|
86
86
|
readonly suppressHorizontalScroll: import("@angular/core").WritableSignal<boolean | undefined>;
|
|
87
87
|
readonly editable: import("@angular/core").WritableSignal<boolean | undefined>;
|
|
88
88
|
readonly cellSelection: import("@angular/core").WritableSignal<boolean | undefined>;
|
|
89
|
+
readonly density: import("@angular/core").WritableSignal<"compact" | "normal" | "comfortable">;
|
|
89
90
|
readonly onCellValueChanged: import("@angular/core").WritableSignal<((event: ICellValueChangedEvent<T>) => void) | undefined>;
|
|
90
91
|
readonly onUndo: import("@angular/core").WritableSignal<(() => void) | undefined>;
|
|
91
92
|
readonly onRedo: import("@angular/core").WritableSignal<(() => void) | undefined>;
|
|
@@ -100,6 +101,7 @@ export declare class OGridService<T> {
|
|
|
100
101
|
readonly onFirstDataRendered: import("@angular/core").WritableSignal<(() => void) | undefined>;
|
|
101
102
|
readonly onError: import("@angular/core").WritableSignal<((error: unknown) => void) | undefined>;
|
|
102
103
|
readonly columnChooserProp: import("@angular/core").WritableSignal<boolean | "toolbar" | "sidebar" | undefined>;
|
|
104
|
+
readonly virtualScroll: import("@angular/core").WritableSignal<IVirtualScrollConfig | undefined>;
|
|
103
105
|
readonly ariaLabel: import("@angular/core").WritableSignal<string | undefined>;
|
|
104
106
|
readonly ariaLabelledBy: import("@angular/core").WritableSignal<string | undefined>;
|
|
105
107
|
private readonly internalData;
|
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
import type { IVisibleRange } from '@alaarab/ogrid-core';
|
|
2
|
-
export interface IVirtualScrollConfig {
|
|
3
|
-
/** Enable virtual scrolling. Default: true when provided. */
|
|
4
|
-
enabled?: boolean;
|
|
5
|
-
/** Row height in pixels (required for virtualization). */
|
|
6
|
-
rowHeight: number;
|
|
7
|
-
/** Number of rows to render outside the visible area. Default: 5. */
|
|
8
|
-
overscan?: number;
|
|
9
|
-
}
|
|
1
|
+
import type { IVisibleRange, IVirtualScrollConfig } from '@alaarab/ogrid-core';
|
|
10
2
|
/**
|
|
11
3
|
* Manages virtual scrolling state using Angular signals.
|
|
12
4
|
* Port of React's useVirtualScroll hook.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { TemplateRef } from '@angular/core';
|
|
2
2
|
import type { IColumnDef, IColumnGroupDef, ICellValueChangedEvent } from './columnTypes';
|
|
3
|
-
export type { RowId, UserLike, UserLikeInput, FilterValue, IFilters, IFetchParams, IPageResult, IDataSource, IGridColumnState, RowSelectionMode, IRowSelectionChangeEvent, StatusBarPanel, IStatusBarProps, IActiveCell, ISelectionRange, SideBarPanelId, ISideBarDef, IOGridApi, } from '@alaarab/ogrid-core';
|
|
3
|
+
export type { RowId, UserLike, UserLikeInput, FilterValue, IFilters, IFetchParams, IPageResult, IDataSource, IGridColumnState, RowSelectionMode, IRowSelectionChangeEvent, StatusBarPanel, IStatusBarProps, IActiveCell, ISelectionRange, SideBarPanelId, ISideBarDef, IVirtualScrollConfig, IOGridApi, } from '@alaarab/ogrid-core';
|
|
4
4
|
export { toUserLike, isInSelectionRange, normalizeSelectionRange } from '@alaarab/ogrid-core';
|
|
5
|
-
import type { RowId, UserLike, IFilters, FilterValue, RowSelectionMode, IRowSelectionChangeEvent, IStatusBarProps, IDataSource, ISideBarDef } from '@alaarab/ogrid-core';
|
|
5
|
+
import type { RowId, UserLike, IFilters, FilterValue, RowSelectionMode, IRowSelectionChangeEvent, IStatusBarProps, IDataSource, ISideBarDef, IVirtualScrollConfig } from '@alaarab/ogrid-core';
|
|
6
6
|
/** Base props shared by both client-side and server-side OGrid modes. */
|
|
7
7
|
interface IOGridBaseProps<T> {
|
|
8
8
|
columns: (IColumnDef<T> | IColumnGroupDef<T>)[];
|
|
@@ -32,6 +32,7 @@ interface IOGridBaseProps<T> {
|
|
|
32
32
|
freezeCols?: number;
|
|
33
33
|
editable?: boolean;
|
|
34
34
|
cellSelection?: boolean;
|
|
35
|
+
density?: 'compact' | 'normal' | 'comfortable';
|
|
35
36
|
onCellValueChanged?: (event: ICellValueChangedEvent<T>) => void;
|
|
36
37
|
onUndo?: () => void;
|
|
37
38
|
onRedo?: () => void;
|
|
@@ -56,6 +57,7 @@ interface IOGridBaseProps<T> {
|
|
|
56
57
|
layoutMode?: 'content' | 'fill';
|
|
57
58
|
suppressHorizontalScroll?: boolean;
|
|
58
59
|
sideBar?: boolean | ISideBarDef;
|
|
60
|
+
virtualScroll?: IVirtualScrollConfig;
|
|
59
61
|
pageSizeOptions?: number[];
|
|
60
62
|
onFirstDataRendered?: () => void;
|
|
61
63
|
onError?: (error: unknown) => void;
|
|
@@ -99,6 +101,7 @@ export interface IOGridDataGridProps<T> {
|
|
|
99
101
|
loadingMessage?: string;
|
|
100
102
|
editable?: boolean;
|
|
101
103
|
cellSelection?: boolean;
|
|
104
|
+
density?: 'compact' | 'normal' | 'comfortable';
|
|
102
105
|
onCellValueChanged?: (event: ICellValueChangedEvent<T>) => void;
|
|
103
106
|
onUndo?: () => void;
|
|
104
107
|
onRedo?: () => void;
|
|
@@ -117,6 +120,7 @@ export interface IOGridDataGridProps<T> {
|
|
|
117
120
|
loadingFilterOptions: Record<string, boolean>;
|
|
118
121
|
peopleSearch?: (query: string) => Promise<UserLike[]>;
|
|
119
122
|
getUserByEmail?: (email: string) => Promise<UserLike | undefined>;
|
|
123
|
+
virtualScroll?: IVirtualScrollConfig;
|
|
120
124
|
emptyState?: {
|
|
121
125
|
onClearAll: () => void;
|
|
122
126
|
hasActiveFilters: boolean;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export type { ColumnFilterType, IColumnFilterDef, IColumnMeta, IColumnDef, IColumnGroupDef, IColumnDefinition, ICellValueChangedEvent, ICellEditorProps, CellEditorParams, IValueParserParams, IDateFilterValue, HeaderCell, HeaderRow, } from './columnTypes';
|
|
2
|
-
export type { RowId, UserLike, UserLikeInput, FilterValue, IFilters, IFetchParams, IPageResult, IDataSource, IGridColumnState, IOGridApi, IOGridProps, IOGridClientProps, IOGridServerProps, IOGridDataGridProps, RowSelectionMode, IRowSelectionChangeEvent, StatusBarPanel, IStatusBarProps, IActiveCell, ISelectionRange, SideBarPanelId, ISideBarDef, } from './dataGridTypes';
|
|
2
|
+
export type { RowId, UserLike, UserLikeInput, FilterValue, IFilters, IFetchParams, IPageResult, IDataSource, IGridColumnState, IOGridApi, IOGridProps, IOGridClientProps, IOGridServerProps, IOGridDataGridProps, RowSelectionMode, IRowSelectionChangeEvent, StatusBarPanel, IStatusBarProps, IActiveCell, ISelectionRange, SideBarPanelId, ISideBarDef, IVirtualScrollConfig, } from './dataGridTypes';
|
|
3
3
|
export { toUserLike, isInSelectionRange, normalizeSelectionRange } from './dataGridTypes';
|
|
@@ -1,107 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* View model helpers for Angular DataGridTable.
|
|
3
|
-
*
|
|
2
|
+
* View model helpers for Angular DataGridTable.
|
|
3
|
+
* Pure functions live in @alaarab/ogrid-core. This file re-exports them.
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import type { RowId, UserLike, IFilters, FilterValue } from '../types/dataGridTypes';
|
|
8
|
-
export interface HeaderFilterConfigInput {
|
|
9
|
-
sortBy?: string;
|
|
10
|
-
sortDirection: 'asc' | 'desc';
|
|
11
|
-
onColumnSort: (columnKey: string) => void;
|
|
12
|
-
filters: IFilters;
|
|
13
|
-
onFilterChange: (key: string, value: FilterValue | undefined) => void;
|
|
14
|
-
filterOptions: Record<string, string[]>;
|
|
15
|
-
loadingFilterOptions: Record<string, boolean>;
|
|
16
|
-
peopleSearch?: (query: string) => Promise<UserLike[]>;
|
|
17
|
-
}
|
|
18
|
-
/** Props to pass to ColumnHeaderFilter. */
|
|
19
|
-
export interface HeaderFilterConfig {
|
|
20
|
-
columnKey: string;
|
|
21
|
-
columnName: string;
|
|
22
|
-
filterType: ColumnFilterType;
|
|
23
|
-
isSorted?: boolean;
|
|
24
|
-
isSortedDescending?: boolean;
|
|
25
|
-
onSort?: () => void;
|
|
26
|
-
selectedValues?: string[];
|
|
27
|
-
onFilterChange?: (values: string[]) => void;
|
|
28
|
-
options?: string[];
|
|
29
|
-
isLoadingOptions?: boolean;
|
|
30
|
-
textValue?: string;
|
|
31
|
-
onTextChange?: (value: string) => void;
|
|
32
|
-
selectedUser?: UserLike;
|
|
33
|
-
onUserChange?: (user: UserLike | undefined) => void;
|
|
34
|
-
peopleSearch?: (query: string) => Promise<UserLike[]>;
|
|
35
|
-
dateValue?: IDateFilterValue;
|
|
36
|
-
onDateChange?: (value: IDateFilterValue | undefined) => void;
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Returns ColumnHeaderFilter props from column def and grid filter/sort state.
|
|
40
|
-
* Use in Angular Material and PrimeNG DataGridTableComponent instead of inline logic.
|
|
41
|
-
*/
|
|
42
|
-
export declare function getHeaderFilterConfig<T>(col: IColumnDef<T>, input: HeaderFilterConfigInput): HeaderFilterConfig;
|
|
43
|
-
export type CellRenderMode = 'editing-inline' | 'editing-popover' | 'display';
|
|
44
|
-
export interface CellRenderDescriptorInput<T> {
|
|
45
|
-
editingCell: {
|
|
46
|
-
rowId: RowId;
|
|
47
|
-
columnId: string;
|
|
48
|
-
} | null;
|
|
49
|
-
activeCell: {
|
|
50
|
-
rowIndex: number;
|
|
51
|
-
columnIndex: number;
|
|
52
|
-
} | null;
|
|
53
|
-
selectionRange: {
|
|
54
|
-
startRow: number;
|
|
55
|
-
startCol: number;
|
|
56
|
-
endRow: number;
|
|
57
|
-
endCol: number;
|
|
58
|
-
} | null;
|
|
59
|
-
cutRange: {
|
|
60
|
-
startRow: number;
|
|
61
|
-
startCol: number;
|
|
62
|
-
endRow: number;
|
|
63
|
-
endCol: number;
|
|
64
|
-
} | null;
|
|
65
|
-
copyRange: {
|
|
66
|
-
startRow: number;
|
|
67
|
-
startCol: number;
|
|
68
|
-
endRow: number;
|
|
69
|
-
endCol: number;
|
|
70
|
-
} | null;
|
|
71
|
-
colOffset: number;
|
|
72
|
-
getRowId: (item: T) => RowId;
|
|
73
|
-
editable?: boolean;
|
|
74
|
-
onCellValueChanged?: unknown;
|
|
75
|
-
/** True while user is drag-selecting cells — hides fill handle during drag. */
|
|
76
|
-
isDragging?: boolean;
|
|
77
|
-
}
|
|
78
|
-
export interface CellRenderDescriptor {
|
|
79
|
-
mode: CellRenderMode;
|
|
80
|
-
editorType?: 'text' | 'select' | 'checkbox' | 'richSelect' | 'date';
|
|
81
|
-
value?: unknown;
|
|
82
|
-
isActive: boolean;
|
|
83
|
-
isInRange: boolean;
|
|
84
|
-
isInCutRange: boolean;
|
|
85
|
-
isInCopyRange: boolean;
|
|
86
|
-
isSelectionEndCell: boolean;
|
|
87
|
-
canEditAny: boolean;
|
|
88
|
-
globalColIndex: number;
|
|
89
|
-
rowId: RowId;
|
|
90
|
-
rowIndex: number;
|
|
91
|
-
/** Raw value for display (when mode === 'display'). */
|
|
92
|
-
displayValue?: unknown;
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* Returns a descriptor for rendering a cell. UI uses this to decide editing-inline vs editing-popover vs display
|
|
96
|
-
* and to apply isActive, isInRange, etc. without duplicating the boolean logic.
|
|
97
|
-
*/
|
|
98
|
-
export declare function getCellRenderDescriptor<T>(item: T, col: IColumnDef<T>, rowIndex: number, colIdx: number, input: CellRenderDescriptorInput<T>): CellRenderDescriptor;
|
|
99
|
-
/**
|
|
100
|
-
* Resolves display content for a cell in display mode.
|
|
101
|
-
* Handles the renderCell → valueFormatter → String() fallback chain.
|
|
102
|
-
*/
|
|
103
|
-
export declare function resolveCellDisplayContent<T>(col: IColumnDef<T>, item: T, displayValue: unknown): string;
|
|
104
|
-
/**
|
|
105
|
-
* Resolves the cellStyle from a column def, handling both function and static values.
|
|
106
|
-
*/
|
|
107
|
-
export declare function resolveCellStyle<T>(col: IColumnDef<T>, item: T): Record<string, string> | undefined;
|
|
5
|
+
export { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, } from '@alaarab/ogrid-core';
|
|
6
|
+
export type { HeaderFilterConfigInput, HeaderFilterConfig, CellRenderDescriptorInput, CellRenderDescriptor, CellRenderMode, } from '@alaarab/ogrid-core';
|
|
@@ -43,26 +43,4 @@ export declare function createDebouncedSignal<T>(source: Signal<T>, delayMs: num
|
|
|
43
43
|
* ```
|
|
44
44
|
*/
|
|
45
45
|
export declare function createDebouncedCallback<T extends (...args: unknown[]) => void>(fn: T, delayMs: number): T;
|
|
46
|
-
|
|
47
|
-
* Simple debounce function (non-Angular-specific, can be used anywhere).
|
|
48
|
-
* Returns a debounced version of the provided function.
|
|
49
|
-
*
|
|
50
|
-
* @param fn - The function to debounce
|
|
51
|
-
* @param delayMs - Delay in milliseconds
|
|
52
|
-
* @returns A debounced version of the function with a `cancel()` method
|
|
53
|
-
*
|
|
54
|
-
* @example
|
|
55
|
-
* ```typescript
|
|
56
|
-
* const handleResize = debounce(() => {
|
|
57
|
-
* console.log('Window resized');
|
|
58
|
-
* }, 200);
|
|
59
|
-
*
|
|
60
|
-
* window.addEventListener('resize', handleResize);
|
|
61
|
-
*
|
|
62
|
-
* // Later, cancel pending execution
|
|
63
|
-
* handleResize.cancel();
|
|
64
|
-
* ```
|
|
65
|
-
*/
|
|
66
|
-
export declare function debounce<T extends (...args: unknown[]) => void>(fn: T, delayMs: number): T & {
|
|
67
|
-
cancel: () => void;
|
|
68
|
-
};
|
|
46
|
+
export { debounce } from '@alaarab/ogrid-core';
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
* Shared utilities for Angular DataGridTable view layer.
|
|
3
3
|
*/
|
|
4
4
|
export type { HeaderFilterConfigInput, HeaderFilterConfig, CellRenderDescriptorInput, CellRenderDescriptor, CellRenderMode, } from './dataGridViewModel';
|
|
5
|
-
export { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, } from './dataGridViewModel';
|
|
5
|
+
export { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, } from './dataGridViewModel';
|
|
6
6
|
export { createDebouncedSignal, createDebouncedCallback, debounce, } from './debounce';
|
|
7
7
|
export { createLatestRef, createLatestCallback, } from './latestRef';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alaarab/ogrid-angular",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.8",
|
|
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",
|
|
@@ -16,13 +16,26 @@
|
|
|
16
16
|
"build": "rimraf dist && tsc -p tsconfig.build.json",
|
|
17
17
|
"test": "jest --passWithNoTests"
|
|
18
18
|
},
|
|
19
|
-
"keywords": [
|
|
19
|
+
"keywords": [
|
|
20
|
+
"ogrid",
|
|
21
|
+
"angular",
|
|
22
|
+
"datatable",
|
|
23
|
+
"typescript",
|
|
24
|
+
"grid",
|
|
25
|
+
"signals"
|
|
26
|
+
],
|
|
20
27
|
"author": "Ala Arab",
|
|
21
28
|
"license": "MIT",
|
|
22
|
-
"files": [
|
|
23
|
-
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"README.md",
|
|
32
|
+
"LICENSE"
|
|
33
|
+
],
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=18"
|
|
36
|
+
},
|
|
24
37
|
"dependencies": {
|
|
25
|
-
"@alaarab/ogrid-core": "2.0.
|
|
38
|
+
"@alaarab/ogrid-core": "2.0.7"
|
|
26
39
|
},
|
|
27
40
|
"peerDependencies": {
|
|
28
41
|
"@angular/core": "^21.0.0",
|
|
@@ -39,5 +52,7 @@
|
|
|
39
52
|
"typescript": "^5.9.3"
|
|
40
53
|
},
|
|
41
54
|
"sideEffects": false,
|
|
42
|
-
"publishConfig": {
|
|
55
|
+
"publishConfig": {
|
|
56
|
+
"access": "public"
|
|
57
|
+
}
|
|
43
58
|
}
|