@gp-grid/angular 0.12.1 → 0.13.0

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.
package/README.md CHANGED
@@ -73,6 +73,61 @@ Import the stylesheet once (e.g. in `styles.css` or `angular.json`):
73
73
 
74
74
  For custom cell, edit, and header renderers, pass `ng-template` references via the column `cellRenderer` / `editRenderer` / `headerRenderer` fields — see the [Angular docs](https://www.gp-grid.io/docs/angular) for the full API.
75
75
 
76
+ ## Dependency injection
77
+
78
+ For components that want lifecycle-managed cleanup or testable seams, use `provideGridData` and `injectGridData`. They wire the same mutable data source through Angular's DI, mirroring `useGridData` in `@gp-grid/react` and `@gp-grid/vue`. The service implements `OnDestroy` and clears the data source automatically when the component is destroyed.
79
+
80
+ ```ts
81
+ import { Component } from "@angular/core";
82
+ import {
83
+ GpGridComponent,
84
+ provideGridData,
85
+ injectGridData,
86
+ } from "@gp-grid/angular";
87
+ import type { AngularColumnDefinition } from "@gp-grid/angular";
88
+
89
+ interface Person {
90
+ id: number;
91
+ name: string;
92
+ age: number;
93
+ }
94
+
95
+ const initialRows: Person[] = [
96
+ { id: 1, name: "Alice", age: 30 },
97
+ { id: 2, name: "Bob", age: 25 },
98
+ ];
99
+
100
+ @Component({
101
+ selector: "app-root",
102
+ standalone: true,
103
+ imports: [GpGridComponent],
104
+ providers: [
105
+ provideGridData<Person>({
106
+ getRowId: (row) => row.id,
107
+ initialData: initialRows,
108
+ }),
109
+ ],
110
+ template: `
111
+ <gp-grid
112
+ [columns]="columns"
113
+ [dataSource]="grid.dataSource"
114
+ [rowHeight]="36" />
115
+ <button (click)="grid.addRows([{ id: 3, name: 'Carol', age: 28 }])">Add</button>
116
+ `,
117
+ })
118
+ export class App {
119
+ protected readonly grid = injectGridData<Person>();
120
+
121
+ protected readonly columns: AngularColumnDefinition[] = [
122
+ { field: "id", cellDataType: "number", headerName: "ID", width: 80 },
123
+ { field: "name", cellDataType: "text", headerName: "Name", width: 200 },
124
+ { field: "age", cellDataType: "number", headerName: "Age", width: 100 },
125
+ ];
126
+ }
127
+ ```
128
+
129
+ `provideGridData` returns a standard Angular `Provider[]`, so it composes with other `provide*` functions in the component's `providers` array. Register it on the consuming component (not on a parent injector) so each component instance gets its own data source.
130
+
76
131
  ## License
77
132
 
78
133
  Apache-2.0 — see [LICENSE](./LICENSE).
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, output, computed, TemplateRef, ChangeDetectionStrategy, Component, ViewChild, signal, effect, HostListener, inject, PLATFORM_ID } from '@angular/core';
2
+ import { input, output, computed, TemplateRef, ChangeDetectionStrategy, Component, ViewChild, signal, effect, HostListener, inject, PLATFORM_ID, InjectionToken, Injectable } from '@angular/core';
3
3
  import { NgTemplateOutlet, isPlatformBrowser } from '@angular/common';
4
4
  import { isCellSelected, isCellActive, getFieldValue, formatCellValue, isCellInFillPreview, buildCellClasses, isCellEditing, calculateFilterPopupPosition, calculateScaledColumnPositions, getTotalWidth, calculateFillHandlePosition, DataSourceOwner, AutoScrollDriver, PendingRowDragController, InputEventAdapter, applyBatchInstructions, scrollCellIntoView, GridCore, createMutableClientDataSource } from '@gp-grid/core';
