@toolbox-web/grid-angular 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1575,6 +1575,10 @@ class GridAdapter {
1575
1575
  if (config.typeDefaults) {
1576
1576
  result.typeDefaults = this.processTypeDefaults(config.typeDefaults);
1577
1577
  }
1578
+ // Process loadingRenderer - convert Angular component class to function
1579
+ if (config.loadingRenderer && isComponentClass(config.loadingRenderer)) {
1580
+ result.loadingRenderer = this.createComponentLoadingRenderer(config.loadingRenderer);
1581
+ }
1578
1582
  return result;
1579
1583
  }
1580
1584
  /**
@@ -1621,6 +1625,14 @@ class GridAdapter {
1621
1625
  if (column.editor && isComponentClass(column.editor)) {
1622
1626
  processed.editor = this.createComponentEditor(column.editor);
1623
1627
  }
1628
+ // Convert headerRenderer component class to function
1629
+ if (column.headerRenderer && isComponentClass(column.headerRenderer)) {
1630
+ processed.headerRenderer = this.createComponentHeaderRenderer(column.headerRenderer);
1631
+ }
1632
+ // Convert headerLabelRenderer component class to function
1633
+ if (column.headerLabelRenderer && isComponentClass(column.headerLabelRenderer)) {
1634
+ processed.headerLabelRenderer = this.createComponentHeaderLabelRenderer(column.headerLabelRenderer);
1635
+ }
1624
1636
  return processed;
1625
1637
  }
1626
1638
  /**
@@ -2094,6 +2106,222 @@ class GridAdapter {
2094
2106
  return hostElement;
2095
2107
  };
2096
2108
  }
2109
+ /**
2110
+ * Creates a header renderer function from an Angular component class.
2111
+ * Mounts the component with full header context (column, value, sortState, etc.).
2112
+ * @internal
2113
+ */
2114
+ createComponentHeaderRenderer(componentClass) {
2115
+ return (ctx) => {
2116
+ const hostElement = document.createElement('span');
2117
+ hostElement.style.display = 'contents';
2118
+ const componentRef = createComponent(componentClass, {
2119
+ environmentInjector: this.injector,
2120
+ hostElement,
2121
+ });
2122
+ this.setComponentInputs(componentRef, {
2123
+ column: ctx.column,
2124
+ value: ctx.value,
2125
+ sortState: ctx.sortState,
2126
+ filterActive: ctx.filterActive,
2127
+ renderSortIcon: ctx.renderSortIcon,
2128
+ renderFilterButton: ctx.renderFilterButton,
2129
+ });
2130
+ this.appRef.attachView(componentRef.hostView);
2131
+ this.componentRefs.push(componentRef);
2132
+ componentRef.changeDetectorRef.detectChanges();
2133
+ return hostElement;
2134
+ };
2135
+ }
2136
+ /**
2137
+ * Creates a header label renderer function from an Angular component class.
2138
+ * Mounts the component with label context (column, value).
2139
+ * @internal
2140
+ */
2141
+ createComponentHeaderLabelRenderer(componentClass) {
2142
+ return (ctx) => {
2143
+ const hostElement = document.createElement('span');
2144
+ hostElement.style.display = 'contents';
2145
+ const componentRef = createComponent(componentClass, {
2146
+ environmentInjector: this.injector,
2147
+ hostElement,
2148
+ });
2149
+ this.setComponentInputs(componentRef, {
2150
+ column: ctx.column,
2151
+ value: ctx.value,
2152
+ });
2153
+ this.appRef.attachView(componentRef.hostView);
2154
+ this.componentRefs.push(componentRef);
2155
+ componentRef.changeDetectorRef.detectChanges();
2156
+ return hostElement;
2157
+ };
2158
+ }
2159
+ /**
2160
+ * Creates a group header renderer function from an Angular component class.
2161
+ *
2162
+ * The component should accept group header inputs (id, label, columns, firstIndex, isImplicit).
2163
+ * Returns the host element directly (groupHeaderRenderer returns an element, not void).
2164
+ * @internal
2165
+ */
2166
+ createComponentGroupHeaderRenderer(componentClass) {
2167
+ return (params) => {
2168
+ const hostElement = document.createElement('span');
2169
+ hostElement.style.display = 'contents';
2170
+ const componentRef = createComponent(componentClass, {
2171
+ environmentInjector: this.injector,
2172
+ hostElement,
2173
+ });
2174
+ this.setComponentInputs(componentRef, {
2175
+ id: params.id,
2176
+ label: params.label,
2177
+ columns: params.columns,
2178
+ firstIndex: params.firstIndex,
2179
+ isImplicit: params.isImplicit,
2180
+ });
2181
+ this.appRef.attachView(componentRef.hostView);
2182
+ this.componentRefs.push(componentRef);
2183
+ componentRef.changeDetectorRef.detectChanges();
2184
+ return hostElement;
2185
+ };
2186
+ }
2187
+ /**
2188
+ * Processes a GroupingColumnsConfig, converting component class references
2189
+ * to actual renderer functions.
2190
+ *
2191
+ * @param config - Angular grouping columns configuration with possible component class references
2192
+ * @returns Processed GroupingColumnsConfig with actual renderer functions
2193
+ */
2194
+ processGroupingColumnsConfig(config) {
2195
+ if (config.groupHeaderRenderer && isComponentClass(config.groupHeaderRenderer)) {
2196
+ return {
2197
+ ...config,
2198
+ groupHeaderRenderer: this.createComponentGroupHeaderRenderer(config.groupHeaderRenderer),
2199
+ };
2200
+ }
2201
+ return config;
2202
+ }
2203
+ /**
2204
+ * Processes a GroupingRowsConfig, converting component class references
2205
+ * to actual renderer functions.
2206
+ *
2207
+ * @param config - Angular grouping rows configuration with possible component class references
2208
+ * @returns Processed GroupingRowsConfig with actual renderer functions
2209
+ */
2210
+ processGroupingRowsConfig(config) {
2211
+ if (config.groupRowRenderer && isComponentClass(config.groupRowRenderer)) {
2212
+ return {
2213
+ ...config,
2214
+ groupRowRenderer: this.createComponentGroupRowRenderer(config.groupRowRenderer),
2215
+ };
2216
+ }
2217
+ return config;
2218
+ }
2219
+ /**
2220
+ * Processes a PinnedRowsConfig, converting component class references
2221
+ * in `customPanels[].render` to actual renderer functions.
2222
+ *
2223
+ * @param config - Angular pinned rows configuration with possible component class references
2224
+ * @returns Processed PinnedRowsConfig with actual renderer functions
2225
+ */
2226
+ processPinnedRowsConfig(config) {
2227
+ if (!Array.isArray(config.customPanels))
2228
+ return config;
2229
+ const hasComponentRender = config.customPanels.some((panel) => isComponentClass(panel.render));
2230
+ if (!hasComponentRender)
2231
+ return config;
2232
+ return {
2233
+ ...config,
2234
+ customPanels: config.customPanels.map((panel) => {
2235
+ if (!isComponentClass(panel.render))
2236
+ return panel;
2237
+ return {
2238
+ ...panel,
2239
+ render: this.createComponentPinnedRowsPanelRenderer(panel.render),
2240
+ };
2241
+ }),
2242
+ };
2243
+ }
2244
+ /**
2245
+ * Creates a pinned rows panel renderer function from an Angular component class.
2246
+ *
2247
+ * The component should accept inputs from PinnedRowsContext (totalRows, filteredRows,
2248
+ * selectedRows, columns, rows, grid).
2249
+ * @internal
2250
+ */
2251
+ createComponentPinnedRowsPanelRenderer(componentClass) {
2252
+ return (ctx) => {
2253
+ const hostElement = document.createElement('span');
2254
+ hostElement.style.display = 'contents';
2255
+ const componentRef = createComponent(componentClass, {
2256
+ environmentInjector: this.injector,
2257
+ hostElement,
2258
+ });
2259
+ this.setComponentInputs(componentRef, {
2260
+ totalRows: ctx.totalRows,
2261
+ filteredRows: ctx.filteredRows,
2262
+ selectedRows: ctx.selectedRows,
2263
+ columns: ctx.columns,
2264
+ rows: ctx.rows,
2265
+ grid: ctx.grid,
2266
+ });
2267
+ this.appRef.attachView(componentRef.hostView);
2268
+ this.componentRefs.push(componentRef);
2269
+ componentRef.changeDetectorRef.detectChanges();
2270
+ return hostElement;
2271
+ };
2272
+ }
2273
+ /**
2274
+ * Creates a loading renderer function from an Angular component class.
2275
+ *
2276
+ * The component should accept a `size` input ('large' | 'small').
2277
+ * @internal
2278
+ */
2279
+ createComponentLoadingRenderer(componentClass) {
2280
+ return (ctx) => {
2281
+ const hostElement = document.createElement('span');
2282
+ hostElement.style.display = 'contents';
2283
+ const componentRef = createComponent(componentClass, {
2284
+ environmentInjector: this.injector,
2285
+ hostElement,
2286
+ });
2287
+ this.setComponentInputs(componentRef, {
2288
+ size: ctx.size,
2289
+ });
2290
+ this.appRef.attachView(componentRef.hostView);
2291
+ this.componentRefs.push(componentRef);
2292
+ componentRef.changeDetectorRef.detectChanges();
2293
+ return hostElement;
2294
+ };
2295
+ }
2296
+ /**
2297
+ * Creates a group row renderer function from an Angular component class.
2298
+ *
2299
+ * The component should accept group row inputs (key, value, depth, rows, expanded, toggleExpand).
2300
+ * Returns the host element directly (groupRowRenderer returns an element, not void).
2301
+ * @internal
2302
+ */
2303
+ createComponentGroupRowRenderer(componentClass) {
2304
+ return (params) => {
2305
+ const hostElement = document.createElement('span');
2306
+ hostElement.style.display = 'contents';
2307
+ const componentRef = createComponent(componentClass, {
2308
+ environmentInjector: this.injector,
2309
+ hostElement,
2310
+ });
2311
+ this.setComponentInputs(componentRef, {
2312
+ key: params.key,
2313
+ value: params.value,
2314
+ depth: params.depth,
2315
+ rows: params.rows,
2316
+ expanded: params.expanded,
2317
+ toggleExpand: params.toggleExpand,
2318
+ });
2319
+ this.appRef.attachView(componentRef.hostView);
2320
+ this.componentRefs.push(componentRef);
2321
+ componentRef.changeDetectorRef.detectChanges();
2322
+ return hostElement;
2323
+ };
2324
+ }
2097
2325
  /**
2098
2326
  * Creates a filter panel renderer function from an Angular component class.
2099
2327
  *
@@ -2580,7 +2808,7 @@ function injectGrid() {
2580
2808
  * ## Usage
2581
2809
  *
2582
2810
  * ```typescript
2583
- * import { Component } from '@angular/core';
2811
+ * import { Component, viewChild, ElementRef } from '@angular/core';
2584
2812
  * import { BaseFilterPanel } from '@toolbox-web/grid-angular';
2585
2813
  *
2586
2814
  * @Component({
@@ -2592,10 +2820,10 @@ function injectGrid() {
2592
2820
  * `
2593
2821
  * })
2594
2822
  * export class TextFilterComponent extends BaseFilterPanel {
2595
- * @ViewChild('input') input!: ElementRef<HTMLInputElement>;
2823
+ * input = viewChild.required<ElementRef<HTMLInputElement>>('input');
2596
2824
  *
2597
2825
  * applyFilter(): void {
2598
- * this.params().applyTextFilter('contains', this.input.nativeElement.value);
2826
+ * this.params().applyTextFilter('contains', this.input().nativeElement.value);
2599
2827
  * }
2600
2828
  * }
2601
2829
  * ```
@@ -3188,7 +3416,7 @@ let anchorCounter = 0;
3188
3416
  * ## Usage
3189
3417
  *
3190
3418
  * ```typescript
3191
- * import { Component, ViewChild, ElementRef } from '@angular/core';
3419
+ * import { Component, viewChild, ElementRef, effect } from '@angular/core';
3192
3420
  * import { BaseOverlayEditor } from '@toolbox-web/grid-angular';
3193
3421
  *
3194
3422
  * @Component({
@@ -3211,18 +3439,22 @@ let anchorCounter = 0;
3211
3439
  * `
3212
3440
  * })
3213
3441
  * export class DateEditorComponent extends BaseOverlayEditor<MyRow, string> {
3214
- * @ViewChild('panel') panelRef!: ElementRef<HTMLElement>;
3215
- * @ViewChild('inlineInput') inputRef!: ElementRef<HTMLInputElement>;
3442
+ * panelRef = viewChild.required<ElementRef<HTMLElement>>('panel');
3443
+ * inputRef = viewChild.required<ElementRef<HTMLInputElement>>('inlineInput');
3216
3444
  *
3217
3445
  * protected override overlayPosition = 'below' as const;
3218
3446
  *
3219
- * ngAfterViewInit(): void {
3220
- * this.initOverlay(this.panelRef.nativeElement);
3221
- * if (this.isCellFocused()) this.showOverlay();
3447
+ * constructor() {
3448
+ * super();
3449
+ * effect(() => {
3450
+ * const panel = this.panelRef().nativeElement;
3451
+ * this.initOverlay(panel);
3452
+ * if (this.isCellFocused()) this.showOverlay();
3453
+ * });
3222
3454
  * }
3223
3455
  *
3224
3456
  * protected getInlineInput(): HTMLInputElement | null {
3225
- * return this.inputRef?.nativeElement ?? null;
3457
+ * return this.inputRef()?.nativeElement ?? null;
3226
3458
  * }
3227
3459
  *
3228
3460
  * protected onOverlayOutsideClick(): void {
@@ -3283,7 +3515,7 @@ class BaseOverlayEditor extends BaseGridEditor {
3283
3515
  /**
3284
3516
  * Initialise the overlay with the panel element.
3285
3517
  *
3286
- * Call this in `ngAfterViewInit` with your `@ViewChild` panel reference.
3518
+ * Call this in an `effect()` or `afterNextRender()` with your `viewChild` panel reference.
3287
3519
  * The panel is moved to `<body>` and hidden until {@link showOverlay} is called.
3288
3520
  *
3289
3521
  * @param panel - The overlay panel DOM element
@@ -4765,7 +4997,7 @@ class Grid {
4765
4997
  *
4766
4998
  * @example
4767
4999
  * ```html
4768
- * <tbw-grid [groupingColumns]="{ columnGroups: [...] }" />
5000
+ * <tbw-grid [groupingColumns]="true" />
4769
5001
  * ```
4770
5002
  */
