@alaarab/ogrid-angular 2.0.3 → 2.0.5

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