5
5
  export { GridCore, createClientDataSource, createDataSourceFromArray, createMutableClientDataSource, createServerDataSource } from '@gp-grid/core';
@@ -941,7 +941,7 @@ class FilterPopupComponent {
941
941
  this.updatePosition();
942
942
  };
943
943
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: FilterPopupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
944
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.7", type: FilterPopupComponent, isStandalone: true, selector: "gp-grid-filter-popup", inputs: { column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, colIndex: { classPropertyName: "colIndex", publicName: "colIndex", isSignal: true, isRequired: true, transformFunction: null }, anchorEl: { classPropertyName: "anchorEl", publicName: "anchorEl", isSignal: true, isRequired: true, transformFunction: null }, distinctValues: { classPropertyName: "distinctValues", publicName: "distinctValues", isSignal: true, isRequired: true, transformFunction: null }, currentFilter: { classPropertyName: "currentFilter", publicName: "currentFilter", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { apply: "apply", close: "close" }, host: { listeners: { "keydown.escape": "onEscape()" } }, viewQueries: [{ propertyName: "popupEl", first: true, predicate: ["popupEl"], descendants: true }], ngImport: i0, template: "\n<div\n #popupEl\n class=\"gp-grid-filter-popup\"\n [style.position]=\"'fixed'\"\n [style.zIndex]=\"10000\"\n [style.top.px]=\"popupTop()\"\n [style.left.px]=\"popupLeft()\"\n [style.minWidth.px]=\"popupMinWidth()\"\n [style.visibility]=\"positioned() ? 'visible' : 'hidden'\"\n (keydown.escape)=\"close.emit()\"\n (click)=\"$event.stopPropagation()\">\n\n <div class=\"gp-grid-filter-header\">\n Filter: {{ column().headerName ?? column().field }}\n </div>\n\n <div [class]=\"'gp-grid-filter-content ' + (isNumberColumn() ? 'gp-grid-filter-number' : 'gp-grid-filter-text')\">\n @if (isNumberColumn()) {\n @for (cond of numberConditions; track $index; let i = $index) {\n <div class=\"gp-grid-filter-condition\">\n @if (i > 0) {\n <div class=\"gp-grid-filter-combination\">\n <button\n type=\"button\"\n [class.active]=\"numberConditions[i - 1]?.nextOperator === 'and'\"\n (click)=\"setNumberNextOp(i - 1, 'and')\">\n AND\n </button>\n <button\n type=\"button\"\n [class.active]=\"numberConditions[i - 1]?.nextOperator === 'or'\"\n (click)=\"setNumberNextOp(i - 1, 'or')\">\n OR\n </button>\n </div>\n }\n <div class=\"gp-grid-filter-row\">\n <select\n [value]=\"cond.operator\"\n (change)=\"onNumberOperatorChange(i, $any($event.target).value)\">\n @for (op of numberOperators; track op.value) {\n <option [value]=\"op.value\">{{ op.label }}</option>\n }\n </select>\n @if (!isValueLessNumberOp(cond.operator)) {\n <input\n type=\"number\"\n [value]=\"cond.value\"\n (input)=\"cond.value = $any($event.target).value\"\n placeholder=\"Value\" />\n @if (cond.operator === 'between') {\n <span class=\"gp-grid-filter-to\">to</span>\n <input\n type=\"number\"\n [value]=\"cond.valueTo\"\n (input)=\"cond.valueTo = $any($event.target).value\"\n placeholder=\"Value\" />\n }\n }\n @if (numberConditions.length > 1) {\n <button\n type=\"button\"\n class=\"gp-grid-filter-remove\"\n (click)=\"removeNumberCondition(i)\">\u00D7</button>\n }\n </div>\n </div>\n }\n <button type=\"button\" class=\"gp-grid-filter-add\" (click)=\"addNumberCondition()\">\n + Add condition\n </button>\n } @else {\n @if (showValuesMode()) {\n <div class=\"gp-grid-filter-mode-toggle\">\n <button\n type=\"button\"\n [class.active]=\"filterMode === 'values'\"\n (click)=\"filterMode = 'values'\">\n Values\n </button>\n <button\n type=\"button\"\n [class.active]=\"filterMode === 'condition'\"\n (click)=\"filterMode = 'condition'\">\n Condition\n </button>\n </div>\n }\n\n @if (filterMode === 'values' && showValuesMode()) {\n <input\n class=\"gp-grid-filter-search\"\n type=\"text\"\n [value]=\"searchText\"\n (input)=\"searchText = $any($event.target).value\"\n placeholder=\"Search...\" />\n <div class=\"gp-grid-filter-actions\">\n <button type=\"button\" (click)=\"selectAll()\">Select All</button>\n <button type=\"button\" (click)=\"deselectAll()\">Deselect All</button>\n </div>\n <div class=\"gp-grid-filter-list\">\n <label class=\"gp-grid-filter-option\">\n <input\n type=\"checkbox\"\n [checked]=\"includeBlanks\"\n (change)=\"includeBlanks = $any($event.target).checked\" />\n <span class=\"gp-grid-filter-blank\">(Blanks)</span>\n </label>\n @for (entry of filteredUniqueEntries(); track entry.key) {\n <label class=\"gp-grid-filter-option\">\n <input\n type=\"checkbox\"\n [checked]=\"selectedValues.has(entry.key)\"\n (change)=\"toggleValue(entry.key, $any($event.target).checked)\" />\n <span>{{ entry.label }}</span>\n </label>\n }\n </div>\n }\n\n @if (filterMode === 'condition') {\n @for (cond of textConditions; track $index; let i = $index) {\n <div class=\"gp-grid-filter-condition\">\n @if (i > 0) {\n <div class=\"gp-grid-filter-combination\">\n <button\n type=\"button\"\n [class.active]=\"textConditions[i - 1]?.nextOperator === 'and'\"\n (click)=\"setTextNextOp(i - 1, 'and')\">\n AND\n </button>\n <button\n type=\"button\"\n [class.active]=\"textConditions[i - 1]?.nextOperator === 'or'\"\n (click)=\"setTextNextOp(i - 1, 'or')\">\n OR\n </button>\n </div>\n }\n <div class=\"gp-grid-filter-row\">\n <select\n [value]=\"cond.operator\"\n (change)=\"onTextOperatorChange(i, $any($event.target).value)\">\n @for (op of textOperators; track op.value) {\n <option [value]=\"op.value\">{{ op.label }}</option>\n }\n </select>\n @if (!isValueLessTextOp(cond.operator)) {\n <input\n class=\"gp-grid-filter-text-input\"\n type=\"text\"\n [value]=\"cond.value\"\n (input)=\"cond.value = $any($event.target).value\"\n placeholder=\"Value\" />\n }\n @if (textConditions.length > 1) {\n <button\n type=\"button\"\n class=\"gp-grid-filter-remove\"\n (click)=\"removeTextCondition(i)\">\u00D7</button>\n }\n </div>\n </div>\n }\n <button type=\"button\" class=\"gp-grid-filter-add\" (click)=\"addTextCondition()\">\n + Add condition\n </button>\n }\n }\n\n <div class=\"gp-grid-filter-buttons\">\n <button type=\"button\" class=\"gp-grid-filter-btn-clear\" (click)=\"handleClear()\">\n Clear\n </button>\n <button type=\"button\" class=\"gp-grid-filter-btn-apply\" (click)=\"handleApply()\">\n Apply\n </button>\n </div>\n </div>\n</div>\n", isInline: true, changeDetection: i0.ChangeDetectionStrategy.Eager });
944
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.7", type: FilterPopupComponent, isStandalone: true, selector: "gp-grid-filter-popup", inputs: { column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, colIndex: { classPropertyName: "colIndex", publicName: "colIndex", isSignal: true, isRequired: true, transformFunction: null }, anchorEl: { classPropertyName: "anchorEl", publicName: "anchorEl", isSignal: true, isRequired: true, transformFunction: null }, distinctValues: { classPropertyName: "distinctValues", publicName: "distinctValues", isSignal: true, isRequired: true, transformFunction: null }, currentFilter: { classPropertyName: "currentFilter", publicName: "currentFilter", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { apply: "apply", close: "close" }, host: { listeners: { "keydown.escape": "onEscape()" } }, viewQueries: [{ propertyName: "popupEl", first: true, predicate: ["popupEl"], descendants: true }], ngImport: i0, template: "\n<div\n #popupEl\n class=\"gp-grid-filter-popup\"\n [style.position]=\"'fixed'\"\n [style.zIndex]=\"10000\"\n [style.top.px]=\"popupTop()\"\n [style.left.px]=\"popupLeft()\"\n [style.minWidth.px]=\"popupMinWidth()\"\n [style.visibility]=\"positioned() ? 'visible' : 'hidden'\"\n (keydown.escape)=\"close.emit()\"\n (click)=\"$event.stopPropagation()\">\n\n <div class=\"gp-grid-filter-header\">\n Filter: {{ column().headerName ?? column().field }}\n </div>\n\n <div [class]=\"'gp-grid-filter-content ' + (isNumberColumn() ? 'gp-grid-filter-number' : 'gp-grid-filter-text')\">\n @if (isNumberColumn()) {\n @for (cond of numberConditions; track $index; let i = $index) {\n <div class=\"gp-grid-filter-condition\">\n @if (i > 0) {\n <div class=\"gp-grid-filter-combination\">\n <button\n type=\"button\"\n [class.active]=\"numberConditions[i - 1]?.nextOperator === 'and'\"\n (click)=\"setNumberNextOp(i - 1, 'and')\">\n AND\n </button>\n <button\n type=\"button\"\n [class.active]=\"numberConditions[i - 1]?.nextOperator === 'or'\"\n (click)=\"setNumberNextOp(i - 1, 'or')\">\n OR\n </button>\n </div>\n }\n <div class=\"gp-grid-filter-row\">\n <select\n [value]=\"cond.operator\"\n (change)=\"onNumberOperatorChange(i, $any($event.target).value)\">\n @for (op of numberOperators; track op.value) {\n <option [value]=\"op.value\">{{ op.label }}</option>\n }\n </select>\n @if (!isValueLessNumberOp(cond.operator)) {\n <input\n type=\"number\"\n [value]=\"cond.value\"\n (input)=\"cond.value = $any($event.target).value\"\n placeholder=\"Value\" />\n @if (cond.operator === 'between') {\n <span class=\"gp-grid-filter-to\">to</span>\n <input\n type=\"number\"\n [value]=\"cond.valueTo\"\n (input)=\"cond.valueTo = $any($event.target).value\"\n placeholder=\"Value\" />\n }\n }\n @if (numberConditions.length > 1) {\n <button\n type=\"button\"\n class=\"gp-grid-filter-remove\"\n (click)=\"removeNumberCondition(i)\">\u00D7</button>\n }\n </div>\n </div>\n }\n <button type=\"button\" class=\"gp-grid-filter-add\" (click)=\"addNumberCondition()\">\n + Add condition\n </button>\n } @else {\n @if (showValuesMode()) {\n <div class=\"gp-grid-filter-mode-toggle\">\n <button\n type=\"button\"\n [class.active]=\"filterMode === 'values'\"\n (click)=\"filterMode = 'values'\">\n Values\n </button>\n <button\n type=\"button\"\n [class.active]=\"filterMode === 'condition'\"\n (click)=\"filterMode = 'condition'\">\n Condition\n </button>\n </div>\n }\n\n @if (filterMode === 'values' && showValuesMode()) {\n <input\n class=\"gp-grid-filter-search\"\n type=\"text\"\n [value]=\"searchText\"\n (input)=\"searchText = $any($event.target).value\"\n placeholder=\"Search...\" />\n <div class=\"gp-grid-filter-actions\">\n <button type=\"button\" (click)=\"selectAll()\">Select All</button>\n <button type=\"button\" (click)=\"deselectAll()\">Deselect All</button>\n </div>\n <div class=\"gp-grid-filter-list\">\n <label class=\"gp-grid-filter-option\">\n <input\n type=\"checkbox\"\n [checked]=\"includeBlanks\"\n (change)=\"includeBlanks = $any($event.target).checked\" />\n <span class=\"gp-grid-filter-blank\">(Blanks)</span>\n </label>\n @for (entry of filteredUniqueEntries(); track entry.key) {\n <label class=\"gp-grid-filter-option\">\n <input\n type=\"checkbox\"\n [checked]=\"selectedValues.has(entry.key)\"\n (change)=\"toggleValue(entry.key, $any($event.target).checked)\" />\n <span>{{ entry.label }}</span>\n </label>\n }\n </div>\n }\n\n @if (filterMode === 'condition') {\n @for (cond of textConditions; track $index; let i = $index) {\n <div class=\"gp-grid-filter-condition\">\n @if (i > 0) {\n <div class=\"gp-grid-filter-combination\">\n <button\n type=\"button\"\n [class.active]=\"textConditions[i - 1]?.nextOperator === 'and'\"\n (click)=\"setTextNextOp(i - 1, 'and')\">\n AND\n </button>\n <button\n type=\"button\"\n [class.active]=\"textConditions[i - 1]?.nextOperator === 'or'\"\n (click)=\"setTextNextOp(i - 1, 'or')\">\n OR\n </button>\n </div>\n }\n <div class=\"gp-grid-filter-row\">\n <select\n [value]=\"cond.operator\"\n (change)=\"onTextOperatorChange(i, $any($event.target).value)\">\n @for (op of textOperators; track op.value) {\n <option [value]=\"op.value\">{{ op.label }}</option>\n }\n </select>\n @if (!isValueLessTextOp(cond.operator)) {\n <input\n class=\"gp-grid-filter-text-input\"\n type=\"text\"\n [value]=\"cond.value\"\n (input)=\"cond.value = $any($event.target).value\"\n placeholder=\"Value\" />\n }\n @if (textConditions.length > 1) {\n <button\n type=\"button\"\n class=\"gp-grid-filter-remove\"\n (click)=\"removeTextCondition(i)\">\u00D7</button>\n }\n </div>\n </div>\n }\n <button type=\"button\" class=\"gp-grid-filter-add\" (click)=\"addTextCondition()\">\n + Add condition\n </button>\n }\n }\n\n <div class=\"gp-grid-filter-buttons\">\n <button type=\"button\" class=\"gp-grid-filter-btn-clear\" (click)=\"handleClear()\">\n Clear\n </button>\n <button type=\"button\" class=\"gp-grid-filter-btn-apply\" (click)=\"handleApply()\">\n Apply\n </button>\n </div>\n </div>\n</div>\n", isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
945
945
  }
