@alaarab/ogrid-angular-material 2.0.2 → 2.0.3

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.
@@ -5,115 +5,8 @@ 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 { Component, input, computed, effect, ChangeDetectionStrategy, viewChild, } from '@angular/core';
8
- import { DataGridStateService, buildHeaderRows, getCellValue, isInSelectionRange, CHECKBOX_COLUMN_WIDTH, DEFAULT_MIN_COLUMN_WIDTH, } from '@alaarab/ogrid-angular';
8
+ import { DataGridStateService, ColumnReorderService, VirtualScrollService, buildHeaderRows, CHECKBOX_COLUMN_WIDTH, DEFAULT_MIN_COLUMN_WIDTH, getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, } from '@alaarab/ogrid-angular';
9
9
  import { ColumnHeaderFilterComponent } from '../column-header-filter/column-header-filter.component';
10
- 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
- function getCellRenderDescriptor(item, col, rowIndex, colIdx, input) {
58
- const rowId = input.getRowId(item);
59
- const globalColIndex = colIdx + input.colOffset;
60
- const colEditable = col.editable === true || (typeof col.editable === 'function' && col.editable(item));
61
- const canEditInline = input.editable !== false && !!colEditable && !!input.onCellValueChanged && typeof col.cellEditor !== 'function';
62
- const canEditPopup = input.editable !== false && !!colEditable && !!input.onCellValueChanged && typeof col.cellEditor === 'function';
63
- const canEditAny = canEditInline || canEditPopup;
64
- const isEditing = input.editingCell?.rowId === rowId && input.editingCell?.columnId === col.columnId;
65
- const isActive = input.activeCell?.rowIndex === rowIndex && input.activeCell?.columnIndex === globalColIndex;
66
- const inRange = input.selectionRange != null && isInSelectionRange(input.selectionRange, rowIndex, colIdx);
67
- const isInCutRange = input.cutRange != null && isInSelectionRange(input.cutRange, rowIndex, colIdx);
68
- const isSelectionEndCell = !input.isDragging && input.copyRange == null && input.cutRange == null &&
69
- input.selectionRange != null && rowIndex === input.selectionRange.endRow && colIdx === input.selectionRange.endCol;
70
- let mode = 'display';
71
- let editorType;
72
- const value = getCellValue(item, col);
73
- if (isEditing && canEditInline) {
74
- mode = 'editing-inline';
75
- if (col.cellEditor === 'text' || col.cellEditor === 'select' || col.cellEditor === 'checkbox' || col.cellEditor === 'richSelect' || col.cellEditor === 'date') {
76
- editorType = col.cellEditor;
77
- }
78
- else if (col.type === 'date')
79
- editorType = 'date';
80
- else if (col.type === 'boolean')
81
- editorType = 'checkbox';
82
- else
83
- editorType = 'text';
84
- }
85
- else if (isEditing && canEditPopup) {
86
- mode = 'editing-popover';
87
- }
88
- return {
89
- mode, editorType, value,
90
- isActive, isInRange: inRange, isInCutRange, isSelectionEndCell,
91
- canEditAny, globalColIndex, rowId, rowIndex, displayValue: value,
92
- };
93
- }
94
- function resolveCellDisplayContent(col, item, displayValue) {
95
- if (col.renderCell && typeof col.renderCell === 'function') {
96
- const result = col.renderCell(item);
97
- return result != null ? String(result) : '';
98
- }
99
- if (col.valueFormatter)
100
- return String(col.valueFormatter(displayValue, item) ?? '');
101
- if (displayValue == null)
102
- return '';
103
- if (col.type === 'date') {
104
- const d = new Date(String(displayValue));
105
- if (!Number.isNaN(d.getTime()))
106
- return d.toLocaleDateString();
107
- }
108
- if (col.type === 'boolean')
109
- return displayValue ? 'True' : 'False';
110
- return String(displayValue);
111
- }
112
- function resolveCellStyle(col, item) {
113
- if (!col.cellStyle)
114
- return undefined;
115
- return typeof col.cellStyle === 'function' ? col.cellStyle(item) : col.cellStyle;
116
- }
117
10
  /**
118
11
  * DataGridTable component using native HTML table with Material Design-inspired styling.
119
12
  * Standalone component — this is the workhorse of the grid.
@@ -124,6 +17,8 @@ let DataGridTableComponent = class DataGridTableComponent {
124
17
  this.wrapperRef = viewChild('wrapperEl');
125
18
  this.tableContainerRef = viewChild('tableContainerEl');
126
19
  this.stateService = new DataGridStateService();
20
+ this.columnReorderService = new ColumnReorderService();
21
+ this.virtualScrollService = new VirtualScrollService();
127
22
  this.lastMouseShift = false;
128
23
  // --- Delegated state ---
129
24
  this.state = computed(() => this.stateService.getState());
@@ -211,8 +106,28 @@ let DataGridTableComponent = class DataGridTableComponent {
211
106
  });
212
107
  effect(() => {
213
108
  const el = this.wrapperRef()?.nativeElement;
214
- if (el)
109
+ if (el) {
215
110
  this.stateService.wrapperEl.set(el);
111
+ this.columnReorderService.wrapperEl.set(el);
112
+ }
113
+ });
114
+ // Wire column reorder service inputs
115
+ effect(() => {
116
+ const p = this.propsInput();
117
+ if (p) {
118
+ const cols = this.visibleCols();
119
+ this.columnReorderService.columns.set(cols);
120
+ this.columnReorderService.columnOrder.set(p.columnOrder);
121
+ this.columnReorderService.onColumnOrderChange.set(p.onColumnOrderChange);
122
+ this.columnReorderService.enabled.set(!!p.onColumnOrderChange);
123
+ }
124
+ });
125
+ // Wire virtual scroll service inputs
126
+ effect(() => {
127
+ const p = this.propsInput();
128
+ if (p) {
129
+ this.virtualScrollService.totalRows.set(p.items.length);
130
+ }
216
131
  });
217
132
  }
218
133
  // --- Helper methods ---
@@ -351,6 +266,9 @@ let DataGridTableComponent = class DataGridTableComponent {
351
266
  onRedo() {
352
267
  this.state().interaction.onRedo?.();
353
268
  }
269
+ onHeaderMouseDown(columnId, event) {
270
+ this.columnReorderService.handleHeaderMouseDown(columnId, event);
271
+ }
354
272
  };
355
273
  DataGridTableComponent = __decorate([
356
274
  Component({
@@ -416,9 +334,12 @@ DataGridTableComponent = __decorate([
416
334
  [class.ogrid-datagrid-th--pinned-left]="col.pinned === 'left' || (isFreezeCol && colIdx === 0)"
417
335
  [class.ogrid-datagrid-th--pinned-right]="col.pinned === 'right'"
418
336
  [attr.rowSpan]="headerRows().length > 1 ? headerRows().length - rowIdx : null"
337
+ [attr.data-column-id]="col.columnId"
419
338
  [style.minWidth.px]="col.minWidth ?? 80"
420
339
  [style.width.px]="colW"
421
340
  [style.maxWidth.px]="colW"
341
+ [style.cursor]="columnReorderService.isDragging() ? 'grabbing' : 'grab'"
342
+ (mousedown)="onHeaderMouseDown(col.columnId, $event)"
422
343
  >
423
344
  <ogrid-column-header-filter
424
345
  [columnKey]="col.columnId"
@@ -586,6 +507,10 @@ DataGridTableComponent = __decorate([
586
507
  </div>
587
508
  </div>
588
509
 
510
+ @if (columnReorderService.isDragging() && columnReorderService.dropIndicatorX() !== null) {
511
+ <div class="ogrid-datagrid-drop-indicator" [style.left.px]="columnReorderService.dropIndicatorX()"></div>
512
+ }
513
+
589
514
  @if (menuPosition()) {
590
515
  <div
591
516
  class="ogrid-datagrid-context-menu-overlay"
@@ -744,6 +669,11 @@ DataGridTableComponent = __decorate([
744
669
  border-radius: 50%; animation: ogrid-spin 0.8s linear infinite;
745
670
  }
746
671
  @keyframes ogrid-spin { to { transform: rotate(360deg); } }
672
+ .ogrid-datagrid-drop-indicator {
673
+ position: absolute; top: 0; bottom: 0; width: 3px;
674
+ background: var(--ogrid-primary, #217346);
675
+ pointer-events: none; z-index: 100; transition: left 0.05s;
676
+ }
747
677
  .ogrid-datagrid-context-menu-overlay {
748
678
  position: fixed; inset: 0; z-index: 1000;
749
679
  }
@@ -1,38 +1,5 @@
1
- import type { IOGridDataGridProps, IColumnDef, RowId, ColumnFilterType, IDateFilterValue, UserLike, IFilters, FilterValue } from '@alaarab/ogrid-angular';
2
- type CellRenderMode = 'editing-inline' | 'editing-popover' | 'display';
3
- interface CellRenderDescriptor {
4
- mode: CellRenderMode;
5
- editorType?: 'text' | 'select' | 'checkbox' | 'richSelect' | 'date';
6
- value?: unknown;
7
- isActive: boolean;
8
- isInRange: boolean;
9
- isInCutRange: boolean;
10
- isSelectionEndCell: boolean;
11
- canEditAny: boolean;
12
- globalColIndex: number;
13
- rowId: RowId;
14
- rowIndex: number;
15
- displayValue?: unknown;
16
- }
17
- interface HeaderFilterConfig {
18
- columnKey: string;
19
- columnName: string;
20
- filterType: ColumnFilterType;
21
- isSorted: boolean;
22
- isSortedDescending: boolean;
23
- onSort: (() => void) | undefined;
24
- selectedValues?: string[];
25
- onFilterChange?: (values: string[]) => void;
26
- options?: string[];
27
- isLoadingOptions?: boolean;
28
- textValue?: string;
29
- onTextChange?: (value: string) => void;
30
- selectedUser?: UserLike;
31
- onUserChange?: (user: UserLike | undefined) => void;
32
- peopleSearch?: (query: string) => Promise<UserLike[]>;
33
- dateValue?: IDateFilterValue;
34
- onDateChange?: (value: IDateFilterValue | undefined) => void;
35
- }
1
+ import { ColumnReorderService, VirtualScrollService } from '@alaarab/ogrid-angular';
2
+ import type { IOGridDataGridProps, IColumnDef, RowId, CellRenderDescriptor, HeaderFilterConfig } from '@alaarab/ogrid-angular';
36
3
  /**
37
4
  * DataGridTable component using native HTML table with Material Design-inspired styling.
38
5
  * Standalone component — this is the workhorse of the grid.
@@ -42,6 +9,8 @@ export declare class DataGridTableComponent<T> {
42
9
  private readonly wrapperRef;
43
10
  private readonly tableContainerRef;
44
11
  private readonly stateService;
12
+ readonly columnReorderService: ColumnReorderService<T>;
13
+ readonly virtualScrollService: VirtualScrollService;
45
14
  private lastMouseShift;
46
15
  constructor();
47
16
  private readonly state;
@@ -95,11 +64,11 @@ export declare class DataGridTableComponent<T> {
95
64
  sortBy?: string;
96
65
  sortDirection: "asc" | "desc";
97
66
  onColumnSort: (columnKey: string) => void;
98
- filters: IFilters;
99
- onFilterChange: (key: string, value: FilterValue | undefined) => void;
67
+ filters: import("@alaarab/ogrid-angular").IFilters;
68
+ onFilterChange: (key: string, value: import("@alaarab/ogrid-angular").FilterValue | undefined) => void;
100
69
  filterOptions: Record<string, string[]>;
101
70
  loadingFilterOptions: Record<string, boolean>;
102
- peopleSearch?: (query: string) => Promise<UserLike[]>;
71
+ peopleSearch?: (query: string) => Promise<import("@alaarab/ogrid-angular").UserLike[]>;
103
72
  }>;
104
73
  readonly cellDescriptorInput: import("@angular/core").Signal<{
105
74
  editingCell: {
@@ -157,5 +126,5 @@ export declare class DataGridTableComponent<T> {
157
126
  handleSelectAllCells(): void;
158
127
  onUndo(): void;
159
128
  onRedo(): void;
129
+ onHeaderMouseDown(columnId: string, event: MouseEvent): void;
160
130
  }
161
- export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alaarab/ogrid-angular-material",
3
- "version": "2.0.2",
3
+ "version": "2.0.3",
4
4
  "description": "OGrid Angular Material – MatTable-based data grid with sorting, filtering, pagination, column chooser, and CSV export.",
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-angular": "2.0.2"
25
+ "@alaarab/ogrid-angular": "2.0.3"
26
26
  },
27
27
  "peerDependencies": {
28
28
  "@angular/core": "^21.0.0",