@gp-grid/angular 0.10.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.
@@ -0,0 +1,148 @@
1
+ import { Signal, computed, signal } from '@angular/core';
2
+ import {
3
+ calculateFillHandlePosition,
4
+ calculateScaledColumnPositions,
5
+ getTotalWidth,
6
+ } from '@gp-grid/core';
7
+ import type {
8
+ BatchChangeSetters,
9
+ CellPosition,
10
+ CellRange,
11
+ ColumnDefinition,
12
+ DragState,
13
+ FillHandlePosition,
14
+ FilterPopupState,
15
+ HeaderData,
16
+ SlotData,
17
+ VisibleColumnInfo,
18
+ } from '@gp-grid/core';
19
+ import type { ActiveFilterPopup, EditingCellState } from './components';
20
+ import type { AngularColumnDefinition } from './types';
21
+
22
+ export interface GpGridViewModelDeps {
23
+ getColumns: () => AngularColumnDefinition[];
24
+ getRows: () => unknown[];
25
+ getRowHeight: () => number;
26
+ }
27
+
28
+ const INITIAL_DRAG_STATE: DragState = {
29
+ isDragging: false,
30
+ dragType: null,
31
+ fillSourceRange: null,
32
+ fillTarget: null,
33
+ columnResize: null,
34
+ columnMove: null,
35
+ rowDrag: null,
36
+ };
37
+
38
+ /**
39
+ * Reactive view-state container for GpGridComponent.
40
+ *
41
+ * Owns every signal and computed the template binds against, plus the
42
+ * batch-change setters bag wired into those signals. The component
43
+ * becomes a thin shell that holds lifecycle, event handlers, and inputs.
44
+ *
45
+ * Angular-specific (uses signal/computed from @angular/core) — lives in
46
+ * the angular package, not core.
47
+ */
48
+ export class GpGridViewModel {
49
+ readonly headerState = signal<Map<number, HeaderData>>(new Map());
50
+ readonly viewportWidth = signal<number>(0);
51
+ readonly scrollLeft = signal<number>(0);
52
+ readonly isLoading = signal<boolean>(false);
53
+ readonly errorMessage = signal<string | null>(null);
54
+ readonly filterPopup = signal<ActiveFilterPopup | null>(null);
55
+ readonly pendingScrollTop = signal<number | null>(null);
56
+ readonly activeCell = signal<CellPosition | null>(null);
57
+ readonly selectionRange = signal<CellRange | null>(null);
58
+ readonly editingCell = signal<EditingCellState | null>(null);
59
+ readonly hoverPosition = signal<CellPosition | null>(null);
60
+ readonly columnsOverride = signal<ColumnDefinition[] | null>(null);
61
+ readonly dragState = signal<DragState>(INITIAL_DRAG_STATE);
62
+ readonly contentWidth = signal<number>(0);
63
+ readonly contentHeight = signal<number>(0);
64
+ readonly rowsWrapperOffset = signal<number>(0);
65
+ readonly slots = signal<Map<string, SlotData>>(new Map());
66
+
67
+ readonly effectiveColumns: Signal<ColumnDefinition[]>;
68
+ readonly visibleColumnWithIndices: Signal<VisibleColumnInfo[]>;
69
+ readonly columnPositions: Signal<number[]>;
70
+ readonly columnWidths: Signal<number[]>;
71
+ readonly totalWidth: Signal<number>;
72
+ readonly fillHandlePosition: Signal<FillHandlePosition | null>;
73
+ readonly slotsArray: Signal<SlotData[]>;
74
+ readonly totalRows: Signal<number>;
75
+
76
+ readonly batchSetters: BatchChangeSetters;
77
+
78
+ private filterAnchorEl: HTMLElement | null = null;
79
+
80
+ constructor(deps: GpGridViewModelDeps) {
81
+ this.effectiveColumns = computed(() =>
82
+ this.columnsOverride() ?? (deps.getColumns() as unknown as ColumnDefinition[])
83
+ );
84
+ this.visibleColumnWithIndices = computed(() =>
85
+ this.effectiveColumns()
86
+ .map((col, index) => ({ column: col, originalIndex: index }))
87
+ .filter(({ column }) => !column.hidden)
88
+ );
89
+ const columnLayout = computed(() =>
90
+ calculateScaledColumnPositions(
91
+ this.visibleColumnWithIndices().map(v => v.column),
92
+ this.viewportWidth(),
93
+ )
94
+ );
95
+ this.columnPositions = computed(() => columnLayout().positions);
96
+ this.columnWidths = computed(() => columnLayout().widths);
97
+ this.totalWidth = computed(() => getTotalWidth(this.columnPositions()));
98
+ this.fillHandlePosition = computed(() =>
99
+ calculateFillHandlePosition({
100
+ activeCell: this.activeCell(),
101
+ selectionRange: this.selectionRange(),
102
+ slots: this.slots(),
103
+ columns: this.effectiveColumns(),
104
+ visibleColumnsWithIndices: this.visibleColumnWithIndices(),
105
+ columnPositions: this.columnPositions(),
106
+ columnWidths: this.columnWidths(),
107
+ rowHeight: deps.getRowHeight(),
108
+ })
109
+ );
110
+ this.slotsArray = computed(() => [...this.slots().values()]);
111
+ this.totalRows = computed(() => deps.getRows().length);
112
+
113
+ this.batchSetters = {
114
+ setContentWidth: (v) => this.contentWidth.set(v),
115
+ setContentHeight: (v) => this.contentHeight.set(v),
116
+ setRowsWrapperOffset: (v) => this.rowsWrapperOffset.set(v),
117
+ setIsLoading: (v) => this.isLoading.set(v),
118
+ setErrorMessage: (v) => this.errorMessage.set(v),
119
+ setPendingScrollTop: (v) => this.pendingScrollTop.set(v),
120
+ setActiveCell: (v) => this.activeCell.set(v),
121
+ setSelectionRange: (v) => this.selectionRange.set(v),
122
+ setEditingCell: (v) => this.editingCell.set(v),
123
+ setHoverPosition: (v) => this.hoverPosition.set(v),
124
+ setColumnsOverride: (v) => this.columnsOverride.set(v),
125
+ onFilterPopupChange: (v) => this.materializeFilterPopup(v),
126
+ };
127
+ }
128
+
129
+ setFilterAnchor(el: HTMLElement | null): void {
130
+ this.filterAnchorEl = el;
131
+ }
132
+
133
+ private materializeFilterPopup(state: FilterPopupState | null): void {
134
+ if (state === null) {
135
+ this.filterPopup.set(null);
136
+ return;
137
+ }
138
+ if (state.isOpen && state.column) {
139
+ this.filterPopup.set({
140
+ colIndex: state.colIndex,
141
+ column: state.column,
142
+ distinctValues: state.distinctValues,
143
+ currentFilter: state.currentFilter,
144
+ anchorEl: this.filterAnchorEl,
145
+ });
146
+ }
147
+ }
148
+ }
@@ -0,0 +1,279 @@
1
+ import {
2
+ Component,
3
+ ElementRef,
4
+ OnInit,
5
+ AfterViewInit,
6
+ OnDestroy,
7
+ ViewChild,
8
+ ChangeDetectionStrategy,
9
+ PLATFORM_ID,
10
+ inject,
11
+ input,
12
+ output,
13
+ effect,
14
+ } from '@angular/core';
15
+ import type { CellRendererTemplate, EditRendererTemplate, HeaderRendererTemplate, HeaderSortEvent } from './components';
16
+ import type { AngularColumnDefinition } from './types';
17
+ import { isPlatformBrowser } from '@angular/common';
18
+ import type {
19
+ CellValueChangedEvent,
20
+ ColumnDefinition,
21
+ ColumnFilterModel,
22
+ DataSource,
23
+ HighlightingOptions,
24
+ RowId,
25
+ } from '@gp-grid/core';
26
+ import {
27
+ GridHeaderComponent,
28
+ GridBodyComponent,
29
+ GridOverlaysComponent,
30
+ } from './components';
31
+ import type {
32
+ HeaderPointerDownEvent,
33
+ FilterPointerDownEvent,
34
+ ResizePointerDownEvent,
35
+ CellPointerDownEvent,
36
+ CellPointerEnterEvent,
37
+ CellDoubleClickEvent,
38
+ FillHandlePointerDownEvent,
39
+ } from './components';
40
+ import { GP_GRID_TEMPLATE } from './gp-grid.template';
41
+ import { GpGridViewModel } from './gp-grid-view-model';
42
+ import { GpGridBindings } from './gp-grid-bindings';
43
+ import { buildGridCore } from './gp-grid.factory';
44
+
45
+ @Component({
46
+ selector: 'gp-grid',
47
+ standalone: true,
48
+ imports: [GridHeaderComponent, GridBodyComponent, GridOverlaysComponent],
49
+ changeDetection: ChangeDetectionStrategy.OnPush,
50
+ styles: [`:host { display: block; height: 100%; min-height: 0; }`],
51
+ template: GP_GRID_TEMPLATE,
52
+ })
53
+ export class GpGridComponent implements OnInit, AfterViewInit, OnDestroy {
54
+ @ViewChild('container', { static: true }) container!: ElementRef<HTMLDivElement>;
55
+ @ViewChild(GridBodyComponent) body!: GridBodyComponent;
56
+
57
+ private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
58
+
59
+ columns = input.required<AngularColumnDefinition[]>();
60
+ rows = input<unknown[]>([]);
61
+ dataSource = input<DataSource<unknown> | null>(null);
62
+ getRowId = input<((row: unknown) => RowId) | null>(null);
63
+ rowHeight = input<number>(32);
64
+ headerHeight = input<number>(32);
65
+ darkMode = input<boolean>(false);
66
+ cellRenderers = input<Record<string, CellRendererTemplate>>({});
67
+ headerRenderers = input<Record<string, HeaderRendererTemplate>>({});
68
+ editRenderers = input<Record<string, EditRendererTemplate>>({});
69
+ cellRenderer = input<CellRendererTemplate | null>(null);
70
+ headerRenderer = input<HeaderRendererTemplate | null>(null);
71
+ editRenderer = input<EditRendererTemplate | null>(null);
72
+ highlighting = input<HighlightingOptions | null>(null);
73
+ rowDragEntireRow = input<boolean>(false);
74
+ overscan = input<number>(3);
75
+ sortingEnabled = input<boolean>(true);
76
+ wheelDampening = input<number>(0.1);
77
+ onRowDragEnd = output<{ source: number; target: number }>();
78
+ onCellValueChanged = output<CellValueChangedEvent<unknown>>();
79
+ onColumnResized = output<{ colIndex: number; newWidth: number }>();
80
+ onColumnMoved = output<{ fromIndex: number; toIndex: number }>();
81
+
82
+ protected readonly vm = new GpGridViewModel({
83
+ getColumns: () => this.columns(),
84
+ getRows: () => this.rows(),
85
+ getRowHeight: () => this.rowHeight(),
86
+ });
87
+
88
+ private readonly bindings = new GpGridBindings<unknown>({
89
+ vm: this.vm,
90
+ isBrowser: this.isBrowser,
91
+ getContainer: () => this.container?.nativeElement ?? null,
92
+ getBody: () => this.body?.scrollContainer?.nativeElement ?? null,
93
+ getRowHeight: () => this.rowHeight(),
94
+ getHeaderHeight: () => this.headerHeight(),
95
+ });
96
+
97
+ constructor() {
98
+ effect(() => this.bindings.applyPendingScroll());
99
+ effect(() => this.bindings.syncHighlighting(this.highlighting()));
100
+ effect(() => this.bindings.syncColumns(this.columns() as unknown as ColumnDefinition[]));
101
+ effect(() => this.bindings.syncRows(this.rows(), this.dataSource()));
102
+ }
103
+
104
+ ngOnInit(): void {
105
+ const core = buildGridCore<unknown>(
106
+ {
107
+ columns: this.columns() as unknown as ColumnDefinition[],
108
+ dataSource: this.bindings.dataSourceOwner.initialize(this.dataSource(), this.rows()),
109
+ rowHeight: this.rowHeight(),
110
+ headerHeight: this.headerHeight(),
111
+ overscan: this.overscan(),
112
+ sortingEnabled: this.sortingEnabled(),
113
+ highlighting: (this.highlighting() ?? undefined) as HighlightingOptions<unknown> | undefined,
114
+ getRowId: this.getRowId() ?? undefined,
115
+ rowDragEntireRow: this.rowDragEntireRow(),
116
+ },
117
+ {
118
+ onRowDragEnd: (source, target) => this.onRowDragEnd.emit({ source, target }),
119
+ onCellValueChanged: (event) => this.onCellValueChanged.emit(event),
120
+ onColumnResized: (colIndex, newWidth) => this.onColumnResized.emit({ colIndex, newWidth }),
121
+ onColumnMoved: (fromIndex, toIndex) => this.onColumnMoved.emit({ fromIndex, toIndex }),
122
+ },
123
+ );
124
+ this.bindings.attach(core);
125
+ }
126
+
127
+ ngAfterViewInit(): void {
128
+ if (this.isBrowser === false) return;
129
+ this.bindings.observeViewport(
130
+ this.container.nativeElement,
131
+ this.body.scrollContainer.nativeElement,
132
+ );
133
+ document.addEventListener('pointermove', this.onDocumentPointerMove, { passive: false });
134
+ document.addEventListener('pointerup', this.onDocumentPointerUp);
135
+ }
136
+
137
+ ngOnDestroy(): void {
138
+ this.bindings.destroy();
139
+ if (this.isBrowser) {
140
+ document.removeEventListener('pointermove', this.onDocumentPointerMove);
141
+ document.removeEventListener('pointerup', this.onDocumentPointerUp);
142
+ }
143
+ }
144
+
145
+ protected onBodyScroll(scrollLeft: number): void {
146
+ this.vm.scrollLeft.set(scrollLeft);
147
+ const el = this.body.scrollContainer.nativeElement;
148
+ this.bindings.coreRef?.setViewport(el.scrollTop, scrollLeft, el.clientWidth, el.clientHeight);
149
+ }
150
+
151
+ protected onHeaderPointerDown(evt: HeaderPointerDownEvent): void {
152
+ if (this.bindings.input.headerPointerDown(evt.colIndex, evt.colWidth, evt.colHeight, evt.event)) {
153
+ evt.event.preventDefault();
154
+ }
155
+ }
156
+
157
+ protected onFilterPointerDown(evt: FilterPointerDownEvent): void {
158
+ this.vm.setFilterAnchor(evt.anchorEl);
159
+ const rect = evt.anchorEl.getBoundingClientRect();
160
+ this.bindings.coreRef?.openFilterPopup(evt.colIndex, {
161
+ top: rect.top,
162
+ left: rect.left,
163
+ width: rect.width,
164
+ height: rect.height,
165
+ });
166
+ }
167
+
168
+ protected onCellPointerDown(evt: CellPointerDownEvent): void {
169
+ const action = this.bindings.input.cellPointerDown(evt.rowIndex, evt.colIndex, evt.event);
170
+ if (action.preventDefault) evt.event.preventDefault();
171
+ if (action.focusContainer) {
172
+ this.container.nativeElement.focus({ preventScroll: true });
173
+ }
174
+ }
175
+
176
+ protected onCellPointerEnter(evt: CellPointerEnterEvent): void {
177
+ this.bindings.input.cellPointerEnter(evt.rowIndex, evt.colIndex);
178
+ }
179
+
180
+ protected onFillHandlePointerDown(evt: FillHandlePointerDownEvent): void {
181
+ const action = this.bindings.input.fillHandlePointerDown(
182
+ this.vm.activeCell(),
183
+ this.vm.selectionRange(),
184
+ evt.event,
185
+ );
186
+ if (action.preventDefault) evt.event.preventDefault();
187
+ if (action.stopPropagation) evt.event.stopPropagation();
188
+ }
189
+
190
+ protected onCellPointerLeave(): void {
191
+ this.bindings.input.cellPointerLeave();
192
+ }
193
+
194
+ protected computeRowClassesFn = (rowIndex: number, rowData: unknown): string[] => {
195
+ return this.bindings.coreRef?.highlight?.computeRowClasses(rowIndex, rowData) ?? [];
196
+ };
197
+
198
+ protected computeCellClassesFn = (
199
+ rowIndex: number,
200
+ colIndex: number,
201
+ column: ColumnDefinition,
202
+ rowData: unknown,
203
+ ): string[] => {
204
+ return this.bindings.coreRef?.highlight?.computeCombinedCellClasses(
205
+ rowIndex,
206
+ colIndex,
207
+ column,
208
+ rowData,
209
+ ) ?? [];
210
+ };
211
+
212
+ protected onCellDoubleClick(evt: CellDoubleClickEvent): void {
213
+ this.bindings.coreRef?.startEdit(evt.rowIndex, evt.colIndex);
214
+ }
215
+
216
+ protected onEditValueChange(value: string): void {
217
+ this.bindings.coreRef?.updateEditValue(value);
218
+ }
219
+
220
+ protected onEditCommit(): void {
221
+ this.bindings.coreRef?.commitEdit();
222
+ }
223
+
224
+ protected onEditCancel(): void {
225
+ this.bindings.coreRef?.cancelEdit();
226
+ }
227
+
228
+ protected onHeaderSort(evt: HeaderSortEvent): void {
229
+ this.bindings.coreRef?.setSort(evt.colId, evt.direction, evt.addToExisting);
230
+ }
231
+
232
+ protected onWheel(event: WheelEvent): void {
233
+ const bodyEl = this.body?.scrollContainer?.nativeElement;
234
+ if (!bodyEl) return;
235
+ const dampened = this.bindings.input.wheel(event.deltaY, event.deltaX, this.wheelDampening());
236
+ if (dampened) {
237
+ event.preventDefault();
238
+ bodyEl.scrollTop += dampened.dy;
239
+ bodyEl.scrollLeft += dampened.dx;
240
+ }
241
+ }
242
+
243
+ protected onKeyDown(event: KeyboardEvent): void {
244
+ const editing = this.vm.editingCell();
245
+ const result = this.bindings.input.keyDown(
246
+ event,
247
+ this.vm.activeCell(),
248
+ editing === null ? null : { row: editing.row, col: editing.col },
249
+ this.vm.filterPopup() !== null,
250
+ );
251
+ if (result.preventDefault) event.preventDefault();
252
+ if (result.scrollToCell) this.bindings.scrollToRow(result.scrollToCell.row);
253
+ }
254
+
255
+ protected onResizePointerDown(evt: ResizePointerDownEvent): void {
256
+ if (this.bindings.input.resizePointerDown(evt.colIndex, evt.colWidth, evt.event)) {
257
+ evt.event.preventDefault();
258
+ }
259
+ }
260
+
261
+ protected onFilterApply(event: { colId: string; filter: ColumnFilterModel | null }): void {
262
+ this.bindings.coreRef?.setFilter(event.colId, event.filter);
263
+ this.vm.filterPopup.set(null);
264
+ }
265
+
266
+ protected onFilterClose(): void {
267
+ this.bindings.coreRef?.closeFilterPopup();
268
+ this.vm.filterPopup.set(null);
269
+ }
270
+
271
+ private onDocumentPointerMove = (event: PointerEvent): void => {
272
+ if (this.bindings.input.documentPointerMove(event)) event.preventDefault();
273
+ };
274
+
275
+ private onDocumentPointerUp = (_event: PointerEvent): void => {
276
+ const { wasRowDrag } = this.bindings.input.documentPointerUp();
277
+ if (wasRowDrag) this.bindings.pendingRowDrag.releaseLocks();
278
+ };
279
+ }
@@ -0,0 +1,52 @@
1
+ import { GridCore } from '@gp-grid/core';
2
+ import type {
3
+ CellValueChangedEvent,
4
+ ColumnDefinition,
5
+ DataSource,
6
+ HighlightingOptions,
7
+ RowId,
8
+ } from '@gp-grid/core';
9
+
10
+ export interface BuildGridCoreInputs<TData> {
11
+ columns: ColumnDefinition[];
12
+ dataSource: DataSource<TData>;
13
+ rowHeight: number;
14
+ headerHeight: number;
15
+ overscan: number;
16
+ sortingEnabled: boolean;
17
+ highlighting: HighlightingOptions<TData> | undefined;
18
+ getRowId: ((row: TData) => RowId) | undefined;
19
+ rowDragEntireRow: boolean;
20
+ }
21
+
22
+ export interface BuildGridCoreEmitters<TData> {
23
+ onRowDragEnd: (source: number, target: number) => void;
24
+ onCellValueChanged: (event: CellValueChangedEvent<TData>) => void;
25
+ onColumnResized: (colIndex: number, newWidth: number) => void;
26
+ onColumnMoved: (fromIndex: number, toIndex: number) => void;
27
+ }
28
+
29
+ export const buildGridCore = <TData>(
30
+ inputs: BuildGridCoreInputs<TData>,
31
+ emitters: BuildGridCoreEmitters<TData>,
32
+ ): GridCore<TData> => {
33
+ const cellValueChanged = inputs.getRowId === undefined
34
+ ? undefined
35
+ : emitters.onCellValueChanged;
36
+
37
+ return new GridCore<TData>({
38
+ columns: inputs.columns,
39
+ dataSource: inputs.dataSource,
40
+ rowHeight: inputs.rowHeight,
41
+ headerHeight: inputs.headerHeight,
42
+ overscan: inputs.overscan,
43
+ sortingEnabled: inputs.sortingEnabled,
44
+ highlighting: inputs.highlighting,
45
+ getRowId: inputs.getRowId,
46
+ rowDragEntireRow: inputs.rowDragEntireRow,
47
+ onRowDragEnd: emitters.onRowDragEnd,
48
+ onCellValueChanged: cellValueChanged,
49
+ onColumnResized: emitters.onColumnResized,
50
+ onColumnMoved: emitters.onColumnMoved,
51
+ });
52
+ };
@@ -0,0 +1,77 @@
1
+ export const GP_GRID_TEMPLATE = `
2
+ <div #container
3
+ [class]="'gp-grid-container' + (darkMode() ? ' gp-grid-container--dark' : '')"
4
+ style="width: 100%; height: 100%; display: flex; flex-direction: column; position: relative; outline: none;"
5
+ tabindex="0"
6
+ (keydown)="onKeyDown($event)"
7
+ (wheel)="onWheel($event)"
8
+ >
9
+ <gp-grid-header
10
+ [headerHeight]="headerHeight()"
11
+ [scrollLeft]="vm.scrollLeft()"
12
+ [contentWidth]="vm.contentWidth()"
13
+ [totalWidth]="vm.totalWidth()"
14
+ [isLoading]="vm.isLoading()"
15
+ [visibleColumnsWithIndices]="vm.visibleColumnWithIndices()"
16
+ [columnPositions]="vm.columnPositions()"
17
+ [columnWidths]="vm.columnWidths()"
18
+ [headers]="vm.headerState()"
19
+ [sortingEnabled]="sortingEnabled()"
20
+ [headerRenderers]="headerRenderers()"
21
+ [globalHeaderRenderer]="headerRenderer()"
22
+ (headerPointerDown)="onHeaderPointerDown($event)"
23
+ (filterPointerDown)="onFilterPointerDown($event)"
24
+ (resizePointerDown)="onResizePointerDown($event)"
25
+ (headerSort)="onHeaderSort($event)"
26
+ />
27
+ <gp-grid-body
28
+ [rowHeight]="rowHeight()"
29
+ [totalHeaderHeight]="headerHeight()"
30
+ [contentWidth]="vm.contentWidth()"
31
+ [contentHeight]="vm.contentHeight()"
32
+ [totalWidth]="vm.totalWidth()"
33
+ [rowsWrapperOffset]="vm.rowsWrapperOffset()"
34
+ [slotsArray]="vm.slotsArray()"
35
+ [visibleColumnWithIndices]="vm.visibleColumnWithIndices()"
36
+ [columnPositions]="vm.columnPositions()"
37
+ [columnWidths]="vm.columnWidths()"
38
+ [totalRows]="vm.totalRows()"
39
+ [activeCell]="vm.activeCell()"
40
+ [selectionRange]="vm.selectionRange()"
41
+ [editingCell]="vm.editingCell()"
42
+ [cellRenderers]="cellRenderers()"
43
+ [globalCellRenderer]="cellRenderer()"
44
+ [editRenderers]="editRenderers()"
45
+ [globalEditRenderer]="editRenderer()"
46
+ [hoverPosition]="vm.hoverPosition()"
47
+ [computeRowClasses]="computeRowClassesFn"
48
+ [computeCellClasses]="computeCellClassesFn"
49
+ [fillHandlePosition]="vm.fillHandlePosition()"
50
+ [dragState]="vm.dragState()"
51
+ (scrolled)="onBodyScroll($event)"
52
+ (cellPointerDown)="onCellPointerDown($event)"
53
+ (cellPointerEnter)="onCellPointerEnter($event)"
54
+ (cellPointerLeave)="onCellPointerLeave()"
55
+ (cellDoubleClick)="onCellDoubleClick($event)"
56
+ (editValueChange)="onEditValueChange($event)"
57
+ (editCommit)="onEditCommit()"
58
+ (editCancel)="onEditCancel()"
59
+ (fillHandlePointerDown)="onFillHandlePointerDown($event)"
60
+ />
61
+ <gp-grid-overlays
62
+ [filterPopup]="vm.filterPopup()"
63
+ [isLoading]="vm.isLoading()"
64
+ [errorMessage]="vm.errorMessage()"
65
+ [headerHeight]="headerHeight()"
66
+ [rowHeight]="rowHeight()"
67
+ [dragState]="vm.dragState()"
68
+ [visibleColumnWithIndices]="vm.visibleColumnWithIndices()"
69
+ [columnPositions]="vm.columnPositions()"
70
+ [scrollLeft]="vm.scrollLeft()"
71
+ [effectiveColumns]="vm.effectiveColumns()"
72
+ [totalWidth]="vm.totalWidth()"
73
+ (filterApply)="onFilterApply($event)"
74
+ (filterClose)="onFilterClose()"
75
+ />
76
+ </div>
77
+ `;
File without changes
@@ -0,0 +1,28 @@
1
+ import type { TemplateRef } from '@angular/core';
2
+ import type {
3
+ ColumnDefinition,
4
+ CellRendererParams,
5
+ EditRendererParams,
6
+ HeaderRendererParams,
7
+ } from '@gp-grid/core';
8
+
9
+ /**
10
+ * Column definition extended for Angular — allows `cellRenderer`,
11
+ * `editRenderer`, and `headerRenderer` to be passed directly as
12
+ * `TemplateRef` references in addition to the core's string-key / function
13
+ * forms.
14
+ */
15
+ export interface AngularColumnDefinition extends Omit<ColumnDefinition, 'cellRenderer' | 'editRenderer' | 'headerRenderer'> {
16
+ cellRenderer?:
17
+ | string
18
+ | TemplateRef<{ $implicit: CellRendererParams }>
19
+ | ((params: CellRendererParams) => unknown);
20
+ editRenderer?:
21
+ | string
22
+ | TemplateRef<{ $implicit: EditRendererParams }>
23
+ | ((params: EditRendererParams) => unknown);
24
+ headerRenderer?:
25
+ | string
26
+ | TemplateRef<{ $implicit: HeaderRendererParams }>
27
+ | ((params: HeaderRendererParams) => unknown);
28
+ }
@@ -0,0 +1,57 @@
1
+ export * from './lib/gp-grid.component';
2
+ export * from './lib/components';
3
+ export * from './lib/types';
4
+ export * from './lib/createGridData';
5
+
6
+ // Re-export core types for convenience
7
+ export type {
8
+ // Basic types
9
+ CellDataType,
10
+ CellValue,
11
+ SortDirection,
12
+ SortModel,
13
+ FilterModel,
14
+ FilterCondition,
15
+ ColumnFilterModel,
16
+
17
+ // Column definition
18
+ ColumnDefinition,
19
+
20
+ // Row ID
21
+ RowId,
22
+
23
+ // Cell position & range
24
+ CellPosition,
25
+ CellRange,
26
+
27
+ // Events
28
+ CellValueChangedEvent,
29
+
30
+ // DataSource
31
+ DataSource,
32
+ DataSourceRequest,
33
+ DataSourceResponse,
34
+
35
+ // Renderer params
36
+ CellRendererParams,
37
+ EditRendererParams,
38
+ HeaderRendererParams,
39
+
40
+ // Highlighting
41
+ HighlightingOptions,
42
+ HighlightContext,
43
+
44
+ // Instructions (for advanced use cases)
45
+ GridInstruction,
46
+ } from '@gp-grid/core';
47
+
48
+ // Re-export data source factories
49
+ export {
50
+ createClientDataSource,
51
+ createServerDataSource,
52
+ createDataSourceFromArray,
53
+ createMutableClientDataSource,
54
+ } from '@gp-grid/core';
55
+
56
+ export type { MutableDataSource } from '@gp-grid/core';
57
+ export { GridCore } from '@gp-grid/core';
package/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "./tsconfig.lib.json",
3
+ "compilerOptions": {
4
+ "paths": {
5
+ "@gp-grid/core": ["../core/src"]
6
+ }
7
+ },
8
+ "include": [
9
+ "src/**/*.ts"
10
+ ]
11
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ES2022",
5
+ "moduleResolution": "node",
6
+ "ignoreDeprecations": "6.0",
7
+ "declaration": true,
8
+ "strict": true,
9
+ "lib": [
10
+ "ES2022",
11
+ "dom"
12
+ ],
13
+ "outDir": "../../dist/angular",
14
+ "paths": {
15
+ "@gp-grid/core": ["../core/src"]
16
+ }
17
+ },
18
+ "angularCompilerOptions": {
19
+ "compilationMode": "partial"
20
+ },
21
+ "include": [
22
+ "src/**/*.ts"
23
+ ],
24
+ "exclude": [
25
+ "src/**/*.spec.ts"
26
+ ]
27
+ }