4771
5003
  groupingColumns = input(...(ngDevMode ? [undefined, { debugName: "groupingColumns" }] : []));
@@ -5311,11 +5543,32 @@ class Grid {
5311
5543
  addPlugin('reorderColumns', this.reorderColumns() ?? this.reorder());
5312
5544
  addPlugin('visibility', this.visibility());
5313
5545
  addPlugin('pinnedColumns', this.pinnedColumns());
5314
- addPlugin('groupingColumns', this.groupingColumns());
5546
+ // Pre-process groupingColumns config to bridge Angular component classes
5547
+ const gcConfig = this.groupingColumns();
5548
+ if (gcConfig && typeof gcConfig === 'object' && this.adapter) {
5549
+ addPlugin('groupingColumns', this.adapter.processGroupingColumnsConfig(gcConfig));
5550
+ }
5551
+ else {
5552
+ addPlugin('groupingColumns', gcConfig);
5553
+ }
5315
5554
  addPlugin('columnVirtualization', this.columnVirtualization());
5316
5555
  addPlugin('reorderRows', this.reorderRows() ?? this.rowReorder());
5317
- addPlugin('groupingRows', this.groupingRows());
5318
- addPlugin('pinnedRows', this.pinnedRows());
5556
+ // Pre-process groupingRows config to bridge Angular component classes
5557
+ const grConfig = this.groupingRows();
5558
+ if (grConfig && typeof grConfig === 'object' && this.adapter) {
5559
+ addPlugin('groupingRows', this.adapter.processGroupingRowsConfig(grConfig));
5560
+ }
5561
+ else {
5562
+ addPlugin('groupingRows', grConfig);
5563
+ }
5564
+ // Pre-process pinnedRows config to bridge Angular component classes in customPanels
5565
+ const prConfig = this.pinnedRows();
5566
+ if (prConfig && typeof prConfig === 'object' && this.adapter) {
5567
+ addPlugin('pinnedRows', this.adapter.processPinnedRowsConfig(prConfig));
5568
+ }
5569
+ else {
5570
+ addPlugin('pinnedRows', prConfig);
5571
+ }
5319
5572
  addPlugin('tree', this.tree());
5320
5573
  addPlugin('masterDetail', this.masterDetail());
5321
5574
  addPlugin('responsive', this.responsive());