@alaarab/ogrid-angular-radix 2.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/README.md +76 -0
  2. package/dist/esm/column-chooser/column-chooser.component.js +199 -0
  3. package/dist/esm/column-header-filter/column-header-filter.component.js +497 -0
  4. package/dist/esm/datagrid-table/datagrid-table.component.js +573 -0
  5. package/dist/esm/index.js +14 -0
  6. package/dist/esm/ogrid/ogrid.component.js +77 -0
  7. package/dist/esm/pagination-controls/pagination-controls.component.js +189 -0
  8. package/dist/types/column-chooser/column-chooser.component.d.ts +26 -0
  9. package/dist/types/column-header-filter/column-header-filter.component.d.ts +67 -0
  10. package/dist/types/datagrid-table/datagrid-table.component.d.ts +131 -0
  11. package/dist/types/index.d.ts +12 -0
  12. package/dist/types/ogrid/ogrid.component.d.ts +14 -0
  13. package/dist/types/pagination-controls/pagination-controls.component.d.ts +15 -0
  14. package/jest-mocks/angular-cdk-overlay.cjs.js +38 -0
  15. package/jest-mocks/style-mock.js +1 -0
  16. package/jest.config.js +43 -0
  17. package/package.json +37 -0
  18. package/scripts/compile-styles.js +53 -0
  19. package/src/__tests__/column-chooser.component.spec.ts.skip +195 -0
  20. package/src/__tests__/column-header-filter.component.spec.ts.skip +401 -0
  21. package/src/__tests__/datagrid-table.component.spec.ts.skip +417 -0
  22. package/src/__tests__/exports.test.ts +54 -0
  23. package/src/__tests__/ogrid.component.spec.ts.skip +236 -0
  24. package/src/__tests__/pagination-controls.component.spec.ts.skip +190 -0
  25. package/src/column-chooser/column-chooser.component.ts +204 -0
  26. package/src/column-header-filter/column-header-filter.component.ts +528 -0
  27. package/src/datagrid-table/datagrid-table.component.scss +289 -0
  28. package/src/datagrid-table/datagrid-table.component.ts +636 -0
  29. package/src/index.ts +16 -0
  30. package/src/ogrid/ogrid.component.ts +78 -0
  31. package/src/pagination-controls/pagination-controls.component.ts +187 -0
  32. package/tsconfig.build.json +9 -0
  33. package/tsconfig.json +21 -0
