@alaarab/ogrid-angular 2.0.23 → 2.1.1

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.
Files changed (33) hide show
  1. package/dist/esm/components/base-column-header-filter.component.js +26 -11
  2. package/dist/esm/components/base-datagrid-table.component.js +64 -46
  3. package/dist/esm/components/base-ogrid.component.js +36 -0
  4. package/dist/esm/components/base-popover-cell-editor.component.js +6 -7
  5. package/dist/esm/components/empty-state.component.js +2 -2
  6. package/dist/esm/components/grid-context-menu.component.js +17 -17
  7. package/dist/esm/components/ogrid-layout.component.js +2 -49
  8. package/dist/esm/components/sidebar.component.js +2 -6
  9. package/dist/esm/components/status-bar.component.js +6 -2
  10. package/dist/esm/index.js +5 -1
  11. package/dist/esm/services/datagrid-editing.service.js +52 -0
  12. package/dist/esm/services/datagrid-interaction.service.js +667 -0
  13. package/dist/esm/services/datagrid-layout.service.js +151 -0
  14. package/dist/esm/services/datagrid-state.service.js +130 -865
  15. package/dist/esm/services/ogrid.service.js +61 -26
  16. package/dist/esm/utils/index.js +1 -1
  17. package/dist/esm/utils/latestRef.js +0 -5
  18. package/dist/types/components/base-column-header-filter.component.d.ts +2 -0
  19. package/dist/types/components/base-datagrid-table.component.d.ts +21 -5
  20. package/dist/types/components/base-ogrid.component.d.ts +10 -0
  21. package/dist/types/components/grid-context-menu.component.d.ts +5 -5
  22. package/dist/types/components/sidebar.component.d.ts +0 -2
  23. package/dist/types/components/status-bar.component.d.ts +4 -1
  24. package/dist/types/index.d.ts +5 -1
  25. package/dist/types/services/datagrid-editing.service.d.ts +31 -0
  26. package/dist/types/services/datagrid-interaction.service.d.ts +86 -0
  27. package/dist/types/services/datagrid-layout.service.d.ts +36 -0
  28. package/dist/types/services/datagrid-state.service.d.ts +20 -39
  29. package/dist/types/services/ogrid.service.d.ts +8 -3
  30. package/dist/types/types/dataGridTypes.d.ts +1 -1
  31. package/dist/types/utils/index.d.ts +1 -1
  32. package/dist/types/utils/latestRef.d.ts +0 -5
  33. package/package.json +10 -3
@@ -5,7 +5,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
7
  import { Injectable, signal, computed, effect, DestroyRef, inject } from '@angular/core';
8
- import { mergeFilter, deriveFilterOptionsFromData, getMultiSelectFilterFields, flattenColumns, processClientSideData, computeNextSortState, } from '@alaarab/ogrid-core';
8
+ import { mergeFilter, deriveFilterOptionsFromData, getMultiSelectFilterFields, flattenColumns, processClientSideData, computeNextSortState, validateColumns, validateRowIds, } from '@alaarab/ogrid-core';
9
9
  const DEFAULT_PAGE_SIZE = 25;
10
10
  const EMPTY_LOADING_OPTIONS = {};
11
11
  const DEFAULT_PANELS = ['columns', 'filters'];
@@ -86,9 +86,10 @@ let OGridService = class OGridService {
86
86
  this.serverItems = signal([]);
87
87
  this.serverTotalCount = signal(0);
88
88
  this.serverLoading = signal(true);
89
- this.fetchId = 0;
89
+ this.fetchAbortController = null;
90
+ this.filterAbortController = null;
90
91
  this.refreshCounter = signal(0);
91
- this.firstDataRendered = false;
92
+ this.firstDataRendered = signal(false);
92
93
  // Side bar state
93
94
  this.sideBarActivePanel = signal(null);
94
95
  // Filter options state
@@ -109,10 +110,12 @@ let OGridService = class OGridService {
109
110
  });
110
111
  this.filters = computed(() => this.controlledFilters() ?? this.internalFilters());
