@toolbox-web/grid-angular 1.3.1 → 1.4.1

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 (102) hide show
  1. package/README.md +146 -54
  2. package/fesm2022/toolbox-web-grid-angular-features-clipboard.mjs +58 -0
  3. package/fesm2022/toolbox-web-grid-angular-features-clipboard.mjs.map +1 -1
  4. package/fesm2022/toolbox-web-grid-angular-features-column-virtualization.mjs +37 -0
  5. package/fesm2022/toolbox-web-grid-angular-features-column-virtualization.mjs.map +1 -1
  6. package/fesm2022/toolbox-web-grid-angular-features-context-menu.mjs +51 -0
  7. package/fesm2022/toolbox-web-grid-angular-features-context-menu.mjs.map +1 -1
  8. package/fesm2022/toolbox-web-grid-angular-features-editing.mjs +115 -1
  9. package/fesm2022/toolbox-web-grid-angular-features-editing.mjs.map +1 -1
  10. package/fesm2022/toolbox-web-grid-angular-features-export.mjs +55 -2
  11. package/fesm2022/toolbox-web-grid-angular-features-export.mjs.map +1 -1
  12. package/fesm2022/toolbox-web-grid-angular-features-filtering.mjs +159 -5
  13. package/fesm2022/toolbox-web-grid-angular-features-filtering.mjs.map +1 -1
  14. package/fesm2022/toolbox-web-grid-angular-features-grouping-columns.mjs +83 -0
  15. package/fesm2022/toolbox-web-grid-angular-features-grouping-columns.mjs.map +1 -1
  16. package/fesm2022/toolbox-web-grid-angular-features-grouping-rows.mjs +82 -0
  17. package/fesm2022/toolbox-web-grid-angular-features-grouping-rows.mjs.map +1 -1
  18. package/fesm2022/toolbox-web-grid-angular-features-master-detail.mjs +115 -2
  19. package/fesm2022/toolbox-web-grid-angular-features-master-detail.mjs.map +1 -1
  20. package/fesm2022/toolbox-web-grid-angular-features-multi-sort.mjs +38 -0
  21. package/fesm2022/toolbox-web-grid-angular-features-multi-sort.mjs.map +1 -1
  22. package/fesm2022/toolbox-web-grid-angular-features-pinned-columns.mjs +37 -0
  23. package/fesm2022/toolbox-web-grid-angular-features-pinned-columns.mjs.map +1 -1
  24. package/fesm2022/toolbox-web-grid-angular-features-pinned-rows.mjs +103 -0
  25. package/fesm2022/toolbox-web-grid-angular-features-pinned-rows.mjs.map +1 -1
  26. package/fesm2022/toolbox-web-grid-angular-features-pivot.mjs +36 -0
  27. package/fesm2022/toolbox-web-grid-angular-features-pivot.mjs.map +1 -1
  28. package/fesm2022/toolbox-web-grid-angular-features-print.mjs +58 -2
  29. package/fesm2022/toolbox-web-grid-angular-features-print.mjs.map +1 -1
  30. package/fesm2022/toolbox-web-grid-angular-features-reorder-columns.mjs +52 -0
  31. package/fesm2022/toolbox-web-grid-angular-features-reorder-columns.mjs.map +1 -1
  32. package/fesm2022/toolbox-web-grid-angular-features-reorder-rows.mjs +41 -0
  33. package/fesm2022/toolbox-web-grid-angular-features-reorder-rows.mjs.map +1 -1
  34. package/fesm2022/toolbox-web-grid-angular-features-responsive.mjs +112 -2
  35. package/fesm2022/toolbox-web-grid-angular-features-responsive.mjs.map +1 -1
  36. package/fesm2022/toolbox-web-grid-angular-features-row-drag-drop.mjs +77 -0
  37. package/fesm2022/toolbox-web-grid-angular-features-row-drag-drop.mjs.map +1 -1
  38. package/fesm2022/toolbox-web-grid-angular-features-selection.mjs +52 -2
  39. package/fesm2022/toolbox-web-grid-angular-features-selection.mjs.map +1 -1
  40. package/fesm2022/toolbox-web-grid-angular-features-server-side.mjs +36 -0
  41. package/fesm2022/toolbox-web-grid-angular-features-server-side.mjs.map +1 -1
  42. package/fesm2022/toolbox-web-grid-angular-features-tooltip.mjs +36 -0
  43. package/fesm2022/toolbox-web-grid-angular-features-tooltip.mjs.map +1 -1
  44. package/fesm2022/toolbox-web-grid-angular-features-tree.mjs +53 -0
  45. package/fesm2022/toolbox-web-grid-angular-features-tree.mjs.map +1 -1
  46. package/fesm2022/toolbox-web-grid-angular-features-undo-redo.mjs +57 -2
  47. package/fesm2022/toolbox-web-grid-angular-features-undo-redo.mjs.map +1 -1
  48. package/fesm2022/toolbox-web-grid-angular-features-visibility.mjs +53 -0
  49. package/fesm2022/toolbox-web-grid-angular-features-visibility.mjs.map +1 -1
  50. package/fesm2022/toolbox-web-grid-angular.mjs +1274 -727
  51. package/fesm2022/toolbox-web-grid-angular.mjs.map +1 -1
  52. package/package.json +1 -1
  53. package/types/toolbox-web-grid-angular-features-clipboard.d.ts +23 -0
  54. package/types/toolbox-web-grid-angular-features-clipboard.d.ts.map +1 -1
  55. package/types/toolbox-web-grid-angular-features-column-virtualization.d.ts +19 -0
  56. package/types/toolbox-web-grid-angular-features-column-virtualization.d.ts.map +1 -1
  57. package/types/toolbox-web-grid-angular-features-context-menu.d.ts +22 -0
  58. package/types/toolbox-web-grid-angular-features-context-menu.d.ts.map +1 -1
  59. package/types/toolbox-web-grid-angular-features-editing.d.ts +32 -0
  60. package/types/toolbox-web-grid-angular-features-editing.d.ts.map +1 -1
  61. package/types/toolbox-web-grid-angular-features-export.d.ts +21 -3
  62. package/types/toolbox-web-grid-angular-features-export.d.ts.map +1 -1
  63. package/types/toolbox-web-grid-angular-features-filtering.d.ts +67 -6
  64. package/types/toolbox-web-grid-angular-features-filtering.d.ts.map +1 -1
  65. package/types/toolbox-web-grid-angular-features-grouping-columns.d.ts +19 -0
  66. package/types/toolbox-web-grid-angular-features-grouping-columns.d.ts.map +1 -1
  67. package/types/toolbox-web-grid-angular-features-grouping-rows.d.ts +25 -0
  68. package/types/toolbox-web-grid-angular-features-grouping-rows.d.ts.map +1 -1
  69. package/types/toolbox-web-grid-angular-features-master-detail.d.ts +23 -0
  70. package/types/toolbox-web-grid-angular-features-master-detail.d.ts.map +1 -1
  71. package/types/toolbox-web-grid-angular-features-multi-sort.d.ts +19 -0
  72. package/types/toolbox-web-grid-angular-features-multi-sort.d.ts.map +1 -1
  73. package/types/toolbox-web-grid-angular-features-pinned-columns.d.ts +18 -0
  74. package/types/toolbox-web-grid-angular-features-pinned-columns.d.ts.map +1 -1
  75. package/types/toolbox-web-grid-angular-features-pinned-rows.d.ts +19 -0
  76. package/types/toolbox-web-grid-angular-features-pinned-rows.d.ts.map +1 -1
  77. package/types/toolbox-web-grid-angular-features-pivot.d.ts +19 -0
  78. package/types/toolbox-web-grid-angular-features-pivot.d.ts.map +1 -1
  79. package/types/toolbox-web-grid-angular-features-print.d.ts +22 -3
  80. package/types/toolbox-web-grid-angular-features-print.d.ts.map +1 -1
  81. package/types/toolbox-web-grid-angular-features-reorder-columns.d.ts +22 -0
  82. package/types/toolbox-web-grid-angular-features-reorder-columns.d.ts.map +1 -1
  83. package/types/toolbox-web-grid-angular-features-reorder-rows.d.ts +19 -0
  84. package/types/toolbox-web-grid-angular-features-reorder-rows.d.ts.map +1 -1
  85. package/types/toolbox-web-grid-angular-features-responsive.d.ts +22 -0
  86. package/types/toolbox-web-grid-angular-features-responsive.d.ts.map +1 -1
  87. package/types/toolbox-web-grid-angular-features-row-drag-drop.d.ts +27 -0
  88. package/types/toolbox-web-grid-angular-features-row-drag-drop.d.ts.map +1 -1
  89. package/types/toolbox-web-grid-angular-features-selection.d.ts +21 -3
  90. package/types/toolbox-web-grid-angular-features-selection.d.ts.map +1 -1
  91. package/types/toolbox-web-grid-angular-features-server-side.d.ts +19 -0
  92. package/types/toolbox-web-grid-angular-features-server-side.d.ts.map +1 -1
  93. package/types/toolbox-web-grid-angular-features-tooltip.d.ts +19 -0
  94. package/types/toolbox-web-grid-angular-features-tooltip.d.ts.map +1 -1
  95. package/types/toolbox-web-grid-angular-features-tree.d.ts +22 -0
  96. package/types/toolbox-web-grid-angular-features-tree.d.ts.map +1 -1
  97. package/types/toolbox-web-grid-angular-features-undo-redo.d.ts +22 -3
  98. package/types/toolbox-web-grid-angular-features-undo-redo.d.ts.map +1 -1
  99. package/types/toolbox-web-grid-angular-features-visibility.d.ts +22 -0
  100. package/types/toolbox-web-grid-angular-features-visibility.d.ts.map +1 -1
  101. package/types/toolbox-web-grid-angular.d.ts +939 -128
  102. package/types/toolbox-web-grid-angular.d.ts.map +1 -1
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, ElementRef, contentChild, TemplateRef, effect, Directive, input, DestroyRef, InjectionToken, Injectable, makeEnvironmentProviders, createComponent, signal, afterNextRender, computed, output, EnvironmentInjector, ApplicationRef, ViewContainerRef } from '@angular/core';
2
+ import { inject, ElementRef, contentChild, TemplateRef, effect, Directive, DestroyRef, input, InjectionToken, Injectable, makeEnvironmentProviders, createComponent, signal, afterNextRender, computed, output, EnvironmentInjector, ApplicationRef, ViewContainerRef } from '@angular/core';
3
3
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
4
4
  import { FormGroup } from '@angular/forms';
5
5
  import { startWith, debounceTime } from 'rxjs/operators';
@@ -18,6 +18,7 @@ import { DataGridElement } from '@toolbox-web/grid';
18
18
  *
19
19
  * Also checks if it's an ES6 class (vs function) by inspecting the
20
20
  * string representation.
21
+ * @since 0.3.0
21
22
  */
22
23
  function isComponentClass(value) {
23
24
  if (typeof value !== 'function' || value.prototype === undefined) {
@@ -85,6 +86,14 @@ function getEditorTemplate(element) {
85
86
  * ```
86
87
  *
87
88
  * @category Directive
89
+ *
90
+ * MOVE-IN-V2: this directive (and its companion `GridEditorContext` type and
91
+ * `getEditorTemplate` helper) will physically move into
92
+ * `@toolbox-web/grid-angular/features/editing` in v2.0.0; the deprecated
93
+ * re-exports from the main `@toolbox-web/grid-angular` entry will be removed
94
+ * at the same time. Consumers should already be importing from the feature
95
+ * entry.
96
+ * @since 0.1.0
88
97
  */
89
98
  class GridColumnEditor {
90
99
  elementRef = inject((ElementRef));
@@ -159,6 +168,7 @@ function getViewTemplate(element) {
159
168
  * ```
160
169
  *
161
170
  * @category Directive
171
+ * @since 0.1.0
162
172
  */
163
173
  class GridColumnView {
164
174
  elementRef = inject((ElementRef));
@@ -189,124 +199,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
189
199
  args: [{ selector: 'tbw-grid-column-view' }]
190
200
  }], propDecorators: { template: [{ type: i0.ContentChild, args: [i0.forwardRef(() => TemplateRef), { isSignal: true }] }] } });
191
201
 
192
- // Global registry mapping DOM elements to their templates
193
- const detailTemplateRegistry = new Map();
194
- /**
195
- * Gets the detail template registered for a given grid element.
196
- * Used by AngularGridAdapter to retrieve templates at render time.
197
- */
198
- function getDetailTemplate(gridElement) {
199
- // Look for tbw-grid-detail child and get its template
200
- const detailElement = gridElement.querySelector('tbw-grid-detail');
201
- if (detailElement) {
202
- return detailTemplateRegistry.get(detailElement);
203
- }
204
- return undefined;
205
- }
206
- /**
207
- * Gets the configuration for the detail view.
208
- */
209
- function getDetailConfig(gridElement) {
210
- const detailElement = gridElement.querySelector('tbw-grid-detail');
211
- if (detailElement) {
212
- const animationAttr = detailElement.getAttribute('animation');
213
- let animation = 'slide';
214
- if (animationAttr === 'false') {
215
- animation = false;
216
- }
217
- else if (animationAttr === 'fade') {
218
- animation = 'fade';
219
- }
220
- return {
221
- showExpandColumn: detailElement.getAttribute('showExpandColumn') !== 'false',
222
- animation,
223
- };
224
- }
225
- return undefined;
226
- }
227
- /**
228
- * Directive that captures an `<ng-template>` for use as a master-detail row renderer.
229
- *
230
- * This enables declarative Angular component usage for expandable detail rows
231
- * that appear below the main row when expanded.
232
- *
233
- * ## Usage
234
- *
235
- * ```html
236
- * <tbw-grid [rows]="rows" [gridConfig]="config">
237
- * <tbw-grid-detail [showExpandColumn]="true" animation="slide">
238
- * <ng-template let-row>
239
- * <app-detail-panel [employee]="row" />
240
- * </ng-template>
241
- * </tbw-grid-detail>
242
- * </tbw-grid>
243
- * ```
244
- *
245
- * The template context provides:
246
- * - `$implicit` / `row`: The full row data object
247
- *
248
- * Import the directive in your component:
249
- *
250
- * ```typescript
251
- * import { GridDetailView } from '@toolbox-web/grid-angular';
252
- *
253
- * @Component({
254
- * imports: [GridDetailView],
255
- * // ...
256
- * })
257
- * ```
258
- *
259
- * @example
260
- * ```html
261
- * <tbw-grid [rows]="rows" [gridConfig]="config">
262
- * <tbw-grid-detail [showExpandColumn]="true" animation="slide">
263
- * <ng-template let-row>
264
- * <app-detail-panel [employee]="row" />
265
- * </ng-template>
266
- * </tbw-grid-detail>
267
- * </tbw-grid>
268
- * ```
269
- *
270
- * @category Directive
271
- */
272
- class GridDetailView {
273
- elementRef = inject((ElementRef));
274
- /** Whether to show the expand/collapse column. Default: true */
275
- showExpandColumn = input(true, ...(ngDevMode ? [{ debugName: "showExpandColumn" }] : /* istanbul ignore next */ []));
276
- /** Animation style for expand/collapse. Default: 'slide' */
277
- animation = input('slide', ...(ngDevMode ? [{ debugName: "animation" }] : /* istanbul ignore next */ []));
278
- /**
279
- * Query for the ng-template content child.
280
- */
281
- template = contentChild((TemplateRef), ...(ngDevMode ? [{ debugName: "template" }] : /* istanbul ignore next */ []));
282
- /** Effect that triggers when the template is available */
283
- onTemplateReceived = effect(() => {
284
- const template = this.template();
285
- if (template) {
286
- // Register the template for this element
287
- detailTemplateRegistry.set(this.elementRef.nativeElement, template);
288
- }
289
- }, ...(ngDevMode ? [{ debugName: "onTemplateReceived" }] : /* istanbul ignore next */ []));
290
- /**
291
- * Static type guard for template context.
292
- * Enables type inference in templates.
293
- */
294
- static ngTemplateContextGuard(dir, ctx) {
295
- return true;
296
- }
297
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: GridDetailView, deps: [], target: i0.ɵɵFactoryTarget.Directive });
298
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "21.2.5", type: GridDetailView, isStandalone: true, selector: "tbw-grid-detail", inputs: { showExpandColumn: { classPropertyName: "showExpandColumn", publicName: "showExpandColumn", isSignal: true, isRequired: false, transformFunction: null }, animation: { classPropertyName: "animation", publicName: "animation", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "template", first: true, predicate: (TemplateRef), descendants: true, isSignal: true }], ngImport: i0 });
299
- }
300
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: GridDetailView, decorators: [{
301
- type: Directive,
302
- args: [{ selector: 'tbw-grid-detail' }]
303
- }], propDecorators: { showExpandColumn: [{ type: i0.Input, args: [{ isSignal: true, alias: "showExpandColumn", required: false }] }], animation: [{ type: i0.Input, args: [{ isSignal: true, alias: "animation", required: false }] }], template: [{ type: i0.ContentChild, args: [i0.forwardRef(() => TemplateRef), { isSignal: true }] }] } });
304
-
305
202
  // Symbol for storing form context on the grid element
306
203
  const FORM_ARRAY_CONTEXT$1 = Symbol('formArrayContext');
307
204
  /**
308
205
  * Gets the FormArrayContext from a grid element, if present.
309
206
  * @internal
207
+ * @since 0.5.0
310
208
  */
