@toolbox-web/grid-angular 0.7.1 → 0.8.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 (211) hide show
  1. package/README.md +71 -0
  2. package/fesm2022/toolbox-web-grid-angular-features-clipboard.mjs +30 -0
  3. package/fesm2022/toolbox-web-grid-angular-features-clipboard.mjs.map +1 -0
  4. package/fesm2022/toolbox-web-grid-angular-features-column-virtualization.mjs +28 -0
  5. package/fesm2022/toolbox-web-grid-angular-features-column-virtualization.mjs.map +1 -0
  6. package/fesm2022/toolbox-web-grid-angular-features-context-menu.mjs +28 -0
  7. package/fesm2022/toolbox-web-grid-angular-features-context-menu.mjs.map +1 -0
  8. package/fesm2022/toolbox-web-grid-angular-features-editing.mjs +33 -0
  9. package/fesm2022/toolbox-web-grid-angular-features-editing.mjs.map +1 -0
  10. package/fesm2022/toolbox-web-grid-angular-features-export.mjs +29 -0
  11. package/fesm2022/toolbox-web-grid-angular-features-export.mjs.map +1 -0
  12. package/fesm2022/toolbox-web-grid-angular-features-filtering.mjs +29 -0
  13. package/fesm2022/toolbox-web-grid-angular-features-filtering.mjs.map +1 -0
  14. package/fesm2022/toolbox-web-grid-angular-features-grouping-columns.mjs +28 -0
  15. package/fesm2022/toolbox-web-grid-angular-features-grouping-columns.mjs.map +1 -0
  16. package/fesm2022/toolbox-web-grid-angular-features-grouping-rows.mjs +25 -0
  17. package/fesm2022/toolbox-web-grid-angular-features-grouping-rows.mjs.map +1 -0
  18. package/fesm2022/toolbox-web-grid-angular-features-master-detail.mjs +25 -0
  19. package/fesm2022/toolbox-web-grid-angular-features-master-detail.mjs.map +1 -0
  20. package/fesm2022/toolbox-web-grid-angular-features-multi-sort.mjs +40 -0
  21. package/fesm2022/toolbox-web-grid-angular-features-multi-sort.mjs.map +1 -0
  22. package/{features/pinned-columns.d.ts → fesm2022/toolbox-web-grid-angular-features-pinned-columns.mjs} +11 -2
  23. package/fesm2022/toolbox-web-grid-angular-features-pinned-columns.mjs.map +1 -0
  24. package/fesm2022/toolbox-web-grid-angular-features-pinned-rows.mjs +28 -0
  25. package/fesm2022/toolbox-web-grid-angular-features-pinned-rows.mjs.map +1 -0
  26. package/fesm2022/toolbox-web-grid-angular-features-pivot.mjs +25 -0
  27. package/fesm2022/toolbox-web-grid-angular-features-pivot.mjs.map +1 -0
  28. package/fesm2022/toolbox-web-grid-angular-features-print.mjs +28 -0
  29. package/fesm2022/toolbox-web-grid-angular-features-print.mjs.map +1 -0
  30. package/fesm2022/toolbox-web-grid-angular-features-reorder.mjs +28 -0
  31. package/fesm2022/toolbox-web-grid-angular-features-reorder.mjs.map +1 -0
  32. package/fesm2022/toolbox-web-grid-angular-features-responsive.mjs +28 -0
  33. package/fesm2022/toolbox-web-grid-angular-features-responsive.mjs.map +1 -0
  34. package/fesm2022/toolbox-web-grid-angular-features-row-reorder.mjs +28 -0
  35. package/fesm2022/toolbox-web-grid-angular-features-row-reorder.mjs.map +1 -0
  36. package/fesm2022/toolbox-web-grid-angular-features-selection.mjs +30 -0
  37. package/fesm2022/toolbox-web-grid-angular-features-selection.mjs.map +1 -0
  38. package/fesm2022/toolbox-web-grid-angular-features-server-side.mjs +25 -0
  39. package/fesm2022/toolbox-web-grid-angular-features-server-side.mjs.map +1 -0
  40. package/fesm2022/toolbox-web-grid-angular-features-sorting.mjs +25 -0
  41. package/fesm2022/toolbox-web-grid-angular-features-sorting.mjs.map +1 -0
  42. package/fesm2022/toolbox-web-grid-angular-features-tree.mjs +28 -0
  43. package/fesm2022/toolbox-web-grid-angular-features-tree.mjs.map +1 -0
  44. package/fesm2022/toolbox-web-grid-angular-features-undo-redo.mjs +30 -0
  45. package/fesm2022/toolbox-web-grid-angular-features-undo-redo.mjs.map +1 -0
  46. package/fesm2022/toolbox-web-grid-angular-features-visibility.mjs +28 -0
  47. package/fesm2022/toolbox-web-grid-angular-features-visibility.mjs.map +1 -0
  48. package/fesm2022/toolbox-web-grid-angular.mjs +3376 -0
  49. package/fesm2022/toolbox-web-grid-angular.mjs.map +1 -0
  50. package/package.json +109 -35
  51. package/types/toolbox-web-grid-angular-features-clipboard.d.ts +3 -0
  52. package/types/toolbox-web-grid-angular-features-clipboard.d.ts.map +1 -0
  53. package/types/toolbox-web-grid-angular-features-column-virtualization.d.ts +3 -0
  54. package/types/toolbox-web-grid-angular-features-column-virtualization.d.ts.map +1 -0
  55. package/types/toolbox-web-grid-angular-features-context-menu.d.ts +3 -0
  56. package/types/toolbox-web-grid-angular-features-context-menu.d.ts.map +1 -0
  57. package/types/toolbox-web-grid-angular-features-editing.d.ts +3 -0
  58. package/types/toolbox-web-grid-angular-features-editing.d.ts.map +1 -0
  59. package/types/toolbox-web-grid-angular-features-export.d.ts +3 -0
  60. package/types/toolbox-web-grid-angular-features-export.d.ts.map +1 -0
  61. package/types/toolbox-web-grid-angular-features-filtering.d.ts +3 -0
  62. package/types/toolbox-web-grid-angular-features-filtering.d.ts.map +1 -0
  63. package/types/toolbox-web-grid-angular-features-grouping-columns.d.ts +3 -0
  64. package/types/toolbox-web-grid-angular-features-grouping-columns.d.ts.map +1 -0
  65. package/types/toolbox-web-grid-angular-features-grouping-rows.d.ts +3 -0
  66. package/types/toolbox-web-grid-angular-features-grouping-rows.d.ts.map +1 -0
  67. package/types/toolbox-web-grid-angular-features-master-detail.d.ts +3 -0
  68. package/types/toolbox-web-grid-angular-features-master-detail.d.ts.map +1 -0
  69. package/types/toolbox-web-grid-angular-features-multi-sort.d.ts +3 -0
  70. package/types/toolbox-web-grid-angular-features-multi-sort.d.ts.map +1 -0
  71. package/types/toolbox-web-grid-angular-features-pinned-columns.d.ts +3 -0
  72. package/types/toolbox-web-grid-angular-features-pinned-columns.d.ts.map +1 -0
  73. package/types/toolbox-web-grid-angular-features-pinned-rows.d.ts +3 -0
  74. package/types/toolbox-web-grid-angular-features-pinned-rows.d.ts.map +1 -0
  75. package/types/toolbox-web-grid-angular-features-pivot.d.ts +3 -0
  76. package/types/toolbox-web-grid-angular-features-pivot.d.ts.map +1 -0
  77. package/types/toolbox-web-grid-angular-features-print.d.ts +3 -0
  78. package/types/toolbox-web-grid-angular-features-print.d.ts.map +1 -0
  79. package/types/toolbox-web-grid-angular-features-reorder.d.ts +3 -0
  80. package/types/toolbox-web-grid-angular-features-reorder.d.ts.map +1 -0
  81. package/types/toolbox-web-grid-angular-features-responsive.d.ts +3 -0
  82. package/types/toolbox-web-grid-angular-features-responsive.d.ts.map +1 -0
  83. package/types/toolbox-web-grid-angular-features-row-reorder.d.ts +3 -0
  84. package/types/toolbox-web-grid-angular-features-row-reorder.d.ts.map +1 -0
  85. package/types/toolbox-web-grid-angular-features-selection.d.ts +3 -0
  86. package/types/toolbox-web-grid-angular-features-selection.d.ts.map +1 -0
  87. package/types/toolbox-web-grid-angular-features-server-side.d.ts +3 -0
  88. package/types/toolbox-web-grid-angular-features-server-side.d.ts.map +1 -0
  89. package/types/toolbox-web-grid-angular-features-sorting.d.ts +3 -0
  90. package/types/toolbox-web-grid-angular-features-sorting.d.ts.map +1 -0
  91. package/types/toolbox-web-grid-angular-features-tree.d.ts +3 -0
  92. package/types/toolbox-web-grid-angular-features-tree.d.ts.map +1 -0
  93. package/types/toolbox-web-grid-angular-features-undo-redo.d.ts +3 -0
  94. package/types/toolbox-web-grid-angular-features-undo-redo.d.ts.map +1 -0
  95. package/types/toolbox-web-grid-angular-features-visibility.d.ts +3 -0
  96. package/types/toolbox-web-grid-angular-features-visibility.d.ts.map +1 -0
  97. package/types/toolbox-web-grid-angular.d.ts +2468 -0
  98. package/types/toolbox-web-grid-angular.d.ts.map +1 -0
  99. package/feature-registry-C-cKloXB.js +0 -45
  100. package/features/clipboard.d.ts +0 -18
  101. package/features/clipboard.d.ts.map +0 -1
  102. package/features/clipboard.js +0 -3
  103. package/features/column-virtualization.d.ts +0 -16
  104. package/features/column-virtualization.d.ts.map +0 -1
  105. package/features/column-virtualization.js +0 -3
  106. package/features/context-menu.d.ts +0 -16
  107. package/features/context-menu.d.ts.map +0 -1
  108. package/features/context-menu.js +0 -3
  109. package/features/editing.d.ts +0 -16
  110. package/features/editing.d.ts.map +0 -1
  111. package/features/editing.js +0 -3
  112. package/features/export.d.ts +0 -17
  113. package/features/export.d.ts.map +0 -1
  114. package/features/export.js +0 -3
  115. package/features/filtering.d.ts +0 -17
  116. package/features/filtering.d.ts.map +0 -1
  117. package/features/filtering.js +0 -3
  118. package/features/grouping-columns.d.ts +0 -16
  119. package/features/grouping-columns.d.ts.map +0 -1
  120. package/features/grouping-columns.js +0 -3
  121. package/features/grouping-rows.d.ts +0 -16
  122. package/features/grouping-rows.d.ts.map +0 -1
  123. package/features/grouping-rows.js +0 -3
  124. package/features/index.d.ts +0 -1
  125. package/features/index.d.ts.map +0 -1
  126. package/features/index.js +0 -22
  127. package/features/master-detail.d.ts +0 -16
  128. package/features/master-detail.d.ts.map +0 -1
  129. package/features/master-detail.js +0 -3
  130. package/features/multi-sort.d.ts +0 -22
  131. package/features/multi-sort.d.ts.map +0 -1
  132. package/features/multi-sort.js +0 -3
  133. package/features/pinned-columns.d.ts.map +0 -1
  134. package/features/pinned-columns.js +0 -3
  135. package/features/pinned-rows.d.ts +0 -16
  136. package/features/pinned-rows.d.ts.map +0 -1
  137. package/features/pinned-rows.js +0 -3
  138. package/features/pivot.d.ts +0 -16
  139. package/features/pivot.d.ts.map +0 -1
  140. package/features/pivot.js +0 -3
  141. package/features/print.d.ts +0 -16
  142. package/features/print.d.ts.map +0 -1
  143. package/features/print.js +0 -3
  144. package/features/reorder.d.ts +0 -16
  145. package/features/reorder.d.ts.map +0 -1
  146. package/features/reorder.js +0 -3
  147. package/features/responsive.d.ts +0 -16
  148. package/features/responsive.d.ts.map +0 -1
  149. package/features/responsive.js +0 -3
  150. package/features/row-reorder.d.ts +0 -16
  151. package/features/row-reorder.d.ts.map +0 -1
  152. package/features/row-reorder.js +0 -3
  153. package/features/selection.d.ts +0 -16
  154. package/features/selection.d.ts.map +0 -1
  155. package/features/selection.js +0 -3
  156. package/features/server-side.d.ts +0 -16
  157. package/features/server-side.d.ts.map +0 -1
  158. package/features/server-side.js +0 -3
  159. package/features/sorting.d.ts +0 -1
  160. package/features/sorting.d.ts.map +0 -1
  161. package/features/sorting.js +0 -1
  162. package/features/tree.d.ts +0 -16
  163. package/features/tree.d.ts.map +0 -1
  164. package/features/tree.js +0 -3
  165. package/features/undo-redo.d.ts +0 -18
  166. package/features/undo-redo.d.ts.map +0 -1
  167. package/features/undo-redo.js +0 -3
  168. package/features/visibility.d.ts +0 -16
  169. package/features/visibility.d.ts.map +0 -1
  170. package/features/visibility.js +0 -3
  171. package/index.d.ts +0 -34
  172. package/index.d.ts.map +0 -1
  173. package/index.js +0 -1916
  174. package/lib/angular-column-config.d.ts +0 -140
  175. package/lib/angular-column-config.d.ts.map +0 -1
  176. package/lib/angular-grid-adapter.d.ts +0 -220
  177. package/lib/angular-grid-adapter.d.ts.map +0 -1
  178. package/lib/base-grid-editor.d.ts +0 -145
  179. package/lib/base-grid-editor.d.ts.map +0 -1
  180. package/lib/component-registry.d.ts +0 -63
  181. package/lib/component-registry.d.ts.map +0 -1
  182. package/lib/directives/grid-column-editor.directive.d.ts +0 -113
  183. package/lib/directives/grid-column-editor.directive.d.ts.map +0 -1
  184. package/lib/directives/grid-column-view.directive.d.ts +0 -69
  185. package/lib/directives/grid-column-view.directive.d.ts.map +0 -1
  186. package/lib/directives/grid-detail-view.directive.d.ts +0 -75
  187. package/lib/directives/grid-detail-view.directive.d.ts.map +0 -1
  188. package/lib/directives/grid-form-array.directive.d.ts +0 -141
  189. package/lib/directives/grid-form-array.directive.d.ts.map +0 -1
  190. package/lib/directives/grid-responsive-card.directive.d.ts +0 -92
  191. package/lib/directives/grid-responsive-card.directive.d.ts.map +0 -1
  192. package/lib/directives/grid-tool-panel.directive.d.ts +0 -91
  193. package/lib/directives/grid-tool-panel.directive.d.ts.map +0 -1
  194. package/lib/directives/grid.directive.d.ts +0 -808
  195. package/lib/directives/grid.directive.d.ts.map +0 -1
  196. package/lib/directives/index.d.ts +0 -9
  197. package/lib/directives/index.d.ts.map +0 -1
  198. package/lib/directives/structural-directives.d.ts +0 -174
  199. package/lib/directives/structural-directives.d.ts.map +0 -1
  200. package/lib/feature-registry.d.ts +0 -72
  201. package/lib/feature-registry.d.ts.map +0 -1
  202. package/lib/grid-type-registry.d.ts +0 -110
  203. package/lib/grid-type-registry.d.ts.map +0 -1
  204. package/lib/inject-grid.d.ts +0 -109
  205. package/lib/inject-grid.d.ts.map +0 -1
  206. package/lib/interfaces/grid-cell-editor.d.ts +0 -85
  207. package/lib/interfaces/grid-cell-editor.d.ts.map +0 -1
  208. package/lib/interfaces/grid-cell-renderer.d.ts +0 -63
  209. package/lib/interfaces/grid-cell-renderer.d.ts.map +0 -1
  210. package/lib/interfaces/index.d.ts +0 -5
  211. package/lib/interfaces/index.d.ts.map +0 -1
