@toolbox-web/grid-angular 1.3.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +109 -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 +105 -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 +1225 -728
  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 +858 -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';
@@ -85,6 +85,13 @@ function getEditorTemplate(element) {
85
85
  * ```
86
86
  *
87
87
  * @category Directive
88
+ *
89
+ * MOVE-IN-V2: this directive (and its companion `GridEditorContext` type and
90
+ * `getEditorTemplate` helper) will physically move into
91
+ * `@toolbox-web/grid-angular/features/editing` in v2.0.0; the deprecated
92
+ * re-exports from the main `@toolbox-web/grid-angular` entry will be removed
93
+ * at the same time. Consumers should already be importing from the feature
94
+ * entry.
88
95
  */
89
96
  class GridColumnEditor {
90
97
  elementRef = inject((ElementRef));
@@ -189,119 +196,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
189
196
  args: [{ selector: 'tbw-grid-column-view' }]
190
197
  }], propDecorators: { template: [{ type: i0.ContentChild, args: [i0.forwardRef(() => TemplateRef), { isSignal: true }] }] } });
191
198
 
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
199
  // Symbol for storing form context on the grid element
306
200
  const FORM_ARRAY_CONTEXT$1 = Symbol('formArrayContext');
307
201
  /**
@@ -370,6 +264,13 @@ function getFormArrayContext(gridElement) {
370
264
  * ```
371
265
  *
372
266
  * @category Directive
267
+ *
268
+ * MOVE-IN-V2: this directive (and its `FormArrayContext` type and
269
+ * `getFormArrayContext` helper) will physically move into
270
+ * `@toolbox-web/grid-angular/features/editing` in v2.0.0; the deprecated
271
+ * re-exports from the main `@toolbox-web/grid-angular` entry will be removed
272
+ * at the same time. Consumers should already be importing from the feature
273
+ * entry.
373
274
  */
374
275
  class GridFormArray {
375
276
  destroyRef = inject(DestroyRef);
@@ -785,88 +686,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
785
686
  }]
786
687
  }], propDecorators: { formArray: [{ type: i0.Input, args: [{ isSignal: true, alias: "formArray", required: true }] }], syncValidation: [{ type: i0.Input, args: [{ isSignal: true, alias: "syncValidation", required: false }] }] } });
787
688
 
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
689
  // Global registry mapping DOM elements to their templates
871
690
  const toolPanelTemplateRegistry = new Map();
872
691
  /**
@@ -1173,6 +992,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
1173
992
  * ```
1174
993
  *
1175
994
  * @category Directive
995
+ *
996
+ * MOVE-IN-V2: this directive (and its `StructuralEditorContext` type) will
997
+ * physically move into `@toolbox-web/grid-angular/features/editing` in v2.0.0;
998
+ * the deprecated re-export from the main `@toolbox-web/grid-angular` entry
999
+ * will be removed at the same time. Consumers should already be importing
1000
+ * from the feature entry. (`TbwRenderer` stays in the main entry — it is
1001
+ * editor-agnostic.)
1176
1002
  */
1177
1003
  class TbwEditor {
1178
1004
  template = inject((TemplateRef));
@@ -1214,6 +1040,70 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
1214
1040
  args: [{ selector: '[tbwEditor]' }]
1215
1041
  }], ctorParameters: () => [] });
1216
1042
 
1043
+ /**
1044
+ * Editor mount hook registry — append-only hooks called when an Angular-managed
1045
+ * editor host is mounted into the DOM with a known owner grid.
1046
+ *
1047
+ * This is the augmentation point that lets feature secondary entries
1048
+ * (e.g. `@toolbox-web/grid-angular/features/editing`) install per-editor
1049
+ * lifecycle behaviour (such as the `before-edit-close` blur bridge)
1050
+ * without coupling the central adapter file to the editing feature.
1051
+ * Mirrors React's and Vue's `editor-mount-hooks` and how core grid plugins
1052
+ * augment the grid via `registerPlugin()`.
1053
+ *
1054
+ * @packageDocumentation
1055
+ * @internal
1056
+ */
1057
+ const editorMountHooks = [];
1058
+ /**
1059
+ * Install an editor-mount hook. Called by feature secondary entries
1060
+ * (e.g. `features/editing`) on import.
1061
+ *
1062
+ * @internal Plugin API
1063
+ */
1064
+ function registerEditorMountHook(hook) {
1065
+ editorMountHooks.push(hook);
1066
+ }
1067
+ /**
1068
+ * Run all registered editor-mount hooks for a freshly mounted editor.
1069
+ * Returns a combined teardown that invokes each hook's teardown in
1070
+ * registration order.
1071
+ *
1072
+ * @internal Adapter use only.
1073
+ */
1074
+ function notifyEditorMounted(container, gridEl) {
1075
+ const teardowns = [];
1076
+ for (const hook of editorMountHooks) {
1077
+ const teardown = hook({ container, gridEl });
1078
+ if (teardown)
1079
+ teardowns.push(teardown);
1080
+ }
1081
+ return () => {
1082
+ for (const teardown of teardowns)
1083
+ teardown();
1084
+ };
1085
+ }
1086
+ /**
1087
+ * Returns a function that, when invoked, blurs the focused input/textarea/select
1088
+ * inside `host` (if any). Used by the `before-edit-close` bridge installed by
1089
+ * `@toolbox-web/grid-angular/features/editing` so editors that commit on
1090
+ * `(blur)` flush their pending value before the cell DOM is torn down by Tab /
1091
+ * programmatic row exit.
1092
+ * @internal
1093
+ */
1094
+ function makeFlushFocusedInput(host) {
1095
+ return () => {
1096
+ const focused = host.ownerDocument.activeElement;
1097
+ if (focused &&
1098
+ host.contains(focused) &&
1099
+ (focused instanceof HTMLInputElement ||
1100
+ focused instanceof HTMLTextAreaElement ||
1101
+ focused instanceof HTMLSelectElement)) {
1102
+ focused.blur();
1103
+ }
1104
+ };
1105
+ }
1106
+
1217
1107
  /**
1218
1108
  * Editor Wiring Helpers
1219
1109
  *
@@ -1435,6 +1325,149 @@ function provideGridTypeDefaults(defaults) {
1435
1325
  return makeEnvironmentProviders([{ provide: GRID_TYPE_DEFAULTS, useValue: defaults }]);
1436
1326
  }
1437
1327
 
1328
+ /**
1329
+ * Append-only bridge registries used by `@toolbox-web/grid-angular`.
1330
+ *
1331
+ * These let feature secondary entries plug into the central `GridAdapter`
1332
+ * without the adapter needing to know about them. Mirrors React's and Vue's
1333
+ * `register*Bridge` modules and how core grid plugins augment the grid via
1334
+ * `registerPlugin()`.
1335
+ *
1336
+ * This module is deliberately framework-free: it holds plain module-level
1337
+ * `let` state and never imports from `@angular/core` or from
1338
+ * `@toolbox-web/grid/plugins/...`. Feature subpaths import the setters via
1339
+ * the `@toolbox-web/grid-angular` package barrel (relative imports outside
1340
+ * a secondary entry's `rootDir` are forbidden by ng-packagr).
1341
+ *
1342
+ * @internal
1343
+ */
1344
+ let detailRendererBridge = null;
1345
+ let responsiveCardRendererBridge = null;
1346
+ let filterPanelTypeDefaultBridge = null;
1347
+ /**
1348
+ * Install the master-detail row-renderer bridge. Called once on import by
1349
+ * `@toolbox-web/grid-angular/features/master-detail`.
1350
+ * @internal Plugin API
1351
+ */
1352
+ function registerDetailRendererBridge(bridge) {
1353
+ detailRendererBridge = bridge;
1354
+ }
1355
+ /**
1356
+ * Install the responsive-card row-renderer bridge. Called once on import by
1357
+ * `@toolbox-web/grid-angular/features/responsive`.
1358
+ * @internal Plugin API
1359
+ */
1360
+ function registerResponsiveCardRendererBridge(bridge) {
1361
+ responsiveCardRendererBridge = bridge;
1362
+ }
1363
+ /**
1364
+ * Install the type-default `filterPanelRenderer` wrapper. Called once on import
1365
+ * by `@toolbox-web/grid-angular/features/filtering`. Without this bridge,
1366
+ * type-default and grid-config-level component-class filterPanelRenderers are
1367
+ * silently dropped — same precondition as the FilteringPlugin (TBW031).
1368
+ * @internal Plugin API
1369
+ */
1370
+ function registerFilterPanelTypeDefaultBridge(bridge) {
1371
+ filterPanelTypeDefaultBridge = bridge;
1372
+ }
1373
+ /** @internal Adapter use only. */
1374
+ function getDetailRendererBridge() {
1375
+ return detailRendererBridge;
1376
+ }
1377
+ /** @internal Adapter use only. */
1378
+ function getResponsiveCardRendererBridge() {
1379
+ return responsiveCardRendererBridge;
1380
+ }
1381
+ /** @internal Adapter use only. */
1382
+ function getFilterPanelTypeDefaultBridge() {
1383
+ return filterPanelTypeDefaultBridge;
1384
+ }
1385
+
1386
+ /**
1387
+ * Internal registries used by `@toolbox-web/grid-angular`.
1388
+ *
1389
+ * Two extension points are exposed so individual feature secondary entries
1390
+ * (e.g. `@toolbox-web/grid-angular/features/master-detail`) can plug into
1391
+ * the core `Grid` directive without the directive needing to know about them.
1392
+ *
1393
+ * - {@link registerTemplateBridge} — runs in `ngAfterContentInit` once Angular
1394
+ * templates inside the grid have been registered. Used to discover light-DOM
1395
+ * slot elements (`<tbw-grid-detail>`, `<tbw-grid-responsive-card>`, …) and
1396
+ * wire them to the corresponding plugin's renderer setter.
1397
+ * - {@link registerFeatureConfigPreprocessor} — runs inside the directive's
1398
+ * feature-plugin builder before `createPluginFromFeature` is called. Lets a
1399
+ * feature transform its config object (e.g. to convert Angular component
1400
+ * classes embedded in `customPanels` to renderer functions).
1401
+ *
1402
+ * Both registries are append-only and module-scoped: they are populated by
1403
+ * side-effect imports of feature secondary entries and consumed by the core
1404
+ * `Grid` directive. Order is insertion order.
1405
+ *
1406
+ * @internal — public for cross-entry-point use; not part of the supported API.
1407
+ */
1408
+ const templateBridges = [];
1409
+ /**
1410
+ * Register a template bridge. Called by feature secondary entries at module
1411
+ * load (e.g. `import '@toolbox-web/grid-angular/features/master-detail'`).
1412
+ *
1413
+ * Bridges are append-only and never deduplicated; calling registration twice
1414
+ * for the same feature module is safe because module imports are deduplicated
1415
+ * by the JS loader.
1416
+ *
1417
+ * @internal
1418
+ */
1419
+ function registerTemplateBridge(bridge) {
1420
+ templateBridges.push(bridge);
1421
+ }
1422
+ /**
1423
+ * Run all registered template bridges for a grid. The directive calls this
1424
+ * from `ngAfterContentInit` after `refreshColumns()`. Bridges run concurrently;
1425
+ * errors thrown by one bridge do not stop the others.
1426
+ *
1427
+ * @internal
1428
+ */
1429
+ function runTemplateBridges(ctx) {
1430
+ for (const bridge of templateBridges) {
1431
+ try {
1432
+ const result = bridge(ctx);
1433
+ if (result && typeof result.catch === 'function') {
1434
+ result.catch((err) => {
1435
+ // eslint-disable-next-line no-console
1436
+ console.error('[tbw-grid-angular] template bridge threw:', err);
1437
+ });
1438
+ }
1439
+ }
1440
+ catch (err) {
1441
+ // eslint-disable-next-line no-console
1442
+ console.error('[tbw-grid-angular] template bridge threw:', err);
1443
+ }
1444
+ }
1445
+ }
1446
+ const featureConfigPreprocessors = new Map();
1447
+ /**
1448
+ * Register a feature config preprocessor. Last registration wins (subsequent
1449
+ * imports of the same feature module re-register the same function, which is
1450
+ * a no-op).
1451
+ *
1452
+ * @internal
1453
+ */
1454
+ function registerFeatureConfigPreprocessor(name, fn) {
1455
+ featureConfigPreprocessors.set(name, fn);
1456
+ }
1457
+ /**
1458
+ * Look up the preprocessor for a feature, if any.
1459
+ *
1460
+ * @internal
1461
+ */
1462
+ function getFeatureConfigPreprocessor(name) {
1463
+ return featureConfigPreprocessors.get(name);
1464
+ }
1465
+
1466
+ // #region Feature bridge registries
1467
+ // (Storage lives in `./internal/feature-bridges` so feature subpaths can
1468
+ // install bridges without pulling Angular runtime into specs that mock
1469
+ // `@angular/core` — see filtering feature spec.)
1470
+ // #endregion
1438
1471
  /**
1439
1472
  * Helper to get view template from either structural directive or nested directive.
1440
1473
  */