311
209
  function getFormArrayContext(gridElement) {
312
210
  return gridElement[FORM_ARRAY_CONTEXT$1];
@@ -370,6 +268,14 @@ function getFormArrayContext(gridElement) {
370
268
  * ```
371
269
  *
372
270
  * @category Directive
271
+ *
272
+ * MOVE-IN-V2: this directive (and its `FormArrayContext` type and
273
+ * `getFormArrayContext` helper) will physically move into
274
+ * `@toolbox-web/grid-angular/features/editing` in v2.0.0; the deprecated
275
+ * re-exports from the main `@toolbox-web/grid-angular` entry will be removed
276
+ * at the same time. Consumers should already be importing from the feature
277
+ * entry.
278
+ * @since 0.5.0
373
279
  */
374
280
  class GridFormArray {
375
281
  destroyRef = inject(DestroyRef);
@@ -785,88 +691,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
785
691
  }]
786
692
  }], propDecorators: { formArray: [{ type: i0.Input, args: [{ isSignal: true, alias: "formArray", required: true }] }], syncValidation: [{ type: i0.Input, args: [{ isSignal: true, alias: "syncValidation", required: false }] }] } });
787
693
 
788
- /**
789
- * Registry to store responsive card templates by grid element.
790
- * Used by AngularGridAdapter to create card renderers.
791
- */
792
- const responsiveCardTemplateRegistry = new Map();
793
- /**
794
- * Retrieves the responsive card template for a grid element.
795
- *
796
- * @param gridElement - The grid element to look up
797
- * @returns The template reference or undefined if not found
798
- */
799
- function getResponsiveCardTemplate(gridElement) {
800
- // Find the tbw-grid-responsive-card element inside the grid
801
- const cardElement = gridElement.querySelector('tbw-grid-responsive-card');
802
- if (!cardElement)
803
- return undefined;
804
- return responsiveCardTemplateRegistry.get(cardElement);
805
- }
806
- /**
807
- * Directive for providing custom Angular templates for responsive card layout.
808
- *
809
- * Use this directive to define how each row should render when the grid
810
- * is in responsive/mobile mode. The template receives the row data and index.
811
- *
812
- * ## Usage
813
- *
814
- * ```html
815
- * <tbw-grid [rows]="employees">
816
- * <tbw-grid-responsive-card>
817
- * <ng-template let-employee let-idx="index">
818
- * <div class="employee-card">
819
- * <img [src]="employee.avatar" alt="">
820
- * <div class="info">
821
- * <strong>{{ employee.name }}</strong>
822
- * <span>{{ employee.department }}</span>
823
- * </div>
824
- * </div>
825
- * </ng-template>
826
- * </tbw-grid-responsive-card>
827
- * </tbw-grid>
828
- * ```
829
- *
830
- * ## Important Notes
831
- *
832
- * - The ResponsivePlugin must be added to your grid config
833
- * - The Grid directive will automatically configure the plugin's cardRenderer
834
- * - Template context provides `$implicit` (row), `row`, and `index`
835
- *
836
- * @see ResponsivePlugin
837
- * @category Directive
838
- */
839
- class GridResponsiveCard {
840
- elementRef = inject((ElementRef));
841
- /**
842
- * The ng-template containing the card content.
843
- */
844
- template = contentChild((TemplateRef), ...(ngDevMode ? [{ debugName: "template" }] : /* istanbul ignore next */ []));
845
- /**
846
- * Effect that registers the template when it becomes available.
847
- */
848
- onTemplateReceived = effect(() => {
849
- const template = this.template();
850
- if (template) {
851
- responsiveCardTemplateRegistry.set(this.elementRef.nativeElement, template);
852
- }
853
- }, ...(ngDevMode ? [{ debugName: "onTemplateReceived" }] : /* istanbul ignore next */ []));
854
- /**
855
- * Type guard for template context inference.
856
- */
857
- static ngTemplateContextGuard(_directive, context) {
858
- return true;
859
- }
860
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: GridResponsiveCard, deps: [], target: i0.ɵɵFactoryTarget.Directive });
861
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "21.2.5", type: GridResponsiveCard, isStandalone: true, selector: "tbw-grid-responsive-card", queries: [{ propertyName: "template", first: true, predicate: (TemplateRef), descendants: true, isSignal: true }], ngImport: i0 });
862
- }
863
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: GridResponsiveCard, decorators: [{
864
- type: Directive,
865
- args: [{
866
- selector: 'tbw-grid-responsive-card',
867
- }]
868
- }], propDecorators: { template: [{ type: i0.ContentChild, args: [i0.forwardRef(() => TemplateRef), { isSignal: true }] }] } });
869
-
870
694
  // Global registry mapping DOM elements to their templates
871
695
  const toolPanelTemplateRegistry = new Map();
872
696
  /**
@@ -941,6 +765,7 @@ function getToolPanelElements(gridElement) {
941
765
  * ```
942
766
  *
943
767
  * @category Directive
768
+ * @since 0.1.0
944
769
  */
945
770
  class GridToolPanel {
946
771
  elementRef = inject((ElementRef));
@@ -1077,6 +902,7 @@ function getStructuralEditorTemplate(columnElement) {
1077
902
  * ```
1078
903
  *
1079
904
  * @category Directive
905
+ * @since 0.1.1
1080
906
  */
1081
907
  class TbwRenderer {
1082
908
  template = inject((TemplateRef));
@@ -1173,6 +999,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
1173
999
  * ```
1174
1000
  *
1175
1001
  * @category Directive
1002
+ *
1003
+ * MOVE-IN-V2: this directive (and its `StructuralEditorContext` type) will
1004
+ * physically move into `@toolbox-web/grid-angular/features/editing` in v2.0.0;
1005
+ * the deprecated re-export from the main `@toolbox-web/grid-angular` entry
1006
+ * will be removed at the same time. Consumers should already be importing
1007
+ * from the feature entry. (`TbwRenderer` stays in the main entry — it is
1008
+ * editor-agnostic.)
1009
+ * @since 0.1.1
1176
1010
  */
1177
1011
  class TbwEditor {
1178
1012
  template = inject((TemplateRef));
@@ -1214,6 +1048,72 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
1214
1048
  args: [{ selector: '[tbwEditor]' }]
1215
1049
  }], ctorParameters: () => [] });
1216
1050
 
1051
+ /**
1052
+ * Editor mount hook registry — append-only hooks called when an Angular-managed
1053
+ * editor host is mounted into the DOM with a known owner grid.
1054
+ *
1055
+ * This is the augmentation point that lets feature secondary entries
1056
+ * (e.g. `@toolbox-web/grid-angular/features/editing`) install per-editor
1057
+ * lifecycle behaviour (such as the `before-edit-close` blur bridge)
1058
+ * without coupling the central adapter file to the editing feature.
1059
+ * Mirrors React's and Vue's `editor-mount-hooks` and how core grid plugins
1060
+ * augment the grid via `registerPlugin()`.
1061
+ *
1062
+ * @packageDocumentation
1063
+ * @internal
1064
+ */
1065
+ const editorMountHooks = [];
1066
+ /**
1067
+ * Install an editor-mount hook. Called by feature secondary entries
1068
+ * (e.g. `features/editing`) on import.
1069
+ *
1070
+ * @internal Plugin API
1071
+ * @since 1.4.0
1072
+ */
1073
+ function registerEditorMountHook(hook) {
1074
+ editorMountHooks.push(hook);
1075
+ }
1076
+ /**
1077
+ * Run all registered editor-mount hooks for a freshly mounted editor.
1078
+ * Returns a combined teardown that invokes each hook's teardown in
1079
+ * registration order.
1080
+ *
1081
+ * @internal Adapter use only.
1082
+ */
1083
+ function notifyEditorMounted(container, gridEl) {
1084
+ const teardowns = [];
1085
+ for (const hook of editorMountHooks) {
1086
+ const teardown = hook({ container, gridEl });
1087
+ if (teardown)
1088
+ teardowns.push(teardown);
1089
+ }
1090
+ return () => {
1091
+ for (const teardown of teardowns)
1092
+ teardown();
1093
+ };
1094
+ }
1095
+ /**
1096
+ * Returns a function that, when invoked, blurs the focused input/textarea/select
1097
+ * inside `host` (if any). Used by the `before-edit-close` bridge installed by
1098
+ * `@toolbox-web/grid-angular/features/editing` so editors that commit on
1099
+ * `(blur)` flush their pending value before the cell DOM is torn down by Tab /
1100
+ * programmatic row exit.
1101
+ * @internal
1102
+ * @since 1.4.0
1103
+ */
1104
+ function makeFlushFocusedInput(host) {
1105
+ return () => {
1106
+ const focused = host.ownerDocument.activeElement;
1107
+ if (focused &&
1108
+ host.contains(focused) &&
1109
+ (focused instanceof HTMLInputElement ||
1110
+ focused instanceof HTMLTextAreaElement ||
1111
+ focused instanceof HTMLSelectElement)) {
1112
+ focused.blur();
1113
+ }
1114
+ };
1115
+ }
1116
+
1217
1117
  /**
1218
1118
  * Editor Wiring Helpers
1219
1119
  *
@@ -1304,6 +1204,7 @@ function wireEditorCallbacks(hostElement, instance, commit, cancel) {
1304
1204
  */
1305
1205
  /**
1306
1206
  * Injection token for providing type defaults at app level.
1207
+ * @since 0.3.0
1307
1208
  */
1308
1209
  const GRID_TYPE_DEFAULTS = new InjectionToken('GRID_TYPE_DEFAULTS');
1309
1210
  /**
@@ -1341,6 +1242,7 @@ const GRID_TYPE_DEFAULTS = new InjectionToken('GRID_TYPE_DEFAULTS');
1341
1242
  * }
1342
1243
  * }
1343
1244
  * ```
1245
+ * @since 0.3.0
1344
1246
  */
1345
1247
  class GridTypeRegistry {
1346
1248
  defaults = new Map();
@@ -1430,11 +1332,162 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
1430
1332
  * ]
1431
1333
  * };