@@ -0,0 +1,3376 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, ElementRef, contentChild, TemplateRef, effect, Directive, input, InjectionToken, Injectable, makeEnvironmentProviders, EventEmitter, createComponent, signal, afterNextRender, computed, output, EnvironmentInjector, ApplicationRef, ViewContainerRef } from '@angular/core';
3
+ import { FormGroup } from '@angular/forms';
4
+ import { DataGridElement } from '@toolbox-web/grid';
5
+
6
+ /**
7
+ * Type guard to check if a value is an Angular component class.
8
+ *
9
+ * Detects Angular components by checking for internal Angular markers:
10
+ * - ɵcmp (component definition)
11
+ * - ɵfac (factory function)
12
+ *
13
+ * Also checks if it's an ES6 class (vs function) by inspecting the
14
+ * string representation.
15
+ */
16
+ function isComponentClass(value) {
17
+ if (typeof value !== 'function' || value.prototype === undefined) {
18
+ return false;
19
+ }
20
+ // Check for Angular component markers (AOT compiled)
21
+ if (Object.prototype.hasOwnProperty.call(value, 'ɵcmp') || Object.prototype.hasOwnProperty.call(value, 'ɵfac')) {
22
+ return true;
23
+ }
24
+ // Check if it's an ES6 class (vs regular function)
25
+ // Class definitions start with "class" in their toString()
26
+ const fnString = Function.prototype.toString.call(value);
27
+ return fnString.startsWith('class ') || fnString.startsWith('class{');
28
+ }
29
+
30
+ // Global registry mapping DOM elements to their templates
31
+ const editorTemplateRegistry = new Map();
32
+ /**
33
+ * Gets the editor template registered for a given element.
34
+ * Used by AngularGridAdapter to retrieve templates at render time.
35
+ */
36
+ function getEditorTemplate(element) {
37
+ return editorTemplateRegistry.get(element);
38
+ }
39
+ /**
40
+ * Directive that captures an `<ng-template>` for use as a cell editor.
41
+ *
42
+ * This enables declarative Angular component usage with proper input bindings
43
+ * that satisfy Angular's AOT compiler.
44
+ *
45
+ * ## Usage
46
+ *
47
+ * ```html
48
+ * <tbw-grid-column field="status" editable>
49
+ * <tbw-grid-column-editor>
50
+ * <ng-template let-value let-row="row" let-onCommit="onCommit" let-onCancel="onCancel">
51
+ * <app-status-select
52
+ * [value]="value"
53
+ * [row]="row"
54
+ * (commit)="onCommit($event)"
55
+ * (cancel)="onCancel()"
56
+ * />
57
+ * </ng-template>
58
+ * </tbw-grid-column-editor>
59
+ * </tbw-grid-column>
60
+ * ```
61
+ *
62
+ * The template context provides:
63
+ * - `$implicit` / `value`: The cell value
64
+ * - `row`: The full row data object
65
+ * - `column`: The column configuration
66
+ * - `onCommit`: Callback function to commit the new value
67
+ * - `onCancel`: Callback function to cancel editing
68
+ *
69
+ * Import the directive in your component:
70
+ *
71
+ * ```typescript
72
+ * import { GridColumnEditor } from '@toolbox-web/grid-angular';
73
+ *
74
+ * @Component({
75
+ * imports: [GridColumnEditor],
76
+ * // ...
77
+ * })
78
+ * ```
79
+ */
80
+ class GridColumnEditor {
81
+ elementRef = inject((ElementRef));
82
+ /**
83
+ * Query for the ng-template content child.
84
+ */
85
+ template = contentChild((TemplateRef), ...(ngDevMode ? [{ debugName: "template" }] : []));
86
+ /** Effect that triggers when the template is available */
87
+ onTemplateReceived = effect(() => {
88
+ const template = this.template();
89
+ if (template) {
90
+ // Register the template for this element
91
+ editorTemplateRegistry.set(this.elementRef.nativeElement, template);
92
+ }
93
+ }, ...(ngDevMode ? [{ debugName: "onTemplateReceived" }] : []));
94
+ /**
95
+ * Static type guard for template context.
96
+ * Enables type inference in templates.
97
+ */
98
+ static ngTemplateContextGuard(dir, ctx) {
99
+ return true;
100
+ }
101
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: GridColumnEditor, deps: [], target: i0.ɵɵFactoryTarget.Directive });
102
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "21.1.1", type: GridColumnEditor, isStandalone: true, selector: "tbw-grid-column-editor", queries: [{ propertyName: "template", first: true, predicate: (TemplateRef), descendants: true, isSignal: true }], ngImport: i0 });
103
+ }
104
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: GridColumnEditor, decorators: [{
105
+ type: Directive,
106
+ args: [{ selector: 'tbw-grid-column-editor' }]
107
+ }], propDecorators: { template: [{ type: i0.ContentChild, args: [i0.forwardRef(() => TemplateRef), { isSignal: true }] }] } });
108
+
109
+ // Global registry mapping DOM elements to their templates
110
+ const templateRegistry = new Map();
111
+ /**
112
+ * Gets the template registered for a given element.
113
+ * Used by AngularGridAdapter to retrieve templates at render time.
114
+ */
115
+ function getViewTemplate(element) {
116
+ return templateRegistry.get(element);
117
+ }
118
+ /**
119
+ * Directive that captures an `<ng-template>` for use as a cell renderer.
120
+ *
121
+ * This enables declarative Angular component usage with proper input bindings
122
+ * that satisfy Angular's AOT compiler.
123
+ *
124
+ * ## Usage
125
+ *
126
+ * ```html
127
+ * <tbw-grid-column field="status">
128
+ * <tbw-grid-column-view>
129
+ * <ng-template let-value let-row="row">
130
+ * <app-status-badge [value]="value" [row]="row" />
131
+ * </ng-template>
132
+ * </tbw-grid-column-view>
133
+ * </tbw-grid-column>
134
+ * ```
135
+ *
136
+ * The template context provides:
137
+ * - `$implicit` / `value`: The cell value
138
+ * - `row`: The full row data object
139
+ * - `column`: The column configuration
140
+ *
141
+ * Import the directive in your component:
142
+ *
143
+ * ```typescript
144
+ * import { GridColumnView } from '@toolbox-web/grid-angular';
145
+ *
146
+ * @Component({
147
+ * imports: [GridColumnView],
148
+ * // ...
149
+ * })
150
+ * ```
151
+ */
152
+ class GridColumnView {
153
+ elementRef = inject((ElementRef));
154
+ /**
155
+ * Query for the ng-template content child.
156
+ */
157
+ template = contentChild((TemplateRef), ...(ngDevMode ? [{ debugName: "template" }] : []));
158
+ /** Effect that triggers when the template is available */
159
+ onTemplateReceived = effect(() => {
160
+ const template = this.template();
161
+ if (template) {
162
+ // Register the template for this element
163
+ templateRegistry.set(this.elementRef.nativeElement, template);
164
+ }
165
+ }, ...(ngDevMode ? [{ debugName: "onTemplateReceived" }] : []));
166
+ /**
167
+ * Static type guard for template context.
168
+ * Enables type inference in templates.
169
+ */
170
+ static ngTemplateContextGuard(dir, ctx) {
171
+ return true;
172
+ }
173
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: GridColumnView, deps: [], target: i0.ɵɵFactoryTarget.Directive });
174
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "21.1.1", type: GridColumnView, isStandalone: true, selector: "tbw-grid-column-view", queries: [{ propertyName: "template", first: true, predicate: (TemplateRef), descendants: true, isSignal: true }], ngImport: i0 });
175
+ }
176
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: GridColumnView, decorators: [{
177
+ type: Directive,
178
+ args: [{ selector: 'tbw-grid-column-view' }]
179
+ }], propDecorators: { template: [{ type: i0.ContentChild, args: [i0.forwardRef(() => TemplateRef), { isSignal: true }] }] } });
180
+
181
+ // Global registry mapping DOM elements to their templates
182
+ const detailTemplateRegistry = new Map();
183
+ /**
184
+ * Gets the detail template registered for a given grid element.
185
+ * Used by AngularGridAdapter to retrieve templates at render time.
186
+ */
187
+ function getDetailTemplate(gridElement) {
188
+ // Look for tbw-grid-detail child and get its template
189
+ const detailElement = gridElement.querySelector('tbw-grid-detail');
190
+ if (detailElement) {
191
+ return detailTemplateRegistry.get(detailElement);
192
+ }
193
+ return undefined;
194
+ }
195
+ /**
196
+ * Gets the configuration for the detail view.
197
+ */
198
+ function getDetailConfig(gridElement) {
199
+ const detailElement = gridElement.querySelector('tbw-grid-detail');
200
+ if (detailElement) {
201
+ const animationAttr = detailElement.getAttribute('animation');
202
+ let animation = 'slide';
203
+ if (animationAttr === 'false') {
204
+ animation = false;
205
+ }
206
+ else if (animationAttr === 'fade') {
207
+ animation = 'fade';
208
+ }
209
+ return {
210
+ showExpandColumn: detailElement.getAttribute('showExpandColumn') !== 'false',
211
+ animation,
212
+ };
213
+ }
214
+ return undefined;
215
+ }
216
+ /**
217
+ * Directive that captures an `<ng-template>` for use as a master-detail row renderer.
218
+ *
219
+ * This enables declarative Angular component usage for expandable detail rows
220
+ * that appear below the main row when expanded.
221
+ *
222
+ * ## Usage
223
+ *
224
+ * ```html
225
+ * <tbw-grid [rows]="rows" [gridConfig]="config">
226
+ * <tbw-grid-detail [showExpandColumn]="true" animation="slide">
227
+ * <ng-template let-row>
228
+ * <app-detail-panel [employee]="row" />
229
+ * </ng-template>
230
+ * </tbw-grid-detail>
231
+ * </tbw-grid>
232
+ * ```
233
+ *
234
+ * The template context provides:
235
+ * - `$implicit` / `row`: The full row data object
236
+ *
237
+ * Import the directive in your component:
238
+ *
239
+ * ```typescript
240
+ * import { GridDetailView } from '@toolbox-web/grid-angular';
241
+ *
242
+ * @Component({
243
+ * imports: [GridDetailView],
244
+ * // ...
245
+ * })
246
+ * ```
247
+ */
248
+ class GridDetailView {
249
+ elementRef = inject((ElementRef));
250
+ /** Whether to show the expand/collapse column. Default: true */
251
+ showExpandColumn = input(true, ...(ngDevMode ? [{ debugName: "showExpandColumn" }] : []));
252
+ /** Animation style for expand/collapse. Default: 'slide' */
253
+ animation = input('slide', ...(ngDevMode ? [{ debugName: "animation" }] : []));
254
+ /**
255
+ * Query for the ng-template content child.
256
+ */
257
+ template = contentChild((TemplateRef), ...(ngDevMode ? [{ debugName: "template" }] : []));
258
+ /** Effect that triggers when the template is available */
259
+ onTemplateReceived = effect(() => {
260
+ const template = this.template();
261
+ if (template) {
262
+ // Register the template for this element
263
+ detailTemplateRegistry.set(this.elementRef.nativeElement, template);
264
+ }
265
+ }, ...(ngDevMode ? [{ debugName: "onTemplateReceived" }] : []));
266
+ /**
267
+ * Static type guard for template context.
268
+ * Enables type inference in templates.
269
+ */
270
+ static ngTemplateContextGuard(dir, ctx) {
271
+ return true;
272
+ }
273
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: GridDetailView, deps: [], target: i0.ɵɵFactoryTarget.Directive });
274
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "21.1.1", 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 });
275
+ }
276
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: GridDetailView, decorators: [{
277
+ type: Directive,
278
+ args: [{ selector: 'tbw-grid-detail' }]
279
+ }], 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 }] }] } });
280
+
281
+ // Symbol for storing form context on the grid element
282
+ const FORM_ARRAY_CONTEXT = Symbol('formArrayContext');
283
+ /**
284
+ * Gets the FormArrayContext from a grid element, if present.
285
+ * @internal
286
+ */
287
+ function getFormArrayContext(gridElement) {
288
+ return gridElement[FORM_ARRAY_CONTEXT];
289
+ }
290
+ /**
291
+ * Directive that binds a FormArray directly to the grid.
292
+ *
293
+ * This is the recommended way to integrate tbw-grid with Angular Reactive Forms.
294
+ * Use a FormArray of FormGroups for row-level validation and cell-level control access.
295
+ *
296
+ * ## Usage
297
+ *
298
+ * ```typescript
299
+ * import { Component, inject } from '@angular/core';
300
+ * import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
301
+ * import { Grid, GridFormArray } from '@toolbox-web/grid-angular';
302
+ *
303
+ * @Component({
304
+ * imports: [Grid, GridFormArray, ReactiveFormsModule],
305
+ * template: \`
306
+ * <form [formGroup]="form">
307
+ * <tbw-grid [formArray]="form.controls.rows" [columns]="columns" />
308
+ * </form>
309
+ * \`
310
+ * })
311
+ * export class MyComponent {
312
+ * private fb = inject(FormBuilder);
313
+ *
314
+ * form = this.fb.group({
315
+ * rows: this.fb.array([
316
+ * this.fb.group({ name: 'Alice', age: 30 }),
317
+ * this.fb.group({ name: 'Bob', age: 25 }),
318
+ * ])
319
+ * });
320
+ *
321
+ * columns = [
322
+ * { field: 'name', header: 'Name', editable: true },
323
+ * { field: 'age', header: 'Age', editable: true }
324
+ * ];
325
+ * }
326
+ * ```
327
+ *
328
+ * ## How It Works
329
+ *
330
+ * - **FormArray → Grid**: The grid displays the FormArray's value as rows
331
+ * - **Grid → FormArray**: When a cell is edited, the corresponding FormControl is updated
332
+ * - FormArrayContext is available for accessing cell-level controls
333
+ *
334
+ * ## Features
335
+ *
336
+ * - Works naturally with FormArray inside a FormGroup
337
+ * - Provides cell-level FormControl access for validation
338
+ * - Supports row-level validation state aggregation
339
+ * - Automatically syncs FormArray changes to the grid
340
+ */
341
+ class GridFormArray {
342
+ elementRef = inject((ElementRef));
343
+ cellCommitListener = null;
344
+ touchListener = null;
345
+ /**
346
+ * The FormArray to bind to the grid.
347
+ */
348
+ formArray = input.required(...(ngDevMode ? [{ debugName: "formArray" }] : []));
349
+ /**
350
+ * Effect that syncs the FormArray value to the grid rows.
351
+ */
352
+ syncFormArrayToGrid = effect(() => {
353
+ const formArray = this.formArray();
354
+ const grid = this.elementRef.nativeElement;
355
+ if (grid && formArray) {
356
+ // Get the raw value (including disabled controls)
357
+ grid.rows = formArray.getRawValue();
358
+ }
359
+ }, ...(ngDevMode ? [{ debugName: "syncFormArrayToGrid" }] : []));
360
+ ngOnInit() {
361
+ const grid = this.elementRef.nativeElement;
362
+ if (!grid)
363
+ return;
364
+ // Store the form context on the grid element for other directives to access
365
+ this.#storeFormContext(grid);
366
+ // Intercept cell-commit events to update the FormArray
367
+ this.cellCommitListener = (e) => {
368
+ const detail = e.detail;
369
+ this.#handleCellCommit(detail);
370
+ };
371
+ grid.addEventListener('cell-commit', this.cellCommitListener);
372
+ // Mark FormArray as touched on first interaction
373
+ this.touchListener = () => {
374
+ this.formArray().markAsTouched();
375
+ // Remove after first touch
376
+ if (this.touchListener) {
377
+ grid.removeEventListener('click', this.touchListener);
378
+ this.touchListener = null;
379
+ }
380
+ };
381
+ grid.addEventListener('click', this.touchListener);
382
+ }
383
+ ngOnDestroy() {
384
+ const grid = this.elementRef.nativeElement;
385
+ if (!grid)
386
+ return;
387
+ if (this.cellCommitListener) {
388
+ grid.removeEventListener('cell-commit', this.cellCommitListener);
389
+ }
390
+ if (this.touchListener) {
391
+ grid.removeEventListener('click', this.touchListener);
392
+ }
393
+ this.#clearFormContext(grid);
394
+ }
395
+ /**
396
+ * Checks if the FormArray contains FormGroups.
397
+ */
398
+ #isFormArrayOfFormGroups() {
399
+ const formArray = this.formArray();
400
+ if (formArray.length === 0)
401
+ return false;
402
+ return formArray.at(0) instanceof FormGroup;
403
+ }
404
+ /**
405
+ * Gets the FormGroup at a specific row index.
406
+ */
407
+ #getRowFormGroup(rowIndex) {
408
+ const formArray = this.formArray();
409
+ const rowControl = formArray.at(rowIndex);
410
+ return rowControl instanceof FormGroup ? rowControl : undefined;
411
+ }
412
+ /**
413
+ * Stores the FormArrayContext on the grid element.
414
+ */
415
+ #storeFormContext(grid) {
416
+ const getRowFormGroup = (rowIndex) => this.#getRowFormGroup(rowIndex);
417
+ const context = {
418
+ getRow: (rowIndex) => {
419
+ const formArray = this.formArray();
420
+ const rowControl = formArray.at(rowIndex);
421
+ return rowControl ? rowControl.value : null;
422
+ },
423
+ updateField: (rowIndex, field, value) => {
424
+ const rowFormGroup = getRowFormGroup(rowIndex);
425
+ if (rowFormGroup) {
426
+ const control = rowFormGroup.get(field);
427
+ if (control) {
428
+ control.setValue(value);
429
+ control.markAsDirty();
430
+ }
431
+ }
432
+ },
433
+ getValue: () => {
434
+ return this.formArray().getRawValue();
435
+ },
436
+ hasFormGroups: this.#isFormArrayOfFormGroups(),
437
+ getControl: (rowIndex, field) => {
438
+ const rowFormGroup = getRowFormGroup(rowIndex);
439
+ if (!rowFormGroup)
440
+ return undefined;
441
+ return rowFormGroup.get(field) ?? undefined;
442
+ },
443
+ getRowFormGroup,
444
+ isRowValid: (rowIndex) => {
445
+ const rowFormGroup = getRowFormGroup(rowIndex);
446
+ if (!rowFormGroup)
447
+ return true;
448
+ return rowFormGroup.valid;
449
+ },
450
+ isRowTouched: (rowIndex) => {
451
+ const rowFormGroup = getRowFormGroup(rowIndex);
452
+ if (!rowFormGroup)
453
+ return false;
454
+ return rowFormGroup.touched;
455
+ },
456
+ isRowDirty: (rowIndex) => {
457
+ const rowFormGroup = getRowFormGroup(rowIndex);
458
+ if (!rowFormGroup)
459
+ return false;
460
+ return rowFormGroup.dirty;
461
+ },
462
+ getRowErrors: (rowIndex) => {
463
+ const rowFormGroup = getRowFormGroup(rowIndex);
464
+ if (!rowFormGroup)
465
+ return null;
466
+ const errors = {};
467
+ let hasErrors = false;
468
+ Object.keys(rowFormGroup.controls).forEach((field) => {
469
+ const control = rowFormGroup.get(field);
470
+ if (control?.errors) {
471
+ errors[field] = control.errors;
472
+ hasErrors = true;
473
+ }
474
+ });
475
+ if (rowFormGroup.errors) {
476
+ errors['_group'] = rowFormGroup.errors;
477
+ hasErrors = true;
478
+ }
479
+ return hasErrors ? errors : null;
480
+ },
481
+ };
482
+ grid[FORM_ARRAY_CONTEXT] = context;
483
+ }
484
+ /**
485
+ * Clears the FormArrayContext from the grid element.
486
+ */
487
+ #clearFormContext(grid) {
488
+ delete grid[FORM_ARRAY_CONTEXT];
489
+ }
490
+ /**
491
+ * Handles cell-commit events by updating the FormControl in the FormGroup.
492
+ */
493
+ #handleCellCommit(detail) {
494
+ const { rowIndex, field, value } = detail;
495
+ const rowFormGroup = this.#getRowFormGroup(rowIndex);
496
+ if (rowFormGroup) {
497
+ const control = rowFormGroup.get(field);
498
+ if (control) {
499
+ control.setValue(value);
500
+ control.markAsDirty();
501
+ control.markAsTouched();
502
+ }
503
+ }
504
+ }
505
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: GridFormArray, deps: [], target: i0.ɵɵFactoryTarget.Directive });
506
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.1", type: GridFormArray, isStandalone: true, selector: "tbw-grid[formArray]", inputs: { formArray: { classPropertyName: "formArray", publicName: "formArray", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 });
507
+ }
508
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: GridFormArray, decorators: [{
509
+ type: Directive,
510
+ args: [{
511
+ selector: 'tbw-grid[formArray]',
512
+ }]
513
+ }], propDecorators: { formArray: [{ type: i0.Input, args: [{ isSignal: true, alias: "formArray", required: true }] }] } });
514
+
515
+ /**
516
+ * Registry to store responsive card templates by grid element.
517
+ * Used by AngularGridAdapter to create card renderers.
518
+ */
519
+ const responsiveCardTemplateRegistry = new Map();
520
+ /**
521
+ * Retrieves the responsive card template for a grid element.
522
+ *
523
+ * @param gridElement - The grid element to look up
524
+ * @returns The template reference or undefined if not found
525
+ */
526
+ function getResponsiveCardTemplate(gridElement) {
527
+ // Find the tbw-grid-responsive-card element inside the grid
528
+ const cardElement = gridElement.querySelector('tbw-grid-responsive-card');
529
+ if (!cardElement)
530
+ return undefined;
531
+ return responsiveCardTemplateRegistry.get(cardElement);
532
+ }
533
+ /**
534
+ * Directive for providing custom Angular templates for responsive card layout.
535
+ *
536
+ * Use this directive to define how each row should render when the grid
537
+ * is in responsive/mobile mode. The template receives the row data and index.
538
+ *
539
+ * ## Usage
540
+ *
541
+ * ```html
542
+ * <tbw-grid [rows]="employees">
543
+ * <tbw-grid-responsive-card>
544
+ * <ng-template let-employee let-idx="index">
545
+ * <div class="employee-card">
546
+ * <img [src]="employee.avatar" alt="">
547
+ * <div class="info">
548
+ * <strong>{{ employee.name }}</strong>
549
+ * <span>{{ employee.department }}</span>
550
+ * </div>
551
+ * </div>
552
+ * </ng-template>
553
+ * </tbw-grid-responsive-card>
554
+ * </tbw-grid>
555
+ * ```
556
+ *
557
+ * ## Important Notes
558
+ *
559
+ * - The ResponsivePlugin must be added to your grid config
560
+ * - The Grid directive will automatically configure the plugin's cardRenderer
561
+ * - Template context provides `$implicit` (row), `row`, and `index`
562
+ *
563
+ * @see ResponsivePlugin
564
+ */
565
+ class GridResponsiveCard {
566
+ elementRef = inject((ElementRef));
567
+ /**
568
+ * The ng-template containing the card content.
569
+ */
570
+ template = contentChild((TemplateRef), ...(ngDevMode ? [{ debugName: "template" }] : []));
571
+ /**
572
+ * Effect that registers the template when it becomes available.
573
+ */
574
+ onTemplateReceived = effect(() => {
575
+ const template = this.template();
576
+ if (template) {
577
+ responsiveCardTemplateRegistry.set(this.elementRef.nativeElement, template);
578
+ }
579
+ }, ...(ngDevMode ? [{ debugName: "onTemplateReceived" }] : []));
580
+ /**
581
+ * Type guard for template context inference.
582
+ */
583
+ static ngTemplateContextGuard(_directive, context) {
584
+ return true;
585
+ }
586
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: GridResponsiveCard, deps: [], target: i0.ɵɵFactoryTarget.Directive });
587
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "21.1.1", type: GridResponsiveCard, isStandalone: true, selector: "tbw-grid-responsive-card", queries: [{ propertyName: "template", first: true, predicate: (TemplateRef), descendants: true, isSignal: true }], ngImport: i0 });
588
+ }
589
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: GridResponsiveCard, decorators: [{
590
+ type: Directive,
591
+ args: [{
592
+ selector: 'tbw-grid-responsive-card',
593
+ }]
594
+ }], propDecorators: { template: [{ type: i0.ContentChild, args: [i0.forwardRef(() => TemplateRef), { isSignal: true }] }] } });
595
+
596
+ // Global registry mapping DOM elements to their templates
597
+ const toolPanelTemplateRegistry = new Map();
598
+ /**
599
+ * Gets the tool panel template registered for a given tool panel element.
600
+ * Used by AngularGridAdapter to retrieve templates at render time.
601
+ */
602
+ function getToolPanelTemplate(panelElement) {
603
+ return toolPanelTemplateRegistry.get(panelElement);
604
+ }
605
+ /**
606
+ * Gets all tool panel elements with registered templates within a grid element.
607
+ */
608
+ function getToolPanelElements(gridElement) {
609
+ const panelElements = gridElement.querySelectorAll('tbw-grid-tool-panel');
610
+ return Array.from(panelElements).filter((el) => toolPanelTemplateRegistry.has(el));
611
+ }
612
+ /**
613
+ * Directive that captures an `<ng-template>` for use as a custom tool panel.
614
+ *
615
+ * This enables declarative Angular component usage for tool panels
616
+ * that appear in the grid's side panel.
617
+ *
618
+ * ## Usage
619
+ *
620
+ * ```html
621
+ * <tbw-grid [rows]="rows" [gridConfig]="config">
622
+ * <tbw-grid-tool-panel
623
+ * id="quick-filters"
624
+ * title="Quick Filters"
625
+ * icon="🔍"
626
+ * tooltip="Apply quick filters"
627
+ * [order]="10"
628
+ * >
629
+ * <ng-template let-grid>
630
+ * <app-quick-filters [grid]="grid" />
631
+ * </ng-template>
632
+ * </tbw-grid-tool-panel>
633
+ * </tbw-grid>
634
+ * ```
635
+ *
636
+ * The template context provides:
637
+ * - `$implicit` / `grid`: The grid element reference
638
+ *
639
+ * ### Attributes
640
+ *
641
+ * - `id` (required): Unique identifier for the panel
642
+ * - `title` (required): Panel title shown in accordion header
643
+ * - `icon`: Icon for accordion section header (emoji or text)
644
+ * - `tooltip`: Tooltip for accordion section header
645
+ * - `order`: Panel order priority (lower = first, default: 100)
646
+ *
647
+ * Import the directive in your component:
648
+ *
649
+ * ```typescript
650
+ * import { GridToolPanel } from '@toolbox-web/grid-angular';
651
+ *
652
+ * @Component({
653
+ * imports: [GridToolPanel],
654
+ * // ...
655
+ * })
656
+ * ```
657
+ */
658
+ class GridToolPanel {
659
+ elementRef = inject((ElementRef));
660
+ /** Unique panel identifier (required) */
661
+ id = input.required({ ...(ngDevMode ? { debugName: "id" } : {}), alias: 'id' });
662
+ /** Panel title shown in accordion header (required) */
663
+ title = input.required({ ...(ngDevMode ? { debugName: "title" } : {}), alias: 'title' });
664
+ /** Icon for accordion section header (emoji or text) */
665
+ icon = input(...(ngDevMode ? [undefined, { debugName: "icon" }] : []));
666
+ /** Tooltip for accordion section header */
667
+ tooltip = input(...(ngDevMode ? [undefined, { debugName: "tooltip" }] : []));
668
+ /** Panel order priority (lower = first, default: 100) */
669
+ order = input(100, ...(ngDevMode ? [{ debugName: "order" }] : []));
670
+ /**
671
+ * Query for the ng-template content child.
672
+ */
673
+ template = contentChild((TemplateRef), ...(ngDevMode ? [{ debugName: "template" }] : []));
674
+ /** Effect that triggers when the template is available */
675
+ onTemplateReceived = effect(() => {
676
+ const template = this.template();
677
+ const element = this.elementRef.nativeElement;
678
+ if (template) {
679
+ // Set attributes from inputs (for light DOM parsing to read)
680
+ element.setAttribute('id', this.id());
681
+ element.setAttribute('title', this.title());
682
+ const icon = this.icon();
683
+ if (icon)
684
+ element.setAttribute('icon', icon);
685
+ const tooltip = this.tooltip();
686
+ if (tooltip)
687
+ element.setAttribute('tooltip', tooltip);
688
+ element.setAttribute('order', String(this.order()));
689
+ // Register the template for this element
690
+ toolPanelTemplateRegistry.set(element, template);
691
+ }
692
+ }, ...(ngDevMode ? [{ debugName: "onTemplateReceived" }] : []));
693
+ /**
694
+ * Static type guard for template context.
695
+ * Enables type inference in templates.
696
+ */
697
+ static ngTemplateContextGuard(dir, ctx) {
698
+ return true;
699
+ }
700
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: GridToolPanel, deps: [], target: i0.ɵɵFactoryTarget.Directive });
701
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "21.1.1", type: GridToolPanel, isStandalone: true, selector: "tbw-grid-tool-panel", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: true, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, tooltip: { classPropertyName: "tooltip", publicName: "tooltip", isSignal: true, isRequired: false, transformFunction: null }, order: { classPropertyName: "order", publicName: "order", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "template", first: true, predicate: (TemplateRef), descendants: true, isSignal: true }], ngImport: i0 });
702
+ }
703
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: GridToolPanel, decorators: [{
704
+ type: Directive,
705
+ args: [{ selector: 'tbw-grid-tool-panel' }]
706
+ }], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: true }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], tooltip: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltip", required: false }] }], order: [{ type: i0.Input, args: [{ isSignal: true, alias: "order", required: false }] }], template: [{ type: i0.ContentChild, args: [i0.forwardRef(() => TemplateRef), { isSignal: true }] }] } });
707
+
708
+ // Registries for structural directive templates
709
+ const structuralViewRegistry = new Map();
710
+ const structuralEditorRegistry = new Map();
711
+ /**
712
+ * Gets the view template registered by the structural directive for a given column element.
713
+ * Falls back to the non-structural directive registry.
714
+ */
715
+ function getStructuralViewTemplate(columnElement) {
716
+ // First check structural directive registry
717
+ const template = structuralViewRegistry.get(columnElement);
718
+ if (template)
719
+ return template;
720
+ // Fall back to the nested element registry
721
+ const viewEl = columnElement.querySelector('tbw-grid-column-view');
722
+ if (viewEl) {
723
+ return getViewTemplate(viewEl);
724
+ }
725
+ return undefined;
726
+ }
727
+ /**
728
+ * Gets the editor template registered by the structural directive for a given column element.
729
+ * Falls back to the non-structural directive registry.
730
+ */
731
+ function getStructuralEditorTemplate(columnElement) {
732
+ // First check structural directive registry
733
+ const template = structuralEditorRegistry.get(columnElement);
734
+ if (template)
735
+ return template;
736
+ // Fall back to the nested element registry
737
+ const editorEl = columnElement.querySelector('tbw-grid-column-editor');
738
+ if (editorEl) {
739
+ return getEditorTemplate(editorEl);
740
+ }
741
+ return undefined;
742
+ }
743
+ /**
744
+ * Structural directive for cell view rendering.
745
+ *
746
+ * This provides a cleaner syntax for defining custom cell renderers without
747
+ * the nested `<tbw-grid-column-view>` and `<ng-template>` boilerplate.
748
+ *
749
+ * ## Usage
750
+ *
751
+ * ```html
752
+ * <!-- Instead of this verbose syntax: -->
753
+ * <tbw-grid-column field="status">
754
+ * <tbw-grid-column-view>
755
+ * <ng-template let-value let-row="row">
756
+ * <app-status-badge [value]="value" />
757
+ * </ng-template>
758
+ * </tbw-grid-column-view>
759
+ * </tbw-grid-column>
760
+ *
761
+ * <!-- Use this cleaner syntax: -->
762
+ * <tbw-grid-column field="status">
763
+ * <app-status-badge *tbwRenderer="let value; row as row" [value]="value" />
764
+ * </tbw-grid-column>
765
+ * ```
766
+ *
767
+ * ## Template Context
768
+ *
769
+ * The structural directive provides the same context as `GridColumnView`:
770
+ * - `$implicit` / `value`: The cell value (use `let value` or `let-value`)
771
+ * - `row`: The full row data object (use `row as row` or `let-row="row"`)
772
+ * - `column`: The column configuration
773
+ *
774
+ * ## Import
775
+ *
776
+ * ```typescript
777
+ * import { TbwRenderer } from '@toolbox-web/grid-angular';
778
+ *
779
+ * @Component({
780
+ * imports: [TbwRenderer],
781
+ * // ...
782
+ * })
783
+ * ```
784
+ */
785
+ class TbwRenderer {
786
+ template = inject((TemplateRef));
787
+ elementRef = inject((ElementRef));
788
+ columnElement = null;
789
+ constructor() {
790
+ // Angular structural directives wrap the host element in a comment node.
791
+ // We need to find the parent tbw-grid-column element.
792
+ // Since we're injected into the template, we use an effect to register once the DOM is stable.
793
+ effect(() => {
794
+ this.registerTemplate();
795
+ });
796
+ }
797
+ registerTemplate() {
798
+ // Find the parent tbw-grid-column element
799
+ // The template's host element may not be in the DOM yet, so we traverse from the comment node
800
+ let parent = this.elementRef.nativeElement?.parentElement;
801
+ while (parent && parent.tagName !== 'TBW-GRID-COLUMN') {
802
+ parent = parent.parentElement;
803
+ }
804
+ if (parent) {
805
+ this.columnElement = parent;
806
+ structuralViewRegistry.set(parent, this.template);
807
+ }
808
+ }
809
+ ngOnDestroy() {
810
+ if (this.columnElement) {
811
+ structuralViewRegistry.delete(this.columnElement);
812
+ }
813
+ }
814
+ /**
815
+ * Static type guard for template context.
816
+ * Uses `any` defaults for ergonomic template usage.
817
+ */
818
+ static ngTemplateContextGuard(dir, ctx) {
819
+ return true;
820
+ }
821
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: TbwRenderer, deps: [], target: i0.ɵɵFactoryTarget.Directive });
822
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.1", type: TbwRenderer, isStandalone: true, selector: "[tbwRenderer]", ngImport: i0 });
823
+ }
824
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: TbwRenderer, decorators: [{
825
+ type: Directive,
826
+ args: [{ selector: '[tbwRenderer]' }]
827
+ }], ctorParameters: () => [] });
828
+ /**
829
+ * Structural directive for cell editor rendering.
830
+ *
831
+ * This provides a cleaner syntax for defining custom cell editors without
832
+ * the nested `<tbw-grid-column-editor>` and `<ng-template>` boilerplate.
833
+ *
834
+ * ## Usage
835
+ *
836
+ * ```html
837
+ * <!-- Instead of this verbose syntax: -->
838
+ * <tbw-grid-column field="status">
839
+ * <tbw-grid-column-editor>
840
+ * <ng-template let-value let-onCommit="onCommit" let-onCancel="onCancel">
841
+ * <app-status-editor [value]="value" (commit)="onCommit($event)" (cancel)="onCancel()" />
842
+ * </ng-template>
843
+ * </tbw-grid-column-editor>
844
+ * </tbw-grid-column>
845
+ *
846
+ * <!-- Use this cleaner syntax (with auto-wiring - no explicit bindings needed!): -->
847
+ * <tbw-grid-column field="status">
848
+ * <app-status-editor *tbwEditor="let value" [value]="value" />
849
+ * </tbw-grid-column>
850
+ * ```
851
+ *
852
+ * ## Template Context
853
+ *
854
+ * The structural directive provides the same context as `GridColumnEditor`:
855
+ * - `$implicit` / `value`: The cell value
856
+ * - `row`: The full row data object
857
+ * - `column`: The column configuration
858
+ * - `onCommit`: Callback function to commit the new value (optional - auto-wired if component emits `commit` event)
859
+ * - `onCancel`: Callback function to cancel editing (optional - auto-wired if component emits `cancel` event)
860
+ *
861
+ * ## Import
862
+ *
863
+ * ```typescript
864
+ * import { TbwEditor } from '@toolbox-web/grid-angular';
865
+ *
866
+ * @Component({
867
+ * imports: [TbwEditor],
868
+ * // ...
869
+ * })
870
+ * ```
871
+ */
872
+ class TbwEditor {
873
+ template = inject((TemplateRef));
874
+ elementRef = inject((ElementRef));
875
+ columnElement = null;
876
+ constructor() {
877
+ effect(() => {
878
+ this.registerTemplate();
879
+ });
880
+ }
881
+ registerTemplate() {
882
+ // Find the parent tbw-grid-column element
883
+ let parent = this.elementRef.nativeElement?.parentElement;
884
+ while (parent && parent.tagName !== 'TBW-GRID-COLUMN') {
885
+ parent = parent.parentElement;
886
+ }
887
+ if (parent) {
888
+ this.columnElement = parent;
889
+ structuralEditorRegistry.set(parent, this.template);
890
+ }
891
+ }
892
+ ngOnDestroy() {
893
+ if (this.columnElement) {
894
+ structuralEditorRegistry.delete(this.columnElement);
895
+ }
896
+ }
897
+ /**
898
+ * Static type guard for template context.
899
+ * Uses `any` defaults for ergonomic template usage.
900
+ */
901
+ static ngTemplateContextGuard(dir, ctx) {
902
+ return true;
903
+ }
904
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: TbwEditor, deps: [], target: i0.ɵɵFactoryTarget.Directive });
905
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.1", type: TbwEditor, isStandalone: true, selector: "[tbwEditor]", ngImport: i0 });
906
+ }
907
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: TbwEditor, decorators: [{
908
+ type: Directive,
909
+ args: [{ selector: '[tbwEditor]' }]
910
+ }], ctorParameters: () => [] });
911
+
912
+ /**
913
+ * Type-level default registry for Angular applications.
914
+ *
915
+ * Provides application-wide type defaults for renderers and editors
916
+ * that all grids inherit automatically.
917
+ */
918
+ /**
919
+ * Injection token for providing type defaults at app level.
920
+ */
921
+ const GRID_TYPE_DEFAULTS = new InjectionToken('GRID_TYPE_DEFAULTS');
922
+ /**
923
+ * Injectable service for managing type-level defaults.
924
+ *
925
+ * Use `provideGridTypeDefaults()` in your app config to set up defaults,
926
+ * or inject this service for dynamic registration.
927
+ *
928
+ * @example
929
+ * ```typescript
930
+ * // App-level setup (app.config.ts)
931
+ * export const appConfig: ApplicationConfig = {
932
+ * providers: [
933
+ * provideGridTypeDefaults({
934
+ * country: {
935
+ * renderer: CountryCellComponent,
936
+ * editor: CountryEditorComponent
937
+ * },
938
+ * status: {
939
+ * renderer: StatusBadgeComponent
940
+ * }
941
+ * })
942
+ * ]
943
+ * };
944
+ *
945
+ * // Dynamic registration
946
+ * @Component({ ... })
947
+ * export class AppComponent {
948
+ * private registry = inject(GridTypeRegistry);
949
+ *
950
+ * ngOnInit() {
951
+ * this.registry.register('currency', {
952
+ * renderer: CurrencyCellComponent
953
+ * });
954
+ * }
955
+ * }
956
+ * ```
957
+ */
958
+ class GridTypeRegistry {
959
+ defaults = new Map();
960
+ constructor() {
961
+ // Merge any initial defaults from provider
962
+ const initial = inject(GRID_TYPE_DEFAULTS, { optional: true });
963
+ if (initial) {
964
+ for (const [type, config] of Object.entries(initial)) {
965
+ this.defaults.set(type, config);
966
+ }
967
+ }
968
+ }
969
+ /**
970
+ * Register type-level defaults for a custom type.
971
+ *
972
+ * @param type - The type name (e.g., 'country', 'currency')
973
+ * @param defaults - Renderer/editor configuration
974
+ */
975
+ register(type, defaults) {
976
+ this.defaults.set(type, defaults);
977
+ }
978
+ /**
979
+ * Get type defaults for a given type.
980
+ */
981
+ get(type) {
982
+ return this.defaults.get(type);
983
+ }
984
+ /**
985
+ * Remove type defaults for a type.
986
+ */
987
+ unregister(type) {
988
+ this.defaults.delete(type);
989
+ }
990
+ /**
991
+ * Check if a type has registered defaults.
992
+ */
993
+ has(type) {
994
+ return this.defaults.has(type);
995
+ }
996
+ /**
997
+ * Get all registered type names.
998
+ */
999
+ getRegisteredTypes() {
1000
+ return Array.from(this.defaults.keys());
1001
+ }
1002
+ /**
1003
+ * Convert to TypeDefault for use with grid's typeDefaults.
1004
+ * This is used internally by the adapter.
1005
+ *
1006
+ * @internal
1007
+ */
1008
+ getAsTypeDefault(type) {
1009
+ const config = this.defaults.get(type);
1010
+ if (!config)
1011
+ return undefined;
1012
+ // Note: The actual renderer/editor functions are created by the adapter
1013
+ // when it calls getTypeDefault() - we just return the config here
1014
+ return {
1015
+ editorParams: config.editorParams,
1016
+ // renderer and editor are handled by the adapter which creates
1017
+ // the actual functions that instantiate Angular components
1018
+ };
1019
+ }
1020
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: GridTypeRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1021
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: GridTypeRegistry, providedIn: 'root' });
1022
+ }
1023
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: GridTypeRegistry, decorators: [{
1024
+ type: Injectable,
1025
+ args: [{ providedIn: 'root' }]
1026
+ }], ctorParameters: () => [] });
1027
+ /**
1028
+ * Provides application-level type defaults for all grids.
1029
+ *
1030
+ * @example
1031
+ * ```typescript
1032
+ * // app.config.ts
1033
+ * import { provideGridTypeDefaults } from '@toolbox-web/grid-angular';
1034
+ * import { CountryCellComponent, StatusBadgeComponent } from './components';
1035
+ *
1036
+ * export const appConfig: ApplicationConfig = {
1037
+ * providers: [
1038
+ * provideGridTypeDefaults({
1039
+ * country: { renderer: CountryCellComponent },
1040
+ * status: { renderer: StatusBadgeComponent },
1041
+ * date: { editor: DatePickerComponent }
1042
+ * })
1043
+ * ]
1044
+ * };
1045
+ * ```
1046
+ */
1047
+ function provideGridTypeDefaults(defaults) {
1048
+ return makeEnvironmentProviders([{ provide: GRID_TYPE_DEFAULTS, useValue: defaults }]);
1049
+ }
1050
+
1051
+ /**
1052
+ * Helper to get view template from either structural directive or nested directive.
1053
+ */
1054
+ function getAnyViewTemplate(element) {
1055
+ // First check structural directive registry (for *tbwRenderer syntax)
1056
+ const structuralTemplate = getStructuralViewTemplate(element);
1057
+ if (structuralTemplate)
1058
+ return structuralTemplate;
1059
+ // Fall back to nested directive (for <tbw-grid-column-view> syntax)
1060
+ return getViewTemplate(element);
1061
+ }
1062
+ /**
1063
+ * Helper to get editor template from either structural directive or nested directive.
1064
+ */
1065
+ function getAnyEditorTemplate(element) {
1066
+ // First check structural directive registry (for *tbwEditor syntax)
1067
+ // The structural context uses `any` types for better ergonomics, but is compatible with GridEditorContext
1068
+ const structuralTemplate = getStructuralEditorTemplate(element);
1069
+ if (structuralTemplate)
1070
+ return structuralTemplate;
1071
+ // Fall back to nested directive (for <tbw-grid-column-editor> syntax)
1072
+ return getEditorTemplate(element);
1073
+ }
1074
+ /**
1075
+ * Framework adapter that enables zero-boilerplate integration of Angular components
1076
+ * with the grid's light DOM configuration API.
1077
+ *
1078
+ * ## Usage
1079
+ *
1080
+ * **One-time setup in your app:**
1081
+ * ```typescript
1082
+ * import { Component, inject, EnvironmentInjector, ApplicationRef, ViewContainerRef } from '@angular/core';
1083
+ * import { GridElement } from '@toolbox-web/grid';
1084
+ * import { AngularGridAdapter } from '@toolbox-web/grid-angular';
1085
+ *
1086
+ * @Component({
1087
+ * selector: 'app-root',
1088
+ * // ...
1089
+ * })
1090
+ * export class AppComponent {
1091
+ * constructor() {
1092
+ * const injector = inject(EnvironmentInjector);
1093
+ * const appRef = inject(ApplicationRef);
1094
+ * const viewContainerRef = inject(ViewContainerRef);
1095
+ * GridElement.registerAdapter(new AngularGridAdapter(injector, appRef, viewContainerRef));
1096
+ * }
1097
+ * }
1098
+ * ```
1099
+ *
1100
+ * **Declarative configuration in templates (structural directive - recommended):**
1101
+ * ```html
1102
+ * <tbw-grid>
1103
+ * <tbw-grid-column field="status">
1104
+ * <app-status-badge *tbwRenderer="let value; row as row" [value]="value" />
1105
+ * <app-status-editor *tbwEditor="let value" [value]="value" />
1106
+ * </tbw-grid-column>
1107
+ * </tbw-grid>
1108
+ * ```
1109
+ *
1110
+ * **Declarative configuration in templates (nested directive - legacy):**
1111
+ * ```html
1112
+ * <tbw-grid>
1113
+ * <tbw-grid-column field="status">
1114
+ * <tbw-grid-column-view>
1115
+ * <ng-template let-value let-row="row">
1116
+ * <app-status-badge [value]="value" [row]="row" />
1117
+ * </ng-template>
1118
+ * </tbw-grid-column-view>
1119
+ * <tbw-grid-column-editor>
1120
+ * <ng-template let-value let-onCommit="onCommit" let-onCancel="onCancel">
1121
+ * <app-status-select [value]="value" (commit)="onCommit($event)" (cancel)="onCancel()" />
1122
+ * </ng-template>
1123
+ * </tbw-grid-column-editor>
1124
+ * </tbw-grid-column>
1125
+ * </tbw-grid>
1126
+ * ```
1127
+ *
1128
+ * The adapter automatically:
1129
+ * - Detects Angular templates registered by directives (both structural and nested)
1130
+ * - Creates embedded views with cell context (value, row, column)
1131
+ * - Handles editor callbacks (onCommit/onCancel)
1132
+ * - Manages view lifecycle and change detection
1133
+ */
1134
+ class AngularGridAdapter {
1135
+ injector;
1136
+ appRef;
1137
+ viewContainerRef;
1138
+ viewRefs = [];
1139
+ componentRefs = [];
1140
+ typeRegistry = null;
1141
+ constructor(injector, appRef, viewContainerRef) {
1142
+ this.injector = injector;
1143
+ this.appRef = appRef;
1144
+ this.viewContainerRef = viewContainerRef;
1145
+ // Register globally for directive access
1146
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1147
+ window.__ANGULAR_GRID_ADAPTER__ = this;
1148
+ // Try to get the type registry from the injector
1149
+ try {
1150
+ this.typeRegistry = this.injector.get(GridTypeRegistry, null);
1151
+ }
1152
+ catch {
1153
+ // GridTypeRegistry not available - type defaults won't be resolved
1154
+ }
1155
+ }
1156
+ /**
1157
+ * Processes an Angular grid configuration, converting component class references
1158
+ * to actual renderer/editor functions.
1159
+ *
1160
+ * Call this method on your gridConfig before passing it to the grid.
1161
+ *
1162
+ * @example
1163
+ * ```typescript
1164
+ * import { AngularGridAdapter, type AngularGridConfig } from '@toolbox-web/grid-angular';
1165
+ *
1166
+ * const config: AngularGridConfig<Employee> = {
1167
+ * columns: [
1168
+ * { field: 'status', renderer: StatusBadgeComponent, editor: StatusEditorComponent },
1169
+ * ],
1170
+ * };
1171
+ *
1172
+ * // In component
1173
+ * constructor() {
1174
+ * const adapter = inject(AngularGridAdapter); // or create new instance
1175
+ * this.processedConfig = adapter.processGridConfig(config);
1176
+ * }
1177
+ * ```
1178
+ *
1179
+ * @param config - Angular grid configuration with possible component class references
1180
+ * @returns Processed GridConfig with actual renderer/editor functions
1181
+ */
1182
+ processGridConfig(config) {
1183
+ if (!config.columns) {
1184
+ return config;
1185
+ }
1186
+ const processedColumns = config.columns.map((col) => this.processColumn(col));
1187
+ return {
1188
+ ...config,
1189
+ columns: processedColumns,
1190
+ };
1191
+ }
1192
+ /**
1193
+ * Processes a single column configuration, converting component class references
1194
+ * to actual renderer/editor functions.
1195
+ *
1196
+ * @param column - Angular column configuration
1197
+ * @returns Processed ColumnConfig
1198
+ */
1199
+ processColumn(column) {
1200
+ const processed = { ...column };
1201
+ // Convert renderer component class to function
1202
+ if (column.renderer && isComponentClass(column.renderer)) {
1203
+ processed.renderer = this.createComponentRenderer(column.renderer);
1204
+ }
1205
+ // Convert editor component class to function
1206
+ if (column.editor && isComponentClass(column.editor)) {
1207
+ processed.editor = this.createComponentEditor(column.editor);
1208
+ }
1209
+ return processed;
1210
+ }
1211
+ /**
1212
+ * Determines if this adapter can handle the given element.
1213
+ * Checks if a template is registered for this element (structural or nested).
1214
+ */
1215
+ canHandle(element) {
1216
+ return getAnyViewTemplate(element) !== undefined || getAnyEditorTemplate(element) !== undefined;
1217
+ }
1218
+ /**
1219
+ * Creates a view renderer function that creates an embedded view
1220
+ * from the registered template and returns its DOM element.
1221
+ *
1222
+ * Returns undefined if no template is registered for this element,
1223
+ * allowing the grid to use its default rendering.
1224
+ */
1225
+ createRenderer(element) {
1226
+ const template = getAnyViewTemplate(element);
1227
+ if (!template) {
1228
+ // Return undefined so the grid uses default rendering
1229
+ // This is important when only an editor template is provided (no view template)
1230
+ return undefined;
1231
+ }
1232
+ return (ctx) => {
1233
+ // Create the context for the template
1234
+ const context = {
1235
+ $implicit: ctx.value,
1236
+ value: ctx.value,
1237
+ row: ctx.row,
1238
+ column: ctx.column,
1239
+ };
1240
+ // Create embedded view from template
1241
+ const viewRef = this.viewContainerRef.createEmbeddedView(template, context);
1242
+ this.viewRefs.push(viewRef);
1243
+ // Trigger change detection
1244
+ viewRef.detectChanges();
1245
+ // Get the first root node (the component's host element)
1246
+ const rootNode = viewRef.rootNodes[0];
1247
+ return rootNode;
1248
+ };
1249
+ }
1250
+ /**
1251
+ * Creates an editor spec that creates an embedded view.
1252
+ *
1253
+ * **Auto-wiring**: The adapter automatically listens for `commit` and `cancel`
1254
+ * CustomEvents on the rendered component. If the component emits these events,
1255
+ * the adapter will call the grid's commit/cancel functions automatically.
1256
+ *
1257
+ * This means templates can be simplified from:
1258
+ * ```html
1259
+ * <app-editor *tbwEditor="let value; onCommit as onCommit"
1260
+ * [value]="value" (commit)="onCommit($event)" />
1261
+ * ```
1262
+ * To just:
1263
+ * ```html
1264
+ * <app-editor *tbwEditor="let value" [value]="value" />
1265
+ * ```
1266
+ * As long as the component emits `(commit)` with the new value.
1267
+ */
1268
+ createEditor(element) {
1269
+ const template = getAnyEditorTemplate(element);
1270
+ // Find the parent grid element for FormArray context access
1271
+ const gridElement = element.closest('tbw-grid');
1272
+ if (!template) {
1273
+ // No warning - this can happen during early initialization before Angular
1274
+ // registers structural directive templates. The grid will re-parse columns
1275
+ // after templates are registered.
1276
+ return () => document.createElement('div');
1277
+ }
1278
+ return (ctx) => {
1279
+ // Create simple callback functions (preferred)
1280
+ const onCommit = (value) => ctx.commit(value);
1281
+ const onCancel = () => ctx.cancel();
1282
+ // Create EventEmitters for backwards compatibility (deprecated)
1283
+ const commitEmitter = new EventEmitter();
1284
+ const cancelEmitter = new EventEmitter();
1285
+ commitEmitter.subscribe((value) => ctx.commit(value));
1286
+ cancelEmitter.subscribe(() => ctx.cancel());
1287
+ // Try to get the FormControl from the FormArrayContext
1288
+ let control;
1289
+ if (gridElement) {
1290
+ const formContext = getFormArrayContext(gridElement);
1291
+ if (formContext?.hasFormGroups) {
1292
+ // Find the row index by looking up ctx.row in the grid's rows
1293
+ const gridRows = gridElement.rows;
1294
+ if (gridRows) {
1295
+ const rowIndex = gridRows.indexOf(ctx.row);
1296
+ if (rowIndex >= 0) {
1297
+ control = formContext.getControl(rowIndex, ctx.field);
1298
+ }
1299
+ }
1300
+ }
1301
+ }
1302
+ // Create the context for the template
1303
+ const context = {
1304
+ $implicit: ctx.value,
1305
+ value: ctx.value,
1306
+ row: ctx.row,
1307
+ column: ctx.column,
1308
+ // Preferred: simple callback functions
1309
+ onCommit,
1310
+ onCancel,
1311
+ // FormControl from FormArray (if available)
1312
+ control,
1313
+ // Deprecated: EventEmitters (for backwards compatibility)
1314
+ commit: commitEmitter,
1315
+ cancel: cancelEmitter,
1316
+ };
1317
+ // Create embedded view from template
1318
+ const viewRef = this.viewContainerRef.createEmbeddedView(template, context);
1319
+ this.viewRefs.push(viewRef);
1320
+ // Trigger change detection
1321
+ viewRef.detectChanges();
1322
+ // Get the first root node (the component's host element)
1323
+ const rootNode = viewRef.rootNodes[0];
1324
+ // Auto-wire: Listen for commit/cancel events on the rendered component.
1325
+ // This allows components to just emit (commit) and (cancel) without
1326
+ // requiring explicit template bindings like (commit)="onCommit($event)".
1327
+ if (rootNode && rootNode.addEventListener) {
1328
+ rootNode.addEventListener('commit', (e) => {
1329
+ const customEvent = e;
1330
+ ctx.commit(customEvent.detail);
1331
+ });
1332
+ rootNode.addEventListener('cancel', () => {
1333
+ ctx.cancel();
1334
+ });
1335
+ }
1336
+ return rootNode;
1337
+ };
1338
+ }
1339
+ /**
1340
+ * Creates a detail renderer function for MasterDetailPlugin.
1341
+ * Renders Angular templates for expandable detail rows.
1342
+ */
1343
+ createDetailRenderer(gridElement) {
1344
+ const template = getDetailTemplate(gridElement);
1345
+ if (!template) {
1346
+ return undefined;
1347
+ }
1348
+ return (row) => {
1349
+ // Create the context for the template
1350
+ const context = {
1351
+ $implicit: row,
1352
+ row: row,
1353
+ };
1354
+ // Create embedded view from template
1355
+ const viewRef = this.viewContainerRef.createEmbeddedView(template, context);
1356
+ this.viewRefs.push(viewRef);
1357
+ // Trigger change detection
1358
+ viewRef.detectChanges();
1359
+ // Create a container for the root nodes
1360
+ const container = document.createElement('div');
1361
+ viewRef.rootNodes.forEach((node) => container.appendChild(node));
1362
+ return container;
1363
+ };
1364
+ }
1365
+ /**
1366
+ * Framework adapter hook called by MasterDetailPlugin during attach().
1367
+ * Parses the <tbw-grid-detail> element and returns an Angular template-based renderer.
1368
+ *
1369
+ * This enables MasterDetailPlugin to automatically use Angular templates
1370
+ * without manual configuration in the Grid directive.
1371
+ */
1372
+ parseDetailElement(detailElement) {
1373
+ // Get the template from the registry for this detail element
1374
+ const template = getDetailTemplate(detailElement.closest('tbw-grid'));
1375
+ if (!template) {
1376
+ return undefined;
1377
+ }
1378
+ // Return a renderer function that creates embedded views
1379
+ // Note: rowIndex is part of the MasterDetailPlugin detailRenderer signature but not needed here
1380
+ return (row) => {
1381
+ const context = {
1382
+ $implicit: row,
1383
+ row: row,
1384
+ };
1385
+ const viewRef = this.viewContainerRef.createEmbeddedView(template, context);
1386
+ this.viewRefs.push(viewRef);
1387
+ viewRef.detectChanges();
1388
+ const container = document.createElement('div');
1389
+ viewRef.rootNodes.forEach((node) => container.appendChild(node));
1390
+ return container;
1391
+ };
1392
+ }
1393
+ /**
1394
+ * Creates a responsive card renderer function for ResponsivePlugin.
1395
+ * Renders Angular templates for card layout in responsive mode.
1396
+ *
1397
+ * @param gridElement - The grid element to look up the template for
1398
+ * @returns A card renderer function or undefined if no template is found
1399
+ */
1400
+ createResponsiveCardRenderer(gridElement) {
1401
+ const template = getResponsiveCardTemplate(gridElement);
1402
+ if (!template) {
1403
+ return undefined;
1404
+ }
1405
+ return (row, rowIndex) => {
1406
+ // Create the context for the template
1407
+ const context = {
1408
+ $implicit: row,
1409
+ row: row,
1410
+ index: rowIndex,
1411
+ };
1412
+ // Create embedded view from template
1413
+ const viewRef = this.viewContainerRef.createEmbeddedView(template, context);
1414
+ this.viewRefs.push(viewRef);
1415
+ // Trigger change detection
1416
+ viewRef.detectChanges();
1417
+ // Create a container for the root nodes
1418
+ const container = document.createElement('div');
1419
+ viewRef.rootNodes.forEach((node) => container.appendChild(node));
1420
+ return container;
1421
+ };
1422
+ }
1423
+ /**
1424
+ * Creates a tool panel renderer from a light DOM element.
1425
+ * The renderer creates an Angular template-based panel content.
1426
+ */
1427
+ createToolPanelRenderer(element) {
1428
+ const template = getToolPanelTemplate(element);
1429
+ if (!template) {
1430
+ return undefined;
1431
+ }
1432
+ // Find the parent grid element for context
1433
+ const gridElement = element.closest('tbw-grid');
1434
+ return (container) => {
1435
+ // Create the context for the template
1436
+ const context = {
1437
+ $implicit: gridElement ?? container,
1438
+ grid: gridElement ?? container,
1439
+ };
1440
+ // Create embedded view from template
1441
+ const viewRef = this.viewContainerRef.createEmbeddedView(template, context);
1442
+ this.viewRefs.push(viewRef);
1443
+ // Trigger change detection
1444
+ viewRef.detectChanges();
1445
+ // Append all root nodes to the container
1446
+ viewRef.rootNodes.forEach((node) => container.appendChild(node));
1447
+ // Return cleanup function
1448
+ return () => {
1449
+ const index = this.viewRefs.indexOf(viewRef);
1450
+ if (index > -1) {
1451
+ this.viewRefs.splice(index, 1);
1452
+ }
1453
+ viewRef.destroy();
1454
+ };
1455
+ };
1456
+ }
1457
+ /**
1458
+ * Gets type-level defaults from the application's GridTypeRegistry.
1459
+ *
1460
+ * This enables application-wide type defaults configured via `provideGridTypeDefaults()`.
1461
+ * The returned TypeDefault contains renderer/editor functions that instantiate
1462
+ * Angular components dynamically.
1463
+ *
1464
+ * @example
1465
+ * ```typescript
1466
+ * // app.config.ts
1467
+ * export const appConfig: ApplicationConfig = {
1468
+ * providers: [
1469
+ * provideGridTypeDefaults({
1470
+ * country: {
1471
+ * renderer: CountryCellComponent,
1472
+ * editor: CountryEditorComponent
1473
+ * }
1474
+ * })
1475
+ * ]
1476
+ * };
1477
+ *
1478
+ * // Any grid with type: 'country' columns will use these components
1479
+ * gridConfig = {
1480
+ * columns: [{ field: 'country', type: 'country' }]
1481
+ * };
1482
+ * ```
1483
+ */
1484
+ getTypeDefault(type) {
1485
+ if (!this.typeRegistry) {
1486
+ return undefined;
1487
+ }
1488
+ const config = this.typeRegistry.get(type);
1489
+ if (!config) {
1490
+ return undefined;
1491
+ }
1492
+ const typeDefault = {
1493
+ editorParams: config.editorParams,
1494
+ };
1495
+ // Create renderer function that instantiates the Angular component
1496
+ if (config.renderer) {
1497
+ typeDefault.renderer = this.createComponentRenderer(config.renderer);
1498
+ }
1499
+ // Create editor function that instantiates the Angular component
1500
+ if (config.editor) {
1501
+ // Type assertion needed: adapter bridges TRow to core's unknown
1502
+ typeDefault.editor = this.createComponentEditor(config.editor);
1503
+ }
1504
+ return typeDefault;
1505
+ }
1506
+ /**
1507
+ * Creates a renderer function from an Angular component class.
1508
+ * @internal
1509
+ */
1510
+ createComponentRenderer(componentClass) {
1511
+ return (ctx) => {
1512
+ // Create a host element for the component
1513
+ const hostElement = document.createElement('span');
1514
+ hostElement.style.display = 'contents';
1515
+ // Create the component dynamically
1516
+ const componentRef = createComponent(componentClass, {
1517
+ environmentInjector: this.injector,
1518
+ hostElement,
1519
+ });
1520
+ // Set inputs - components should have value, row, column inputs
1521
+ this.setComponentInputs(componentRef, {
1522
+ value: ctx.value,
1523
+ row: ctx.row,
1524
+ column: ctx.column,
1525
+ });
1526
+ // Attach to app for change detection
1527
+ this.appRef.attachView(componentRef.hostView);
1528
+ this.componentRefs.push(componentRef);
1529
+ // Trigger change detection
1530
+ componentRef.changeDetectorRef.detectChanges();
1531
+ return hostElement;
1532
+ };
1533
+ }
1534
+ /**
1535
+ * Creates an editor function from an Angular component class.
1536
+ * @internal
1537
+ */
1538
+ createComponentEditor(componentClass) {
1539
+ return (ctx) => {
1540
+ // Create a host element for the component
1541
+ const hostElement = document.createElement('span');
1542
+ hostElement.style.display = 'contents';
1543
+ // Create the component dynamically
1544
+ const componentRef = createComponent(componentClass, {
1545
+ environmentInjector: this.injector,
1546
+ hostElement,
1547
+ });
1548
+ // Set inputs - components should have value, row, column inputs
1549
+ this.setComponentInputs(componentRef, {
1550
+ value: ctx.value,
1551
+ row: ctx.row,
1552
+ column: ctx.column,
1553
+ });
1554
+ // Attach to app for change detection
1555
+ this.appRef.attachView(componentRef.hostView);
1556
+ this.componentRefs.push(componentRef);
1557
+ // Trigger change detection
1558
+ componentRef.changeDetectorRef.detectChanges();
1559
+ // Subscribe to Angular outputs (commit/cancel) on the component instance.
1560
+ // This works with Angular's output() signal API.
1561
+ const instance = componentRef.instance;
1562
+ this.subscribeToOutput(instance, 'commit', (value) => ctx.commit(value));
1563
+ this.subscribeToOutput(instance, 'cancel', () => ctx.cancel());
1564
+ // Also listen for DOM events as fallback (for components that dispatch CustomEvents)
1565
+ hostElement.addEventListener('commit', (e) => {
1566
+ const customEvent = e;
1567
+ ctx.commit(customEvent.detail);
1568
+ });
1569
+ hostElement.addEventListener('cancel', () => {
1570
+ ctx.cancel();
1571
+ });
1572
+ return hostElement;
1573
+ };
1574
+ }
1575
+ /**
1576
+ * Subscribes to an Angular output on a component instance.
1577
+ * Works with both EventEmitter and OutputEmitterRef (signal outputs).
1578
+ * @internal
1579
+ */
1580
+ subscribeToOutput(instance, outputName, callback) {
1581
+ const output = instance[outputName];
1582
+ if (!output)
1583
+ return;
1584
+ // Check if it's an Observable-like (EventEmitter or OutputEmitterRef)
1585
+ if (typeof output.subscribe === 'function') {
1586
+ output.subscribe(callback);
1587
+ }
1588
+ }
1589
+ /**
1590
+ * Sets component inputs using Angular's setInput API.
1591
+ * @internal
1592
+ */
1593
+ setComponentInputs(componentRef, inputs) {
1594
+ for (const [key, value] of Object.entries(inputs)) {
1595
+ try {
1596
+ componentRef.setInput(key, value);
1597
+ }
1598
+ catch {
1599
+ // Input doesn't exist on component - that's okay, some inputs are optional
1600
+ }
1601
+ }
1602
+ }
1603
+ /**
1604
+ * Clean up all view references and component references.
1605
+ * Call this when your app/component is destroyed.
1606
+ */
1607
+ destroy() {
1608
+ this.viewRefs.forEach((ref) => ref.destroy());
1609
+ this.viewRefs = [];
1610
+ this.componentRefs.forEach((ref) => ref.destroy());
1611
+ this.componentRefs = [];
1612
+ }
1613
+ }
1614
+
1615
+ /**
1616
+ * Icon configuration registry for Angular applications.
1617
+ *
1618
+ * Provides application-wide icon overrides for all grids via
1619
+ * Angular's dependency injection.
1620
+ */
1621
+ /**
1622
+ * Injection token for providing icon overrides at app level.
1623
+ */
1624
+ const GRID_ICONS = new InjectionToken('GRID_ICONS');
1625
+ /**
1626
+ * Injectable service for managing grid icons.
1627
+ *
1628
+ * Use `provideGridIcons()` in your app config to set up icons,
1629
+ * or inject this service for dynamic registration.
1630
+ *
1631
+ * @example
1632
+ * ```typescript
1633
+ * // App-level setup (app.config.ts)
1634
+ * export const appConfig: ApplicationConfig = {
1635
+ * providers: [
1636
+ * provideGridIcons({
1637
+ * expand: '➕',
1638
+ * collapse: '➖',
1639
+ * sortAsc: '↑',
1640
+ * sortDesc: '↓',
1641
+ * })
1642
+ * ]
1643
+ * };
1644
+ *
1645
+ * // Dynamic registration
1646
+ * @Component({ ... })
1647
+ * export class AppComponent {
1648
+ * private registry = inject(GridIconRegistry);
1649
+ *
1650
+ * ngOnInit() {
1651
+ * this.registry.set('filter', '<svg>...</svg>');
1652
+ * }
1653
+ * }
1654
+ * ```
1655
+ */
1656
+ class GridIconRegistry {
1657
+ icons = new Map();
1658
+ constructor() {
1659
+ // Merge any initial icons from provider
1660
+ const initial = inject(GRID_ICONS, { optional: true });
1661
+ if (initial) {
1662
+ for (const [key, value] of Object.entries(initial)) {
1663
+ this.icons.set(key, value);
1664
+ }
1665
+ }
1666
+ }
1667
+ /**
1668
+ * Set an icon override.
1669
+ *
1670
+ * @param name - The icon name (e.g., 'expand', 'collapse', 'filter')
1671
+ * @param value - The icon value (string text or SVG markup)
1672
+ */
1673
+ set(name, value) {
1674
+ this.icons.set(name, value);
1675
+ }
1676
+ /**
1677
+ * Get an icon value.
1678
+ */
1679
+ get(name) {
1680
+ return this.icons.get(name);
1681
+ }
1682
+ /**
1683
+ * Remove an icon override.
1684
+ */
1685
+ remove(name) {
1686
+ this.icons.delete(name);
1687
+ }
1688
+ /**
1689
+ * Check if an icon has an override.
1690
+ */
1691
+ has(name) {
1692
+ return this.icons.has(name);
1693
+ }
1694
+ /**
1695
+ * Get all icon overrides as a GridIcons partial.
1696
+ * Used internally by the adapter.
1697
+ *
1698
+ * @internal
1699
+ */
1700
+ getAll() {
1701
+ const result = {};
1702
+ for (const [key, value] of this.icons) {
1703
+ result[key] = value;
1704
+ }
1705
+ return result;
1706
+ }
1707
+ /**
1708
+ * Get all registered icon names.
1709
+ */
1710
+ getRegisteredIcons() {
1711
+ return Array.from(this.icons.keys());
1712
+ }
1713
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: GridIconRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1714
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: GridIconRegistry, providedIn: 'root' });
1715
+ }
1716
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: GridIconRegistry, decorators: [{
1717
+ type: Injectable,
1718
+ args: [{ providedIn: 'root' }]
1719
+ }], ctorParameters: () => [] });
1720
+ /**
1721
+ * Provides application-level icon overrides for all grids.
1722
+ *
1723
+ * Available icons to override:
1724
+ * - `expand` - Expand icon for collapsed items (trees, groups, details)
1725
+ * - `collapse` - Collapse icon for expanded items
1726
+ * - `sortAsc` - Sort ascending indicator
1727
+ * - `sortDesc` - Sort descending indicator
1728
+ * - `sortNone` - Sort neutral/unsorted indicator
1729
+ * - `submenuArrow` - Submenu arrow for context menus
1730
+ * - `dragHandle` - Drag handle icon for reordering
1731
+ * - `toolPanel` - Tool panel toggle icon in toolbar
1732
+ * - `filter` - Filter icon in column headers
1733
+ * - `filterActive` - Filter icon when filter is active
1734
+ * - `print` - Print icon for print button
1735
+ *
1736
+ * @example
1737
+ * ```typescript
1738
+ * // app.config.ts
1739
+ * import { provideGridIcons } from '@toolbox-web/grid-angular';
1740
+ *
1741
+ * export const appConfig: ApplicationConfig = {
1742
+ * providers: [
1743
+ * provideGridIcons({
1744
+ * expand: '➕',
1745
+ * collapse: '➖',
1746
+ * sortAsc: '↑',
1747
+ * sortDesc: '↓',
1748
+ * filter: '<svg viewBox="0 0 16 16">...</svg>',
1749
+ * })
1750
+ * ]
1751
+ * };
1752
+ * ```
1753
+ */
1754
+ function provideGridIcons(icons) {
1755
+ return makeEnvironmentProviders([{ provide: GRID_ICONS, useValue: icons }]);
1756
+ }
1757
+
1758
+ /**
1759
+ * Angular inject function for programmatic access to a grid instance.
1760
+ *
1761
+ * This function should be called in the constructor or as a field initializer
1762
+ * of an Angular component that contains a `<tbw-grid>` element.
1763
+ *
1764
+ * ## Usage
1765
+ *
1766
+ * ```typescript
1767
+ * import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
1768
+ * import { Grid, injectGrid } from '@toolbox-web/grid-angular';
1769
+ *
1770
+ * @Component({
1771
+ * selector: 'app-my-grid',
1772
+ * imports: [Grid],
1773
+ * schemas: [CUSTOM_ELEMENTS_SCHEMA],
1774
+ * template: `
1775
+ * <button (click)="handleResize()">Force Layout</button>
1776
+ * <button (click)="handleExport()" [disabled]="!grid.isReady()">Export</button>
1777
+ * <tbw-grid [rows]="rows" [gridConfig]="config"></tbw-grid>
1778
+ * `
1779
+ * })
1780
+ * export class MyGridComponent {
1781
+ * grid = injectGrid<Employee>();
1782
+ *
1783
+ * async handleResize() {
1784
+ * await this.grid.forceLayout();
1785
+ * }
1786
+ *
1787
+ * async handleExport() {
1788
+ * const config = await this.grid.getConfig();
1789
+ * console.log('Exporting with columns:', config?.columns);
1790
+ * }
1791
+ * }
1792
+ * ```
1793
+ *
1794
+ * @returns Object with grid access methods and state signals
1795
+ */
1796
+ function injectGrid() {
1797
+ const elementRef = inject(ElementRef);
1798
+ // Reactive signals
1799
+ const isReady = signal(false, ...(ngDevMode ? [{ debugName: "isReady" }] : []));
1800
+ const config = signal(null, ...(ngDevMode ? [{ debugName: "config" }] : []));
1801
+ const element = signal(null, ...(ngDevMode ? [{ debugName: "element" }] : []));
1802
+ // Initialize after render
1803
+ afterNextRender(() => {
1804
+ const gridElement = elementRef.nativeElement.querySelector('tbw-grid');
1805
+ if (!gridElement) {
1806
+ console.warn('[injectGrid] No tbw-grid element found in component');
1807
+ return;
1808
+ }
1809
+ element.set(gridElement);
1810
+ // Wait for grid to be ready
1811
+ gridElement.ready?.().then(async () => {
1812
+ isReady.set(true);
1813
+ const effectiveConfig = gridElement.getConfig?.();
1814
+ if (effectiveConfig) {
1815
+ config.set(effectiveConfig);
1816
+ }
1817
+ });
1818
+ });
1819
+ // Computed visible columns
1820
+ const visibleColumns = computed(() => {
1821
+ const currentConfig = config();
1822
+ if (!currentConfig?.columns)
1823
+ return [];
1824
+ return currentConfig.columns.filter((col) => !col.hidden);
1825
+ }, ...(ngDevMode ? [{ debugName: "visibleColumns" }] : []));
1826
+ // ═══════════════════════════════════════════════════════════════════
1827
+ // CORE METHODS
1828
+ // ═══════════════════════════════════════════════════════════════════
1829
+ const getConfig = async () => {
1830
+ const gridElement = element();
1831
+ if (!gridElement)
1832
+ return null;
1833
+ const effectiveConfig = gridElement.getConfig?.();
1834
+ return effectiveConfig ?? null;
1835
+ };
1836
+ const forceLayout = async () => {
1837
+ const gridElement = element();
1838
+ if (!gridElement)
1839
+ return;
1840
+ await gridElement.forceLayout?.();
1841
+ };
1842
+ const toggleGroup = async (key) => {
1843
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1844
+ const gridElement = element();
1845
+ if (!gridElement)
1846
+ return;
1847
+ await gridElement.toggleGroup?.(key);
1848
+ };
1849
+ const registerStyles = (id, css) => {
1850
+ element()?.registerStyles?.(id, css);
1851
+ };
1852
+ const unregisterStyles = (id) => {
1853
+ element()?.unregisterStyles?.(id);
1854
+ };
1855
+ // ═══════════════════════════════════════════════════════════════════
1856
+ // SELECTION CONVENIENCE METHODS
1857
+ // ═══════════════════════════════════════════════════════════════════
1858
+ const selectAll = () => {
1859
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1860
+ const gridElement = element();
1861
+ const plugin = gridElement?.getPluginByName?.('selection');
1862
+ if (!plugin) {
1863
+ console.warn('[injectGrid] selectAll requires SelectionPlugin');
1864
+ return;
1865
+ }
1866
+ // Row mode: select all row indices
1867
+ if (plugin.config?.mode === 'row') {
1868
+ const rows = gridElement?.rows ?? [];
1869
+ const allIndices = new Set(rows.map((_, i) => i));
1870
+ plugin.selected = allIndices;
1871
+ plugin.requestAfterRender?.();
1872
+ }
1873
+ };
1874
+ const clearSelection = () => {
1875
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1876
+ const gridElement = element();
1877
+ const plugin = gridElement?.getPluginByName?.('selection');
1878
+ if (!plugin)
1879
+ return;
1880
+ const mode = plugin.config?.mode;
1881
+ if (mode === 'row') {
1882
+ plugin.selected = new Set();
1883
+ }
1884
+ else if (mode === 'range' || mode === 'cell') {
1885
+ plugin.ranges = [];
1886
+ }
1887
+ plugin.requestAfterRender?.();
1888
+ };
1889
+ const getSelectedIndices = () => {
1890
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1891
+ const gridElement = element();
1892
+ const plugin = gridElement?.getPluginByName?.('selection');
1893
+ if (!plugin)
1894
+ return new Set();
1895
+ if (plugin.config?.mode === 'row') {
1896
+ return new Set(plugin.selected ?? []);
1897
+ }
1898
+ // Range/cell mode: extract unique row indices from ranges
1899
+ const ranges = plugin.ranges ?? [];
1900
+ const indices = new Set();
1901
+ for (const range of ranges) {
1902
+ for (let r = range.startRow; r <= range.endRow; r++) {
1903
+ indices.add(r);
1904
+ }
1905
+ }
1906
+ return indices;
1907
+ };
1908
+ const getSelectedRows = () => {
1909
+ const gridElement = element();
1910
+ if (!gridElement)
1911
+ return [];
1912
+ const rows = gridElement.rows ?? [];
1913
+ const indices = getSelectedIndices();
1914
+ return Array.from(indices)
1915
+ .filter((i) => i >= 0 && i < rows.length)
1916
+ .map((i) => rows[i]);
1917
+ };
1918
+ // ═══════════════════════════════════════════════════════════════════
1919
+ // EXPORT CONVENIENCE METHODS
1920
+ // ═══════════════════════════════════════════════════════════════════
1921
+ const exportToCsv = (filename) => {
1922
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1923
+ const gridElement = element();
1924
+ const plugin = gridElement?.getPluginByName?.('export');
1925
+ if (!plugin) {
1926
+ console.warn('[injectGrid] exportToCsv requires ExportPlugin');
1927
+ return;
1928
+ }
1929
+ plugin.exportToCsv?.(filename);
1930
+ };
1931
+ const exportToJson = (filename) => {
1932
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1933
+ const gridElement = element();
1934
+ const plugin = gridElement?.getPluginByName?.('export');
1935
+ if (!plugin) {
1936
+ console.warn('[injectGrid] exportToJson requires ExportPlugin');
1937
+ return;
1938
+ }
1939
+ plugin.exportToJson?.(filename);
1940
+ };
1941
+ return {
1942
+ element,
1943
+ isReady,
1944
+ config,
1945
+ visibleColumns,
1946
+ getConfig,
1947
+ forceLayout,
1948
+ toggleGroup,
1949
+ registerStyles,
1950
+ unregisterStyles,
1951
+ selectAll,
1952
+ clearSelection,
1953
+ getSelectedIndices,
1954
+ getSelectedRows,
1955
+ exportToCsv,
1956
+ exportToJson,
1957
+ };
1958
+ }
1959
+
1960
+ /**
1961
+ * Feature Registry for @toolbox-web/grid-angular
1962
+ *
1963
+ * This module provides a synchronous registry for plugin factories.
1964
+ * Features are registered via side-effect imports, enabling tree-shaking
1965
+ * while maintaining the clean input-based API.
1966
+ *
1967
+ * @example
1968
+ * ```typescript
1969
+ * // Import features you need (side-effect imports)
1970
+ * import '@toolbox-web/grid-angular/features/selection';
1971
+ * import '@toolbox-web/grid-angular/features/filtering';
1972
+ *
1973
+ * // Inputs work automatically - no async loading, no HTTP requests
1974
+ * <tbw-grid [selection]="'range'" [filtering]="{ debounceMs: 200 }" />
1975
+ * ```
1976
+ */
1977
+ /**
1978
+ * Central registry mapping feature names to their plugin factories.
1979
+ * Populated by side-effect feature imports.
1980
+ */
1981
+ const featureRegistry = new Map();
1982
+ /**
1983
+ * Set of features that have been used without being registered.
1984
+ * Used to show helpful warnings only once per feature.
1985
+ */
1986
+ const warnedFeatures = new Set();
1987
+ /**
1988
+ * Register a feature's plugin factory.
1989
+ * Called by side-effect feature imports.
1990
+ *
1991
+ * @param name - The feature name (matches the input name on Grid directive)
1992
+ * @param factory - Function that creates the plugin instance
1993
+ *
1994
+ * @example
1995
+ * ```ts
1996
+ * // features/selection.ts
1997
+ * import { SelectionPlugin } from '@toolbox-web/grid/plugins/selection';
1998
+ * import { registerFeature } from '../lib/feature-registry';
1999
+ *
2000
+ * registerFeature('selection', (config) => new SelectionPlugin(config));
2001
+ * ```
2002
+ */
2003
+ function registerFeature(name, factory) {
2004
+ featureRegistry.set(name, {
2005
+ factory: factory,
2006
+ name,
2007
+ });
2008
+ }
2009
+ /**
2010
+ * Check if a feature is registered.
2011
+ */
2012
+ function isFeatureRegistered(name) {
2013
+ return featureRegistry.has(name);
2014
+ }
2015
+ /**
2016
+ * Get a registered feature's factory.
2017
+ * Returns undefined if not registered.
2018
+ */
2019
+ function getFeatureFactory(name) {
2020
+ return featureRegistry.get(name)?.factory;
2021
+ }
2022
+ /**
2023
+ * Get all registered feature names.
2024
+ * Useful for debugging.
2025
+ */
2026
+ function getRegisteredFeatures() {
2027
+ return Array.from(featureRegistry.keys());
2028
+ }
2029
+ /**
2030
+ * Create a plugin instance for a feature.
2031
+ * Shows a helpful warning if the feature is not registered.
2032
+ *
2033
+ * @param name - Feature name
2034
+ * @param config - Plugin configuration
2035
+ * @returns Plugin instance or undefined if not registered
2036
+ */
2037
+ function createPluginFromFeature(name, config) {
2038
+ const entry = featureRegistry.get(name);
2039
+ if (!entry) {
2040
+ // Show warning only once per feature in development
2041
+ const isDev = typeof window !== 'undefined' &&
2042
+ (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1');
2043
+ if (!warnedFeatures.has(name) && isDev) {
2044
+ warnedFeatures.add(name);
2045
+ console.warn(`[tbw-grid] Feature "${name}" input is set but the feature is not registered.\n` +
2046
+ `Add this import to enable it:\n\n` +
2047
+ ` import '@toolbox-web/grid-angular/features/${toKebabCase(name)}';\n`);
2048
+ }
2049
+ return undefined;
2050
+ }
2051
+ return entry.factory(config);
2052
+ }
2053
+ /**
2054
+ * Convert camelCase to kebab-case for import paths.
2055
+ */
2056
+ function toKebabCase(str) {
2057
+ return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
2058
+ }
2059
+ /**
2060
+ * Clear the registry. For testing only.
2061
+ * @internal
2062
+ */
2063
+ function clearFeatureRegistry() {
2064
+ featureRegistry.clear();
2065
+ warnedFeatures.clear();
2066
+ }
2067
+
2068
+ /**
2069
+ * Base class for grid cell editors.
2070
+ *
2071
+ * Provides common functionality for Angular cell editors:
2072
+ * - Automatic value resolution from FormControl or value input
2073
+ * - Common inputs (value, row, column, control)
2074
+ * - Common outputs (commit, cancel)
2075
+ * - Validation state helpers
2076
+ *
2077
+ * ## Usage
2078
+ *
2079
+ * ```typescript
2080
+ * import { Component } from '@angular/core';
2081
+ * import { BaseGridEditor } from '@toolbox-web/grid-angular';
2082
+ *
2083
+ * @Component({
2084
+ * selector: 'app-my-editor',
2085
+ * template: \`
2086
+ * <input
2087
+ * [value]="currentValue()"
2088
+ * [class.is-invalid]="isInvalid()"
2089
+ * (input)="commitValue($event.target.value)"
2090
+ * (keydown.escape)="cancelEdit()"
2091
+ * />
2092
+ * @if (hasErrors()) {
2093
+ * <div class="error">{{ firstErrorMessage() }}</div>
2094
+ * }
2095
+ * \`
2096
+ * })
2097
+ * export class MyEditorComponent extends BaseGridEditor<MyRow, string> {
2098
+ * // Override to customize error messages
2099
+ * protected override getErrorMessage(errorKey: string): string {
2100
+ * if (errorKey === 'required') return 'This field is required';
2101
+ * if (errorKey === 'minlength') return 'Too short';
2102
+ * return super.getErrorMessage(errorKey);
2103
+ * }
2104
+ * }
2105
+ * ```
2106
+ *
2107
+ * ## Template Syntax
2108
+ *
2109
+ * When using the base class, you only need to pass the control:
2110
+ *
2111
+ * ```html
2112
+ * <tbw-grid-column field="name">
2113
+ * <app-my-editor *tbwEditor="let _; control as control" [control]="control" />
2114
+ * </tbw-grid-column>
2115
+ * ```
2116
+ *
2117
+ * Or without FormArray binding (fallback to value):
2118
+ *
2119
+ * ```html
2120
+ * <tbw-grid-column field="name">
2121
+ * <app-my-editor *tbwEditor="let value" [value]="value" />
2122
+ * </tbw-grid-column>
2123
+ * ```
2124
+ *
2125
+ * @typeParam TRow - The row data type
2126
+ * @typeParam TValue - The cell value type
2127
+ */
2128
+ class BaseGridEditor {
2129
+ elementRef = inject(ElementRef);
2130
+ // ============================================================================
2131
+ // Inputs
2132
+ // ============================================================================
2133
+ /**
2134
+ * The cell value. Used when FormControl is not available.
2135
+ * When a FormControl is provided, value is derived from control.value instead.
2136
+ */
2137
+ value = input(...(ngDevMode ? [undefined, { debugName: "value" }] : []));
2138
+ /**
2139
+ * The full row data object.
2140
+ */
2141
+ row = input(...(ngDevMode ? [undefined, { debugName: "row" }] : []));
2142
+ /**
2143
+ * The column configuration.
2144
+ */
2145
+ column = input(...(ngDevMode ? [undefined, { debugName: "column" }] : []));
2146
+ /**
2147
+ * The FormControl for this cell, if the grid is bound to a FormArray.
2148
+ * When provided, the editor uses control.value instead of the value input.
2149
+ */
2150
+ control = input(...(ngDevMode ? [undefined, { debugName: "control" }] : []));
2151
+ // ============================================================================
2152
+ // Outputs
2153
+ // ============================================================================
2154
+ /**
2155
+ * Emits when the user commits a new value.
2156
+ */
2157
+ commit = output();
2158
+ /**
2159
+ * Emits when the user cancels editing.
2160
+ */
2161
+ cancel = output();
2162
+ // ============================================================================
2163
+ // Computed State
2164
+ // ============================================================================
2165
+ /**
2166
+ * The current value, derived from FormControl if available, otherwise from value input.
2167
+ * This is the recommended way to get the current value in your editor template.
2168
+ */
2169
+ currentValue = computed(() => {
2170
+ const ctrl = this.control();
2171
+ if (ctrl) {
2172
+ return ctrl.value;
2173
+ }
2174
+ return this.value();
2175
+ }, ...(ngDevMode ? [{ debugName: "currentValue" }] : []));
2176
+ /**
2177
+ * Whether the control is invalid (has validation errors).
2178
+ * Returns false if no FormControl is available.
2179
+ */
2180
+ isInvalid = computed(() => {
2181
+ return this.control()?.invalid ?? false;
2182
+ }, ...(ngDevMode ? [{ debugName: "isInvalid" }] : []));
2183
+ /**
2184
+ * Whether the control is dirty (has been modified).
2185
+ * Returns false if no FormControl is available.
2186
+ */
2187
+ isDirty = computed(() => {
2188
+ return this.control()?.dirty ?? false;
2189
+ }, ...(ngDevMode ? [{ debugName: "isDirty" }] : []));
2190
+ /**
2191
+ * Whether the control has been touched.
2192
+ * Returns false if no FormControl is available.
2193
+ */
2194
+ isTouched = computed(() => {
2195
+ return this.control()?.touched ?? false;
2196
+ }, ...(ngDevMode ? [{ debugName: "isTouched" }] : []));
2197
+ /**
2198
+ * Whether the control has any validation errors.
2199
+ */
2200
+ hasErrors = computed(() => {
2201
+ const ctrl = this.control();
2202
+ return ctrl?.errors != null && Object.keys(ctrl.errors).length > 0;
2203
+ }, ...(ngDevMode ? [{ debugName: "hasErrors" }] : []));
2204
+ /**
2205
+ * The first error message from the control's validation errors.
2206
+ * Returns an empty string if no errors.
2207
+ */
2208
+ firstErrorMessage = computed(() => {
2209
+ const ctrl = this.control();
2210
+ if (!ctrl?.errors)
2211
+ return '';
2212
+ const firstKey = Object.keys(ctrl.errors)[0];
2213
+ return this.getErrorMessage(firstKey, ctrl.errors[firstKey]);
2214
+ }, ...(ngDevMode ? [{ debugName: "firstErrorMessage" }] : []));
2215
+ /**
2216
+ * All error messages from the control's validation errors.
2217
+ */
2218
+ allErrorMessages = computed(() => {
2219
+ const ctrl = this.control();
2220
+ if (!ctrl?.errors)
2221
+ return [];
2222
+ return Object.entries(ctrl.errors).map(([key, value]) => this.getErrorMessage(key, value));
2223
+ }, ...(ngDevMode ? [{ debugName: "allErrorMessages" }] : []));
2224
+ // ============================================================================
2225
+ // Methods
2226
+ // ============================================================================
2227
+ /**
2228
+ * Commit a new value. Emits the commit output AND dispatches a DOM event.
2229
+ * The DOM event enables the grid's auto-wiring to catch the commit.
2230
+ * Call this when the user confirms their edit.
2231
+ */
2232
+ commitValue(newValue) {
2233
+ // Emit Angular output for template bindings
2234
+ this.commit.emit(newValue);
2235
+ // Dispatch DOM CustomEvent for grid's auto-wiring
2236
+ // This allows the adapter to catch commits without explicit (commit)="..." bindings
2237
+ this.elementRef.nativeElement.dispatchEvent(new CustomEvent('commit', { detail: newValue, bubbles: true }));
2238
+ }
2239
+ /**
2240
+ * Cancel editing. Emits the cancel output AND dispatches a DOM event.
2241
+ * Call this when the user cancels (e.g., presses Escape).
2242
+ */
2243
+ cancelEdit() {
2244
+ // Emit Angular output for template bindings
2245
+ this.cancel.emit();
2246
+ // Dispatch DOM CustomEvent for grid's auto-wiring
2247
+ this.elementRef.nativeElement.dispatchEvent(new CustomEvent('cancel', { bubbles: true }));
2248
+ }
2249
+ /**
2250
+ * Get a human-readable error message for a validation error.
2251
+ * Override this method to customize error messages for your editor.
2252
+ *
2253
+ * @param errorKey - The validation error key (e.g., 'required', 'minlength')
2254
+ * @param errorValue - The error value (e.g., { requiredLength: 5, actualLength: 3 })
2255
+ * @returns A human-readable error message
2256
+ */
2257
+ getErrorMessage(errorKey, errorValue) {
2258
+ switch (errorKey) {
2259
+ case 'required':
2260
+ return 'This field is required';
2261
+ case 'minlength': {
2262
+ const err = errorValue;
2263
+ return `Minimum length is ${err?.requiredLength ?? 'unknown'}`;
2264
+ }
2265
+ case 'maxlength': {
2266
+ const err = errorValue;
2267
+ return `Maximum length is ${err?.requiredLength ?? 'unknown'}`;
2268
+ }
2269
+ case 'min': {
2270
+ const err = errorValue;
2271
+ return `Minimum value is ${err?.min ?? 'unknown'}`;
2272
+ }
2273
+ case 'max': {
2274
+ const err = errorValue;
2275
+ return `Maximum value is ${err?.max ?? 'unknown'}`;
2276
+ }
2277
+ case 'email':
2278
+ return 'Invalid email address';
2279
+ case 'pattern':
2280
+ return 'Invalid format';
2281
+ default:
2282
+ return `Invalid value (${errorKey})`;
2283
+ }
2284
+ }
2285
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: BaseGridEditor, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2286
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.1", type: BaseGridEditor, isStandalone: true, inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, row: { classPropertyName: "row", publicName: "row", isSignal: true, isRequired: false, transformFunction: null }, column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: false, transformFunction: null }, control: { classPropertyName: "control", publicName: "control", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { commit: "commit", cancel: "cancel" }, ngImport: i0 });
2287
+ }
2288
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: BaseGridEditor, decorators: [{
2289
+ type: Directive
2290
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], row: [{ type: i0.Input, args: [{ isSignal: true, alias: "row", required: false }] }], column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: false }] }], control: [{ type: i0.Input, args: [{ isSignal: true, alias: "control", required: false }] }], commit: [{ type: i0.Output, args: ["commit"] }], cancel: [{ type: i0.Output, args: ["cancel"] }] } });
2291
+
2292
+ /**
2293
+ * Directive that automatically registers the Angular adapter with tbw-grid elements.
2294
+ *
2295
+ * This directive eliminates the need to manually register the adapter in your component
2296
+ * constructor. Simply import this directive and it will handle adapter registration.
2297
+ *
2298
+ * ## Usage
2299
+ *
2300
+ * ```typescript
2301
+ * import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
2302
+ * import { Grid } from '@toolbox-web/grid-angular';
2303
+ *
2304
+ * @Component({
2305
+ * selector: 'app-root',
2306
+ * imports: [Grid],
2307
+ * schemas: [CUSTOM_ELEMENTS_SCHEMA],
2308
+ * template: `
2309
+ * <tbw-grid [rows]="rows" [gridConfig]="config" [customStyles]="myStyles">
2310
+ * <!-- column templates -->
2311
+ * </tbw-grid>
2312
+ * `
2313
+ * })
2314
+ * export class AppComponent {
2315
+ * rows = [...];
2316
+ * config = {...};
2317
+ * myStyles = `.my-class { color: red; }`;
2318
+ * }
2319
+ * ```
2320
+ *
2321
+ * The directive automatically:
2322
+ * - Creates an AngularGridAdapter instance
2323
+ * - Registers it with the GridElement
2324
+ * - Injects custom styles into the grid
2325
+ * - Handles cleanup on destruction
2326
+ */
2327
+ class Grid {
2328
+ elementRef = inject((ElementRef));
2329
+ injector = inject(EnvironmentInjector);
2330
+ appRef = inject(ApplicationRef);
2331
+ viewContainerRef = inject(ViewContainerRef);
2332
+ iconRegistry = inject(GridIconRegistry, { optional: true });
2333
+ adapter = null;
2334
+ constructor() {
2335
+ // Effect to process angularConfig and apply to grid
2336
+ // This merges feature input plugins with the user's config plugins
2337
+ effect(() => {
2338
+ const config = this.angularConfig();
2339
+ if (!this.adapter)
2340
+ return;
2341
+ // Process the config to convert component classes to actual renderer/editor functions
2342
+ const processedConfig = config ? this.adapter.processGridConfig(config) : {};
2343
+ // Create plugins from feature inputs and merge with config plugins
2344
+ const featurePlugins = this.createFeaturePlugins();
2345
+ const configPlugins = processedConfig.plugins || [];
2346
+ // Merge: feature plugins first, then config plugins
2347
+ const mergedPlugins = [...featurePlugins, ...configPlugins];
2348
+ // Build core config overrides from individual inputs
2349
+ const sortableValue = this.sortable();
2350
+ const filterableValue = this.filterable();
2351
+ const selectableValue = this.selectable();
2352
+ const coreConfigOverrides = {};
2353
+ if (sortableValue !== undefined) {
2354
+ coreConfigOverrides['sortable'] = sortableValue;
2355
+ }
2356
+ if (filterableValue !== undefined) {
2357
+ coreConfigOverrides['filterable'] = filterableValue;
2358
+ }
2359
+ if (selectableValue !== undefined) {
2360
+ coreConfigOverrides['selectable'] = selectableValue;
2361
+ }
2362
+ // Merge icon overrides from registry with any existing icons in config
2363
+ // Registry icons are base, config.icons override them
2364
+ const registryIcons = this.iconRegistry?.getAll();
2365
+ if (registryIcons && Object.keys(registryIcons).length > 0) {
2366
+ const existingIcons = processedConfig?.icons || config?.icons || {};
2367
+ coreConfigOverrides['icons'] = { ...registryIcons, ...existingIcons };
2368
+ }
2369
+ // Apply to the grid element
2370
+ const grid = this.elementRef.nativeElement;
2371
+ grid.gridConfig = {
2372
+ ...processedConfig,
2373
+ ...coreConfigOverrides,
2374
+ plugins: mergedPlugins.length > 0 ? mergedPlugins : undefined,
2375
+ };
2376
+ });
2377
+ // Effect to sync loading state to the grid element
2378
+ effect(() => {
2379
+ const loadingValue = this.loading();
2380
+ if (loadingValue === undefined)
2381
+ return;
2382
+ const grid = this.elementRef.nativeElement;
2383
+ grid.loading = loadingValue;
2384
+ });
2385
+ }
2386
+ /**
2387
+ * Custom CSS styles to inject into the grid.
2388
+ * Use this to style custom cell renderers, editors, or detail panels.
2389
+ *
2390
+ * @example
2391
+ * ```typescript
2392
+ * // In your component
2393
+ * customStyles = `
2394
+ * .my-detail-panel { padding: 16px; }
2395
+ * .my-status-badge { border-radius: 4px; }
2396
+ * `;
2397
+ * ```
2398
+ *
2399
+ * ```html
2400
+ * <tbw-grid [customStyles]="customStyles">...</tbw-grid>
2401
+ * ```
2402
+ */
2403
+ customStyles = input(...(ngDevMode ? [undefined, { debugName: "customStyles" }] : []));
2404
+ /**
2405
+ * Grid-wide sorting toggle.
2406
+ * When false, disables sorting for all columns regardless of their individual `sortable` setting.
2407
+ * When true (default), columns with `sortable: true` can be sorted.
2408
+ *
2409
+ * This is a core grid config property, not a plugin feature.
2410
+ * For multi-column sorting, also add the `[multiSort]` feature.
2411
+ *
2412
+ * @default true
2413
+ *
2414
+ * @example
2415
+ * ```html
2416
+ * <!-- Disable all sorting -->
2417
+ * <tbw-grid [sortable]="false" />
2418
+ *
2419
+ * <!-- Enable sorting (default) - columns still need sortable: true -->
2420
+ * <tbw-grid [sortable]="true" />
2421
+ *
2422
+ * <!-- Enable multi-column sorting -->
2423
+ * <tbw-grid [sortable]="true" [multiSort]="true" />
2424
+ * ```
2425
+ */
2426
+ sortable = input(...(ngDevMode ? [undefined, { debugName: "sortable" }] : []));
2427
+ /**
2428
+ * Grid-wide filtering toggle.
2429
+ * When false, disables filtering for all columns regardless of their individual `filterable` setting.
2430
+ * When true (default), columns with `filterable: true` can be filtered.
2431
+ *
2432
+ * Requires the FilteringPlugin to be loaded.
2433
+ *
2434
+ * @default true
2435
+ *
2436
+ * @example
2437
+ * ```html
2438
+ * <!-- Disable all filtering -->
2439
+ * <tbw-grid [filterable]="false" [filtering]="true" />
2440
+ *
2441
+ * <!-- Enable filtering (default) -->
2442
+ * <tbw-grid [filterable]="true" [filtering]="true" />
2443
+ * ```
2444
+ */
2445
+ filterable = input(...(ngDevMode ? [undefined, { debugName: "filterable" }] : []));
2446
+ /**
2447
+ * Grid-wide selection toggle.
2448
+ * When false, disables selection for all rows/cells.
2449
+ * When true (default), selection is enabled based on plugin mode.
2450
+ *
2451
+ * Requires the SelectionPlugin to be loaded.
2452
+ *
2453
+ * @default true
2454
+ *
2455
+ * @example
2456
+ * ```html
2457
+ * <!-- Disable all selection -->
2458
+ * <tbw-grid [selectable]="false" [selection]="'range'" />
2459
+ *
2460
+ * <!-- Enable selection (default) -->
2461
+ * <tbw-grid [selectable]="true" [selection]="'range'" />
2462
+ * ```
2463
+ */
2464
+ selectable = input(...(ngDevMode ? [undefined, { debugName: "selectable" }] : []));
2465
+ /**
2466
+ * Show a loading overlay on the grid.
2467
+ * Use this during initial data fetch or refresh operations.
2468
+ *
2469
+ * For row/cell loading states, access the grid element directly:
2470
+ * - `grid.setRowLoading(rowId, true/false)`
2471
+ * - `grid.setCellLoading(rowId, field, true/false)`
2472
+ *
2473
+ * @default false
2474
+ *
2475
+ * @example
2476
+ * ```html
2477
+ * <!-- Show loading during data fetch -->
2478
+ * <tbw-grid [loading]="isLoading" [rows]="rows" />
2479
+ * ```
2480
+ *
2481
+ * ```typescript
2482
+ * isLoading = true;
2483
+ *
2484
+ * ngOnInit() {
2485
+ * this.dataService.fetchData().subscribe(data => {
2486
+ * this.rows = data;
2487
+ * this.isLoading = false;
2488
+ * });
2489
+ * }
2490
+ * ```
2491
+ */
2492
+ loading = input(...(ngDevMode ? [undefined, { debugName: "loading" }] : []));
2493
+ /**
2494
+ * Angular-specific grid configuration that supports component classes for renderers/editors.
2495
+ *
2496
+ * Use this input when you want to specify Angular component classes directly in column configs.
2497
+ * Components must implement the appropriate interfaces:
2498
+ * - Renderers: `AngularCellRenderer<TRow, TValue>` - requires `value()` and `row()` signal inputs
2499
+ * - Editors: `AngularCellEditor<TRow, TValue>` - adds `commit` and `cancel` outputs
2500
+ *
2501
+ * The directive automatically processes component classes and converts them to grid-compatible
2502
+ * renderer/editor functions before applying to the grid.
2503
+ *
2504
+ * @example
2505
+ * ```typescript
2506
+ * // Component that implements AngularCellEditor
2507
+ * @Component({...})
2508
+ * export class BonusEditorComponent implements AngularCellEditor<Employee, number> {
2509
+ * value = input.required<number>();
2510
+ * row = input.required<Employee>();
2511
+ * commit = output<number>();
2512
+ * cancel = output<void>();
2513
+ * }
2514
+ *
2515
+ * // In your grid config
2516
+ * config: AngularGridConfig<Employee> = {
2517
+ * columns: [
2518
+ * { field: 'name', header: 'Name' },
2519
+ * { field: 'bonus', header: 'Bonus', editable: true, editor: BonusEditorComponent }
2520
+ * ]
2521
+ * };
2522
+ * ```
2523
+ *
2524
+ * ```html
2525
+ * <tbw-grid [angularConfig]="config" [rows]="employees"></tbw-grid>
2526
+ * ```
2527
+ */
2528
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2529
+ angularConfig = input(...(ngDevMode ? [undefined, { debugName: "angularConfig" }] : []));
2530
+ // ═══════════════════════════════════════════════════════════════════════════
2531
+ // FEATURE INPUTS - Declarative plugin configuration
2532
+ // ═══════════════════════════════════════════════════════════════════════════
2533
+ /**
2534
+ * Enable cell/row/range selection.
2535
+ *
2536
+ * **Requires feature import:**
2537
+ * ```typescript
2538
+ * import '@toolbox-web/grid-angular/features/selection';
2539
+ * ```
2540
+ *
2541
+ * @example
2542
+ * ```html
2543
+ * <!-- Shorthand - just the mode -->
2544
+ * <tbw-grid [selection]="'range'" />
2545
+ *
2546
+ * <!-- Full config object -->
2547
+ * <tbw-grid [selection]="{ mode: 'range', checkbox: true }" />
2548
+ * ```
2549
+ */
2550
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2551
+ selection = input(...(ngDevMode ? [undefined, { debugName: "selection" }] : []));
2552
+ /**
2553
+ * Enable inline cell editing.
2554
+ *
2555
+ * **Requires feature import:**
2556
+ * ```typescript
2557
+ * import '@toolbox-web/grid-angular/features/editing';
2558
+ * ```
2559
+ *
2560
+ * @example
2561
+ * ```html
2562
+ * <!-- Enable with default trigger (dblclick) -->
2563
+ * <tbw-grid [editing]="true" />
2564
+ *
2565
+ * <!-- Specify trigger -->
2566
+ * <tbw-grid [editing]="'click'" />
2567
+ * <tbw-grid [editing]="'dblclick'" />
2568
+ * <tbw-grid [editing]="'manual'" />
2569
+ * ```
2570
+ */
2571
+ editing = input(...(ngDevMode ? [undefined, { debugName: "editing" }] : []));
2572
+ /**
2573
+ * Enable clipboard copy/paste. Requires selection to be enabled.
2574
+ *
2575
+ * **Requires feature import:**
2576
+ * ```typescript
2577
+ * import '@toolbox-web/grid-angular/features/clipboard';
2578
+ * ```
2579
+ *
2580
+ * @example
2581
+ * ```html
2582
+ * <tbw-grid [selection]="'range'" [clipboard]="true" />
2583
+ * ```
2584
+ */
2585
+ clipboard = input(...(ngDevMode ? [undefined, { debugName: "clipboard" }] : []));
2586
+ /**
2587
+ * Enable right-click context menu.
2588
+ *
2589
+ * **Requires feature import:**
2590
+ * ```typescript
2591
+ * import '@toolbox-web/grid-angular/features/context-menu';
2592
+ * ```
2593
+ *
2594
+ * @example
2595
+ * ```html
2596
+ * <tbw-grid [contextMenu]="true" />
2597
+ * ```
2598
+ */
2599
+ contextMenu = input(...(ngDevMode ? [undefined, { debugName: "contextMenu" }] : []));
2600
+ /**
2601
+ * Enable multi-column sorting.
2602
+ *
2603
+ * Multi-sort allows users to sort by multiple columns simultaneously.
2604
+ * For basic single-column sorting, columns with `sortable: true` work without this plugin.
2605
+ *
2606
+ * **Requires feature import:**
2607
+ * ```typescript
2608
+ * import '@toolbox-web/grid-angular/features/multi-sort';
2609
+ * ```
2610
+ *
2611
+ * @example
2612
+ * ```html
2613
+ * <!-- Enable multi-column sorting -->
2614
+ * <tbw-grid [multiSort]="true" />
2615
+ *
2616
+ * <!-- Limit to single column (uses plugin but restricts to 1 column) -->
2617
+ * <tbw-grid [multiSort]="'single'" />
2618
+ *
2619
+ * <!-- Full config -->
2620
+ * <tbw-grid [multiSort]="{ maxSortColumns: 3 }" />
2621
+ * ```
2622
+ */
2623
+ multiSort = input(...(ngDevMode ? [undefined, { debugName: "multiSort" }] : []));
2624
+ /**
2625
+ * @deprecated Use `[multiSort]` instead. Will be removed in a future version.
2626
+ *
2627
+ * Enable column sorting. This is an alias for `[multiSort]`.
2628
+ *
2629
+ * **Requires feature import:**
2630
+ * ```typescript
2631
+ * import '@toolbox-web/grid-angular/features/multi-sort';
2632
+ * ```
2633
+ */
2634
+ sorting = input(...(ngDevMode ? [undefined, { debugName: "sorting" }] : []));
2635
+ /**
2636
+ * Enable column filtering.
2637
+ *
2638
+ * **Requires feature import:**
2639
+ * ```typescript
2640
+ * import '@toolbox-web/grid-angular/features/filtering';
2641
+ * ```
2642
+ *
2643
+ * @example
2644
+ * ```html
2645
+ * <tbw-grid [filtering]="true" />
2646
+ * <tbw-grid [filtering]="{ debounceMs: 200 }" />
2647
+ * ```
2648
+ */
2649
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2650
+ filtering = input(...(ngDevMode ? [undefined, { debugName: "filtering" }] : []));
2651
+ /**
2652
+ * Enable column drag-to-reorder.
2653
+ *
2654
+ * **Requires feature import:**
2655
+ * ```typescript
2656
+ * import '@toolbox-web/grid-angular/features/reorder';
2657
+ * ```
2658
+ *
2659
+ * @example
2660
+ * ```html
2661
+ * <tbw-grid [reorder]="true" />
2662
+ * ```
2663
+ */
2664
+ reorder = input(...(ngDevMode ? [undefined, { debugName: "reorder" }] : []));
2665
+ /**
2666
+ * Enable column visibility toggle panel.
2667
+ *
2668
+ * **Requires feature import:**
2669
+ * ```typescript
2670
+ * import '@toolbox-web/grid-angular/features/visibility';
2671
+ * ```
2672
+ *
2673
+ * @example
2674
+ * ```html
2675
+ * <tbw-grid [visibility]="true" />
2676
+ * ```
2677
+ */
2678
+ visibility = input(...(ngDevMode ? [undefined, { debugName: "visibility" }] : []));
2679
+ /**
2680
+ * Enable pinned/sticky columns.
2681
+ * Columns are pinned via the `sticky` column property.
2682
+ *
2683
+ * **Requires feature import:**
2684
+ * ```typescript
2685
+ * import '@toolbox-web/grid-angular/features/pinned-columns';
2686
+ * ```
2687
+ *
2688
+ * @example
2689
+ * ```html
2690
+ * <tbw-grid [pinnedColumns]="true" [columns]="[
2691
+ * { field: 'id', sticky: 'left' },
2692
+ * { field: 'name' },
2693
+ * { field: 'actions', sticky: 'right' }
2694
+ * ]" />
2695
+ * ```
2696
+ */
2697
+ pinnedColumns = input(...(ngDevMode ? [undefined, { debugName: "pinnedColumns" }] : []));
2698
+ /**
2699
+ * Enable multi-level column headers (column groups).
2700
+ *
2701
+ * **Requires feature import:**
2702
+ * ```typescript
2703
+ * import '@toolbox-web/grid-angular/features/grouping-columns';
2704
+ * ```
2705
+ *
2706
+ * @example
2707
+ * ```html
2708
+ * <tbw-grid [groupingColumns]="{ columnGroups: [...] }" />
2709
+ * ```
2710
+ */
2711
+ groupingColumns = input(...(ngDevMode ? [undefined, { debugName: "groupingColumns" }] : []));
2712
+ /**
2713
+ * Enable horizontal column virtualization for wide grids.
2714
+ *
2715
+ * **Requires feature import:**
2716
+ * ```typescript
2717
+ * import '@toolbox-web/grid-angular/features/column-virtualization';
2718
+ * ```
2719
+ *
2720
+ * @example
2721
+ * ```html
2722
+ * <tbw-grid [columnVirtualization]="true" />
2723
+ * ```
2724
+ */
2725
+ columnVirtualization = input(...(ngDevMode ? [undefined, { debugName: "columnVirtualization" }] : []));
2726
+ /**
2727
+ * Enable row drag-to-reorder.
2728
+ *
2729
+ * **Requires feature import:**
2730
+ * ```typescript
2731
+ * import '@toolbox-web/grid-angular/features/row-reorder';
2732
+ * ```
2733
+ *
2734
+ * @example
2735
+ * ```html
2736
+ * <tbw-grid [rowReorder]="true" />
2737
+ * ```
2738
+ */
2739
+ rowReorder = input(...(ngDevMode ? [undefined, { debugName: "rowReorder" }] : []));
2740
+ /**
2741
+ * Enable row grouping by field values.
2742
+ *
2743
+ * **Requires feature import:**
2744
+ * ```typescript
2745
+ * import '@toolbox-web/grid-angular/features/grouping-rows';
2746
+ * ```
2747
+ *
2748
+ * @example
2749
+ * ```html
2750
+ * <tbw-grid [groupingRows]="{ groupBy: ['department'] }" />
2751
+ * ```
2752
+ */
2753
+ groupingRows = input(...(ngDevMode ? [undefined, { debugName: "groupingRows" }] : []));
2754
+ /**
2755
+ * Enable pinned rows (aggregation/status bar).
2756
+ *
2757
+ * **Requires feature import:**
2758
+ * ```typescript
2759
+ * import '@toolbox-web/grid-angular/features/pinned-rows';
2760
+ * ```
2761
+ *
2762
+ * @example
2763
+ * ```html
2764
+ * <tbw-grid [pinnedRows]="{ bottom: [{ type: 'aggregation' }] }" />
2765
+ * ```
2766
+ */
2767
+ pinnedRows = input(...(ngDevMode ? [undefined, { debugName: "pinnedRows" }] : []));
2768
+ /**
2769
+ * Enable hierarchical tree view.
2770
+ *
2771
+ * **Requires feature import:**
2772
+ * ```typescript
2773
+ * import '@toolbox-web/grid-angular/features/tree';
2774
+ * ```
2775
+ *
2776
+ * @example
2777
+ * ```html
2778
+ * <tbw-grid [tree]="{ childrenField: 'children' }" />
2779
+ * ```
2780
+ */
2781
+ tree = input(...(ngDevMode ? [undefined, { debugName: "tree" }] : []));
2782
+ /**
2783
+ * Enable master-detail expandable rows.
2784
+ *
2785
+ * **Requires feature import:**
2786
+ * ```typescript
2787
+ * import '@toolbox-web/grid-angular/features/master-detail';
2788
+ * ```
2789
+ *
2790
+ * @example
2791
+ * ```html
2792
+ * <tbw-grid [masterDetail]="{ detailRenderer: detailFn }" />
2793
+ * ```
2794
+ */
2795
+ masterDetail = input(...(ngDevMode ? [undefined, { debugName: "masterDetail" }] : []));
2796
+ /**
2797
+ * Enable responsive card layout for narrow viewports.
2798
+ *
2799
+ * **Requires feature import:**
2800
+ * ```typescript
2801
+ * import '@toolbox-web/grid-angular/features/responsive';
2802
+ * ```
2803
+ *
2804
+ * @example
2805
+ * ```html
2806
+ * <tbw-grid [responsive]="{ breakpoint: 768 }" />
2807
+ * ```
2808
+ */
2809
+ responsive = input(...(ngDevMode ? [undefined, { debugName: "responsive" }] : []));
2810
+ /**
2811
+ * Enable undo/redo for cell edits. Requires editing to be enabled.
2812
+ *
2813
+ * **Requires feature import:**
2814
+ * ```typescript
2815
+ * import '@toolbox-web/grid-angular/features/undo-redo';
2816
+ * ```
2817
+ *
2818
+ * @example
2819
+ * ```html
2820
+ * <tbw-grid [editing]="'dblclick'" [undoRedo]="true" />
2821
+ * ```
2822
+ */
2823
+ undoRedo = input(...(ngDevMode ? [undefined, { debugName: "undoRedo" }] : []));
2824
+ /**
2825
+ * Enable CSV/JSON export functionality.
2826
+ *
2827
+ * **Requires feature import:**
2828
+ * ```typescript
2829
+ * import '@toolbox-web/grid-angular/features/export';
2830
+ * ```
2831
+ *
2832
+ * @example
2833
+ * ```html
2834
+ * <tbw-grid [export]="true" />
2835
+ * <tbw-grid [export]="{ filename: 'data.csv' }" />
2836
+ * ```
2837
+ */
2838
+ exportFeature = input(...(ngDevMode ? [undefined, { debugName: "exportFeature" }] : []));
2839
+ /**
2840
+ * Enable print functionality.
2841
+ *
2842
+ * **Requires feature import:**
2843
+ * ```typescript
2844
+ * import '@toolbox-web/grid-angular/features/print';
2845
+ * ```
2846
+ *
2847
+ * @example
2848
+ * ```html
2849
+ * <tbw-grid [print]="true" />
2850
+ * ```
2851
+ */
2852
+ print = input(...(ngDevMode ? [undefined, { debugName: "print" }] : []));
2853
+ /**
2854
+ * Enable pivot table functionality.
2855
+ *
2856
+ * **Requires feature import:**
2857
+ * ```typescript
2858
+ * import '@toolbox-web/grid-angular/features/pivot';
2859
+ * ```
2860
+ *
2861
+ * @example
2862
+ * ```html
2863
+ * <tbw-grid [pivot]="{ rowFields: ['category'], valueField: 'sales' }" />
2864
+ * ```
2865
+ */
2866
+ pivot = input(...(ngDevMode ? [undefined, { debugName: "pivot" }] : []));
2867
+ /**
2868
+ * Enable server-side data operations.
2869
+ *
2870
+ * **Requires feature import:**
2871
+ * ```typescript
2872
+ * import '@toolbox-web/grid-angular/features/server-side';
2873
+ * ```
2874
+ *
2875
+ * @example
2876
+ * ```html
2877
+ * <tbw-grid [serverSide]="{ dataSource: fetchDataFn }" />
2878
+ * ```
2879
+ */
2880
+ serverSide = input(...(ngDevMode ? [undefined, { debugName: "serverSide" }] : []));
2881
+ // ═══════════════════════════════════════════════════════════════════════════
2882
+ // EVENT OUTPUTS - All grid events
2883
+ // ═══════════════════════════════════════════════════════════════════════════
2884
+ /**
2885
+ * Emitted when a cell is clicked.
2886
+ *
2887
+ * @example
2888
+ * ```html
2889
+ * <tbw-grid (cellClick)="onCellClick($event)">...</tbw-grid>
2890
+ * ```
2891
+ */
2892
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2893
+ cellClick = output();
2894
+ /**
2895
+ * Emitted when a row is clicked.
2896
+ *
2897
+ * @example
2898
+ * ```html
2899
+ * <tbw-grid (rowClick)="onRowClick($event)">...</tbw-grid>
2900
+ * ```
2901
+ */
2902
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2903
+ rowClick = output();
2904
+ /**
2905
+ * Emitted when a cell is activated (Enter key or double-click).
2906
+ *
2907
+ * @example
2908
+ * ```html
2909
+ * <tbw-grid (cellActivate)="onCellActivate($event)">...</tbw-grid>
2910
+ * ```
2911
+ */
2912
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2913
+ cellActivate = output();
2914
+ /**
2915
+ * Emitted when a cell value changes (before commit).
2916
+ *
2917
+ * @example
2918
+ * ```html
2919
+ * <tbw-grid (cellChange)="onCellChange($event)">...</tbw-grid>
2920
+ * ```
2921
+ */
2922
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2923
+ cellChange = output();
2924
+ /**
2925
+ * Emitted when a cell value is committed (inline editing).
2926
+ * Provides the row, field, new value, and change tracking information.
2927
+ *
2928
+ * @example
2929
+ * ```html
2930
+ * <tbw-grid (cellCommit)="onCellCommit($event)">...</tbw-grid>
2931
+ * ```
2932
+ *
2933
+ * ```typescript
2934
+ * onCellCommit(event: CellCommitEvent) {
2935
+ * console.log(`Changed ${event.field} to ${event.value} in row ${event.rowIndex}`);
2936
+ * }
2937
+ * ```
2938
+ */
2939
+ cellCommit = output();
2940
+ /**
2941
+ * Emitted when a row's values are committed (bulk/row editing).
2942
+ * Provides the row data and change tracking information.
2943
+ *
2944
+ * @example
2945
+ * ```html
2946
+ * <tbw-grid (rowCommit)="onRowCommit($event)">...</tbw-grid>
2947
+ * ```
2948
+ */
2949
+ rowCommit = output();
2950
+ /**
2951
+ * Emitted when the changed rows are reset.
2952
+ *
2953
+ * @example
2954
+ * ```html
2955
+ * <tbw-grid (changedRowsReset)="onChangedRowsReset($event)">...</tbw-grid>
2956
+ * ```
2957
+ */
2958
+ changedRowsReset = output();
2959
+ /**
2960
+ * Emitted when sort state changes.
2961
+ *
2962
+ * @example
2963
+ * ```html
2964
+ * <tbw-grid (sortChange)="onSortChange($event)">...</tbw-grid>
2965
+ * ```
2966
+ */
2967
+ sortChange = output();
2968
+ /**
2969
+ * Emitted when filter values change.
2970
+ *
2971
+ * @example
2972
+ * ```html
2973
+ * <tbw-grid (filterChange)="onFilterChange($event)">...</tbw-grid>
2974
+ * ```
2975
+ */
2976
+ filterChange = output();
2977
+ /**
2978
+ * Emitted when a column is resized.
2979
+ *
2980
+ * @example
2981
+ * ```html
2982
+ * <tbw-grid (columnResize)="onColumnResize($event)">...</tbw-grid>
2983
+ * ```
2984
+ */
2985
+ columnResize = output();
2986
+ /**
2987
+ * Emitted when a column is moved via drag-and-drop.
2988
+ *
2989
+ * @example
2990
+ * ```html
2991
+ * <tbw-grid (columnMove)="onColumnMove($event)">...</tbw-grid>
2992
+ * ```
2993
+ */
2994
+ columnMove = output();
2995
+ /**
2996
+ * Emitted when column visibility changes.
2997
+ *
2998
+ * @example
2999
+ * ```html
3000
+ * <tbw-grid (columnVisibility)="onColumnVisibility($event)">...</tbw-grid>
3001
+ * ```
3002
+ */
3003
+ columnVisibility = output();
3004
+ /**
3005
+ * Emitted when column state changes (resize, reorder, visibility).
3006
+ *
3007
+ * @example
3008
+ * ```html
3009
+ * <tbw-grid (columnStateChange)="onColumnStateChange($event)">...</tbw-grid>
3010
+ * ```
3011
+ */
3012
+ columnStateChange = output();
3013
+ /**
3014
+ * Emitted when selection changes.
3015
+ *
3016
+ * @example
3017
+ * ```html
3018
+ * <tbw-grid (selectionChange)="onSelectionChange($event)">...</tbw-grid>
3019
+ * ```
3020
+ */
3021
+ selectionChange = output();
3022
+ /**
3023
+ * Emitted when a row is moved via drag-and-drop.
3024
+ *
3025
+ * @example
3026
+ * ```html
3027
+ * <tbw-grid (rowMove)="onRowMove($event)">...</tbw-grid>
3028
+ * ```
3029
+ */
3030
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3031
+ rowMove = output();
3032
+ /**
3033
+ * Emitted when a group is expanded or collapsed.
3034
+ *
3035
+ * @example
3036
+ * ```html
3037
+ * <tbw-grid (groupToggle)="onGroupToggle($event)">...</tbw-grid>
3038
+ * ```
3039
+ */
3040
+ groupToggle = output();
3041
+ /**
3042
+ * Emitted when a tree node is expanded.
3043
+ *
3044
+ * @example
3045
+ * ```html
3046
+ * <tbw-grid (treeExpand)="onTreeExpand($event)">...</tbw-grid>
3047
+ * ```
3048
+ */
3049
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3050
+ treeExpand = output();
3051
+ /**
3052
+ * Emitted when a detail panel is expanded or collapsed.
3053
+ *
3054
+ * @example
3055
+ * ```html
3056
+ * <tbw-grid (detailExpand)="onDetailExpand($event)">...</tbw-grid>
3057
+ * ```
3058
+ */
3059
+ detailExpand = output();
3060
+ /**
3061
+ * Emitted when responsive mode changes (table ↔ card).
3062
+ *
3063
+ * @example
3064
+ * ```html
3065
+ * <tbw-grid (responsiveChange)="onResponsiveChange($event)">...</tbw-grid>
3066
+ * ```
3067
+ */
3068
+ responsiveChange = output();
3069
+ /**
3070
+ * Emitted when cells are copied to clipboard.
3071
+ *
3072
+ * @example
3073
+ * ```html
3074
+ * <tbw-grid (copy)="onCopy($event)">...</tbw-grid>
3075
+ * ```
3076
+ */
3077
+ copy = output();
3078
+ /**
3079
+ * Emitted when cells are pasted from clipboard.
3080
+ *
3081
+ * @example
3082
+ * ```html
3083
+ * <tbw-grid (paste)="onPaste($event)">...</tbw-grid>
3084
+ * ```
3085
+ */
3086
+ paste = output();
3087
+ /**
3088
+ * Emitted when undo/redo is performed.
3089
+ *
3090
+ * @example
3091
+ * ```html
3092
+ * <tbw-grid (undoRedoAction)="onUndoRedo($event)">...</tbw-grid>
3093
+ * ```
3094
+ */
3095
+ undoRedoAction = output();
3096
+ /**
3097
+ * Emitted when export completes.
3098
+ *
3099
+ * @example
3100
+ * ```html
3101
+ * <tbw-grid (exportComplete)="onExportComplete($event)">...</tbw-grid>
3102
+ * ```
3103
+ */
3104
+ exportComplete = output();
3105
+ /**
3106
+ * Emitted when print starts.
3107
+ *
3108
+ * @example
3109
+ * ```html
3110
+ * <tbw-grid (printStart)="onPrintStart($event)">...</tbw-grid>
3111
+ * ```
3112
+ */
3113
+ printStart = output();
3114
+ /**
3115
+ * Emitted when print completes.
3116
+ *
3117
+ * @example
3118
+ * ```html
3119
+ * <tbw-grid (printComplete)="onPrintComplete($event)">...</tbw-grid>
3120
+ * ```
3121
+ */
3122
+ printComplete = output();
3123
+ // Map of output names to event names for automatic wiring
3124
+ eventOutputMap = {
3125
+ cellClick: 'cell-click',
3126
+ rowClick: 'row-click',
3127
+ cellActivate: 'cell-activate',
3128
+ cellChange: 'cell-change',
3129
+ cellCommit: 'cell-commit',
3130
+ rowCommit: 'row-commit',
3131
+ changedRowsReset: 'changed-rows-reset',
3132
+ sortChange: 'sort-change',
3133
+ filterChange: 'filter-change',
3134
+ columnResize: 'column-resize',
3135
+ columnMove: 'column-move',
3136
+ columnVisibility: 'column-visibility',
3137
+ columnStateChange: 'column-state-change',
3138
+ selectionChange: 'selection-change',
3139
+ rowMove: 'row-move',
3140
+ groupToggle: 'group-toggle',
3141
+ treeExpand: 'tree-expand',
3142
+ detailExpand: 'detail-expand',
3143
+ responsiveChange: 'responsive-change',
3144
+ copy: 'copy',
3145
+ paste: 'paste',
3146
+ undoRedoAction: 'undo-redo',
3147
+ exportComplete: 'export-complete',
3148
+ printStart: 'print-start',
3149
+ printComplete: 'print-complete',
3150
+ };
3151
+ // Store event listeners for cleanup
3152
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3153
+ eventListeners = new Map();
3154
+ ngOnInit() {
3155
+ // Create and register the adapter
3156
+ this.adapter = new AngularGridAdapter(this.injector, this.appRef, this.viewContainerRef);
3157
+ DataGridElement.registerAdapter(this.adapter);
3158
+ const grid = this.elementRef.nativeElement;
3159
+ // Wire up all event listeners based on eventOutputMap
3160
+ this.setupEventListeners(grid);
3161
+ // Register adapter on the grid element so MasterDetailPlugin can use it
3162
+ // via the __frameworkAdapter hook during attach()
3163
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3164
+ grid.__frameworkAdapter = this.adapter;
3165
+ }
3166
+ /**
3167
+ * Sets up event listeners for all outputs using the eventOutputMap.
3168
+ */
3169
+ setupEventListeners(grid) {
3170
+ // Wire up all event listeners
3171
+ for (const [outputName, eventName] of Object.entries(this.eventOutputMap)) {
3172
+ const listener = (e) => {
3173
+ const detail = e.detail;
3174
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3175
+ this[outputName].emit(detail);
3176
+ };
3177
+ grid.addEventListener(eventName, listener);
3178
+ this.eventListeners.set(eventName, listener);
3179
+ }
3180
+ }
3181
+ /**
3182
+ * Creates plugins from feature inputs.
3183
+ * Uses the feature registry to allow tree-shaking - only imported features are bundled.
3184
+ * Returns the array of created plugins (doesn't modify grid).
3185
+ */
3186
+ createFeaturePlugins() {
3187
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3188
+ const plugins = [];
3189
+ // Helper to add plugin if feature is registered
3190
+ const addPlugin = (name, config) => {
3191
+ if (config === undefined || config === null || config === false)
3192
+ return;
3193
+ const plugin = createPluginFromFeature(name, config);
3194
+ if (plugin)
3195
+ plugins.push(plugin);
3196
+ };
3197
+ // Add plugins for each feature input
3198
+ addPlugin('selection', this.selection());
3199
+ addPlugin('editing', this.editing());
3200
+ addPlugin('clipboard', this.clipboard());
3201
+ addPlugin('contextMenu', this.contextMenu());
3202
+ // multiSort is the primary input; sorting is a deprecated alias
3203
+ addPlugin('multiSort', this.multiSort() ?? this.sorting());
3204
+ addPlugin('filtering', this.filtering());
3205
+ addPlugin('reorder', this.reorder());
3206
+ addPlugin('visibility', this.visibility());
3207
+ addPlugin('pinnedColumns', this.pinnedColumns());
3208
+ addPlugin('groupingColumns', this.groupingColumns());
3209
+ addPlugin('columnVirtualization', this.columnVirtualization());
3210
+ addPlugin('rowReorder', this.rowReorder());
3211
+ addPlugin('groupingRows', this.groupingRows());
3212
+ addPlugin('pinnedRows', this.pinnedRows());
3213
+ addPlugin('tree', this.tree());
3214
+ addPlugin('masterDetail', this.masterDetail());
3215
+ addPlugin('responsive', this.responsive());
3216
+ addPlugin('undoRedo', this.undoRedo());
3217
+ addPlugin('export', this.exportFeature());
3218
+ addPlugin('print', this.print());
3219
+ addPlugin('pivot', this.pivot());
3220
+ addPlugin('serverSide', this.serverSide());
3221
+ return plugins;
3222
+ }
3223
+ ngAfterContentInit() {
3224
+ // After Angular child directives have initialized (GridColumnView, GridColumnEditor, GridDetailView, GridToolPanel),
3225
+ // force the grid to re-parse light DOM columns so adapters can create renderers/editors
3226
+ const grid = this.elementRef.nativeElement;
3227
+ if (grid && typeof grid.refreshColumns === 'function') {
3228
+ // Use setTimeout to ensure Angular effects have run (template registration)
3229
+ setTimeout(() => {
3230
+ grid.refreshColumns();
3231
+ // Configure MasterDetailPlugin after Angular templates are registered
3232
+ this.configureMasterDetail(grid);
3233
+ // Configure ResponsivePlugin card renderer if template is present
3234
+ this.configureResponsiveCard(grid);
3235
+ // Refresh shell header to pick up tool panel templates
3236
+ // This allows Angular templates to be used in tool panels
3237
+ if (typeof grid.refreshShellHeader === 'function') {
3238
+ grid.refreshShellHeader();
3239
+ }
3240
+ // Register custom styles if provided
3241
+ this.registerCustomStyles(grid);
3242
+ }, 0);
3243
+ }
3244
+ }
3245
+ /**
3246
+ * Registers custom styles into the grid.
3247
+ * Uses the grid's registerStyles() API for clean encapsulation.
3248
+ */
3249
+ registerCustomStyles(grid) {
3250
+ const styles = this.customStyles();
3251
+ if (!styles)
3252
+ return;
3253
+ // Wait for grid to be ready before registering styles
3254
+ grid.ready?.().then(() => {
3255
+ grid.registerStyles?.('angular-custom-styles', styles);
3256
+ });
3257
+ }
3258
+ /**
3259
+ * Configures the MasterDetailPlugin after Angular templates are registered.
3260
+ * - If plugin exists: refresh its detail renderer
3261
+ * - If plugin doesn't exist but <tbw-grid-detail> is present: dynamically import and add the plugin
3262
+ */
3263
+ async configureMasterDetail(grid) {
3264
+ if (!this.adapter)
3265
+ return;
3266
+ // Check for existing plugin by name to avoid importing the class
3267
+ const existingPlugin = grid.gridConfig?.plugins?.find((p) => p.name === 'masterDetail');
3268
+ if (existingPlugin && typeof existingPlugin.refreshDetailRenderer === 'function') {
3269
+ // Plugin exists - just refresh the renderer to pick up Angular templates
3270
+ existingPlugin.refreshDetailRenderer();
3271
+ return;
3272
+ }
3273
+ // Check if <tbw-grid-detail> is present in light DOM
3274
+ const detailElement = grid.querySelector('tbw-grid-detail');
3275
+ if (!detailElement)
3276
+ return;
3277
+ // Create detail renderer from Angular template
3278
+ const detailRenderer = this.adapter.createDetailRenderer(grid);
3279
+ if (!detailRenderer)
3280
+ return;
3281
+ // Parse configuration from attributes
3282
+ const animationAttr = detailElement.getAttribute('animation');
3283
+ let animation = 'slide';
3284
+ if (animationAttr === 'false') {
3285
+ animation = false;
3286
+ }
3287
+ else if (animationAttr === 'fade') {
3288
+ animation = 'fade';
3289
+ }
3290
+ const showExpandColumn = detailElement.getAttribute('showExpandColumn') !== 'false';
3291
+ // Dynamically import the plugin to avoid bundling it when not used
3292
+ const { MasterDetailPlugin } = await import('@toolbox-web/grid/plugins/master-detail');
3293
+ // Create and add the plugin
3294
+ const plugin = new MasterDetailPlugin({
3295
+ detailRenderer: detailRenderer,
3296
+ showExpandColumn,
3297
+ animation,
3298
+ });
3299
+ const currentConfig = grid.gridConfig || {};
3300
+ const existingPlugins = currentConfig.plugins || [];
3301
+ grid.gridConfig = {
3302
+ ...currentConfig,
3303
+ plugins: [...existingPlugins, plugin],
3304
+ };
3305
+ }
3306
+ /**
3307
+ * Configures the ResponsivePlugin with Angular template-based card renderer.
3308
+ * - If plugin exists: updates its cardRenderer configuration
3309
+ * - If plugin doesn't exist but <tbw-grid-responsive-card> is present: logs a warning
3310
+ */
3311
+ configureResponsiveCard(grid) {
3312
+ if (!this.adapter)
3313
+ return;
3314
+ // Check if <tbw-grid-responsive-card> is present in light DOM
3315
+ const cardElement = grid.querySelector('tbw-grid-responsive-card');
3316
+ if (!cardElement)
3317
+ return;
3318
+ // Create card renderer from Angular template
3319
+ const cardRenderer = this.adapter.createResponsiveCardRenderer(grid);
3320
+ if (!cardRenderer)
3321
+ return;
3322
+ // Find existing plugin by name to avoid importing the class
3323
+ const existingPlugin = grid.gridConfig?.plugins?.find((p) => p.name === 'responsive');
3324
+ if (existingPlugin && typeof existingPlugin.setCardRenderer === 'function') {
3325
+ // Plugin exists - update its cardRenderer
3326
+ existingPlugin.setCardRenderer(cardRenderer);
3327
+ return;
3328
+ }
3329
+ // Plugin doesn't exist - log a warning
3330
+ console.warn('[tbw-grid-angular] <tbw-grid-responsive-card> found but ResponsivePlugin is not configured.\n' +
3331
+ 'Add ResponsivePlugin to your gridConfig.plugins array:\n\n' +
3332
+ ' import { ResponsivePlugin } from "@toolbox-web/grid/plugins/responsive";\n' +
3333
+ ' gridConfig = {\n' +
3334
+ ' plugins: [new ResponsivePlugin({ breakpoint: 600 })]\n' +
3335
+ ' };');
3336
+ }
3337
+ ngOnDestroy() {
3338
+ const grid = this.elementRef.nativeElement;
3339
+ // Cleanup all event listeners
3340
+ if (grid) {
3341
+ for (const [eventName, listener] of this.eventListeners) {
3342
+ grid.removeEventListener(eventName, listener);
3343
+ }
3344
+ this.eventListeners.clear();
3345
+ }
3346
+ // Cleanup custom styles
3347
+ if (grid && this.customStyles()) {
3348
+ grid.unregisterStyles?.('angular-custom-styles');
3349
+ }
3350
+ // Cleanup adapter if needed
3351
+ if (this.adapter) {
3352
+ this.adapter.destroy?.();
3353
+ this.adapter = null;
3354
+ }
3355
+ }
3356
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: Grid, deps: [], target: i0.ɵɵFactoryTarget.Directive });
3357
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.1", 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 }, angularConfig: { classPropertyName: "angularConfig", publicName: "angularConfig", 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 }, sorting: { classPropertyName: "sorting", publicName: "sorting", isSignal: true, isRequired: false, transformFunction: null }, filtering: { classPropertyName: "filtering", publicName: "filtering", isSignal: true, isRequired: false, transformFunction: null }, reorder: { classPropertyName: "reorder", publicName: "reorder", 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 }, rowReorder: { classPropertyName: "rowReorder", publicName: "rowReorder", 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: "exportFeature", 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 } }, 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", groupToggle: "groupToggle", treeExpand: "treeExpand", detailExpand: "detailExpand", responsiveChange: "responsiveChange", copy: "copy", paste: "paste", undoRedoAction: "undoRedoAction", exportComplete: "exportComplete", printStart: "printStart", printComplete: "printComplete" }, ngImport: i0 });
3358
+ }
3359
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: Grid, decorators: [{
3360
+ type: Directive,
3361
+ args: [{ selector: 'tbw-grid' }]
3362
+ }], 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 }] }], angularConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "angularConfig", 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 }] }], sorting: [{ type: i0.Input, args: [{ isSignal: true, alias: "sorting", required: false }] }], filtering: [{ type: i0.Input, args: [{ isSignal: true, alias: "filtering", required: false }] }], reorder: [{ type: i0.Input, args: [{ isSignal: true, alias: "reorder", 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 }] }], rowReorder: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowReorder", 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: "exportFeature", 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 }] }], 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"] }], 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"] }] } });
3363
+
3364
+ /**
3365
+ * @packageDocumentation
3366
+ * @toolbox-web/grid-angular - Angular adapter for @toolbox-web/grid.
3367
+ *
3368
+ * Provides directives for seamless Angular integration with the grid component.
3369
+ */
3370
+
3371
+ /**
3372
+ * Generated bundle index. Do not edit.
3373
+ */
3374
+
3375
+ export { AngularGridAdapter, BaseGridEditor, GRID_ICONS, GRID_TYPE_DEFAULTS, Grid, GridColumnEditor, GridColumnView, GridDetailView, GridFormArray, GridIconRegistry, GridResponsiveCard, GridToolPanel, GridTypeRegistry, TbwEditor as TbwCellEditor, TbwRenderer as TbwCellView, TbwEditor, TbwRenderer, clearFeatureRegistry, createPluginFromFeature, getFeatureFactory, getFormArrayContext, getRegisteredFeatures, injectGrid, isComponentClass, isFeatureRegistered, provideGridIcons, provideGridTypeDefaults, registerFeature };
3376
+ //# sourceMappingURL=toolbox-web-grid-angular.mjs.map