@toolbox-web/grid-angular 0.7.0 → 0.7.2

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 (210) hide show
  1. package/fesm2022/toolbox-web-grid-angular-features-clipboard.mjs +30 -0
  2. package/fesm2022/toolbox-web-grid-angular-features-clipboard.mjs.map +1 -0
  3. package/fesm2022/toolbox-web-grid-angular-features-column-virtualization.mjs +28 -0
  4. package/fesm2022/toolbox-web-grid-angular-features-column-virtualization.mjs.map +1 -0
  5. package/fesm2022/toolbox-web-grid-angular-features-context-menu.mjs +28 -0
  6. package/fesm2022/toolbox-web-grid-angular-features-context-menu.mjs.map +1 -0
  7. package/fesm2022/toolbox-web-grid-angular-features-editing.mjs +33 -0
  8. package/fesm2022/toolbox-web-grid-angular-features-editing.mjs.map +1 -0
  9. package/fesm2022/toolbox-web-grid-angular-features-export.mjs +29 -0
  10. package/fesm2022/toolbox-web-grid-angular-features-export.mjs.map +1 -0
  11. package/fesm2022/toolbox-web-grid-angular-features-filtering.mjs +29 -0
  12. package/fesm2022/toolbox-web-grid-angular-features-filtering.mjs.map +1 -0
  13. package/fesm2022/toolbox-web-grid-angular-features-grouping-columns.mjs +28 -0
  14. package/fesm2022/toolbox-web-grid-angular-features-grouping-columns.mjs.map +1 -0
  15. package/fesm2022/toolbox-web-grid-angular-features-grouping-rows.mjs +25 -0
  16. package/fesm2022/toolbox-web-grid-angular-features-grouping-rows.mjs.map +1 -0
  17. package/fesm2022/toolbox-web-grid-angular-features-master-detail.mjs +25 -0
  18. package/fesm2022/toolbox-web-grid-angular-features-master-detail.mjs.map +1 -0
  19. package/fesm2022/toolbox-web-grid-angular-features-multi-sort.mjs +40 -0
  20. package/fesm2022/toolbox-web-grid-angular-features-multi-sort.mjs.map +1 -0
  21. package/{features/pinned-columns.d.ts → fesm2022/toolbox-web-grid-angular-features-pinned-columns.mjs} +11 -2
  22. package/fesm2022/toolbox-web-grid-angular-features-pinned-columns.mjs.map +1 -0
  23. package/fesm2022/toolbox-web-grid-angular-features-pinned-rows.mjs +28 -0
  24. package/fesm2022/toolbox-web-grid-angular-features-pinned-rows.mjs.map +1 -0
  25. package/fesm2022/toolbox-web-grid-angular-features-pivot.mjs +25 -0
  26. package/fesm2022/toolbox-web-grid-angular-features-pivot.mjs.map +1 -0
  27. package/fesm2022/toolbox-web-grid-angular-features-print.mjs +28 -0
  28. package/fesm2022/toolbox-web-grid-angular-features-print.mjs.map +1 -0
  29. package/fesm2022/toolbox-web-grid-angular-features-reorder.mjs +28 -0
  30. package/fesm2022/toolbox-web-grid-angular-features-reorder.mjs.map +1 -0
  31. package/fesm2022/toolbox-web-grid-angular-features-responsive.mjs +28 -0
  32. package/fesm2022/toolbox-web-grid-angular-features-responsive.mjs.map +1 -0
  33. package/fesm2022/toolbox-web-grid-angular-features-row-reorder.mjs +28 -0
  34. package/fesm2022/toolbox-web-grid-angular-features-row-reorder.mjs.map +1 -0
  35. package/fesm2022/toolbox-web-grid-angular-features-selection.mjs +30 -0
  36. package/fesm2022/toolbox-web-grid-angular-features-selection.mjs.map +1 -0
  37. package/fesm2022/toolbox-web-grid-angular-features-server-side.mjs +25 -0
  38. package/fesm2022/toolbox-web-grid-angular-features-server-side.mjs.map +1 -0
  39. package/fesm2022/toolbox-web-grid-angular-features-sorting.mjs +25 -0
  40. package/fesm2022/toolbox-web-grid-angular-features-sorting.mjs.map +1 -0
  41. package/fesm2022/toolbox-web-grid-angular-features-tree.mjs +28 -0
  42. package/fesm2022/toolbox-web-grid-angular-features-tree.mjs.map +1 -0
  43. package/fesm2022/toolbox-web-grid-angular-features-undo-redo.mjs +30 -0
  44. package/fesm2022/toolbox-web-grid-angular-features-undo-redo.mjs.map +1 -0
  45. package/fesm2022/toolbox-web-grid-angular-features-visibility.mjs +28 -0
  46. package/fesm2022/toolbox-web-grid-angular-features-visibility.mjs.map +1 -0
  47. package/fesm2022/toolbox-web-grid-angular.mjs +3189 -0
  48. package/fesm2022/toolbox-web-grid-angular.mjs.map +1 -0
  49. package/package.json +109 -35
  50. package/types/toolbox-web-grid-angular-features-clipboard.d.ts +3 -0
  51. package/types/toolbox-web-grid-angular-features-clipboard.d.ts.map +1 -0
  52. package/types/toolbox-web-grid-angular-features-column-virtualization.d.ts +3 -0
  53. package/types/toolbox-web-grid-angular-features-column-virtualization.d.ts.map +1 -0
  54. package/types/toolbox-web-grid-angular-features-context-menu.d.ts +3 -0
  55. package/types/toolbox-web-grid-angular-features-context-menu.d.ts.map +1 -0
  56. package/types/toolbox-web-grid-angular-features-editing.d.ts +3 -0
  57. package/types/toolbox-web-grid-angular-features-editing.d.ts.map +1 -0
  58. package/types/toolbox-web-grid-angular-features-export.d.ts +3 -0
  59. package/types/toolbox-web-grid-angular-features-export.d.ts.map +1 -0
  60. package/types/toolbox-web-grid-angular-features-filtering.d.ts +3 -0
  61. package/types/toolbox-web-grid-angular-features-filtering.d.ts.map +1 -0
  62. package/types/toolbox-web-grid-angular-features-grouping-columns.d.ts +3 -0
  63. package/types/toolbox-web-grid-angular-features-grouping-columns.d.ts.map +1 -0
  64. package/types/toolbox-web-grid-angular-features-grouping-rows.d.ts +3 -0
  65. package/types/toolbox-web-grid-angular-features-grouping-rows.d.ts.map +1 -0
  66. package/types/toolbox-web-grid-angular-features-master-detail.d.ts +3 -0
  67. package/types/toolbox-web-grid-angular-features-master-detail.d.ts.map +1 -0
  68. package/types/toolbox-web-grid-angular-features-multi-sort.d.ts +3 -0
  69. package/types/toolbox-web-grid-angular-features-multi-sort.d.ts.map +1 -0
  70. package/types/toolbox-web-grid-angular-features-pinned-columns.d.ts +3 -0
  71. package/types/toolbox-web-grid-angular-features-pinned-columns.d.ts.map +1 -0
  72. package/types/toolbox-web-grid-angular-features-pinned-rows.d.ts +3 -0
  73. package/types/toolbox-web-grid-angular-features-pinned-rows.d.ts.map +1 -0
  74. package/types/toolbox-web-grid-angular-features-pivot.d.ts +3 -0
  75. package/types/toolbox-web-grid-angular-features-pivot.d.ts.map +1 -0
  76. package/types/toolbox-web-grid-angular-features-print.d.ts +3 -0
  77. package/types/toolbox-web-grid-angular-features-print.d.ts.map +1 -0
  78. package/types/toolbox-web-grid-angular-features-reorder.d.ts +3 -0
  79. package/types/toolbox-web-grid-angular-features-reorder.d.ts.map +1 -0
  80. package/types/toolbox-web-grid-angular-features-responsive.d.ts +3 -0
  81. package/types/toolbox-web-grid-angular-features-responsive.d.ts.map +1 -0
  82. package/types/toolbox-web-grid-angular-features-row-reorder.d.ts +3 -0
  83. package/types/toolbox-web-grid-angular-features-row-reorder.d.ts.map +1 -0
  84. package/types/toolbox-web-grid-angular-features-selection.d.ts +3 -0
  85. package/types/toolbox-web-grid-angular-features-selection.d.ts.map +1 -0
  86. package/types/toolbox-web-grid-angular-features-server-side.d.ts +3 -0
  87. package/types/toolbox-web-grid-angular-features-server-side.d.ts.map +1 -0
  88. package/types/toolbox-web-grid-angular-features-sorting.d.ts +3 -0
  89. package/types/toolbox-web-grid-angular-features-sorting.d.ts.map +1 -0
  90. package/types/toolbox-web-grid-angular-features-tree.d.ts +3 -0
  91. package/types/toolbox-web-grid-angular-features-tree.d.ts.map +1 -0
  92. package/types/toolbox-web-grid-angular-features-undo-redo.d.ts +3 -0
  93. package/types/toolbox-web-grid-angular-features-undo-redo.d.ts.map +1 -0
  94. package/types/toolbox-web-grid-angular-features-visibility.d.ts +3 -0
  95. package/types/toolbox-web-grid-angular-features-visibility.d.ts.map +1 -0
  96. package/types/toolbox-web-grid-angular.d.ts +2325 -0
  97. package/types/toolbox-web-grid-angular.d.ts.map +1 -0
  98. package/feature-registry-C-cKloXB.js +0 -45
  99. package/features/clipboard.d.ts +0 -18
  100. package/features/clipboard.d.ts.map +0 -1
  101. package/features/clipboard.js +0 -3
  102. package/features/column-virtualization.d.ts +0 -16
  103. package/features/column-virtualization.d.ts.map +0 -1
  104. package/features/column-virtualization.js +0 -3
  105. package/features/context-menu.d.ts +0 -16
  106. package/features/context-menu.d.ts.map +0 -1
  107. package/features/context-menu.js +0 -3
  108. package/features/editing.d.ts +0 -16
  109. package/features/editing.d.ts.map +0 -1
  110. package/features/editing.js +0 -3
  111. package/features/export.d.ts +0 -17
  112. package/features/export.d.ts.map +0 -1
  113. package/features/export.js +0 -3
  114. package/features/filtering.d.ts +0 -17
  115. package/features/filtering.d.ts.map +0 -1
  116. package/features/filtering.js +0 -3
  117. package/features/grouping-columns.d.ts +0 -16
  118. package/features/grouping-columns.d.ts.map +0 -1
  119. package/features/grouping-columns.js +0 -3
  120. package/features/grouping-rows.d.ts +0 -16
  121. package/features/grouping-rows.d.ts.map +0 -1
  122. package/features/grouping-rows.js +0 -3
  123. package/features/index.d.ts +0 -1
  124. package/features/index.d.ts.map +0 -1
  125. package/features/index.js +0 -22
  126. package/features/master-detail.d.ts +0 -16
  127. package/features/master-detail.d.ts.map +0 -1
  128. package/features/master-detail.js +0 -3
  129. package/features/multi-sort.d.ts +0 -22
  130. package/features/multi-sort.d.ts.map +0 -1
  131. package/features/pinned-columns.d.ts.map +0 -1
  132. package/features/pinned-columns.js +0 -3
  133. package/features/pinned-rows.d.ts +0 -16
  134. package/features/pinned-rows.d.ts.map +0 -1
  135. package/features/pinned-rows.js +0 -3
  136. package/features/pivot.d.ts +0 -16
  137. package/features/pivot.d.ts.map +0 -1
  138. package/features/pivot.js +0 -3
  139. package/features/print.d.ts +0 -16
  140. package/features/print.d.ts.map +0 -1
  141. package/features/print.js +0 -3
  142. package/features/reorder.d.ts +0 -16
  143. package/features/reorder.d.ts.map +0 -1
  144. package/features/reorder.js +0 -3
  145. package/features/responsive.d.ts +0 -16
  146. package/features/responsive.d.ts.map +0 -1
  147. package/features/responsive.js +0 -3
  148. package/features/row-reorder.d.ts +0 -16
  149. package/features/row-reorder.d.ts.map +0 -1
  150. package/features/row-reorder.js +0 -3
  151. package/features/selection.d.ts +0 -16
  152. package/features/selection.d.ts.map +0 -1
  153. package/features/selection.js +0 -3
  154. package/features/server-side.d.ts +0 -16
  155. package/features/server-side.d.ts.map +0 -1
  156. package/features/server-side.js +0 -3
  157. package/features/sorting.d.ts +0 -1
  158. package/features/sorting.d.ts.map +0 -1
  159. package/features/sorting.js +0 -1
  160. package/features/tree.d.ts +0 -16
  161. package/features/tree.d.ts.map +0 -1
  162. package/features/tree.js +0 -3
  163. package/features/undo-redo.d.ts +0 -18
  164. package/features/undo-redo.d.ts.map +0 -1
  165. package/features/undo-redo.js +0 -3
  166. package/features/visibility.d.ts +0 -16
  167. package/features/visibility.d.ts.map +0 -1
  168. package/features/visibility.js +0 -3
  169. package/index.d.ts +0 -34
  170. package/index.d.ts.map +0 -1
  171. package/index.js +0 -1916
  172. package/lib/angular-column-config.d.ts +0 -140
  173. package/lib/angular-column-config.d.ts.map +0 -1
  174. package/lib/angular-grid-adapter.d.ts +0 -220
  175. package/lib/angular-grid-adapter.d.ts.map +0 -1
  176. package/lib/base-grid-editor.d.ts +0 -145
  177. package/lib/base-grid-editor.d.ts.map +0 -1
  178. package/lib/component-registry.d.ts +0 -63
  179. package/lib/component-registry.d.ts.map +0 -1
  180. package/lib/directives/grid-column-editor.directive.d.ts +0 -113
  181. package/lib/directives/grid-column-editor.directive.d.ts.map +0 -1
  182. package/lib/directives/grid-column-view.directive.d.ts +0 -69
  183. package/lib/directives/grid-column-view.directive.d.ts.map +0 -1
  184. package/lib/directives/grid-detail-view.directive.d.ts +0 -75
  185. package/lib/directives/grid-detail-view.directive.d.ts.map +0 -1
  186. package/lib/directives/grid-form-array.directive.d.ts +0 -141
  187. package/lib/directives/grid-form-array.directive.d.ts.map +0 -1
  188. package/lib/directives/grid-responsive-card.directive.d.ts +0 -92
  189. package/lib/directives/grid-responsive-card.directive.d.ts.map +0 -1
  190. package/lib/directives/grid-tool-panel.directive.d.ts +0 -91
  191. package/lib/directives/grid-tool-panel.directive.d.ts.map +0 -1
  192. package/lib/directives/grid.directive.d.ts +0 -808
  193. package/lib/directives/grid.directive.d.ts.map +0 -1
  194. package/lib/directives/index.d.ts +0 -9
  195. package/lib/directives/index.d.ts.map +0 -1
  196. package/lib/directives/structural-directives.d.ts +0 -174
  197. package/lib/directives/structural-directives.d.ts.map +0 -1
  198. package/lib/feature-registry.d.ts +0 -72
  199. package/lib/feature-registry.d.ts.map +0 -1
  200. package/lib/grid-type-registry.d.ts +0 -110
  201. package/lib/grid-type-registry.d.ts.map +0 -1
  202. package/lib/inject-grid.d.ts +0 -109
  203. package/lib/inject-grid.d.ts.map +0 -1
  204. package/lib/interfaces/grid-cell-editor.d.ts +0 -85
  205. package/lib/interfaces/grid-cell-editor.d.ts.map +0 -1
  206. package/lib/interfaces/grid-cell-renderer.d.ts +0 -63
  207. package/lib/interfaces/grid-cell-renderer.d.ts.map +0 -1
  208. package/lib/interfaces/index.d.ts +0 -5
  209. package/lib/interfaces/index.d.ts.map +0 -1
  210. package/multi-sort-DPbW58yz.js +0 -3