1432
1334
  * ```
1335
+ * @since 0.3.0
1433
1336
  */
1434
1337
  function provideGridTypeDefaults(defaults) {
1435
1338
  return makeEnvironmentProviders([{ provide: GRID_TYPE_DEFAULTS, useValue: defaults }]);
1436
1339
  }
1437
1340
 
1341
+ /**
1342
+ * Append-only bridge registries used by `@toolbox-web/grid-angular`.
1343
+ *
1344
+ * These let feature secondary entries plug into the central `GridAdapter`
1345
+ * without the adapter needing to know about them. Mirrors React's and Vue's
1346
+ * `register*Bridge` modules and how core grid plugins augment the grid via
1347
+ * `registerPlugin()`.
1348
+ *
1349
+ * This module is deliberately framework-free: it holds plain module-level
1350
+ * `let` state and never imports from `@angular/core` or from
1351
+ * `@toolbox-web/grid/plugins/...`. Feature subpaths import the setters via
1352
+ * the `@toolbox-web/grid-angular` package barrel (relative imports outside
1353
+ * a secondary entry's `rootDir` are forbidden by ng-packagr).
1354
+ *
1355
+ * @internal
1356
+ */
1357
+ let detailRendererBridge = null;
1358
+ let responsiveCardRendererBridge = null;
1359
+ let filterPanelTypeDefaultBridge = null;
1360
+ /**
1361
+ * Install the master-detail row-renderer bridge. Called once on import by
1362
+ * `@toolbox-web/grid-angular/features/master-detail`.
1363
+ * @internal Plugin API
1364
+ * @since 1.4.0
1365
+ */
1366
+ function registerDetailRendererBridge(bridge) {
1367
+ detailRendererBridge = bridge;
1368
+ }
1369
+ /**
1370
+ * Install the responsive-card row-renderer bridge. Called once on import by
1371
+ * `@toolbox-web/grid-angular/features/responsive`.
1372
+ * @internal Plugin API
1373
+ * @since 1.4.0
1374
+ */
1375
+ function registerResponsiveCardRendererBridge(bridge) {
1376
+ responsiveCardRendererBridge = bridge;
1377
+ }
1378
+ /**
1379
+ * Install the type-default `filterPanelRenderer` wrapper. Called once on import
1380
+ * by `@toolbox-web/grid-angular/features/filtering`. Without this bridge,
1381
+ * type-default and grid-config-level component-class filterPanelRenderers are
1382
+ * silently dropped — same precondition as the FilteringPlugin (TBW031).
1383
+ * @internal Plugin API
1384
+ * @since 1.4.0
1385
+ */
1386
+ function registerFilterPanelTypeDefaultBridge(bridge) {
1387
+ filterPanelTypeDefaultBridge = bridge;
1388
+ }
1389
+ /** @internal Adapter use only. */
1390
+ function getDetailRendererBridge() {
1391
+ return detailRendererBridge;
1392
+ }
1393
+ /** @internal Adapter use only. */
1394
+ function getResponsiveCardRendererBridge() {
1395
+ return responsiveCardRendererBridge;
1396
+ }
1397
+ /** @internal Adapter use only. */
1398
+ function getFilterPanelTypeDefaultBridge() {
1399
+ return filterPanelTypeDefaultBridge;
1400
+ }
1401
+
1402
+ /**
1403
+ * Internal registries used by `@toolbox-web/grid-angular`.
1404
+ *
1405
+ * Two extension points are exposed so individual feature secondary entries
1406
+ * (e.g. `@toolbox-web/grid-angular/features/master-detail`) can plug into
1407
+ * the core `Grid` directive without the directive needing to know about them.
1408
+ *
1409
+ * - {@link registerTemplateBridge} — runs in `ngAfterContentInit` once Angular
1410
+ * templates inside the grid have been registered. Used to discover light-DOM
1411
+ * slot elements (`<tbw-grid-detail>`, `<tbw-grid-responsive-card>`, …) and
1412
+ * wire them to the corresponding plugin's renderer setter.
1413
+ * - {@link registerFeatureConfigPreprocessor} — runs inside the directive's
1414
+ * feature-plugin builder before `createPluginFromFeature` is called. Lets a
1415
+ * feature transform its config object (e.g. to convert Angular component
1416
+ * classes embedded in `customPanels` to renderer functions).
1417
+ *
1418
+ * Both registries are append-only and module-scoped: they are populated by
1419
+ * side-effect imports of feature secondary entries and consumed by the core
1420
+ * `Grid` directive. Order is insertion order.
1421
+ *
1422
+ * @internal — public for cross-entry-point use; not part of the supported API.
1423
+ */
1424
+ const templateBridges = [];
1425
+ /**
1426
+ * Register a template bridge. Called by feature secondary entries at module
1427
+ * load (e.g. `import '@toolbox-web/grid-angular/features/master-detail'`).
1428
+ *
1429
+ * Bridges are append-only and never deduplicated; calling registration twice
1430
+ * for the same feature module is safe because module imports are deduplicated
1431
+ * by the JS loader.
1432
+ *
1433
+ * @internal
1434
+ * @since 1.4.0
1435
+ */
1436
+ function registerTemplateBridge(bridge) {
1437
+ templateBridges.push(bridge);
1438
+ }
1439
+ /**
1440
+ * Run all registered template bridges for a grid. The directive calls this
1441
+ * from `ngAfterContentInit` after `refreshColumns()`. Bridges run concurrently;
1442
+ * errors thrown by one bridge do not stop the others.
1443
+ *
1444
+ * @internal
1445
+ * @since 1.4.0
1446
+ */
1447
+ function runTemplateBridges(ctx) {
1448
+ for (const bridge of templateBridges) {
1449
+ try {
1450
+ const result = bridge(ctx);
1451
+ if (result && typeof result.catch === 'function') {
1452
+ result.catch((err) => {
1453
+ // eslint-disable-next-line no-console
1454
+ console.error('[tbw-grid-angular] template bridge threw:', err);
1455
+ });
1456
+ }
1457
+ }
1458
+ catch (err) {
1459
+ // eslint-disable-next-line no-console
1460
+ console.error('[tbw-grid-angular] template bridge threw:', err);
1461
+ }
1462
+ }
1463
+ }
1464
+ const featureConfigPreprocessors = new Map();
1465
+ /**
1466
+ * Register a feature config preprocessor. Last registration wins (subsequent
1467
+ * imports of the same feature module re-register the same function, which is
1468
+ * a no-op).
1469
+ *
1470
+ * @internal
1471
+ * @since 1.4.0
1472
+ */
1473
+ function registerFeatureConfigPreprocessor(name, fn) {
1474
+ featureConfigPreprocessors.set(name, fn);
1475
+ }
1476
+ /**
1477
+ * Look up the preprocessor for a feature, if any.
1478
+ *
1479
+ * @internal
1480
+ * @since 1.4.0
1481
+ */
1482
+ function getFeatureConfigPreprocessor(name) {
1483
+ return featureConfigPreprocessors.get(name);
1484
+ }
1485
+
1486
+ // #region Feature bridge registries
1487
+ // (Storage lives in `./internal/feature-bridges` so feature subpaths can
1488
+ // install bridges without pulling Angular runtime into specs that mock
1489
+ // `@angular/core` — see filtering feature spec.)
1490
+ // #endregion
1438
1491
  /**
1439
1492
  * Helper to get view template from either structural directive or nested directive.
1440
1493
  */
@@ -1544,6 +1597,7 @@ function syncRootNodes(viewRef, container) {
1544
1597
  container.replaceChildren(...rootNodes);
1545
1598
  }
1546
1599
  }
1600
+ /** @since 0.11.0 */
1547
1601
  class GridAdapter {
1548
1602
  injector;
1549
1603
  appRef;
@@ -1555,20 +1609,16 @@ class GridAdapter {
1555
1609
  /** Editor-specific component refs tracked separately for per-cell cleanup via releaseCell. */
1556
1610
  editorComponentRefs = [];
1557
1611
  /**
1558
- * Per-editor `before-edit-close` listener teardown functions, keyed by
1559
- * editor host element.
1560
- *
1561
- * The grid's editing plugin emits `before-edit-close` on the host
1562
- * `<tbw-grid>` before tearing down a row's managed editors. Angular
1563
- * editors that commit on `(blur)` rely on the focused input firing `blur`
1564
- * naturally, but Tab / programmatic row exit rebuilds the cell DOM
1565
- * synchronously without giving the focused input a chance to blur first
1566
- * — pending input is silently discarded.
1612
+ * Per-editor mount-hook teardown functions, keyed by editor host element.
1567
1613
  *
1568
- * Mirror of Vue's `editorBeforeCloseUnsubs` and React's
1569
- * `wrapReactEditor` queueMicrotask bridge.
1614
+ * Populated by {@link runEditorMountHooks} (which invokes
1615
+ * {@link notifyEditorMounted}) and torn down per-cell from
1616
+ * {@link releaseCell}, with full sweep on {@link destroy}. The actual
1617
+ * lifecycle behaviour is supplied by feature secondary entries (e.g.
1618
+ * `@toolbox-web/grid-angular/features/editing` installs the
1619
+ * `before-edit-close` blur bridge).
1570
1620
  */
1571
- editorBeforeCloseUnsubs = new Map();
1621
+ editorMountTeardowns = new Map();
1572
1622
  typeRegistry = null;
1573
1623
  constructor(injector, appRef, viewContainerRef) {
1574
1624
  this.injector = injector;
@@ -1656,9 +1706,13 @@ class GridAdapter {
1656
1706
  if (config.editor && isComponentClass(config.editor)) {
1657
1707
  processedConfig.editor = this.createComponentEditor(config.editor);
1658
1708
  }
1659
- // Convert filterPanelRenderer component class to function
1709
+ // Convert filterPanelRenderer component class to function via the
1710
+ // filtering feature bridge. Without `@toolbox-web/grid-angular/features/filtering`
1711
+ // imported, component-class filterPanelRenderers are dropped silently.
1660
1712
  if (config.filterPanelRenderer && isComponentClass(config.filterPanelRenderer)) {
1661
- processedConfig.filterPanelRenderer = this.createComponentFilterPanelRenderer(config.filterPanelRenderer);
1713
+ const wrapped = getFilterPanelTypeDefaultBridge()?.(config.filterPanelRenderer, this);
1714
+ if (wrapped)
1715
+ processedConfig.filterPanelRenderer = wrapped;
1662
1716
  }
1663
1717
  processed[type] = processedConfig;
1664
1718
  }
@@ -1843,7 +1897,7 @@ class GridAdapter {
1843
1897
  const container = document.createElement('span');
1844
1898
  container.style.display = 'contents';
1845
1899
  syncRootNodes(viewRef, container);
1846
- this.attachBeforeEditCloseFlush(container);
1900
+ this.runEditorMountHooks(container);
1847
1901
  // Auto-wire: Listen for commit/cancel events on the rendered component.
1848
1902
  // This allows components to just emit (commit) and (cancel) without
1849
1903
  // requiring explicit template bindings like (commit)="onCommit($event)".
@@ -1869,101 +1923,40 @@ class GridAdapter {
1869
1923
  };
1870
1924
  }
1871
1925
  /**
1872
- * Creates a detail renderer function for MasterDetailPlugin.
1873
- * Renders Angular templates for expandable detail rows.
1926
+ * Creates a detail renderer function for MasterDetailPlugin. Delegates to
1927
+ * the bridge installed by `@toolbox-web/grid-angular/features/master-detail`.
1928
+ * Returns undefined if the feature is not imported or no `<tbw-grid-detail>`
1929
+ * template is registered for this grid.
1874
1930
  */
1875
1931
  createDetailRenderer(gridElement) {
1876
- const template = getDetailTemplate(gridElement);
1877
- if (!template) {
1878
- return undefined;
1879
- }
1880
- return (row) => {
1881
- // Create the context for the template
1882
- const context = {
1883
- $implicit: row,
1884
- row: row,
1885
- };
1886
- // Create embedded view from template
1887
- const viewRef = this.viewContainerRef.createEmbeddedView(template, context);
1888
- this.viewRefs.push(viewRef);
1889
- // Trigger change detection
1890
- viewRef.detectChanges();
1891
- // Create a container for the root nodes
1892
- const container = document.createElement('div');
1893
- viewRef.rootNodes.forEach((node) => container.appendChild(node));
1894
- return container;
1895
- };
1932
+ return getDetailRendererBridge()?.(gridElement, this);
1896
1933
  }
1897
1934
  /**
1898
- * Framework adapter hook called by MasterDetailPlugin during attach().
1899
- * Parses the <tbw-grid-detail> element and returns an Angular template-based renderer.
1900
- *
1901
- * This enables MasterDetailPlugin to automatically use Angular templates
1902
- * without manual configuration in the Grid directive.
1935
+ * FrameworkAdapter hook called by MasterDetailPlugin during attach(). Delegates
1936
+ * to {@link createDetailRenderer} (bridge installed by master-detail feature).
1903
1937
  */
1904
1938
  parseDetailElement(detailElement) {
1905
- // Get the template from the registry for this detail element
1906
- const template = getDetailTemplate(detailElement.closest('tbw-grid'));
1907
- if (!template) {
1939
+ const gridElement = detailElement.closest('tbw-grid');
1940
+ if (!gridElement)
1908
1941
  return undefined;
1909
- }
1910
- // Return a renderer function that creates embedded views
1911
- // Note: rowIndex is part of the MasterDetailPlugin detailRenderer signature but not needed here
1912
- return (row) => {
1913
- const context = {
1914
- $implicit: row,
1915
- row: row,
1916
- };
1917
- const viewRef = this.viewContainerRef.createEmbeddedView(template, context);
1918
- this.viewRefs.push(viewRef);
1919
- viewRef.detectChanges();
1920
- const container = document.createElement('div');
1921
- viewRef.rootNodes.forEach((node) => container.appendChild(node));
1922
- return container;
1923
- };
1942
+ return getDetailRendererBridge()?.(gridElement, this);
1924
1943
  }
1925
1944
  /**
1926
- * Creates a responsive card renderer function for ResponsivePlugin.
1927
- * Renders Angular templates for card layout in responsive mode.
1928
- *
1929
- * @param gridElement - The grid element to look up the template for
1930
- * @returns A card renderer function or undefined if no template is found
1945
+ * Creates a responsive card renderer function for ResponsivePlugin. Delegates
1946
+ * to the bridge installed by `@toolbox-web/grid-angular/features/responsive`.
1931
1947
  */
1932
1948
  createResponsiveCardRenderer(gridElement) {
1933
- const template = getResponsiveCardTemplate(gridElement);
1934
- if (!template) {
1935
- return undefined;
1936
- }
1937
- return (row, rowIndex) => {
1938
- // Create the context for the template
1939
- const context = {
1940
- $implicit: row,
1941
- row: row,
1942
- index: rowIndex,
1943
- };
1944
- // Create embedded view from template
1945
- const viewRef = this.viewContainerRef.createEmbeddedView(template, context);
1946
- this.viewRefs.push(viewRef);
1947
- // Trigger change detection
1948
- viewRef.detectChanges();
1949
- // Create a container for the root nodes
1950
- const container = document.createElement('div');
1951
- viewRef.rootNodes.forEach((node) => container.appendChild(node));
1952
- return container;
1953
- };
1949
+ return getResponsiveCardRendererBridge()?.(gridElement, this);
1954
1950
  }
1955
1951
  /**
1956
- * FrameworkAdapter hook called by ResponsivePlugin during attach().
1957
- * Parses the `<tbw-grid-responsive-card>` element and delegates to
1958
- * {@link createResponsiveCardRenderer}. Required for parity with the Vue
1959
- * adapter so ResponsivePlugin's standard lookup path works for Angular
1960
- * users without relying on imperative `refreshCardRenderer` calls.
1952
+ * FrameworkAdapter hook called by ResponsivePlugin during attach(). Delegates
1953
+ * to {@link createResponsiveCardRenderer} (bridge installed by responsive feature).
1961
1954
  */
1962
1955
  parseResponsiveCardElement(cardElement) {
1963
1956
  const gridElement = cardElement.closest('tbw-grid');
1964
1957
  if (!gridElement)
1965
1958
  return undefined;
1966
- return this.createResponsiveCardRenderer(gridElement);
1959
+ return getResponsiveCardRendererBridge()?.(gridElement, this);
1967
1960
  }
1968
1961
  /**
1969
1962
  * Creates a tool panel renderer from a light DOM element.
@@ -2047,8 +2040,11 @@ class GridAdapter {
2047
2040
  typeDefault.editor = this.createComponentEditor(config.editor);
2048
2041
  }
2049
2042
  // Create filterPanelRenderer function that instantiates the Angular component
2043
+ // via the filtering feature bridge. Drop silently if the feature is not imported.
2050
2044
  if (config.filterPanelRenderer && isComponentClass(config.filterPanelRenderer)) {
2051
- typeDefault.filterPanelRenderer = this.createComponentFilterPanelRenderer(config.filterPanelRenderer);
2045
+ const wrapped = getFilterPanelTypeDefaultBridge()?.(config.filterPanelRenderer, this);
2046
+ if (wrapped)
2047
+ typeDefault.filterPanelRenderer = wrapped;
2052
2048
  }
2053
2049
  else if (config.filterPanelRenderer) {
2054
2050
  typeDefault.filterPanelRenderer = config.filterPanelRenderer;
@@ -2056,47 +2052,56 @@ class GridAdapter {
2056
2052
  return typeDefault;
2057
2053
  }
2058
2054
  /**
2059
- * Creates and mounts an Angular component dynamically.
2060
- * Shared logic between renderer and editor component creation.
2055
+ * Generalized component-mount primitive. All `createComponent*Renderer` methods
2056
+ * are thin wrappers around this. Returns a function `(ctx) => { hostElement, componentRef }`
2057
+ * so callers that need the `componentRef` (editor wiring, value-change subscription)
2058
+ * still have it; callers that only need the host element use `.hostElement`.
2059
+ *
2060
+ * Public so feature secondary entries can compose their own component renderers
2061
+ * without re-implementing the mount/track plumbing.
2062
+ *
2063
+ * @param componentClass Angular component class to instantiate per call.
2064
+ * @param mapInputs Maps the renderer context to a `setInput()` bag.
2065
+ * @param pool Which `componentRefs[]` array tracks the instance for cleanup.
2066
+ * `'render'` (default) is the long-lived pool cleared at `dispose()`.
2067
+ * `'editor'` is the per-cell pool swept by `releaseCell()`.
2061
2068
  * @internal
2062
2069
  */
2063
- mountComponent(componentClass, inputs, isEditor = false) {
2064
- // Create a host element for the component
2065
- const hostElement = document.createElement('span');
2066
- hostElement.style.display = 'contents';
2067
- // Create the component dynamically
2068
- const componentRef = createComponent(componentClass, {
2069
- environmentInjector: this.injector,
2070
- hostElement,
2071
- });
2072
- // Set inputs - components should have value, row, column inputs
2073
- this.setComponentInputs(componentRef, inputs);
2074
- // Attach to app for change detection
2075
- this.appRef.attachView(componentRef.hostView);
2076
- // Track in editor-specific array for per-cell cleanup, or general array for renderers
2077
- if (isEditor) {
2078
- this.editorComponentRefs.push(componentRef);
2079
- }
2080
- else {
2081
- this.componentRefs.push(componentRef);
2082
- }
2083
- // Trigger change detection
2084
- componentRef.changeDetectorRef.detectChanges();
2085
- return { hostElement, componentRef };
2070
+ mountComponentRenderer(componentClass, mapInputs, pool = 'render') {
2071
+ return (ctx) => {
2072
+ const hostElement = document.createElement('span');
2073
+ hostElement.style.display = 'contents';
2074
+ const componentRef = createComponent(componentClass, {
2075
+ environmentInjector: this.injector,
2076
+ hostElement,
2077
+ });
2078
+ this.setComponentInputs(componentRef, mapInputs(ctx));
2079
+ this.appRef.attachView(componentRef.hostView);
2080
+ (pool === 'editor' ? this.editorComponentRefs : this.componentRefs).push(componentRef);
2081
+ componentRef.changeDetectorRef.detectChanges();
2082
+ return { hostElement, componentRef };
2083
+ };
2086
2084
  }
2087
2085
  /**
2088
2086
  * Creates a renderer function from an Angular component class.
2087
+ * Wraps {@link mountComponentRenderer} with a per-cell `WeakMap` cache so
2088
+ * scroll-recycled cells reuse the existing component (just refresh inputs)
2089
+ * instead of mounting a fresh one.
2089
2090
  * @internal
2090
2091
  */
2091
2092
  createComponentRenderer(componentClass) {
2092
- // Cell cache for component-based renderers - maps cell element to its component ref
2093
2093
  const cellCache = new WeakMap();
2094
+ const mount = this.mountComponentRenderer(componentClass, (ctx) => ({
2095
+ value: ctx.value,
2096
+ row: ctx.row,
2097
+ column: ctx.column,
2098
+ }));
2094
2099
  return (ctx) => {
2095
2100
  const cellEl = ctx.cellEl;
2096
2101
  if (cellEl) {
2097
2102
  const cached = cellCache.get(cellEl);
2098
2103
  if (cached) {
2099
- // Reuse existing component - just update inputs
2104
+ // Reuse existing component - just update inputs.
2100
2105
  this.setComponentInputs(cached.componentRef, {
2101
2106
  value: ctx.value,
2102
2107
  row: ctx.row,
@@ -2106,31 +2111,25 @@ class GridAdapter {
2106
2111
  return cached.hostElement;
2107
2112
  }
2108
2113
  }
2109
- const { hostElement, componentRef } = this.mountComponent(componentClass, {
2110
- value: ctx.value,
2111
- row: ctx.row,
2112
- column: ctx.column,
2113
- });
2114
- // Cache for reuse on scroll recycles
2115
- if (cellEl) {
2114
+ const { hostElement, componentRef } = mount(ctx);
2115
+ if (cellEl)
2116
2116
  cellCache.set(cellEl, { componentRef, hostElement });
2117
- }
2118
2117
  return hostElement;
2119
2118
  };
2120
2119
  }
2121
2120
  /**
2122
2121
  * Creates an editor function from an Angular component class.
2122
+ * Wraps {@link mountComponentRenderer} (using the `'editor'` pool for per-cell
2123
+ * cleanup) plus editor-specific wiring: callback bridge, mount-hook fan-out
2124
+ * (see {@link runEditorMountHooks}), and external value-change subscription.
2123
2125
  * @internal
2124
2126
  */
2125
2127
  createComponentEditor(componentClass) {
2128
+ const mount = this.mountComponentRenderer(componentClass, (ctx) => ({ value: ctx.value, row: ctx.row, column: ctx.column }), 'editor');
2126
2129
  return (ctx) => {
2127
- const { hostElement, componentRef } = this.mountComponent(componentClass, {
2128
- value: ctx.value,
2129
- row: ctx.row,
2130
- column: ctx.column,
2131
- }, true);
2130
+ const { hostElement, componentRef } = mount(ctx);
2132
2131
  wireEditorCallbacks(hostElement, componentRef.instance, (value) => ctx.commit(value), () => ctx.cancel());
2133
- this.attachBeforeEditCloseFlush(hostElement);
2132
+ this.runEditorMountHooks(hostElement);
2134
2133
  // Auto-update editor when value changes externally (e.g., via updateRow cascade
2135
2134
  // or Escape-revert). Update the component input and run detectChanges() —
2136
2135
  // the component's own template handles rendering regardless of editor type.
@@ -2155,179 +2154,30 @@ class GridAdapter {
2155
2154
  /**
2156
2155
  * Creates a header renderer function from an Angular component class.
2157
2156
  * Mounts the component with full header context (column, value, sortState, etc.).
2158
- * @internal
2159
- */
2160
- createComponentHeaderRenderer(componentClass) {
2161
- return (ctx) => {
2162
- const hostElement = document.createElement('span');
2163
- hostElement.style.display = 'contents';
2164
- const componentRef = createComponent(componentClass, {
2165
- environmentInjector: this.injector,
2166
- hostElement,
2167
- });
2168
- this.setComponentInputs(componentRef, {
2169
- column: ctx.column,
2170
- value: ctx.value,
2171
- sortState: ctx.sortState,
2172
- filterActive: ctx.filterActive,
2173
- renderSortIcon: ctx.renderSortIcon,
2174
- renderFilterButton: ctx.renderFilterButton,
2175
- });
2176
- this.appRef.attachView(componentRef.hostView);
2177
- this.componentRefs.push(componentRef);
2178
- componentRef.changeDetectorRef.detectChanges();
2179
- return hostElement;
2180
- };
2181
- }
2182
- /**
2183
- * Creates a header label renderer function from an Angular component class.
2184
- * Mounts the component with label context (column, value).
2185
- * @internal
2186
- */
2187
- createComponentHeaderLabelRenderer(componentClass) {
2188
- return (ctx) => {
2189
- const hostElement = document.createElement('span');
2190
- hostElement.style.display = 'contents';
2191
- const componentRef = createComponent(componentClass, {
2192
- environmentInjector: this.injector,
2193
- hostElement,
2194
- });
2195
- this.setComponentInputs(componentRef, {
2196
- column: ctx.column,
2197
- value: ctx.value,
2198
- });
2199
- this.appRef.attachView(componentRef.hostView);
2200
- this.componentRefs.push(componentRef);
2201
- componentRef.changeDetectorRef.detectChanges();
2202
- return hostElement;
2203
- };
2204
- }
2205
- /**
2206
- * Creates a group header renderer function from an Angular component class.
2207
- *
2208
- * The component should accept group header inputs (id, label, columns, firstIndex, isImplicit).
2209
- * Returns the host element directly (groupHeaderRenderer returns an element, not void).
2210
- * @internal
2211
- */
2212
- createComponentGroupHeaderRenderer(componentClass) {
2213
- return (params) => {
2214
- const hostElement = document.createElement('span');
2215
- hostElement.style.display = 'contents';
2216
- const componentRef = createComponent(componentClass, {
2217
- environmentInjector: this.injector,
2218
- hostElement,
2219
- });
2220
- this.setComponentInputs(componentRef, {
2221
- id: params.id,
2222
- label: params.label,
2223
- columns: params.columns,
2224
- firstIndex: params.firstIndex,
2225
- isImplicit: params.isImplicit,
2226
- });
2227
- this.appRef.attachView(componentRef.hostView);
2228
- this.componentRefs.push(componentRef);
2229
- componentRef.changeDetectorRef.detectChanges();
2230
- return hostElement;
2231
- };
2232
- }
2233
- /**
2234
- * Processes a GroupingColumnsConfig, converting component class references
2235
- * to actual renderer functions.
2236
- *
2237
- * @param config - Angular grouping columns configuration with possible component class references
2238
- * @returns Processed GroupingColumnsConfig with actual renderer functions
2239
- */
2240
- processGroupingColumnsConfig(config) {
2241
- const processed = { ...config };
2242
- let changed = false;
2243
- // Bridge top-level groupHeaderRenderer component class
2244
- if (config.groupHeaderRenderer && isComponentClass(config.groupHeaderRenderer)) {
2245
- processed.groupHeaderRenderer = this.createComponentGroupHeaderRenderer(config.groupHeaderRenderer);
2246
- changed = true;
2247
- }
2248
- // Bridge per-group renderer component classes inside columnGroups
2249
- if (Array.isArray(config.columnGroups)) {
2250
- const mappedGroups = config.columnGroups.map((def) => {
2251
- if (def.renderer && isComponentClass(def.renderer)) {
2252
- changed = true;
2253
- return { ...def, renderer: this.createComponentGroupHeaderRenderer(def.renderer) };
2254
- }
2255
- return def;
2256
- });
2257
- if (changed)
2258
- processed.columnGroups = mappedGroups;
2259
- }
2260
- return changed ? processed : config;
2261
- }
2262
- /**
2263
- * Processes a GroupingRowsConfig, converting component class references
2264
- * to actual renderer functions.
2265
- *
2266
- * @param config - Angular grouping rows configuration with possible component class references
2267
- * @returns Processed GroupingRowsConfig with actual renderer functions
2268
- */
2269
- processGroupingRowsConfig(config) {
2270
- if (config.groupRowRenderer && isComponentClass(config.groupRowRenderer)) {
2271
- return {
2272
- ...config,
2273
- groupRowRenderer: this.createComponentGroupRowRenderer(config.groupRowRenderer),
2274
- };
2275
- }
2276
- return config;
2277
- }
2278
- /**
2279
- * Processes a PinnedRowsConfig, converting component class references
2280
- * in `customPanels[].render` to actual renderer functions.
2281
- *
2282
- * @param config - Angular pinned rows configuration with possible component class references
2283
- * @returns Processed PinnedRowsConfig with actual renderer functions
2284
- */
2285
- processPinnedRowsConfig(config) {
2286
- if (!Array.isArray(config.customPanels))
2287
- return config;
2288
- const hasComponentRender = config.customPanels.some((panel) => isComponentClass(panel.render));
2289
- if (!hasComponentRender)
2290
- return config;
2291
- return {
2292
- ...config,
2293
- customPanels: config.customPanels.map((panel) => {
2294
- if (!isComponentClass(panel.render))
2295
- return panel;
2296
- return {
2297
- ...panel,
2298
- render: this.createComponentPinnedRowsPanelRenderer(panel.render),
2299
- };
2300
- }),
2301
- };
2302
- }
2303
- /**
2304
- * Creates a pinned rows panel renderer function from an Angular component class.
2305
- *
2306
- * The component should accept inputs from PinnedRowsContext (totalRows, filteredRows,
2307
- * selectedRows, columns, rows, grid).
2308
- * @internal
2309
- */
2310
- createComponentPinnedRowsPanelRenderer(componentClass) {
2311
- return (ctx) => {
2312
- const hostElement = document.createElement('span');
2313
- hostElement.style.display = 'contents';
2314
- const componentRef = createComponent(componentClass, {
2315
- environmentInjector: this.injector,
2316
- hostElement,
2317
- });
2318
- this.setComponentInputs(componentRef, {
2319
- totalRows: ctx.totalRows,
2320
- filteredRows: ctx.filteredRows,
2321
- selectedRows: ctx.selectedRows,
2322
- columns: ctx.columns,
2323
- rows: ctx.rows,
2324
- grid: ctx.grid,
2325
- });
2326
- this.appRef.attachView(componentRef.hostView);
2327
- this.componentRefs.push(componentRef);
2328
- componentRef.changeDetectorRef.detectChanges();
2329
- return hostElement;
2330
- };
2157
+ * @internal
2158
+ */
2159
+ createComponentHeaderRenderer(componentClass) {
2160
+ const mount = this.mountComponentRenderer(componentClass, (ctx) => ({
2161
+ column: ctx.column,
2162
+ value: ctx.value,
2163
+ sortState: ctx.sortState,
2164
+ filterActive: ctx.filterActive,
2165
+ renderSortIcon: ctx.renderSortIcon,
2166
+ renderFilterButton: ctx.renderFilterButton,
2167
+ }));
2168
+ return (ctx) => mount(ctx).hostElement;
2169
+ }
2170
+ /**
2171
+ * Creates a header label renderer function from an Angular component class.
2172
+ * Mounts the component with label context (column, value).
2173
+ * @internal
2174
+ */
2175
+ createComponentHeaderLabelRenderer(componentClass) {
2176
+ const mount = this.mountComponentRenderer(componentClass, (ctx) => ({
2177
+ column: ctx.column,
2178
+ value: ctx.value,
2179
+ }));
2180
+ return (ctx) => mount(ctx).hostElement;
2331
2181
  }
2332
2182
  /**
2333
2183
  * Creates a loading renderer function from an Angular component class.
@@ -2336,78 +2186,59 @@ class GridAdapter {
2336
2186
  * @internal
2337
2187
  */
2338
2188
  createComponentLoadingRenderer(componentClass) {
2339
- return (ctx) => {
2340
- const hostElement = document.createElement('span');
2341
- hostElement.style.display = 'contents';
2342
- const componentRef = createComponent(componentClass, {
2343
- environmentInjector: this.injector,
2344
- hostElement,
2345
- });
2346
- this.setComponentInputs(componentRef, {
2347
- size: ctx.size,
2348
- });
2349
- this.appRef.attachView(componentRef.hostView);
2350
- this.componentRefs.push(componentRef);
2351
- componentRef.changeDetectorRef.detectChanges();
2352
- return hostElement;
2353
- };
2189
+ const mount = this.mountComponentRenderer(componentClass, (ctx) => ({ size: ctx.size }));
2190
+ return (ctx) => mount(ctx).hostElement;
2354
2191
  }
2355
2192
  /**
2356
- * Creates a group row renderer function from an Angular component class.
2357
- *
2358
- * The component should accept group row inputs (key, value, depth, rows, expanded, toggleExpand).
2359
- * Returns the host element directly (groupRowRenderer returns an element, not void).
2193
+ * Create an embedded view from a `TemplateRef` and append-track it on the
2194
+ * adapter's view-ref pool so it is cleaned up on `destroy()` / `unmount()`.
2195
+ * Public so feature secondary entries can mount Angular templates (e.g.
2196
+ * master-detail rows, responsive cards) without reaching into the adapter's
2197
+ * private `viewContainerRef` / `viewRefs`.
2360
2198
  * @internal
2361
2199
  */
2362
- createComponentGroupRowRenderer(componentClass) {
2363
- return (params) => {
2364
- const hostElement = document.createElement('span');
2365
- hostElement.style.display = 'contents';
2366
- const componentRef = createComponent(componentClass, {
2367
- environmentInjector: this.injector,
2368
- hostElement,
2369
- });
2370
- this.setComponentInputs(componentRef, {
2371
- key: params.key,
2372
- value: params.value,
2373
- depth: params.depth,
2374
- rows: params.rows,
2375
- expanded: params.expanded,
2376
- toggleExpand: params.toggleExpand,
2377
- });
2378
- this.appRef.attachView(componentRef.hostView);
2379
- this.componentRefs.push(componentRef);
2380
- componentRef.changeDetectorRef.detectChanges();
2381
- return hostElement;
2382
- };
2200
+ createTrackedEmbeddedView(template, context) {
2201
+ const viewRef = this.viewContainerRef.createEmbeddedView(template, context);
2202
+ this.viewRefs.push(viewRef);
2203
+ viewRef.detectChanges();
2204
+ return viewRef;
2383
2205
  }
2384
2206
  /**
2385
- * Creates a filter panel renderer function from an Angular component class.
2386
- *
2387
- * The component must implement `FilterPanel` (i.e., have a `params` input).
2388
- * The component is mounted into the filter panel container element.
2207
+ * Processes a GroupingColumnsConfig. Delegates to the feature config
2208
+ * preprocessor installed by `@toolbox-web/grid-angular/features/grouping-columns`,
2209
+ * which handles converting Angular component class references to actual
2210
+ * renderer functions. Returns the input config unchanged if the feature
2211
+ * is not imported.
2212
+ */
2213
+ processGroupingColumnsConfig(config) {
2214
+ return this.applyFeatureConfigPreprocessor('groupingColumns', config);
2215
+ }
2216
+ /**
2217
+ * Processes a GroupingRowsConfig. Delegates to the feature config preprocessor
2218
+ * installed by `@toolbox-web/grid-angular/features/grouping-rows`.
2219
+ */
2220
+ processGroupingRowsConfig(config) {
2221
+ return this.applyFeatureConfigPreprocessor('groupingRows', config);
2222
+ }
2223
+ /**
2224
+ * Processes a PinnedRowsConfig. Delegates to the feature config preprocessor
2225
+ * installed by `@toolbox-web/grid-angular/features/pinned-rows`.
2226
+ */
2227
+ processPinnedRowsConfig(config) {
2228
+ return this.applyFeatureConfigPreprocessor('pinnedRows', config);
2229
+ }
2230
+ /**
2231
+ * Run a registered feature-config preprocessor against `config`, returning
2232
+ * the original config unchanged when the feature is not imported.
2389
2233
  * @internal
2390
2234
  */
2391
- createComponentFilterPanelRenderer(componentClass) {
2392
- return (container, params) => {
2393
- const hostElement = document.createElement('span');
2394
- hostElement.style.display = 'contents';
2395
- const componentRef = createComponent(componentClass, {
2396
- environmentInjector: this.injector,
2397
- hostElement,
2398
- });
2399
- // Set params input
2400
- try {
2401
- componentRef.setInput('params', params);
2402
- }
2403
- catch {
2404
- // Input doesn't exist on component — ignore
2405
- }
2406
- this.appRef.attachView(componentRef.hostView);
2407
- this.componentRefs.push(componentRef);
2408
- componentRef.changeDetectorRef.detectChanges();
2409
- container.appendChild(hostElement);
2410
- };
2235
+ applyFeatureConfigPreprocessor(name, config) {
2236
+ if (!config || typeof config !== 'object')
2237
+ return config;
2238
+ const preprocessor = getFeatureConfigPreprocessor(name);
2239
+ if (!preprocessor)
2240
+ return config;
2241
+ return preprocessor(config, this);
2411
2242
  }
2412
2243
  /**
2413
2244
  * Sets component inputs using Angular's setInput API.
@@ -2449,11 +2280,11 @@ class GridAdapter {
2449
2280
  this.editorComponentRefs.splice(i, 1);
2450
2281
  }
2451
2282
  }
2452
- // Detach `before-edit-close` listeners for editor hosts inside this cell
2453
- for (const [hostEl, unsub] of this.editorBeforeCloseUnsubs) {
2283
+ // Detach editor-mount hook teardowns for editor hosts inside this cell.
2284
+ for (const [hostEl, unsub] of this.editorMountTeardowns) {
2454
2285
  if (cellEl.contains(hostEl)) {
2455
2286
  unsub();
2456
- this.editorBeforeCloseUnsubs.delete(hostEl);
2287
+ this.editorMountTeardowns.delete(hostEl);
2457
2288
  }
2458
2289
  }
2459
2290
  }
@@ -2493,40 +2324,28 @@ class GridAdapter {
2493
2324
  this.componentRefs = [];
2494
2325
  this.editorComponentRefs.forEach((ref) => ref.destroy());
2495
2326
  this.editorComponentRefs = [];
2496
- this.editorBeforeCloseUnsubs.forEach((unsub) => unsub());
2497
- this.editorBeforeCloseUnsubs.clear();
2327
+ this.editorMountTeardowns.forEach((unsub) => unsub());
2328
+ this.editorMountTeardowns.clear();
2498
2329
  }
2499
2330
  /**
2500
- * Attaches a `before-edit-close` listener on the host grid that flushes
2501
- * the focused input inside `host` via native `.blur()` so editors that
2502
- * commit on `(blur)` flush pending input before the cell DOM is torn
2503
- * down by Tab / programmatic row exit.
2504
- *
2505
- * The grid is resolved lazily via `queueMicrotask` because the host is
2506
- * appended to the cell *after* the editor wrapper returns. Mirror of
2507
- * Vue's `attachBeforeEditCloseFlush` and React's `wrapReactEditor`
2331
+ * Runs every registered {@link EditorMountHook} against a freshly mounted
2332
+ * editor host once it has been parented to the grid. The grid is resolved
2333
+ * lazily via `queueMicrotask` because the host is appended to the cell
2334
+ * *after* the editor wrapper returns. Mirror of Vue's
2335
+ * `attachBeforeEditCloseFlush` and React's `wrapReactEditor`
2508
2336
  * queueMicrotask bridge.
2337
+ *
2338
+ * Without any feature imports the hook list is empty and this is a no-op
2339
+ * — `before-edit-close` blur handling lives in
2340
+ * `@toolbox-web/grid-angular/features/editing`.
2509
2341
  * @internal
2510
2342
  */
2511
- attachBeforeEditCloseFlush(host) {
2343
+ runEditorMountHooks(host) {
2512
2344
  queueMicrotask(() => {
2513
2345
  const gridEl = host.closest('tbw-grid');
2514
2346
  if (!gridEl)
2515
2347
  return;
2516
- const flush = () => {
2517
- const focused = host.ownerDocument.activeElement;
2518
- if (focused &&
2519
- host.contains(focused) &&
2520
- (focused instanceof HTMLInputElement ||
2521
- focused instanceof HTMLTextAreaElement ||
2522
- focused instanceof HTMLSelectElement)) {
2523
- focused.blur();
2524
- }
2525
- };
2526
- gridEl.addEventListener('before-edit-close', flush);
2527
- this.editorBeforeCloseUnsubs.set(host, () => {
2528
- gridEl.removeEventListener('before-edit-close', flush);
2529
- });
2348
+ this.editorMountTeardowns.set(host, notifyEditorMounted(host, gridEl));
2530
2349
  });
2531
2350
  }
2532
2351
  }
@@ -2539,6 +2358,7 @@ class GridAdapter {
2539
2358
  */
2540
2359
  /**
2541
2360
  * Injection token for providing icon overrides at app level.
2361
+ * @since 0.8.0
2542
2362
  */
2543
2363
  const GRID_ICONS = new InjectionToken('GRID_ICONS');
2544
2364
  /**
@@ -2571,6 +2391,7 @@ const GRID_ICONS = new InjectionToken('GRID_ICONS');
2571
2391
  * }
2572
2392
  * }
2573
2393
  * ```