@@ -0,0 +1,573 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
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
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { Component, input, signal, computed, effect, ChangeDetectionStrategy, viewChild, } from '@angular/core';
8
+ import { DataGridStateService, ColumnReorderService, VirtualScrollService, StatusBarComponent, GridContextMenuComponent, MarchingAntsOverlayComponent, EmptyStateComponent, buildHeaderRows, DEFAULT_MIN_COLUMN_WIDTH, getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, } from '@alaarab/ogrid-angular';
9
+ import { ColumnHeaderFilterComponent } from '../column-header-filter/column-header-filter.component';
10
+ /**
11
+ * DataGridTable component for Angular Radix using native HTML table.
12
+ * Standalone component with lightweight styling and CSS variables.
13
+ */
14
+ let DataGridTableComponent = class DataGridTableComponent {
15
+ constructor() {
16
+ this.propsInput = input.required({ alias: 'props' });
17
+ this.wrapperRef = viewChild('wrapperEl');
18
+ this.tableContainerRef = viewChild('tableContainerEl');
19
+ this.stateService = new DataGridStateService();
20
+ this.columnReorderService = new ColumnReorderService();
21
+ this.virtualScrollService = new VirtualScrollService();
22
+ this.lastMouseShift = false;
23
+ this.columnSizingVersion = signal(0);
24
+ // --- Delegated state ---
25
+ this.state = computed(() => this.stateService.getState());
26
+ this.items = computed(() => this.propsInput()?.items ?? []);
27
+ this.getRowId = computed(() => this.propsInput()?.getRowId ?? ((item) => item['id']));
28
+ this.isLoading = computed(() => this.propsInput()?.isLoading ?? false);
29
+ this.loadingMessage = computed(() => 'Loading\u2026');
30
+ this.freezeRows = computed(() => this.propsInput()?.freezeRows);
31
+ this.freezeCols = computed(() => this.propsInput()?.freezeCols);
32
+ this.layoutModeFit = computed(() => (this.propsInput()?.layoutMode ?? 'fill') === 'content');
33
+ this.ariaLabel = computed(() => this.propsInput()?.['aria-label'] ?? 'Data grid');
34
+ this.ariaLabelledBy = computed(() => this.propsInput()?.['aria-labelledby']);
35
+ this.emptyState = computed(() => this.propsInput()?.emptyState);
36
+ // State service outputs
37
+ this.visibleCols = computed(() => this.state().layout.visibleCols);
38
+ this.hasCheckboxCol = computed(() => this.state().layout.hasCheckboxCol);
39
+ this.colOffset = computed(() => this.state().layout.colOffset);
40
+ this.containerWidth = computed(() => this.state().layout.containerWidth);
41
+ this.minTableWidth = computed(() => this.state().layout.minTableWidth);
42
+ this.desiredTableWidth = computed(() => this.state().layout.desiredTableWidth);
43
+ this.columnSizingOverrides = computed(() => this.state().layout.columnSizingOverrides);
44
+ this.selectedRowIds = computed(() => this.state().rowSelection.selectedRowIds);
45
+ this.allSelected = computed(() => this.state().rowSelection.allSelected);
46
+ this.someSelected = computed(() => this.state().rowSelection.someSelected);
47
+ this.editingCell = computed(() => this.state().editing.editingCell);
48
+ this.pendingEditorValue = computed(() => this.state().editing.pendingEditorValue);
49
+ this.activeCell = computed(() => this.state().interaction.activeCell);
50
+ this.selectionRange = computed(() => this.state().interaction.selectionRange);
51
+ this.hasCellSelection = computed(() => this.state().interaction.hasCellSelection);
52
+ this.cutRange = computed(() => this.state().interaction.cutRange);
53
+ this.copyRange = computed(() => this.state().interaction.copyRange);
54
+ this.canUndo = computed(() => this.state().interaction.canUndo);
55
+ this.canRedo = computed(() => this.state().interaction.canRedo);
56
+ this.isDragging = computed(() => this.state().interaction.isDragging);
57
+ this.menuPosition = computed(() => this.state().contextMenu.menuPosition);
58
+ this.statusBarConfig = computed(() => this.state().viewModels.statusBarConfig);
59
+ this.showEmptyInGrid = computed(() => this.state().viewModels.showEmptyInGrid);
60
+ this.headerFilterInput = computed(() => this.state().viewModels.headerFilterInput);
61
+ this.cellDescriptorInput = computed(() => this.state().viewModels.cellDescriptorInput);
62
+ this.allowOverflowX = computed(() => {
63
+ const p = this.propsInput();
64
+ if (p?.suppressHorizontalScroll)
65
+ return false;
66
+ const cw = this.containerWidth();
67
+ const mtw = this.minTableWidth();
68
+ const dtw = this.desiredTableWidth();
69
+ return cw > 0 && (mtw > cw || dtw > cw);
70
+ });
71
+ this.selectionCellCount = computed(() => {
72
+ const sr = this.selectionRange();
73
+ if (!sr)
74
+ return undefined;
75
+ return (Math.abs(sr.endRow - sr.startRow) + 1) * (Math.abs(sr.endCol - sr.startCol) + 1);
76
+ });
77
+ // Header rows from column definition
78
+ this.headerRows = computed(() => {
79
+ const p = this.propsInput();
80
+ if (!p)
81
+ return [];
82
+ return buildHeaderRows(p.columns, p.visibleColumns);
83
+ });
84
+ // Pre-computed column layouts
85
+ this.columnLayouts = computed(() => {
86
+ const cols = this.visibleCols();
87
+ const fc = this.freezeCols();
88
+ return cols.map((col, colIdx) => {
89
+ const isFreezeCol = fc != null && fc >= 1 && colIdx < fc;
90
+ const pinnedLeft = col.pinned === 'left' || (isFreezeCol && colIdx === 0);
91
+ const pinnedRight = col.pinned === 'right';
92
+ const w = this.getColumnWidth(col);
93
+ return {
94
+ col,
95
+ pinnedLeft,
96
+ pinnedRight,
97
+ minWidth: col.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH,
98
+ width: w,
99
+ };
100
+ });
101
+ });
102
+ // Wire props and wrapper element to state service
103
+ effect(() => {
104
+ const p = this.propsInput();
105
+ if (p)
106
+ this.stateService.props.set(p);
107
+ });
108
+ effect(() => {
109
+ const el = this.wrapperRef()?.nativeElement;
110
+ if (el) {
111
+ this.stateService.wrapperEl.set(el);
112
+ this.columnReorderService.wrapperEl.set(el);
113
+ }
114
+ });
115
+ // Wire column reorder service inputs
116
+ effect(() => {
117
+ const p = this.propsInput();
118
+ if (p) {
119
+ const cols = this.visibleCols();
120
+ this.columnReorderService.columns.set(cols);
121
+ this.columnReorderService.columnOrder.set(p.columnOrder);
122
+ this.columnReorderService.onColumnOrderChange.set(p.onColumnOrderChange);
123
+ this.columnReorderService.enabled.set(!!p.onColumnOrderChange);
124
+ }
125
+ });
126
+ // Wire virtual scroll service inputs
127
+ effect(() => {
128
+ const p = this.propsInput();
129
+ if (p) {
130
+ this.virtualScrollService.totalRows.set(p.items.length);
131
+ }
132
+ });
133
+ }
134
+ // --- Helper methods ---
135
+ asColumnDef(colDef) {
136
+ return colDef;
137
+ }
138
+ visibleColIndex(col) {
139
+ return this.visibleCols().indexOf(col);
140
+ }
141
+ getColumnWidth(col) {
142
+ const overrides = this.columnSizingOverrides();
143
+ const override = overrides[col.columnId];
144
+ if (override)
145
+ return override.widthPx;
146
+ return col.defaultWidth ?? col.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH;
147
+ }
148
+ getFilterConfig(col) {
149
+ return getHeaderFilterConfig(col, this.headerFilterInput());
150
+ }
151
+ getCellDescriptor(item, col, rowIndex, colIdx) {
152
+ return getCellRenderDescriptor(item, col, rowIndex, colIdx, this.cellDescriptorInput());
153
+ }
154
+ resolveCellContent(col, item, displayValue) {
155
+ return resolveCellDisplayContent(col, item, displayValue);
156
+ }
157
+ resolveCellStyleFn(col, item) {
158
+ return resolveCellStyle(col, item);
159
+ }
160
+ getSelectValues(col) {
161
+ const params = col.cellEditorParams;
162
+ if (params && typeof params === 'object' && 'values' in params) {
163
+ return params.values.map(String);
164
+ }
165
+ return [];
166
+ }
167
+ formatDateForInput(value) {
168
+ if (!value)
169
+ return '';
170
+ const d = new Date(String(value));
171
+ if (Number.isNaN(d.getTime()))
172
+ return '';
173
+ return d.toISOString().split('T')[0];
174
+ }
175
+ // --- Event handlers ---
176
+ onWrapperMouseDown(event) {
177
+ this.lastMouseShift = event.shiftKey;
178
+ }
179
+ onGridKeyDown(event) {
180
+ this.state().interaction.handleGridKeyDown(event);
181
+ }
182
+ onCellMouseDown(event, rowIndex, globalColIndex) {
183
+ this.state().interaction.handleCellMouseDown(event, rowIndex, globalColIndex);
184
+ }
185
+ onCellClick(rowIndex, globalColIndex) {
186
+ this.state().interaction.setActiveCell({ rowIndex, columnIndex: globalColIndex });
187
+ }
188
+ onCellContextMenu(event) {
189
+ this.state().contextMenu.handleCellContextMenu(event);
190
+ }
191
+ onCellDblClick(rowId, columnId) {
192
+ this.state().editing.setEditingCell({ rowId, columnId });
193
+ }
194
+ onFillHandleMouseDown(event) {
195
+ this.state().interaction.handleFillHandleMouseDown(event);
196
+ }
197
+ onResizeStart(event, col) {
198
+ event.preventDefault();
199
+ const startX = event.clientX;
200
+ const startWidth = this.getColumnWidth(col);
201
+ const minWidth = col.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH;
202
+ const onMove = (e) => {
203
+ const delta = e.clientX - startX;
204
+ const newWidth = Math.max(minWidth, startWidth + delta);
205
+ const overrides = { ...this.columnSizingOverrides(), [col.columnId]: { widthPx: newWidth } };
206
+ this.state().layout.setColumnSizingOverrides(overrides);
207
+ this.columnSizingVersion.update(v => v + 1);
208
+ };
209
+ const onUp = () => {
210
+ window.removeEventListener('mousemove', onMove);
211
+ window.removeEventListener('mouseup', onUp);
212
+ const finalWidth = this.getColumnWidth(col);
213
+ this.state().layout.onColumnResized?.(col.columnId, finalWidth);
214
+ };
215
+ window.addEventListener('mousemove', onMove);
216
+ window.addEventListener('mouseup', onUp);
217
+ }
218
+ onSelectAllChange(event) {
219
+ const checked = event.target.checked;
220
+ this.state().rowSelection.handleSelectAll(!!checked);
221
+ }
222
+ onRowClick(event, rowId) {
223
+ const p = this.propsInput();
224
+ if (p?.rowSelection !== 'single')
225
+ return;
226
+ const ids = this.selectedRowIds();
227
+ this.state().rowSelection.updateSelection(ids.has(rowId) ? new Set() : new Set([rowId]));
228
+ }
229
+ onRowCheckboxChange(rowId, event, rowIndex) {
230
+ const checked = event.target.checked;
231
+ this.state().rowSelection.handleRowCheckboxChange(rowId, checked, rowIndex, this.lastMouseShift);
232
+ }
233
+ commitEdit(item, columnId, oldValue, newValue, rowIndex, globalColIndex) {
234
+ this.state().editing.commitCellEdit(item, columnId, oldValue, newValue, rowIndex, globalColIndex);
235
+ }
236
+ cancelEdit() {
237
+ this.state().editing.setEditingCell(null);
238
+ }
239
+ onEditorKeydown(event, item, columnId, oldValue, rowIndex, globalColIndex) {
240
+ if (event.key === 'Enter') {
241
+ event.preventDefault();
242
+ const newValue = event.target.value;
243
+ this.commitEdit(item, columnId, oldValue, newValue, rowIndex, globalColIndex);
244
+ }
245
+ else if (event.key === 'Escape') {
246
+ event.preventDefault();
247
+ this.cancelEdit();
248
+ }
249
+ }
250
+ closeContextMenu() {
251
+ this.state().contextMenu.closeContextMenu();
252
+ }
253
+ handleCopy() {
254
+ this.state().interaction.handleCopy();
255
+ }
256
+ handleCut() {
257
+ this.state().interaction.handleCut();
258
+ }
259
+ handlePaste() {
260
+ void this.state().interaction.handlePaste();
261
+ }
262
+ handleSelectAllCells() {
263
+ this.state().interaction.handleSelectAllCells();
264
+ }
265
+ onUndo() {
266
+ this.state().interaction.onUndo?.();
267
+ }
268
+ onRedo() {
269
+ this.state().interaction.onRedo?.();
270
+ }
271
+ onHeaderMouseDown(columnId, event) {
272
+ this.columnReorderService.handleHeaderMouseDown(columnId, event);
273
+ }
274
+ };
275
+ DataGridTableComponent = __decorate([
276
+ Component({
277
+ selector: 'ogrid-datagrid-table',
278
+ standalone: true,
279
+ imports: [ColumnHeaderFilterComponent, StatusBarComponent, GridContextMenuComponent, MarchingAntsOverlayComponent, EmptyStateComponent],
280
+ providers: [DataGridStateService],
281
+ changeDetection: ChangeDetectionStrategy.OnPush,
282
+ styleUrl: './datagrid-table.component.scss',
283
+ template: `
284
+ <div class="ogrid-datagrid-root">
285
+ <div
286
+ #wrapperEl
287
+ class="ogrid-datagrid-wrapper"
288
+ [class.ogrid-datagrid-wrapper--fit]="layoutModeFit()"
289
+ [class.ogrid-datagrid-wrapper--overflow-x]="allowOverflowX()"
290
+ tabindex="0"
291
+ role="region"
292
+ [attr.aria-label]="ariaLabel()"
293
+ [attr.aria-labelledby]="ariaLabelledBy()"
294
+ (mousedown)="onWrapperMouseDown($event)"
295
+ (keydown)="onGridKeyDown($event)"
296
+ (contextmenu)="$event.preventDefault()"
297
+ [attr.data-overflow-x]="allowOverflowX() ? 'true' : 'false'"
298
+ >
299
+ <div class="ogrid-datagrid-scroll-wrapper">
300
+ <div [style.minWidth.px]="allowOverflowX() ? minTableWidth() : undefined">
301
+ <div [class.ogrid-datagrid-table-wrapper--loading]="isLoading() && items().length > 0" #tableContainerEl>
302
+ <table class="ogrid-datagrid-table" [style.minWidth.px]="minTableWidth()"
303
+ [attr.data-freeze-rows]="freezeRows()"
304
+ [attr.data-freeze-cols]="freezeCols()"
305
+ >
306
+ <thead class="ogrid-datagrid-thead">
307
+ @for (row of headerRows(); track $index; let rowIdx = $index) {
308
+ <tr class="ogrid-datagrid-header-row">
309
+ @if (rowIdx === headerRows().length - 1 && hasCheckboxCol()) {
310
+ <th class="ogrid-datagrid-th ogrid-datagrid-checkbox-col" [attr.rowSpan]="headerRows().length > 1 ? 1 : null">
311
+ <div class="ogrid-datagrid-checkbox-wrapper">
312
+ <input
313
+ type="checkbox"
314
+ [checked]="allSelected()"
315
+ [indeterminate]="someSelected()"
316
+ (change)="onSelectAllChange($event)"
317
+ aria-label="Select all rows"
318
+ />
319
+ </div>
320
+ </th>
321
+ }
322
+ @if (rowIdx === 0 && rowIdx < headerRows().length - 1 && hasCheckboxCol()) {
323
+ <th [attr.rowSpan]="headerRows().length - 1" class="ogrid-datagrid-th" style="width: 48px; min-width: 48px; padding: 0;"></th>
324
+ }
325
+ @for (cell of row; track $index; let cellIdx = $index) {
326
+ @if (cell.isGroup) {
327
+ <th [attr.colSpan]="cell.colSpan" scope="colgroup" class="ogrid-datagrid-th ogrid-datagrid-group-header">
328
+ {{ cell.label }}
329
+ </th>
330
+ } @else {
331
+ @let col = asColumnDef(cell.columnDef);
332
+ @let colIdx = visibleColIndex(col);
333
+ @let isFreezeCol = freezeCols() != null && (freezeCols() ?? 0) >= 1 && colIdx < (freezeCols() ?? 0);
334
+ @let colW = getColumnWidth(col);
335
+ <th scope="col"
336
+ class="ogrid-datagrid-th"
337
+ [class.ogrid-datagrid-th--pinned-left]="col.pinned === 'left' || (isFreezeCol && colIdx === 0)"
338
+ [class.ogrid-datagrid-th--pinned-right]="col.pinned === 'right'"
339
+ [attr.rowSpan]="headerRows().length > 1 ? headerRows().length - rowIdx : null"
340
+ [attr.data-column-id]="col.columnId"
341
+ [style.minWidth.px]="col.minWidth ?? 80"
342
+ [style.width.px]="colW"
343
+ [style.maxWidth.px]="colW"
344
+ [style.cursor]="columnReorderService.isDragging() ? 'grabbing' : 'grab'"
345
+ (mousedown)="onHeaderMouseDown(col.columnId, $event)"
346
+ >
347
+ <column-header-filter
348
+ [columnKey]="col.columnId"
349
+ [columnName]="col.name"
350
+ [filterType]="getFilterConfig(col).filterType"
351
+ [isSorted]="getFilterConfig(col).isSorted"
352
+ [isSortedDescending]="getFilterConfig(col).isSortedDescending"
353
+ [onSort]="getFilterConfig(col).onSort"
354
+ [selectedValues]="getFilterConfig(col).selectedValues"
355
+ [onFilterChange]="getFilterConfig(col).onFilterChange"
356
+ [options]="getFilterConfig(col).options"
357
+ [isLoadingOptions]="getFilterConfig(col).isLoadingOptions ?? false"
358
+ [textValue]="getFilterConfig(col).textValue ?? ''"
359
+ [onTextChange]="getFilterConfig(col).onTextChange"
360
+ [selectedUser]="getFilterConfig(col).selectedUser"
361
+ [onUserChange]="getFilterConfig(col).onUserChange"
362
+ [peopleSearch]="getFilterConfig(col).peopleSearch"
363
+ [dateValue]="getFilterConfig(col).dateValue"
364
+ [onDateChange]="getFilterConfig(col).onDateChange"
365
+ />
366
+ <div class="ogrid-datagrid-resize-handle" (mousedown)="onResizeStart($event, col)"></div>
367
+ </th>
368
+ }
369
+ }
370
+ </tr>
371
+ }
372
+ </thead>
373
+ @if (!showEmptyInGrid()) {
374
+ <tbody>
375
+ @for (item of items(); track getRowId()(item); let rowIndex = $index) {
376
+ @let rowId = getRowId()(item);
377
+ @let isSelected = selectedRowIds().has(rowId);
378
+ <tr
379
+ class="ogrid-datagrid-row"
380
+ [class.ogrid-datagrid-row--selected]="isSelected"
381
+ [attr.data-row-id]="rowId"
382
+ (click)="onRowClick($event, rowId)"
383
+ >
384
+ @if (hasCheckboxCol()) {
385
+ <td class="ogrid-datagrid-td ogrid-datagrid-checkbox-col">
386
+ <div
387
+ class="ogrid-datagrid-checkbox-wrapper"
388
+ [attr.data-row-index]="rowIndex"
389
+ [attr.data-col-index]="0"
390
+ (click)="$event.stopPropagation()"
391
+ >
392
+ <input
393
+ type="checkbox"
394
+ [checked]="isSelected"
395
+ (change)="onRowCheckboxChange(rowId, $event, rowIndex)"
396
+ [attr.aria-label]="'Select row ' + (rowIndex + 1)"
397
+ />
398
+ </div>
399
+ </td>
400
+ }
401
+ @for (colLayout of columnLayouts(); track colLayout.col.columnId; let colIdx = $index) {
402
+ <td
403
+ class="ogrid-datagrid-td"
404
+ [class.ogrid-datagrid-td--pinned-left]="colLayout.pinnedLeft"
405
+ [class.ogrid-datagrid-td--pinned-right]="colLayout.pinnedRight"
406
+ [style.minWidth.px]="colLayout.minWidth"
407
+ [style.width.px]="colLayout.width"
408
+ [style.maxWidth.px]="colLayout.width"
409
+ >
410
+ @let descriptor = getCellDescriptor(item, colLayout.col, rowIndex, colIdx);
411
+ @if (descriptor.mode === 'editing-inline') {
412
+ <div class="ogrid-datagrid-cell ogrid-datagrid-cell--editing">
413
+ @switch (descriptor.editorType) {
414
+ @case ('checkbox') {
415
+ <input
416
+ type="checkbox"
417
+ [checked]="!!descriptor.value"
418
+ (change)="commitEdit(item, colLayout.col.columnId, descriptor.value, ($event.target as HTMLInputElement).checked, rowIndex, descriptor.globalColIndex)"
419
+ (keydown)="$event.key === 'Escape' && cancelEdit()"
420
+ />
421
+ }
422
+ @case ('select') {
423
+ <select
424
+ class="ogrid-datagrid-editor-select"
425
+ [value]="descriptor.value != null ? '' + descriptor.value : ''"
426
+ (change)="commitEdit(item, colLayout.col.columnId, descriptor.value, ($event.target as HTMLSelectElement).value, rowIndex, descriptor.globalColIndex)"
427
+ (keydown)="$event.key === 'Escape' && cancelEdit()"
428
+ >
429
+ @for (v of getSelectValues(colLayout.col); track v) {
430
+ <option [value]="v">{{ v }}</option>
431
+ }
432
+ </select>
433
+ }
434
+ @case ('date') {
435
+ <input
436
+ type="date"
437
+ class="ogrid-datagrid-editor-input"
438
+ [value]="formatDateForInput(descriptor.value)"
439
+ (change)="commitEdit(item, colLayout.col.columnId, descriptor.value, ($event.target as HTMLInputElement).value, rowIndex, descriptor.globalColIndex)"
440
+ (keydown)="onEditorKeydown($event, item, colLayout.col.columnId, descriptor.value, rowIndex, descriptor.globalColIndex)"
441
+ />
442
+ }
443
+ @default {
444
+ <input
445
+ type="text"
446
+ class="ogrid-datagrid-editor-input"
447
+ [value]="descriptor.value != null ? '' + descriptor.value : ''"
448
+ (keydown)="onEditorKeydown($event, item, colLayout.col.columnId, descriptor.value, rowIndex, descriptor.globalColIndex)"
449
+ />
450
+ }
451
+ }
452
+ </div>
453
+ } @else {
454
+ @let content = resolveCellContent(colLayout.col, item, descriptor.displayValue);
455
+ @let cellStyle = resolveCellStyleFn(colLayout.col, item);
456
+ <div
457
+ class="ogrid-datagrid-cell"
458
+ [class.ogrid-datagrid-cell--active]="descriptor.isActive && !descriptor.isInRange"
459
+ [class.ogrid-datagrid-cell--in-range]="descriptor.isInRange"
460
+ [class.ogrid-datagrid-cell--in-cut-range]="descriptor.isInCutRange"
461
+ [class.ogrid-datagrid-cell--editable]="descriptor.canEditAny"
462
+ [class.ogrid-datagrid-cell--numeric]="colLayout.col.type === 'numeric'"
463
+ [class.ogrid-datagrid-cell--boolean]="colLayout.col.type === 'boolean'"
464
+ [attr.data-row-index]="rowIndex"
465
+ [attr.data-col-index]="descriptor.globalColIndex"
466
+ [attr.data-in-range]="descriptor.isInRange ? 'true' : null"
467
+ [attr.tabindex]="descriptor.isActive ? 0 : -1"
468
+ (mousedown)="onCellMouseDown($event, rowIndex, descriptor.globalColIndex)"
469
+ (click)="onCellClick(rowIndex, descriptor.globalColIndex)"
470
+ (contextmenu)="onCellContextMenu($event)"
471
+ (dblclick)="descriptor.canEditAny ? onCellDblClick(descriptor.rowId, colLayout.col.columnId) : null"
472
+ [attr.role]="descriptor.canEditAny ? 'button' : null"
473
+ [style]="cellStyle ?? undefined"
474
+ >
475
+ {{ content }}
476
+ @if (descriptor.canEditAny && descriptor.isSelectionEndCell) {
477
+ <div
478
+ class="ogrid-datagrid-fill-handle"
479
+ (mousedown)="onFillHandleMouseDown($event)"
480
+ aria-label="Fill handle"
481
+ ></div>
482
+ }
483
+ </div>
484
+ }
485
+ </td>
486
+ }
487
+ </tr>
488
+ }
489
+ </tbody>
490
+ }
491
+ </table>
492
+
493
+ <ogrid-marching-ants-overlay
494
+ [containerEl]="tableContainerEl()"
495
+ [selectionRange]="state().interaction.selectionRange"
496
+ [copyRange]="state().interaction.copyRange"
497
+ [cutRange]="state().interaction.cutRange"
498
+ [colOffset]="state().layout.colOffset"
499
+ [columnSizingVersion]="columnSizingVersion()"
500
+ ></ogrid-marching-ants-overlay>
501
+
502
+ @if (showEmptyInGrid() && emptyState()) {
503
+ <div class="ogrid-datagrid-empty">
504
+ <div class="ogrid-datagrid-empty__title">No results found</div>
505
+ <div class="ogrid-datagrid-empty__message">
506
+ @if (emptyState()!.message != null) {
507
+ {{ emptyState()!.message }}
508
+ } @else if (emptyState()!.hasActiveFilters) {
509
+ No items match your current filters. Try adjusting your search or
510
+ <button class="ogrid-datagrid-empty__clear" (click)="emptyState()!.onClearAll?.()">clear all filters</button>
511
+ to see all items.
512
+ } @else {
513
+ There are no items available at this time.
514
+ }
515
+ </div>
516
+ </div>
517
+ }
518
+ </div>
519
+ </div>
520
+ </div>
521
+
522
+ @if (columnReorderService.isDragging() && columnReorderService.dropIndicatorX() !== null) {
523
+ <div class="ogrid-datagrid-drop-indicator" [style.left.px]="columnReorderService.dropIndicatorX()"></div>
524
+ }
525
+
526
+ @if (menuPosition()) {
527
+ <div
528
+ class="ogrid-datagrid-context-menu-overlay"
529
+ (click)="closeContextMenu()"
530
+ (contextmenu)="$event.preventDefault(); closeContextMenu()"
531
+ >
532
+ <ogrid-grid-context-menu
533
+ [x]="menuPosition()!.x"
534
+ [y]="menuPosition()!.y"
535
+ [hasSelection]="hasCellSelection()"
536
+ [canUndo]="canUndo()"
537
+ [canRedo]="canRedo()"
538
+ (undoAction)="onUndo()"
539
+ (redoAction)="onRedo()"
540
+ (copyAction)="handleCopy()"
541
+ (cutAction)="handleCut()"
542
+ (pasteAction)="handlePaste()"
543
+ (selectAllAction)="handleSelectAllCells()"
544
+ (closeAction)="closeContextMenu()"
545
+ />
546
+ </div>
547
+ }
548
+ </div>
549
+
550
+ @if (statusBarConfig()) {
551
+ <ogrid-status-bar
552
+ [totalCount]="statusBarConfig()!.totalCount"
553
+ [filteredCount]="statusBarConfig()!.filteredCount"
554
+ [selectedCount]="statusBarConfig()!.selectedCount ?? selectedRowIds().size"
555
+ [selectedCellCount]="selectionCellCount()"
556
+ [aggregation]="statusBarConfig()!.aggregation"
557
+ [suppressRowCount]="statusBarConfig()!.suppressRowCount"
558
+ />
559
+ }
560
+
561
+ @if (isLoading()) {
562
+ <div class="ogrid-datagrid-loading-overlay">
563
+ <div class="ogrid-datagrid-loading-inner">
564
+ <div class="ogrid-datagrid-spinner"></div>
565
+ <span>{{ loadingMessage() }}</span>
566
+ </div>
567
+ </div>
568
+ }
569
+ </div>
570
+ `,
571
+ })
572
+ ], DataGridTableComponent);
573
+ export { DataGridTableComponent };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @alaarab/ogrid-angular-radix
3
+ *
4
+ * Lightweight Angular data grid using Angular CDK for overlays.
5
+ * This is the recommended "default" option for Angular developers.
6
+ */
7
+ // Re-export everything from the base Angular package
8
+ export * from '@alaarab/ogrid-angular';
9
+ // Export our UI components
10
+ export { OGridComponent } from './ogrid/ogrid.component';
11
+ export { DataGridTableComponent } from './datagrid-table/datagrid-table.component';
12
+ export { ColumnHeaderFilterComponent } from './column-header-filter/column-header-filter.component';
13
+ export { ColumnChooserComponent } from './column-chooser/column-chooser.component';
14
+ export { PaginationControlsComponent } from './pagination-controls/pagination-controls.component';
@@ -0,0 +1,77 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
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
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { Component, input, computed, ChangeDetectionStrategy } from '@angular/core';
8
+ import { OGridService, OGridLayoutComponent, } from '@alaarab/ogrid-angular';
9
+ import { DataGridTableComponent } from '../datagrid-table/datagrid-table.component';
10
+ import { ColumnChooserComponent } from '../column-chooser/column-chooser.component';
11
+ import { PaginationControlsComponent } from '../pagination-controls/pagination-controls.component';
12
+ /**
13
+ * Top-level OGrid component for Angular Radix (lightweight Angular CDK-based implementation).
14
+ * This is the recommended default option for Angular developers.
15
+ * Standalone component — provides OGridService and renders OGridLayout with all sub-components.
16
+ */
17
+ let OGridComponent = class OGridComponent {
18
+ constructor() {
19
+ this.props = input.required();
20
+ this.dataGridProps = computed(() => {
21
+ this.ogridService.configure(this.props());
22
+ return this.ogridService.dataGridProps();
23
+ });
24
+ this.ogridService = new OGridService();
25
+ }
26
+ onPageSizeChange(size) {
27
+ this.ogridService.pagination().setPageSize(size);
28
+ this.ogridService.pagination().setPage(1);
29
+ }
30
+ };
31
+ OGridComponent = __decorate([
32
+ Component({
33
+ selector: 'ogrid',
34
+ standalone: true,
35
+ imports: [
36
+ OGridLayoutComponent,
37
+ DataGridTableComponent,
38
+ ColumnChooserComponent,
39
+ PaginationControlsComponent,
40
+ ],
41
+ providers: [OGridService],
42
+ changeDetection: ChangeDetectionStrategy.OnPush,
43
+ template: `
44
+ <ogrid-layout
45
+ [className]="ogridService.className()"
46
+ [sideBar]="ogridService.sideBarProps()"
47
+ [toolbar]="ogridService.toolbar()"
48
+ [toolbarBelow]="ogridService.toolbarBelow()"
49
+ >
50
+ <ng-container toolbar-end>
51
+ @if (ogridService.columnChooserPlacement() === 'toolbar') {
52
+ <ogrid-column-chooser
53
+ [columns]="ogridService.columnChooser().columns"
54
+ [visibleColumns]="ogridService.columnChooser().visibleColumns"
55
+ (visibilityChange)="ogridService.columnChooser().onVisibilityChange($event.columnKey, $event.visible)"
56
+ />
57
+ }
58
+ </ng-container>
59
+
60
+ <ogrid-datagrid-table [props]="dataGridProps()" />
61
+
62
+ <ng-container pagination>
63
+ <ogrid-pagination-controls
64
+ [currentPage]="ogridService.pagination().page"
65
+ [pageSize]="ogridService.pagination().pageSize"
66
+ [totalCount]="ogridService.pagination().displayTotalCount"
67
+ [pageSizeOptions]="ogridService.pagination().pageSizeOptions"
68
+ [entityLabelPlural]="ogridService.pagination().entityLabelPlural"
69
+ (pageChange)="ogridService.pagination().setPage($event)"
70
+ (pageSizeChange)="onPageSizeChange($event)"
71
+ />
72
+ </ng-container>
73
+ </ogrid-layout>
74
+ `,
75
+ })
76
+ ], OGridComponent);
77
+ export { OGridComponent };