946
946
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: FilterPopupComponent, decorators: [{
947
947
  type: Component,
@@ -949,7 +949,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImpor
949
949
  selector: 'gp-grid-filter-popup',
950
950
  standalone: true,
951
951
  imports: [],
952
- changeDetection: ChangeDetectionStrategy.Eager,
952
+ changeDetection: ChangeDetectionStrategy.OnPush,
953
953
  template: FILTER_POPUP_TEMPLATE,
954
954
  }]
955
955
  }], ctorParameters: () => [], propDecorators: { popupEl: [{
@@ -1625,9 +1625,102 @@ const createGridData = (initialData, options) => {
1625
1625
  };
1626
1626
  };
1627
1627
 
1628
+ /**
1629
+ * Injection token holding the options passed to {@link provideGridData}.
1630
+ * Read internally by {@link GridDataService}; consumers should not depend on it directly.
1631
+ */
1632
+ const GRID_DATA_OPTIONS = new InjectionToken("GRID_DATA_OPTIONS");
1633
+ /**
1634
+ * Angular service mirroring the React `useGridData` hook and the Vue
1635
+ * `useGridData` composable. Wraps `createMutableClientDataSource` with
1636
+ * automatic cleanup on component destroy.
1637
+ *
1638
+ * Provided per-component via {@link provideGridData}; injected via
1639
+ * {@link injectGridData} (or `inject(GridDataService)` with a manual cast).
1640
+ */
1641
+ class GridDataService {
1642
+ dataSource;
1643
+ constructor() {
1644
+ const options = inject(GRID_DATA_OPTIONS);
1645
+ this.dataSource = createMutableClientDataSource(options.initialData, {
1646
+ getRowId: options.getRowId,
1647
+ debounceMs: options.debounceMs,
1648
+ useWorker: options.useWorker,
1649
+ parallelSort: options.parallelSort,
1650
+ });
1651
+ }
1652
+ updateRow(id, data) {
1653
+ this.dataSource.updateRow(id, data);
1654
+ }
1655
+ addRows(rows) {
1656
+ this.dataSource.addRows(rows);
1657
+ }
1658
+ removeRows(ids) {
1659
+ this.dataSource.removeRows(ids);
1660
+ }
1661
+ updateCell(id, field, value) {
1662
+ this.dataSource.updateCell(id, field, value);
1663
+ }
1664
+ clear() {
1665
+ this.dataSource.clear();
1666
+ }
1667
+ getRowById(id) {
1668
+ return this.dataSource.getRowById(id);
1669
+ }
1670
+ getTotalRowCount() {
1671
+ return this.dataSource.getTotalRowCount();
1672
+ }
1673
+ flushTransactions() {
1674
+ return this.dataSource.flushTransactions();
1675
+ }
1676
+ ngOnDestroy() {
1677
+ this.dataSource.clear();
1678
+ }
1679
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: GridDataService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1680
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: GridDataService });
1681
+ }
1682
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: GridDataService, decorators: [{
1683
+ type: Injectable
1684
+ }], ctorParameters: () => [] });
1685
+ /**
1686
+ * Typed convenience helper for `inject(GridDataService) as GridDataService<TData>`.
1687
+ *
1688
+ * @example
1689
+ * ```ts
1690
+ * @Component({
1691
+ * providers: [provideGridData<Person>({ getRowId: (r) => r.id, initialData: rows })],
1692
+ * })
1693
+ * export class MyGridComponent {
1694
+ * protected readonly grid = injectGridData<Person>();
1695
+ * }
1696
+ * ```
1697
+ */
1698
+ const injectGridData = () => inject(GridDataService);
1699
+
1700
+ /**
1701
+ * Returns the providers needed to bind a {@link GridDataService} to the
1702
+ * current injector (typically a component's `providers` array). The service
1703
+ * is scoped to that injector — registering it on a parent injector would
1704
+ * silently share the underlying data source between children.
1705
+ *
1706
+ * @example
1707
+ * ```ts
1708
+ * @Component({
1709
+ * providers: [provideGridData<Person>({ getRowId: (r) => r.id, initialData: rows })],
1710
+ * })
1711
+ * export class MyGridComponent {
1712
+ * protected readonly grid = injectGridData<Person>();
1713
+ * }
1714
+ * ```
1715
+ */
1716
+ const provideGridData = (options) => [
1717
+ { provide: GRID_DATA_OPTIONS, useValue: options },
1718
+ GridDataService,
1719
+ ];
1720
+
1628
1721
  /**
1629
1722
  * Generated bundle index. Do not edit.
1630
1723
  */