2394
+ * @since 0.8.0
2574
2395
  */
2575
2396
  class GridIconRegistry {
2576
2397
  icons = new Map();
@@ -2669,6 +2490,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
2669
2490
  * ]
2670
2491
  * };
2671
2492
  * ```
2493
+ * @since 0.8.0
2672
2494
  */
2673
2495
  function provideGridIcons(icons) {
2674
2496
  return makeEnvironmentProviders([{ provide: GRID_ICONS, useValue: icons }]);
@@ -2713,6 +2535,7 @@ function provideGridIcons(icons) {
2713
2535
  * Defaults to `'tbw-grid'` (first grid in the component). Use when the
2714
2536
  * component contains multiple grids, e.g. `'tbw-grid.primary'` or `'#my-grid'`.
2715
2537
  * @returns Object with grid access methods and state signals
2538
+ * @since 0.6.0
2716
2539
  */
2717
2540
  function injectGrid(selector = 'tbw-grid') {
2718
2541
  const elementRef = inject(ElementRef);
@@ -2788,6 +2611,12 @@ function injectGrid(selector = 'tbw-grid') {
2788
2611
  const unregisterStyles = (id) => {
2789
2612
  element()?.unregisterStyles?.(id);
2790
2613
  };
2614
+ const getPlugin = (pluginClass) => {
2615
+ return element()?.getPlugin?.(pluginClass);
2616
+ };
2617
+ const getPluginByName = ((name) => {
2618
+ return element()?.getPluginByName?.(name);
2619
+ });
2791
2620
  return {
2792
2621
  element,
2793
2622
  isReady,
@@ -2798,6 +2627,8 @@ function injectGrid(selector = 'tbw-grid') {
2798
2627
  toggleGroup,
2799
2628
  registerStyles,
2800
2629
  unregisterStyles,
2630
+ getPlugin,
2631
+ getPluginByName,
2801
2632
  };
2802
2633
  }
2803
2634
 
@@ -2864,6 +2695,12 @@ function injectGrid(selector = 'tbw-grid') {
2864
2695
  * ```
2865
2696
  *
2866
2697
  * @typeParam TRow - The row data type (available via `params().column`)
2698
+ *
2699
+ * MOVE-IN-V2: this class will physically move into
2700
+ * `@toolbox-web/grid-angular/features/filtering` in v2.0.0; the deprecated
2701
+ * re-export from the main `@toolbox-web/grid-angular` entry will be removed at
2702
+ * the same time. Consumers should already be importing from the feature entry.
2703
+ * @since 0.13.0
2867
2704
  */
2868
2705
  class BaseFilterPanel {
2869
2706
  /**
@@ -2967,6 +2804,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
2967
2804
  *
2968
2805
  * @typeParam TRow - The row data type
2969
2806
  * @typeParam TValue - The cell value type
2807
+ *
2808
+ * MOVE-IN-V2: this class will physically move into
2809
+ * `@toolbox-web/grid-angular/features/editing` in v2.0.0; the deprecated
2810
+ * re-export from the main `@toolbox-web/grid-angular` entry will be removed at
2811
+ * the same time. Consumers should already be importing from the feature entry.
2812
+ * @since 0.5.0
2970
2813
  */
2971
2814
  class BaseGridEditor {
2972
2815
  elementRef = inject(ElementRef);
@@ -3269,6 +3112,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
3269
3112
  *
3270
3113
  * @typeParam TRow - The row data type
3271
3114
  * @typeParam TValue - The cell/control value type
3115
+ *
3116
+ * MOVE-IN-V2: this class will physically move into
3117
+ * `@toolbox-web/grid-angular/features/editing` in v2.0.0; the deprecated
3118
+ * re-export from the main `@toolbox-web/grid-angular` entry will be removed at
3119
+ * the same time. Consumers should already be importing from the feature entry.
3120
+ * @since 0.13.0
3272
3121
  */
3273
3122
  class BaseGridEditorCVA extends BaseGridEditor {
3274
3123
  // ============================================================================
@@ -3532,6 +3381,13 @@ let anchorCounter = 0;
3532
3381
  *
3533
3382
  * @typeParam TRow - The row data type
3534
3383
  * @typeParam TValue - The cell value type
3384
+ *
3385
+ * MOVE-IN-V2: this class (and its companion `OverlayPosition` type) will
3386
+ * physically move into `@toolbox-web/grid-angular/features/editing` in v2.0.0;
3387
+ * the deprecated re-export from the main `@toolbox-web/grid-angular` entry
3388
+ * will be removed at the same time. Consumers should already be importing
3389
+ * from the feature entry.
3390
+ * @since 0.13.0
3535
3391
  */
3536
3392
  class BaseOverlayEditor extends BaseGridEditor {
3537
3393
  _elementRef = inject(ElementRef);
@@ -3998,6 +3854,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
3998
3854
  * ```
3999
3855
  *
4000
3856
  * @category Directive
3857
+ * @since 0.13.0
4001
3858
  */
4002
3859
  class TbwGridColumn {
4003
3860
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: TbwGridColumn, deps: [], target: i0.ɵɵFactoryTarget.Directive });
@@ -4010,6 +3867,104 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
4010
3867
  }]
4011
3868
  }] });
4012
3869
 
3870
+ // Global registry mapping DOM elements to their templates
3871
+ const detailTemplateRegistry = new Map();
3872
+ /**
3873
+ * Gets the detail template registered for a given grid element.
3874
+ * Used by AngularGridAdapter to retrieve templates at render time.
3875
+ * @since 0.1.0
3876
+ */
3877
+ function getDetailTemplate(gridElement) {
3878
+ // Look for tbw-grid-detail child and get its template
3879
+ const detailElement = gridElement.querySelector('tbw-grid-detail');
3880
+ if (detailElement) {
3881
+ return detailTemplateRegistry.get(detailElement);
3882
+ }
3883
+ return undefined;
3884
+ }
3885
+ /**
3886
+ * Directive that captures an `<ng-template>` for use as a master-detail row renderer.
3887
+ *
3888
+ * This enables declarative Angular component usage for expandable detail rows
3889
+ * that appear below the main row when expanded.
3890
+ *
3891
+ * ## Usage
3892
+ *
3893
+ * ```html
3894
+ * <tbw-grid [rows]="rows" [gridConfig]="config">
3895
+ * <tbw-grid-detail [showExpandColumn]="true" animation="slide">
3896
+ * <ng-template let-row>
3897
+ * <app-detail-panel [employee]="row" />
3898
+ * </ng-template>
3899
+ * </tbw-grid-detail>
3900
+ * </tbw-grid>
3901
+ * ```
3902
+ *
3903
+ * The template context provides:
3904
+ * - `$implicit` / `row`: The full row data object
3905
+ *
3906
+ * Import the directive from the master-detail feature entry:
3907
+ *
3908
+ * ```typescript
3909
+ * import { GridDetailView } from '@toolbox-web/grid-angular/features/master-detail';
3910
+ *
3911
+ * @Component({
3912
+ * imports: [GridDetailView],
3913
+ * // ...
3914
+ * })
3915
+ * ```
3916
+ *
3917
+ * > Note: `GridDetailView` is also re-exported from `@toolbox-web/grid-angular`
3918
+ * > for backwards compatibility, but that re-export is deprecated and will be
3919
+ * > removed in v2.0.0. Always import from the feature entry.
3920
+ *
3921
+ * @example
3922
+ * ```html
3923
+ * <tbw-grid [rows]="rows" [gridConfig]="config">
3924
+ * <tbw-grid-detail [showExpandColumn]="true" animation="slide">
3925
+ * <ng-template let-row>
3926
+ * <app-detail-panel [employee]="row" />
3927
+ * </ng-template>
3928
+ * </tbw-grid-detail>
3929
+ * </tbw-grid>
3930
+ * ```
3931
+ *
3932
+ * @category Directive
3933
+ * @since 0.1.0
3934
+ */
3935
+ class GridDetailView {
3936
+ elementRef = inject((ElementRef));
3937
+ /** Whether to show the expand/collapse column. Default: true */
3938
+ showExpandColumn = input(true, ...(ngDevMode ? [{ debugName: "showExpandColumn" }] : /* istanbul ignore next */ []));
3939
+ /** Animation style for expand/collapse. Default: 'slide' */
3940
+ animation = input('slide', ...(ngDevMode ? [{ debugName: "animation" }] : /* istanbul ignore next */ []));
3941
+ /**
3942
+ * Query for the ng-template content child.
3943
+ */
3944
+ template = contentChild((TemplateRef), ...(ngDevMode ? [{ debugName: "template" }] : /* istanbul ignore next */ []));
3945
+ /** Effect that triggers when the template is available */
3946
+ onTemplateReceived = effect(() => {
3947
+ const template = this.template();
3948
+ if (template) {
3949
+ // Register the template for this element
3950
+ detailTemplateRegistry.set(this.elementRef.nativeElement, template);
3951
+ }
3952
+ }, ...(ngDevMode ? [{ debugName: "onTemplateReceived" }] : /* istanbul ignore next */ []));
3953
+ /**
3954
+ * Static type guard for template context.
3955
+ * Enables type inference in templates.
3956
+ */
3957
+ static ngTemplateContextGuard(dir, ctx) {
3958
+ return true;
3959
+ }
3960
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: GridDetailView, deps: [], target: i0.ɵɵFactoryTarget.Directive });
3961
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "21.2.5", type: GridDetailView, isStandalone: true, selector: "tbw-grid-detail", inputs: { showExpandColumn: { classPropertyName: "showExpandColumn", publicName: "showExpandColumn", isSignal: true, isRequired: false, transformFunction: null }, animation: { classPropertyName: "animation", publicName: "animation", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "template", first: true, predicate: (TemplateRef), descendants: true, isSignal: true }], ngImport: i0 });
3962
+ }
3963
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: GridDetailView, decorators: [{
3964
+ type: Directive,
3965
+ args: [{ selector: 'tbw-grid-detail' }]
3966
+ }], propDecorators: { showExpandColumn: [{ type: i0.Input, args: [{ isSignal: true, alias: "showExpandColumn", required: false }] }], animation: [{ type: i0.Input, args: [{ isSignal: true, alias: "animation", required: false }] }], template: [{ type: i0.ContentChild, args: [i0.forwardRef(() => TemplateRef), { isSignal: true }] }] } });
3967
+
4013
3968
  /**
4014
3969
  * Directive that registers `<tbw-grid-header>` as a known Angular element.
4015
3970
  *
@@ -4036,6 +3991,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
4036
3991
  * ```
4037
3992
  *
4038
3993
  * @category Directive
3994
+ * @since 0.13.0
4039
3995
  */
4040
3996
  class TbwGridHeader {
4041
3997
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: TbwGridHeader, deps: [], target: i0.ɵɵFactoryTarget.Directive });
@@ -4053,6 +4009,7 @@ const FORM_ARRAY_CONTEXT = Symbol('formArrayContext');
4053
4009
  /**
4054
4010
  * Gets the FormArrayContext from a grid element, if present.
4055
4011
  * @internal
4012
+ * @since 0.11.0
4056
4013
  */
4057
4014
  function getLazyFormContext(gridElement) {
4058
4015
  return gridElement[FORM_ARRAY_CONTEXT];
@@ -4132,6 +4089,14 @@ function getLazyFormContext(gridElement) {
4132
4089
  *
4133
4090
  * @see GridFormArray For small datasets with full upfront validation
4134
4091
  * @category Directive
4092
+ *
4093
+ * MOVE-IN-V2: this directive (and its `LazyFormFactory`, `RowFormChangeEvent`
4094
+ * types and `getLazyFormContext` helper) will physically move into
4095
+ * `@toolbox-web/grid-angular/features/editing` in v2.0.0; the deprecated
4096
+ * re-exports from the main `@toolbox-web/grid-angular` entry will be removed
4097
+ * at the same time. Consumers should already be importing from the feature
4098
+ * entry.
4099
+ * @since 0.11.0
4135
4100
  */
4136
4101
  class GridLazyForm {
4137
4102
  elementRef = inject((ElementRef));
@@ -4517,15 +4482,121 @@ class GridLazyForm {
4517
4482
  }
4518
4483
  return true;
4519
4484
  }
4520
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: GridLazyForm, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4521
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.5", type: GridLazyForm, isStandalone: true, selector: "tbw-grid[lazyForm]", inputs: { lazyForm: { classPropertyName: "lazyForm", publicName: "lazyForm", isSignal: true, isRequired: true, transformFunction: null }, syncValidation: { classPropertyName: "syncValidation", publicName: "syncValidation", isSignal: true, isRequired: false, transformFunction: null }, keepFormGroups: { classPropertyName: "keepFormGroups", publicName: "keepFormGroups", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { rowFormChange: "rowFormChange" }, ngImport: i0 });
4485
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: GridLazyForm, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4486
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.5", type: GridLazyForm, isStandalone: true, selector: "tbw-grid[lazyForm]", inputs: { lazyForm: { classPropertyName: "lazyForm", publicName: "lazyForm", isSignal: true, isRequired: true, transformFunction: null }, syncValidation: { classPropertyName: "syncValidation", publicName: "syncValidation", isSignal: true, isRequired: false, transformFunction: null }, keepFormGroups: { classPropertyName: "keepFormGroups", publicName: "keepFormGroups", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { rowFormChange: "rowFormChange" }, ngImport: i0 });
4487
+ }
4488
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: GridLazyForm, decorators: [{
4489
+ type: Directive,
4490
+ args: [{
4491
+ selector: 'tbw-grid[lazyForm]',
4492
+ }]
4493
+ }], propDecorators: { lazyForm: [{ type: i0.Input, args: [{ isSignal: true, alias: "lazyForm", required: true }] }], syncValidation: [{ type: i0.Input, args: [{ isSignal: true, alias: "syncValidation", required: false }] }], keepFormGroups: [{ type: i0.Input, args: [{ isSignal: true, alias: "keepFormGroups", required: false }] }], rowFormChange: [{ type: i0.Output, args: ["rowFormChange"] }] } });
4494
+
4495
+ /**
4496
+ * Registry to store responsive card templates by grid element.
4497
+ * Used by AngularGridAdapter to create card renderers.
4498
+ */
4499
+ const responsiveCardTemplateRegistry = new Map();
4500
+ /**
4501
+ * Retrieves the responsive card template for a grid element.
4502
+ *
4503
+ * @param gridElement - The grid element to look up
4504
+ * @returns The template reference or undefined if not found
4505
+ * @since 0.4.0
4506
+ */
4507
+ function getResponsiveCardTemplate(gridElement) {
4508
+ // Find the tbw-grid-responsive-card element inside the grid
4509
+ const cardElement = gridElement.querySelector('tbw-grid-responsive-card');
4510
+ if (!cardElement)
4511
+ return undefined;
4512
+ return responsiveCardTemplateRegistry.get(cardElement);
4513
+ }
4514
+ /**
4515
+ * Directive for providing custom Angular templates for responsive card layout.
4516
+ *
4517
+ * Use this directive to define how each row should render when the grid
4518
+ * is in responsive/mobile mode. The template receives the row data and index.
4519
+ *
4520
+ * ## Usage
4521
+ *
4522
+ * ```html
4523
+ * <tbw-grid [rows]="employees">
4524
+ * <tbw-grid-responsive-card>
4525
+ * <ng-template let-employee let-idx="index">
4526
+ * <div class="employee-card">
4527
+ * <img [src]="employee.avatar" alt="">
4528
+ * <div class="info">
4529
+ * <strong>{{ employee.name }}</strong>
4530
+ * <span>{{ employee.department }}</span>
4531
+ * </div>
4532
+ * </div>
4533
+ * </ng-template>
4534
+ * </tbw-grid-responsive-card>
4535
+ * </tbw-grid>
4536
+ * ```
4537
+ *
4538
+ * ## Important Notes
4539
+ *
4540
+ * - The ResponsivePlugin must be added to your grid config
4541
+ * - The Grid directive will automatically configure the plugin's cardRenderer
4542
+ * - Template context provides `$implicit` (row), `row`, and `index`
4543
+ *
4544
+ * @see ResponsivePlugin
4545
+ * @category Directive
4546
+ * @since 0.4.0
4547
+ */
4548
+ class GridResponsiveCard {
4549
+ elementRef = inject((ElementRef));
4550
+ /**
4551
+ * Card row height in pixels. Use `'auto'` for dynamic height based on content.
4552
+ *
4553
+ * Mirrors to the `card-row-height` attribute on the underlying
4554
+ * `<tbw-grid-responsive-card>` element which the ResponsivePlugin reads.
4555
+ *
4556
+ * @default 'auto'
4557
+ */
4558
+ cardRowHeight = input(...(ngDevMode ? [undefined, { debugName: "cardRowHeight" }] : /* istanbul ignore next */ []));
4559
+ /**
4560
+ * The ng-template containing the card content.
4561
+ */
4562
+ template = contentChild((TemplateRef), ...(ngDevMode ? [{ debugName: "template" }] : /* istanbul ignore next */ []));
4563
+ /**
4564
+ * Effect that registers the template when it becomes available.
4565
+ */
4566
+ onTemplateReceived = effect(() => {
4567
+ const template = this.template();
4568
+ if (template) {
4569
+ responsiveCardTemplateRegistry.set(this.elementRef.nativeElement, template);
4570
+ }
4571
+ }, ...(ngDevMode ? [{ debugName: "onTemplateReceived" }] : /* istanbul ignore next */ []));
4572
+ /**
4573
+ * Effect that mirrors the `cardRowHeight` input to the kebab-cased attribute
4574
+ * read by the ResponsivePlugin.
4575
+ */
4576
+ onCardRowHeightChange = effect(() => {
4577
+ const value = this.cardRowHeight();
4578
+ const element = this.elementRef.nativeElement;
4579
+ if (value === undefined) {
4580
+ element.removeAttribute('card-row-height');
4581
+ return;
4582
+ }
4583
+ element.setAttribute('card-row-height', value === 'auto' ? 'auto' : String(value));
4584
+ }, ...(ngDevMode ? [{ debugName: "onCardRowHeightChange" }] : /* istanbul ignore next */ []));
4585
+ /**
4586
+ * Type guard for template context inference.
4587
+ */
4588
+ static ngTemplateContextGuard(_directive, context) {
4589
+ return true;
4590
+ }
4591
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: GridResponsiveCard, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4592
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "21.2.5", type: GridResponsiveCard, isStandalone: true, selector: "tbw-grid-responsive-card", inputs: { cardRowHeight: { classPropertyName: "cardRowHeight", publicName: "cardRowHeight", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "template", first: true, predicate: (TemplateRef), descendants: true, isSignal: true }], ngImport: i0 });
4522
4593
  }