@@ -0,0 +1,3189 @@
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
+ * Angular inject function for programmatic access to a grid instance.
1617
+ *
1618
+ * This function should be called in the constructor or as a field initializer
1619
+ * of an Angular component that contains a `<tbw-grid>` element.
1620
+ *
1621
+ * ## Usage
1622
+ *
1623
+ * ```typescript
1624
+ * import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
1625
+ * import { Grid, injectGrid } from '@toolbox-web/grid-angular';
1626
+ *
1627
+ * @Component({
1628
+ * selector: 'app-my-grid',
1629
+ * imports: [Grid],
1630
+ * schemas: [CUSTOM_ELEMENTS_SCHEMA],
1631
+ * template: `
1632
+ * <button (click)="handleResize()">Force Layout</button>
1633
+ * <button (click)="handleExport()" [disabled]="!grid.isReady()">Export</button>
1634
+ * <tbw-grid [rows]="rows" [gridConfig]="config"></tbw-grid>
1635
+ * `
1636
+ * })
1637
+ * export class MyGridComponent {
1638
+ * grid = injectGrid<Employee>();
1639
+ *
1640
+ * async handleResize() {
1641
+ * await this.grid.forceLayout();
1642
+ * }
1643
+ *
1644
+ * async handleExport() {
1645
+ * const config = await this.grid.getConfig();
1646
+ * console.log('Exporting with columns:', config?.columns);
1647
+ * }
1648
+ * }
1649
+ * ```
1650
+ *
1651
+ * @returns Object with grid access methods and state signals
1652
+ */
1653
+ function injectGrid() {
1654
+ const elementRef = inject(ElementRef);
1655
+ // Reactive signals
1656
+ const isReady = signal(false, ...(ngDevMode ? [{ debugName: "isReady" }] : []));
1657
+ const config = signal(null, ...(ngDevMode ? [{ debugName: "config" }] : []));
1658
+ const element = signal(null, ...(ngDevMode ? [{ debugName: "element" }] : []));
1659
+ // Initialize after render
1660
+ afterNextRender(() => {
1661
+ const gridElement = elementRef.nativeElement.querySelector('tbw-grid');
1662
+ if (!gridElement) {
1663
+ console.warn('[injectGrid] No tbw-grid element found in component');
1664
+ return;
1665
+ }
1666
+ element.set(gridElement);
1667
+ // Wait for grid to be ready
1668
+ gridElement.ready?.().then(async () => {
1669
+ isReady.set(true);
1670
+ const effectiveConfig = gridElement.getConfig?.();
1671
+ if (effectiveConfig) {
1672
+ config.set(effectiveConfig);
1673
+ }
1674
+ });
1675
+ });
1676
+ // Computed visible columns
1677
+ const visibleColumns = computed(() => {
1678
+ const currentConfig = config();
1679
+ if (!currentConfig?.columns)
1680
+ return [];
1681
+ return currentConfig.columns.filter((col) => !col.hidden);
1682
+ }, ...(ngDevMode ? [{ debugName: "visibleColumns" }] : []));
1683
+ // ═══════════════════════════════════════════════════════════════════
1684
+ // CORE METHODS
1685
+ // ═══════════════════════════════════════════════════════════════════
1686
+ const getConfig = async () => {
1687
+ const gridElement = element();
1688
+ if (!gridElement)
1689
+ return null;
1690
+ const effectiveConfig = gridElement.getConfig?.();
1691
+ return effectiveConfig ?? null;
1692
+ };
1693
+ const forceLayout = async () => {
1694
+ const gridElement = element();
1695
+ if (!gridElement)
1696
+ return;
1697
+ await gridElement.forceLayout?.();
1698
+ };
1699
+ const toggleGroup = async (key) => {
1700
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1701
+ const gridElement = element();
1702
+ if (!gridElement)
1703
+ return;
1704
+ await gridElement.toggleGroup?.(key);
1705
+ };
1706
+ const registerStyles = (id, css) => {
1707
+ element()?.registerStyles?.(id, css);
1708
+ };
1709
+ const unregisterStyles = (id) => {
1710
+ element()?.unregisterStyles?.(id);
1711
+ };
1712
+ // ═══════════════════════════════════════════════════════════════════
1713
+ // SELECTION CONVENIENCE METHODS
1714
+ // ═══════════════════════════════════════════════════════════════════
1715
+ const selectAll = () => {
1716
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1717
+ const gridElement = element();
1718
+ const plugin = gridElement?.getPluginByName?.('selection');
1719
+ if (!plugin) {
1720
+ console.warn('[injectGrid] selectAll requires SelectionPlugin');
1721
+ return;
1722
+ }
1723
+ // Row mode: select all row indices
1724
+ if (plugin.config?.mode === 'row') {
1725
+ const rows = gridElement?.rows ?? [];
1726
+ const allIndices = new Set(rows.map((_, i) => i));
1727
+ plugin.selected = allIndices;
1728
+ plugin.requestAfterRender?.();
1729
+ }
1730
+ };
1731
+ const clearSelection = () => {
1732
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1733
+ const gridElement = element();
1734
+ const plugin = gridElement?.getPluginByName?.('selection');
1735
+ if (!plugin)
1736
+ return;
1737
+ const mode = plugin.config?.mode;
1738
+ if (mode === 'row') {
1739
+ plugin.selected = new Set();
1740
+ }
1741
+ else if (mode === 'range' || mode === 'cell') {
1742
+ plugin.ranges = [];
1743
+ }
1744
+ plugin.requestAfterRender?.();
1745
+ };
1746
+ const getSelectedIndices = () => {
1747
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1748
+ const gridElement = element();
1749
+ const plugin = gridElement?.getPluginByName?.('selection');
1750
+ if (!plugin)
1751
+ return new Set();
1752
+ if (plugin.config?.mode === 'row') {
1753
+ return new Set(plugin.selected ?? []);
1754
+ }
1755
+ // Range/cell mode: extract unique row indices from ranges
1756
+ const ranges = plugin.ranges ?? [];
1757
+ const indices = new Set();
1758
+ for (const range of ranges) {
1759
+ for (let r = range.startRow; r <= range.endRow; r++) {
1760
+ indices.add(r);
1761
+ }
1762
+ }
1763
+ return indices;
1764
+ };
1765
+ const getSelectedRows = () => {
1766
+ const gridElement = element();
1767
+ if (!gridElement)
1768
+ return [];
1769
+ const rows = gridElement.rows ?? [];
1770
+ const indices = getSelectedIndices();
1771
+ return Array.from(indices)
1772
+ .filter((i) => i >= 0 && i < rows.length)
1773
+ .map((i) => rows[i]);
1774
+ };
1775
+ // ═══════════════════════════════════════════════════════════════════
1776
+ // EXPORT CONVENIENCE METHODS
1777
+ // ═══════════════════════════════════════════════════════════════════
1778
+ const exportToCsv = (filename) => {
1779
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1780
+ const gridElement = element();
1781
+ const plugin = gridElement?.getPluginByName?.('export');
1782
+ if (!plugin) {
1783
+ console.warn('[injectGrid] exportToCsv requires ExportPlugin');
1784
+ return;
1785
+ }
1786
+ plugin.exportToCsv?.(filename);
1787
+ };
1788
+ const exportToJson = (filename) => {
1789
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1790
+ const gridElement = element();
1791
+ const plugin = gridElement?.getPluginByName?.('export');
1792
+ if (!plugin) {
1793
+ console.warn('[injectGrid] exportToJson requires ExportPlugin');
1794
+ return;
1795
+ }
1796
+ plugin.exportToJson?.(filename);
1797
+ };
1798
+ return {
1799
+ element,
1800
+ isReady,
1801
+ config,
1802
+ visibleColumns,
1803
+ getConfig,
1804
+ forceLayout,
1805
+ toggleGroup,
1806
+ registerStyles,
1807
+ unregisterStyles,
1808
+ selectAll,
1809
+ clearSelection,
1810
+ getSelectedIndices,
1811
+ getSelectedRows,
1812
+ exportToCsv,
1813
+ exportToJson,
1814
+ };
1815
+ }
1816
+
1817
+ /**
1818
+ * Feature Registry for @toolbox-web/grid-angular
1819
+ *
1820
+ * This module provides a synchronous registry for plugin factories.
1821
+ * Features are registered via side-effect imports, enabling tree-shaking
1822
+ * while maintaining the clean input-based API.
1823
+ *
1824
+ * @example
1825
+ * ```typescript
1826
+ * // Import features you need (side-effect imports)
1827
+ * import '@toolbox-web/grid-angular/features/selection';
1828
+ * import '@toolbox-web/grid-angular/features/filtering';
1829
+ *
1830
+ * // Inputs work automatically - no async loading, no HTTP requests
1831
+ * <tbw-grid [selection]="'range'" [filtering]="{ debounceMs: 200 }" />
1832
+ * ```
1833
+ */
1834
+ /**
1835
+ * Central registry mapping feature names to their plugin factories.
1836
+ * Populated by side-effect feature imports.
1837
+ */
1838
+ const featureRegistry = new Map();
1839
+ /**
1840
+ * Set of features that have been used without being registered.
1841
+ * Used to show helpful warnings only once per feature.
1842
+ */
1843
+ const warnedFeatures = new Set();
1844
+ /**
1845
+ * Register a feature's plugin factory.
1846
+ * Called by side-effect feature imports.
1847
+ *
1848
+ * @param name - The feature name (matches the input name on Grid directive)
1849
+ * @param factory - Function that creates the plugin instance
1850
+ *
1851
+ * @example
1852
+ * ```ts
1853
+ * // features/selection.ts
1854
+ * import { SelectionPlugin } from '@toolbox-web/grid/plugins/selection';
1855
+ * import { registerFeature } from '../lib/feature-registry';
1856
+ *
1857
+ * registerFeature('selection', (config) => new SelectionPlugin(config));
1858
+ * ```
1859
+ */
1860
+ function registerFeature(name, factory) {
1861
+ featureRegistry.set(name, {
1862
+ factory: factory,
1863
+ name,
1864
+ });
1865
+ }
1866
+ /**
1867
+ * Check if a feature is registered.
1868
+ */
1869
+ function isFeatureRegistered(name) {
1870
+ return featureRegistry.has(name);
1871
+ }
1872
+ /**
1873
+ * Get a registered feature's factory.
1874
+ * Returns undefined if not registered.
1875
+ */
1876
+ function getFeatureFactory(name) {
1877
+ return featureRegistry.get(name)?.factory;
1878
+ }
1879
+ /**
1880
+ * Get all registered feature names.
1881
+ * Useful for debugging.
1882
+ */
1883
+ function getRegisteredFeatures() {
1884
+ return Array.from(featureRegistry.keys());
1885
+ }
1886
+ /**
1887
+ * Create a plugin instance for a feature.
1888
+ * Shows a helpful warning if the feature is not registered.
1889
+ *
1890
+ * @param name - Feature name
1891
+ * @param config - Plugin configuration
1892
+ * @returns Plugin instance or undefined if not registered
1893
+ */
1894
+ function createPluginFromFeature(name, config) {
1895
+ const entry = featureRegistry.get(name);
1896
+ if (!entry) {
1897
+ // Show warning only once per feature in development
1898
+ const isDev = typeof window !== 'undefined' &&
1899
+ (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1');
1900
+ if (!warnedFeatures.has(name) && isDev) {
1901
+ warnedFeatures.add(name);
1902
+ console.warn(`[tbw-grid] Feature "${name}" input is set but the feature is not registered.\n` +
1903
+ `Add this import to enable it:\n\n` +
1904
+ ` import '@toolbox-web/grid-angular/features/${toKebabCase(name)}';\n`);
1905
+ }
1906
+ return undefined;
1907
+ }
1908
+ return entry.factory(config);
1909
+ }
1910
+ /**
1911
+ * Convert camelCase to kebab-case for import paths.
1912
+ */
1913
+ function toKebabCase(str) {
1914
+ return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
1915
+ }
1916
+ /**
1917
+ * Clear the registry. For testing only.
1918
+ * @internal
1919
+ */
1920
+ function clearFeatureRegistry() {
1921
+ featureRegistry.clear();
1922
+ warnedFeatures.clear();
1923
+ }
1924
+
1925
+ /**
1926
+ * Base class for grid cell editors.
1927
+ *
1928
+ * Provides common functionality for Angular cell editors:
1929
+ * - Automatic value resolution from FormControl or value input
1930
+ * - Common inputs (value, row, column, control)
1931
+ * - Common outputs (commit, cancel)
1932
+ * - Validation state helpers
1933
+ *
1934
+ * ## Usage
1935
+ *
1936
+ * ```typescript
1937
+ * import { Component } from '@angular/core';
1938
+ * import { BaseGridEditor } from '@toolbox-web/grid-angular';
1939
+ *
1940
+ * @Component({
1941
+ * selector: 'app-my-editor',
1942
+ * template: \`
1943
+ * <input
1944
+ * [value]="currentValue()"
1945
+ * [class.is-invalid]="isInvalid()"
1946
+ * (input)="commitValue($event.target.value)"
1947
+ * (keydown.escape)="cancelEdit()"
1948
+ * />
1949
+ * @if (hasErrors()) {
1950
+ * <div class="error">{{ firstErrorMessage() }}</div>
1951
+ * }
1952
+ * \`
1953
+ * })
1954
+ * export class MyEditorComponent extends BaseGridEditor<MyRow, string> {
1955
+ * // Override to customize error messages
1956
+ * protected override getErrorMessage(errorKey: string): string {
1957
+ * if (errorKey === 'required') return 'This field is required';
1958
+ * if (errorKey === 'minlength') return 'Too short';
1959
+ * return super.getErrorMessage(errorKey);
1960
+ * }
1961
+ * }
1962
+ * ```
1963
+ *
1964
+ * ## Template Syntax
1965
+ *
1966
+ * When using the base class, you only need to pass the control:
1967
+ *
1968
+ * ```html
1969
+ * <tbw-grid-column field="name">
1970
+ * <app-my-editor *tbwEditor="let _; control as control" [control]="control" />
1971
+ * </tbw-grid-column>
1972
+ * ```
1973
+ *
1974
+ * Or without FormArray binding (fallback to value):
1975
+ *
1976
+ * ```html
1977
+ * <tbw-grid-column field="name">
1978
+ * <app-my-editor *tbwEditor="let value" [value]="value" />
1979
+ * </tbw-grid-column>
1980
+ * ```
1981
+ *
1982
+ * @typeParam TRow - The row data type
1983
+ * @typeParam TValue - The cell value type
1984
+ */
1985
+ class BaseGridEditor {
1986
+ elementRef = inject(ElementRef);
1987
+ // ============================================================================
1988
+ // Inputs
1989
+ // ============================================================================
1990
+ /**
1991
+ * The cell value. Used when FormControl is not available.
1992
+ * When a FormControl is provided, value is derived from control.value instead.
1993
+ */
1994
+ value = input(...(ngDevMode ? [undefined, { debugName: "value" }] : []));
1995
+ /**
1996
+ * The full row data object.
1997
+ */
1998
+ row = input(...(ngDevMode ? [undefined, { debugName: "row" }] : []));
1999
+ /**
2000
+ * The column configuration.
2001
+ */
2002
+ column = input(...(ngDevMode ? [undefined, { debugName: "column" }] : []));
2003
+ /**
2004
+ * The FormControl for this cell, if the grid is bound to a FormArray.
2005
+ * When provided, the editor uses control.value instead of the value input.
2006
+ */
2007
+ control = input(...(ngDevMode ? [undefined, { debugName: "control" }] : []));
2008
+ // ============================================================================
2009
+ // Outputs
2010
+ // ============================================================================
2011
+ /**
2012
+ * Emits when the user commits a new value.
2013
+ */
2014
+ commit = output();
2015
+ /**
2016
+ * Emits when the user cancels editing.
2017
+ */
2018
+ cancel = output();
2019
+ // ============================================================================
2020
+ // Computed State
2021
+ // ============================================================================
2022
+ /**
2023
+ * The current value, derived from FormControl if available, otherwise from value input.
2024
+ * This is the recommended way to get the current value in your editor template.
2025
+ */
2026
+ currentValue = computed(() => {
2027
+ const ctrl = this.control();
2028
+ if (ctrl) {
2029
+ return ctrl.value;
2030
+ }
2031
+ return this.value();
2032
+ }, ...(ngDevMode ? [{ debugName: "currentValue" }] : []));
2033
+ /**
2034
+ * Whether the control is invalid (has validation errors).
2035
+ * Returns false if no FormControl is available.
2036
+ */
2037
+ isInvalid = computed(() => {
2038
+ return this.control()?.invalid ?? false;
2039
+ }, ...(ngDevMode ? [{ debugName: "isInvalid" }] : []));
2040
+ /**
2041
+ * Whether the control is dirty (has been modified).
2042
+ * Returns false if no FormControl is available.
2043
+ */
2044
+ isDirty = computed(() => {
2045
+ return this.control()?.dirty ?? false;
2046
+ }, ...(ngDevMode ? [{ debugName: "isDirty" }] : []));
2047
+ /**
2048
+ * Whether the control has been touched.
2049
+ * Returns false if no FormControl is available.
2050
+ */
2051
+ isTouched = computed(() => {
2052
+ return this.control()?.touched ?? false;
2053
+ }, ...(ngDevMode ? [{ debugName: "isTouched" }] : []));
2054
+ /**
2055
+ * Whether the control has any validation errors.
2056
+ */
2057
+ hasErrors = computed(() => {
2058
+ const ctrl = this.control();
2059
+ return ctrl?.errors != null && Object.keys(ctrl.errors).length > 0;
2060
+ }, ...(ngDevMode ? [{ debugName: "hasErrors" }] : []));
2061
+ /**
2062
+ * The first error message from the control's validation errors.
2063
+ * Returns an empty string if no errors.
2064
+ */
2065
+ firstErrorMessage = computed(() => {
2066
+ const ctrl = this.control();
2067
+ if (!ctrl?.errors)
2068
+ return '';
2069
+ const firstKey = Object.keys(ctrl.errors)[0];
2070
+ return this.getErrorMessage(firstKey, ctrl.errors[firstKey]);
2071
+ }, ...(ngDevMode ? [{ debugName: "firstErrorMessage" }] : []));
2072
+ /**
2073
+ * All error messages from the control's validation errors.
2074
+ */
2075
+ allErrorMessages = computed(() => {
2076
+ const ctrl = this.control();
2077
+ if (!ctrl?.errors)
2078
+ return [];
2079
+ return Object.entries(ctrl.errors).map(([key, value]) => this.getErrorMessage(key, value));
2080
+ }, ...(ngDevMode ? [{ debugName: "allErrorMessages" }] : []));
2081
+ // ============================================================================
2082
+ // Methods
2083
+ // ============================================================================
2084
+ /**
2085
+ * Commit a new value. Emits the commit output AND dispatches a DOM event.
2086
+ * The DOM event enables the grid's auto-wiring to catch the commit.
2087
+ * Call this when the user confirms their edit.
2088
+ */
2089
+ commitValue(newValue) {
2090
+ // Emit Angular output for template bindings
2091
+ this.commit.emit(newValue);
2092
+ // Dispatch DOM CustomEvent for grid's auto-wiring
2093
+ // This allows the adapter to catch commits without explicit (commit)="..." bindings
2094
+ this.elementRef.nativeElement.dispatchEvent(new CustomEvent('commit', { detail: newValue, bubbles: true }));
2095
+ }
2096
+ /**
2097
+ * Cancel editing. Emits the cancel output AND dispatches a DOM event.
2098
+ * Call this when the user cancels (e.g., presses Escape).
2099
+ */
2100
+ cancelEdit() {
2101
+ // Emit Angular output for template bindings
2102
+ this.cancel.emit();
2103
+ // Dispatch DOM CustomEvent for grid's auto-wiring
2104
+ this.elementRef.nativeElement.dispatchEvent(new CustomEvent('cancel', { bubbles: true }));
2105
+ }
2106
+ /**
2107
+ * Get a human-readable error message for a validation error.
2108
+ * Override this method to customize error messages for your editor.
2109
+ *
2110
+ * @param errorKey - The validation error key (e.g., 'required', 'minlength')
2111
+ * @param errorValue - The error value (e.g., { requiredLength: 5, actualLength: 3 })
2112
+ * @returns A human-readable error message
2113
+ */
2114
+ getErrorMessage(errorKey, errorValue) {
2115
+ switch (errorKey) {
2116
+ case 'required':
2117
+ return 'This field is required';
2118
+ case 'minlength': {
2119
+ const err = errorValue;
2120
+ return `Minimum length is ${err?.requiredLength ?? 'unknown'}`;
2121
+ }
2122
+ case 'maxlength': {
2123
+ const err = errorValue;
2124
+ return `Maximum length is ${err?.requiredLength ?? 'unknown'}`;
2125
+ }
2126
+ case 'min': {
2127
+ const err = errorValue;
2128
+ return `Minimum value is ${err?.min ?? 'unknown'}`;
2129
+ }
2130
+ case 'max': {
2131
+ const err = errorValue;
2132
+ return `Maximum value is ${err?.max ?? 'unknown'}`;
2133
+ }
2134
+ case 'email':
2135
+ return 'Invalid email address';
2136
+ case 'pattern':
2137
+ return 'Invalid format';
2138
+ default:
2139
+ return `Invalid value (${errorKey})`;
2140
+ }
2141
+ }
2142
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: BaseGridEditor, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2143
+ 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 });
2144
+ }
2145
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: BaseGridEditor, decorators: [{
2146
+ type: Directive
2147
+ }], 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"] }] } });
2148
+
2149
+ /**
2150
+ * Directive that automatically registers the Angular adapter with tbw-grid elements.
2151
+ *
2152
+ * This directive eliminates the need to manually register the adapter in your component
2153
+ * constructor. Simply import this directive and it will handle adapter registration.
2154
+ *
2155
+ * ## Usage
2156
+ *
2157
+ * ```typescript
2158
+ * import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
2159
+ * import { Grid } from '@toolbox-web/grid-angular';
2160
+ *
2161
+ * @Component({
2162
+ * selector: 'app-root',
2163
+ * imports: [Grid],
2164
+ * schemas: [CUSTOM_ELEMENTS_SCHEMA],
2165
+ * template: `
2166
+ * <tbw-grid [rows]="rows" [gridConfig]="config" [customStyles]="myStyles">
2167
+ * <!-- column templates -->
2168
+ * </tbw-grid>
2169
+ * `
2170
+ * })
2171
+ * export class AppComponent {
2172
+ * rows = [...];
2173
+ * config = {...};
2174
+ * myStyles = `.my-class { color: red; }`;
2175
+ * }
2176
+ * ```
2177
+ *
2178
+ * The directive automatically:
2179
+ * - Creates an AngularGridAdapter instance
2180
+ * - Registers it with the GridElement
2181
+ * - Injects custom styles into the grid
2182
+ * - Handles cleanup on destruction
2183
+ */
2184
+ class Grid {
2185
+ elementRef = inject((ElementRef));
2186
+ injector = inject(EnvironmentInjector);
2187
+ appRef = inject(ApplicationRef);
2188
+ viewContainerRef = inject(ViewContainerRef);
2189
+ adapter = null;
2190
+ constructor() {
2191
+ // Effect to process angularConfig and apply to grid
2192
+ // This merges feature input plugins with the user's config plugins
2193
+ effect(() => {
2194
+ const config = this.angularConfig();
2195
+ if (!this.adapter)
2196
+ return;
2197
+ // Process the config to convert component classes to actual renderer/editor functions
2198
+ const processedConfig = config ? this.adapter.processGridConfig(config) : {};
2199
+ // Create plugins from feature inputs and merge with config plugins
2200
+ const featurePlugins = this.createFeaturePlugins();
2201
+ const configPlugins = processedConfig.plugins || [];
2202
+ // Merge: feature plugins first, then config plugins
2203
+ const mergedPlugins = [...featurePlugins, ...configPlugins];
2204
+ // Build core config overrides from individual inputs
2205
+ const sortableValue = this.sortable();
2206
+ const filterableValue = this.filterable();
2207
+ const selectableValue = this.selectable();
2208
+ const coreConfigOverrides = {};
2209
+ if (sortableValue !== undefined) {
2210
+ coreConfigOverrides['sortable'] = sortableValue;
2211
+ }
2212
+ if (filterableValue !== undefined) {
2213
+ coreConfigOverrides['filterable'] = filterableValue;
2214
+ }
2215
+ if (selectableValue !== undefined) {
2216
+ coreConfigOverrides['selectable'] = selectableValue;
2217
+ }
2218
+ // Apply to the grid element
2219
+ const grid = this.elementRef.nativeElement;
2220
+ grid.gridConfig = {
2221
+ ...processedConfig,
2222
+ ...coreConfigOverrides,
2223
+ plugins: mergedPlugins.length > 0 ? mergedPlugins : undefined,
2224
+ };
2225
+ });
2226
+ }
2227
+ /**
2228
+ * Custom CSS styles to inject into the grid.
2229
+ * Use this to style custom cell renderers, editors, or detail panels.
2230
+ *
2231
+ * @example
2232
+ * ```typescript
2233
+ * // In your component
2234
+ * customStyles = `
2235
+ * .my-detail-panel { padding: 16px; }
2236
+ * .my-status-badge { border-radius: 4px; }
2237
+ * `;
2238
+ * ```
2239
+ *
2240
+ * ```html
2241
+ * <tbw-grid [customStyles]="customStyles">...</tbw-grid>
2242
+ * ```
2243
+ */
2244
+ customStyles = input(...(ngDevMode ? [undefined, { debugName: "customStyles" }] : []));
2245
+ /**
2246
+ * Grid-wide sorting toggle.
2247
+ * When false, disables sorting for all columns regardless of their individual `sortable` setting.
2248
+ * When true (default), columns with `sortable: true` can be sorted.
2249
+ *
2250
+ * This is a core grid config property, not a plugin feature.
2251
+ * For multi-column sorting, also add the `[multiSort]` feature.
2252
+ *
2253
+ * @default true
2254
+ *
2255
+ * @example
2256
+ * ```html
2257
+ * <!-- Disable all sorting -->
2258
+ * <tbw-grid [sortable]="false" />
2259
+ *
2260
+ * <!-- Enable sorting (default) - columns still need sortable: true -->
2261
+ * <tbw-grid [sortable]="true" />
2262
+ *
2263
+ * <!-- Enable multi-column sorting -->
2264
+ * <tbw-grid [sortable]="true" [multiSort]="true" />
2265
+ * ```
2266
+ */
2267
+ sortable = input(...(ngDevMode ? [undefined, { debugName: "sortable" }] : []));
2268
+ /**
2269
+ * Grid-wide filtering toggle.
2270
+ * When false, disables filtering for all columns regardless of their individual `filterable` setting.
2271
+ * When true (default), columns with `filterable: true` can be filtered.
2272
+ *
2273
+ * Requires the FilteringPlugin to be loaded.
2274
+ *
2275
+ * @default true
2276
+ *
2277
+ * @example
2278
+ * ```html
2279
+ * <!-- Disable all filtering -->
2280
+ * <tbw-grid [filterable]="false" [filtering]="true" />
2281
+ *
2282
+ * <!-- Enable filtering (default) -->
2283
+ * <tbw-grid [filterable]="true" [filtering]="true" />
2284
+ * ```
2285
+ */
2286
+ filterable = input(...(ngDevMode ? [undefined, { debugName: "filterable" }] : []));
2287
+ /**
2288
+ * Grid-wide selection toggle.
2289
+ * When false, disables selection for all rows/cells.
2290
+ * When true (default), selection is enabled based on plugin mode.
2291
+ *
2292
+ * Requires the SelectionPlugin to be loaded.
2293
+ *
2294
+ * @default true
2295
+ *
2296
+ * @example
2297
+ * ```html
2298
+ * <!-- Disable all selection -->
2299
+ * <tbw-grid [selectable]="false" [selection]="'range'" />
2300
+ *
2301
+ * <!-- Enable selection (default) -->
2302
+ * <tbw-grid [selectable]="true" [selection]="'range'" />
2303
+ * ```
2304
+ */
2305
+ selectable = input(...(ngDevMode ? [undefined, { debugName: "selectable" }] : []));
2306
+ /**
2307
+ * Angular-specific grid configuration that supports component classes for renderers/editors.
2308
+ *
2309
+ * Use this input when you want to specify Angular component classes directly in column configs.
2310
+ * Components must implement the appropriate interfaces:
2311
+ * - Renderers: `AngularCellRenderer<TRow, TValue>` - requires `value()` and `row()` signal inputs
2312
+ * - Editors: `AngularCellEditor<TRow, TValue>` - adds `commit` and `cancel` outputs
2313
+ *
2314
+ * The directive automatically processes component classes and converts them to grid-compatible
2315
+ * renderer/editor functions before applying to the grid.
2316
+ *
2317
+ * @example
2318
+ * ```typescript
2319
+ * // Component that implements AngularCellEditor
2320
+ * @Component({...})
2321
+ * export class BonusEditorComponent implements AngularCellEditor<Employee, number> {
2322
+ * value = input.required<number>();
2323
+ * row = input.required<Employee>();
2324
+ * commit = output<number>();
2325
+ * cancel = output<void>();
2326
+ * }
2327
+ *
2328
+ * // In your grid config
2329
+ * config: AngularGridConfig<Employee> = {
2330
+ * columns: [
2331
+ * { field: 'name', header: 'Name' },
2332
+ * { field: 'bonus', header: 'Bonus', editable: true, editor: BonusEditorComponent }
2333
+ * ]
2334
+ * };
2335
+ * ```
2336
+ *
2337
+ * ```html
2338
+ * <tbw-grid [angularConfig]="config" [rows]="employees"></tbw-grid>
2339
+ * ```
2340
+ */
2341
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2342
+ angularConfig = input(...(ngDevMode ? [undefined, { debugName: "angularConfig" }] : []));
2343
+ // ═══════════════════════════════════════════════════════════════════════════
2344
+ // FEATURE INPUTS - Declarative plugin configuration
2345
+ // ═══════════════════════════════════════════════════════════════════════════
2346
+ /**
2347
+ * Enable cell/row/range selection.
2348
+ *
2349
+ * **Requires feature import:**
2350
+ * ```typescript
2351
+ * import '@toolbox-web/grid-angular/features/selection';
2352
+ * ```
2353
+ *
2354
+ * @example
2355
+ * ```html
2356
+ * <!-- Shorthand - just the mode -->
2357
+ * <tbw-grid [selection]="'range'" />
2358
+ *
2359
+ * <!-- Full config object -->
2360
+ * <tbw-grid [selection]="{ mode: 'range', checkbox: true }" />
2361
+ * ```
2362
+ */
2363
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2364
+ selection = input(...(ngDevMode ? [undefined, { debugName: "selection" }] : []));
2365
+ /**
2366
+ * Enable inline cell editing.
2367
+ *
2368
+ * **Requires feature import:**
2369
+ * ```typescript
2370
+ * import '@toolbox-web/grid-angular/features/editing';
2371
+ * ```
2372
+ *
2373
+ * @example
2374
+ * ```html
2375
+ * <!-- Enable with default trigger (dblclick) -->
2376
+ * <tbw-grid [editing]="true" />
2377
+ *
2378
+ * <!-- Specify trigger -->
2379
+ * <tbw-grid [editing]="'click'" />
2380
+ * <tbw-grid [editing]="'dblclick'" />
2381
+ * <tbw-grid [editing]="'manual'" />
2382
+ * ```
2383
+ */
2384
+ editing = input(...(ngDevMode ? [undefined, { debugName: "editing" }] : []));
2385
+ /**
2386
+ * Enable clipboard copy/paste. Requires selection to be enabled.
2387
+ *
2388
+ * **Requires feature import:**
2389
+ * ```typescript
2390
+ * import '@toolbox-web/grid-angular/features/clipboard';
2391
+ * ```
2392
+ *
2393
+ * @example
2394
+ * ```html
2395
+ * <tbw-grid [selection]="'range'" [clipboard]="true" />
2396
+ * ```
2397
+ */
2398
+ clipboard = input(...(ngDevMode ? [undefined, { debugName: "clipboard" }] : []));
2399
+ /**
2400
+ * Enable right-click context menu.
2401
+ *
2402
+ * **Requires feature import:**
2403
+ * ```typescript
2404
+ * import '@toolbox-web/grid-angular/features/context-menu';
2405
+ * ```
2406
+ *
2407
+ * @example
2408
+ * ```html
2409
+ * <tbw-grid [contextMenu]="true" />
2410
+ * ```
2411
+ */
2412
+ contextMenu = input(...(ngDevMode ? [undefined, { debugName: "contextMenu" }] : []));
2413
+ /**
2414
+ * Enable multi-column sorting.
2415
+ *
2416
+ * Multi-sort allows users to sort by multiple columns simultaneously.
2417
+ * For basic single-column sorting, columns with `sortable: true` work without this plugin.
2418
+ *
2419
+ * **Requires feature import:**
2420
+ * ```typescript
2421
+ * import '@toolbox-web/grid-angular/features/multi-sort';
2422
+ * ```
2423
+ *
2424
+ * @example
2425
+ * ```html
2426
+ * <!-- Enable multi-column sorting -->
2427
+ * <tbw-grid [multiSort]="true" />
2428
+ *
2429
+ * <!-- Limit to single column (uses plugin but restricts to 1 column) -->
2430
+ * <tbw-grid [multiSort]="'single'" />
2431
+ *
2432
+ * <!-- Full config -->
2433
+ * <tbw-grid [multiSort]="{ maxSortColumns: 3 }" />
2434
+ * ```
2435
+ */
2436
+ multiSort = input(...(ngDevMode ? [undefined, { debugName: "multiSort" }] : []));
2437
+ /**
2438
+ * @deprecated Use `[multiSort]` instead. Will be removed in a future version.
2439
+ *
2440
+ * Enable column sorting. This is an alias for `[multiSort]`.
2441
+ *
2442
+ * **Requires feature import:**
2443
+ * ```typescript
2444
+ * import '@toolbox-web/grid-angular/features/multi-sort';
2445
+ * ```
2446
+ */
2447
+ sorting = input(...(ngDevMode ? [undefined, { debugName: "sorting" }] : []));
2448
+ /**
2449
+ * Enable column filtering.
2450
+ *
2451
+ * **Requires feature import:**
2452
+ * ```typescript
2453
+ * import '@toolbox-web/grid-angular/features/filtering';
2454
+ * ```
2455
+ *
2456
+ * @example
2457
+ * ```html
2458
+ * <tbw-grid [filtering]="true" />
2459
+ * <tbw-grid [filtering]="{ debounceMs: 200 }" />
2460
+ * ```
2461
+ */
2462
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2463
+ filtering = input(...(ngDevMode ? [undefined, { debugName: "filtering" }] : []));
2464
+ /**
2465
+ * Enable column drag-to-reorder.
2466
+ *
2467
+ * **Requires feature import:**
2468
+ * ```typescript
2469
+ * import '@toolbox-web/grid-angular/features/reorder';
2470
+ * ```
2471
+ *
2472
+ * @example
2473
+ * ```html
2474
+ * <tbw-grid [reorder]="true" />
2475
+ * ```
2476
+ */
2477
+ reorder = input(...(ngDevMode ? [undefined, { debugName: "reorder" }] : []));
2478
+ /**
2479
+ * Enable column visibility toggle panel.
2480
+ *
2481
+ * **Requires feature import:**
2482
+ * ```typescript
2483
+ * import '@toolbox-web/grid-angular/features/visibility';
2484
+ * ```
2485
+ *
2486
+ * @example
2487
+ * ```html
2488
+ * <tbw-grid [visibility]="true" />
2489
+ * ```
2490
+ */
2491
+ visibility = input(...(ngDevMode ? [undefined, { debugName: "visibility" }] : []));
2492
+ /**
2493
+ * Enable pinned/sticky columns.
2494
+ * Columns are pinned via the `sticky` column property.
2495
+ *
2496
+ * **Requires feature import:**
2497
+ * ```typescript
2498
+ * import '@toolbox-web/grid-angular/features/pinned-columns';
2499
+ * ```
2500
+ *
2501
+ * @example
2502
+ * ```html
2503
+ * <tbw-grid [pinnedColumns]="true" [columns]="[
2504
+ * { field: 'id', sticky: 'left' },
2505
+ * { field: 'name' },
2506
+ * { field: 'actions', sticky: 'right' }
2507
+ * ]" />
2508
+ * ```
2509
+ */
2510
+ pinnedColumns = input(...(ngDevMode ? [undefined, { debugName: "pinnedColumns" }] : []));
2511
+ /**
2512
+ * Enable multi-level column headers (column groups).
2513
+ *
2514
+ * **Requires feature import:**
2515
+ * ```typescript
2516
+ * import '@toolbox-web/grid-angular/features/grouping-columns';
2517
+ * ```
2518
+ *
2519
+ * @example
2520
+ * ```html
2521
+ * <tbw-grid [groupingColumns]="{ columnGroups: [...] }" />
2522
+ * ```
2523
+ */
2524
+ groupingColumns = input(...(ngDevMode ? [undefined, { debugName: "groupingColumns" }] : []));
2525
+ /**
2526
+ * Enable horizontal column virtualization for wide grids.
2527
+ *
2528
+ * **Requires feature import:**
2529
+ * ```typescript
2530
+ * import '@toolbox-web/grid-angular/features/column-virtualization';
2531
+ * ```
2532
+ *
2533
+ * @example
2534
+ * ```html
2535
+ * <tbw-grid [columnVirtualization]="true" />
2536
+ * ```
2537
+ */
2538
+ columnVirtualization = input(...(ngDevMode ? [undefined, { debugName: "columnVirtualization" }] : []));
2539
+ /**
2540
+ * Enable row drag-to-reorder.
2541
+ *
2542
+ * **Requires feature import:**
2543
+ * ```typescript
2544
+ * import '@toolbox-web/grid-angular/features/row-reorder';
2545
+ * ```
2546
+ *
2547
+ * @example
2548
+ * ```html
2549
+ * <tbw-grid [rowReorder]="true" />
2550
+ * ```
2551
+ */
2552
+ rowReorder = input(...(ngDevMode ? [undefined, { debugName: "rowReorder" }] : []));
2553
+ /**
2554
+ * Enable row grouping by field values.
2555
+ *
2556
+ * **Requires feature import:**
2557
+ * ```typescript
2558
+ * import '@toolbox-web/grid-angular/features/grouping-rows';
2559
+ * ```
2560
+ *
2561
+ * @example
2562
+ * ```html
2563
+ * <tbw-grid [groupingRows]="{ groupBy: ['department'] }" />
2564
+ * ```
2565
+ */
2566
+ groupingRows = input(...(ngDevMode ? [undefined, { debugName: "groupingRows" }] : []));
2567
+ /**
2568
+ * Enable pinned rows (aggregation/status bar).
2569
+ *
2570
+ * **Requires feature import:**
2571
+ * ```typescript
2572
+ * import '@toolbox-web/grid-angular/features/pinned-rows';
2573
+ * ```
2574
+ *
2575
+ * @example
2576
+ * ```html
2577
+ * <tbw-grid [pinnedRows]="{ bottom: [{ type: 'aggregation' }] }" />
2578
+ * ```
2579
+ */
2580
+ pinnedRows = input(...(ngDevMode ? [undefined, { debugName: "pinnedRows" }] : []));
2581
+ /**
2582
+ * Enable hierarchical tree view.
2583
+ *
2584
+ * **Requires feature import:**
2585
+ * ```typescript
2586
+ * import '@toolbox-web/grid-angular/features/tree';
2587
+ * ```
2588
+ *
2589
+ * @example
2590
+ * ```html
2591
+ * <tbw-grid [tree]="{ childrenField: 'children' }" />
2592
+ * ```
2593
+ */
2594
+ tree = input(...(ngDevMode ? [undefined, { debugName: "tree" }] : []));
2595
+ /**
2596
+ * Enable master-detail expandable rows.
2597
+ *
2598
+ * **Requires feature import:**
2599
+ * ```typescript
2600
+ * import '@toolbox-web/grid-angular/features/master-detail';
2601
+ * ```
2602
+ *
2603
+ * @example
2604
+ * ```html
2605
+ * <tbw-grid [masterDetail]="{ detailRenderer: detailFn }" />
2606
+ * ```
2607
+ */
2608
+ masterDetail = input(...(ngDevMode ? [undefined, { debugName: "masterDetail" }] : []));
2609
+ /**
2610
+ * Enable responsive card layout for narrow viewports.
2611
+ *
2612
+ * **Requires feature import:**
2613
+ * ```typescript
2614
+ * import '@toolbox-web/grid-angular/features/responsive';
2615
+ * ```
2616
+ *
2617
+ * @example
2618
+ * ```html
2619
+ * <tbw-grid [responsive]="{ breakpoint: 768 }" />
2620
+ * ```
2621
+ */
2622
+ responsive = input(...(ngDevMode ? [undefined, { debugName: "responsive" }] : []));
2623
+ /**
2624
+ * Enable undo/redo for cell edits. Requires editing to be enabled.
2625
+ *
2626
+ * **Requires feature import:**
2627
+ * ```typescript
2628
+ * import '@toolbox-web/grid-angular/features/undo-redo';
2629
+ * ```
2630
+ *
2631
+ * @example
2632
+ * ```html
2633
+ * <tbw-grid [editing]="'dblclick'" [undoRedo]="true" />
2634
+ * ```
2635
+ */
2636
+ undoRedo = input(...(ngDevMode ? [undefined, { debugName: "undoRedo" }] : []));
2637
+ /**
2638
+ * Enable CSV/JSON export functionality.
2639
+ *
2640
+ * **Requires feature import:**
2641
+ * ```typescript
2642
+ * import '@toolbox-web/grid-angular/features/export';
2643
+ * ```
2644
+ *
2645
+ * @example
2646
+ * ```html
2647
+ * <tbw-grid [export]="true" />
2648
+ * <tbw-grid [export]="{ filename: 'data.csv' }" />
2649
+ * ```
2650
+ */
2651
+ exportFeature = input(...(ngDevMode ? [undefined, { debugName: "exportFeature" }] : []));
2652
+ /**
2653
+ * Enable print functionality.
2654
+ *
2655
+ * **Requires feature import:**
2656
+ * ```typescript
2657
+ * import '@toolbox-web/grid-angular/features/print';
2658
+ * ```
2659
+ *
2660
+ * @example
2661
+ * ```html
2662
+ * <tbw-grid [print]="true" />
2663
+ * ```
2664
+ */
2665
+ print = input(...(ngDevMode ? [undefined, { debugName: "print" }] : []));
2666
+ /**
2667
+ * Enable pivot table functionality.
2668
+ *
2669
+ * **Requires feature import:**
2670
+ * ```typescript
2671
+ * import '@toolbox-web/grid-angular/features/pivot';
2672
+ * ```
2673
+ *
2674
+ * @example
2675
+ * ```html
2676
+ * <tbw-grid [pivot]="{ rowFields: ['category'], valueField: 'sales' }" />
2677
+ * ```
2678
+ */
2679
+ pivot = input(...(ngDevMode ? [undefined, { debugName: "pivot" }] : []));
2680
+ /**
2681
+ * Enable server-side data operations.
2682
+ *
2683
+ * **Requires feature import:**
2684
+ * ```typescript
2685
+ * import '@toolbox-web/grid-angular/features/server-side';
2686
+ * ```
2687
+ *
2688
+ * @example
2689
+ * ```html
2690
+ * <tbw-grid [serverSide]="{ dataSource: fetchDataFn }" />
2691
+ * ```
2692
+ */
2693
+ serverSide = input(...(ngDevMode ? [undefined, { debugName: "serverSide" }] : []));
2694
+ // ═══════════════════════════════════════════════════════════════════════════
2695
+ // EVENT OUTPUTS - All grid events
2696
+ // ═══════════════════════════════════════════════════════════════════════════
2697
+ /**
2698
+ * Emitted when a cell is clicked.
2699
+ *
2700
+ * @example
2701
+ * ```html
2702
+ * <tbw-grid (cellClick)="onCellClick($event)">...</tbw-grid>
2703
+ * ```
2704
+ */
2705
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2706
+ cellClick = output();
2707
+ /**
2708
+ * Emitted when a row is clicked.
2709
+ *
2710
+ * @example
2711
+ * ```html
2712
+ * <tbw-grid (rowClick)="onRowClick($event)">...</tbw-grid>
2713
+ * ```
2714
+ */
2715
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2716
+ rowClick = output();
2717
+ /**
2718
+ * Emitted when a cell is activated (Enter key or double-click).
2719
+ *
2720
+ * @example
2721
+ * ```html
2722
+ * <tbw-grid (cellActivate)="onCellActivate($event)">...</tbw-grid>
2723
+ * ```
2724
+ */
2725
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2726
+ cellActivate = output();
2727
+ /**
2728
+ * Emitted when a cell value changes (before commit).
2729
+ *
2730
+ * @example
2731
+ * ```html
2732
+ * <tbw-grid (cellChange)="onCellChange($event)">...</tbw-grid>
2733
+ * ```
2734
+ */
2735
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2736
+ cellChange = output();
2737
+ /**
2738
+ * Emitted when a cell value is committed (inline editing).
2739
+ * Provides the row, field, new value, and change tracking information.
2740
+ *
2741
+ * @example
2742
+ * ```html
2743
+ * <tbw-grid (cellCommit)="onCellCommit($event)">...</tbw-grid>
2744
+ * ```
2745
+ *
2746
+ * ```typescript
2747
+ * onCellCommit(event: CellCommitEvent) {
2748
+ * console.log(`Changed ${event.field} to ${event.value} in row ${event.rowIndex}`);
2749
+ * }
2750
+ * ```
2751
+ */
2752
+ cellCommit = output();
2753
+ /**
2754
+ * Emitted when a row's values are committed (bulk/row editing).
2755
+ * Provides the row data and change tracking information.
2756
+ *
2757
+ * @example
2758
+ * ```html
2759
+ * <tbw-grid (rowCommit)="onRowCommit($event)">...</tbw-grid>
2760
+ * ```
2761
+ */
2762
+ rowCommit = output();
2763
+ /**
2764
+ * Emitted when the changed rows are reset.
2765
+ *
2766
+ * @example
2767
+ * ```html
2768
+ * <tbw-grid (changedRowsReset)="onChangedRowsReset($event)">...</tbw-grid>
2769
+ * ```
2770
+ */
2771
+ changedRowsReset = output();
2772
+ /**
2773
+ * Emitted when sort state changes.
2774
+ *
2775
+ * @example
2776
+ * ```html
2777
+ * <tbw-grid (sortChange)="onSortChange($event)">...</tbw-grid>
2778
+ * ```
2779
+ */
2780
+ sortChange = output();
2781
+ /**
2782
+ * Emitted when filter values change.
2783
+ *
2784
+ * @example
2785
+ * ```html
2786
+ * <tbw-grid (filterChange)="onFilterChange($event)">...</tbw-grid>
2787
+ * ```
2788
+ */
2789
+ filterChange = output();
2790
+ /**
2791
+ * Emitted when a column is resized.
2792
+ *
2793
+ * @example
2794
+ * ```html
2795
+ * <tbw-grid (columnResize)="onColumnResize($event)">...</tbw-grid>
2796
+ * ```
2797
+ */
2798
+ columnResize = output();
2799
+ /**
2800
+ * Emitted when a column is moved via drag-and-drop.
2801
+ *
2802
+ * @example
2803
+ * ```html
2804
+ * <tbw-grid (columnMove)="onColumnMove($event)">...</tbw-grid>
2805
+ * ```
2806
+ */
2807
+ columnMove = output();
2808
+ /**
2809
+ * Emitted when column visibility changes.
2810
+ *
2811
+ * @example
2812
+ * ```html
2813
+ * <tbw-grid (columnVisibility)="onColumnVisibility($event)">...</tbw-grid>
2814
+ * ```
2815
+ */
2816
+ columnVisibility = output();
2817
+ /**
2818
+ * Emitted when column state changes (resize, reorder, visibility).
2819
+ *
2820
+ * @example
2821
+ * ```html
2822
+ * <tbw-grid (columnStateChange)="onColumnStateChange($event)">...</tbw-grid>
2823
+ * ```
2824
+ */
2825
+ columnStateChange = output();
2826
+ /**
2827
+ * Emitted when selection changes.
2828
+ *
2829
+ * @example
2830
+ * ```html
2831
+ * <tbw-grid (selectionChange)="onSelectionChange($event)">...</tbw-grid>
2832
+ * ```
2833
+ */
2834
+ selectionChange = output();
2835
+ /**
2836
+ * Emitted when a row is moved via drag-and-drop.
2837
+ *
2838
+ * @example
2839
+ * ```html
2840
+ * <tbw-grid (rowMove)="onRowMove($event)">...</tbw-grid>
2841
+ * ```
2842
+ */
2843
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2844
+ rowMove = output();
2845
+ /**
2846
+ * Emitted when a group is expanded or collapsed.
2847
+ *
2848
+ * @example
2849
+ * ```html
2850
+ * <tbw-grid (groupToggle)="onGroupToggle($event)">...</tbw-grid>
2851
+ * ```
2852
+ */
2853
+ groupToggle = output();
2854
+ /**
2855
+ * Emitted when a tree node is expanded.
2856
+ *
2857
+ * @example
2858
+ * ```html
2859
+ * <tbw-grid (treeExpand)="onTreeExpand($event)">...</tbw-grid>
2860
+ * ```
2861
+ */
2862
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2863
+ treeExpand = output();
2864
+ /**
2865
+ * Emitted when a detail panel is expanded or collapsed.
2866
+ *
2867
+ * @example
2868
+ * ```html
2869
+ * <tbw-grid (detailExpand)="onDetailExpand($event)">...</tbw-grid>
2870
+ * ```
2871
+ */
2872
+ detailExpand = output();
2873
+ /**
2874
+ * Emitted when responsive mode changes (table ↔ card).
2875
+ *
2876
+ * @example
2877
+ * ```html
2878
+ * <tbw-grid (responsiveChange)="onResponsiveChange($event)">...</tbw-grid>
2879
+ * ```
2880
+ */
2881
+ responsiveChange = output();
2882
+ /**
2883
+ * Emitted when cells are copied to clipboard.
2884
+ *
2885
+ * @example
2886
+ * ```html
2887
+ * <tbw-grid (copy)="onCopy($event)">...</tbw-grid>
2888
+ * ```
2889
+ */
2890
+ copy = output();
2891
+ /**
2892
+ * Emitted when cells are pasted from clipboard.
2893
+ *
2894
+ * @example
2895
+ * ```html
2896
+ * <tbw-grid (paste)="onPaste($event)">...</tbw-grid>
2897
+ * ```
2898
+ */
2899
+ paste = output();
2900
+ /**
2901
+ * Emitted when undo/redo is performed.
2902
+ *
2903
+ * @example
2904
+ * ```html
2905
+ * <tbw-grid (undoRedoAction)="onUndoRedo($event)">...</tbw-grid>
2906
+ * ```
2907
+ */
2908
+ undoRedoAction = output();
2909
+ /**
2910
+ * Emitted when export completes.
2911
+ *
2912
+ * @example
2913
+ * ```html
2914
+ * <tbw-grid (exportComplete)="onExportComplete($event)">...</tbw-grid>
2915
+ * ```
2916
+ */
2917
+ exportComplete = output();
2918
+ /**
2919
+ * Emitted when print starts.
2920
+ *
2921
+ * @example
2922
+ * ```html
2923
+ * <tbw-grid (printStart)="onPrintStart($event)">...</tbw-grid>
2924
+ * ```
2925
+ */
2926
+ printStart = output();
2927
+ /**
2928
+ * Emitted when print completes.
2929
+ *
2930
+ * @example
2931
+ * ```html
2932
+ * <tbw-grid (printComplete)="onPrintComplete($event)">...</tbw-grid>
2933
+ * ```
2934
+ */
2935
+ printComplete = output();
2936
+ // Map of output names to event names for automatic wiring
2937
+ eventOutputMap = {
2938
+ cellClick: 'cell-click',
2939
+ rowClick: 'row-click',
2940
+ cellActivate: 'cell-activate',
2941
+ cellChange: 'cell-change',
2942
+ cellCommit: 'cell-commit',
2943
+ rowCommit: 'row-commit',
2944
+ changedRowsReset: 'changed-rows-reset',
2945
+ sortChange: 'sort-change',
2946
+ filterChange: 'filter-change',
2947
+ columnResize: 'column-resize',
2948
+ columnMove: 'column-move',
2949
+ columnVisibility: 'column-visibility',
2950
+ columnStateChange: 'column-state-change',
2951
+ selectionChange: 'selection-change',
2952
+ rowMove: 'row-move',
2953
+ groupToggle: 'group-toggle',
2954
+ treeExpand: 'tree-expand',
2955
+ detailExpand: 'detail-expand',
2956
+ responsiveChange: 'responsive-change',
2957
+ copy: 'copy',
2958
+ paste: 'paste',
2959
+ undoRedoAction: 'undo-redo',
2960
+ exportComplete: 'export-complete',
2961
+ printStart: 'print-start',
2962
+ printComplete: 'print-complete',
2963
+ };
2964
+ // Store event listeners for cleanup
2965
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2966
+ eventListeners = new Map();
2967
+ ngOnInit() {
2968
+ // Create and register the adapter
2969
+ this.adapter = new AngularGridAdapter(this.injector, this.appRef, this.viewContainerRef);
2970
+ DataGridElement.registerAdapter(this.adapter);
2971
+ const grid = this.elementRef.nativeElement;
2972
+ // Wire up all event listeners based on eventOutputMap
2973
+ this.setupEventListeners(grid);
2974
+ // Register adapter on the grid element so MasterDetailPlugin can use it
2975
+ // via the __frameworkAdapter hook during attach()
2976
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2977
+ grid.__frameworkAdapter = this.adapter;
2978
+ }
2979
+ /**
2980
+ * Sets up event listeners for all outputs using the eventOutputMap.
2981
+ */
2982
+ setupEventListeners(grid) {
2983
+ // Wire up all event listeners
2984
+ for (const [outputName, eventName] of Object.entries(this.eventOutputMap)) {
2985
+ const listener = (e) => {
2986
+ const detail = e.detail;
2987
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2988
+ this[outputName].emit(detail);
2989
+ };
2990
+ grid.addEventListener(eventName, listener);
2991
+ this.eventListeners.set(eventName, listener);
2992
+ }
2993
+ }
2994
+ /**
2995
+ * Creates plugins from feature inputs.
2996
+ * Uses the feature registry to allow tree-shaking - only imported features are bundled.
2997
+ * Returns the array of created plugins (doesn't modify grid).
2998
+ */
2999
+ createFeaturePlugins() {
3000
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3001
+ const plugins = [];
3002
+ // Helper to add plugin if feature is registered
3003
+ const addPlugin = (name, config) => {
3004
+ if (config === undefined || config === null || config === false)
3005
+ return;
3006
+ const plugin = createPluginFromFeature(name, config);
3007
+ if (plugin)
3008
+ plugins.push(plugin);
3009
+ };
3010
+ // Add plugins for each feature input
3011
+ addPlugin('selection', this.selection());
3012
+ addPlugin('editing', this.editing());
3013
+ addPlugin('clipboard', this.clipboard());
3014
+ addPlugin('contextMenu', this.contextMenu());
3015
+ // multiSort is the primary input; sorting is a deprecated alias
3016
+ addPlugin('multiSort', this.multiSort() ?? this.sorting());
3017
+ addPlugin('filtering', this.filtering());
3018
+ addPlugin('reorder', this.reorder());
3019
+ addPlugin('visibility', this.visibility());
3020
+ addPlugin('pinnedColumns', this.pinnedColumns());
3021
+ addPlugin('groupingColumns', this.groupingColumns());
3022
+ addPlugin('columnVirtualization', this.columnVirtualization());
3023
+ addPlugin('rowReorder', this.rowReorder());
3024
+ addPlugin('groupingRows', this.groupingRows());
3025
+ addPlugin('pinnedRows', this.pinnedRows());
3026
+ addPlugin('tree', this.tree());
3027
+ addPlugin('masterDetail', this.masterDetail());
3028
+ addPlugin('responsive', this.responsive());
3029
+ addPlugin('undoRedo', this.undoRedo());
3030
+ addPlugin('export', this.exportFeature());
3031
+ addPlugin('print', this.print());
3032
+ addPlugin('pivot', this.pivot());
3033
+ addPlugin('serverSide', this.serverSide());
3034
+ return plugins;
3035
+ }
3036
+ ngAfterContentInit() {
3037
+ // After Angular child directives have initialized (GridColumnView, GridColumnEditor, GridDetailView, GridToolPanel),
3038
+ // force the grid to re-parse light DOM columns so adapters can create renderers/editors
3039
+ const grid = this.elementRef.nativeElement;
3040
+ if (grid && typeof grid.refreshColumns === 'function') {
3041
+ // Use setTimeout to ensure Angular effects have run (template registration)
3042
+ setTimeout(() => {
3043
+ grid.refreshColumns();
3044
+ // Configure MasterDetailPlugin after Angular templates are registered
3045
+ this.configureMasterDetail(grid);
3046
+ // Configure ResponsivePlugin card renderer if template is present
3047
+ this.configureResponsiveCard(grid);
3048
+ // Refresh shell header to pick up tool panel templates
3049
+ // This allows Angular templates to be used in tool panels
3050
+ if (typeof grid.refreshShellHeader === 'function') {
3051
+ grid.refreshShellHeader();
3052
+ }
3053
+ // Register custom styles if provided
3054
+ this.registerCustomStyles(grid);
3055
+ }, 0);
3056
+ }
3057
+ }
3058
+ /**
3059
+ * Registers custom styles into the grid.
3060
+ * Uses the grid's registerStyles() API for clean encapsulation.
3061
+ */
3062
+ registerCustomStyles(grid) {
3063
+ const styles = this.customStyles();
3064
+ if (!styles)
3065
+ return;
3066
+ // Wait for grid to be ready before registering styles
3067
+ grid.ready?.().then(() => {
3068
+ grid.registerStyles?.('angular-custom-styles', styles);
3069
+ });
3070
+ }
3071
+ /**
3072
+ * Configures the MasterDetailPlugin after Angular templates are registered.
3073
+ * - If plugin exists: refresh its detail renderer
3074
+ * - If plugin doesn't exist but <tbw-grid-detail> is present: dynamically import and add the plugin
3075
+ */
3076
+ async configureMasterDetail(grid) {
3077
+ if (!this.adapter)
3078
+ return;
3079
+ // Check for existing plugin by name to avoid importing the class
3080
+ const existingPlugin = grid.gridConfig?.plugins?.find((p) => p.name === 'masterDetail');
3081
+ if (existingPlugin && typeof existingPlugin.refreshDetailRenderer === 'function') {
3082
+ // Plugin exists - just refresh the renderer to pick up Angular templates
3083
+ existingPlugin.refreshDetailRenderer();
3084
+ return;
3085
+ }
3086
+ // Check if <tbw-grid-detail> is present in light DOM
3087
+ const detailElement = grid.querySelector('tbw-grid-detail');
3088
+ if (!detailElement)
3089
+ return;
3090
+ // Create detail renderer from Angular template
3091
+ const detailRenderer = this.adapter.createDetailRenderer(grid);
3092
+ if (!detailRenderer)
3093
+ return;
3094
+ // Parse configuration from attributes
3095
+ const animationAttr = detailElement.getAttribute('animation');
3096
+ let animation = 'slide';
3097
+ if (animationAttr === 'false') {
3098
+ animation = false;
3099
+ }
3100
+ else if (animationAttr === 'fade') {
3101
+ animation = 'fade';
3102
+ }
3103
+ const showExpandColumn = detailElement.getAttribute('showExpandColumn') !== 'false';
3104
+ // Dynamically import the plugin to avoid bundling it when not used
3105
+ const { MasterDetailPlugin } = await import('@toolbox-web/grid/plugins/master-detail');
3106
+ // Create and add the plugin
3107
+ const plugin = new MasterDetailPlugin({
3108
+ detailRenderer: detailRenderer,
3109
+ showExpandColumn,
3110
+ animation,
3111
+ });
3112
+ const currentConfig = grid.gridConfig || {};
3113
+ const existingPlugins = currentConfig.plugins || [];
3114
+ grid.gridConfig = {
3115
+ ...currentConfig,
3116
+ plugins: [...existingPlugins, plugin],
3117
+ };
3118
+ }
3119
+ /**
3120
+ * Configures the ResponsivePlugin with Angular template-based card renderer.
3121
+ * - If plugin exists: updates its cardRenderer configuration
3122
+ * - If plugin doesn't exist but <tbw-grid-responsive-card> is present: logs a warning
3123
+ */
3124
+ configureResponsiveCard(grid) {
3125
+ if (!this.adapter)
3126
+ return;
3127
+ // Check if <tbw-grid-responsive-card> is present in light DOM
3128
+ const cardElement = grid.querySelector('tbw-grid-responsive-card');
3129
+ if (!cardElement)
3130
+ return;
3131
+ // Create card renderer from Angular template
3132
+ const cardRenderer = this.adapter.createResponsiveCardRenderer(grid);
3133
+ if (!cardRenderer)
3134
+ return;
3135
+ // Find existing plugin by name to avoid importing the class
3136
+ const existingPlugin = grid.gridConfig?.plugins?.find((p) => p.name === 'responsive');
3137
+ if (existingPlugin && typeof existingPlugin.setCardRenderer === 'function') {
3138
+ // Plugin exists - update its cardRenderer
3139
+ existingPlugin.setCardRenderer(cardRenderer);
3140
+ return;
3141
+ }
3142
+ // Plugin doesn't exist - log a warning
3143
+ console.warn('[tbw-grid-angular] <tbw-grid-responsive-card> found but ResponsivePlugin is not configured.\n' +
3144
+ 'Add ResponsivePlugin to your gridConfig.plugins array:\n\n' +
3145
+ ' import { ResponsivePlugin } from "@toolbox-web/grid/plugins/responsive";\n' +
3146
+ ' gridConfig = {\n' +
3147
+ ' plugins: [new ResponsivePlugin({ breakpoint: 600 })]\n' +
3148
+ ' };');
3149
+ }
3150
+ ngOnDestroy() {
3151
+ const grid = this.elementRef.nativeElement;
3152
+ // Cleanup all event listeners
3153
+ if (grid) {
3154
+ for (const [eventName, listener] of this.eventListeners) {
3155
+ grid.removeEventListener(eventName, listener);
3156
+ }
3157
+ this.eventListeners.clear();
3158
+ }
3159
+ // Cleanup custom styles
3160
+ if (grid && this.customStyles()) {
3161
+ grid.unregisterStyles?.('angular-custom-styles');
3162
+ }
3163
+ // Cleanup adapter if needed
3164
+ if (this.adapter) {
3165
+ this.adapter.destroy?.();
3166
+ this.adapter = null;
3167
+ }
3168
+ }
3169
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: Grid, deps: [], target: i0.ɵɵFactoryTarget.Directive });
3170
+ 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 }, 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 });
3171
+ }
3172
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: Grid, decorators: [{
3173
+ type: Directive,
3174
+ args: [{ selector: 'tbw-grid' }]
3175
+ }], 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 }] }], 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"] }] } });
3176
+
3177
+ /**
3178
+ * @packageDocumentation
3179
+ * @toolbox-web/grid-angular - Angular adapter for @toolbox-web/grid.
3180
+ *
3181
+ * Provides directives for seamless Angular integration with the grid component.
3182
+ */
3183
+
3184
+ /**
3185
+ * Generated bundle index. Do not edit.
3186
+ */
3187
+
3188
+ export { AngularGridAdapter, BaseGridEditor, GRID_TYPE_DEFAULTS, Grid, GridColumnEditor, GridColumnView, GridDetailView, GridFormArray, GridResponsiveCard, GridToolPanel, GridTypeRegistry, TbwEditor as TbwCellEditor, TbwRenderer as TbwCellView, TbwEditor, TbwRenderer, clearFeatureRegistry, createPluginFromFeature, getFeatureFactory, getFormArrayContext, getRegisteredFeatures, injectGrid, isComponentClass, isFeatureRegistered, provideGridTypeDefaults, registerFeature };
3189
+ //# sourceMappingURL=toolbox-web-grid-angular.mjs.map