1631
1724
 
1632
- export { FilterPopupComponent, GpGridComponent, GridBodyComponent, GridHeaderComponent, GridOverlaysComponent, createGridData };
1725
+ export { FilterPopupComponent, GRID_DATA_OPTIONS, GpGridComponent, GridBodyComponent, GridDataService, GridHeaderComponent, GridOverlaysComponent, createGridData, injectGridData, provideGridData };
1633
1726
  //# sourceMappingURL=gp-grid-angular.mjs.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gp-grid/angular",
3
3
  "description": "A high-performance Angular data grid component with virtual scrolling, cell selection, sorting, filtering, and Excel-like editing",
4
- "version": "0.12.1",
4
+ "version": "0.13.0",
5
5
  "license": "Apache-2.0",
6
6
  "module": "fesm2022/gp-grid-angular.mjs",
7
7
  "typings": "types/gp-grid-angular.d.ts",
@@ -51,7 +51,7 @@
51
51
  "@angular/core": ">=18.0.0"
52
52
  },
53
53
  "dependencies": {
54
- "@gp-grid/core": "0.12.1",
54
+ "@gp-grid/core": "0.13.0",
55
55
  "tslib": "^2.8.1"
56
56
  },
57
57
  "sideEffects": false,
@@ -1,5 +1,5 @@
1
1
  import * as _angular_core from '@angular/core';