4523
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: GridLazyForm, decorators: [{
4594
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: GridResponsiveCard, decorators: [{
4524
4595
  type: Directive,
4525
4596
  args: [{
4526
- selector: 'tbw-grid[lazyForm]',
4597
+ selector: 'tbw-grid-responsive-card',
4527
4598
  }]
4528
- }], propDecorators: { lazyForm: [{ type: i0.Input, args: [{ isSignal: true, alias: "lazyForm", required: true }] }], syncValidation: [{ type: i0.Input, args: [{ isSignal: true, alias: "syncValidation", required: false }] }], keepFormGroups: [{ type: i0.Input, args: [{ isSignal: true, alias: "keepFormGroups", required: false }] }], rowFormChange: [{ type: i0.Output, args: ["rowFormChange"] }] } });
4599
+ }], propDecorators: { cardRowHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "cardRowHeight", required: false }] }], template: [{ type: i0.ContentChild, args: [i0.forwardRef(() => TemplateRef), { isSignal: true }] }] } });
4529
4600
 
4530
4601
  /**
4531
4602
  * Directive that registers `<tbw-grid-tool-buttons>` as a known Angular element.
@@ -4554,6 +4625,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
4554
4625
  * ```
4555
4626
  *
4556
4627
  * @category Directive
4628
+ * @since 0.13.0
4557
4629
  */
4558
4630
  class TbwGridToolButtons {
4559
4631
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: TbwGridToolButtons, deps: [], target: i0.ɵɵFactoryTarget.Directive });
@@ -4566,6 +4638,159 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
4566
4638
  }]
4567
4639
  }] });
4568
4640
 
4641
+ /** Capitalize the first letter of a string for header generation. */
4642
+ function capitalize(str) {
4643
+ return str.charAt(0).toUpperCase() + str.slice(1);
4644
+ }
4645
+ /**
4646
+ * Generate a human-readable header from a field name.
4647
+ *
4648
+ * Handles camelCase, snake_case, and kebab-case.
4649
+ *
4650
+ * @example
4651
+ * generateHeader('firstName') → 'First Name'
4652
+ * generateHeader('last_name') → 'Last Name'
4653
+ * generateHeader('email-address') → 'Email Address'
4654
+ * generateHeader('id') → 'ID' (special case)
4655
+ */
4656
+ function generateHeader(field) {
4657
+ if (field.toLowerCase() === 'id')
4658
+ return 'ID';
4659
+ const words = field
4660
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
4661
+ .replace(/[_-]/g, ' ')
4662
+ .split(' ')
4663
+ .filter(Boolean);
4664
+ return words.map(capitalize).join(' ');
4665
+ }
4666
+ /** Valid column types from the shorthand notation. */
4667
+ const VALID_TYPES = new Set(['string', 'number', 'boolean', 'date', 'datetime', 'currency']);
4668
+ /**
4669
+ * Parse a column shorthand string into a ColumnConfig.
4670
+ *
4671
+ * Supports formats:
4672
+ * - `'fieldName'` → `{ field: 'fieldName', header: 'Field Name' }`
4673
+ * - `'fieldName:type'` → `{ field: 'fieldName', header: 'Field Name', type: 'type' }`
4674
+ *
4675
+ * @param shorthand - The shorthand string (e.g., 'name', 'salary:number')
4676
+ * @returns A ColumnConfig object
4677
+ * @since 1.4.0
4678
+ */
4679
+ function parseColumnShorthand(shorthand) {
4680
+ const colonIndex = shorthand.lastIndexOf(':');
4681
+ if (colonIndex > 0) {
4682
+ const potentialType = shorthand.slice(colonIndex + 1).toLowerCase();
4683
+ if (VALID_TYPES.has(potentialType)) {
4684
+ const field = shorthand.slice(0, colonIndex);
4685
+ return {
4686
+ field: field,
4687
+ header: generateHeader(field),
4688
+ type: potentialType,
4689
+ };
4690
+ }
4691
+ }
4692
+ return {
4693
+ field: shorthand,
4694
+ header: generateHeader(shorthand),
4695
+ };
4696
+ }
4697
+ /**
4698
+ * Normalize an array of column shorthands to ColumnConfig objects.
4699
+ *
4700
+ * @param columns - Array of column shorthands (strings or ColumnConfig objects)
4701
+ * @returns Array of ColumnConfig objects
4702
+ * @since 1.4.0
4703
+ */
4704
+ function normalizeColumns(columns) {
4705
+ return columns.map((col) => (typeof col === 'string' ? parseColumnShorthand(col) : col));
4706
+ }
4707
+ /**
4708
+ * Apply column defaults to a list of columns. Individual column properties
4709
+ * override defaults.
4710
+ * @since 1.4.0
4711
+ */
4712
+ function applyColumnDefaults(columns, defaults) {
4713
+ if (!defaults)
4714
+ return columns;
4715
+ return columns.map((col) => ({ ...defaults, ...col }));
4716
+ }
4717
+ /** Check if an array of columns contains any shorthand strings. * @since 1.4.0
4718
+ */
4719
+ function hasColumnShorthands(columns) {
4720
+ return columns.some((col) => typeof col === 'string');
4721
+ }
4722
+
4723
+ /** Per-element map of claimed feature names → config getter. */
4724
+ const featureClaims = new WeakMap();
4725
+ /** Per-element set of claimed event names (matches `keyof DataGridEventMap`). */
4726
+ const eventClaims = new WeakMap();
4727
+ /**
4728
+ * Register a feature claim. Called by a feature directive's constructor;
4729
+ * the {@link Grid} directive will then use {@link getFeatureClaim} during
4730
+ * plugin creation instead of reading its own deprecated input.
4731
+ * @internal
4732
+ * @since 1.4.0
4733
+ */
4734
+ function registerFeatureClaim(grid, name, getConfig) {
4735
+ let map = featureClaims.get(grid);
4736
+ if (!map) {
4737
+ map = new Map();
4738
+ featureClaims.set(grid, map);
4739
+ }
4740
+ map.set(name, getConfig);
4741
+ }
4742
+ /**
4743
+ * Look up a feature claim. Returns the registered config getter, or
4744
+ * `undefined` if no directive owns this feature on this element.
4745
+ * @internal
4746
+ * @since 1.4.0
4747
+ */
4748
+ function getFeatureClaim(grid, name) {
4749
+ return featureClaims.get(grid)?.get(name);
4750
+ }
4751
+ /**
4752
+ * Drop a feature claim. Called by a feature directive's `ngOnDestroy` so
4753
+ * that, if the directive is removed (e.g. via `*ngIf`) but the host
4754
+ * `<tbw-grid>` survives, {@link Grid}'s deprecated input takes back over.
4755
+ * @internal
4756
+ * @since 1.4.0
4757
+ */
4758
+ function unregisterFeatureClaim(grid, name) {
4759
+ featureClaims.get(grid)?.delete(name);
4760
+ }
4761
+ /**
4762
+ * Mark an event as owned by a feature directive. {@link Grid}'s
4763
+ * `setupEventListeners` skips wiring its own deprecated `output()` for any
4764
+ * claimed event — the directive owns the listener and the emit.
4765
+ * @internal
4766
+ * @since 1.4.0
4767
+ */
4768
+ function claimEvent(grid, eventName) {
4769
+ let set = eventClaims.get(grid);
4770
+ if (!set) {
4771
+ set = new Set();
4772
+ eventClaims.set(grid, set);
4773
+ }
4774
+ set.add(eventName);
4775
+ }
4776
+ /**
4777
+ * Returns true if a directive has claimed this event on this grid element.
4778
+ * @internal
4779
+ * @since 1.4.0
4780
+ */
4781
+ function isEventClaimed(grid, eventName) {
4782
+ return eventClaims.get(grid)?.has(eventName) ?? false;
4783
+ }
4784
+ /**
4785
+ * Drop an event claim. Pair with {@link claimEvent} in a directive's
4786
+ * `ngOnDestroy`.
4787
+ * @internal
4788
+ * @since 1.4.0
4789
+ */
4790
+ function unclaimEvent(grid, eventName) {
4791
+ eventClaims.get(grid)?.delete(eventName);
4792
+ }
4793
+
4569
4794
  /**
4570
4795
  * Directive that automatically registers the Angular adapter with tbw-grid elements.
4571
4796
  *
@@ -4601,6 +4826,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
4601
4826
  * - Handles cleanup on destruction
4602
4827
  *
4603
4828
  * @category Directive
4829
+ * @since 0.1.0
4604
4830
  */
4605
4831
  class Grid {
4606
4832
  elementRef = inject((ElementRef));
@@ -4683,13 +4909,25 @@ class Grid {
4683
4909
  if (columnsValue === undefined)
4684
4910
  return;
4685
4911
  const grid = this.elementRef.nativeElement;
4912
+ // First normalize any shorthand strings to ColumnConfig objects, then
4913
+ // merge in any per-grid column defaults. Individual column props always win.
4914
+ // Note: Angular ColumnConfig allows component classes for renderer/editor,
4915
+ // which the adapter normalizes via processColumn below; we widen to `any`
4916
+ // here so the shorthand helpers (typed against the core ColumnConfig) accept
4917
+ // the Angular-flavoured payload unchanged.
4918
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4919
+ const normalized = applyColumnDefaults(
4920
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4921
+ normalizeColumns(columnsValue),
4922
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4923
+ this.columnDefaults());
4686
4924
  // Process columns through the adapter to convert Angular component classes
4687
4925
  // (renderer/editor) to functions — the grid's columns setter does NOT call
4688
4926
  // processConfig, unlike gridConfig. Without this, raw component classes
4689
4927
  // would be invoked without `new`, causing runtime errors.
4690
4928
  const processed = this.adapter
4691
- ? columnsValue.map((col) => this.adapter.processColumn(col))
4692
- : columnsValue;
4929
+ ? normalized.map((col) => this.adapter.processColumn(col))
4930
+ : normalized;
4693
4931
  grid.columns = processed;
4694
4932
  });
4695
4933
  // Effect to sync fitMode to the grid element
