@alaarab/ogrid-angular 2.0.2 → 2.0.4
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/marching-ants-overlay.component.js +2 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/services/column-reorder.service.js +180 -0
- package/dist/esm/services/datagrid-state.service.js +18 -12
- package/dist/esm/services/ogrid.service.js +88 -37
- package/dist/esm/services/virtual-scroll.service.js +91 -0
- package/dist/esm/utils/dataGridViewModel.js +163 -0
- package/dist/esm/utils/debounce.js +105 -0
- package/dist/esm/utils/index.js +8 -0
- package/dist/esm/utils/latestRef.js +46 -0
- package/dist/types/components/marching-ants-overlay.component.d.ts +1 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/services/column-reorder.service.d.ts +30 -0
- package/dist/types/services/datagrid-state.service.d.ts +4 -2
- package/dist/types/services/ogrid.service.d.ts +29 -6
- package/dist/types/services/virtual-scroll.service.d.ts +54 -0
- package/dist/types/types/dataGridTypes.d.ts +3 -0
- package/dist/types/utils/dataGridViewModel.d.ts +107 -0
- package/dist/types/utils/debounce.d.ts +68 -0
- package/dist/types/utils/index.d.ts +7 -0
- package/dist/types/utils/latestRef.d.ts +42 -0
- package/package.json +2 -2
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* View model helpers for Angular DataGridTable. Core owns the logic; UI packages only render.
|
|
3
|
+
* Ported from React's dataGridViewModel.ts to eliminate duplication in Angular Material and PrimeNG packages.
|
|
4
|
+
*/
|
|
5
|
+
import { getCellValue, isInSelectionRange } from '@alaarab/ogrid-core';
|
|
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
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Debounce utilities for Angular using signals.
|
|
3
|
+
* Provides functional parity with React's useDebounce and useDebouncedCallback.
|
|
4
|
+
*/
|
|
5
|
+
import { signal, effect } from '@angular/core';
|
|
6
|
+
/**
|
|
7
|
+
* Creates a debounced signal that updates after the specified delay when the source value changes.
|
|
8
|
+
*
|
|
9
|
+
* @param source - The signal to debounce
|
|
10
|
+
* @param delayMs - Delay in milliseconds
|
|
11
|
+
* @returns A signal containing the debounced value
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* const searchQuery = signal('');
|
|
16
|
+
* const debouncedQuery = createDebouncedSignal(searchQuery, 300);
|
|
17
|
+
*
|
|
18
|
+
* effect(() => {
|
|
19
|
+
* console.log('Debounced search:', debouncedQuery());
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export function createDebouncedSignal(source, delayMs) {
|
|
24
|
+
const debouncedValue = signal(source());
|
|
25
|
+
effect((onCleanup) => {
|
|
26
|
+
const currentValue = source();
|
|
27
|
+
const timeoutId = setTimeout(() => {
|
|
28
|
+
debouncedValue.set(currentValue);
|
|
29
|
+
}, delayMs);
|
|
30
|
+
onCleanup(() => clearTimeout(timeoutId));
|
|
31
|
+
});
|
|
32
|
+
return debouncedValue;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Creates a debounced function that delays invoking the provided function
|
|
36
|
+
* until after `delayMs` milliseconds have elapsed since the last time it was invoked.
|
|
37
|
+
*
|
|
38
|
+
* @param fn - The function to debounce
|
|
39
|
+
* @param delayMs - Delay in milliseconds
|
|
40
|
+
* @returns A debounced version of the function
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* const saveData = (value: string) => {
|
|
45
|
+
* console.log('Saving:', value);
|
|
46
|
+
* };
|
|
47
|
+
*
|
|
48
|
+
* const debouncedSave = createDebouncedCallback(saveData, 500);
|
|
49
|
+
*
|
|
50
|
+
* // Multiple rapid calls will only trigger once after 500ms
|
|
51
|
+
* debouncedSave('hello');
|
|
52
|
+
* debouncedSave('world'); // Only this will execute after 500ms
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export function createDebouncedCallback(fn, delayMs) {
|
|
56
|
+
let timeoutId = null;
|
|
57
|
+
return ((...args) => {
|
|
58
|
+
if (timeoutId !== null) {
|
|
59
|
+
clearTimeout(timeoutId);
|
|
60
|
+
}
|
|
61
|
+
timeoutId = setTimeout(() => {
|
|
62
|
+
fn(...args);
|
|
63
|
+
timeoutId = null;
|
|
64
|
+
}, delayMs);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Simple debounce function (non-Angular-specific, can be used anywhere).
|
|
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
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for Angular DataGridTable view layer.
|
|
3
|
+
*/
|
|
4
|
+
export { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, } from './dataGridViewModel';
|
|
5
|
+
// Debounce utilities
|
|
6
|
+
export { createDebouncedSignal, createDebouncedCallback, debounce, } from './debounce';
|
|
7
|
+
// Latest ref utilities
|
|
8
|
+
export { createLatestRef, createLatestCallback, } from './latestRef';
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Latest ref utility for Angular using signals.
|
|
3
|
+
* Provides functional parity with React's useLatestRef.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Creates a stable wrapper function that always calls the latest version of the provided function.
|
|
7
|
+
* Useful for event handlers and callbacks where you want a stable reference but need to call
|
|
8
|
+
* the latest implementation.
|
|
9
|
+
*
|
|
10
|
+
* @param fn - Signal containing the function to wrap
|
|
11
|
+
* @returns A stable function that always invokes the latest version
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* class MyService {
|
|
16
|
+
* readonly onSave = signal<(value: string) => void>((val) => console.log('Default:', val));
|
|
17
|
+
* readonly stableOnSave = createLatestCallback(this.onSave);
|
|
18
|
+
*
|
|
19
|
+
* constructor() {
|
|
20
|
+
* // Setup event listener with stable reference
|
|
21
|
+
* effect((onCleanup) => {
|
|
22
|
+
* // stableOnSave never changes, so this effect only runs once
|
|
23
|
+
* const callback = () => this.stableOnSave('data');
|
|
24
|
+
* window.addEventListener('click', callback);
|
|
25
|
+
* onCleanup(() => window.removeEventListener('click', callback));
|
|
26
|
+
* });
|
|
27
|
+
* }
|
|
28
|
+
*
|
|
29
|
+
* updateHandler(newFn: (value: string) => void) {
|
|
30
|
+
* // Even though we change the function, the callback reference stays stable
|
|
31
|
+
* this.onSave.set(newFn);
|
|
32
|
+
* }
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export function createLatestCallback(fn) {
|
|
37
|
+
// Return a stable function that always calls the current value of the signal
|
|
38
|
+
return ((...args) => {
|
|
39
|
+
return fn()(...args);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Alias for createLatestCallback for consistency with React/Vue naming.
|
|
44
|
+
* @deprecated Use createLatestCallback instead
|
|
45
|
+
*/
|
|
46
|
+
export const createLatestRef = createLatestCallback;
|
|
@@ -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;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -7,6 +7,9 @@ export { OGridService } from './services/ogrid.service';
|
|
|
7
7
|
export type { ColumnChooserPlacement, OGridPagination, OGridColumnChooser, OGridFilters, OGridSideBarState, } from './services/ogrid.service';
|
|
8
8
|
export { DataGridStateService } from './services/datagrid-state.service';
|
|
9
9
|
export type { DataGridLayoutState, DataGridRowSelectionState, DataGridEditingState, DataGridCellInteractionState, DataGridContextMenuState, DataGridViewModelState, DataGridStateResult, } from './services/datagrid-state.service';
|
|
10
|
+
export { ColumnReorderService } from './services/column-reorder.service';
|
|
11
|
+
export { VirtualScrollService } from './services/virtual-scroll.service';
|
|
12
|
+
export type { IVirtualScrollConfig } from './services/virtual-scroll.service';
|
|
10
13
|
export { OGridLayoutComponent } from './components/ogrid-layout.component';
|
|
11
14
|
export { StatusBarComponent } from './components/status-bar.component';
|
|
12
15
|
export { GridContextMenuComponent } from './components/grid-context-menu.component';
|
|
@@ -14,3 +17,5 @@ export { SideBarComponent } from './components/sidebar.component';
|
|
|
14
17
|
export type { SideBarProps, SideBarFilterColumn } from './components/sidebar.component';
|
|
15
18
|
export { MarchingAntsOverlayComponent } from './components/marching-ants-overlay.component';
|
|
16
19
|
export { EmptyStateComponent } from './components/empty-state.component';
|
|
20
|
+
export type { HeaderFilterConfigInput, HeaderFilterConfig, CellRenderDescriptorInput, CellRenderDescriptor, CellRenderMode, } from './utils';
|
|
21
|
+
export { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, createDebouncedSignal, createDebouncedCallback, debounce, createLatestRef, createLatestCallback, } from './utils';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { IColumnDef } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Manages column reorder drag interactions with RAF-throttled updates.
|
|
4
|
+
* Angular signals-based port of React's useColumnReorder hook.
|
|
5
|
+
*/
|
|
6
|
+
export declare class ColumnReorderService<T> {
|
|
7
|
+
private destroyRef;
|
|
8
|
+
readonly columns: import("@angular/core").WritableSignal<IColumnDef<T>[]>;
|
|
9
|
+
readonly columnOrder: import("@angular/core").WritableSignal<string[] | undefined>;
|
|
10
|
+
readonly onColumnOrderChange: import("@angular/core").WritableSignal<((order: string[]) => void) | undefined>;
|
|
11
|
+
readonly enabled: import("@angular/core").WritableSignal<boolean>;
|
|
12
|
+
readonly wrapperEl: import("@angular/core").WritableSignal<HTMLElement | null>;
|
|
13
|
+
readonly isDragging: import("@angular/core").WritableSignal<boolean>;
|
|
14
|
+
readonly dropIndicatorX: import("@angular/core").WritableSignal<number | null>;
|
|
15
|
+
private rafId;
|
|
16
|
+
private cleanupFn;
|
|
17
|
+
private latestDropTargetIndex;
|
|
18
|
+
constructor();
|
|
19
|
+
/**
|
|
20
|
+
* Call this from the header cell's mousedown handler.
|
|
21
|
+
* @param columnId - The column being dragged
|
|
22
|
+
* @param event - The native MouseEvent
|
|
23
|
+
*/
|
|
24
|
+
handleHeaderMouseDown(columnId: string, event: MouseEvent): void;
|
|
25
|
+
/**
|
|
26
|
+
* Calculate drop target from mouse position and header cell rects.
|
|
27
|
+
* Same logic as React's useColumnReorder inline calculation.
|
|
28
|
+
*/
|
|
29
|
+
private calculateDrop;
|
|
30
|
+
}
|
|
@@ -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;
|
|
@@ -159,7 +160,6 @@ export declare class DataGridStateService<T> {
|
|
|
159
160
|
private rafId;
|
|
160
161
|
private lastMousePos;
|
|
161
162
|
private autoScrollInterval;
|
|
162
|
-
private windowCleanups;
|
|
163
163
|
private resizeObserver;
|
|
164
164
|
private readonly propsResolved;
|
|
165
165
|
readonly cellSelection: import("@angular/core").Signal<boolean>;
|
|
@@ -169,8 +169,10 @@ export declare class DataGridStateService<T> {
|
|
|
169
169
|
readonly visibleCols: import("@angular/core").Signal<IColumnDef<T>[]>;
|
|
170
170
|
readonly visibleColumnCount: import("@angular/core").Signal<number>;
|
|
171
171
|
readonly hasCheckboxCol: import("@angular/core").Signal<boolean>;
|
|
172
|
+
readonly hasRowNumbersCol: import("@angular/core").Signal<boolean>;
|
|
173
|
+
readonly specialColsCount: import("@angular/core").Signal<number>;
|
|
172
174
|
readonly totalColCount: import("@angular/core").Signal<number>;
|
|
173
|
-
readonly colOffset: import("@angular/core").Signal<
|
|
175
|
+
readonly colOffset: import("@angular/core").Signal<number>;
|
|
174
176
|
readonly rowIndexByRowId: import("@angular/core").Signal<Map<RowId, number>>;
|
|
175
177
|
readonly selectedRowIds: import("@angular/core").Signal<Set<RowId>>;
|
|
176
178
|
readonly allSelected: import("@angular/core").Signal<boolean>;
|
|
@@ -105,10 +105,10 @@ export declare class OGridService<T> {
|
|
|
105
105
|
private readonly internalData;
|
|
106
106
|
private readonly internalLoading;
|
|
107
107
|
private readonly internalPage;
|
|
108
|
-
private readonly
|
|
109
|
-
private readonly
|
|
108
|
+
private readonly internalPageSizeOverride;
|
|
109
|
+
private readonly internalSortOverride;
|
|
110
110
|
private readonly internalFilters;
|
|
111
|
-
private readonly
|
|
111
|
+
private readonly internalVisibleColumnsOverride;
|
|
112
112
|
private readonly internalSelectedRows;
|
|
113
113
|
private readonly columnWidthOverrides;
|
|
114
114
|
private readonly pinnedOverrides;
|
|
@@ -121,9 +121,6 @@ export declare class OGridService<T> {
|
|
|
121
121
|
private readonly sideBarActivePanel;
|
|
122
122
|
private readonly serverFilterOptions;
|
|
123
123
|
private readonly loadingFilterOptions;
|
|
124
|
-
private sortInitialized;
|
|
125
|
-
private visibleColumnsInitialized;
|
|
126
|
-
private pageSizeInitialized;
|
|
127
124
|
readonly columns: import("@angular/core").Signal<IColumnDef<T>[]>;
|
|
128
125
|
readonly isServerSide: import("@angular/core").Signal<boolean>;
|
|
129
126
|
readonly isClientSide: import("@angular/core").Signal<boolean>;
|
|
@@ -187,5 +184,31 @@ export declare class OGridService<T> {
|
|
|
187
184
|
handleColumnResized(columnId: string, width: number): void;
|
|
188
185
|
handleColumnPinned(columnId: string, pinned: 'left' | 'right' | null): void;
|
|
189
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>;
|
|
190
213
|
getApi(): IOGridApi<T>;
|
|
191
214
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
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
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Manages virtual scrolling state using Angular signals.
|
|
12
|
+
* Port of React's useVirtualScroll hook.
|
|
13
|
+
*
|
|
14
|
+
* Uses core's pure-TS `computeVisibleRange` and `getScrollTopForRow` utilities.
|
|
15
|
+
* The UI layer (Angular Material / PrimeNG) provides the scrollable container
|
|
16
|
+
* and calls `onScroll()` / sets `containerHeight`.
|
|
17
|
+
*/
|
|
18
|
+
export declare class VirtualScrollService {
|
|
19
|
+
private destroyRef;
|
|
20
|
+
readonly totalRows: import("@angular/core").WritableSignal<number>;
|
|
21
|
+
readonly config: import("@angular/core").WritableSignal<IVirtualScrollConfig>;
|
|
22
|
+
readonly containerHeight: import("@angular/core").WritableSignal<number>;
|
|
23
|
+
readonly scrollTop: import("@angular/core").WritableSignal<number>;
|
|
24
|
+
private containerEl;
|
|
25
|
+
readonly rowHeight: import("@angular/core").Signal<number>;
|
|
26
|
+
readonly overscan: import("@angular/core").Signal<number>;
|
|
27
|
+
readonly enabled: import("@angular/core").Signal<boolean>;
|
|
28
|
+
/** Whether virtual scrolling is actually active (enabled + enough rows). */
|
|
29
|
+
readonly isActive: import("@angular/core").Signal<boolean>;
|
|
30
|
+
/** The visible range of rows with spacer offsets. */
|
|
31
|
+
readonly visibleRange: import("@angular/core").Signal<IVisibleRange>;
|
|
32
|
+
/** Total scrollable height in pixels. */
|
|
33
|
+
readonly totalHeight: import("@angular/core").Signal<number>;
|
|
34
|
+
constructor();
|
|
35
|
+
/**
|
|
36
|
+
* Set the scrollable container element.
|
|
37
|
+
* Used for programmatic scrolling (scrollToRow).
|
|
38
|
+
*/
|
|
39
|
+
setContainer(el: HTMLElement | null): void;
|
|
40
|
+
/**
|
|
41
|
+
* Call this from the container's scroll event handler.
|
|
42
|
+
*/
|
|
43
|
+
onScroll(event: Event): void;
|
|
44
|
+
/**
|
|
45
|
+
* Scroll to a specific row index.
|
|
46
|
+
* @param index - The row index to scroll to.
|
|
47
|
+
* @param align - Where to position the row: 'start' (top), 'center', or 'end' (bottom). Default: 'start'.
|
|
48
|
+
*/
|
|
49
|
+
scrollToRow(index: number, align?: 'start' | 'center' | 'end'): void;
|
|
50
|
+
/**
|
|
51
|
+
* Update the virtual scroll configuration.
|
|
52
|
+
*/
|
|
53
|
+
updateConfig(updates: Partial<IVirtualScrollConfig>): void;
|
|
54
|
+
}
|
|
@@ -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;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* View model helpers for Angular DataGridTable. Core owns the logic; UI packages only render.
|
|
3
|
+
* Ported from React's dataGridViewModel.ts to eliminate duplication in Angular Material and PrimeNG packages.
|
|
4
|
+
*/
|
|
5
|
+
import type { ColumnFilterType, IDateFilterValue } from '../types/columnTypes';
|
|
6
|
+
import type { IColumnDef } from '../types/columnTypes';
|
|
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;
|