@alaarab/ogrid-angular 2.0.7 → 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.
@@ -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);
@@ -65,6 +66,34 @@ export class BaseDataGridTableComponent {
65
66
  this.cellDescriptorInput = computed(() => this.state().viewModels.cellDescriptorInput);
66
67
  // Pinning state
67
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);
68
97
  this.allowOverflowX = computed(() => {
69
98
  const p = this.getProps();
70
99
  if (p?.suppressHorizontalScroll)
@@ -144,6 +173,21 @@ export class BaseDataGridTableComponent {
144
173
  const p = this.getProps();
145
174
  if (p) {
146
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);
147
191
  }
148
192
  });
149
193
  }
@@ -173,6 +217,13 @@ export class BaseDataGridTableComponent {
173
217
  resolveCellStyleFn(col, item) {
174
218
  return resolveCellStyle(col, item);
175
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
+ }
176
227
  getSelectValues(col) {
177
228
  const params = col.cellEditorParams;
178
229
  if (params && typeof params === 'object' && 'values' in params) {
@@ -188,6 +239,23 @@ export class BaseDataGridTableComponent {
188
239
  return '';
189
240
  return d.toISOString().split('T')[0];
190
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
+ }
191
259
  // --- Event handlers ---
192
260
  onWrapperMouseDown(event) {
193
261
  this.lastMouseShift = event.shiftKey;
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';
@@ -67,6 +67,7 @@ let OGridService = class OGridService {
67
67
  this.onFirstDataRendered = signal(undefined);
68
68
  this.onError = signal(undefined);
69
69
  this.columnChooserProp = signal(undefined);
70
+ this.virtualScroll = signal(undefined);
70
71
  this.ariaLabel = signal(undefined);
71
72
  this.ariaLabelledBy = signal(undefined);
72
73
  // --- Internal state signals ---
@@ -245,6 +246,7 @@ let OGridService = class OGridService {
245
246
  getUserByEmail: this.dataSource()?.getUserByEmail?.bind(this.dataSource()),
246
247
  layoutMode: this.layoutMode(),
247
248
  suppressHorizontalScroll: this.suppressHorizontalScroll(),
249
+ virtualScroll: this.virtualScroll(),
248
250
  'aria-label': this.ariaLabel(),
249
251
  'aria-labelledby': this.ariaLabelledBy(),
250
252
  emptyState: {
@@ -519,6 +521,8 @@ let OGridService = class OGridService {
519
521
  this.onError.set(props.onError);
520
522
  if (props.columnChooser !== undefined)
521
523
  this.columnChooserProp.set(props.columnChooser);
524
+ if (props.virtualScroll !== undefined)
525
+ this.virtualScroll.set(props.virtualScroll);
522
526
  if (props.entityLabelPlural !== undefined)
523
527
  this.entityLabelPlural.set(props.entityLabelPlural);
524
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. Core owns the logic; UI packages only render.
3
- * Ported from React's dataGridViewModel.ts to eliminate duplication in Angular Material and PrimeNG packages.
2
+ * View model helpers for Angular DataGridTable.
3
+ * Pure functions live in @alaarab/ogrid-core. This file re-exports them.
4
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
- }
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
- * 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
- }
67
+ // Re-export simple debounce from core
68
+ export { debounce } from '@alaarab/ogrid-core';
@@ -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
@@ -27,6 +27,7 @@ export declare abstract class BaseDataGridTableComponent<T = unknown> {
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>;
@@ -104,6 +105,14 @@ export declare abstract class BaseDataGridTableComponent<T = unknown> {
104
105
  isDragging: boolean;
105
106
  }>;
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>;
107
116
  readonly allowOverflowX: import("@angular/core").Signal<boolean>;
108
117
  readonly selectionCellCount: import("@angular/core").Signal<number | undefined>;
109
118
  readonly headerRows: import("@angular/core").Signal<import("@alaarab/ogrid-core").HeaderRow<T>[]>;
@@ -124,10 +133,16 @@ export declare abstract class BaseDataGridTableComponent<T = unknown> {
124
133
  getColumnWidth(col: IColumnDef<T>): number;
125
134
  getFilterConfig(col: IColumnDef<T>): HeaderFilterConfig;
126
135
  getCellDescriptor(item: T, col: IColumnDef<T>, rowIndex: number, colIdx: number): CellRenderDescriptor;
127
- resolveCellContent(col: IColumnDef<T>, item: T, displayValue: unknown): string;
136
+ resolveCellContent(col: IColumnDef<T>, item: T, displayValue: unknown): unknown;
128
137
  resolveCellStyleFn(col: IColumnDef<T>, item: T): Record<string, string> | undefined;
138
+ buildPopoverEditorProps(item: T, col: IColumnDef<T>, descriptor: CellRenderDescriptor): unknown;
129
139
  getSelectValues(col: IColumnDef<T>): string[];
130
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;
131
146
  onWrapperMouseDown(event: MouseEvent): void;
132
147
  onGridKeyDown(event: KeyboardEvent): void;
133
148
  onCellMouseDown(event: MouseEvent, rowIndex: number, globalColIndex: number): void;
@@ -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';
@@ -9,7 +10,6 @@ export { DataGridStateService } from './services/datagrid-state.service';
9
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';
@@ -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. */
@@ -101,6 +101,7 @@ export declare class OGridService<T> {
101
101
  readonly onFirstDataRendered: import("@angular/core").WritableSignal<(() => void) | undefined>;
102
102
  readonly onError: import("@angular/core").WritableSignal<((error: unknown) => void) | undefined>;
103
103
  readonly columnChooserProp: import("@angular/core").WritableSignal<boolean | "toolbar" | "sidebar" | undefined>;
104
+ readonly virtualScroll: import("@angular/core").WritableSignal<IVirtualScrollConfig | undefined>;
104
105
  readonly ariaLabel: import("@angular/core").WritableSignal<string | undefined>;
105
106
  readonly ariaLabelledBy: import("@angular/core").WritableSignal<string | undefined>;
106
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>)[];
@@ -57,6 +57,7 @@ interface IOGridBaseProps<T> {
57
57
  layoutMode?: 'content' | 'fill';
58
58
  suppressHorizontalScroll?: boolean;
59
59
  sideBar?: boolean | ISideBarDef;
60
+ virtualScroll?: IVirtualScrollConfig;
60
61
  pageSizeOptions?: number[];
61
62
  onFirstDataRendered?: () => void;
62
63
  onError?: (error: unknown) => void;
@@ -119,6 +120,7 @@ export interface IOGridDataGridProps<T> {
119
120
  loadingFilterOptions: Record<string, boolean>;
120
121
  peopleSearch?: (query: string) => Promise<UserLike[]>;
121
122
  getUserByEmail?: (email: string) => Promise<UserLike | undefined>;
123
+ virtualScroll?: IVirtualScrollConfig;
122
124
  emptyState?: {
123
125
  onClearAll: () => void;
124
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. Core owns the logic; UI packages only render.
3
- * Ported from React's dataGridViewModel.ts to eliminate duplication in Angular Material and PrimeNG packages.
2
+ * View model helpers for Angular DataGridTable.
3
+ * Pure functions live in @alaarab/ogrid-core. This file re-exports them.
4
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;
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.7",
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,11 +16,24 @@
16
16
  "build": "rimraf dist && tsc -p tsconfig.build.json",
17
17
  "test": "jest --passWithNoTests"
18
18
  },
19
- "keywords": ["ogrid", "angular", "datatable", "typescript", "grid", "signals"],
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": ["dist", "README.md", "LICENSE"],
23
- "engines": { "node": ">=18" },
29
+ "files": [
30
+ "dist",
31
+ "README.md",
32
+ "LICENSE"
33
+ ],
34
+ "engines": {
35
+ "node": ">=18"
36
+ },
24
37
  "dependencies": {
25
38
  "@alaarab/ogrid-core": "2.0.7"
26
39
  },
@@ -39,5 +52,7 @@
39
52
  "typescript": "^5.9.3"
40
53
  },
41
54
  "sideEffects": false,
42
- "publishConfig": { "access": "public" }
55
+ "publishConfig": {
56
+ "access": "public"
57
+ }
43
58
  }