@@ -4824,21 +5062,35 @@ class Grid {
4824
5062
  /**
4825
5063
  * Column configuration array.
4826
5064
  *
5065
+ * Accepts either full `ColumnConfig` objects or shorthand strings such as
5066
+ * `'name'` or `'salary:number'`. Shorthands auto-generate human-readable
5067
+ * headers from the field name.
5068
+ *
4827
5069
  * Shorthand for setting columns without wrapping them in a full `gridConfig`.
4828
5070
  * If both `columns` and `gridConfig.columns` are set, `columns` takes precedence
4829
5071
  * (see configuration precedence system).
4830
5072
  *
4831
5073
  * @example
4832
5074
  * ```html
4833
- * <tbw-grid [rows]="data" [columns]="[
4834
- * { field: 'id', header: 'ID', pinned: 'left', width: 80 },
4835
- * { field: 'name', header: 'Name' },
4836
- * { field: 'email', header: 'Email' }
4837
- * ]" />
5075
+ * <tbw-grid [rows]="data" [columns]="['id:number', 'name', { field: 'status', editable: true }]" />
4838
5076
  * ```
4839
5077
  */
4840
5078
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
4841
5079
  columns = input(...(ngDevMode ? [undefined, { debugName: "columns" }] : /* istanbul ignore next */ []));
5080
+ /**
5081
+ * Default column properties applied to every column in `columns`.
5082
+ * Individual column properties override these defaults.
5083
+ *
5084
+ * @example
5085
+ * ```html
5086
+ * <tbw-grid
5087
+ * [columnDefaults]="{ sortable: true, resizable: true }"
5088
+ * [columns]="[{ field: 'id', sortable: false }, { field: 'name' }]"
5089
+ * />
5090
+ * ```
5091
+ */
5092
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
5093
+ columnDefaults = input(...(ngDevMode ? [undefined, { debugName: "columnDefaults" }] : /* istanbul ignore next */ []));
4842
5094
  /**
4843
5095
  * Column sizing strategy.
4844
5096
  *
@@ -4912,6 +5164,9 @@ class Grid {
4912
5164
  * <!-- Full config object -->
4913
5165
  * <tbw-grid [selection]="{ mode: 'range', checkbox: true }" />
4914
5166
  * ```
5167
+ *
5168
+ * @deprecated Use `GridSelectionDirective` from
5169
+ * `@toolbox-web/grid-angular/features/selection`. Will be removed in v2.0.0.
4915
5170
  */
4916
5171
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
4917
5172
  selection = input(...(ngDevMode ? [undefined, { debugName: "selection" }] : /* istanbul ignore next */ []));
@@ -4936,6 +5191,9 @@ class Grid {
4936
5191
  * <!-- Full config with callbacks -->
4937
5192
  * <tbw-grid [editing]="{ editOn: 'dblclick', onBeforeEditClose: myCallback }" />
4938
5193
  * ```
5194
+ *
5195
+ * @deprecated Use `GridEditingDirective` from
5196
+ * `@toolbox-web/grid-angular/features/editing`. Will be removed in v2.0.0.
4939
5197
  */
4940
5198
  editing = input(...(ngDevMode ? [undefined, { debugName: "editing" }] : /* istanbul ignore next */ []));
4941
5199
  /**
@@ -4950,6 +5208,9 @@ class Grid {
4950
5208
  * ```html
4951
5209
  * <tbw-grid [selection]="'range'" [clipboard]="true" />
4952
5210
  * ```
5211
+ *
5212
+ * @deprecated Use `GridClipboardDirective` from
5213
+ * `@toolbox-web/grid-angular/features/clipboard`. Will be removed in v2.0.0.
4953
5214
  */
4954
5215
  clipboard = input(...(ngDevMode ? [undefined, { debugName: "clipboard" }] : /* istanbul ignore next */ []));
4955
5216
  /**
@@ -4964,6 +5225,9 @@ class Grid {
4964
5225
  * ```html
4965
5226
  * <tbw-grid [contextMenu]="true" />
4966
5227
  * ```
5228
+ *
5229
+ * @deprecated Use `GridContextMenuDirective` from
5230
+ * `@toolbox-web/grid-angular/features/context-menu`. Will be removed in v2.0.0.
4967
5231
  */
4968
5232
  contextMenu = input(...(ngDevMode ? [undefined, { debugName: "contextMenu" }] : /* istanbul ignore next */ []));
4969
5233
  /**
@@ -4988,6 +5252,9 @@ class Grid {
4988
5252
  * <!-- Full config -->
4989
5253
  * <tbw-grid [multiSort]="{ maxSortColumns: 3 }" />
4990
5254
  * ```
5255
+ *
5256
+ * @deprecated Use `GridMultiSortDirective` from
5257
+ * `@toolbox-web/grid-angular/features/multi-sort`. Will be removed in v2.0.0.
4991
5258
  */
4992
5259
  multiSort = input(...(ngDevMode ? [undefined, { debugName: "multiSort" }] : /* istanbul ignore next */ []));
4993
5260
  /**
@@ -5003,6 +5270,13 @@ class Grid {
5003
5270
  * <tbw-grid [filtering]="true" />
5004
5271
  * <tbw-grid [filtering]="{ debounceMs: 200 }" />
5005
5272
  * ```
5273
+ *
5274
+ * @deprecated Use `GridFilteringDirective` from
5275
+ * `@toolbox-web/grid-angular/features/filtering` and add it to your
5276
+ * component's `imports`. The directive owns the `filtering` input + the
5277
+ * `filterChange` output and lets the typed surface tree-shake away when
5278
+ * the feature is not imported. This input remains as a non-breaking shim
5279
+ * and will be removed in v2.0.0.
5006
5280
  */
5007
5281
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5008
5282
  filtering = input(...(ngDevMode ? [undefined, { debugName: "filtering" }] : /* istanbul ignore next */ []));
@@ -5018,6 +5292,9 @@ class Grid {
5018
5292
  * ```html
5019
5293
  * <tbw-grid [reorderColumns]="true" />
5020
5294
  * ```
5295
+ *
5296
+ * @deprecated Use `GridReorderColumnsDirective` from
5297
+ * `@toolbox-web/grid-angular/features/reorder-columns`. Will be removed in v2.0.0.
5021
5298
  */
5022
5299
  reorderColumns = input(...(ngDevMode ? [undefined, { debugName: "reorderColumns" }] : /* istanbul ignore next */ []));
5023
5300
  /**
@@ -5032,6 +5309,9 @@ class Grid {
5032
5309
  * ```html
5033
5310
  * <tbw-grid [visibility]="true" />
5034
5311
  * ```
5312
+ *
5313
+ * @deprecated Use `GridVisibilityDirective` from
5314
+ * `@toolbox-web/grid-angular/features/visibility`. Will be removed in v2.0.0.
5035
5315
  */
5036
5316
  visibility = input(...(ngDevMode ? [undefined, { debugName: "visibility" }] : /* istanbul ignore next */ []));
5037
5317
  /**
@@ -5051,6 +5331,9 @@ class Grid {
5051
5331
  * { field: 'actions', pinned: 'right' }
5052
5332
  * ]" />
5053
5333
  * ```
5334
+ *
5335
+ * @deprecated Use `GridPinnedColumnsDirective` from
5336
+ * `@toolbox-web/grid-angular/features/pinned-columns`. Will be removed in v2.0.0.
5054
5337
  */
5055
5338
  pinnedColumns = input(...(ngDevMode ? [undefined, { debugName: "pinnedColumns" }] : /* istanbul ignore next */ []));
5056
5339
  /**
@@ -5065,6 +5348,9 @@ class Grid {
5065
5348
  * ```html
5066
5349
  * <tbw-grid [groupingColumns]="true" />
5067
5350
  * ```
5351
+ *
5352
+ * @deprecated Use `GridGroupingColumnsDirective` from
5353
+ * `@toolbox-web/grid-angular/features/grouping-columns`. Will be removed in v2.0.0.
5068
5354
  */
5069
5355
  groupingColumns = input(...(ngDevMode ? [undefined, { debugName: "groupingColumns" }] : /* istanbul ignore next */ []));
5070
5356
  /**
@@ -5079,6 +5365,9 @@ class Grid {
5079
5365
  * ```html
5080
5366
  * <tbw-grid [columnVirtualization]="true" />
5081
5367
  * ```
5368
+ *
5369
+ * @deprecated Use `GridColumnVirtualizationDirective` from
5370
+ * `@toolbox-web/grid-angular/features/column-virtualization`. Will be removed in v2.0.0.
5082
5371
  */
5083
5372
  columnVirtualization = input(...(ngDevMode ? [undefined, { debugName: "columnVirtualization" }] : /* istanbul ignore next */ []));
5084
5373
  /**
@@ -5104,6 +5393,9 @@ class Grid {
5104
5393
  * ```html
5105
5394
  * <tbw-grid [rowDragDrop]="{ dropZone: 'employees', operation: 'move' }" />
5106
5395
  * ```
5396
+ *
5397
+ * @deprecated Use `GridRowDragDropDirective` from
5398
+ * `@toolbox-web/grid-angular/features/row-drag-drop`. Will be removed in v2.0.0.
5107
5399
  */
5108
5400
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5109
5401
  rowDragDrop = input(...(ngDevMode ? [undefined, { debugName: "rowDragDrop" }] : /* istanbul ignore next */ []));
@@ -5119,6 +5411,9 @@ class Grid {
5119
5411
  * ```html
5120
5412
  * <tbw-grid [groupingRows]="{ groupBy: ['department'] }" />
5121
5413
  * ```
5414
+ *
5415
+ * @deprecated Use `GridGroupingRowsDirective` from
5416
+ * `@toolbox-web/grid-angular/features/grouping-rows`. Will be removed in v2.0.0.
5122
5417
  */
5123
5418
  groupingRows = input(...(ngDevMode ? [undefined, { debugName: "groupingRows" }] : /* istanbul ignore next */ []));
5124
5419
  /**
@@ -5133,6 +5428,9 @@ class Grid {
5133
5428
  * ```html
5134
5429
  * <tbw-grid [pinnedRows]="{ bottom: [{ type: 'aggregation' }] }" />
5135
5430
  * ```
5431
+ *
5432
+ * @deprecated Use `GridPinnedRowsDirective` from
5433
+ * `@toolbox-web/grid-angular/features/pinned-rows`. Will be removed in v2.0.0.
5136
5434
  */
5137
5435
  pinnedRows = input(...(ngDevMode ? [undefined, { debugName: "pinnedRows" }] : /* istanbul ignore next */ []));
5138
5436
  /**
@@ -5147,6 +5445,9 @@ class Grid {
5147
5445
  * ```html
5148
5446
  * <tbw-grid [tree]="{ childrenField: 'children' }" />
5149
5447
  * ```
5448
+ *
5449
+ * @deprecated Use `GridTreeDirective` from
5450
+ * `@toolbox-web/grid-angular/features/tree`. Will be removed in v2.0.0.
5150
5451
  */
5151
5452
  tree = input(...(ngDevMode ? [undefined, { debugName: "tree" }] : /* istanbul ignore next */ []));
5152
5453
  /**
@@ -5161,6 +5462,9 @@ class Grid {
5161
5462
  * ```html
5162
5463
  * <tbw-grid [masterDetail]="{ detailRenderer: detailFn }" />
5163
5464
  * ```
5465
+ *
5466
+ * @deprecated Use `GridMasterDetailDirective` from
5467
+ * `@toolbox-web/grid-angular/features/master-detail`. Will be removed in v2.0.0.
5164
5468
  */
5165
5469
  masterDetail = input(...(ngDevMode ? [undefined, { debugName: "masterDetail" }] : /* istanbul ignore next */ []));
5166
5470
  /**
@@ -5175,6 +5479,9 @@ class Grid {
5175
5479
  * ```html
5176
5480
  * <tbw-grid [responsive]="{ breakpoint: 768 }" />
5177
5481
  * ```
5482
+ *
5483
+ * @deprecated Use `GridResponsiveDirective` from
5484
+ * `@toolbox-web/grid-angular/features/responsive`. Will be removed in v2.0.0.
5178
5485
  */
5179
5486
  responsive = input(...(ngDevMode ? [undefined, { debugName: "responsive" }] : /* istanbul ignore next */ []));
5180
5487
  /**
@@ -5189,6 +5496,9 @@ class Grid {
5189
5496
  * ```html
5190
5497
  * <tbw-grid [editing]="'dblclick'" [undoRedo]="true" />
5191
5498
  * ```
5499
+ *
5500
+ * @deprecated Use `GridUndoRedoDirective` from
5501
+ * `@toolbox-web/grid-angular/features/undo-redo`. Will be removed in v2.0.0.
5192
5502
  */
5193
5503
  undoRedo = input(...(ngDevMode ? [undefined, { debugName: "undoRedo" }] : /* istanbul ignore next */ []));
5194
5504
  /**
@@ -5204,6 +5514,9 @@ class Grid {
5204
5514
  * <tbw-grid [export]="true" />
5205
5515
  * <tbw-grid [export]="{ filename: 'data.csv' }" />
5206
5516
  * ```
5517
+ *
5518
+ * @deprecated Use `GridExportDirective` from
5519
+ * `@toolbox-web/grid-angular/features/export`. Will be removed in v2.0.0.
5207
5520
  */
5208
5521
  exportFeature = input(undefined, { ...(ngDevMode ? { debugName: "exportFeature" } : /* istanbul ignore next */ {}), alias: 'export' });
5209
5522
  /**
@@ -5218,6 +5531,9 @@ class Grid {
5218
5531
  * ```html
5219
5532
  * <tbw-grid [print]="true" />
5220
5533
  * ```
5534
+ *
5535
+ * @deprecated Use `GridPrintDirective` from
5536
+ * `@toolbox-web/grid-angular/features/print`. Will be removed in v2.0.0.
5221
5537
  */
5222
5538
  print = input(...(ngDevMode ? [undefined, { debugName: "print" }] : /* istanbul ignore next */ []));
5223
5539
  /**
@@ -5232,6 +5548,9 @@ class Grid {
5232
5548
  * ```html
5233
5549
  * <tbw-grid [pivot]="{ rowFields: ['category'], valueField: 'sales' }" />
5234
5550
  * ```
5551
+ *
5552
+ * @deprecated Use `GridPivotDirective` from
5553
+ * `@toolbox-web/grid-angular/features/pivot`. Will be removed in v2.0.0.
5235
5554
  */
5236
5555
  pivot = input(...(ngDevMode ? [undefined, { debugName: "pivot" }] : /* istanbul ignore next */ []));
5237
5556
  /**
@@ -5246,6 +5565,9 @@ class Grid {
5246
5565
  * ```html
5247
5566
  * <tbw-grid [serverSide]="{ dataSource: fetchDataFn }" />
5248
5567
  * ```
5568
+ *
5569
+ * @deprecated Use `GridServerSideDirective` from
5570
+ * `@toolbox-web/grid-angular/features/server-side`. Will be removed in v2.0.0.
5249
5571
  */
5250
5572
  serverSide = input(...(ngDevMode ? [undefined, { debugName: "serverSide" }] : /* istanbul ignore next */ []));
5251
5573
  /**
@@ -5256,6 +5578,9 @@ class Grid {
5256
5578
  * <tbw-grid [tooltip]="true" />
5257
5579
  * <tbw-grid [tooltip]="{ header: true, cell: false }" />
5258
5580
  * ```
5581
+ *
5582
+ * @deprecated Use `GridTooltipDirective` from
5583
+ * `@toolbox-web/grid-angular/features/tooltip`. Will be removed in v2.0.0.
5259
5584
  */
5260
5585
  tooltip = input(...(ngDevMode ? [undefined, { debugName: "tooltip" }] : /* istanbul ignore next */ []));
5261
5586
  // ═══════════════════════════════════════════════════════════════════════════
@@ -5315,8 +5640,85 @@ class Grid {
5315
5640
  * console.log(`Changed ${event.field} to ${event.value} in row ${event.rowIndex}`);
5316
5641
  * }
5317
5642
  * ```
5643
+ *
5644
+ * @deprecated Use `GridEditingDirective` from
5645
+ * `@toolbox-web/grid-angular/features/editing`. Will be removed in v2.0.0.
5318
5646
  */
5319
5647
  cellCommit = output();
5648
+ /**
5649
+ * Emitted when a cell edit is cancelled (Escape, click outside without
5650
+ * commit, or `editor.cancel()`).
5651
+ *
5652
+ * @example
5653
+ * ```html
5654
+ * <tbw-grid (cellCancel)="onCellCancel($event)">...</tbw-grid>
5655
+ * ```
5656
+ *
5657
+ * @deprecated Use `GridEditingDirective` from
5658
+ * `@toolbox-web/grid-angular/features/editing`. Will be removed in v2.0.0.
5659
+ */
5660
+ cellCancel = output();
5661
+ /**
5662
+ * Emitted when a cell editor opens.
5663
+ *
5664
+ * @example
5665
+ * ```html
5666
+ * <tbw-grid (editOpen)="onEditOpen($event)">...</tbw-grid>
5667
+ * ```
5668
+ *
5669
+ * @deprecated Use `GridEditingDirective` from
5670
+ * `@toolbox-web/grid-angular/features/editing`. Will be removed in v2.0.0.
5671
+ */
5672
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
5673
+ editOpen = output();
5674
+ /**
5675
+ * Emitted before an editor closes. Useful for last-chance validation.
5676
+ *
5677
+ * @example
5678
+ * ```html
5679
+ * <tbw-grid (beforeEditClose)="onBeforeEditClose($event)">...</tbw-grid>
5680
+ * ```
5681
+ *
5682
+ * @deprecated Use `GridEditingDirective` from
5683
+ * `@toolbox-web/grid-angular/features/editing`. Will be removed in v2.0.0.
5684
+ */
5685
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
5686
+ beforeEditClose = output();
5687
+ /**
5688
+ * Emitted after an editor closes (whether committed or cancelled).
5689
+ *
5690
+ * @example
5691
+ * ```html
5692
+ * <tbw-grid (editClose)="onEditClose($event)">...</tbw-grid>
5693
+ * ```
5694
+ *
5695
+ * @deprecated Use `GridEditingDirective` from
5696
+ * `@toolbox-web/grid-angular/features/editing`. Will be removed in v2.0.0.
5697
+ */
5698
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
5699
+ editClose = output();
5700
+ /**
5701
+ * Emitted when the dirty / changed-rows state transitions.
5702
+ *
5703
+ * @example
5704
+ * ```html
5705
+ * <tbw-grid (dirtyChange)="onDirtyChange($event)">...</tbw-grid>
5706
+ * ```
5707
+ *
5708
+ * @deprecated Use `GridEditingDirective` from
5709
+ * `@toolbox-web/grid-angular/features/editing`. Will be removed in v2.0.0.
5710
+ */
5711
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
5712
+ dirtyChange = output();
5713
+ /**
5714
+ * Emitted when row data is replaced (e.g. via the `rows` setter).
5715
+ *
5716
+ * @example
5717
+ * ```html
5718
+ * <tbw-grid (dataChange)="onDataChange($event)">...</tbw-grid>
5719
+ * ```
5720
+ */
5721
+ dataChange = output();
5320
5722
  /**
5321
5723
  * Emitted when a row's values are committed (bulk/row editing).
5322
5724
  * Provides the row data and change tracking information.
@@ -5325,6 +5727,9 @@ class Grid {
5325
5727
  * ```html
5326
5728
  * <tbw-grid (rowCommit)="onRowCommit($event)">...</tbw-grid>
5327
5729
  * ```
5730
+ *
5731
+ * @deprecated Use `GridEditingDirective` from
5732
+ * `@toolbox-web/grid-angular/features/editing`. Will be removed in v2.0.0.
5328
5733
  */
5329
5734
  rowCommit = output();
5330
5735
  /**
@@ -5334,6 +5739,9 @@ class Grid {
5334
5739
  * ```html
5335
5740
  * <tbw-grid (changedRowsReset)="onChangedRowsReset($event)">...</tbw-grid>
5336
5741
  * ```
5742
+ *
5743
+ * @deprecated Use `GridEditingDirective` from
5744
+ * `@toolbox-web/grid-angular/features/editing`. Will be removed in v2.0.0.
5337
5745
  */
5338
5746
  changedRowsReset = output();
5339
5747
  /**
@@ -5352,6 +5760,11 @@ class Grid {
5352
5760
  * ```html
5353
5761
  * <tbw-grid (filterChange)="onFilterChange($event)">...</tbw-grid>
5354
5762
  * ```
5763
+ *
5764
+ * @deprecated Use `GridFilteringDirective` from
5765
+ * `@toolbox-web/grid-angular/features/filtering` (the directive
5766
+ * declares the `(filterChange)` output). This output remains as a
5767
+ * non-breaking shim and will be removed in v2.0.0.
5355
5768
  */
5356
5769
  filterChange = output();
5357
5770
  /**
@@ -5363,6 +5776,15 @@ class Grid {
5363
5776
  * ```
5364
5777
  */
5365
5778
  columnResize = output();
5779
+ /**
5780
+ * Emitted when a column's width is reset (double-click on the resize handle).
5781
+ *
5782
+ * @example
5783
+ * ```html
5784
+ * <tbw-grid (columnResizeReset)="onColumnResizeReset($event)">...</tbw-grid>
5785
+ * ```
5786
+ */
5787
+ columnResizeReset = output();
5366
5788
  /**
5367
5789
  * Emitted when a column is moved via drag-and-drop.
5368
5790
  *
@@ -5370,15 +5792,23 @@ class Grid {
5370
5792
  * ```html
5371
5793
  * <tbw-grid (columnMove)="onColumnMove($event)">...</tbw-grid>
5372
5794
  * ```
5795
+ *
5796
+ * @deprecated Use `GridReorderColumnsDirective` from
5797
+ * `@toolbox-web/grid-angular/features/reorder-columns`. Will be removed in v2.0.0.
5373
5798
  */
5374
5799
  columnMove = output();
5375
5800
  /**
5376
- * Emitted when column visibility changes.
5801
+ * Emitted when a column is shown or hidden — either via the visibility
5802
+ * sidebar, `grid.toggleColumnVisibility(field)`, `grid.setColumnVisible(field, visible)`,
5803
+ * or `grid.showAllColumns()`.
5377
5804
  *
5378
5805
  * @example
5379
5806
  * ```html
5380
5807
  * <tbw-grid (columnVisibility)="onColumnVisibility($event)">...</tbw-grid>
5381
5808
  * ```
5809
+ *
5810
+ * @deprecated Use `GridVisibilityDirective` from
5811
+ * `@toolbox-web/grid-angular/features/visibility`. Will be removed in v2.0.0.
5382
5812
  */
5383
5813
  columnVisibility = output();
5384
5814
  /**
@@ -5397,6 +5827,9 @@ class Grid {
5397
5827
  * ```html
5398
5828
  * <tbw-grid (selectionChange)="onSelectionChange($event)">...</tbw-grid>
5399
5829
  * ```
5830
+ *
5831
+ * @deprecated Use `GridSelectionDirective` from
5832
+ * `@toolbox-web/grid-angular/features/selection`. Will be removed in v2.0.0.
5400
5833
  */
5401
5834
  selectionChange = output();
5402
5835
  /**
@@ -5406,6 +5839,9 @@ class Grid {
5406
5839
  * ```html
5407
5840
  * <tbw-grid (rowMove)="onRowMove($event)">...</tbw-grid>
5408
5841
  * ```
5842
+ *
5843
+ * @deprecated Use `GridRowDragDropDirective` from
5844
+ * `@toolbox-web/grid-angular/features/row-drag-drop`. Will be removed in v2.0.0.
5409
5845
  */
5410
5846
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5411
5847
  rowMove = output();
@@ -5416,23 +5852,35 @@ class Grid {
5416
5852
  * ```html
5417
5853
  * <tbw-grid (rowDragStart)="onRowDragStart($event)">...</tbw-grid>
5418
5854
  * ```
5855
+ *
5856
+ * @deprecated Use `GridRowDragDropDirective` from
5857
+ * `@toolbox-web/grid-angular/features/row-drag-drop`. Will be removed in v2.0.0.
5419
5858
  */
5420
5859
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5421
5860
  rowDragStart = output();
5422
5861
  /**
5423
5862
  * Emitted when a row drag ends (after drop or cancel).
5863
+ *
5864
+ * @deprecated Use `GridRowDragDropDirective` from
5865
+ * `@toolbox-web/grid-angular/features/row-drag-drop`. Will be removed in v2.0.0.
5424
5866
  */
5425
5867
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5426
5868
  rowDragEnd = output();
5427
5869
  /**
5428
5870
  * Emitted on the target grid when rows are dropped from another grid.
5429
5871
  * Cancelable via `event.preventDefault()`.
5872
+ *
5873
+ * @deprecated Use `GridRowDragDropDirective` from
5874
+ * `@toolbox-web/grid-angular/features/row-drag-drop`. Will be removed in v2.0.0.
5430
5875
  */
5431
5876
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5432
5877
  rowDrop = output();
5433
5878
  /**
5434
5879
  * Emitted on BOTH source and target grids after a successful cross-grid
5435
5880
  * row transfer.
5881
+ *
5882
+ * @deprecated Use `GridRowDragDropDirective` from
5883
+ * `@toolbox-web/grid-angular/features/row-drag-drop`. Will be removed in v2.0.0.
5436
5884
  */
5437
5885
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5438
5886
  rowTransfer = output();
@@ -5443,8 +5891,35 @@ class Grid {
5443
5891
  * ```html
5444
5892
  * <tbw-grid (groupToggle)="onGroupToggle($event)">...</tbw-grid>
5445
5893
  * ```
5894
+ *
5895
+ * @deprecated Use `GridGroupingRowsDirective` from
5896
+ * `@toolbox-web/grid-angular/features/grouping-rows`. Will be removed in v2.0.0.
5446
5897
  */
5447
5898
  groupToggle = output();
5899
+ /**
5900
+ * Emitted when a group is expanded.
5901
+ *
5902
+ * @example
5903
+ * ```html
5904
+ * <tbw-grid (groupExpand)="onGroupExpand($event)">...</tbw-grid>
5905
+ * ```
5906
+ *
5907
+ * @deprecated Use `GridGroupingRowsDirective` from
5908
+ * `@toolbox-web/grid-angular/features/grouping-rows`. Will be removed in v2.0.0.
5909
+ */
5910
+ groupExpand = output();
5911
+ /**
5912
+ * Emitted when a group is collapsed.
5913
+ *
5914
+ * @example
5915
+ * ```html
5916
+ * <tbw-grid (groupCollapse)="onGroupCollapse($event)">...</tbw-grid>
5917
+ * ```
5918
+ *
5919
+ * @deprecated Use `GridGroupingRowsDirective` from
5920
+ * `@toolbox-web/grid-angular/features/grouping-rows`. Will be removed in v2.0.0.
5921
+ */
5922
+ groupCollapse = output();
5448
5923
  /**
5449
5924
  * Emitted when a tree node is expanded.
5450
5925
  *
@@ -5452,6 +5927,9 @@ class Grid {
5452
5927
  * ```html
5453
5928
  * <tbw-grid (treeExpand)="onTreeExpand($event)">...</tbw-grid>
5454
5929
  * ```
5930
+ *
5931
+ * @deprecated Use `GridTreeDirective` from
5932
+ * `@toolbox-web/grid-angular/features/tree`. Will be removed in v2.0.0.
5455
5933
  */
5456
5934
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5457
5935
  treeExpand = output();
@@ -5462,6 +5940,9 @@ class Grid {
5462
5940
  * ```html
5463
5941
  * <tbw-grid (detailExpand)="onDetailExpand($event)">...</tbw-grid>
5464
5942
  * ```
5943
+ *
5944
+ * @deprecated Use `GridMasterDetailDirective` from
5945
+ * `@toolbox-web/grid-angular/features/master-detail`. Will be removed in v2.0.0.
5465
5946
  */
5466
5947
  detailExpand = output();
5467
5948
  /**
@@ -5471,8 +5952,23 @@ class Grid {
5471
5952
  * ```html
5472
5953
  * <tbw-grid (responsiveChange)="onResponsiveChange($event)">...</tbw-grid>
5473
5954
  * ```
5955
+ *
5956
+ * @deprecated Use `GridResponsiveDirective` from
5957
+ * `@toolbox-web/grid-angular/features/responsive`. Will be removed in v2.0.0.
5474
5958
  */
5475
5959
  responsiveChange = output();
5960
+ /**
5961
+ * Emitted when the context menu opens.
5962
+ *
5963
+ * @example
5964
+ * ```html
5965
+ * <tbw-grid (contextMenuOpen)="onContextMenuOpen($event)">...</tbw-grid>
5966
+ * ```
5967
+ *
5968
+ * @deprecated Use `GridContextMenuDirective` from
5969
+ * `@toolbox-web/grid-angular/features/context-menu`. Will be removed in v2.0.0.
5970
+ */
5971
+ contextMenuOpen = output();
5476
5972
  /**
5477
5973
  * Emitted when cells are copied to clipboard.
5478
5974
  *
@@ -5480,6 +5976,9 @@ class Grid {
5480
5976
  * ```html
5481
5977
  * <tbw-grid (copy)="onCopy($event)">...</tbw-grid>
5482
5978
  * ```
5979
+ *
5980
+ * @deprecated Use `GridClipboardDirective` from
5981
+ * `@toolbox-web/grid-angular/features/clipboard`. Will be removed in v2.0.0.
5483
5982
  */
5484
5983
  copy = output();
5485
5984
  /**
@@ -5489,17 +5988,35 @@ class Grid {
5489
5988
  * ```html
5490
5989
  * <tbw-grid (paste)="onPaste($event)">...</tbw-grid>
5491
5990
  * ```
5991
+ *
5992
+ * @deprecated Use `GridClipboardDirective` from
5993
+ * `@toolbox-web/grid-angular/features/clipboard`. Will be removed in v2.0.0.
5492
5994
  */
5493
5995
  paste = output();
5494
5996
  /**
5495
- * Emitted when undo/redo is performed.
5997
+ * Emitted when an undo action is performed.
5998
+ *
5999
+ * @example
6000
+ * ```html
6001
+ * <tbw-grid (undo)="onUndo($event)">...</tbw-grid>
6002
+ * ```
6003
+ *
6004
+ * @deprecated Use `GridUndoRedoDirective` from
6005
+ * `@toolbox-web/grid-angular/features/undo-redo`. Will be removed in v2.0.0.
6006
+ */
6007
+ undo = output();
6008
+ /**
6009
+ * Emitted when a redo action is performed.
5496
6010
  *
5497
6011
  * @example
5498
6012
  * ```html
5499
- * <tbw-grid (undoRedoAction)="onUndoRedo($event)">...</tbw-grid>
6013
+ * <tbw-grid (redo)="onRedo($event)">...</tbw-grid>
5500
6014
  * ```
6015
+ *
6016
+ * @deprecated Use `GridUndoRedoDirective` from
6017
+ * `@toolbox-web/grid-angular/features/undo-redo`. Will be removed in v2.0.0.
5501
6018
  */
5502
- undoRedoAction = output();
6019
+ redo = output();
5503
6020
  /**
5504
6021
  * Emitted when export completes.
5505
6022
  *
@@ -5507,6 +6024,9 @@ class Grid {
5507
6024
  * ```html
5508
6025
  * <tbw-grid (exportComplete)="onExportComplete($event)">...</tbw-grid>
5509
6026
  * ```
6027
+ *
6028
+ * @deprecated Use `GridExportDirective` from
6029
+ * `@toolbox-web/grid-angular/features/export`. Will be removed in v2.0.0.
5510
6030
  */
5511
6031
  exportComplete = output();
5512
6032
  /**
@@ -5516,6 +6036,9 @@ class Grid {
5516
6036
  * ```html
5517
6037
  * <tbw-grid (printStart)="onPrintStart($event)">...</tbw-grid>
5518
6038
  * ```
6039
+ *
6040
+ * @deprecated Use `GridPrintDirective` from
6041
+ * `@toolbox-web/grid-angular/features/print`. Will be removed in v2.0.0.
5519
6042
  */
5520
6043
  printStart = output();
5521
6044
  /**
@@ -5525,6 +6048,9 @@ class Grid {
5525
6048
  * ```html
5526
6049
  * <tbw-grid (printComplete)="onPrintComplete($event)">...</tbw-grid>
5527
6050
  * ```
6051
+ *
6052
+ * @deprecated Use `GridPrintDirective` from
6053
+ * `@toolbox-web/grid-angular/features/print`. Will be removed in v2.0.0.
5528
6054
  */
5529
6055
  printComplete = output();
5530
6056
  /**
@@ -5543,18 +6069,31 @@ class Grid {
5543
6069
  * ```
5544
6070
  */
5545
6071
  tbwScroll = output();
5546
- // Map of output names to event names for automatic wiring
6072
+ // Map of output names to event names for automatic wiring.
6073
+ //
6074
+ // The `satisfies` clause enforces compile-time sync against
6075
+ // `DataGridEventMap`: every value must be a real event name (typos and
6076
+ // stale entries pointing at non-existent events fail to compile).
6077
+ // Plugin event augmentations of `DataGridEventMap` flow through
6078
+ // automatically via the `/all` import.
5547
6079
  eventOutputMap = {
5548
6080
  cellClick: 'cell-click',
5549
6081
  rowClick: 'row-click',
5550
6082
  cellActivate: 'cell-activate',
5551
6083
  cellChange: 'cell-change',
5552
6084
  cellCommit: 'cell-commit',
6085
+ cellCancel: 'cell-cancel',
5553
6086
  rowCommit: 'row-commit',
5554
6087
  changedRowsReset: 'changed-rows-reset',
6088
+ editOpen: 'edit-open',
6089
+ beforeEditClose: 'before-edit-close',
6090
+ editClose: 'edit-close',
6091
+ dirtyChange: 'dirty-change',
6092
+ dataChange: 'data-change',
5555
6093
  sortChange: 'sort-change',
5556
6094
  filterChange: 'filter-change',
5557
6095
  columnResize: 'column-resize',
6096
+ columnResizeReset: 'column-resize-reset',
5558
6097
  columnMove: 'column-move',
5559
6098
  columnVisibility: 'column-visibility',
5560
6099
  columnStateChange: 'column-state-change',
@@ -5565,17 +6104,39 @@ class Grid {
5565
6104
  rowDrop: 'row-drop',
5566
6105
  rowTransfer: 'row-transfer',
5567
6106
  groupToggle: 'group-toggle',
6107
+ groupExpand: 'group-expand',
6108
+ groupCollapse: 'group-collapse',
5568
6109
  treeExpand: 'tree-expand',
5569
6110
  detailExpand: 'detail-expand',
5570
6111
  responsiveChange: 'responsive-change',
6112
+ contextMenuOpen: 'context-menu-open',
5571
6113
  copy: 'copy',
5572
6114
  paste: 'paste',
5573
- undoRedoAction: 'undo-redo',
6115
+ undo: 'undo',
6116
+ redo: 'redo',
5574
6117
  exportComplete: 'export-complete',
5575
6118
  printStart: 'print-start',
5576
6119
  printComplete: 'print-complete',
5577
6120
  tbwScroll: 'tbw-scroll',
5578
6121
  };
6122
+ // ─────────────────────────────────────────────────────────────────────────
6123
+ // Forward-only event coverage guard.
6124
+ //
6125
+ // Mirrors the React adapter's `_AssertFeaturePropsCoverCore` pattern. If a
6126
+ // new event is added to core's `DataGridEventMap` (via plugin module
6127
+ // augmentation in `/all`) but no `eventOutputMap` entry covers it, this
6128
+ // type fails to evaluate to `true` and the build breaks. Adapter consumers
6129
+ // never see a silently-dropped event.
6130
+ //
6131
+ // Reverse direction (extra `eventOutputMap` entries pointing at non-existent
6132
+ // events) is already enforced by the `satisfies` clause above.
6133
+ //
6134
+ // To consciously omit an event from the Angular surface, add it to the
6135
+ // `IntentionallyOmittedEvents` union below with a comment explaining why.
6136
+ // ─────────────────────────────────────────────────────────────────────────
6137
+ /** Events deliberately not exposed as Angular outputs. Keep empty unless documented. */
6138
+ _intentionallyOmittedEvents;
6139
+ _assertEventOutputMapCoversCore;
5579
6140
  // Store event listeners for cleanup
5580
6141
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5581
6142
  eventListeners = new Map();
@@ -5594,10 +6155,18 @@ class Grid {
5594
6155
  }
5595
6156
  /**
5596
6157
  * Sets up event listeners for all outputs using the eventOutputMap.
6158
+ *
6159
+ * Hybrid v1.x / v2 ownership: events claimed by an attribute-selector
6160
+ * feature directive (via `claimEvent` in `feature-claims.ts`) are skipped
6161
+ * here so the directive's own `output()` is the sole emitter. Without
6162
+ * this skip both this directive's deprecated output and the directive's
6163
+ * new output would fire for the same DOM event.
5597
6164
  */
5598
6165
  setupEventListeners(grid) {
5599
6166
  // Wire up all event listeners
5600
6167
  for (const [outputName, eventName] of Object.entries(this.eventOutputMap)) {
6168
+ if (isEventClaimed(grid, eventName))
6169
+ continue;
5601
6170
  const listener = (e) => {
5602
6171
  const detail = e.detail;
5603
6172
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -5610,57 +6179,62 @@ class Grid {
5610
6179
  /**
5611
6180
  * Creates plugins from feature inputs.
5612
6181
  * Uses the feature registry to allow tree-shaking - only imported features are bundled.
6182
+ * Per-feature config bridging (e.g. converting Angular component classes inside
6183
+ * `groupingColumns` / `groupingRows` / `pinnedRows` configs to renderer functions)
6184
+ * runs via `getFeatureConfigPreprocessor`, populated by feature secondary entries.
6185
+ *
6186
+ * Hybrid v1.x / v2 ownership: when an attribute-selector feature directive
6187
+ * (e.g. `GridFilteringDirective`) is present on the same `<tbw-grid>`
6188
+ * element it claims its feature in `feature-claims.ts`. We then read the
6189
+ * claim's config getter — which transitively reads the directive's input
6190
+ * signal, establishing reactive dependency tracking — instead of the
6191
+ * deprecated input on this directive. This keeps the existing `[filtering]`
6192
+ * binding working when used directly on `<tbw-grid>` (no directive, no
6193
+ * claim) while letting the directive own the binding when imported.
6194
+ *
5613
6195
  * Returns the array of created plugins (doesn't modify grid).
5614
6196
  */
5615
6197
  createFeaturePlugins() {
5616
6198
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5617
6199
  const plugins = [];
6200
+ const adapter = this.adapter;
6201
+ const grid = this.elementRef.nativeElement;
5618
6202
  // Helper to add plugin if feature is registered
5619
- const addPlugin = (name, config) => {
6203
+ const addPlugin = (name, ownInput) => {
6204
+ // Directive-owned config wins. Reading the claim's getter inside this
6205
+ // effect registers the directive's input signal as a dependency, so
6206
+ // changes to e.g. `[filtering]` on the directive re-trigger this effect.
6207
+ const claim = getFeatureClaim(grid, name);
6208
+ const config = claim ? claim() : ownInput;
5620
6209
  if (config === undefined || config === null || config === false)
5621
6210
  return;
5622
- const plugin = createPluginFromFeature(name, config);
6211
+ // Apply per-feature config preprocessor (registered by feature secondary entries)
6212
+ // to bridge Angular component classes embedded in the config before instantiation.
6213
+ let finalConfig = config;
6214
+ if (adapter && config !== true && typeof config === 'object') {
6215
+ const preprocess = getFeatureConfigPreprocessor(name);
6216
+ if (preprocess)
6217
+ finalConfig = preprocess(config, adapter);
6218
+ }
6219
+ const plugin = createPluginFromFeature(name, finalConfig);
5623
6220
  if (plugin)
5624
6221
  plugins.push(plugin);
5625
6222
  };
5626
- // Add plugins for each feature input
5627
6223
  addPlugin('selection', this.selection());
5628
6224
  addPlugin('editing', this.editing());
5629
6225
  addPlugin('clipboard', this.clipboard());
5630
6226
  addPlugin('contextMenu', this.contextMenu());
5631
- // multiSort is the primary input
5632
6227
  addPlugin('multiSort', this.multiSort());
5633
6228
  addPlugin('filtering', this.filtering());
5634
6229
  addPlugin('reorderColumns', this.reorderColumns());
5635
6230
  addPlugin('visibility', this.visibility());
5636
6231
  addPlugin('pinnedColumns', this.pinnedColumns());
5637
- // Pre-process groupingColumns config to bridge Angular component classes
5638
- const gcConfig = this.groupingColumns();
5639
- if (gcConfig && typeof gcConfig === 'object' && this.adapter) {
5640
- addPlugin('groupingColumns', this.adapter.processGroupingColumnsConfig(gcConfig));
5641
- }
5642
- else {
5643
- addPlugin('groupingColumns', gcConfig);
5644
- }
6232
+ addPlugin('groupingColumns', this.groupingColumns());
5645
6233
  addPlugin('columnVirtualization', this.columnVirtualization());
5646
6234
  addPlugin('reorderRows', this.reorderRows());
5647
6235
  addPlugin('rowDragDrop', this.rowDragDrop());
5648
- // Pre-process groupingRows config to bridge Angular component classes
5649
- const grConfig = this.groupingRows();
5650
- if (grConfig && typeof grConfig === 'object' && this.adapter) {
5651
- addPlugin('groupingRows', this.adapter.processGroupingRowsConfig(grConfig));
5652
- }
5653
- else {
5654
- addPlugin('groupingRows', grConfig);
5655
- }
5656
- // Pre-process pinnedRows config to bridge Angular component classes in customPanels
5657
- const prConfig = this.pinnedRows();
5658
- if (prConfig && typeof prConfig === 'object' && this.adapter) {
5659
- addPlugin('pinnedRows', this.adapter.processPinnedRowsConfig(prConfig));
5660
- }
5661
- else {
5662
- addPlugin('pinnedRows', prConfig);
5663
- }
6236
+ addPlugin('groupingRows', this.groupingRows());
6237
+ addPlugin('pinnedRows', this.pinnedRows());
5664
6238
  addPlugin('tree', this.tree());
5665
6239
  addPlugin('masterDetail', this.masterDetail());
5666
6240
  addPlugin('responsive', this.responsive());
@@ -5680,10 +6254,13 @@ class Grid {
5680
6254
  // Use setTimeout to ensure Angular effects have run (template registration)
5681
6255
  setTimeout(() => {
5682
6256
  grid.refreshColumns();
5683
- // Configure MasterDetailPlugin after Angular templates are registered
5684
- this.configureMasterDetail(grid);
5685
- // Configure ResponsivePlugin card renderer if template is present
5686
- this.configureResponsiveCard(grid);
6257
+ // Run feature-registered template bridges. Each bridge wires a specific
6258
+ // light-DOM slot element (<tbw-grid-detail>, <tbw-grid-responsive-card>, ...)
6259
+ // to its plugin. Bridges are registered by feature secondary entries
6260
+ // (e.g. `import '@toolbox-web/grid-angular/features/master-detail';`).
6261
+ if (this.adapter) {
6262
+ runTemplateBridges({ grid, adapter: this.adapter });
6263
+ }
5687
6264
  // Refresh shell header to pick up tool panel templates
5688
6265
  // This allows Angular templates to be used in tool panels
5689
6266
  if (typeof grid.refreshShellHeader === 'function') {
@@ -5707,85 +6284,6 @@ class Grid {
5707
6284
  grid.registerStyles?.('angular-custom-styles', styles);
5708
6285
  });
5709
6286
  }
5710
- /**
5711
- * Configures the MasterDetailPlugin after Angular templates are registered.
5712
- * - If plugin exists: refresh its detail renderer
5713
- * - If plugin doesn't exist but <tbw-grid-detail> is present: dynamically import and add the plugin
5714
- */
5715
- async configureMasterDetail(grid) {
5716
- if (!this.adapter)
5717
- return;
5718
- // Check for existing plugin by name to avoid importing the class
5719
- const existingPlugin = grid.gridConfig?.plugins?.find((p) => p.name === 'masterDetail');
5720
- if (existingPlugin && typeof existingPlugin.refreshDetailRenderer === 'function') {
5721
- // Plugin exists - just refresh the renderer to pick up Angular templates
5722
- existingPlugin.refreshDetailRenderer();
5723
- return;
5724
- }
5725
- // Check if <tbw-grid-detail> is present in light DOM
5726
- const detailElement = grid.querySelector('tbw-grid-detail');
5727
- if (!detailElement)
5728
- return;
5729
- // Create detail renderer from Angular template
5730
- const detailRenderer = this.adapter.createDetailRenderer(grid);
5731
- if (!detailRenderer)
5732
- return;
5733
- // Parse configuration from attributes
5734
- const animationAttr = detailElement.getAttribute('animation');
5735
- let animation = 'slide';
5736
- if (animationAttr === 'false') {
5737
- animation = false;
5738
- }
5739
- else if (animationAttr === 'fade') {
5740
- animation = 'fade';
5741
- }
5742
- const showExpandColumn = detailElement.getAttribute('showExpandColumn') !== 'false';
5743
- // Dynamically import the plugin to avoid bundling it when not used
5744
- const { MasterDetailPlugin } = await import('@toolbox-web/grid/plugins/master-detail');
5745
- // Create and add the plugin
5746
- const plugin = new MasterDetailPlugin({
5747
- detailRenderer: detailRenderer,
5748
- showExpandColumn,
5749
- animation,
5750
- });
5751
- const currentConfig = grid.gridConfig || {};
5752
- const existingPlugins = currentConfig.plugins || [];
5753
- grid.gridConfig = {
5754
- ...currentConfig,
5755
- plugins: [...existingPlugins, plugin],
5756
- };
5757
- }
5758
- /**
5759
- * Configures the ResponsivePlugin with Angular template-based card renderer.
5760
- * - If plugin exists: updates its cardRenderer configuration
5761
- * - If plugin doesn't exist but <tbw-grid-responsive-card> is present: logs a warning
5762
- */
5763
- configureResponsiveCard(grid) {
5764
- if (!this.adapter)
5765
- return;
5766
- // Check if <tbw-grid-responsive-card> is present in light DOM
5767
- const cardElement = grid.querySelector('tbw-grid-responsive-card');
5768
- if (!cardElement)
5769
- return;
5770
- // Create card renderer from Angular template
5771
- const cardRenderer = this.adapter.createResponsiveCardRenderer(grid);
5772
- if (!cardRenderer)
5773
- return;
5774
- // Find existing plugin by name to avoid importing the class
5775
- const existingPlugin = grid.gridConfig?.plugins?.find((p) => p.name === 'responsive');
5776
- if (existingPlugin && typeof existingPlugin.setCardRenderer === 'function') {
5777
- // Plugin exists - update its cardRenderer
5778
- existingPlugin.setCardRenderer(cardRenderer);
5779
- return;
5780
- }
5781
- // Plugin doesn't exist - log a warning
5782
- console.warn('[tbw-grid-angular] <tbw-grid-responsive-card> found but ResponsivePlugin is not configured.\n' +
5783
- 'Add ResponsivePlugin to your gridConfig.plugins array:\n\n' +
5784
- ' import { ResponsivePlugin } from "@toolbox-web/grid/plugins/responsive";\n' +
5785
- ' gridConfig = {\n' +
5786
- ' plugins: [new ResponsivePlugin({ breakpoint: 600 })]\n' +
5787
- ' };');
5788
- }
5789
6287
  ngOnDestroy() {
5790
6288
  const grid = this.elementRef.nativeElement;
5791
6289
  // Cleanup all event listeners
@@ -5806,12 +6304,61 @@ class Grid {
5806
6304
  }
5807
6305
  }
5808
6306
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: Grid, deps: [], target: i0.ɵɵFactoryTarget.Directive });
5809
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.5", type: Grid, isStandalone: true, selector: "tbw-grid", inputs: { customStyles: { classPropertyName: "customStyles", publicName: "customStyles", isSignal: true, isRequired: false, transformFunction: null }, sortable: { classPropertyName: "sortable", publicName: "sortable", isSignal: true, isRequired: false, transformFunction: null }, filterable: { classPropertyName: "filterable", publicName: "filterable", isSignal: true, isRequired: false, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, fitMode: { classPropertyName: "fitMode", publicName: "fitMode", isSignal: true, isRequired: false, transformFunction: null }, gridConfig: { classPropertyName: "gridConfig", publicName: "gridConfig", isSignal: true, isRequired: false, transformFunction: null }, selection: { classPropertyName: "selection", publicName: "selection", isSignal: true, isRequired: false, transformFunction: null }, editing: { classPropertyName: "editing", publicName: "editing", isSignal: true, isRequired: false, transformFunction: null }, clipboard: { classPropertyName: "clipboard", publicName: "clipboard", isSignal: true, isRequired: false, transformFunction: null }, contextMenu: { classPropertyName: "contextMenu", publicName: "contextMenu", isSignal: true, isRequired: false, transformFunction: null }, multiSort: { classPropertyName: "multiSort", publicName: "multiSort", isSignal: true, isRequired: false, transformFunction: null }, filtering: { classPropertyName: "filtering", publicName: "filtering", isSignal: true, isRequired: false, transformFunction: null }, reorderColumns: { classPropertyName: "reorderColumns", publicName: "reorderColumns", isSignal: true, isRequired: false, transformFunction: null }, visibility: { classPropertyName: "visibility", publicName: "visibility", isSignal: true, isRequired: false, transformFunction: null }, pinnedColumns: { classPropertyName: "pinnedColumns", publicName: "pinnedColumns", isSignal: true, isRequired: false, transformFunction: null }, groupingColumns: { classPropertyName: "groupingColumns", publicName: "groupingColumns", isSignal: true, isRequired: false, transformFunction: null }, columnVirtualization: { classPropertyName: "columnVirtualization", publicName: "columnVirtualization", isSignal: true, isRequired: false, transformFunction: null }, reorderRows: { classPropertyName: "reorderRows", publicName: "reorderRows", isSignal: true, isRequired: false, transformFunction: null }, rowDragDrop: { classPropertyName: "rowDragDrop", publicName: "rowDragDrop", isSignal: true, isRequired: false, transformFunction: null }, groupingRows: { classPropertyName: "groupingRows", publicName: "groupingRows", isSignal: true, isRequired: false, transformFunction: null }, pinnedRows: { classPropertyName: "pinnedRows", publicName: "pinnedRows", isSignal: true, isRequired: false, transformFunction: null }, tree: { classPropertyName: "tree", publicName: "tree", isSignal: true, isRequired: false, transformFunction: null }, masterDetail: { classPropertyName: "masterDetail", publicName: "masterDetail", isSignal: true, isRequired: false, transformFunction: null }, responsive: { classPropertyName: "responsive", publicName: "responsive", isSignal: true, isRequired: false, transformFunction: null }, undoRedo: { classPropertyName: "undoRedo", publicName: "undoRedo", isSignal: true, isRequired: false, transformFunction: null }, exportFeature: { classPropertyName: "exportFeature", publicName: "export", isSignal: true, isRequired: false, transformFunction: null }, print: { classPropertyName: "print", publicName: "print", isSignal: true, isRequired: false, transformFunction: null }, pivot: { classPropertyName: "pivot", publicName: "pivot", isSignal: true, isRequired: false, transformFunction: null }, serverSide: { classPropertyName: "serverSide", publicName: "serverSide", isSignal: true, isRequired: false, transformFunction: null }, tooltip: { classPropertyName: "tooltip", publicName: "tooltip", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cellClick: "cellClick", rowClick: "rowClick", cellActivate: "cellActivate", cellChange: "cellChange", cellCommit: "cellCommit", rowCommit: "rowCommit", changedRowsReset: "changedRowsReset", sortChange: "sortChange", filterChange: "filterChange", columnResize: "columnResize", columnMove: "columnMove", columnVisibility: "columnVisibility", columnStateChange: "columnStateChange", selectionChange: "selectionChange", rowMove: "rowMove", rowDragStart: "rowDragStart", rowDragEnd: "rowDragEnd", rowDrop: "rowDrop", rowTransfer: "rowTransfer", groupToggle: "groupToggle", treeExpand: "treeExpand", detailExpand: "detailExpand", responsiveChange: "responsiveChange", copy: "copy", paste: "paste", undoRedoAction: "undoRedoAction", exportComplete: "exportComplete", printStart: "printStart", printComplete: "printComplete", tbwScroll: "tbwScroll" }, ngImport: i0 });
6307
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.5", type: Grid, isStandalone: true, selector: "tbw-grid", inputs: { customStyles: { classPropertyName: "customStyles", publicName: "customStyles", isSignal: true, isRequired: false, transformFunction: null }, sortable: { classPropertyName: "sortable", publicName: "sortable", isSignal: true, isRequired: false, transformFunction: null }, filterable: { classPropertyName: "filterable", publicName: "filterable", isSignal: true, isRequired: false, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, columnDefaults: { classPropertyName: "columnDefaults", publicName: "columnDefaults", isSignal: true, isRequired: false, transformFunction: null }, fitMode: { classPropertyName: "fitMode", publicName: "fitMode", isSignal: true, isRequired: false, transformFunction: null }, gridConfig: { classPropertyName: "gridConfig", publicName: "gridConfig", isSignal: true, isRequired: false, transformFunction: null }, selection: { classPropertyName: "selection", publicName: "selection", isSignal: true, isRequired: false, transformFunction: null }, editing: { classPropertyName: "editing", publicName: "editing", isSignal: true, isRequired: false, transformFunction: null }, clipboard: { classPropertyName: "clipboard", publicName: "clipboard", isSignal: true, isRequired: false, transformFunction: null }, contextMenu: { classPropertyName: "contextMenu", publicName: "contextMenu", isSignal: true, isRequired: false, transformFunction: null }, multiSort: { classPropertyName: "multiSort", publicName: "multiSort", isSignal: true, isRequired: false, transformFunction: null }, filtering: { classPropertyName: "filtering", publicName: "filtering", isSignal: true, isRequired: false, transformFunction: null }, reorderColumns: { classPropertyName: "reorderColumns", publicName: "reorderColumns", isSignal: true, isRequired: false, transformFunction: null }, visibility: { classPropertyName: "visibility", publicName: "visibility", isSignal: true, isRequired: false, transformFunction: null }, pinnedColumns: { classPropertyName: "pinnedColumns", publicName: "pinnedColumns", isSignal: true, isRequired: false, transformFunction: null }, groupingColumns: { classPropertyName: "groupingColumns", publicName: "groupingColumns", isSignal: true, isRequired: false, transformFunction: null }, columnVirtualization: { classPropertyName: "columnVirtualization", publicName: "columnVirtualization", isSignal: true, isRequired: false, transformFunction: null }, reorderRows: { classPropertyName: "reorderRows", publicName: "reorderRows", isSignal: true, isRequired: false, transformFunction: null }, rowDragDrop: { classPropertyName: "rowDragDrop", publicName: "rowDragDrop", isSignal: true, isRequired: false, transformFunction: null }, groupingRows: { classPropertyName: "groupingRows", publicName: "groupingRows", isSignal: true, isRequired: false, transformFunction: null }, pinnedRows: { classPropertyName: "pinnedRows", publicName: "pinnedRows", isSignal: true, isRequired: false, transformFunction: null }, tree: { classPropertyName: "tree", publicName: "tree", isSignal: true, isRequired: false, transformFunction: null }, masterDetail: { classPropertyName: "masterDetail", publicName: "masterDetail", isSignal: true, isRequired: false, transformFunction: null }, responsive: { classPropertyName: "responsive", publicName: "responsive", isSignal: true, isRequired: false, transformFunction: null }, undoRedo: { classPropertyName: "undoRedo", publicName: "undoRedo", isSignal: true, isRequired: false, transformFunction: null }, exportFeature: { classPropertyName: "exportFeature", publicName: "export", isSignal: true, isRequired: false, transformFunction: null }, print: { classPropertyName: "print", publicName: "print", isSignal: true, isRequired: false, transformFunction: null }, pivot: { classPropertyName: "pivot", publicName: "pivot", isSignal: true, isRequired: false, transformFunction: null }, serverSide: { classPropertyName: "serverSide", publicName: "serverSide", isSignal: true, isRequired: false, transformFunction: null }, tooltip: { classPropertyName: "tooltip", publicName: "tooltip", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cellClick: "cellClick", rowClick: "rowClick", cellActivate: "cellActivate", cellChange: "cellChange", cellCommit: "cellCommit", cellCancel: "cellCancel", editOpen: "editOpen", beforeEditClose: "beforeEditClose", editClose: "editClose", dirtyChange: "dirtyChange", dataChange: "dataChange", rowCommit: "rowCommit", changedRowsReset: "changedRowsReset", sortChange: "sortChange", filterChange: "filterChange", columnResize: "columnResize", columnResizeReset: "columnResizeReset", columnMove: "columnMove", columnVisibility: "columnVisibility", columnStateChange: "columnStateChange", selectionChange: "selectionChange", rowMove: "rowMove", rowDragStart: "rowDragStart", rowDragEnd: "rowDragEnd", rowDrop: "rowDrop", rowTransfer: "rowTransfer", groupToggle: "groupToggle", groupExpand: "groupExpand", groupCollapse: "groupCollapse", treeExpand: "treeExpand", detailExpand: "detailExpand", responsiveChange: "responsiveChange", contextMenuOpen: "contextMenuOpen", copy: "copy", paste: "paste", undo: "undo", redo: "redo", exportComplete: "exportComplete", printStart: "printStart", printComplete: "printComplete", tbwScroll: "tbwScroll" }, ngImport: i0 });
5810
6308
  }
5811
6309
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: Grid, decorators: [{
5812
6310
  type: Directive,
5813
6311
  args: [{ selector: 'tbw-grid' }]
5814
- }], ctorParameters: () => [], propDecorators: { customStyles: [{ type: i0.Input, args: [{ isSignal: true, alias: "customStyles", required: false }] }], sortable: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortable", required: false }] }], filterable: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterable", required: false }] }], selectable: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectable", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], fitMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "fitMode", required: false }] }], gridConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "gridConfig", required: false }] }], selection: [{ type: i0.Input, args: [{ isSignal: true, alias: "selection", required: false }] }], editing: [{ type: i0.Input, args: [{ isSignal: true, alias: "editing", required: false }] }], clipboard: [{ type: i0.Input, args: [{ isSignal: true, alias: "clipboard", required: false }] }], contextMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "contextMenu", required: false }] }], multiSort: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiSort", required: false }] }], filtering: [{ type: i0.Input, args: [{ isSignal: true, alias: "filtering", required: false }] }], reorderColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "reorderColumns", required: false }] }], visibility: [{ type: i0.Input, args: [{ isSignal: true, alias: "visibility", required: false }] }], pinnedColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "pinnedColumns", required: false }] }], groupingColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupingColumns", required: false }] }], columnVirtualization: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnVirtualization", required: false }] }], reorderRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "reorderRows", required: false }] }], rowDragDrop: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowDragDrop", required: false }] }], groupingRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupingRows", required: false }] }], pinnedRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "pinnedRows", required: false }] }], tree: [{ type: i0.Input, args: [{ isSignal: true, alias: "tree", required: false }] }], masterDetail: [{ type: i0.Input, args: [{ isSignal: true, alias: "masterDetail", required: false }] }], responsive: [{ type: i0.Input, args: [{ isSignal: true, alias: "responsive", required: false }] }], undoRedo: [{ type: i0.Input, args: [{ isSignal: true, alias: "undoRedo", required: false }] }], exportFeature: [{ type: i0.Input, args: [{ isSignal: true, alias: "export", required: false }] }], print: [{ type: i0.Input, args: [{ isSignal: true, alias: "print", required: false }] }], pivot: [{ type: i0.Input, args: [{ isSignal: true, alias: "pivot", required: false }] }], serverSide: [{ type: i0.Input, args: [{ isSignal: true, alias: "serverSide", required: false }] }], tooltip: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltip", required: false }] }], cellClick: [{ type: i0.Output, args: ["cellClick"] }], rowClick: [{ type: i0.Output, args: ["rowClick"] }], cellActivate: [{ type: i0.Output, args: ["cellActivate"] }], cellChange: [{ type: i0.Output, args: ["cellChange"] }], cellCommit: [{ type: i0.Output, args: ["cellCommit"] }], rowCommit: [{ type: i0.Output, args: ["rowCommit"] }], changedRowsReset: [{ type: i0.Output, args: ["changedRowsReset"] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }], filterChange: [{ type: i0.Output, args: ["filterChange"] }], columnResize: [{ type: i0.Output, args: ["columnResize"] }], columnMove: [{ type: i0.Output, args: ["columnMove"] }], columnVisibility: [{ type: i0.Output, args: ["columnVisibility"] }], columnStateChange: [{ type: i0.Output, args: ["columnStateChange"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], rowMove: [{ type: i0.Output, args: ["rowMove"] }], rowDragStart: [{ type: i0.Output, args: ["rowDragStart"] }], rowDragEnd: [{ type: i0.Output, args: ["rowDragEnd"] }], rowDrop: [{ type: i0.Output, args: ["rowDrop"] }], rowTransfer: [{ type: i0.Output, args: ["rowTransfer"] }], groupToggle: [{ type: i0.Output, args: ["groupToggle"] }], treeExpand: [{ type: i0.Output, args: ["treeExpand"] }], detailExpand: [{ type: i0.Output, args: ["detailExpand"] }], responsiveChange: [{ type: i0.Output, args: ["responsiveChange"] }], copy: [{ type: i0.Output, args: ["copy"] }], paste: [{ type: i0.Output, args: ["paste"] }], undoRedoAction: [{ type: i0.Output, args: ["undoRedoAction"] }], exportComplete: [{ type: i0.Output, args: ["exportComplete"] }], printStart: [{ type: i0.Output, args: ["printStart"] }], printComplete: [{ type: i0.Output, args: ["printComplete"] }], tbwScroll: [{ type: i0.Output, args: ["tbwScroll"] }] } });
6312
+ }], ctorParameters: () => [], propDecorators: { customStyles: [{ type: i0.Input, args: [{ isSignal: true, alias: "customStyles", required: false }] }], sortable: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortable", required: false }] }], filterable: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterable", required: false }] }], selectable: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectable", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], columnDefaults: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnDefaults", required: false }] }], fitMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "fitMode", required: false }] }], gridConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "gridConfig", required: false }] }], selection: [{ type: i0.Input, args: [{ isSignal: true, alias: "selection", required: false }] }], editing: [{ type: i0.Input, args: [{ isSignal: true, alias: "editing", required: false }] }], clipboard: [{ type: i0.Input, args: [{ isSignal: true, alias: "clipboard", required: false }] }], contextMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "contextMenu", required: false }] }], multiSort: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiSort", required: false }] }], filtering: [{ type: i0.Input, args: [{ isSignal: true, alias: "filtering", required: false }] }], reorderColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "reorderColumns", required: false }] }], visibility: [{ type: i0.Input, args: [{ isSignal: true, alias: "visibility", required: false }] }], pinnedColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "pinnedColumns", required: false }] }], groupingColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupingColumns", required: false }] }], columnVirtualization: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnVirtualization", required: false }] }], reorderRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "reorderRows", required: false }] }], rowDragDrop: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowDragDrop", required: false }] }], groupingRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupingRows", required: false }] }], pinnedRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "pinnedRows", required: false }] }], tree: [{ type: i0.Input, args: [{ isSignal: true, alias: "tree", required: false }] }], masterDetail: [{ type: i0.Input, args: [{ isSignal: true, alias: "masterDetail", required: false }] }], responsive: [{ type: i0.Input, args: [{ isSignal: true, alias: "responsive", required: false }] }], undoRedo: [{ type: i0.Input, args: [{ isSignal: true, alias: "undoRedo", required: false }] }], exportFeature: [{ type: i0.Input, args: [{ isSignal: true, alias: "export", required: false }] }], print: [{ type: i0.Input, args: [{ isSignal: true, alias: "print", required: false }] }], pivot: [{ type: i0.Input, args: [{ isSignal: true, alias: "pivot", required: false }] }], serverSide: [{ type: i0.Input, args: [{ isSignal: true, alias: "serverSide", required: false }] }], tooltip: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltip", required: false }] }], cellClick: [{ type: i0.Output, args: ["cellClick"] }], rowClick: [{ type: i0.Output, args: ["rowClick"] }], cellActivate: [{ type: i0.Output, args: ["cellActivate"] }], cellChange: [{ type: i0.Output, args: ["cellChange"] }], cellCommit: [{ type: i0.Output, args: ["cellCommit"] }], cellCancel: [{ type: i0.Output, args: ["cellCancel"] }], editOpen: [{ type: i0.Output, args: ["editOpen"] }], beforeEditClose: [{ type: i0.Output, args: ["beforeEditClose"] }], editClose: [{ type: i0.Output, args: ["editClose"] }], dirtyChange: [{ type: i0.Output, args: ["dirtyChange"] }], dataChange: [{ type: i0.Output, args: ["dataChange"] }], rowCommit: [{ type: i0.Output, args: ["rowCommit"] }], changedRowsReset: [{ type: i0.Output, args: ["changedRowsReset"] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }], filterChange: [{ type: i0.Output, args: ["filterChange"] }], columnResize: [{ type: i0.Output, args: ["columnResize"] }], columnResizeReset: [{ type: i0.Output, args: ["columnResizeReset"] }], columnMove: [{ type: i0.Output, args: ["columnMove"] }], columnVisibility: [{ type: i0.Output, args: ["columnVisibility"] }], columnStateChange: [{ type: i0.Output, args: ["columnStateChange"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], rowMove: [{ type: i0.Output, args: ["rowMove"] }], rowDragStart: [{ type: i0.Output, args: ["rowDragStart"] }], rowDragEnd: [{ type: i0.Output, args: ["rowDragEnd"] }], rowDrop: [{ type: i0.Output, args: ["rowDrop"] }], rowTransfer: [{ type: i0.Output, args: ["rowTransfer"] }], groupToggle: [{ type: i0.Output, args: ["groupToggle"] }], groupExpand: [{ type: i0.Output, args: ["groupExpand"] }], groupCollapse: [{ type: i0.Output, args: ["groupCollapse"] }], treeExpand: [{ type: i0.Output, args: ["treeExpand"] }], detailExpand: [{ type: i0.Output, args: ["detailExpand"] }], responsiveChange: [{ type: i0.Output, args: ["responsiveChange"] }], contextMenuOpen: [{ type: i0.Output, args: ["contextMenuOpen"] }], copy: [{ type: i0.Output, args: ["copy"] }], paste: [{ type: i0.Output, args: ["paste"] }], undo: [{ type: i0.Output, args: ["undo"] }], redo: [{ type: i0.Output, args: ["redo"] }], exportComplete: [{ type: i0.Output, args: ["exportComplete"] }], printStart: [{ type: i0.Output, args: ["printStart"] }], printComplete: [{ type: i0.Output, args: ["printComplete"] }], tbwScroll: [{ type: i0.Output, args: ["tbwScroll"] }] } });
6313
+
6314
+ /**
6315
+ * Combined provider helper for grid type defaults and icons.
6316
+ *
6317
+ * Convenience function that combines `provideGridTypeDefaults` and
6318
+ * `provideGridIcons` into a single call for application bootstrap.
6319
+ *
6320
+ * @example
6321
+ * ```typescript
6322
+ * // app.config.ts
6323
+ * import { ApplicationConfig } from '@angular/core';
6324
+ * import { provideGrid } from '@toolbox-web/grid-angular';
6325
+ *
6326
+ * export const appConfig: ApplicationConfig = {
6327
+ * providers: [
6328
+ * provideGrid({
6329
+ * typeDefaults: {
6330
+ * country: { renderer: CountryCellComponent },
6331
+ * },
6332
+ * icons: {
6333
+ * sortAsc: '↑',
6334
+ * sortDesc: '↓',
6335
+ * },
6336
+ * }),
6337
+ * ],
6338
+ * };
6339
+ * ```
6340
+ */
6341
+ /**
6342
+ * Combined provider for grid type defaults and icons.
6343
+ *
6344
+ * Returns environment providers that can be added to your `ApplicationConfig`
6345
+ * `providers` array. Either field is optional — only the registries you
6346
+ * supply are wired up.
6347
+ *
6348
+ * Equivalent to calling `provideGridTypeDefaults(options.typeDefaults)` and
6349
+ * `provideGridIcons(options.icons)` separately.
6350
+ * @since 1.4.0
6351
+ */
6352
+ function provideGrid(options = {}) {
6353
+ const providers = [];
6354
+ if (options.typeDefaults) {
6355
+ providers.push(provideGridTypeDefaults(options.typeDefaults));
6356
+ }
6357
+ if (options.icons) {
6358
+ providers.push(provideGridIcons(options.icons));
6359
+ }
6360
+ return makeEnvironmentProviders(providers);
6361
+ }
5815
6362
 