2
- import { TemplateRef, ElementRef, AfterViewInit, OnDestroy, Signal, OnInit } from '@angular/core';
2
+ import { TemplateRef, ElementRef, AfterViewInit, OnDestroy, Signal, OnInit, InjectionToken, Provider } from '@angular/core';
3
3
  import * as _gp_grid_core from '@gp-grid/core';
4
4
  import { HeaderRendererParams, SortDirection, VisibleColumnInfo, HeaderData, ColumnDefinition, SlotData, CellPosition, CellRange, CellValue, CellRendererParams, EditRendererParams, FillHandlePosition, DragState, ColumnFilterModel, BatchChangeSetters, DataSource, RowId, HighlightingOptions, CellValueChangedEvent, ParallelSortOptions, MutableDataSource } from '@gp-grid/core';
5
5
  export { CellDataType, CellPosition, CellRange, CellRendererParams, CellValue, CellValueChangedEvent, ColumnDefinition, ColumnFilterModel, DataSource, DataSourceRequest, DataSourceResponse, EditRendererParams, FilterCondition, FilterModel, GridCore, GridInstruction, HeaderRendererParams, HighlightContext, HighlightingOptions, MutableDataSource, RowId, SortDirection, SortModel, createClientDataSource, createDataSourceFromArray, createMutableClientDataSource, createServerDataSource } from '@gp-grid/core';
