@toolbox-web/grid-angular 1.3.0 → 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 +1238 -682
  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 +865 -107
  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
  */
@@ -1554,6 +1587,17 @@ class GridAdapter {
1554
1587
  editorViewRefs = [];
1555
1588
  /** Editor-specific component refs tracked separately for per-cell cleanup via releaseCell. */
1556
1589
  editorComponentRefs = [];
1590
+ /**
1591
+ * Per-editor mount-hook teardown functions, keyed by editor host element.
1592
+ *
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).
1599
+ */
1600
+ editorMountTeardowns = new Map();
1557
1601
  typeRegistry = null;
1558
1602
  constructor(injector, appRef, viewContainerRef) {
1559
1603
  this.injector = injector;
@@ -1641,9 +1685,13 @@ class GridAdapter {
1641
1685
  if (config.editor && isComponentClass(config.editor)) {
1642
1686
  processedConfig.editor = this.createComponentEditor(config.editor);
1643
1687
  }
1644
- // 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.
1645
1691
  if (config.filterPanelRenderer && isComponentClass(config.filterPanelRenderer)) {
1646
- processedConfig.filterPanelRenderer = this.createComponentFilterPanelRenderer(config.filterPanelRenderer);
1692
+ const wrapped = getFilterPanelTypeDefaultBridge()?.(config.filterPanelRenderer, this);
1693
+ if (wrapped)
1694
+ processedConfig.filterPanelRenderer = wrapped;
1647
1695
  }
1648
1696
  processed[type] = processedConfig;
1649
1697
  }
@@ -1828,6 +1876,7 @@ class GridAdapter {
1828
1876
  const container = document.createElement('span');
1829
1877
  container.style.display = 'contents';
1830
1878
  syncRootNodes(viewRef, container);
1879
+ this.runEditorMountHooks(container);
1831
1880
  // Auto-wire: Listen for commit/cancel events on the rendered component.
1832
1881
  // This allows components to just emit (commit) and (cancel) without
1833
1882
  // requiring explicit template bindings like (commit)="onCommit($event)".
@@ -1853,101 +1902,40 @@ class GridAdapter {
1853
1902
  };
1854
1903
  }
1855
1904
  /**
1856
- * Creates a detail renderer function for MasterDetailPlugin.
1857
- * 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.
1858
1909
  */
1859
1910
  createDetailRenderer(gridElement) {
1860
- const template = getDetailTemplate(gridElement);
1861
- if (!template) {
1862
- return undefined;
1863
- }
1864
- return (row) => {
1865
- // Create the context for the template
1866
- const context = {
1867
- $implicit: row,
1868
- row: row,
1869
- };
1870
- // Create embedded view from template
1871
- const viewRef = this.viewContainerRef.createEmbeddedView(template, context);
1872
- this.viewRefs.push(viewRef);
1873
- // Trigger change detection
1874
- viewRef.detectChanges();
1875
- // Create a container for the root nodes
1876
- const container = document.createElement('div');
1877
- viewRef.rootNodes.forEach((node) => container.appendChild(node));
1878
- return container;
1879
- };
1911
+ return getDetailRendererBridge()?.(gridElement, this);
1880
1912
  }
1881
1913
  /**
1882
- * Framework adapter hook called by MasterDetailPlugin during attach().
1883
- * Parses the <tbw-grid-detail> element and returns an Angular template-based renderer.
1884
- *
1885
- * This enables MasterDetailPlugin to automatically use Angular templates
1886
- * 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).
1887
1916
  */
1888
1917
  parseDetailElement(detailElement) {
1889
- // Get the template from the registry for this detail element
1890
- const template = getDetailTemplate(detailElement.closest('tbw-grid'));
1891
- if (!template) {
1918
+ const gridElement = detailElement.closest('tbw-grid');
1919
+ if (!gridElement)
1892
1920
  return undefined;
1893
- }
1894
- // Return a renderer function that creates embedded views
1895
- // Note: rowIndex is part of the MasterDetailPlugin detailRenderer signature but not needed here
1896
- return (row) => {
1897
- const context = {
1898
- $implicit: row,
1899
- row: row,
1900
- };
1901
- const viewRef = this.viewContainerRef.createEmbeddedView(template, context);
1902
- this.viewRefs.push(viewRef);
1903
- viewRef.detectChanges();
1904
- const container = document.createElement('div');
1905
- viewRef.rootNodes.forEach((node) => container.appendChild(node));
1906
- return container;
1907
- };
1921
+ return getDetailRendererBridge()?.(gridElement, this);
1908
1922
  }
1909
1923
  /**
1910
- * Creates a responsive card renderer function for ResponsivePlugin.
1911
- * Renders Angular templates for card layout in responsive mode.
1912
- *
1913
- * @param gridElement - The grid element to look up the template for
1914
- * @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`.
1915
1926
  */
1916
1927
  createResponsiveCardRenderer(gridElement) {
1917
- const template = getResponsiveCardTemplate(gridElement);
1918
- if (!template) {
1919
- return undefined;
1920
- }
1921
- return (row, rowIndex) => {
1922
- // Create the context for the template
1923
- const context = {
1924
- $implicit: row,
1925
- row: row,
1926
- index: rowIndex,
1927
- };
1928
- // Create embedded view from template
1929
- const viewRef = this.viewContainerRef.createEmbeddedView(template, context);
1930
- this.viewRefs.push(viewRef);
1931
- // Trigger change detection
1932
- viewRef.detectChanges();
1933
- // Create a container for the root nodes
1934
- const container = document.createElement('div');
1935
- viewRef.rootNodes.forEach((node) => container.appendChild(node));
1936
- return container;
1937
- };
1928
+ return getResponsiveCardRendererBridge()?.(gridElement, this);
1938
1929
  }
1939
1930
  /**
1940
- * FrameworkAdapter hook called by ResponsivePlugin during attach().
1941
- * Parses the `<tbw-grid-responsive-card>` element and delegates to
1942
- * {@link createResponsiveCardRenderer}. Required for parity with the Vue
1943
- * adapter so ResponsivePlugin's standard lookup path works for Angular
1944
- * 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).
1945
1933
  */
1946
1934
  parseResponsiveCardElement(cardElement) {
1947
1935
  const gridElement = cardElement.closest('tbw-grid');
1948
1936
  if (!gridElement)
1949
1937
  return undefined;
1950
- return this.createResponsiveCardRenderer(gridElement);
1938
+ return getResponsiveCardRendererBridge()?.(gridElement, this);
1951
1939
  }
1952
1940
  /**
1953
1941
  * Creates a tool panel renderer from a light DOM element.
@@ -2031,8 +2019,11 @@ class GridAdapter {
2031
2019
  typeDefault.editor = this.createComponentEditor(config.editor);
2032
2020
  }
2033
2021
  // Create filterPanelRenderer function that instantiates the Angular component
2022
+ // via the filtering feature bridge. Drop silently if the feature is not imported.
2034
2023
  if (config.filterPanelRenderer && isComponentClass(config.filterPanelRenderer)) {
2035
- typeDefault.filterPanelRenderer = this.createComponentFilterPanelRenderer(config.filterPanelRenderer);
2024
+ const wrapped = getFilterPanelTypeDefaultBridge()?.(config.filterPanelRenderer, this);
2025
+ if (wrapped)
2026
+ typeDefault.filterPanelRenderer = wrapped;
2036
2027
  }
2037
2028
  else if (config.filterPanelRenderer) {
2038
2029
  typeDefault.filterPanelRenderer = config.filterPanelRenderer;
@@ -2040,47 +2031,56 @@ class GridAdapter {
2040
2031
  return typeDefault;
2041
2032
  }
2042
2033
  /**
2043
- * Creates and mounts an Angular component dynamically.
2044
- * 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()`.
2045
2047
  * @internal
2046
2048
  */
2047
- mountComponent(componentClass, inputs, isEditor = false) {
2048
- // Create a host element for the component
2049
- const hostElement = document.createElement('span');
2050
- hostElement.style.display = 'contents';
2051
- // Create the component dynamically
2052
- const componentRef = createComponent(componentClass, {
2053
- environmentInjector: this.injector,
2054
- hostElement,
2055
- });
2056
- // Set inputs - components should have value, row, column inputs
2057
- this.setComponentInputs(componentRef, inputs);
2058
- // Attach to app for change detection
2059
- this.appRef.attachView(componentRef.hostView);
2060
- // Track in editor-specific array for per-cell cleanup, or general array for renderers
2061
- if (isEditor) {
2062
- this.editorComponentRefs.push(componentRef);
2063
- }
2064
- else {
2065
- this.componentRefs.push(componentRef);
2066
- }
2067
- // Trigger change detection
2068
- componentRef.changeDetectorRef.detectChanges();
2069
- 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
+ };
2070
2063
  }
2071
2064
  /**
2072
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.
2073
2069
  * @internal
2074
2070
  */
2075
2071
  createComponentRenderer(componentClass) {
2076
- // Cell cache for component-based renderers - maps cell element to its component ref
2077
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
+ }));
2078
2078
  return (ctx) => {
2079
2079
  const cellEl = ctx.cellEl;
2080
2080
  if (cellEl) {
2081
2081
  const cached = cellCache.get(cellEl);
2082
2082
  if (cached) {
2083
- // Reuse existing component - just update inputs
2083
+ // Reuse existing component - just update inputs.
2084
2084
  this.setComponentInputs(cached.componentRef, {
2085
2085
  value: ctx.value,
2086
2086
  row: ctx.row,
@@ -2090,30 +2090,25 @@ class GridAdapter {
2090
2090
  return cached.hostElement;
2091
2091
  }
2092
2092
  }
2093
- const { hostElement, componentRef } = this.mountComponent(componentClass, {
2094
- value: ctx.value,
2095
- row: ctx.row,
2096
- column: ctx.column,
2097
- });
2098
- // Cache for reuse on scroll recycles
2099
- if (cellEl) {
2093
+ const { hostElement, componentRef } = mount(ctx);
2094
+ if (cellEl)
2100
2095
  cellCache.set(cellEl, { componentRef, hostElement });
2101
- }
2102
2096
  return hostElement;
2103
2097
  };
2104
2098
  }
2105
2099
  /**
2106
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.
2107
2104
  * @internal
2108
2105
  */
2109
2106
  createComponentEditor(componentClass) {
2107
+ const mount = this.mountComponentRenderer(componentClass, (ctx) => ({ value: ctx.value, row: ctx.row, column: ctx.column }), 'editor');
2110
2108
  return (ctx) => {
2111
- const { hostElement, componentRef } = this.mountComponent(componentClass, {
2112
- value: ctx.value,
2113
- row: ctx.row,
2114
- column: ctx.column,
2115
- }, true);
2109
+ const { hostElement, componentRef } = mount(ctx);
2116
2110
  wireEditorCallbacks(hostElement, componentRef.instance, (value) => ctx.commit(value), () => ctx.cancel());
2111
+ this.runEditorMountHooks(hostElement);
2117
2112
  // Auto-update editor when value changes externally (e.g., via updateRow cascade
2118
2113
  // or Escape-revert). Update the component input and run detectChanges() —
2119
2114
  // the component's own template handles rendering regardless of editor type.
@@ -2141,176 +2136,27 @@ class GridAdapter {
2141
2136
  * @internal
2142
2137
  */
2143
2138
  createComponentHeaderRenderer(componentClass) {
2144
- return (ctx) => {
2145
- const hostElement = document.createElement('span');
2146
- hostElement.style.display = 'contents';
2147
- const componentRef = createComponent(componentClass, {
2148
- environmentInjector: this.injector,
2149
- hostElement,
2150
- });
2151
- this.setComponentInputs(componentRef, {
2152
- column: ctx.column,
2153
- value: ctx.value,
2154
- sortState: ctx.sortState,
2155
- filterActive: ctx.filterActive,
2156
- renderSortIcon: ctx.renderSortIcon,
2157
- renderFilterButton: ctx.renderFilterButton,
2158
- });
2159
- this.appRef.attachView(componentRef.hostView);
2160
- this.componentRefs.push(componentRef);
2161
- componentRef.changeDetectorRef.detectChanges();
2162
- return hostElement;
2163
- };
2164
- }
2165
- /**
2166
- * Creates a header label renderer function from an Angular component class.
2167
- * Mounts the component with label context (column, value).
2168
- * @internal
2169
- */
2170
- createComponentHeaderLabelRenderer(componentClass) {
2171
- return (ctx) => {
2172
- const hostElement = document.createElement('span');
2173
- hostElement.style.display = 'contents';
2174
- const componentRef = createComponent(componentClass, {
2175
- environmentInjector: this.injector,
2176
- hostElement,
2177
- });
2178
- this.setComponentInputs(componentRef, {
2179
- column: ctx.column,
2180
- value: ctx.value,
2181
- });
2182
- this.appRef.attachView(componentRef.hostView);
2183
- this.componentRefs.push(componentRef);
2184
- componentRef.changeDetectorRef.detectChanges();
2185
- return hostElement;
2186
- };
2187
- }
2188
- /**
2189
- * Creates a group header renderer function from an Angular component class.
2190
- *
2191
- * The component should accept group header inputs (id, label, columns, firstIndex, isImplicit).
2192
- * Returns the host element directly (groupHeaderRenderer returns an element, not void).
2193
- * @internal
2194
- */
2195
- createComponentGroupHeaderRenderer(componentClass) {
2196
- return (params) => {
2197
- const hostElement = document.createElement('span');
2198
- hostElement.style.display = 'contents';
2199
- const componentRef = createComponent(componentClass, {
2200
- environmentInjector: this.injector,
2201
- hostElement,
2202
- });
2203
- this.setComponentInputs(componentRef, {
2204
- id: params.id,
2205
- label: params.label,
2206
- columns: params.columns,
2207
- firstIndex: params.firstIndex,
2208
- isImplicit: params.isImplicit,
2209
- });
2210
- this.appRef.attachView(componentRef.hostView);
2211
- this.componentRefs.push(componentRef);
2212
- componentRef.changeDetectorRef.detectChanges();
2213
- return hostElement;
2214
- };
2215
- }
2216
- /**
2217
- * Processes a GroupingColumnsConfig, converting component class references
2218
- * to actual renderer functions.
2219
- *
2220
- * @param config - Angular grouping columns configuration with possible component class references
2221
- * @returns Processed GroupingColumnsConfig with actual renderer functions
2222
- */
2223
- processGroupingColumnsConfig(config) {
2224
- const processed = { ...config };
2225
- let changed = false;
2226
- // Bridge top-level groupHeaderRenderer component class
2227
- if (config.groupHeaderRenderer && isComponentClass(config.groupHeaderRenderer)) {
2228
- processed.groupHeaderRenderer = this.createComponentGroupHeaderRenderer(config.groupHeaderRenderer);
2229
- changed = true;
2230
- }
2231
- // Bridge per-group renderer component classes inside columnGroups
2232
- if (Array.isArray(config.columnGroups)) {
2233
- const mappedGroups = config.columnGroups.map((def) => {
2234
- if (def.renderer && isComponentClass(def.renderer)) {
2235
- changed = true;
2236
- return { ...def, renderer: this.createComponentGroupHeaderRenderer(def.renderer) };
2237
- }
2238
- return def;
2239
- });
2240
- if (changed)
2241
- processed.columnGroups = mappedGroups;
2242
- }
2243
- return changed ? processed : config;
2244
- }
2245
- /**
2246
- * Processes a GroupingRowsConfig, converting component class references
2247
- * to actual renderer functions.
2248
- *
2249
- * @param config - Angular grouping rows configuration with possible component class references
2250
- * @returns Processed GroupingRowsConfig with actual renderer functions
2251
- */
2252
- processGroupingRowsConfig(config) {
2253
- if (config.groupRowRenderer && isComponentClass(config.groupRowRenderer)) {
2254
- return {
2255
- ...config,
2256
- groupRowRenderer: this.createComponentGroupRowRenderer(config.groupRowRenderer),
2257
- };
2258
- }
2259
- return config;
2260
- }
2261
- /**
2262
- * Processes a PinnedRowsConfig, converting component class references
2263
- * in `customPanels[].render` to actual renderer functions.
2264
- *
2265
- * @param config - Angular pinned rows configuration with possible component class references
2266
- * @returns Processed PinnedRowsConfig with actual renderer functions
2267
- */
2268
- processPinnedRowsConfig(config) {
2269
- if (!Array.isArray(config.customPanels))
2270
- return config;
2271
- const hasComponentRender = config.customPanels.some((panel) => isComponentClass(panel.render));
2272
- if (!hasComponentRender)
2273
- return config;
2274
- return {
2275
- ...config,
2276
- customPanels: config.customPanels.map((panel) => {
2277
- if (!isComponentClass(panel.render))
2278
- return panel;
2279
- return {
2280
- ...panel,
2281
- render: this.createComponentPinnedRowsPanelRenderer(panel.render),
2282
- };
2283
- }),
2284
- };
2285
- }
2286
- /**
2287
- * Creates a pinned rows panel renderer function from an Angular component class.
2288
- *
2289
- * The component should accept inputs from PinnedRowsContext (totalRows, filteredRows,
2290
- * selectedRows, columns, rows, grid).
2291
- * @internal
2292
- */
2293
- createComponentPinnedRowsPanelRenderer(componentClass) {
2294
- return (ctx) => {
2295
- const hostElement = document.createElement('span');
2296
- hostElement.style.display = 'contents';
2297
- const componentRef = createComponent(componentClass, {
2298
- environmentInjector: this.injector,
2299
- hostElement,
2300
- });
2301
- this.setComponentInputs(componentRef, {
2302
- totalRows: ctx.totalRows,
2303
- filteredRows: ctx.filteredRows,
2304
- selectedRows: ctx.selectedRows,
2305
- columns: ctx.columns,
2306
- rows: ctx.rows,
2307
- grid: ctx.grid,
2308
- });
2309
- this.appRef.attachView(componentRef.hostView);
2310
- this.componentRefs.push(componentRef);
2311
- componentRef.changeDetectorRef.detectChanges();
2312
- return hostElement;
2313
- };
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;
2314
2160
  }
2315
2161
  /**
2316
2162
  * Creates a loading renderer function from an Angular component class.
@@ -2319,78 +2165,59 @@ class GridAdapter {
2319
2165
  * @internal
2320
2166
  */
2321
2167
  createComponentLoadingRenderer(componentClass) {
2322
- return (ctx) => {
2323
- const hostElement = document.createElement('span');
2324
- hostElement.style.display = 'contents';
2325
- const componentRef = createComponent(componentClass, {
2326
- environmentInjector: this.injector,
2327
- hostElement,
2328
- });
2329
- this.setComponentInputs(componentRef, {
2330
- size: ctx.size,
2331
- });
2332
- this.appRef.attachView(componentRef.hostView);
2333
- this.componentRefs.push(componentRef);
2334
- componentRef.changeDetectorRef.detectChanges();
2335
- return hostElement;
2336
- };
2168
+ const mount = this.mountComponentRenderer(componentClass, (ctx) => ({ size: ctx.size }));
2169
+ return (ctx) => mount(ctx).hostElement;
2337
2170
  }
2338
2171
  /**
2339
- * Creates a group row renderer function from an Angular component class.
2340
- *
2341
- * The component should accept group row inputs (key, value, depth, rows, expanded, toggleExpand).
2342
- * 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`.
2343
2177
  * @internal
2344
2178
  */
2345
- createComponentGroupRowRenderer(componentClass) {
2346
- return (params) => {
2347
- const hostElement = document.createElement('span');
2348
- hostElement.style.display = 'contents';
2349
- const componentRef = createComponent(componentClass, {
2350
- environmentInjector: this.injector,
2351
- hostElement,
2352
- });
2353
- this.setComponentInputs(componentRef, {
2354
- key: params.key,
2355
- value: params.value,
2356
- depth: params.depth,
2357
- rows: params.rows,
2358
- expanded: params.expanded,
2359
- toggleExpand: params.toggleExpand,
2360
- });
2361
- this.appRef.attachView(componentRef.hostView);
2362
- this.componentRefs.push(componentRef);
2363
- componentRef.changeDetectorRef.detectChanges();
2364
- return hostElement;
2365
- };
2179
+ createTrackedEmbeddedView(template, context) {
2180
+ const viewRef = this.viewContainerRef.createEmbeddedView(template, context);
2181
+ this.viewRefs.push(viewRef);
2182
+ viewRef.detectChanges();
2183
+ return viewRef;
2366
2184
  }
2367
2185
  /**
2368
- * Creates a filter panel renderer function from an Angular component class.
2369
- *
2370
- * The component must implement `FilterPanel` (i.e., have a `params` input).
2371
- * 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.
2372
2212
  * @internal
2373
2213
  */
2374
- createComponentFilterPanelRenderer(componentClass) {
2375
- return (container, params) => {
2376
- const hostElement = document.createElement('span');
2377
- hostElement.style.display = 'contents';
2378
- const componentRef = createComponent(componentClass, {
2379
- environmentInjector: this.injector,
2380
- hostElement,
2381
- });
2382
- // Set params input
2383
- try {
2384
- componentRef.setInput('params', params);
2385
- }
2386
- catch {
2387
- // Input doesn't exist on component — ignore
2388
- }
2389
- this.appRef.attachView(componentRef.hostView);
2390
- this.componentRefs.push(componentRef);
2391
- componentRef.changeDetectorRef.detectChanges();
2392
- container.appendChild(hostElement);
2393
- };
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);
2394
2221
  }
2395
2222
  /**
2396
2223
  * Sets component inputs using Angular's setInput API.
@@ -2432,6 +2259,13 @@ class GridAdapter {
2432
2259
  this.editorComponentRefs.splice(i, 1);
2433
2260
  }
2434
2261
  }
2262
+ // Detach editor-mount hook teardowns for editor hosts inside this cell.
2263
+ for (const [hostEl, unsub] of this.editorMountTeardowns) {
2264
+ if (cellEl.contains(hostEl)) {
2265
+ unsub();
2266
+ this.editorMountTeardowns.delete(hostEl);
2267
+ }
2268
+ }
2435
2269
  }
2436
2270
  /**
2437
2271
  * Unmount a specific container (e.g., detail panel, tool panel).
@@ -2469,6 +2303,29 @@ class GridAdapter {
2469
2303
  this.componentRefs = [];
2470
2304
  this.editorComponentRefs.forEach((ref) => ref.destroy());
2471
2305
  this.editorComponentRefs = [];
2306
+ this.editorMountTeardowns.forEach((unsub) => unsub());
2307
+ this.editorMountTeardowns.clear();
2308
+ }
2309
+ /**
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`
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`.
2320
+ * @internal
2321
+ */
2322
+ runEditorMountHooks(host) {
2323
+ queueMicrotask(() => {
2324
+ const gridEl = host.closest('tbw-grid');
2325
+ if (!gridEl)
2326
+ return;
2327
+ this.editorMountTeardowns.set(host, notifyEditorMounted(host, gridEl));
2328
+ });
2472
2329
  }
2473
2330
  }
2474
2331
 
@@ -2729,6 +2586,12 @@ function injectGrid(selector = 'tbw-grid') {
2729
2586
  const unregisterStyles = (id) => {
2730
2587
  element()?.unregisterStyles?.(id);
2731
2588
  };
2589
+ const getPlugin = (pluginClass) => {
2590
+ return element()?.getPlugin?.(pluginClass);
2591
+ };
2592
+ const getPluginByName = ((name) => {
2593
+ return element()?.getPluginByName?.(name);
2594
+ });
2732
2595
  return {
2733
2596
  element,
2734
2597
  isReady,
@@ -2739,6 +2602,8 @@ function injectGrid(selector = 'tbw-grid') {
2739
2602
  toggleGroup,
2740
2603
  registerStyles,
2741
2604
  unregisterStyles,
2605
+ getPlugin,
2606
+ getPluginByName,
2742
2607
  };
2743
2608
  }
2744
2609
 
@@ -2805,6 +2670,11 @@ function injectGrid(selector = 'tbw-grid') {
2805
2670
  * ```
2806
2671
  *
2807
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.
2808
2678
  */
2809
2679
  class BaseFilterPanel {
2810
2680
  /**
@@ -2908,6 +2778,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
2908
2778
  *
2909
2779
  * @typeParam TRow - The row data type
2910
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.
2911
2786
  */
2912
2787
  class BaseGridEditor {
2913
2788
  elementRef = inject(ElementRef);
@@ -3210,6 +3085,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
3210
3085
  *
3211
3086
  * @typeParam TRow - The row data type
3212
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.
3213
3093
  */
3214
3094
  class BaseGridEditorCVA extends BaseGridEditor {
3215
3095
  // ============================================================================
@@ -3473,6 +3353,12 @@ let anchorCounter = 0;
3473
3353
  *
3474
3354
  * @typeParam TRow - The row data type
3475
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.
3476
3362
  */
3477
3363
  class BaseOverlayEditor extends BaseGridEditor {
3478
3364
  _elementRef = inject(ElementRef);
@@ -3951,6 +3837,102 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
3951
3837
  }]
3952
3838
  }] });
3953
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
+
3954
3936
  /**
3955
3937
  * Directive that registers `<tbw-grid-header>` as a known Angular element.
3956
3938
  *
@@ -4073,6 +4055,13 @@ function getLazyFormContext(gridElement) {
4073
4055
  *
4074
4056
  * @see GridFormArray For small datasets with full upfront validation
4075
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.
4076
4065
  */
4077
4066
  class GridLazyForm {
4078
4067
  elementRef = inject((ElementRef));
@@ -4458,15 +4447,119 @@ class GridLazyForm {
4458
4447
  }
4459
4448
  return true;
4460
4449
  }
4461
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: GridLazyForm, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4462
- 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 });
4463
4556
  }
4464
- 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: [{
4465
4558
  type: Directive,
4466
4559
  args: [{
4467
- selector: 'tbw-grid[lazyForm]',
4560
+ selector: 'tbw-grid-responsive-card',
4468
4561
  }]
4469
- }], 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 }] }] } });
4470
4563
 
4471
4564
  /**
4472
4565
  * Directive that registers `<tbw-grid-tool-buttons>` as a known Angular element.
@@ -4507,6 +4600,149 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
4507
4600
  }]
4508
4601
  }] });
4509
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
+
4510
4746
  /**
4511
4747
  * Directive that automatically registers the Angular adapter with tbw-grid elements.
4512
4748
  *
@@ -4624,13 +4860,25 @@ class Grid {
4624
4860
  if (columnsValue === undefined)
4625
4861
  return;
4626
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());
4627
4875
  // Process columns through the adapter to convert Angular component classes
4628
4876
  // (renderer/editor) to functions — the grid's columns setter does NOT call
4629
4877
  // processConfig, unlike gridConfig. Without this, raw component classes
4630
4878
  // would be invoked without `new`, causing runtime errors.
4631
4879
  const processed = this.adapter
4632
- ? columnsValue.map((col) => this.adapter.processColumn(col))
4633
- : columnsValue;
4880
+ ? normalized.map((col) => this.adapter.processColumn(col))
4881
+ : normalized;
4634
4882
  grid.columns = processed;
4635
4883
  });
4636
4884
  // Effect to sync fitMode to the grid element
@@ -4765,21 +5013,35 @@ class Grid {
4765
5013
  /**
4766
5014
  * Column configuration array.
4767
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
+ *
4768
5020
  * Shorthand for setting columns without wrapping them in a full `gridConfig`.
4769
5021
  * If both `columns` and `gridConfig.columns` are set, `columns` takes precedence
4770
5022
  * (see configuration precedence system).
4771
5023
  *
4772
5024
  * @example
4773
5025
  * ```html
4774
- * <tbw-grid [rows]="data" [columns]="[
4775
- * { field: 'id', header: 'ID', pinned: 'left', width: 80 },
4776
- * { field: 'name', header: 'Name' },
4777
- * { field: 'email', header: 'Email' }
4778
- * ]" />
5026
+ * <tbw-grid [rows]="data" [columns]="['id:number', 'name', { field: 'status', editable: true }]" />
4779
5027
  * ```
4780
5028
  */
4781
5029
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
4782
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 */ []));
4783
5045
  /**
4784
5046
  * Column sizing strategy.
4785
5047
  *
@@ -4853,6 +5115,9 @@ class Grid {
4853
5115
  * <!-- Full config object -->
4854
5116
  * <tbw-grid [selection]="{ mode: 'range', checkbox: true }" />
4855
5117
  * ```
5118
+ *
5119
+ * @deprecated Use `GridSelectionDirective` from
5120
+ * `@toolbox-web/grid-angular/features/selection`. Will be removed in v2.0.0.
4856
5121
  */
4857
5122
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
4858
5123
  selection = input(...(ngDevMode ? [undefined, { debugName: "selection" }] : /* istanbul ignore next */ []));
@@ -4877,6 +5142,9 @@ class Grid {
4877
5142
  * <!-- Full config with callbacks -->
4878
5143
  * <tbw-grid [editing]="{ editOn: 'dblclick', onBeforeEditClose: myCallback }" />
4879
5144
  * ```
5145
+ *
5146
+ * @deprecated Use `GridEditingDirective` from
5147
+ * `@toolbox-web/grid-angular/features/editing`. Will be removed in v2.0.0.
4880
5148
  */
4881
5149
  editing = input(...(ngDevMode ? [undefined, { debugName: "editing" }] : /* istanbul ignore next */ []));
4882
5150
  /**
@@ -4891,6 +5159,9 @@ class Grid {
4891
5159
  * ```html
4892
5160
  * <tbw-grid [selection]="'range'" [clipboard]="true" />
4893
5161
  * ```
5162
+ *
5163
+ * @deprecated Use `GridClipboardDirective` from
5164
+ * `@toolbox-web/grid-angular/features/clipboard`. Will be removed in v2.0.0.
4894
5165
  */
4895
5166
  clipboard = input(...(ngDevMode ? [undefined, { debugName: "clipboard" }] : /* istanbul ignore next */ []));
4896
5167
  /**
@@ -4905,6 +5176,9 @@ class Grid {
4905
5176
  * ```html
4906
5177
  * <tbw-grid [contextMenu]="true" />
4907
5178
  * ```
5179
+ *
5180
+ * @deprecated Use `GridContextMenuDirective` from
5181
+ * `@toolbox-web/grid-angular/features/context-menu`. Will be removed in v2.0.0.
4908
5182
  */
4909
5183
  contextMenu = input(...(ngDevMode ? [undefined, { debugName: "contextMenu" }] : /* istanbul ignore next */ []));
4910
5184
  /**
@@ -4929,6 +5203,9 @@ class Grid {
4929
5203
  * <!-- Full config -->
4930
5204
  * <tbw-grid [multiSort]="{ maxSortColumns: 3 }" />
4931
5205
  * ```
5206
+ *
5207
+ * @deprecated Use `GridMultiSortDirective` from
5208
+ * `@toolbox-web/grid-angular/features/multi-sort`. Will be removed in v2.0.0.
4932
5209
  */
4933
5210
  multiSort = input(...(ngDevMode ? [undefined, { debugName: "multiSort" }] : /* istanbul ignore next */ []));
4934
5211
  /**
@@ -4944,6 +5221,13 @@ class Grid {
4944
5221
  * <tbw-grid [filtering]="true" />
4945
5222
  * <tbw-grid [filtering]="{ debounceMs: 200 }" />
4946
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.
4947
5231
  */
4948
5232
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
4949
5233
  filtering = input(...(ngDevMode ? [undefined, { debugName: "filtering" }] : /* istanbul ignore next */ []));
@@ -4959,6 +5243,9 @@ class Grid {
4959
5243
  * ```html
4960
5244
  * <tbw-grid [reorderColumns]="true" />
4961
5245
  * ```
5246
+ *
5247
+ * @deprecated Use `GridReorderColumnsDirective` from
5248
+ * `@toolbox-web/grid-angular/features/reorder-columns`. Will be removed in v2.0.0.
4962
5249
  */
4963
5250
  reorderColumns = input(...(ngDevMode ? [undefined, { debugName: "reorderColumns" }] : /* istanbul ignore next */ []));
4964
5251
  /**
@@ -4973,6 +5260,9 @@ class Grid {
4973
5260
  * ```html
4974
5261
  * <tbw-grid [visibility]="true" />
4975
5262
  * ```
5263
+ *
5264
+ * @deprecated Use `GridVisibilityDirective` from
5265
+ * `@toolbox-web/grid-angular/features/visibility`. Will be removed in v2.0.0.
4976
5266
  */
4977
5267
  visibility = input(...(ngDevMode ? [undefined, { debugName: "visibility" }] : /* istanbul ignore next */ []));
4978
5268
  /**
@@ -4992,6 +5282,9 @@ class Grid {
4992
5282
  * { field: 'actions', pinned: 'right' }
4993
5283
  * ]" />
4994
5284
  * ```
5285
+ *
5286
+ * @deprecated Use `GridPinnedColumnsDirective` from
5287
+ * `@toolbox-web/grid-angular/features/pinned-columns`. Will be removed in v2.0.0.
4995
5288
  */
4996
5289
  pinnedColumns = input(...(ngDevMode ? [undefined, { debugName: "pinnedColumns" }] : /* istanbul ignore next */ []));
4997
5290
  /**
@@ -5006,6 +5299,9 @@ class Grid {
5006
5299
  * ```html
5007
5300
  * <tbw-grid [groupingColumns]="true" />
5008
5301
  * ```
5302
+ *
5303
+ * @deprecated Use `GridGroupingColumnsDirective` from
5304
+ * `@toolbox-web/grid-angular/features/grouping-columns`. Will be removed in v2.0.0.
5009
5305
  */
5010
5306
  groupingColumns = input(...(ngDevMode ? [undefined, { debugName: "groupingColumns" }] : /* istanbul ignore next */ []));
5011
5307
  /**
@@ -5020,6 +5316,9 @@ class Grid {
5020
5316
  * ```html
5021
5317
  * <tbw-grid [columnVirtualization]="true" />
5022
5318
  * ```
5319
+ *
5320
+ * @deprecated Use `GridColumnVirtualizationDirective` from
5321
+ * `@toolbox-web/grid-angular/features/column-virtualization`. Will be removed in v2.0.0.
5023
5322
  */
5024
5323
  columnVirtualization = input(...(ngDevMode ? [undefined, { debugName: "columnVirtualization" }] : /* istanbul ignore next */ []));
5025
5324
  /**
@@ -5045,6 +5344,9 @@ class Grid {
5045
5344
  * ```html
5046
5345
  * <tbw-grid [rowDragDrop]="{ dropZone: 'employees', operation: 'move' }" />
5047
5346
  * ```
5347
+ *
5348
+ * @deprecated Use `GridRowDragDropDirective` from
5349
+ * `@toolbox-web/grid-angular/features/row-drag-drop`. Will be removed in v2.0.0.
5048
5350
  */
5049
5351
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5050
5352
  rowDragDrop = input(...(ngDevMode ? [undefined, { debugName: "rowDragDrop" }] : /* istanbul ignore next */ []));
@@ -5060,6 +5362,9 @@ class Grid {
5060
5362
  * ```html
5061
5363
  * <tbw-grid [groupingRows]="{ groupBy: ['department'] }" />
5062
5364
  * ```
5365
+ *
5366
+ * @deprecated Use `GridGroupingRowsDirective` from
5367
+ * `@toolbox-web/grid-angular/features/grouping-rows`. Will be removed in v2.0.0.
5063
5368
  */
5064
5369
  groupingRows = input(...(ngDevMode ? [undefined, { debugName: "groupingRows" }] : /* istanbul ignore next */ []));
5065
5370
  /**
@@ -5074,6 +5379,9 @@ class Grid {
5074
5379
  * ```html
5075
5380
  * <tbw-grid [pinnedRows]="{ bottom: [{ type: 'aggregation' }] }" />
5076
5381
  * ```
5382
+ *
5383
+ * @deprecated Use `GridPinnedRowsDirective` from
5384
+ * `@toolbox-web/grid-angular/features/pinned-rows`. Will be removed in v2.0.0.
5077
5385
  */
5078
5386
  pinnedRows = input(...(ngDevMode ? [undefined, { debugName: "pinnedRows" }] : /* istanbul ignore next */ []));
5079
5387
  /**
@@ -5088,6 +5396,9 @@ class Grid {
5088
5396
  * ```html
5089
5397
  * <tbw-grid [tree]="{ childrenField: 'children' }" />
5090
5398
  * ```
5399
+ *
5400
+ * @deprecated Use `GridTreeDirective` from
5401
+ * `@toolbox-web/grid-angular/features/tree`. Will be removed in v2.0.0.
5091
5402
  */
5092
5403
  tree = input(...(ngDevMode ? [undefined, { debugName: "tree" }] : /* istanbul ignore next */ []));
5093
5404
  /**
@@ -5102,6 +5413,9 @@ class Grid {
5102
5413
  * ```html
5103
5414
  * <tbw-grid [masterDetail]="{ detailRenderer: detailFn }" />
5104
5415
  * ```
5416
+ *
5417
+ * @deprecated Use `GridMasterDetailDirective` from
5418
+ * `@toolbox-web/grid-angular/features/master-detail`. Will be removed in v2.0.0.
5105
5419
  */
5106
5420
  masterDetail = input(...(ngDevMode ? [undefined, { debugName: "masterDetail" }] : /* istanbul ignore next */ []));
5107
5421
  /**
@@ -5116,6 +5430,9 @@ class Grid {
5116
5430
  * ```html
5117
5431
  * <tbw-grid [responsive]="{ breakpoint: 768 }" />
5118
5432
  * ```
5433
+ *
5434
+ * @deprecated Use `GridResponsiveDirective` from
5435
+ * `@toolbox-web/grid-angular/features/responsive`. Will be removed in v2.0.0.
5119
5436
  */
5120
5437
  responsive = input(...(ngDevMode ? [undefined, { debugName: "responsive" }] : /* istanbul ignore next */ []));
5121
5438
  /**
@@ -5130,6 +5447,9 @@ class Grid {
5130
5447
  * ```html
5131
5448
  * <tbw-grid [editing]="'dblclick'" [undoRedo]="true" />
5132
5449
  * ```
5450
+ *
5451
+ * @deprecated Use `GridUndoRedoDirective` from
5452
+ * `@toolbox-web/grid-angular/features/undo-redo`. Will be removed in v2.0.0.
5133
5453
  */
5134
5454
  undoRedo = input(...(ngDevMode ? [undefined, { debugName: "undoRedo" }] : /* istanbul ignore next */ []));
5135
5455
  /**
@@ -5145,6 +5465,9 @@ class Grid {
5145
5465
  * <tbw-grid [export]="true" />
5146
5466
  * <tbw-grid [export]="{ filename: 'data.csv' }" />
5147
5467
  * ```
5468
+ *
5469
+ * @deprecated Use `GridExportDirective` from
5470
+ * `@toolbox-web/grid-angular/features/export`. Will be removed in v2.0.0.
5148
5471
  */
5149
5472
  exportFeature = input(undefined, { ...(ngDevMode ? { debugName: "exportFeature" } : /* istanbul ignore next */ {}), alias: 'export' });
5150
5473
  /**
@@ -5159,6 +5482,9 @@ class Grid {
5159
5482
  * ```html
5160
5483
  * <tbw-grid [print]="true" />
5161
5484
  * ```
5485
+ *
5486
+ * @deprecated Use `GridPrintDirective` from
5487
+ * `@toolbox-web/grid-angular/features/print`. Will be removed in v2.0.0.
5162
5488
  */
5163
5489
  print = input(...(ngDevMode ? [undefined, { debugName: "print" }] : /* istanbul ignore next */ []));
5164
5490
  /**
@@ -5173,6 +5499,9 @@ class Grid {
5173
5499
  * ```html
5174
5500
  * <tbw-grid [pivot]="{ rowFields: ['category'], valueField: 'sales' }" />
5175
5501
  * ```
5502
+ *
5503
+ * @deprecated Use `GridPivotDirective` from
5504
+ * `@toolbox-web/grid-angular/features/pivot`. Will be removed in v2.0.0.
5176
5505
  */
5177
5506
  pivot = input(...(ngDevMode ? [undefined, { debugName: "pivot" }] : /* istanbul ignore next */ []));
5178
5507
  /**
@@ -5187,6 +5516,9 @@ class Grid {
5187
5516
  * ```html
5188
5517
  * <tbw-grid [serverSide]="{ dataSource: fetchDataFn }" />
5189
5518
  * ```
5519
+ *
5520
+ * @deprecated Use `GridServerSideDirective` from
5521
+ * `@toolbox-web/grid-angular/features/server-side`. Will be removed in v2.0.0.
5190
5522
  */
5191
5523
  serverSide = input(...(ngDevMode ? [undefined, { debugName: "serverSide" }] : /* istanbul ignore next */ []));
5192
5524
  /**
@@ -5197,6 +5529,9 @@ class Grid {
5197
5529
  * <tbw-grid [tooltip]="true" />
5198
5530
  * <tbw-grid [tooltip]="{ header: true, cell: false }" />
5199
5531
  * ```
5532
+ *
5533
+ * @deprecated Use `GridTooltipDirective` from
5534
+ * `@toolbox-web/grid-angular/features/tooltip`. Will be removed in v2.0.0.
5200
5535
  */
5201
5536
  tooltip = input(...(ngDevMode ? [undefined, { debugName: "tooltip" }] : /* istanbul ignore next */ []));
5202
5537
  // ═══════════════════════════════════════════════════════════════════════════
@@ -5256,8 +5591,85 @@ class Grid {
5256
5591
  * console.log(`Changed ${event.field} to ${event.value} in row ${event.rowIndex}`);
5257
5592
  * }
5258
5593
  * ```
5594
+ *
5595
+ * @deprecated Use `GridEditingDirective` from
5596
+ * `@toolbox-web/grid-angular/features/editing`. Will be removed in v2.0.0.
5259
5597
  */
5260
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();
5261
5673
  /**
5262
5674
  * Emitted when a row's values are committed (bulk/row editing).
5263
5675
  * Provides the row data and change tracking information.
@@ -5266,6 +5678,9 @@ class Grid {
5266
5678
  * ```html
5267
5679
  * <tbw-grid (rowCommit)="onRowCommit($event)">...</tbw-grid>
5268
5680
  * ```
5681
+ *
5682
+ * @deprecated Use `GridEditingDirective` from
5683
+ * `@toolbox-web/grid-angular/features/editing`. Will be removed in v2.0.0.
5269
5684
  */
5270
5685
  rowCommit = output();
5271
5686
  /**
@@ -5275,6 +5690,9 @@ class Grid {
5275
5690
  * ```html
5276
5691
  * <tbw-grid (changedRowsReset)="onChangedRowsReset($event)">...</tbw-grid>
5277
5692
  * ```
5693
+ *
5694
+ * @deprecated Use `GridEditingDirective` from
5695
+ * `@toolbox-web/grid-angular/features/editing`. Will be removed in v2.0.0.
5278
5696
  */
5279
5697
  changedRowsReset = output();
5280
5698
  /**
@@ -5293,6 +5711,11 @@ class Grid {
5293
5711
  * ```html
5294
5712
  * <tbw-grid (filterChange)="onFilterChange($event)">...</tbw-grid>
5295
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.
5296
5719
  */
5297
5720
  filterChange = output();
5298
5721
  /**
@@ -5304,6 +5727,15 @@ class Grid {
5304
5727
  * ```
5305
5728
  */
5306
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();
5307
5739
  /**
5308
5740
  * Emitted when a column is moved via drag-and-drop.
5309
5741
  *
@@ -5311,15 +5743,23 @@ class Grid {
5311
5743
  * ```html
5312
5744
  * <tbw-grid (columnMove)="onColumnMove($event)">...</tbw-grid>
5313
5745
  * ```
5746
+ *
5747
+ * @deprecated Use `GridReorderColumnsDirective` from
5748
+ * `@toolbox-web/grid-angular/features/reorder-columns`. Will be removed in v2.0.0.
5314
5749
  */
5315
5750
  columnMove = output();
5316
5751
  /**
5317
- * 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()`.
5318
5755
  *
5319
5756
  * @example
5320
5757
  * ```html
5321
5758
  * <tbw-grid (columnVisibility)="onColumnVisibility($event)">...</tbw-grid>
5322
5759
  * ```
5760
+ *
5761
+ * @deprecated Use `GridVisibilityDirective` from
5762
+ * `@toolbox-web/grid-angular/features/visibility`. Will be removed in v2.0.0.
5323
5763
  */
5324
5764
  columnVisibility = output();
5325
5765
  /**
@@ -5338,6 +5778,9 @@ class Grid {
5338
5778
  * ```html
5339
5779
  * <tbw-grid (selectionChange)="onSelectionChange($event)">...</tbw-grid>
5340
5780
  * ```
5781
+ *
5782
+ * @deprecated Use `GridSelectionDirective` from
5783
+ * `@toolbox-web/grid-angular/features/selection`. Will be removed in v2.0.0.
5341
5784
  */
5342
5785
  selectionChange = output();
5343
5786
  /**
@@ -5347,6 +5790,9 @@ class Grid {
5347
5790
  * ```html
5348
5791
  * <tbw-grid (rowMove)="onRowMove($event)">...</tbw-grid>
5349
5792
  * ```
5793
+ *
5794
+ * @deprecated Use `GridRowDragDropDirective` from
5795
+ * `@toolbox-web/grid-angular/features/row-drag-drop`. Will be removed in v2.0.0.
5350
5796
  */
5351
5797
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5352
5798
  rowMove = output();
@@ -5357,23 +5803,35 @@ class Grid {
5357
5803
  * ```html
5358
5804
  * <tbw-grid (rowDragStart)="onRowDragStart($event)">...</tbw-grid>
5359
5805
  * ```
5806
+ *
5807
+ * @deprecated Use `GridRowDragDropDirective` from
5808
+ * `@toolbox-web/grid-angular/features/row-drag-drop`. Will be removed in v2.0.0.
5360
5809
  */
5361
5810
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5362
5811
  rowDragStart = output();
5363
5812
  /**
5364
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.
5365
5817
  */
5366
5818
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5367
5819
  rowDragEnd = output();
5368
5820
  /**
5369
5821
  * Emitted on the target grid when rows are dropped from another grid.
5370
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.
5371
5826
  */
5372
5827
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5373
5828
  rowDrop = output();
5374
5829
  /**
5375
5830
  * Emitted on BOTH source and target grids after a successful cross-grid
5376
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.
5377
5835
  */
5378
5836
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5379
5837
  rowTransfer = output();
@@ -5384,8 +5842,35 @@ class Grid {
5384
5842
  * ```html
5385
5843
  * <tbw-grid (groupToggle)="onGroupToggle($event)">...</tbw-grid>
5386
5844
  * ```
5845
+ *
5846
+ * @deprecated Use `GridGroupingRowsDirective` from
5847
+ * `@toolbox-web/grid-angular/features/grouping-rows`. Will be removed in v2.0.0.
5387
5848
  */
5388
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();
5389
5874
  /**
5390
5875
  * Emitted when a tree node is expanded.
5391
5876
  *
@@ -5393,6 +5878,9 @@ class Grid {
5393
5878
  * ```html
5394
5879
  * <tbw-grid (treeExpand)="onTreeExpand($event)">...</tbw-grid>
5395
5880
  * ```
5881
+ *
5882
+ * @deprecated Use `GridTreeDirective` from
5883
+ * `@toolbox-web/grid-angular/features/tree`. Will be removed in v2.0.0.
5396
5884
  */
5397
5885
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5398
5886
  treeExpand = output();
@@ -5403,6 +5891,9 @@ class Grid {
5403
5891
  * ```html
5404
5892
  * <tbw-grid (detailExpand)="onDetailExpand($event)">...</tbw-grid>
5405
5893
  * ```
5894
+ *
5895
+ * @deprecated Use `GridMasterDetailDirective` from
5896
+ * `@toolbox-web/grid-angular/features/master-detail`. Will be removed in v2.0.0.
5406
5897
  */
5407
5898
  detailExpand = output();
5408
5899
  /**
@@ -5412,8 +5903,23 @@ class Grid {
5412
5903
  * ```html
5413
5904
  * <tbw-grid (responsiveChange)="onResponsiveChange($event)">...</tbw-grid>
5414
5905
  * ```
5906
+ *
5907
+ * @deprecated Use `GridResponsiveDirective` from
5908
+ * `@toolbox-web/grid-angular/features/responsive`. Will be removed in v2.0.0.
5415
5909
  */
5416
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();
5417
5923
  /**
5418
5924
  * Emitted when cells are copied to clipboard.
5419
5925
  *
@@ -5421,6 +5927,9 @@ class Grid {
5421
5927
  * ```html
5422
5928
  * <tbw-grid (copy)="onCopy($event)">...</tbw-grid>
5423
5929
  * ```
5930
+ *
5931
+ * @deprecated Use `GridClipboardDirective` from
5932
+ * `@toolbox-web/grid-angular/features/clipboard`. Will be removed in v2.0.0.
5424
5933
  */
5425
5934
  copy = output();
5426
5935
  /**
@@ -5430,17 +5939,35 @@ class Grid {
5430
5939
  * ```html
5431
5940
  * <tbw-grid (paste)="onPaste($event)">...</tbw-grid>
5432
5941
  * ```
5942
+ *
5943
+ * @deprecated Use `GridClipboardDirective` from
5944
+ * `@toolbox-web/grid-angular/features/clipboard`. Will be removed in v2.0.0.
5433
5945
  */
5434
5946
  paste = output();
5435
5947
  /**
5436
- * 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.
5437
5961
  *
5438
5962
  * @example
5439
5963
  * ```html
5440
- * <tbw-grid (undoRedoAction)="onUndoRedo($event)">...</tbw-grid>
5964
+ * <tbw-grid (redo)="onRedo($event)">...</tbw-grid>
5441
5965
  * ```
5966
+ *
5967
+ * @deprecated Use `GridUndoRedoDirective` from
5968
+ * `@toolbox-web/grid-angular/features/undo-redo`. Will be removed in v2.0.0.
5442
5969
  */
5443
- undoRedoAction = output();
5970
+ redo = output();
5444
5971
  /**
5445
5972
  * Emitted when export completes.
5446
5973
  *
@@ -5448,6 +5975,9 @@ class Grid {
5448
5975
  * ```html
5449
5976
  * <tbw-grid (exportComplete)="onExportComplete($event)">...</tbw-grid>
5450
5977
  * ```
5978
+ *
5979
+ * @deprecated Use `GridExportDirective` from
5980
+ * `@toolbox-web/grid-angular/features/export`. Will be removed in v2.0.0.
5451
5981
  */
5452
5982
  exportComplete = output();
5453
5983
  /**
@@ -5457,6 +5987,9 @@ class Grid {
5457
5987
  * ```html
5458
5988
  * <tbw-grid (printStart)="onPrintStart($event)">...</tbw-grid>
5459
5989
  * ```
5990
+ *
5991
+ * @deprecated Use `GridPrintDirective` from
5992
+ * `@toolbox-web/grid-angular/features/print`. Will be removed in v2.0.0.
5460
5993
  */
5461
5994
  printStart = output();
5462
5995
  /**
@@ -5466,6 +5999,9 @@ class Grid {
5466
5999
  * ```html
5467
6000
  * <tbw-grid (printComplete)="onPrintComplete($event)">...</tbw-grid>
5468
6001
  * ```
6002
+ *
6003
+ * @deprecated Use `GridPrintDirective` from
6004
+ * `@toolbox-web/grid-angular/features/print`. Will be removed in v2.0.0.
5469
6005
  */
5470
6006
  printComplete = output();
5471
6007
  /**
@@ -5484,18 +6020,31 @@ class Grid {
5484
6020
  * ```
5485
6021
  */
5486
6022
  tbwScroll = output();
5487
- // 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.
5488
6030
  eventOutputMap = {
5489
6031
  cellClick: 'cell-click',
5490
6032
  rowClick: 'row-click',
5491
6033
  cellActivate: 'cell-activate',
5492
6034
  cellChange: 'cell-change',
5493
6035
  cellCommit: 'cell-commit',
6036
+ cellCancel: 'cell-cancel',
5494
6037
  rowCommit: 'row-commit',
5495
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',
5496
6044
  sortChange: 'sort-change',
5497
6045
  filterChange: 'filter-change',
5498
6046
  columnResize: 'column-resize',
6047
+ columnResizeReset: 'column-resize-reset',
5499
6048
  columnMove: 'column-move',
5500
6049
  columnVisibility: 'column-visibility',
5501
6050
  columnStateChange: 'column-state-change',
@@ -5506,17 +6055,39 @@ class Grid {
5506
6055
  rowDrop: 'row-drop',
5507
6056
  rowTransfer: 'row-transfer',
5508
6057
  groupToggle: 'group-toggle',
6058
+ groupExpand: 'group-expand',
6059
+ groupCollapse: 'group-collapse',
5509
6060
  treeExpand: 'tree-expand',
5510
6061
  detailExpand: 'detail-expand',
5511
6062
  responsiveChange: 'responsive-change',
6063
+ contextMenuOpen: 'context-menu-open',
5512
6064
  copy: 'copy',
5513
6065
  paste: 'paste',
5514
- undoRedoAction: 'undo-redo',
6066
+ undo: 'undo',
6067
+ redo: 'redo',
5515
6068
  exportComplete: 'export-complete',
5516
6069
  printStart: 'print-start',
5517
6070
  printComplete: 'print-complete',
5518
6071
  tbwScroll: 'tbw-scroll',
5519
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;
5520
6091
  // Store event listeners for cleanup
5521
6092
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5522
6093
  eventListeners = new Map();
@@ -5535,10 +6106,18 @@ class Grid {
5535
6106
  }
5536
6107
  /**
5537
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.
5538
6115
  */
5539
6116
  setupEventListeners(grid) {
5540
6117
  // Wire up all event listeners
5541
6118
  for (const [outputName, eventName] of Object.entries(this.eventOutputMap)) {
6119
+ if (isEventClaimed(grid, eventName))
6120
+ continue;
5542
6121
  const listener = (e) => {
5543
6122
  const detail = e.detail;
5544
6123
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -5551,57 +6130,62 @@ class Grid {
5551
6130
  /**
5552
6131
  * Creates plugins from feature inputs.
5553
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
+ *
5554
6146
  * Returns the array of created plugins (doesn't modify grid).
5555
6147
  */
5556
6148
  createFeaturePlugins() {
5557
6149
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5558
6150
  const plugins = [];
6151
+ const adapter = this.adapter;
6152
+ const grid = this.elementRef.nativeElement;
5559
6153
  // Helper to add plugin if feature is registered
5560
- 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;
5561
6160
  if (config === undefined || config === null || config === false)
5562
6161
  return;
5563
- 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);
5564
6171
  if (plugin)
5565
6172
  plugins.push(plugin);
5566
6173
  };
5567
- // Add plugins for each feature input
5568
6174
  addPlugin('selection', this.selection());
5569
6175
  addPlugin('editing', this.editing());
5570
6176
  addPlugin('clipboard', this.clipboard());
5571
6177
  addPlugin('contextMenu', this.contextMenu());
5572
- // multiSort is the primary input
5573
6178
  addPlugin('multiSort', this.multiSort());
5574
6179
  addPlugin('filtering', this.filtering());
5575
6180
  addPlugin('reorderColumns', this.reorderColumns());
5576
6181
  addPlugin('visibility', this.visibility());
5577
6182
  addPlugin('pinnedColumns', this.pinnedColumns());
5578
- // Pre-process groupingColumns config to bridge Angular component classes
5579
- const gcConfig = this.groupingColumns();
5580
- if (gcConfig && typeof gcConfig === 'object' && this.adapter) {
5581
- addPlugin('groupingColumns', this.adapter.processGroupingColumnsConfig(gcConfig));
5582
- }
5583
- else {
5584
- addPlugin('groupingColumns', gcConfig);
5585
- }
6183
+ addPlugin('groupingColumns', this.groupingColumns());
5586
6184
  addPlugin('columnVirtualization', this.columnVirtualization());
5587
6185
  addPlugin('reorderRows', this.reorderRows());
5588
6186
  addPlugin('rowDragDrop', this.rowDragDrop());
5589
- // Pre-process groupingRows config to bridge Angular component classes
5590
- const grConfig = this.groupingRows();
5591
- if (grConfig && typeof grConfig === 'object' && this.adapter) {
5592
- addPlugin('groupingRows', this.adapter.processGroupingRowsConfig(grConfig));
5593
- }
5594
- else {
5595
- addPlugin('groupingRows', grConfig);
5596
- }
5597
- // Pre-process pinnedRows config to bridge Angular component classes in customPanels
5598
- const prConfig = this.pinnedRows();
5599
- if (prConfig && typeof prConfig === 'object' && this.adapter) {
5600
- addPlugin('pinnedRows', this.adapter.processPinnedRowsConfig(prConfig));
5601
- }
5602
- else {
5603
- addPlugin('pinnedRows', prConfig);
5604
- }
6187
+ addPlugin('groupingRows', this.groupingRows());
6188
+ addPlugin('pinnedRows', this.pinnedRows());
5605
6189
  addPlugin('tree', this.tree());
5606
6190
  addPlugin('masterDetail', this.masterDetail());
5607
6191
  addPlugin('responsive', this.responsive());
@@ -5621,10 +6205,13 @@ class Grid {
5621
6205
  // Use setTimeout to ensure Angular effects have run (template registration)
5622
6206
  setTimeout(() => {
5623
6207
  grid.refreshColumns();
5624
- // Configure MasterDetailPlugin after Angular templates are registered
5625
- this.configureMasterDetail(grid);
5626
- // Configure ResponsivePlugin card renderer if template is present
5627
- 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
+ }
5628
6215
  // Refresh shell header to pick up tool panel templates
5629
6216
  // This allows Angular templates to be used in tool panels
5630
6217
  if (typeof grid.refreshShellHeader === 'function') {
@@ -5648,85 +6235,6 @@ class Grid {
5648
6235
  grid.registerStyles?.('angular-custom-styles', styles);
5649
6236
  });
5650
6237
  }
5651
- /**
5652
- * Configures the MasterDetailPlugin after Angular templates are registered.
5653
- * - If plugin exists: refresh its detail renderer
5654
- * - If plugin doesn't exist but <tbw-grid-detail> is present: dynamically import and add the plugin
5655
- */
5656
- async configureMasterDetail(grid) {
5657
- if (!this.adapter)
5658
- return;
5659
- // Check for existing plugin by name to avoid importing the class
5660
- const existingPlugin = grid.gridConfig?.plugins?.find((p) => p.name === 'masterDetail');
5661
- if (existingPlugin && typeof existingPlugin.refreshDetailRenderer === 'function') {
5662
- // Plugin exists - just refresh the renderer to pick up Angular templates
5663
- existingPlugin.refreshDetailRenderer();
5664
- return;
5665
- }
5666
- // Check if <tbw-grid-detail> is present in light DOM
5667
- const detailElement = grid.querySelector('tbw-grid-detail');
5668
- if (!detailElement)
5669
- return;
5670
- // Create detail renderer from Angular template
5671
- const detailRenderer = this.adapter.createDetailRenderer(grid);
5672
- if (!detailRenderer)
5673
- return;
5674
- // Parse configuration from attributes
5675
- const animationAttr = detailElement.getAttribute('animation');
5676
- let animation = 'slide';
5677
- if (animationAttr === 'false') {
5678
- animation = false;
5679
- }
5680
- else if (animationAttr === 'fade') {
5681
- animation = 'fade';
5682
- }
5683
- const showExpandColumn = detailElement.getAttribute('showExpandColumn') !== 'false';
5684
- // Dynamically import the plugin to avoid bundling it when not used
5685
- const { MasterDetailPlugin } = await import('@toolbox-web/grid/plugins/master-detail');
5686
- // Create and add the plugin
5687
- const plugin = new MasterDetailPlugin({
5688
- detailRenderer: detailRenderer,
5689
- showExpandColumn,
5690
- animation,
5691
- });
5692
- const currentConfig = grid.gridConfig || {};
5693
- const existingPlugins = currentConfig.plugins || [];
5694
- grid.gridConfig = {
5695
- ...currentConfig,
5696
- plugins: [...existingPlugins, plugin],
5697
- };
5698
- }
5699
- /**
5700
- * Configures the ResponsivePlugin with Angular template-based card renderer.
5701
- * - If plugin exists: updates its cardRenderer configuration
5702
- * - If plugin doesn't exist but <tbw-grid-responsive-card> is present: logs a warning
5703
- */
5704
- configureResponsiveCard(grid) {
5705
- if (!this.adapter)
5706
- return;
5707
- // Check if <tbw-grid-responsive-card> is present in light DOM
5708
- const cardElement = grid.querySelector('tbw-grid-responsive-card');
5709
- if (!cardElement)
5710
- return;
5711
- // Create card renderer from Angular template
5712
- const cardRenderer = this.adapter.createResponsiveCardRenderer(grid);
5713
- if (!cardRenderer)
5714
- return;
5715
- // Find existing plugin by name to avoid importing the class
5716
- const existingPlugin = grid.gridConfig?.plugins?.find((p) => p.name === 'responsive');
5717
- if (existingPlugin && typeof existingPlugin.setCardRenderer === 'function') {
5718
- // Plugin exists - update its cardRenderer
5719
- existingPlugin.setCardRenderer(cardRenderer);
5720
- return;
5721
- }
5722
- // Plugin doesn't exist - log a warning
5723
- console.warn('[tbw-grid-angular] <tbw-grid-responsive-card> found but ResponsivePlugin is not configured.\n' +
5724
- 'Add ResponsivePlugin to your gridConfig.plugins array:\n\n' +
5725
- ' import { ResponsivePlugin } from "@toolbox-web/grid/plugins/responsive";\n' +
5726
- ' gridConfig = {\n' +
5727
- ' plugins: [new ResponsivePlugin({ breakpoint: 600 })]\n' +
5728
- ' };');
5729
- }
5730
6238
  ngOnDestroy() {
5731
6239
  const grid = this.elementRef.nativeElement;
5732
6240
  // Cleanup all event listeners
@@ -5747,12 +6255,60 @@ class Grid {
5747
6255
  }
5748
6256
  }
5749
6257
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: Grid, deps: [], target: i0.ɵɵFactoryTarget.Directive });
5750
- 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 });
5751
6259
  }
5752
6260
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: Grid, decorators: [{
5753
6261
  type: Directive,
5754
6262
  args: [{ selector: 'tbw-grid' }]
5755
- }], 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
+ }
5756
6312
 
5757
6313
  /**
5758
6314
  * @packageDocumentation
@@ -5766,5 +6322,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
5766
6322
  * Generated bundle index. Do not edit.
5767
6323
  */
5768
6324
 
5769
- 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 };
5770
6326
  //# sourceMappingURL=toolbox-web-grid-angular.mjs.map