@@ -1555,20 +1588,16 @@ class GridAdapter {
1555
1588
  /** Editor-specific component refs tracked separately for per-cell cleanup via releaseCell. */
1556
1589
  editorComponentRefs = [];
1557
1590
  /**
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.
1591
+ * Per-editor mount-hook teardown functions, keyed by editor host element.
1567
1592
  *
1568
- * Mirror of Vue's `editorBeforeCloseUnsubs` and React's
1569
- * `wrapReactEditor` queueMicrotask bridge.
1593
+ * Populated by {@link runEditorMountHooks} (which invokes
1594
+ * {@link notifyEditorMounted}) and torn down per-cell from
1595
+ * {@link releaseCell}, with full sweep on {@link destroy}. The actual
1596
+ * lifecycle behaviour is supplied by feature secondary entries (e.g.
1597
+ * `@toolbox-web/grid-angular/features/editing` installs the
1598
+ * `before-edit-close` blur bridge).
1570
1599
  */
1571
- editorBeforeCloseUnsubs = new Map();
1600
+ editorMountTeardowns = new Map();
1572
1601
  typeRegistry = null;
1573
1602
  constructor(injector, appRef, viewContainerRef) {
1574
1603
  this.injector = injector;
@@ -1656,9 +1685,13 @@ class GridAdapter {
1656
1685
  if (config.editor && isComponentClass(config.editor)) {
1657
1686
  processedConfig.editor = this.createComponentEditor(config.editor);
1658
1687
  }
1659
- // Convert filterPanelRenderer component class to function
1688
+ // Convert filterPanelRenderer component class to function via the
1689
+ // filtering feature bridge. Without `@toolbox-web/grid-angular/features/filtering`
1690
+ // imported, component-class filterPanelRenderers are dropped silently.
1660
1691
  if (config.filterPanelRenderer && isComponentClass(config.filterPanelRenderer)) {
1661
- processedConfig.filterPanelRenderer = this.createComponentFilterPanelRenderer(config.filterPanelRenderer);
1692
+ const wrapped = getFilterPanelTypeDefaultBridge()?.(config.filterPanelRenderer, this);
1693
+ if (wrapped)
1694
+ processedConfig.filterPanelRenderer = wrapped;
1662
1695
  }
1663
1696
  processed[type] = processedConfig;
1664
1697
  }
@@ -1843,7 +1876,7 @@ class GridAdapter {
1843
1876
  const container = document.createElement('span');
1844
1877
  container.style.display = 'contents';
1845
1878
  syncRootNodes(viewRef, container);
1846
- this.attachBeforeEditCloseFlush(container);
1879
+ this.runEditorMountHooks(container);
1847
1880
  // Auto-wire: Listen for commit/cancel events on the rendered component.
1848
1881
  // This allows components to just emit (commit) and (cancel) without
1849
1882
  // requiring explicit template bindings like (commit)="onCommit($event)".
@@ -1869,101 +1902,40 @@ class GridAdapter {
1869
1902
  };
1870
1903
  }
1871
1904
  /**
1872
- * Creates a detail renderer function for MasterDetailPlugin.
1873
- * Renders Angular templates for expandable detail rows.
1905
+ * Creates a detail renderer function for MasterDetailPlugin. Delegates to
1906
+ * the bridge installed by `@toolbox-web/grid-angular/features/master-detail`.
1907
+ * Returns undefined if the feature is not imported or no `<tbw-grid-detail>`
1908
+ * template is registered for this grid.
1874
1909
  */
1875
1910
  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
- };
1911
+ return getDetailRendererBridge()?.(gridElement, this);
1896
1912
  }
1897
1913
  /**
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.
1914
+ * FrameworkAdapter hook called by MasterDetailPlugin during attach(). Delegates
1915
+ * to {@link createDetailRenderer} (bridge installed by master-detail feature).
1903
1916
  */
1904
1917
  parseDetailElement(detailElement) {
1905
- // Get the template from the registry for this detail element
1906
- const template = getDetailTemplate(detailElement.closest('tbw-grid'));
1907
- if (!template) {
1918
+ const gridElement = detailElement.closest('tbw-grid');
1919
+ if (!gridElement)
1908
1920
  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
- };
1921
+ return getDetailRendererBridge()?.(gridElement, this);
1924
1922
  }
1925
1923
  /**
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
1924
+ * Creates a responsive card renderer function for ResponsivePlugin. Delegates
1925
+ * to the bridge installed by `@toolbox-web/grid-angular/features/responsive`.
1931
1926
  */
1932
1927
  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
- };
1928
+ return getResponsiveCardRendererBridge()?.(gridElement, this);
1954
1929
  }
1955
1930
  /**
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.
1931
+ * FrameworkAdapter hook called by ResponsivePlugin during attach(). Delegates
1932
+ * to {@link createResponsiveCardRenderer} (bridge installed by responsive feature).
1961
1933
  */
1962
1934
  parseResponsiveCardElement(cardElement) {
1963
1935
  const gridElement = cardElement.closest('tbw-grid');
1964
1936
  if (!gridElement)
1965
1937
  return undefined;
1966
- return this.createResponsiveCardRenderer(gridElement);
1938
+ return getResponsiveCardRendererBridge()?.(gridElement, this);
1967
1939
  }
1968
1940
  /**
1969
1941
  * Creates a tool panel renderer from a light DOM element.
@@ -2047,8 +2019,11 @@ class GridAdapter {
2047
2019
  typeDefault.editor = this.createComponentEditor(config.editor);
2048
2020
  }
2049
2021
  // Create filterPanelRenderer function that instantiates the Angular component
2022
+ // via the filtering feature bridge. Drop silently if the feature is not imported.
2050
2023
  if (config.filterPanelRenderer && isComponentClass(config.filterPanelRenderer)) {
2051
- typeDefault.filterPanelRenderer = this.createComponentFilterPanelRenderer(config.filterPanelRenderer);
2024
+ const wrapped = getFilterPanelTypeDefaultBridge()?.(config.filterPanelRenderer, this);
2025
+ if (wrapped)
2026
+ typeDefault.filterPanelRenderer = wrapped;
2052
2027
  }
2053
2028
  else if (config.filterPanelRenderer) {
2054
2029
  typeDefault.filterPanelRenderer = config.filterPanelRenderer;
@@ -2056,47 +2031,56 @@ class GridAdapter {
2056
2031
  return typeDefault;
2057
2032
  }
2058
2033
  /**
2059
- * Creates and mounts an Angular component dynamically.
2060
- * Shared logic between renderer and editor component creation.
2034
+ * Generalized component-mount primitive. All `createComponent*Renderer` methods
2035
+ * are thin wrappers around this. Returns a function `(ctx) => { hostElement, componentRef }`
2036
+ * so callers that need the `componentRef` (editor wiring, value-change subscription)
2037
+ * still have it; callers that only need the host element use `.hostElement`.
2038
+ *
2039
+ * Public so feature secondary entries can compose their own component renderers
2040
+ * without re-implementing the mount/track plumbing.
2041
+ *
2042
+ * @param componentClass Angular component class to instantiate per call.
2043
+ * @param mapInputs Maps the renderer context to a `setInput()` bag.
2044
+ * @param pool Which `componentRefs[]` array tracks the instance for cleanup.
2045
+ * `'render'` (default) is the long-lived pool cleared at `dispose()`.
2046
+ * `'editor'` is the per-cell pool swept by `releaseCell()`.
2061
2047
  * @internal
2062
2048
  */
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 };
2049
+ mountComponentRenderer(componentClass, mapInputs, pool = 'render') {
2050
+ return (ctx) => {
2051
+ const hostElement = document.createElement('span');
2052
+ hostElement.style.display = 'contents';
2053
+ const componentRef = createComponent(componentClass, {
2054
+ environmentInjector: this.injector,
2055
+ hostElement,
2056
+ });
2057
+ this.setComponentInputs(componentRef, mapInputs(ctx));
2058
+ this.appRef.attachView(componentRef.hostView);
2059
+ (pool === 'editor' ? this.editorComponentRefs : this.componentRefs).push(componentRef);
2060
+ componentRef.changeDetectorRef.detectChanges();
2061
+ return { hostElement, componentRef };
2062
+ };
2086
2063
  }
2087
2064
  /**
2088
2065
  * Creates a renderer function from an Angular component class.
2066
+ * Wraps {@link mountComponentRenderer} with a per-cell `WeakMap` cache so
2067
+ * scroll-recycled cells reuse the existing component (just refresh inputs)
2068
+ * instead of mounting a fresh one.
2089
2069
  * @internal
2090
2070
  */