@@ -445,5 +445,78 @@ interface GridDataApi<TData> {
445
445
  */
446
446
  declare const createGridData: <TData = unknown>(initialData: TData[], options: CreateGridDataOptions<TData>) => GridDataApi<TData>;
447
447
 
448
- export { FilterPopupComponent, GpGridComponent, GridBodyComponent, GridHeaderComponent, GridOverlaysComponent, createGridData };
449
- export type { ActiveFilterPopup, AngularColumnDefinition, CellClassFn, CellDoubleClickEvent, CellPointerDownEvent, CellPointerEnterEvent, CellRendererTemplate, CreateGridDataOptions, EditRendererTemplate, EditingCellState, FillHandlePointerDownEvent, FilterPointerDownEvent, GridDataApi, HeaderPointerDownEvent, HeaderRendererTemplate, HeaderSortEvent, ResizePointerDownEvent, RowClassFn };
448
+ interface GridDataOptions<TData> {
449
+ /** Initial rows to seed the data source. */
450
+ initialData: TData[];
451
+ /** Function to extract a unique ID from each row. Required. */
452
+ getRowId: (row: TData) => RowId;
453
+ /** Debounce time for batching transactions in ms. Default 50. */
454
+ debounceMs?: number;
455
+ /** Use Web Worker for sorting large datasets (default: true) */
456
+ useWorker?: boolean;
457
+ /** Options for parallel sorting (only used when useWorker is true) */
458
+ parallelSort?: ParallelSortOptions | false;
459
+ }
460
+ /**
461
+ * Injection token holding the options passed to {@link provideGridData}.
462
+ * Read internally by {@link GridDataService}; consumers should not depend on it directly.
463
+ */
464
+ declare const GRID_DATA_OPTIONS: InjectionToken<GridDataOptions<unknown>>;
465
+ /**
466
+ * Angular service mirroring the React `useGridData` hook and the Vue
467
+ * `useGridData` composable. Wraps `createMutableClientDataSource` with
468
+ * automatic cleanup on component destroy.
469
+ *
470
+ * Provided per-component via {@link provideGridData}; injected via
471
+ * {@link injectGridData} (or `inject(GridDataService)` with a manual cast).
472
+ */
473
+ declare class GridDataService<TData = unknown> implements OnDestroy {
474
+ readonly dataSource: MutableDataSource<TData>;
475
+ constructor();
476
+ updateRow(id: RowId, data: Partial<TData>): void;
477
+ addRows(rows: TData[]): void;
478
+ removeRows(ids: RowId[]): void;
479
+ updateCell(id: RowId, field: string, value: CellValue): void;
480
+ clear(): void;
481
+ getRowById(id: RowId): TData | undefined;
482
+ getTotalRowCount(): number;
483
+ flushTransactions(): Promise<void>;
484
+ ngOnDestroy(): void;
485
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<GridDataService<any>, never>;
486
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<GridDataService<any>>;
487
+ }
488
+ /**
489
+ * Typed convenience helper for `inject(GridDataService) as GridDataService<TData>`.
490
+ *
491
+ * @example
492
+ * ```ts
493
+ * @Component({
494
+ * providers: [provideGridData<Person>({ getRowId: (r) => r.id, initialData: rows })],
495
+ * })
496
+ * export class MyGridComponent {
497
+ * protected readonly grid = injectGridData<Person>();
498
+ * }
499
+ * ```
500
+ */
501
+ declare const injectGridData: <TData>() => GridDataService<TData>;
502
+
503
+ /**
504
+ * Returns the providers needed to bind a {@link GridDataService} to the
505
+ * current injector (typically a component's `providers` array). The service
506
+ * is scoped to that injector — registering it on a parent injector would
507
+ * silently share the underlying data source between children.
508
+ *
509
+ * @example
510
+ * ```ts
511
+ * @Component({
512
+ * providers: [provideGridData<Person>({ getRowId: (r) => r.id, initialData: rows })],
513
+ * })
514
+ * export class MyGridComponent {
515
+ * protected readonly grid = injectGridData<Person>();
516
+ * }
517
+ * ```
518
+ */
519
+ declare const provideGridData: <TData>(options: GridDataOptions<TData>) => Provider[];
520
+
521
+ export { FilterPopupComponent, GRID_DATA_OPTIONS, GpGridComponent, GridBodyComponent, GridDataService, GridHeaderComponent, GridOverlaysComponent, createGridData, injectGridData, provideGridData };
522
+ export type { ActiveFilterPopup, AngularColumnDefinition, CellClassFn, CellDoubleClickEvent, CellPointerDownEvent, CellPointerEnterEvent, CellRendererTemplate, CreateGridDataOptions, EditRendererTemplate, EditingCellState, FillHandlePointerDownEvent, FilterPointerDownEvent, GridDataApi, GridDataOptions, HeaderPointerDownEvent, HeaderRendererTemplate, HeaderSortEvent, ResizePointerDownEvent, RowClassFn };