111
112
  this.visibleColumns = computed(() => {
112
- if (this.controlledVisibleColumns())
113
- return this.controlledVisibleColumns();
114
- if (this.internalVisibleColumnsOverride())
115
- return this.internalVisibleColumnsOverride();
113
+ const controlled = this.controlledVisibleColumns();
114
+ if (controlled)
115
+ return controlled;
116
+ const override = this.internalVisibleColumnsOverride();
117
+ if (override)
118
+ return override;
116
119
  const cols = this.columns();
117
120
  if (cols.length === 0)
118
121
  return new Set();
@@ -196,8 +199,8 @@ let OGridService = class OGridService {
196
199
  .map((c) => ({
197
200
  columnId: c.columnId,
198
201
  name: c.name,
199
- filterField: c.filterable.filterField ?? c.columnId,
200
- filterType: c.filterable.type,
202
+ filterField: c.filterable?.filterField ?? c.columnId,
203
+ filterType: c.filterable?.type,
201
204
  })));
202
205
  this.sideBarState = computed(() => ({
203
206
  isEnabled: this.sideBarEnabled(),
@@ -217,6 +220,9 @@ let OGridService = class OGridService {
217
220
  this.handleSelectionChangeFn = (event) => this.handleSelectionChange(event);
218
221
  this.handleFilterChangeFn = (key, value) => this.handleFilterChange(key, value);
219
222
  this.clearAllFiltersFn = () => this.setFilters({});
223
+ this.setPageFn = (p) => this.setPage(p);
224
+ this.setPageSizeFn = (size) => this.setPageSize(size);
225
+ this.handleVisibilityChangeFn = (columnKey, isVisible) => this.handleVisibilityChange(columnKey, isVisible);
220
226
  // --- Data grid props computed ---
221
227
  this.dataGridProps = computed(() => ({
222
228
  items: this.displayItems(),
@@ -270,15 +276,15 @@ let OGridService = class OGridService {
270
276
  page: this.page(),
271
277
  pageSize: this.pageSize(),
272
278
  displayTotalCount: this.displayTotalCount(),
273
- setPage: (p) => this.setPage(p),
274
- setPageSize: (size) => this.setPageSize(size),
279
+ setPage: this.setPageFn,
280
+ setPageSize: this.setPageSizeFn,
275
281
  pageSizeOptions: this.pageSizeOptions(),
276
282
  entityLabelPlural: this.entityLabelPlural(),
277
283
  }));
278
284
  this.columnChooser = computed(() => ({
279
285
  columns: this.columnChooserColumns(),
280
286
  visibleColumns: this.visibleColumns(),
281
- onVisibilityChange: (columnKey, isVisible) => this.handleVisibilityChange(columnKey, isVisible),
287
+ onVisibilityChange: this.handleVisibilityChangeFn,
282
288
  placement: this.columnChooserPlacement(),
283
289
  }));
284
290
  this.filtersResult = computed(() => ({
@@ -304,8 +310,17 @@ let OGridService = class OGridService {
304
310
  filterOptions: this.clientFilterOptions(),
305
311
  };
306
312
  });
307
- // Server-side data fetching effect
313
+ // Validate columns once (on first non-empty columns signal)
314
+ let columnsValidated = false;
308
315
  effect(() => {
316
+ const cols = this.columns();
317
+ if (!columnsValidated && cols.length > 0) {
318
+ columnsValidated = true;
319
+ validateColumns(cols);
320
+ }
321
+ });
322
+ // Server-side data fetching effect
323
+ effect((onCleanup) => {
309
324
  const ds = this.dataSource();
310
325
  if (!this.isServerSide() || !ds) {
311
326
  if (!this.isServerSide())
@@ -318,36 +333,46 @@ let OGridService = class OGridService {
318
333
  const filters = this.filters();
319
334
  // Read refreshCounter to trigger re-fetches
320
335
  this.refreshCounter();
321
- const id = ++this.fetchId;
336
+ const controller = new AbortController();
337
+ this.fetchAbortController = controller;
322
338
  this.serverLoading.set(true);
323
339
  ds.fetchPage({ page, pageSize, sort: { field: sort.field, direction: sort.direction }, filters })
324
340
  .then((res) => {
325
- if (id !== this.fetchId)
341
+ if (controller.signal.aborted)
326
342
  return;
327
343
  this.serverItems.set(res.items);
328
344
  this.serverTotalCount.set(res.totalCount);
329
345
  })
330
346
  .catch((err) => {
331
- if (id !== this.fetchId)
347
+ if (controller.signal.aborted)
332
348
  return;
333
349
  this.onError()?.(err);
334
350
  this.serverItems.set([]);
335
351
  this.serverTotalCount.set(0);
336
352
  })
337
353
  .finally(() => {
338
- if (id === this.fetchId)
354
+ if (!controller.signal.aborted)
339
355
  this.serverLoading.set(false);
340
356
  });
357
+ onCleanup(() => {
358
+ controller.abort();
359
+ });
341
360
  });
342
- // Fire onFirstDataRendered once
361
+ // Fire onFirstDataRendered once; also validate row IDs on first data
362
+ let rowIdsValidated = false;
343
363
  effect(() => {
344
- if (!this.firstDataRendered && this.displayItems().length > 0) {
345
- this.firstDataRendered = true;
364
+ const items = this.displayItems();
365
+ if (!this.firstDataRendered() && items.length > 0) {
366
+ this.firstDataRendered.set(true);
346
367
  this.onFirstDataRendered()?.();
347
368
  }
369
+ if (!rowIdsValidated && items.length > 0) {
370
+ rowIdsValidated = true;
371
+ validateRowIds(items, this.getRowId());
372
+ }
348
373
  });
349
374
  // Load server filter options
350
- effect(() => {
375
+ effect((onCleanup) => {
351
376
  const ds = this.dataSource();
352
377
  const fields = this.multiSelectFilterFields();
353
378
  const fetcher = ds && 'fetchFilterOptions' in ds && typeof ds.fetchFilterOptions === 'function'
@@ -358,6 +383,8 @@ let OGridService = class OGridService {
358
383
  this.loadingFilterOptions.set({});
359
384
  return;
360
385
  }
386
+ const controller = new AbortController();
387
+ this.filterAbortController = controller;
361
388
  const loading = {};
362
389
  fields.forEach((f) => { loading[f] = true; });
363
390
  this.loadingFilterOptions.set(loading);
@@ -370,9 +397,14 @@ let OGridService = class OGridService {
370
397
  results[field] = [];
371
398
  }
372
399
  })).then(() => {
400
+ if (controller.signal.aborted)
401
+ return;
373
402
  this.serverFilterOptions.set(results);
374
403
  this.loadingFilterOptions.set({});
375
404
  });
405
+ onCleanup(() => {
406
+ controller.abort();
407
+ });
376
408
  });
377
409
  // Initialize sidebar default panel
378
410
  effect(() => {
@@ -381,8 +413,12 @@ let OGridService = class OGridService {
381
413
  this.sideBarActivePanel.set(parsed.defaultPanel);
382
414
  }
383
415
  });
384
- // Cleanup on destroy — reset callback signals to prevent closure retention
416
+ // Cleanup on destroy — abort in-flight requests and reset callback signals
385
417
  this.destroyRef.onDestroy(() => {
418
+ this.fetchAbortController?.abort();
419
+ this.filterAbortController?.abort();
420
+ this.fetchAbortController = null;
421
+ this.filterAbortController = null;
386
422
  this.onPageChange.set(undefined);
387
423
  this.onPageSizeChange.set(undefined);
388
424
  this.onSortChange.set(undefined);
@@ -576,8 +612,7 @@ let OGridService = class OGridService {
576
612
  */
577
613
  unpinColumn(columnId) {
578
614
  this.pinnedOverrides.update((prev) => {
579
- const next = { ...prev };
580
- delete next[columnId];
615
+ const { [columnId]: _, ...next } = prev;
581
616
  return next;
582
617
  });
583
618
  this.onColumnPinned()?.(columnId, null);
@@ -641,8 +676,8 @@ let OGridService = class OGridService {
641
676
  this.setVisibleColumns(new Set(state.visibleColumns));
642
677
  if (state.sort)
643
678
  this.setSort(state.sort);
644
- if (state.columnOrder && this.onColumnOrderChange())
645
- this.onColumnOrderChange()(state.columnOrder);
679
+ if (state.columnOrder)
680
+ this.onColumnOrderChange()?.(state.columnOrder);
646
681
  if (state.columnWidths)
647
682
  this.columnWidthOverrides.set(state.columnWidths);
648
683
  if (state.filters)
@@ -5,4 +5,4 @@ export { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayConte
5
5
  // Debounce utilities
6
6
  export { createDebouncedSignal, createDebouncedCallback, debounce, } from './debounce';
7
7
  // Latest ref utilities
8
- export { createLatestRef, createLatestCallback, } from './latestRef';
8
+ export { createLatestCallback, } from './latestRef';
@@ -39,8 +39,3 @@ export function createLatestCallback(fn) {
39
39
  return fn()(...args);
40
40
  });
41
41
  }
42
- /**
43
- * Alias for createLatestCallback for consistency with React/Vue naming.
44
- * @deprecated Use createLatestCallback instead
45
- */
46
- export const createLatestRef = createLatestCallback;
@@ -86,5 +86,7 @@ export declare abstract class BaseColumnHeaderFilterComponent {
86
86
  handleClearUser(): void;
87
87
  handleDateApply(): void;
88
88
  handleDateClear(): void;
89
+ /** Clean up debounce timer on destroy. */
90
+ ngOnDestroy(): void;
89
91
  onDocumentClick(event: MouseEvent, selectorName: string): void;
90
92
  }
@@ -20,7 +20,7 @@ export declare abstract class BaseDataGridTableComponent<T = unknown> {
20
20
  protected lastMouseShift: boolean;
21
21
  readonly columnSizingVersion: import("@angular/core").WritableSignal<number>;
22
22
  /** Dirty flag — set when column layout changes, cleared after measurement. */
23
- private measureDirty;
23
+ private readonly measureDirty;
24
24
  /** DOM-measured column widths from the last layout pass.
25
25
  * Used as a minWidth floor to prevent columns from shrinking
26
26
  * when new data loads (e.g. server-side pagination). */
@@ -41,8 +41,14 @@ export declare abstract class BaseDataGridTableComponent<T = unknown> {
41
41
  * Only updates the signal when values actually change, to avoid render loops. */
42
42
  private measureColumnWidths;
43
43
  readonly state: import("@angular/core").Signal<import("../services/datagrid-state.service").DataGridStateResult<T>>;
44
+ protected readonly layoutState: import("@angular/core").Signal<import("../services/datagrid-state.service").DataGridLayoutState<T>>;
45
+ protected readonly rowSelectionState: import("@angular/core").Signal<import("../services/datagrid-state.service").DataGridRowSelectionState>;
46
+ protected readonly editingState: import("@angular/core").Signal<import("../services/datagrid-state.service").DataGridEditingState<T>>;
47
+ protected readonly interactionState: import("@angular/core").Signal<import("../services/datagrid-state.service").DataGridCellInteractionState>;
48
+ protected readonly contextMenuState: import("@angular/core").Signal<import("../services/datagrid-state.service").DataGridContextMenuState>;
49
+ protected readonly viewModelsState: import("@angular/core").Signal<import("../services/datagrid-state.service").DataGridViewModelState<T>>;
50
+ protected readonly pinningState: import("@angular/core").Signal<import("../services/datagrid-state.service").DataGridPinningState>;
44
51
  readonly tableContainerEl: import("@angular/core").Signal<HTMLElement | null>;
45
- readonly allItems: import("@angular/core").Signal<T[]>;
46
52
  readonly items: import("@angular/core").Signal<T[]>;
47
53
  readonly getRowId: import("@angular/core").Signal<(item: T) => RowId>;
48
54
  readonly isLoading: import("@angular/core").Signal<boolean>;
@@ -55,7 +61,7 @@ export declare abstract class BaseDataGridTableComponent<T = unknown> {
55
61
  onClearAll: () => void;
56
62
  hasActiveFilters: boolean;
57
63
  message?: string;
58
- render?: import("@angular/core").TemplateRef<unknown>;
64
+ render?: unknown;
59
65
  } | undefined>;
60
66
  readonly currentPage: import("@angular/core").Signal<number>;
61
67
  readonly pageSize: import("@angular/core").Signal<number>;
@@ -144,8 +150,18 @@ export declare abstract class BaseDataGridTableComponent<T = unknown> {
144
150
  rightOffsets: Record<string, number>;
145
151
  }>;
146
152
  /**
147
- * Initialize base wiring effects. Must be called from subclass constructor
148
- * (effects need to run inside an injection context).
153
+ * Initialize base wiring effects. Must be called from subclass constructor.
154
+ *
155
+ * **Timing:** Angular requires `effect()` to be created inside an injection
156
+ * context (constructor or field initializer). On the first run, signals like
157
+ * `wrapperElSignal()` return `null` because the DOM hasn't been created yet.
158
+ * After `ngAfterViewInit` sets these signals, Angular's signal graph
159
+ * automatically re-runs each effect. The null guards inside each effect body
160
+ * ensure the first (null) run is a safe no-op.
161
+ *
162
+ * Sequence:
163
+ * 1. Constructor → `initBase()` → effects created, first run (signals null → no-ops)
164
+ * 2. `ngAfterViewInit` → `wrapperElSignal.set(el)` → effects re-run with real values
149
165
  */
150
166
  protected initBase(): void;
151
167
  /** Lookup effective min-width for a column (includes measured width floor) */
@@ -0,0 +1,10 @@
1
+ import { OGridService } from '../services/ogrid.service';
2
+ import type { IOGridProps } from '../types';
3
+ export declare abstract class BaseOGridComponent<T> {
4
+ private readonly propsSignal;
5
+ readonly ogridService: OGridService<T>;
6
+ set props(value: IOGridProps<T>);
7
+ constructor();
8
+ get showToolbar(): boolean;
9
+ onPageSizeChange(size: number): void;
10
+ }
@@ -14,13 +14,13 @@ export declare class GridContextMenuComponent {
14
14
  contextMenuItemShortcut?: string;
15
15
  contextMenuDivider?: string;
16
16
  } | undefined;
17
- copy: EventEmitter<void>;
18
- cut: EventEmitter<void>;
19
- paste: EventEmitter<void>;
20
- selectAll: EventEmitter<void>;
17
+ copyAction: EventEmitter<void>;
18
+ cutAction: EventEmitter<void>;
19
+ pasteAction: EventEmitter<void>;
20
+ selectAllAction: EventEmitter<void>;
21
21
  undoAction: EventEmitter<void>;
22
22
  redoAction: EventEmitter<void>;
23
- close: EventEmitter<void>;
23
+ closeAction: EventEmitter<void>;
24
24
  menuRef?: ElementRef<HTMLDivElement>;
25
25
  readonly menuItems: import("@alaarab/ogrid-core").GridContextMenuItem[];
26
26
  readonly formatShortcutFn: typeof formatShortcut;
@@ -23,8 +23,6 @@ export interface SideBarProps {
23
23
  export declare class SideBarComponent {
24
24
  sideBarProps: SideBarProps | null;
25
25
  readonly panelLabels: Record<SideBarPanelId, string>;
26
- readonly tabWidth = 36;
27
- readonly panelWidth = 240;
28
26
  onTabClick(panel: SideBarPanelId): void;
29
27
  allVisible(): boolean;
30
28
  onSelectAll(): void;
@@ -1,4 +1,5 @@
1
- export declare class StatusBarComponent {
1
+ import { OnChanges } from '@angular/core';
2
+ export declare class StatusBarComponent implements OnChanges {
2
3
  totalCount: number;
3
4
  filteredCount: number | undefined;
4
5
  selectedCount: number | undefined;
@@ -17,5 +18,7 @@ export declare class StatusBarComponent {
17
18
  statusBarLabel?: string;
18
19
  statusBarValue?: string;
19
20
  } | undefined;
21
+ private cachedParts;
22
+ ngOnChanges(): void;
20
23
  getParts(): import("@alaarab/ogrid-core").StatusBarPart[];
21
24
  }
@@ -8,6 +8,9 @@ export { OGridService } from './services/ogrid.service';
8
8
  export type { ColumnChooserPlacement, OGridPagination, OGridColumnChooser, OGridFilters, OGridSideBarState, } from './services/ogrid.service';
9
9
  export { DataGridStateService } from './services/datagrid-state.service';
10
10
  export type { DataGridLayoutState, DataGridRowSelectionState, DataGridEditingState, DataGridCellInteractionState, DataGridContextMenuState, DataGridViewModelState, DataGridPinningState, DataGridStateResult, } from './services/datagrid-state.service';
11
+ export { DataGridLayoutHelper } from './services/datagrid-layout.service';
12
+ export { DataGridEditingHelper } from './services/datagrid-editing.service';
13
+ export { DataGridInteractionHelper } from './services/datagrid-interaction.service';
11
14
  export { ColumnReorderService } from './services/column-reorder.service';
12
15
  export { VirtualScrollService } from './services/virtual-scroll.service';
13
16
  export { OGridLayoutComponent } from './components/ogrid-layout.component';
@@ -17,6 +20,7 @@ export { SideBarComponent } from './components/sidebar.component';
17
20
  export type { SideBarProps, SideBarFilterColumn } from './components/sidebar.component';
18
21
  export { MarchingAntsOverlayComponent } from './components/marching-ants-overlay.component';
19
22
  export { EmptyStateComponent } from './components/empty-state.component';
23
+ export { BaseOGridComponent } from './components/base-ogrid.component';
20
24
  export { BaseDataGridTableComponent } from './components/base-datagrid-table.component';
21
25
  export { BaseColumnHeaderFilterComponent } from './components/base-column-header-filter.component';
22
26
  export type { IColumnHeaderFilterProps } from './components/base-column-header-filter.component';
@@ -28,4 +32,4 @@ export { INLINE_CELL_EDITOR_TEMPLATE, INLINE_CELL_EDITOR_STYLES } from './compon
28
32
  export { BasePopoverCellEditorComponent, POPOVER_CELL_EDITOR_TEMPLATE, POPOVER_CELL_EDITOR_OVERLAY_STYLES } from './components/base-popover-cell-editor.component';
29
33
  export { OGRID_THEME_VARS_CSS } from './styles/ogrid-theme-vars';
30
34
  export type { HeaderFilterConfigInput, HeaderFilterConfig, CellRenderDescriptorInput, CellRenderDescriptor, CellRenderMode, } from './utils';
31
- export { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, createDebouncedSignal, createDebouncedCallback, debounce, createLatestRef, createLatestCallback, } from './utils';
35
+ export { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, createDebouncedSignal, createDebouncedCallback, debounce, createLatestCallback, } from './utils';
@@ -0,0 +1,31 @@
1
+ import type { RowId, IActiveCell, ICellValueChangedEvent } from '../types';
2
+ import type { IColumnDef as IAngularColumnDef } from '../types';
3
+ type IColumnDef<T> = IAngularColumnDef<T>;
4
+ /**
5
+ * Manages cell editing state, inline/popover editor, and commit/cancel logic.
6
+ * Extracted from DataGridStateService for modularity.
7
+ *
8
+ * Not @Injectable — instantiated and owned by DataGridStateService.
9
+ */
10
+ export declare class DataGridEditingHelper<T> {
11
+ readonly editingCellSig: import("@angular/core").WritableSignal<{
12
+ rowId: RowId;
13
+ columnId: string;
14
+ } | null>;
15
+ readonly pendingEditorValueSig: import("@angular/core").WritableSignal<unknown>;
16
+ readonly popoverAnchorElSig: import("@angular/core").WritableSignal<HTMLElement | null>;
17
+ /** Injected dependencies */
18
+ private getVisibleCols;
19
+ private getItems;
20
+ private getWrappedOnCellValueChanged;
21
+ private setActiveCellFn;
22
+ constructor(getVisibleCols: () => IColumnDef<T>[], getItems: () => T[], getWrappedOnCellValueChanged: () => ((event: ICellValueChangedEvent<T>) => void) | undefined, setActiveCellFn: (cell: IActiveCell | null) => void);
23
+ setEditingCell(cell: {
24
+ rowId: RowId;
25
+ columnId: string;
26
+ } | null): void;
27
+ setPendingEditorValue(value: unknown): void;
28
+ commitCellEdit(item: T, columnId: string, oldValue: unknown, newValue: unknown, rowIndex: number, globalColIndex: number): void;
29
+ cancelPopoverEdit(): void;
30
+ }
31
+ export {};
@@ -0,0 +1,86 @@
1
+ import { UndoRedoStack } from '@alaarab/ogrid-core';
2
+ import type { RowId, IActiveCell, ISelectionRange, ICellValueChangedEvent } from '../types';
3
+ import type { IColumnDef as IAngularColumnDef } from '../types';
4
+ type IColumnDef<T> = IAngularColumnDef<T>;
5
+ /**
6
+ * Manages cell selection, keyboard navigation, clipboard, fill handle, and undo/redo.
7
+ * Extracted from DataGridStateService for modularity.
8
+ *
9
+ * Not @Injectable — instantiated and owned by DataGridStateService.
10
+ */
11
+ export declare class DataGridInteractionHelper<T> {
12
+ readonly activeCellSig: import("@angular/core").WritableSignal<IActiveCell | null>;
13
+ readonly selectionRangeSig: import("@angular/core").WritableSignal<ISelectionRange | null>;
14
+ readonly isDraggingSig: import("@angular/core").WritableSignal<boolean>;
15
+ readonly contextMenuPositionSig: import("@angular/core").WritableSignal<{
16
+ x: number;
17
+ y: number;
18
+ } | null>;
19
+ readonly cutRangeSig: import("@angular/core").WritableSignal<ISelectionRange | null>;
20
+ readonly copyRangeSig: import("@angular/core").WritableSignal<ISelectionRange | null>;
21
+ private internalClipboard;
22
+ readonly undoRedoStack: UndoRedoStack<ICellValueChangedEvent<T>>;
23
+ readonly undoLengthSig: import("@angular/core").WritableSignal<number>;
24
+ readonly redoLengthSig: import("@angular/core").WritableSignal<number>;
25
+ readonly canUndo: import("@angular/core").Signal<boolean>;
26
+ readonly canRedo: import("@angular/core").Signal<boolean>;
27
+ readonly hasCellSelection: import("@angular/core").Signal<boolean>;
28
+ fillDragStart: {
29
+ startRow: number;
30
+ startCol: number;
31
+ } | null;
32
+ fillRafId: number;
33
+ fillMoveHandler: ((e: MouseEvent) => void) | null;
34
+ fillUpHandler: (() => void) | null;
35
+ dragStartPos: {
36
+ row: number;
37
+ col: number;
38
+ } | null;
39
+ dragMoved: boolean;
40
+ isDraggingRef: boolean;
41
+ liveDragRange: ISelectionRange | null;
42
+ rafId: number;
43
+ lastMousePos: {
44
+ cx: number;
45
+ cy: number;
46
+ } | null;
47
+ autoScrollInterval: ReturnType<typeof setInterval> | null;
48
+ setActiveCell(cell: IActiveCell | null): void;
49
+ setSelectionRange(range: ISelectionRange | null): void;
50
+ setContextMenuPosition(pos: {
51
+ x: number;
52
+ y: number;
53
+ } | null): void;
54
+ handleCellContextMenu(e: {
55
+ clientX: number;
56
+ clientY: number;
57
+ preventDefault?: () => void;
58
+ }): void;
59
+ closeContextMenu(): void;
60
+ handleCopy(items: T[], visibleCols: IColumnDef<T>[], colOffset: number): void;
61
+ handleCut(items: T[], visibleCols: IColumnDef<T>[], colOffset: number, editable: boolean | undefined, wrappedOnCellValueChanged: ((event: ICellValueChangedEvent<T>) => void) | undefined): void;
62
+ handlePaste(items: T[], visibleCols: IColumnDef<T>[], colOffset: number, editable: boolean | undefined, wrappedOnCellValueChanged: ((event: ICellValueChangedEvent<T>) => void) | undefined): Promise<void>;
63
+ clearClipboardRanges(): void;
64
+ beginBatch(): void;
65
+ endBatch(): void;
66
+ undo(originalOnCellValueChanged: ((event: ICellValueChangedEvent<T>) => void) | undefined): void;
67
+ redo(originalOnCellValueChanged: ((event: ICellValueChangedEvent<T>) => void) | undefined): void;
68
+ handleCellMouseDown(e: MouseEvent, rowIndex: number, globalColIndex: number, colOffset: number, wrapperEl: HTMLElement | null): void;
69
+ handleSelectAllCells(rowCount: number, visibleColCount: number, colOffset: number): void;
70
+ handleFillHandleMouseDown(e: MouseEvent): void;
71
+ handleGridKeyDown(e: KeyboardEvent, items: T[], getRowId: (item: T) => RowId, visibleCols: IColumnDef<T>[], colOffset: number, hasCheckboxCol: boolean, visibleColumnCount: number, editable: boolean | undefined, wrappedOnCellValueChanged: ((event: ICellValueChangedEvent<T>) => void) | undefined, originalOnCellValueChanged: ((event: ICellValueChangedEvent<T>) => void) | undefined, rowSelection: string, selectedRowIds: Set<RowId>, wrapperEl: HTMLElement | null, handleRowCheckboxChange: (rowId: RowId, checked: boolean, rowIndex: number, shiftKey: boolean) => void, editingCell: {
72
+ rowId: RowId;
73
+ columnId: string;
74
+ } | null, setEditingCell: (cell: {
75
+ rowId: RowId;
76
+ columnId: string;
77
+ } | null) => void): void;
78
+ onWindowMouseMove(e: MouseEvent, colOffset: number, wrapperEl: HTMLElement | null): void;
79
+ onWindowMouseUp(colOffset: number, wrapperEl: HTMLElement | null): void;
80
+ resolveRangeFromMouse(cx: number, cy: number, colOffset: number): ISelectionRange | null;
81
+ applyDragAttrs(range: ISelectionRange, colOff: number, wrapper: HTMLElement | null): void;
82
+ clearDragAttrs(wrapper: HTMLElement | null): void;
83
+ getEffectiveRange(colOffset: number): ISelectionRange | null;
84
+ destroy(): void;
85
+ }
86
+ export {};
@@ -0,0 +1,36 @@
1
+ import { signal, computed, NgZone } from '@angular/core';
2
+ import type { RowId } from '../types';
3
+ import type { IColumnDef as IAngularColumnDef } from '../types';
4
+ import type { IOGridDataGridProps } from '../types';
5
+ type IColumnDef<T> = IAngularColumnDef<T>;
6
+ /**
7
+ * Manages column layout, visibility, sizing, and container measurement.
8
+ * Extracted from DataGridStateService for modularity.
9
+ *
10
+ * Not @Injectable — instantiated and owned by DataGridStateService.
11
+ */
12
+ export declare class DataGridLayoutHelper<T> {
13
+ readonly props: ReturnType<typeof signal<IOGridDataGridProps<T> | null>>;
14
+ readonly wrapperEl: ReturnType<typeof signal<HTMLElement | null>>;
15
+ readonly containerWidthSig: import("@angular/core").WritableSignal<number>;
16
+ readonly columnSizingOverridesSig: import("@angular/core").WritableSignal<Record<string, {
17
+ widthPx: number;
18
+ }>>;
19
+ private resizeObserver;
20
+ private readonly initialColumnWidthsSig;
21
+ readonly flatColumnsRaw: ReturnType<typeof computed<IColumnDef<T>[]>>;
22
+ readonly flatColumns: ReturnType<typeof computed<IColumnDef<T>[]>>;
23
+ readonly visibleCols: ReturnType<typeof computed<IColumnDef<T>[]>>;
24
+ readonly visibleColumnCount: ReturnType<typeof computed<number>>;
25
+ readonly hasCheckboxCol: ReturnType<typeof computed<boolean>>;
26
+ readonly hasRowNumbersCol: ReturnType<typeof computed<boolean>>;
27
+ readonly specialColsCount: ReturnType<typeof computed<number>>;
28
+ readonly totalColCount: ReturnType<typeof computed<number>>;
29
+ readonly colOffset: ReturnType<typeof computed<number>>;
30
+ readonly rowIndexByRowId: ReturnType<typeof computed<Map<RowId, number>>>;
31
+ readonly minTableWidth: ReturnType<typeof computed<number>>;
32
+ readonly desiredTableWidth: ReturnType<typeof computed<number>>;
33
+ constructor(props: ReturnType<typeof signal<IOGridDataGridProps<T> | null>>, wrapperEl: ReturnType<typeof signal<HTMLElement | null>>, ngZone: NgZone);
34
+ destroy(): void;
35
+ }
36
+ export {};
@@ -1,6 +1,9 @@
1
1
  import type { RowId, IActiveCell, ISelectionRange, IStatusBarProps, IFilters, FilterValue, UserLike, ICellValueChangedEvent } from '../types';
2
2
  import type { IColumnDef as IAngularColumnDef } from '../types';
3
3
  import type { IOGridDataGridProps } from '../types';
4
+ import { DataGridLayoutHelper } from './datagrid-layout.service';
5
+ import { DataGridEditingHelper } from './datagrid-editing.service';
6
+ import { DataGridInteractionHelper } from './datagrid-interaction.service';
4
7
  type IColumnDef<T> = IAngularColumnDef<T>;
5
8
  export interface DataGridLayoutState<T> {
6
9
  flatColumns: IColumnDef<T>[];
@@ -50,14 +53,17 @@ export interface DataGridEditingState<T> {
50
53
  }
51
54
  export interface DataGridCellInteractionState {
52
55
  activeCell: IActiveCell | null;
53
- setActiveCell: (cell: IActiveCell | null) => void;
56
+ /** Set active cell. Undefined when cell selection is disabled. */
57
+ setActiveCell?: (cell: IActiveCell | null) => void;
54
58
  selectionRange: ISelectionRange | null;
55
- setSelectionRange: (range: ISelectionRange | null) => void;
59
+ /** Set selection range. Undefined when cell selection is disabled. */
60
+ setSelectionRange?: (range: ISelectionRange | null) => void;
56
61
  handleCellMouseDown: (e: MouseEvent, rowIndex: number, globalColIndex: number) => void;
57
62
  handleSelectAllCells: () => void;
58
63
  hasCellSelection: boolean;
59
64
  handleGridKeyDown: (e: KeyboardEvent) => void;
60
- handleFillHandleMouseDown: (e: MouseEvent) => void;
65
+ /** Handle fill handle mouse down. Undefined when cell selection is disabled. */
66
+ handleFillHandleMouseDown?: (e: MouseEvent) => void;
61
67
  handleCopy: () => void;
62
68
  handleCut: () => void;
63
69
  handlePaste: () => Promise<void>;
@@ -75,7 +81,8 @@ export interface DataGridContextMenuState {
75
81
  x: number;
76
82
  y: number;
77
83
  } | null;
78
- setMenuPosition: (pos: {
84
+ /** Set menu position. Undefined when cell selection is disabled. */
85
+ setMenuPosition?: (pos: {
79
86
  x: number;
80
87
  y: number;
81
88
  } | null) => void;
@@ -157,46 +164,24 @@ export declare class DataGridStateService<T> {
157
164
  private ngZone;
158
165
  readonly props: import("@angular/core").WritableSignal<IOGridDataGridProps<T> | null>;
159
166
  readonly wrapperEl: import("@angular/core").WritableSignal<HTMLElement | null>;
160
- private readonly editingCellSig;
161
- private readonly pendingEditorValueSig;
162
- private readonly activeCellSig;
163
- private readonly selectionRangeSig;
164
- private readonly isDraggingSig;
165
- private readonly contextMenuPositionSig;
167
+ /** Layout helper: column layout, visibility, sizing, container measurement. */
168
+ readonly layoutHelper: DataGridLayoutHelper<T>;
169
+ /** Editing helper: cell editing state, commit/cancel logic. */
170
+ readonly editingHelper: DataGridEditingHelper<T>;
171
+ /** Interaction helper: cell selection, keyboard nav, clipboard, fill handle, undo/redo. */
172
+ readonly interactionHelper: DataGridInteractionHelper<T>;
166
173
  private readonly internalSelectedRows;
167
- private readonly popoverAnchorElSig;
168
- private readonly containerWidthSig;
169
- private readonly columnSizingOverridesSig;
170
- private readonly cutRangeSig;
171
- private readonly copyRangeSig;
172
- private internalClipboard;
173
- private readonly undoRedoStack;
174
- private readonly undoLengthSig;
175
- private readonly redoLengthSig;
176
- private fillDragStart;
177
- private fillRafId;
178
- private fillMoveHandler;
179
- private fillUpHandler;
180
174
  private lastClickedRow;
181
- private dragStartPos;
182
- private dragMoved;
183
- private isDraggingRef;
184
- private liveDragRange;
185
- private rafId;
186
- private lastMousePos;
187
- private autoScrollInterval;
188
- private resizeObserver;
189
175
  private readonly headerMenuIsOpenSig;
190
176
  private readonly headerMenuOpenForColumnSig;
191
177
  private readonly headerMenuAnchorElementSig;
192
178
  private readonly propsResolved;
193
179
  readonly cellSelection: import("@angular/core").Signal<boolean>;
194
180
  private readonly originalOnCellValueChanged;
195
- private readonly initialColumnWidthsSig;
196
181
  private readonly wrappedOnCellValueChanged;
197
- readonly flatColumnsRaw: import("@angular/core").Signal<IColumnDef<T>[]>;
198
- readonly flatColumns: import("@angular/core").Signal<IColumnDef<T>[]>;
199
- readonly visibleCols: import("@angular/core").Signal<IColumnDef<T>[]>;
182
+ readonly flatColumnsRaw: import("@angular/core").Signal<IAngularColumnDef<T>[]>;
183
+ readonly flatColumns: import("@angular/core").Signal<IAngularColumnDef<T>[]>;
184
+ readonly visibleCols: import("@angular/core").Signal<IAngularColumnDef<T>[]>;
200
185
  readonly visibleColumnCount: import("@angular/core").Signal<number>;
201
186
  readonly hasCheckboxCol: import("@angular/core").Signal<boolean>;
202
187
  readonly hasRowNumbersCol: import("@angular/core").Signal<boolean>;
@@ -271,12 +256,8 @@ export declare class DataGridStateService<T> {
271
256
  headerMenuPinRight(): void;
272
257
  headerMenuUnpin(): void;
273
258
  getState(): DataGridStateResult<T>;
274
- private getEffectiveRange;
275
259
  private onWindowMouseMove;
276
260
  private onWindowMouseUp;
277
- private resolveRangeFromMouse;
278
- private applyDragAttrs;
279
- private clearDragAttrs;
280
261
  private setupFillHandleDrag;
281
262
  }
282
263
  export {};