2091
2071
  createComponentRenderer(componentClass) {
2092
- // Cell cache for component-based renderers - maps cell element to its component ref
2093
2072
  const cellCache = new WeakMap();
2073
+ const mount = this.mountComponentRenderer(componentClass, (ctx) => ({
2074
+ value: ctx.value,
2075
+ row: ctx.row,
2076
+ column: ctx.column,
2077
+ }));
2094
2078
  return (ctx) => {
2095
2079
  const cellEl = ctx.cellEl;
2096
2080
  if (cellEl) {
2097
2081
  const cached = cellCache.get(cellEl);
2098
2082
  if (cached) {
2099
- // Reuse existing component - just update inputs
2083
+ // Reuse existing component - just update inputs.
2100
2084
  this.setComponentInputs(cached.componentRef, {
2101
2085
  value: ctx.value,
2102
2086
  row: ctx.row,
@@ -2106,31 +2090,25 @@ class GridAdapter {
2106
2090
  return cached.hostElement;
2107
2091
  }
2108
2092
  }
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) {
2093
+ const { hostElement, componentRef } = mount(ctx);
2094
+ if (cellEl)
2116
2095
  cellCache.set(cellEl, { componentRef, hostElement });
2117
- }
2118
2096
  return hostElement;
2119
2097
  };
2120
2098
  }
2121
2099
  /**
2122
2100
  * Creates an editor function from an Angular component class.
2101
+ * Wraps {@link mountComponentRenderer} (using the `'editor'` pool for per-cell
2102
+ * cleanup) plus editor-specific wiring: callback bridge, mount-hook fan-out
2103
+ * (see {@link runEditorMountHooks}), and external value-change subscription.
2123
2104
  * @internal
2124
2105
  */
2125
2106
  createComponentEditor(componentClass) {
2107
+ const mount = this.mountComponentRenderer(componentClass, (ctx) => ({ value: ctx.value, row: ctx.row, column: ctx.column }), 'editor');
2126
2108
  return (ctx) => {
2127
- const { hostElement, componentRef } = this.mountComponent(componentClass, {
2128
- value: ctx.value,
2129
- row: ctx.row,
2130
- column: ctx.column,
2131
- }, true);
2109
+ const { hostElement, componentRef } = mount(ctx);
2132
2110
  wireEditorCallbacks(hostElement, componentRef.instance, (value) => ctx.commit(value), () => ctx.cancel());
2133
- this.attachBeforeEditCloseFlush(hostElement);
2111
+ this.runEditorMountHooks(hostElement);
2134
2112
  // Auto-update editor when value changes externally (e.g., via updateRow cascade
2135
2113
  // or Escape-revert). Update the component input and run detectChanges() —
2136
2114
  // the component's own template handles rendering regardless of editor type.
@@ -2154,180 +2132,31 @@ class GridAdapter {
2154
2132
  }
2155
2133
  /**
2156
2134
  * Creates a header renderer function from an Angular component class.
2157
- * 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
- };
2135
+ * Mounts the component with full header context (column, value, sortState, etc.).
2136
+ * @internal
2137
+ */
2138
+ createComponentHeaderRenderer(componentClass) {
2139
+ const mount = this.mountComponentRenderer(componentClass, (ctx) => ({
2140
+ column: ctx.column,
2141
+ value: ctx.value,
2142
+ sortState: ctx.sortState,
2143
+ filterActive: ctx.filterActive,
2144
+ renderSortIcon: ctx.renderSortIcon,
2145
+ renderFilterButton: ctx.renderFilterButton,
2146
+ }));
2147
+ return (ctx) => mount(ctx).hostElement;
2148
+ }
2149
+ /**
2150
+ * Creates a header label renderer function from an Angular component class.
2151
+ * Mounts the component with label context (column, value).
2152
+ * @internal
2153
+ */
2154
+ createComponentHeaderLabelRenderer(componentClass) {
2155
+ const mount = this.mountComponentRenderer(componentClass, (ctx) => ({
2156
+ column: ctx.column,
2157
+ value: ctx.value,
2158
+ }));
2159
+ return (ctx) => mount(ctx).hostElement;
2331
2160
  }
2332
2161
  /**
2333
2162
  * Creates a loading renderer function from an Angular component class.
@@ -2336,78 +2165,59 @@ class GridAdapter {
2336
2165
  * @internal
2337
2166
  */
2338
2167
  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
- };
2168
+ const mount = this.mountComponentRenderer(componentClass, (ctx) => ({ size: ctx.size }));
2169
+ return (ctx) => mount(ctx).hostElement;
2354
2170
  }
2355
2171
  /**
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).
2172
+ * Create an embedded view from a `TemplateRef` and append-track it on the
2173
+ * adapter's view-ref pool so it is cleaned up on `destroy()` / `unmount()`.
2174
+ * Public so feature secondary entries can mount Angular templates (e.g.
2175
+ * master-detail rows, responsive cards) without reaching into the adapter's
2176
+ * private `viewContainerRef` / `viewRefs`.
2360
2177
  * @internal
2361
2178
  */
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
- };
2179
+ createTrackedEmbeddedView(template, context) {
2180
+ const viewRef = this.viewContainerRef.createEmbeddedView(template, context);
2181
+ this.viewRefs.push(viewRef);
2182
+ viewRef.detectChanges();
2183
+ return viewRef;
2383
2184
  }
2384
2185
  /**
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.
2186
+ * Processes a GroupingColumnsConfig. Delegates to the feature config
2187
+ * preprocessor installed by `@toolbox-web/grid-angular/features/grouping-columns`,
2188
+ * which handles converting Angular component class references to actual
2189
+ * renderer functions. Returns the input config unchanged if the feature
2190
+ * is not imported.
2191
+ */
2192
+ processGroupingColumnsConfig(config) {
2193
+ return this.applyFeatureConfigPreprocessor('groupingColumns', config);
2194
+ }
2195
+ /**
2196
+ * Processes a GroupingRowsConfig. Delegates to the feature config preprocessor
2197
+ * installed by `@toolbox-web/grid-angular/features/grouping-rows`.
2198
+ */
2199
+ processGroupingRowsConfig(config) {
2200
+ return this.applyFeatureConfigPreprocessor('groupingRows', config);
2201
+ }
2202
+ /**
2203
+ * Processes a PinnedRowsConfig. Delegates to the feature config preprocessor
2204
+ * installed by `@toolbox-web/grid-angular/features/pinned-rows`.
2205
+ */
2206
+ processPinnedRowsConfig(config) {
2207
+ return this.applyFeatureConfigPreprocessor('pinnedRows', config);
2208
+ }
2209
+ /**
2210
+ * Run a registered feature-config preprocessor against `config`, returning
2211
+ * the original config unchanged when the feature is not imported.
2389
2212
  * @internal
2390
2213
  */
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
- };
2214
+ applyFeatureConfigPreprocessor(name, config) {
2215
+ if (!config || typeof config !== 'object')
2216
+ return config;
2217
+ const preprocessor = getFeatureConfigPreprocessor(name);
2218
+ if (!preprocessor)
2219
+ return config;
2220
+ return preprocessor(config, this);
2411
2221
  }
2412
2222
  /**
2413
2223
  * Sets component inputs using Angular's setInput API.
@@ -2449,11 +2259,11 @@ class GridAdapter {
2449
2259
  this.editorComponentRefs.splice(i, 1);
2450
2260
  }
2451
2261
  }
2452
- // Detach `before-edit-close` listeners for editor hosts inside this cell
2453
- for (const [hostEl, unsub] of this.editorBeforeCloseUnsubs) {
2262
+ // Detach editor-mount hook teardowns for editor hosts inside this cell.
2263
+ for (const [hostEl, unsub] of this.editorMountTeardowns) {
2454
2264
  if (cellEl.contains(hostEl)) {
2455
2265
  unsub();
2456
- this.editorBeforeCloseUnsubs.delete(hostEl);
2266
+ this.editorMountTeardowns.delete(hostEl);
2457
2267
  }
2458
2268
  }
2459
2269
  }
@@ -2493,40 +2303,28 @@ class GridAdapter {
2493
2303
  this.componentRefs = [];
2494
2304
  this.editorComponentRefs.forEach((ref) => ref.destroy());
2495
2305
  this.editorComponentRefs = [];
2496
- this.editorBeforeCloseUnsubs.forEach((unsub) => unsub());
2497
- this.editorBeforeCloseUnsubs.clear();
2306
+ this.editorMountTeardowns.forEach((unsub) => unsub());
2307
+ this.editorMountTeardowns.clear();
2498
2308
  }
2499
2309
  /**
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`
2310
+ * Runs every registered {@link EditorMountHook} against a freshly mounted
2311
+ * editor host once it has been parented to the grid. The grid is resolved
2312
+ * lazily via `queueMicrotask` because the host is appended to the cell
2313
+ * *after* the editor wrapper returns. Mirror of Vue's
2314
+ * `attachBeforeEditCloseFlush` and React's `wrapReactEditor`
2508
2315
  * queueMicrotask bridge.
2316
+ *
2317
+ * Without any feature imports the hook list is empty and this is a no-op
2318
+ * — `before-edit-close` blur handling lives in
2319
+ * `@toolbox-web/grid-angular/features/editing`.
2509
2320
  * @internal
2510
2321
  */
2511
- attachBeforeEditCloseFlush(host) {
2322
+ runEditorMountHooks(host) {
2512
2323
  queueMicrotask(() => {
2513
2324
  const gridEl = host.closest('tbw-grid');
2514
2325
  if (!gridEl)
2515
2326
  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
- });
2327
+ this.editorMountTeardowns.set(host, notifyEditorMounted(host, gridEl));
2530
2328
  });
2531
2329
  }
2532
2330
  }