5816
6363
  /**
5817
6364
  * @packageDocumentation
@@ -5825,5 +6372,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
5825
6372
  * Generated bundle index. Do not edit.
5826
6373
  */
5827
6374
 
5828
- export { BaseFilterPanel, BaseGridEditor, BaseGridEditorCVA, BaseOverlayEditor, GRID_ICONS, GRID_TYPE_DEFAULTS, Grid, GridAdapter, GridColumnEditor, GridColumnView, GridDetailView, GridFormArray, GridIconRegistry, GridLazyForm, GridResponsiveCard, GridToolPanel, GridTypeRegistry, TbwEditor, TbwGridColumn, TbwGridHeader, TbwGridToolButtons, TbwRenderer, getFormArrayContext, getLazyFormContext, injectGrid, isComponentClass, provideGridIcons, provideGridTypeDefaults };
6375
+ export { BaseFilterPanel, BaseGridEditor, BaseGridEditorCVA, BaseOverlayEditor, GRID_ICONS, GRID_TYPE_DEFAULTS, Grid, GridAdapter, GridColumnEditor, GridColumnView, GridDetailView, GridFormArray, GridIconRegistry, GridLazyForm, GridResponsiveCard, GridToolPanel, GridTypeRegistry, TbwEditor, TbwGridColumn, TbwGridHeader, TbwGridToolButtons, TbwRenderer, applyColumnDefaults, claimEvent, getDetailTemplate, getFeatureClaim, getFeatureConfigPreprocessor, getFormArrayContext, getLazyFormContext, getResponsiveCardTemplate, hasColumnShorthands, injectGrid, isComponentClass, isEventClaimed, makeFlushFocusedInput, normalizeColumns, parseColumnShorthand, provideGrid, provideGridIcons, provideGridTypeDefaults, registerDetailRendererBridge, registerEditorMountHook, registerFeatureClaim, registerFeatureConfigPreprocessor, registerFilterPanelTypeDefaultBridge, registerResponsiveCardRendererBridge, registerTemplateBridge, runTemplateBridges, unclaimEvent, unregisterFeatureClaim };
5829
6376
  //# sourceMappingURL=toolbox-web-grid-angular.mjs.map