@@ -2788,6 +2586,12 @@ function injectGrid(selector = 'tbw-grid') {
2788
2586
  const unregisterStyles = (id) => {
2789
2587
  element()?.unregisterStyles?.(id);
2790
2588
  };
2589
+ const getPlugin = (pluginClass) => {
2590
+ return element()?.getPlugin?.(pluginClass);
2591
+ };
2592
+ const getPluginByName = ((name) => {
2593
+ return element()?.getPluginByName?.(name);
2594
+ });
2791
2595
  return {
2792
2596
  element,
2793
2597
  isReady,
@@ -2798,6 +2602,8 @@ function injectGrid(selector = 'tbw-grid') {
2798
2602
  toggleGroup,
2799
2603
  registerStyles,
2800
2604
  unregisterStyles,
2605
+ getPlugin,
2606
+ getPluginByName,
2801
2607
  };
2802
2608
  }
2803
2609
 
@@ -2864,6 +2670,11 @@ function injectGrid(selector = 'tbw-grid') {
2864
2670
  * ```
2865
2671
  *
2866
2672
  * @typeParam TRow - The row data type (available via `params().column`)
2673
+ *
2674
+ * MOVE-IN-V2: this class will physically move into
2675
+ * `@toolbox-web/grid-angular/features/filtering` in v2.0.0; the deprecated
2676
+ * re-export from the main `@toolbox-web/grid-angular` entry will be removed at
2677
+ * the same time. Consumers should already be importing from the feature entry.
2867
2678
  */
2868
2679
  class BaseFilterPanel {
2869
2680
  /**
@@ -2967,6 +2778,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
2967
2778
  *
2968
2779
  * @typeParam TRow - The row data type
2969
2780
  * @typeParam TValue - The cell value type
2781
+ *
2782
+ * MOVE-IN-V2: this class will physically move into
2783
+ * `@toolbox-web/grid-angular/features/editing` in v2.0.0; the deprecated
2784
+ * re-export from the main `@toolbox-web/grid-angular` entry will be removed at
2785
+ * the same time. Consumers should already be importing from the feature entry.
2970
2786
  */
2971
2787
  class BaseGridEditor {
2972
2788
  elementRef = inject(ElementRef);
@@ -3269,6 +3085,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
3269
3085
  *
3270
3086
  * @typeParam TRow - The row data type
3271
3087
  * @typeParam TValue - The cell/control value type
3088
+ *
3089
+ * MOVE-IN-V2: this class will physically move into
3090
+ * `@toolbox-web/grid-angular/features/editing` in v2.0.0; the deprecated
3091
+ * re-export from the main `@toolbox-web/grid-angular` entry will be removed at
3092
+ * the same time. Consumers should already be importing from the feature entry.
3272
3093
  */
3273
3094
  class BaseGridEditorCVA extends BaseGridEditor {
3274
3095
  // ============================================================================
@@ -3532,6 +3353,12 @@ let anchorCounter = 0;
3532
3353
  *
3533
3354
  * @typeParam TRow - The row data type
3534
3355
  * @typeParam TValue - The cell value type
3356
+ *
3357
+ * MOVE-IN-V2: this class (and its companion `OverlayPosition` type) will
3358
+ * physically move into `@toolbox-web/grid-angular/features/editing` in v2.0.0;
3359
+ * the deprecated re-export from the main `@toolbox-web/grid-angular` entry
3360
+ * will be removed at the same time. Consumers should already be importing
3361
+ * from the feature entry.
3535
3362
  */
3536
3363
  class BaseOverlayEditor extends BaseGridEditor {
3537
3364
  _elementRef = inject(ElementRef);
@@ -4010,6 +3837,102 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
4010
3837
  }]
4011
3838
  }] });
4012
3839
 
3840
+ // Global registry mapping DOM elements to their templates
3841
+ const detailTemplateRegistry = new Map();
3842
+ /**
3843
+ * Gets the detail template registered for a given grid element.
3844
+ * Used by AngularGridAdapter to retrieve templates at render time.
3845
+ */
3846
+ function getDetailTemplate(gridElement) {
3847
+ // Look for tbw-grid-detail child and get its template
3848
+ const detailElement = gridElement.querySelector('tbw-grid-detail');
3849
+ if (detailElement) {
3850
+ return detailTemplateRegistry.get(detailElement);
3851
+ }
3852
+ return undefined;
3853
+ }
3854
+ /**
3855
+ * Directive that captures an `<ng-template>` for use as a master-detail row renderer.
3856
+ *
3857
+ * This enables declarative Angular component usage for expandable detail rows
3858
+ * that appear below the main row when expanded.
3859
+ *
3860
+ * ## Usage
3861
+ *
3862
+ * ```html
3863
+ * <tbw-grid [rows]="rows" [gridConfig]="config">
3864
+ * <tbw-grid-detail [showExpandColumn]="true" animation="slide">
3865
+ * <ng-template let-row>
3866
+ * <app-detail-panel [employee]="row" />
3867
+ * </ng-template>
3868
+ * </tbw-grid-detail>
3869
+ * </tbw-grid>
3870
+ * ```
3871
+ *
3872
+ * The template context provides:
3873
+ * - `$implicit` / `row`: The full row data object
3874
+ *
3875
+ * Import the directive from the master-detail feature entry:
3876
+ *
3877
+ * ```typescript
3878
+ * import { GridDetailView } from '@toolbox-web/grid-angular/features/master-detail';
3879
+ *
3880
+ * @Component({
3881
+ * imports: [GridDetailView],
3882
+ * // ...
3883
+ * })
3884
+ * ```
3885
+ *
3886
+ * > Note: `GridDetailView` is also re-exported from `@toolbox-web/grid-angular`
3887
+ * > for backwards compatibility, but that re-export is deprecated and will be
3888
+ * > removed in v2.0.0. Always import from the feature entry.
3889
+ *
3890
+ * @example
3891
+ * ```html
3892
+ * <tbw-grid [rows]="rows" [gridConfig]="config">
3893
+ * <tbw-grid-detail [showExpandColumn]="true" animation="slide">
3894
+ * <ng-template let-row>
3895
+ * <app-detail-panel [employee]="row" />
3896
+ * </ng-template>
3897
+ * </tbw-grid-detail>
3898
+ * </tbw-grid>
3899
+ * ```
3900
+ *
3901
+ * @category Directive
3902
+ */
3903
+ class GridDetailView {
3904
+ elementRef = inject((ElementRef));
3905
+ /** Whether to show the expand/collapse column. Default: true */
3906
+ showExpandColumn = input(true, ...(ngDevMode ? [{ debugName: "showExpandColumn" }] : /* istanbul ignore next */ []));
3907
+ /** Animation style for expand/collapse. Default: 'slide' */
3908
+ animation = input('slide', ...(ngDevMode ? [{ debugName: "animation" }] : /* istanbul ignore next */ []));
3909
+ /**
3910
+ * Query for the ng-template content child.
3911
+ */
3912
+ template = contentChild((TemplateRef), ...(ngDevMode ? [{ debugName: "template" }] : /* istanbul ignore next */ []));
3913
+ /** Effect that triggers when the template is available */
3914
+ onTemplateReceived = effect(() => {
3915
+ const template = this.template();
3916
+ if (template) {
3917
+ // Register the template for this element
3918
+ detailTemplateRegistry.set(this.elementRef.nativeElement, template);
3919
+ }
3920
+ }, ...(ngDevMode ? [{ debugName: "onTemplateReceived" }] : /* istanbul ignore next */ []));
3921
+ /**
3922
+ * Static type guard for template context.
3923
+ * Enables type inference in templates.
3924
+ */
3925
+ static ngTemplateContextGuard(dir, ctx) {
3926
+ return true;
3927
+ }
3928
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: GridDetailView, deps: [], target: i0.ɵɵFactoryTarget.Directive });
3929
+ 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 });
3930
+ }
3931
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: GridDetailView, decorators: [{
3932
+ type: Directive,
3933
+ args: [{ selector: 'tbw-grid-detail' }]
3934
+ }], 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 }] }] } });
3935
+
4013
3936
  /**
4014
3937
  * Directive that registers `<tbw-grid-header>` as a known Angular element.
4015
3938
  *
@@ -4132,6 +4055,13 @@ function getLazyFormContext(gridElement) {
4132
4055
  *
4133
4056
  * @see GridFormArray For small datasets with full upfront validation
4134
4057
  * @category Directive
4058
+ *
4059
+ * MOVE-IN-V2: this directive (and its `LazyFormFactory`, `RowFormChangeEvent`
4060
+ * types and `getLazyFormContext` helper) will physically move into
4061
+ * `@toolbox-web/grid-angular/features/editing` in v2.0.0; the deprecated
4062
+ * re-exports from the main `@toolbox-web/grid-angular` entry will be removed
4063
+ * at the same time. Consumers should already be importing from the feature
4064
+ * entry.
4135
4065
  */
4136
4066
  class GridLazyForm {
4137
4067
  elementRef = inject((ElementRef));
@@ -4517,15 +4447,119 @@ class GridLazyForm {
4517
4447
  }
4518
4448
  return true;
4519
4449
  }
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 });
4450
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: GridLazyForm, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4451
+ 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 });
4452
+ }
4453
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: GridLazyForm, decorators: [{
4454
+ type: Directive,
4455
+ args: [{
4456
+ selector: 'tbw-grid[lazyForm]',
4457
+ }]
4458
+ }], 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"] }] } });
4459
+
4460
+ /**
4461
+ * Registry to store responsive card templates by grid element.
4462
+ * Used by AngularGridAdapter to create card renderers.
4463
+ */
4464
+ const responsiveCardTemplateRegistry = new Map();
4465
+ /**
4466
+ * Retrieves the responsive card template for a grid element.
4467
+ *
4468
+ * @param gridElement - The grid element to look up
4469
+ * @returns The template reference or undefined if not found
4470
+ */
4471
+ function getResponsiveCardTemplate(gridElement) {
4472
+ // Find the tbw-grid-responsive-card element inside the grid
4473
+ const cardElement = gridElement.querySelector('tbw-grid-responsive-card');
4474
+ if (!cardElement)
4475
+ return undefined;
4476
+ return responsiveCardTemplateRegistry.get(cardElement);
4477
+ }
4478
+ /**
4479
+ * Directive for providing custom Angular templates for responsive card layout.
4480
+ *
4481
+ * Use this directive to define how each row should render when the grid
4482
+ * is in responsive/mobile mode. The template receives the row data and index.
4483
+ *
4484
+ * ## Usage
4485
+ *
4486
+ * ```html
4487
+ * <tbw-grid [rows]="employees">
4488
+ * <tbw-grid-responsive-card>
4489
+ * <ng-template let-employee let-idx="index">
4490
+ * <div class="employee-card">
4491
+ * <img [src]="employee.avatar" alt="">
4492
+ * <div class="info">
4493
+ * <strong>{{ employee.name }}</strong>
4494
+ * <span>{{ employee.department }}</span>
4495
+ * </div>
4496
+ * </div>
4497
+ * </ng-template>
4498
+ * </tbw-grid-responsive-card>
4499
+ * </tbw-grid>
4500
+ * ```
4501
+ *
4502
+ * ## Important Notes
4503
+ *
4504
+ * - The ResponsivePlugin must be added to your grid config
4505
+ * - The Grid directive will automatically configure the plugin's cardRenderer
4506
+ * - Template context provides `$implicit` (row), `row`, and `index`
4507
+ *
4508
+ * @see ResponsivePlugin
4509
+ * @category Directive
4510
+ */
4511
+ class GridResponsiveCard {
4512
+ elementRef = inject((ElementRef));
4513
+ /**
4514
+ * Card row height in pixels. Use `'auto'` for dynamic height based on content.
4515
+ *
4516
+ * Mirrors to the `card-row-height` attribute on the underlying
4517
+ * `<tbw-grid-responsive-card>` element which the ResponsivePlugin reads.
4518
+ *
4519
+ * @default 'auto'
4520
+ */
4521
+ cardRowHeight = input(...(ngDevMode ? [undefined, { debugName: "cardRowHeight" }] : /* istanbul ignore next */ []));
4522
+ /**
4523
+ * The ng-template containing the card content.
4524
+ */
4525
+ template = contentChild((TemplateRef), ...(ngDevMode ? [{ debugName: "template" }] : /* istanbul ignore next */ []));
4526
+ /**
4527
+ * Effect that registers the template when it becomes available.
4528
+ */
4529
+ onTemplateReceived = effect(() => {
4530
+ const template = this.template();
4531
+ if (template) {
4532
+ responsiveCardTemplateRegistry.set(this.elementRef.nativeElement, template);
4533
+ }
4534
+ }, ...(ngDevMode ? [{ debugName: "onTemplateReceived" }] : /* istanbul ignore next */ []));
4535
+ /**
4536
+ * Effect that mirrors the `cardRowHeight` input to the kebab-cased attribute
4537
+ * read by the ResponsivePlugin.
4538
+ */
4539
+ onCardRowHeightChange = effect(() => {
4540
+ const value = this.cardRowHeight();
4541
+ const element = this.elementRef.nativeElement;
4542
+ if (value === undefined) {
4543
+ element.removeAttribute('card-row-height');
4544
+ return;
4545
+ }
4546
+ element.setAttribute('card-row-height', value === 'auto' ? 'auto' : String(value));
4547
+ }, ...(ngDevMode ? [{ debugName: "onCardRowHeightChange" }] : /* istanbul ignore next */ []));
4548
+ /**
4549
+ * Type guard for template context inference.
4550
+ */
4551
+ static ngTemplateContextGuard(_directive, context) {
4552
+ return true;
4553
+ }
4554
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: GridResponsiveCard, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4555
+ 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
4556
  }
4523
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: GridLazyForm, decorators: [{
4557
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: GridResponsiveCard, decorators: [{
4524
4558
  type: Directive,
4525
4559
  args: [{
4526
- selector: 'tbw-grid[lazyForm]',
4560
+ selector: 'tbw-grid-responsive-card',
4527
4561
  }]
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"] }] } });
4562
+ }], propDecorators: { cardRowHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "cardRowHeight", required: false }] }], template: [{ type: i0.ContentChild, args: [i0.forwardRef(() => TemplateRef), { isSignal: true }] }] } });
4529
4563
 
4530
4564
  /**
4531
4565
  * Directive that registers `<tbw-grid-tool-buttons>` as a known Angular element.
@@ -4566,6 +4600,149 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
4566
4600
  }]
4567
4601
  }] });
4568
4602
 
4603
+ /** Capitalize the first letter of a string for header generation. */
4604
+ function capitalize(str) {
4605
+ return str.charAt(0).toUpperCase() + str.slice(1);
4606
+ }
4607
+ /**
4608
+ * Generate a human-readable header from a field name.
4609
+ *
4610
+ * Handles camelCase, snake_case, and kebab-case.
4611
+ *
4612
+ * @example
4613
+ * generateHeader('firstName') → 'First Name'
4614
+ * generateHeader('last_name') → 'Last Name'
4615
+ * generateHeader('email-address') → 'Email Address'
4616
+ * generateHeader('id') → 'ID' (special case)
4617
+ */
4618
+ function generateHeader(field) {
4619
+ if (field.toLowerCase() === 'id')
4620
+ return 'ID';
4621
+ const words = field
4622
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
4623
+ .replace(/[_-]/g, ' ')
4624
+ .split(' ')
4625
+ .filter(Boolean);
4626
+ return words.map(capitalize).join(' ');
4627
+ }
4628
+ /** Valid column types from the shorthand notation. */
4629
+ const VALID_TYPES = new Set(['string', 'number', 'boolean', 'date', 'datetime', 'currency']);
4630
+ /**
4631
+ * Parse a column shorthand string into a ColumnConfig.
4632
+ *
4633
+ * Supports formats:
4634
+ * - `'fieldName'` → `{ field: 'fieldName', header: 'Field Name' }`
4635
+ * - `'fieldName:type'` → `{ field: 'fieldName', header: 'Field Name', type: 'type' }`
4636
+ *
4637
+ * @param shorthand - The shorthand string (e.g., 'name', 'salary:number')
4638
+ * @returns A ColumnConfig object
4639
+ */
4640
+ function parseColumnShorthand(shorthand) {
4641
+ const colonIndex = shorthand.lastIndexOf(':');
4642
+ if (colonIndex > 0) {
4643
+ const potentialType = shorthand.slice(colonIndex + 1).toLowerCase();
4644
+ if (VALID_TYPES.has(potentialType)) {
4645
+ const field = shorthand.slice(0, colonIndex);
4646
+ return {
4647
+ field: field,
4648
+ header: generateHeader(field),
4649
+ type: potentialType,
4650
+ };
4651
+ }
4652
+ }
4653
+ return {
4654
+ field: shorthand,
4655
+ header: generateHeader(shorthand),
4656
+ };
4657
+ }
4658
+ /**
4659
+ * Normalize an array of column shorthands to ColumnConfig objects.
4660
+ *
4661
+ * @param columns - Array of column shorthands (strings or ColumnConfig objects)
4662
+ * @returns Array of ColumnConfig objects
4663
+ */
4664
+ function normalizeColumns(columns) {
4665
+ return columns.map((col) => (typeof col === 'string' ? parseColumnShorthand(col) : col));
4666
+ }
4667
+ /**
4668
+ * Apply column defaults to a list of columns. Individual column properties
4669
+ * override defaults.
4670
+ */
4671
+ function applyColumnDefaults(columns, defaults) {
4672
+ if (!defaults)
4673
+ return columns;
4674
+ return columns.map((col) => ({ ...defaults, ...col }));
4675
+ }
4676
+ /** Check if an array of columns contains any shorthand strings. */
4677
+ function hasColumnShorthands(columns) {
4678
+ return columns.some((col) => typeof col === 'string');
4679
+ }
4680
+
4681
+ /** Per-element map of claimed feature names → config getter. */
4682
+ const featureClaims = new WeakMap();
4683
+ /** Per-element set of claimed event names (matches `keyof DataGridEventMap`). */
4684
+ const eventClaims = new WeakMap();
4685
+ /**
4686
+ * Register a feature claim. Called by a feature directive's constructor;
4687
+ * the {@link Grid} directive will then use {@link getFeatureClaim} during
4688
+ * plugin creation instead of reading its own deprecated input.
4689
+ * @internal
4690
+ */
4691
+ function registerFeatureClaim(grid, name, getConfig) {
4692
+ let map = featureClaims.get(grid);
4693
+ if (!map) {
4694
+ map = new Map();
4695
+ featureClaims.set(grid, map);
4696
+ }
4697
+ map.set(name, getConfig);
4698
+ }
4699
+ /**
4700
+ * Look up a feature claim. Returns the registered config getter, or
4701
+ * `undefined` if no directive owns this feature on this element.
4702
+ * @internal
4703
+ */
4704
+ function getFeatureClaim(grid, name) {
4705
+ return featureClaims.get(grid)?.get(name);
4706
+ }
4707
+ /**
4708
+ * Drop a feature claim. Called by a feature directive's `ngOnDestroy` so
4709
+ * that, if the directive is removed (e.g. via `*ngIf`) but the host
4710
+ * `<tbw-grid>` survives, {@link Grid}'s deprecated input takes back over.
4711
+ * @internal
4712
+ */
4713
+ function unregisterFeatureClaim(grid, name) {
4714
+ featureClaims.get(grid)?.delete(name);
4715
+ }
4716
+ /**
4717
+ * Mark an event as owned by a feature directive. {@link Grid}'s
4718
+ * `setupEventListeners` skips wiring its own deprecated `output()` for any
4719
+ * claimed event — the directive owns the listener and the emit.
4720
+ * @internal
4721
+ */
4722
+ function claimEvent(grid, eventName) {
4723
+ let set = eventClaims.get(grid);
4724
+ if (!set) {
4725
+ set = new Set();
4726
+ eventClaims.set(grid, set);
4727
+ }
4728
+ set.add(eventName);
4729
+ }
4730
+ /**
4731
+ * Returns true if a directive has claimed this event on this grid element.
4732
+ * @internal
4733
+ */
4734
+ function isEventClaimed(grid, eventName) {
4735
+ return eventClaims.get(grid)?.has(eventName) ?? false;
4736
+ }
4737
+ /**
4738
+ * Drop an event claim. Pair with {@link claimEvent} in a directive's
4739
+ * `ngOnDestroy`.
4740
+ * @internal
4741
+ */
4742
+ function unclaimEvent(grid, eventName) {
4743
+ eventClaims.get(grid)?.delete(eventName);
4744
+ }
4745
+
4569
4746
  /**
4570
4747
  * Directive that automatically registers the Angular adapter with tbw-grid elements.
4571
4748
  *
@@ -4683,13 +4860,25 @@ class Grid {
4683
4860
  if (columnsValue === undefined)
4684
4861
  return;
4685
4862
  const grid = this.elementRef.nativeElement;
4863
+ // First normalize any shorthand strings to ColumnConfig objects, then
4864
+ // merge in any per-grid column defaults. Individual column props always win.
4865
+ // Note: Angular ColumnConfig allows component classes for renderer/editor,
4866
+ // which the adapter normalizes via processColumn below; we widen to `any`
4867
+ // here so the shorthand helpers (typed against the core ColumnConfig) accept
4868
+ // the Angular-flavoured payload unchanged.
4869
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4870
+ const normalized = applyColumnDefaults(
4871
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4872
+ normalizeColumns(columnsValue),
4873
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4874
+ this.columnDefaults());
4686
4875
  // Process columns through the adapter to convert Angular component classes
4687
4876
  // (renderer/editor) to functions — the grid's columns setter does NOT call
4688
4877
  // processConfig, unlike gridConfig. Without this, raw component classes
4689
4878
  // would be invoked without `new`, causing runtime errors.
4690
4879
  const processed = this.adapter
4691
- ? columnsValue.map((col) => this.adapter.processColumn(col))
4692
- : columnsValue;
4880
+ ? normalized.map((col) => this.adapter.processColumn(col))
4881
+ : normalized;
4693
4882
  grid.columns = processed;
4694
4883
  });
4695
4884
  // Effect to sync fitMode to the grid element
@@ -4824,21 +5013,35 @@ class Grid {
4824
5013
  /**
4825
5014
  * Column configuration array.
4826
5015
  *
5016
+ * Accepts either full `ColumnConfig` objects or shorthand strings such as
5017
+ * `'name'` or `'salary:number'`. Shorthands auto-generate human-readable
5018
+ * headers from the field name.
5019
+ *
4827
5020
  * Shorthand for setting columns without wrapping them in a full `gridConfig`.
4828
5021
  * If both `columns` and `gridConfig.columns` are set, `columns` takes precedence
4829
5022
  * (see configuration precedence system).
4830
5023
  *
4831
5024
  * @example
4832
5025
  * ```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
- * ]" />
5026
+ * <tbw-grid [rows]="data" [columns]="['id:number', 'name', { field: 'status', editable: true }]" />
4838
5027
  * ```
4839
5028
  */
4840
5029
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
4841
5030
  columns = input(...(ngDevMode ? [undefined, { debugName: "columns" }] : /* istanbul ignore next */ []));
5031
+ /**
5032
+ * Default column properties applied to every column in `columns`.
5033
+ * Individual column properties override these defaults.
5034
+ *
5035
+ * @example
5036
+ * ```html
5037
+ * <tbw-grid
5038
+ * [columnDefaults]="{ sortable: true, resizable: true }"
5039
+ * [columns]="[{ field: 'id', sortable: false }, { field: 'name' }]"
5040
+ * />
5041
+ * ```
5042
+ */
5043
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
5044
+ columnDefaults = input(...(ngDevMode ? [undefined, { debugName: "columnDefaults" }] : /* istanbul ignore next */ []));
4842
5045
  /**
4843
5046
  * Column sizing strategy.
4844
5047
  *
@@ -4912,6 +5115,9 @@ class Grid {
4912
5115
  * <!-- Full config object -->
4913
5116
  * <tbw-grid [selection]="{ mode: 'range', checkbox: true }" />
4914
5117
  * ```
5118
+ *
5119
+ * @deprecated Use `GridSelectionDirective` from
5120
+ * `@toolbox-web/grid-angular/features/selection`. Will be removed in v2.0.0.
4915
5121
  */
4916
5122
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
4917
5123
  selection = input(...(ngDevMode ? [undefined, { debugName: "selection" }] : /* istanbul ignore next */ []));
@@ -4936,6 +5142,9 @@ class Grid {
4936
5142
  * <!-- Full config with callbacks -->
4937
5143
  * <tbw-grid [editing]="{ editOn: 'dblclick', onBeforeEditClose: myCallback }" />
4938
5144
  * ```
5145
+ *
5146
+ * @deprecated Use `GridEditingDirective` from
5147
+ * `@toolbox-web/grid-angular/features/editing`. Will be removed in v2.0.0.
4939
5148
  */
4940
5149
  editing = input(...(ngDevMode ? [undefined, { debugName: "editing" }] : /* istanbul ignore next */ []));
4941
5150
  /**
@@ -4950,6 +5159,9 @@ class Grid {
4950
5159
  * ```html
4951
5160
  * <tbw-grid [selection]="'range'" [clipboard]="true" />
4952
5161
  * ```
5162
+ *
5163
+ * @deprecated Use `GridClipboardDirective` from
5164
+ * `@toolbox-web/grid-angular/features/clipboard`. Will be removed in v2.0.0.
4953
5165
  */
4954
5166
  clipboard = input(...(ngDevMode ? [undefined, { debugName: "clipboard" }] : /* istanbul ignore next */ []));
4955
5167
  /**
@@ -4964,6 +5176,9 @@ class Grid {
4964
5176
  * ```html
4965
5177
  * <tbw-grid [contextMenu]="true" />
4966
5178
  * ```
5179
+ *
5180
+ * @deprecated Use `GridContextMenuDirective` from
5181
+ * `@toolbox-web/grid-angular/features/context-menu`. Will be removed in v2.0.0.
4967
5182
  */
4968
5183
  contextMenu = input(...(ngDevMode ? [undefined, { debugName: "contextMenu" }] : /* istanbul ignore next */ []));
4969
5184
  /**
@@ -4988,6 +5203,9 @@ class Grid {
4988
5203
  * <!-- Full config -->
4989
5204
  * <tbw-grid [multiSort]="{ maxSortColumns: 3 }" />
4990
5205
  * ```
5206
+ *
5207
+ * @deprecated Use `GridMultiSortDirective` from
5208
+ * `@toolbox-web/grid-angular/features/multi-sort`. Will be removed in v2.0.0.
4991
5209
  */
4992
5210
  multiSort = input(...(ngDevMode ? [undefined, { debugName: "multiSort" }] : /* istanbul ignore next */ []));
4993
5211
  /**
@@ -5003,6 +5221,13 @@ class Grid {
5003
5221
  * <tbw-grid [filtering]="true" />
5004
5222
  * <tbw-grid [filtering]="{ debounceMs: 200 }" />
5005
5223
  * ```
5224
+ *
5225
+ * @deprecated Use `GridFilteringDirective` from
5226
+ * `@toolbox-web/grid-angular/features/filtering` and add it to your
5227
+ * component's `imports`. The directive owns the `filtering` input + the
5228
+ * `filterChange` output and lets the typed surface tree-shake away when
5229
+ * the feature is not imported. This input remains as a non-breaking shim
5230
+ * and will be removed in v2.0.0.
5006
5231
  */
5007
5232
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5008
5233
  filtering = input(...(ngDevMode ? [undefined, { debugName: "filtering" }] : /* istanbul ignore next */ []));
@@ -5018,6 +5243,9 @@ class Grid {
5018
5243
  * ```html
5019
5244
  * <tbw-grid [reorderColumns]="true" />
5020
5245
  * ```
5246
+ *
5247
+ * @deprecated Use `GridReorderColumnsDirective` from
5248
+ * `@toolbox-web/grid-angular/features/reorder-columns`. Will be removed in v2.0.0.
5021
5249
  */
5022
5250
  reorderColumns = input(...(ngDevMode ? [undefined, { debugName: "reorderColumns" }] : /* istanbul ignore next */ []));
5023
5251
  /**
@@ -5032,6 +5260,9 @@ class Grid {
5032
5260
  * ```html
5033
5261
  * <tbw-grid [visibility]="true" />
5034
5262
  * ```
5263
+ *
5264
+ * @deprecated Use `GridVisibilityDirective` from
5265
+ * `@toolbox-web/grid-angular/features/visibility`. Will be removed in v2.0.0.
5035
5266
  */
5036
5267
  visibility = input(...(ngDevMode ? [undefined, { debugName: "visibility" }] : /* istanbul ignore next */ []));
5037
5268
  /**
@@ -5051,6 +5282,9 @@ class Grid {
5051
5282
  * { field: 'actions', pinned: 'right' }
5052
5283
  * ]" />
5053
5284
  * ```
5285
+ *
5286
+ * @deprecated Use `GridPinnedColumnsDirective` from
5287
+ * `@toolbox-web/grid-angular/features/pinned-columns`. Will be removed in v2.0.0.
5054
5288
  */
5055
5289
  pinnedColumns = input(...(ngDevMode ? [undefined, { debugName: "pinnedColumns" }] : /* istanbul ignore next */ []));
5056
5290
  /**
@@ -5065,6 +5299,9 @@ class Grid {
5065
5299
  * ```html
5066
5300
  * <tbw-grid [groupingColumns]="true" />
5067
5301
  * ```
5302
+ *
5303
+ * @deprecated Use `GridGroupingColumnsDirective` from
5304
+ * `@toolbox-web/grid-angular/features/grouping-columns`. Will be removed in v2.0.0.
5068
5305
  */
5069
5306
  groupingColumns = input(...(ngDevMode ? [undefined, { debugName: "groupingColumns" }] : /* istanbul ignore next */ []));
5070
5307
  /**
@@ -5079,6 +5316,9 @@ class Grid {
5079
5316
  * ```html
5080
5317
  * <tbw-grid [columnVirtualization]="true" />
5081
5318
  * ```
5319
+ *
5320
+ * @deprecated Use `GridColumnVirtualizationDirective` from
5321
+ * `@toolbox-web/grid-angular/features/column-virtualization`. Will be removed in v2.0.0.
5082
5322
  */
5083
5323
  columnVirtualization = input(...(ngDevMode ? [undefined, { debugName: "columnVirtualization" }] : /* istanbul ignore next */ []));
5084
5324
  /**
@@ -5104,6 +5344,9 @@ class Grid {
5104
5344
  * ```html
5105
5345
  * <tbw-grid [rowDragDrop]="{ dropZone: 'employees', operation: 'move' }" />
5106
5346
  * ```
5347
+ *
5348
+ * @deprecated Use `GridRowDragDropDirective` from
5349
+ * `@toolbox-web/grid-angular/features/row-drag-drop`. Will be removed in v2.0.0.
5107
5350
  */
5108
5351
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5109
5352
  rowDragDrop = input(...(ngDevMode ? [undefined, { debugName: "rowDragDrop" }] : /* istanbul ignore next */ []));
@@ -5119,6 +5362,9 @@ class Grid {
5119
5362
  * ```html
5120
5363
  * <tbw-grid [groupingRows]="{ groupBy: ['department'] }" />
5121
5364
  * ```
5365
+ *
5366
+ * @deprecated Use `GridGroupingRowsDirective` from
5367
+ * `@toolbox-web/grid-angular/features/grouping-rows`. Will be removed in v2.0.0.
5122
5368
  */
5123
5369
  groupingRows = input(...(ngDevMode ? [undefined, { debugName: "groupingRows" }] : /* istanbul ignore next */ []));
5124
5370
  /**
@@ -5133,6 +5379,9 @@ class Grid {
5133
5379
  * ```html
5134
5380
  * <tbw-grid [pinnedRows]="{ bottom: [{ type: 'aggregation' }] }" />
5135
5381
  * ```
5382
+ *
5383
+ * @deprecated Use `GridPinnedRowsDirective` from
5384
+ * `@toolbox-web/grid-angular/features/pinned-rows`. Will be removed in v2.0.0.
5136
5385
  */
5137
5386
  pinnedRows = input(...(ngDevMode ? [undefined, { debugName: "pinnedRows" }] : /* istanbul ignore next */ []));
5138
5387
  /**
@@ -5147,6 +5396,9 @@ class Grid {
5147
5396
  * ```html
5148
5397
  * <tbw-grid [tree]="{ childrenField: 'children' }" />
5149
5398
  * ```
5399
+ *
5400
+ * @deprecated Use `GridTreeDirective` from
5401
+ * `@toolbox-web/grid-angular/features/tree`. Will be removed in v2.0.0.
5150
5402
  */
5151
5403
  tree = input(...(ngDevMode ? [undefined, { debugName: "tree" }] : /* istanbul ignore next */ []));
5152
5404
  /**
@@ -5161,6 +5413,9 @@ class Grid {
5161
5413
  * ```html
5162
5414
  * <tbw-grid [masterDetail]="{ detailRenderer: detailFn }" />
5163
5415
  * ```
5416
+ *
5417
+ * @deprecated Use `GridMasterDetailDirective` from
5418
+ * `@toolbox-web/grid-angular/features/master-detail`. Will be removed in v2.0.0.
5164
5419
  */
5165
5420
  masterDetail = input(...(ngDevMode ? [undefined, { debugName: "masterDetail" }] : /* istanbul ignore next */ []));
5166
5421
  /**
@@ -5175,6 +5430,9 @@ class Grid {
5175
5430
  * ```html
5176
5431
  * <tbw-grid [responsive]="{ breakpoint: 768 }" />
5177
5432
  * ```
5433
+ *
5434
+ * @deprecated Use `GridResponsiveDirective` from
5435
+ * `@toolbox-web/grid-angular/features/responsive`. Will be removed in v2.0.0.
5178
5436
  */
5179
5437
  responsive = input(...(ngDevMode ? [undefined, { debugName: "responsive" }] : /* istanbul ignore next */ []));
5180
5438
  /**
@@ -5189,6 +5447,9 @@ class Grid {
5189
5447
  * ```html
5190
5448
  * <tbw-grid [editing]="'dblclick'" [undoRedo]="true" />
5191
5449
  * ```
5450
+ *
5451
+ * @deprecated Use `GridUndoRedoDirective` from
5452
+ * `@toolbox-web/grid-angular/features/undo-redo`. Will be removed in v2.0.0.
5192
5453
  */
5193
5454
  undoRedo = input(...(ngDevMode ? [undefined, { debugName: "undoRedo" }] : /* istanbul ignore next */ []));
5194
5455
  /**
@@ -5204,6 +5465,9 @@ class Grid {
5204
5465
  * <tbw-grid [export]="true" />
5205
5466
  * <tbw-grid [export]="{ filename: 'data.csv' }" />
5206
5467
  * ```
5468
+ *
5469
+ * @deprecated Use `GridExportDirective` from
5470
+ * `@toolbox-web/grid-angular/features/export`. Will be removed in v2.0.0.
5207
5471
  */
5208
5472
  exportFeature = input(undefined, { ...(ngDevMode ? { debugName: "exportFeature" } : /* istanbul ignore next */ {}), alias: 'export' });
5209
5473
  /**
@@ -5218,6 +5482,9 @@ class Grid {
5218
5482
  * ```html
5219
5483
  * <tbw-grid [print]="true" />
5220
5484
  * ```
5485
+ *
5486
+ * @deprecated Use `GridPrintDirective` from
5487
+ * `@toolbox-web/grid-angular/features/print`. Will be removed in v2.0.0.
5221
5488
  */
5222
5489
  print = input(...(ngDevMode ? [undefined, { debugName: "print" }] : /* istanbul ignore next */ []));
5223
5490
  /**
@@ -5232,6 +5499,9 @@ class Grid {
5232
5499
  * ```html
5233
5500
  * <tbw-grid [pivot]="{ rowFields: ['category'], valueField: 'sales' }" />
5234
5501
  * ```
5502
+ *
5503
+ * @deprecated Use `GridPivotDirective` from
5504
+ * `@toolbox-web/grid-angular/features/pivot`. Will be removed in v2.0.0.
5235
5505
  */
5236
5506
  pivot = input(...(ngDevMode ? [undefined, { debugName: "pivot" }] : /* istanbul ignore next */ []));
5237
5507
  /**
@@ -5246,6 +5516,9 @@ class Grid {
5246
5516
  * ```html
5247
5517
  * <tbw-grid [serverSide]="{ dataSource: fetchDataFn }" />
5248
5518
  * ```
5519
+ *
5520
+ * @deprecated Use `GridServerSideDirective` from
5521
+ * `@toolbox-web/grid-angular/features/server-side`. Will be removed in v2.0.0.
5249
5522
  */
5250
5523
  serverSide = input(...(ngDevMode ? [undefined, { debugName: "serverSide" }] : /* istanbul ignore next */ []));
5251
5524
  /**
@@ -5256,6 +5529,9 @@ class Grid {
5256
5529
  * <tbw-grid [tooltip]="true" />
5257
5530
  * <tbw-grid [tooltip]="{ header: true, cell: false }" />
5258
5531
  * ```
5532
+ *
5533
+ * @deprecated Use `GridTooltipDirective` from
5534
+ * `@toolbox-web/grid-angular/features/tooltip`. Will be removed in v2.0.0.
5259
5535
  */
5260
5536
  tooltip = input(...(ngDevMode ? [undefined, { debugName: "tooltip" }] : /* istanbul ignore next */ []));
5261
5537
  // ═══════════════════════════════════════════════════════════════════════════
@@ -5315,8 +5591,85 @@ class Grid {
5315
5591
  * console.log(`Changed ${event.field} to ${event.value} in row ${event.rowIndex}`);
5316
5592
  * }
5317
5593
  * ```
5594
+ *
5595
+ * @deprecated Use `GridEditingDirective` from
5596
+ * `@toolbox-web/grid-angular/features/editing`. Will be removed in v2.0.0.
5318
5597
  */
5319
5598
  cellCommit = output();
5599
+ /**
5600
+ * Emitted when a cell edit is cancelled (Escape, click outside without
5601
+ * commit, or `editor.cancel()`).
5602
+ *
5603
+ * @example
5604
+ * ```html
5605
+ * <tbw-grid (cellCancel)="onCellCancel($event)">...</tbw-grid>
5606
+ * ```
5607
+ *
5608
+ * @deprecated Use `GridEditingDirective` from
5609
+ * `@toolbox-web/grid-angular/features/editing`. Will be removed in v2.0.0.
5610
+ */
5611
+ cellCancel = output();
5612
+ /**
5613
+ * Emitted when a cell editor opens.
5614
+ *
5615
+ * @example
5616
+ * ```html
5617
+ * <tbw-grid (editOpen)="onEditOpen($event)">...</tbw-grid>
5618
+ * ```
5619
+ *
5620
+ * @deprecated Use `GridEditingDirective` from
5621
+ * `@toolbox-web/grid-angular/features/editing`. Will be removed in v2.0.0.
5622
+ */
5623
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
5624
+ editOpen = output();
5625
+ /**
5626
+ * Emitted before an editor closes. Useful for last-chance validation.
5627
+ *
5628
+ * @example
5629
+ * ```html
5630
+ * <tbw-grid (beforeEditClose)="onBeforeEditClose($event)">...</tbw-grid>
5631
+ * ```
5632
+ *
5633
+ * @deprecated Use `GridEditingDirective` from
5634
+ * `@toolbox-web/grid-angular/features/editing`. Will be removed in v2.0.0.
5635
+ */
5636
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
5637
+ beforeEditClose = output();
5638
+ /**
5639
+ * Emitted after an editor closes (whether committed or cancelled).
5640
+ *
5641
+ * @example
5642
+ * ```html
5643
+ * <tbw-grid (editClose)="onEditClose($event)">...</tbw-grid>
5644
+ * ```
5645
+ *
5646
+ * @deprecated Use `GridEditingDirective` from
5647
+ * `@toolbox-web/grid-angular/features/editing`. Will be removed in v2.0.0.
5648
+ */
5649
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
5650
+ editClose = output();
5651
+ /**
5652
+ * Emitted when the dirty / changed-rows state transitions.
5653
+ *
5654
+ * @example
5655
+ * ```html
5656
+ * <tbw-grid (dirtyChange)="onDirtyChange($event)">...</tbw-grid>
5657
+ * ```
5658
+ *
5659
+ * @deprecated Use `GridEditingDirective` from
5660
+ * `@toolbox-web/grid-angular/features/editing`. Will be removed in v2.0.0.
5661
+ */
5662
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
5663
+ dirtyChange = output();
5664
+ /**
5665
+ * Emitted when row data is replaced (e.g. via the `rows` setter).
5666
+ *
5667
+ * @example
5668
+ * ```html
5669
+ * <tbw-grid (dataChange)="onDataChange($event)">...</tbw-grid>
5670
+ * ```
5671
+ */
5672
+ dataChange = output();
5320
5673
  /**
5321
5674
  * Emitted when a row's values are committed (bulk/row editing).
5322
5675
  * Provides the row data and change tracking information.
@@ -5325,6 +5678,9 @@ class Grid {
5325
5678
  * ```html
5326
5679
  * <tbw-grid (rowCommit)="onRowCommit($event)">...</tbw-grid>
5327
5680
  * ```
5681
+ *
5682
+ * @deprecated Use `GridEditingDirective` from
5683
+ * `@toolbox-web/grid-angular/features/editing`. Will be removed in v2.0.0.
5328
5684
  */
5329
5685
  rowCommit = output();
5330
5686
  /**
@@ -5334,6 +5690,9 @@ class Grid {
5334
5690
  * ```html
5335
5691
  * <tbw-grid (changedRowsReset)="onChangedRowsReset($event)">...</tbw-grid>
5336
5692
  * ```
5693
+ *
5694
+ * @deprecated Use `GridEditingDirective` from
5695
+ * `@toolbox-web/grid-angular/features/editing`. Will be removed in v2.0.0.
5337
5696
  */
5338
5697
  changedRowsReset = output();
5339
5698
  /**
@@ -5352,6 +5711,11 @@ class Grid {
5352
5711
  * ```html
5353
5712
  * <tbw-grid (filterChange)="onFilterChange($event)">...</tbw-grid>
5354
5713
  * ```
5714
+ *
5715
+ * @deprecated Use `GridFilteringDirective` from
5716
+ * `@toolbox-web/grid-angular/features/filtering` (the directive
5717
+ * declares the `(filterChange)` output). This output remains as a
5718
+ * non-breaking shim and will be removed in v2.0.0.
5355
5719
  */
5356
5720
  filterChange = output();
5357
5721
  /**
@@ -5363,6 +5727,15 @@ class Grid {
5363
5727
  * ```
5364
5728
  */
5365
5729
  columnResize = output();
5730
+ /**
5731
+ * Emitted when a column's width is reset (double-click on the resize handle).
5732
+ *
5733
+ * @example
5734
+ * ```html
5735
+ * <tbw-grid (columnResizeReset)="onColumnResizeReset($event)">...</tbw-grid>
5736
+ * ```
5737
+ */
5738
+ columnResizeReset = output();
5366
5739
  /**
5367
5740
  * Emitted when a column is moved via drag-and-drop.
5368
5741
  *
@@ -5370,15 +5743,23 @@ class Grid {
5370
5743
  * ```html
5371
5744
  * <tbw-grid (columnMove)="onColumnMove($event)">...</tbw-grid>
5372
5745
  * ```
5746
+ *
5747
+ * @deprecated Use `GridReorderColumnsDirective` from
5748
+ * `@toolbox-web/grid-angular/features/reorder-columns`. Will be removed in v2.0.0.
5373
5749
  */
5374
5750
  columnMove = output();
5375
5751
  /**
5376
- * Emitted when column visibility changes.
5752
+ * Emitted when a column is shown or hidden — either via the visibility
5753
+ * sidebar, `grid.toggleColumnVisibility(field)`, `grid.setColumnVisible(field, visible)`,
5754
+ * or `grid.showAllColumns()`.
5377
5755
  *
5378
5756
  * @example
5379
5757
  * ```html
5380
5758
  * <tbw-grid (columnVisibility)="onColumnVisibility($event)">...</tbw-grid>
5381
5759
  * ```
5760
+ *
5761
+ * @deprecated Use `GridVisibilityDirective` from
5762
+ * `@toolbox-web/grid-angular/features/visibility`. Will be removed in v2.0.0.
5382
5763
  */
5383
5764
  columnVisibility = output();
5384
5765
  /**
@@ -5397,6 +5778,9 @@ class Grid {
5397
5778
  * ```html
5398
5779
  * <tbw-grid (selectionChange)="onSelectionChange($event)">...</tbw-grid>
5399
5780
  * ```
5781
+ *
5782
+ * @deprecated Use `GridSelectionDirective` from
5783
+ * `@toolbox-web/grid-angular/features/selection`. Will be removed in v2.0.0.
5400
5784
  */
5401
5785
  selectionChange = output();
5402
5786
  /**
@@ -5406,6 +5790,9 @@ class Grid {
5406
5790
  * ```html
5407
5791
  * <tbw-grid (rowMove)="onRowMove($event)">...</tbw-grid>
5408
5792
  * ```
5793
+ *
5794
+ * @deprecated Use `GridRowDragDropDirective` from
5795
+ * `@toolbox-web/grid-angular/features/row-drag-drop`. Will be removed in v2.0.0.
5409
5796
  */
5410
5797
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5411
5798
  rowMove = output();
@@ -5416,23 +5803,35 @@ class Grid {
5416
5803
  * ```html
5417
5804
  * <tbw-grid (rowDragStart)="onRowDragStart($event)">...</tbw-grid>
5418
5805
  * ```
5806
+ *
5807
+ * @deprecated Use `GridRowDragDropDirective` from
5808
+ * `@toolbox-web/grid-angular/features/row-drag-drop`. Will be removed in v2.0.0.
5419
5809
  */
5420
5810
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5421
5811
  rowDragStart = output();
5422
5812
  /**
5423
5813
  * Emitted when a row drag ends (after drop or cancel).
5814
+ *
5815
+ * @deprecated Use `GridRowDragDropDirective` from
5816
+ * `@toolbox-web/grid-angular/features/row-drag-drop`. Will be removed in v2.0.0.
5424
5817
  */
5425
5818
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5426
5819
  rowDragEnd = output();
5427
5820
  /**
5428
5821
  * Emitted on the target grid when rows are dropped from another grid.
5429
5822
  * Cancelable via `event.preventDefault()`.
5823
+ *
5824
+ * @deprecated Use `GridRowDragDropDirective` from
5825
+ * `@toolbox-web/grid-angular/features/row-drag-drop`. Will be removed in v2.0.0.
5430
5826
  */
5431
5827
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5432
5828
  rowDrop = output();
5433
5829
  /**
5434
5830
  * Emitted on BOTH source and target grids after a successful cross-grid
5435
5831
  * row transfer.
5832
+ *
5833
+ * @deprecated Use `GridRowDragDropDirective` from
5834
+ * `@toolbox-web/grid-angular/features/row-drag-drop`. Will be removed in v2.0.0.
5436
5835
  */
5437
5836
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5438
5837
  rowTransfer = output();
@@ -5443,8 +5842,35 @@ class Grid {
5443
5842
  * ```html
5444
5843
  * <tbw-grid (groupToggle)="onGroupToggle($event)">...</tbw-grid>
5445
5844
  * ```
5845
+ *
5846
+ * @deprecated Use `GridGroupingRowsDirective` from
5847
+ * `@toolbox-web/grid-angular/features/grouping-rows`. Will be removed in v2.0.0.
5446
5848
  */
5447
5849
  groupToggle = output();
5850
+ /**
5851
+ * Emitted when a group is expanded.
5852
+ *
5853
+ * @example
5854
+ * ```html
5855
+ * <tbw-grid (groupExpand)="onGroupExpand($event)">...</tbw-grid>
5856
+ * ```
5857
+ *
5858
+ * @deprecated Use `GridGroupingRowsDirective` from
5859
+ * `@toolbox-web/grid-angular/features/grouping-rows`. Will be removed in v2.0.0.
5860
+ */
5861
+ groupExpand = output();
5862
+ /**
5863
+ * Emitted when a group is collapsed.
5864
+ *
5865
+ * @example
5866
+ * ```html
5867
+ * <tbw-grid (groupCollapse)="onGroupCollapse($event)">...</tbw-grid>
5868
+ * ```
5869
+ *
5870
+ * @deprecated Use `GridGroupingRowsDirective` from
5871
+ * `@toolbox-web/grid-angular/features/grouping-rows`. Will be removed in v2.0.0.
5872
+ */
5873
+ groupCollapse = output();
5448
5874
  /**
5449
5875
  * Emitted when a tree node is expanded.
5450
5876
  *
@@ -5452,6 +5878,9 @@ class Grid {
5452
5878
  * ```html
5453
5879
  * <tbw-grid (treeExpand)="onTreeExpand($event)">...</tbw-grid>
5454
5880
  * ```
5881
+ *
5882
+ * @deprecated Use `GridTreeDirective` from
5883
+ * `@toolbox-web/grid-angular/features/tree`. Will be removed in v2.0.0.
5455
5884
  */
5456
5885
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5457
5886
  treeExpand = output();
@@ -5462,6 +5891,9 @@ class Grid {
5462
5891
  * ```html
5463
5892
  * <tbw-grid (detailExpand)="onDetailExpand($event)">...</tbw-grid>
5464
5893
  * ```
5894
+ *
5895
+ * @deprecated Use `GridMasterDetailDirective` from
5896
+ * `@toolbox-web/grid-angular/features/master-detail`. Will be removed in v2.0.0.
5465
5897
  */
5466
5898
  detailExpand = output();
5467
5899
  /**
@@ -5471,8 +5903,23 @@ class Grid {
5471
5903
  * ```html
5472
5904
  * <tbw-grid (responsiveChange)="onResponsiveChange($event)">...</tbw-grid>
5473
5905
  * ```
5906
+ *
5907
+ * @deprecated Use `GridResponsiveDirective` from
5908
+ * `@toolbox-web/grid-angular/features/responsive`. Will be removed in v2.0.0.
5474
5909
  */
5475
5910
  responsiveChange = output();
5911
+ /**
5912
+ * Emitted when the context menu opens.
5913
+ *
5914
+ * @example
5915
+ * ```html
5916
+ * <tbw-grid (contextMenuOpen)="onContextMenuOpen($event)">...</tbw-grid>
5917
+ * ```
5918
+ *
5919
+ * @deprecated Use `GridContextMenuDirective` from
5920
+ * `@toolbox-web/grid-angular/features/context-menu`. Will be removed in v2.0.0.
5921
+ */
5922
+ contextMenuOpen = output();
5476
5923
  /**
5477
5924
  * Emitted when cells are copied to clipboard.
5478
5925
  *
@@ -5480,6 +5927,9 @@ class Grid {
5480
5927
  * ```html
5481
5928
  * <tbw-grid (copy)="onCopy($event)">...</tbw-grid>
5482
5929
  * ```
5930
+ *
5931
+ * @deprecated Use `GridClipboardDirective` from
5932
+ * `@toolbox-web/grid-angular/features/clipboard`. Will be removed in v2.0.0.
5483
5933
  */
5484
5934
  copy = output();
5485
5935
  /**
@@ -5489,17 +5939,35 @@ class Grid {
5489
5939
  * ```html
5490
5940
  * <tbw-grid (paste)="onPaste($event)">...</tbw-grid>
5491
5941
  * ```
5942
+ *
5943
+ * @deprecated Use `GridClipboardDirective` from
5944
+ * `@toolbox-web/grid-angular/features/clipboard`. Will be removed in v2.0.0.
5492
5945
  */
5493
5946
  paste = output();
5494
5947
  /**
5495
- * Emitted when undo/redo is performed.
5948
+ * Emitted when an undo action is performed.
5949
+ *
5950
+ * @example
5951
+ * ```html
5952
+ * <tbw-grid (undo)="onUndo($event)">...</tbw-grid>
5953
+ * ```
5954
+ *
5955
+ * @deprecated Use `GridUndoRedoDirective` from
5956
+ * `@toolbox-web/grid-angular/features/undo-redo`. Will be removed in v2.0.0.
5957
+ */
5958
+ undo = output();
5959
+ /**
5960
+ * Emitted when a redo action is performed.
5496
5961
  *
5497
5962
  * @example
5498
5963
  * ```html
5499
- * <tbw-grid (undoRedoAction)="onUndoRedo($event)">...</tbw-grid>
5964
+ * <tbw-grid (redo)="onRedo($event)">...</tbw-grid>
5500
5965
  * ```
5966
+ *
5967
+ * @deprecated Use `GridUndoRedoDirective` from
5968
+ * `@toolbox-web/grid-angular/features/undo-redo`. Will be removed in v2.0.0.
5501
5969
  */
5502
- undoRedoAction = output();
5970
+ redo = output();
5503
5971
  /**
5504
5972
  * Emitted when export completes.
5505
5973
  *
@@ -5507,6 +5975,9 @@ class Grid {
5507
5975
  * ```html
5508
5976
  * <tbw-grid (exportComplete)="onExportComplete($event)">...</tbw-grid>
5509
5977
  * ```
5978
+ *
5979
+ * @deprecated Use `GridExportDirective` from
5980
+ * `@toolbox-web/grid-angular/features/export`. Will be removed in v2.0.0.
5510
5981
  */
5511
5982
  exportComplete = output();
5512
5983
  /**
@@ -5516,6 +5987,9 @@ class Grid {
5516
5987
  * ```html
5517
5988
  * <tbw-grid (printStart)="onPrintStart($event)">...</tbw-grid>
5518
5989
  * ```
5990
+ *
5991
+ * @deprecated Use `GridPrintDirective` from
5992
+ * `@toolbox-web/grid-angular/features/print`. Will be removed in v2.0.0.
5519
5993
  */
5520
5994
  printStart = output();
5521
5995
  /**
@@ -5525,6 +5999,9 @@ class Grid {
5525
5999
  * ```html
5526
6000
  * <tbw-grid (printComplete)="onPrintComplete($event)">...</tbw-grid>
5527
6001
  * ```
6002
+ *
6003
+ * @deprecated Use `GridPrintDirective` from
6004
+ * `@toolbox-web/grid-angular/features/print`. Will be removed in v2.0.0.
5528
6005
  */
5529
6006
  printComplete = output();
5530
6007
  /**
@@ -5543,18 +6020,31 @@ class Grid {
5543
6020
  * ```
5544
6021
  */
5545
6022
  tbwScroll = output();
5546
- // Map of output names to event names for automatic wiring
6023
+ // Map of output names to event names for automatic wiring.
6024
+ //
6025
+ // The `satisfies` clause enforces compile-time sync against
6026
+ // `DataGridEventMap`: every value must be a real event name (typos and
6027
+ // stale entries pointing at non-existent events fail to compile).
6028
+ // Plugin event augmentations of `DataGridEventMap` flow through
6029
+ // automatically via the `/all` import.
5547
6030
  eventOutputMap = {
5548
6031
  cellClick: 'cell-click',
5549
6032
  rowClick: 'row-click',
5550
6033
  cellActivate: 'cell-activate',
5551
6034
  cellChange: 'cell-change',
5552
6035
  cellCommit: 'cell-commit',
6036
+ cellCancel: 'cell-cancel',
5553
6037
  rowCommit: 'row-commit',
5554
6038
  changedRowsReset: 'changed-rows-reset',
6039
+ editOpen: 'edit-open',
6040
+ beforeEditClose: 'before-edit-close',
6041
+ editClose: 'edit-close',
6042
+ dirtyChange: 'dirty-change',
6043
+ dataChange: 'data-change',
5555
6044
  sortChange: 'sort-change',
5556
6045
  filterChange: 'filter-change',
5557
6046
  columnResize: 'column-resize',
6047
+ columnResizeReset: 'column-resize-reset',
5558
6048
  columnMove: 'column-move',
5559
6049
  columnVisibility: 'column-visibility',
5560
6050
  columnStateChange: 'column-state-change',
@@ -5565,17 +6055,39 @@ class Grid {
5565
6055
  rowDrop: 'row-drop',
5566
6056
  rowTransfer: 'row-transfer',
5567
6057
  groupToggle: 'group-toggle',
6058
+ groupExpand: 'group-expand',
6059
+ groupCollapse: 'group-collapse',
5568
6060
  treeExpand: 'tree-expand',
5569
6061
  detailExpand: 'detail-expand',
5570
6062
  responsiveChange: 'responsive-change',
6063
+ contextMenuOpen: 'context-menu-open',
5571
6064
  copy: 'copy',
5572
6065
  paste: 'paste',
5573
- undoRedoAction: 'undo-redo',
6066
+ undo: 'undo',
6067
+ redo: 'redo',
5574
6068
  exportComplete: 'export-complete',
5575
6069
  printStart: 'print-start',
5576
6070
  printComplete: 'print-complete',
5577
6071
  tbwScroll: 'tbw-scroll',
5578
6072
  };
6073
+ // ─────────────────────────────────────────────────────────────────────────
6074
+ // Forward-only event coverage guard.
6075
+ //
6076
+ // Mirrors the React adapter's `_AssertFeaturePropsCoverCore` pattern. If a
6077
+ // new event is added to core's `DataGridEventMap` (via plugin module
6078
+ // augmentation in `/all`) but no `eventOutputMap` entry covers it, this
6079
+ // type fails to evaluate to `true` and the build breaks. Adapter consumers
6080
+ // never see a silently-dropped event.
6081
+ //
6082
+ // Reverse direction (extra `eventOutputMap` entries pointing at non-existent
6083
+ // events) is already enforced by the `satisfies` clause above.
6084
+ //
6085
+ // To consciously omit an event from the Angular surface, add it to the
6086
+ // `IntentionallyOmittedEvents` union below with a comment explaining why.
6087
+ // ─────────────────────────────────────────────────────────────────────────
6088
+ /** Events deliberately not exposed as Angular outputs. Keep empty unless documented. */
6089
+ _intentionallyOmittedEvents;
6090
+ _assertEventOutputMapCoversCore;
5579
6091
  // Store event listeners for cleanup
5580
6092
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5581
6093
  eventListeners = new Map();
@@ -5594,10 +6106,18 @@ class Grid {
5594
6106
  }
5595
6107
  /**
5596
6108
  * Sets up event listeners for all outputs using the eventOutputMap.
6109
+ *
6110
+ * Hybrid v1.x / v2 ownership: events claimed by an attribute-selector
6111
+ * feature directive (via `claimEvent` in `feature-claims.ts`) are skipped
6112
+ * here so the directive's own `output()` is the sole emitter. Without
6113
+ * this skip both this directive's deprecated output and the directive's
6114
+ * new output would fire for the same DOM event.
5597
6115
  */
5598
6116
  setupEventListeners(grid) {
5599
6117
  // Wire up all event listeners
5600
6118
  for (const [outputName, eventName] of Object.entries(this.eventOutputMap)) {
6119
+ if (isEventClaimed(grid, eventName))
6120
+ continue;
5601
6121
  const listener = (e) => {
5602
6122
  const detail = e.detail;
5603
6123
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -5610,57 +6130,62 @@ class Grid {
5610
6130
  /**
5611
6131
  * Creates plugins from feature inputs.
5612
6132
  * Uses the feature registry to allow tree-shaking - only imported features are bundled.
6133
+ * Per-feature config bridging (e.g. converting Angular component classes inside
6134
+ * `groupingColumns` / `groupingRows` / `pinnedRows` configs to renderer functions)
6135
+ * runs via `getFeatureConfigPreprocessor`, populated by feature secondary entries.
6136
+ *
6137
+ * Hybrid v1.x / v2 ownership: when an attribute-selector feature directive
6138
+ * (e.g. `GridFilteringDirective`) is present on the same `<tbw-grid>`
6139
+ * element it claims its feature in `feature-claims.ts`. We then read the
6140
+ * claim's config getter — which transitively reads the directive's input
6141
+ * signal, establishing reactive dependency tracking — instead of the
6142
+ * deprecated input on this directive. This keeps the existing `[filtering]`
6143
+ * binding working when used directly on `<tbw-grid>` (no directive, no
6144
+ * claim) while letting the directive own the binding when imported.
6145
+ *
5613
6146
  * Returns the array of created plugins (doesn't modify grid).
5614
6147
  */
5615
6148
  createFeaturePlugins() {
5616
6149
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5617
6150
  const plugins = [];
6151
+ const adapter = this.adapter;
6152
+ const grid = this.elementRef.nativeElement;
5618
6153
  // Helper to add plugin if feature is registered
5619
- const addPlugin = (name, config) => {
6154
+ const addPlugin = (name, ownInput) => {
6155
+ // Directive-owned config wins. Reading the claim's getter inside this
6156
+ // effect registers the directive's input signal as a dependency, so
6157
+ // changes to e.g. `[filtering]` on the directive re-trigger this effect.
6158
+ const claim = getFeatureClaim(grid, name);
6159
+ const config = claim ? claim() : ownInput;
5620
6160
  if (config === undefined || config === null || config === false)
5621
6161
  return;
5622
- const plugin = createPluginFromFeature(name, config);
6162
+ // Apply per-feature config preprocessor (registered by feature secondary entries)
6163
+ // to bridge Angular component classes embedded in the config before instantiation.
6164
+ let finalConfig = config;
6165
+ if (adapter && config !== true && typeof config === 'object') {
6166
+ const preprocess = getFeatureConfigPreprocessor(name);
6167
+ if (preprocess)
6168
+ finalConfig = preprocess(config, adapter);
6169
+ }
6170
+ const plugin = createPluginFromFeature(name, finalConfig);
5623
6171
  if (plugin)
5624
6172
  plugins.push(plugin);
5625
6173
  };
5626
- // Add plugins for each feature input
5627
6174
  addPlugin('selection', this.selection());
5628
6175
  addPlugin('editing', this.editing());
5629
6176
  addPlugin('clipboard', this.clipboard());
5630
6177
  addPlugin('contextMenu', this.contextMenu());
5631
- // multiSort is the primary input
5632
6178
  addPlugin('multiSort', this.multiSort());
5633
6179
  addPlugin('filtering', this.filtering());
5634
6180
  addPlugin('reorderColumns', this.reorderColumns());
5635
6181
  addPlugin('visibility', this.visibility());
5636
6182
  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
- }
6183
+ addPlugin('groupingColumns', this.groupingColumns());
5645
6184
  addPlugin('columnVirtualization', this.columnVirtualization());
5646
6185
  addPlugin('reorderRows', this.reorderRows());
5647
6186
  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
- }
6187
+ addPlugin('groupingRows', this.groupingRows());
6188
+ addPlugin('pinnedRows', this.pinnedRows());
5664
6189
  addPlugin('tree', this.tree());
5665
6190
  addPlugin('masterDetail', this.masterDetail());
5666
6191
  addPlugin('responsive', this.responsive());
@@ -5680,10 +6205,13 @@ class Grid {
5680
6205
  // Use setTimeout to ensure Angular effects have run (template registration)
5681
6206
  setTimeout(() => {
5682
6207
  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);
6208
+ // Run feature-registered template bridges. Each bridge wires a specific
6209
+ // light-DOM slot element (<tbw-grid-detail>, <tbw-grid-responsive-card>, ...)
6210
+ // to its plugin. Bridges are registered by feature secondary entries
6211
+ // (e.g. `import '@toolbox-web/grid-angular/features/master-detail';`).
6212
+ if (this.adapter) {
6213
+ runTemplateBridges({ grid, adapter: this.adapter });
6214
+ }
5687
6215
  // Refresh shell header to pick up tool panel templates
5688
6216
  // This allows Angular templates to be used in tool panels
5689
6217
  if (typeof grid.refreshShellHeader === 'function') {
@@ -5707,85 +6235,6 @@ class Grid {
5707
6235
  grid.registerStyles?.('angular-custom-styles', styles);
5708
6236
  });
5709
6237
  }
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
6238
  ngOnDestroy() {
5790
6239
  const grid = this.elementRef.nativeElement;
5791
6240
  // Cleanup all event listeners
@@ -5806,12 +6255,60 @@ class Grid {
5806
6255
  }
5807
6256
  }
5808
6257
  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 });
6258
+ 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
6259
  }
5811
6260
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: Grid, decorators: [{
5812
6261
  type: Directive,
5813
6262
  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"] }] } });
6263
+ }], 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"] }] } });
6264
+
6265
+ /**
6266
+ * Combined provider helper for grid type defaults and icons.
6267
+ *
6268
+ * Convenience function that combines `provideGridTypeDefaults` and
6269
+ * `provideGridIcons` into a single call for application bootstrap.
6270
+ *
6271
+ * @example
6272
+ * ```typescript
6273
+ * // app.config.ts
6274
+ * import { ApplicationConfig } from '@angular/core';
6275
+ * import { provideGrid } from '@toolbox-web/grid-angular';
6276
+ *
6277
+ * export const appConfig: ApplicationConfig = {
6278
+ * providers: [
6279
+ * provideGrid({
6280
+ * typeDefaults: {
6281
+ * country: { renderer: CountryCellComponent },
6282
+ * },
6283
+ * icons: {
6284
+ * sortAsc: '↑',
6285
+ * sortDesc: '↓',
6286
+ * },
6287
+ * }),
6288
+ * ],
6289
+ * };
6290
+ * ```
6291
+ */
6292
+ /**
6293
+ * Combined provider for grid type defaults and icons.
6294
+ *
6295
+ * Returns environment providers that can be added to your `ApplicationConfig`
6296
+ * `providers` array. Either field is optional — only the registries you
6297
+ * supply are wired up.
6298
+ *
6299
+ * Equivalent to calling `provideGridTypeDefaults(options.typeDefaults)` and
6300
+ * `provideGridIcons(options.icons)` separately.
6301
+ */
6302
+ function provideGrid(options = {}) {
6303
+ const providers = [];
6304
+ if (options.typeDefaults) {
6305
+ providers.push(provideGridTypeDefaults(options.typeDefaults));
6306
+ }
6307
+ if (options.icons) {
6308
+ providers.push(provideGridIcons(options.icons));
6309
+ }
6310
+ return makeEnvironmentProviders(providers);
6311
+ }
5815
6312
 
5816
6313
  /**
5817
6314
  * @packageDocumentation
@@ -5825,5 +6322,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
5825
6322
  * Generated bundle index. Do not edit.
5826
6323
  */
5827
6324
 
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 };
6325
+ 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
6326
  //# sourceMappingURL=toolbox-web-grid-angular.mjs.map