@toolbox-web/grid-angular 0.9.1 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/README.md +28 -22
  2. package/fesm2022/toolbox-web-grid-angular-features-export.mjs +123 -4
  3. package/fesm2022/toolbox-web-grid-angular-features-export.mjs.map +1 -1
  4. package/fesm2022/toolbox-web-grid-angular-features-filtering.mjs +123 -1
  5. package/fesm2022/toolbox-web-grid-angular-features-filtering.mjs.map +1 -1
  6. package/fesm2022/toolbox-web-grid-angular-features-print.mjs +89 -1
  7. package/fesm2022/toolbox-web-grid-angular-features-print.mjs.map +1 -1
  8. package/fesm2022/toolbox-web-grid-angular-features-selection.mjs +133 -1
  9. package/fesm2022/toolbox-web-grid-angular-features-selection.mjs.map +1 -1
  10. package/fesm2022/toolbox-web-grid-angular-features-undo-redo.mjs +106 -1
  11. package/fesm2022/toolbox-web-grid-angular-features-undo-redo.mjs.map +1 -1
  12. package/fesm2022/toolbox-web-grid-angular.mjs +757 -122
  13. package/fesm2022/toolbox-web-grid-angular.mjs.map +1 -1
  14. package/package.json +1 -1
  15. package/types/toolbox-web-grid-angular-features-export.d.ts +113 -1
  16. package/types/toolbox-web-grid-angular-features-export.d.ts.map +1 -1
  17. package/types/toolbox-web-grid-angular-features-filtering.d.ts +120 -1
  18. package/types/toolbox-web-grid-angular-features-filtering.d.ts.map +1 -1
  19. package/types/toolbox-web-grid-angular-features-print.d.ts +91 -1
  20. package/types/toolbox-web-grid-angular-features-print.d.ts.map +1 -1
  21. package/types/toolbox-web-grid-angular-features-selection.d.ts +114 -1
  22. package/types/toolbox-web-grid-angular-features-selection.d.ts.map +1 -1
  23. package/types/toolbox-web-grid-angular-features-undo-redo.d.ts +107 -1
  24. package/types/toolbox-web-grid-angular-features-undo-redo.d.ts.map +1 -1
  25. package/types/toolbox-web-grid-angular.d.ts +419 -91
  26. package/types/toolbox-web-grid-angular.d.ts.map +1 -1
@@ -1,8 +1,12 @@
1
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';
2
+ import { inject, ElementRef, contentChild, TemplateRef, effect, Directive, input, DestroyRef, InjectionToken, Injectable, makeEnvironmentProviders, EventEmitter, createComponent, signal, afterNextRender, computed, output, EnvironmentInjector, ApplicationRef, ViewContainerRef } from '@angular/core';
3
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
3
4
  import { FormGroup } from '@angular/forms';
5
+ import { startWith, debounceTime } from 'rxjs/operators';
4
6
  import { DataGridElement } from '@toolbox-web/grid';
5
7
 
8
+ // #endregion
9
+ // #region Utilities
6
10
  /**
7
11
  * Type guard to check if a value is an Angular component class.
8
12
  *
@@ -26,6 +30,7 @@ function isComponentClass(value) {
26
30
  const fnString = Function.prototype.toString.call(value);
27
31
  return fnString.startsWith('class ') || fnString.startsWith('class{');
28
32
  }
33
+ // #endregion
29
34
 
30
35
  // Global registry mapping DOM elements to their templates
31
36
  const editorTemplateRegistry = new Map();
@@ -279,13 +284,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImpor
279
284
  }], 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
285
 
281
286
  // Symbol for storing form context on the grid element
282
- const FORM_ARRAY_CONTEXT = Symbol('formArrayContext');
287
+ const FORM_ARRAY_CONTEXT$1 = Symbol('formArrayContext');
283
288
  /**
284
289
  * Gets the FormArrayContext from a grid element, if present.
285
290
  * @internal
286
291
  */
287
292
  function getFormArrayContext(gridElement) {
288
- return gridElement[FORM_ARRAY_CONTEXT];
293
+ return gridElement[FORM_ARRAY_CONTEXT$1];
289
294
  }
290
295
  /**
291
296
  * Directive that binds a FormArray directly to the grid.
@@ -339,10 +344,13 @@ function getFormArrayContext(gridElement) {
339
344
  * - Automatically syncs FormArray changes to the grid
340
345
  */
341
346
  class GridFormArray {
347
+ destroyRef = inject(DestroyRef);
342
348
  elementRef = inject((ElementRef));
343
349
  cellCommitListener = null;
344
350
  rowCommitListener = null;
345
351
  touchListener = null;
352
+ valueChangesSubscription = null;
353
+ statusChangesSubscriptions = [];
346
354
  /**
347
355
  * The FormArray to bind to the grid.
348
356
  */
@@ -354,20 +362,30 @@ class GridFormArray {
354
362
  * - After a cell commit, if the FormControl is invalid, the cell is marked with `setInvalid()`
355
363
  * - When a FormControl becomes valid, `clearInvalid()` is called
356
364
  * - On `row-commit`, if the row's FormGroup has invalid controls, the commit is prevented
365
+ * - In grid mode: validation state is synced on initial render and updated reactively
357
366
  *
358
367
  * @default true
359
368
  */
360
369
  syncValidation = input(true, ...(ngDevMode ? [{ debugName: "syncValidation" }] : []));
361
370
  /**
362
- * Effect that syncs the FormArray value to the grid rows.
371
+ * Effect that sets up valueChanges subscription when FormArray changes.
372
+ * This handles both initial binding and when the FormArray reference changes.
363
373
  */
364
374
  syncFormArrayToGrid = effect(() => {
365
375
  const formArray = this.formArray();
366
376
  const grid = this.elementRef.nativeElement;
367
- if (grid && formArray) {
368
- // Get the raw value (including disabled controls)
377
+ if (!grid || !formArray)
378
+ return;
379
+ // Unsubscribe from previous FormArray if any
380
+ this.valueChangesSubscription?.unsubscribe();
381
+ // Subscribe to valueChanges to sync grid rows when FormArray content changes.
382
+ // Use startWith to immediately sync the current value.
383
+ // Note: We use getRawValue() to include disabled controls.
384
+ this.valueChangesSubscription = formArray.valueChanges
385
+ .pipe(startWith(formArray.getRawValue()), takeUntilDestroyed(this.destroyRef))
386
+ .subscribe(() => {
369
387
  grid.rows = formArray.getRawValue();
370
- }
388
+ });
371
389
  }, ...(ngDevMode ? [{ debugName: "syncFormArrayToGrid" }] : []));
372
390
  ngOnInit() {
373
391
  const grid = this.elementRef.nativeElement;
@@ -399,6 +417,15 @@ class GridFormArray {
399
417
  }
400
418
  };
401
419
  grid.addEventListener('click', this.touchListener);
420
+ // If in grid mode with syncValidation, set up reactive validation
421
+ // Wait for grid to be ready so we can detect the editing mode
422
+ grid.ready?.().then(() => {
423
+ if (this.syncValidation() && this.#isGridMode()) {
424
+ this.#setupReactiveValidation();
425
+ // Sync initial validation state for all existing controls
426
+ this.#syncAllValidationState();
427
+ }
428
+ });
402
429
  }
403
430
  ngOnDestroy() {
404
431
  const grid = this.elementRef.nativeElement;
@@ -413,8 +440,96 @@ class GridFormArray {
413
440
  if (this.touchListener) {
414
441
  grid.removeEventListener('click', this.touchListener);
415
442
  }
443
+ if (this.valueChangesSubscription) {
444
+ this.valueChangesSubscription.unsubscribe();
445
+ }
446
+ // Clean up status change subscriptions (grid mode)
447
+ this.statusChangesSubscriptions.forEach((sub) => sub.unsubscribe());
448
+ this.statusChangesSubscriptions = [];
416
449
  this.#clearFormContext(grid);
417
450
  }
451
+ /**
452
+ * Checks if the EditingPlugin is in 'grid' mode.
453
+ */
454
+ #isGridMode() {
455
+ const grid = this.elementRef.nativeElement;
456
+ if (!grid)
457
+ return false;
458
+ const editingPlugin = grid.getPluginByName?.('editing');
459
+ return editingPlugin?.config?.mode === 'grid';
460
+ }
461
+ /**
462
+ * Sets up reactive validation syncing for grid mode.
463
+ * Subscribes to statusChanges on all FormControls to update validation state in real-time.
464
+ */
465
+ #setupReactiveValidation() {
466
+ const formArray = this.formArray();
467
+ const grid = this.elementRef.nativeElement;
468
+ if (!grid)
469
+ return;
470
+ // Clean up any existing subscriptions
471
+ this.statusChangesSubscriptions.forEach((sub) => sub.unsubscribe());
472
+ this.statusChangesSubscriptions = [];
473
+ // Subscribe to each FormGroup's statusChanges
474
+ for (let rowIndex = 0; rowIndex < formArray.length; rowIndex++) {
475
+ const rowFormGroup = this.#getRowFormGroup(rowIndex);
476
+ if (!rowFormGroup)
477
+ continue;
478
+ // Get the row ID for this row
479
+ const rowId = this.#getRowId(grid, rowIndex);
480
+ if (!rowId)
481
+ continue;
482
+ // Subscribe to status changes for each control in the FormGroup
483
+ Object.keys(rowFormGroup.controls).forEach((field) => {
484
+ const control = rowFormGroup.get(field);
485
+ if (!control)
486
+ return;
487
+ const sub = control.statusChanges.pipe(debounceTime(50), takeUntilDestroyed(this.destroyRef)).subscribe(() => {
488
+ this.#syncControlValidationToGrid(rowId, field, control);
489
+ });
490
+ this.statusChangesSubscriptions.push(sub);
491
+ });
492
+ }
493
+ }
494
+ /**
495
+ * Syncs validation state for all controls in the FormArray.
496
+ * Called once on init in grid mode to show pre-existing validation errors.
497
+ */
498
+ #syncAllValidationState() {
499
+ const formArray = this.formArray();
500
+ const grid = this.elementRef.nativeElement;
501
+ if (!grid)
502
+ return;
503
+ for (let rowIndex = 0; rowIndex < formArray.length; rowIndex++) {
504
+ const rowFormGroup = this.#getRowFormGroup(rowIndex);
505
+ if (!rowFormGroup)
506
+ continue;
507
+ const rowId = this.#getRowId(grid, rowIndex);
508
+ if (!rowId)
509
+ continue;
510
+ Object.keys(rowFormGroup.controls).forEach((field) => {
511
+ const control = rowFormGroup.get(field);
512
+ if (control) {
513
+ this.#syncControlValidationToGrid(rowId, field, control);
514
+ }
515
+ });
516
+ }
517
+ }
518
+ /**
519
+ * Gets the row ID for a given row index using the grid's getRowId method.
520
+ */
521
+ #getRowId(grid, rowIndex) {
522
+ try {
523
+ const rows = grid.rows;
524
+ const row = rows?.[rowIndex];
525
+ if (!row)
526
+ return undefined;
527
+ return grid.getRowId?.(row);
528
+ }
529
+ catch {
530
+ return undefined;
531
+ }
532
+ }
418
533
  /**
419
534
  * Checks if the FormArray contains FormGroups.
420
535
  */
@@ -502,13 +617,13 @@ class GridFormArray {
502
617
  return hasErrors ? errors : null;
503
618
  },
504
619
  };
505
- grid[FORM_ARRAY_CONTEXT] = context;
620
+ grid[FORM_ARRAY_CONTEXT$1] = context;
506
621
  }
507
622
  /**
508
623
  * Clears the FormArrayContext from the grid element.
509
624
  */
510
625
  #clearFormContext(grid) {
511
- delete grid[FORM_ARRAY_CONTEXT];
626
+ delete grid[FORM_ARRAY_CONTEXT$1];
512
627
  }
513
628
  /**
514
629
  * Handles cell-commit events by updating the FormControl in the FormGroup.
@@ -1169,7 +1284,7 @@ function getAnyEditorTemplate(element) {
1169
1284
  * ```typescript
1170
1285
  * import { Component, inject, EnvironmentInjector, ApplicationRef, ViewContainerRef } from '@angular/core';
1171
1286
  * import { GridElement } from '@toolbox-web/grid';
1172
- * import { AngularGridAdapter } from '@toolbox-web/grid-angular';
1287
+ * import { GridAdapter } from '@toolbox-web/grid-angular';
1173
1288
  *
1174
1289
  * @Component({
1175
1290
  * selector: 'app-root',
@@ -1180,7 +1295,7 @@ function getAnyEditorTemplate(element) {
1180
1295
  * const injector = inject(EnvironmentInjector);
1181
1296
  * const appRef = inject(ApplicationRef);
1182
1297
  * const viewContainerRef = inject(ViewContainerRef);
1183
- * GridElement.registerAdapter(new AngularGridAdapter(injector, appRef, viewContainerRef));
1298
+ * GridElement.registerAdapter(new GridAdapter(injector, appRef, viewContainerRef));
1184
1299
  * }
1185
1300
  * }
1186
1301
  * ```
@@ -1219,7 +1334,7 @@ function getAnyEditorTemplate(element) {
1219
1334
  * - Handles editor callbacks (onCommit/onCancel)
1220
1335
  * - Manages view lifecycle and change detection
1221
1336
  */
1222
- class AngularGridAdapter {
1337
+ class GridAdapter {
1223
1338
  injector;
1224
1339
  appRef;
1225
1340
  viewContainerRef;
@@ -1249,9 +1364,9 @@ class AngularGridAdapter {
1249
1364
  *
1250
1365
  * @example
1251
1366
  * ```typescript
1252
- * import { AngularGridAdapter, type AngularGridConfig } from '@toolbox-web/grid-angular';
1367
+ * import { GridAdapter, type GridConfig } from '@toolbox-web/grid-angular';
1253
1368
  *
1254
- * const config: AngularGridConfig<Employee> = {
1369
+ * const config: GridConfig<Employee> = {
1255
1370
  * columns: [
1256
1371
  * { field: 'status', renderer: StatusBadgeComponent, editor: StatusEditorComponent },
1257
1372
  * ],
@@ -1259,7 +1374,7 @@ class AngularGridAdapter {
1259
1374
  *
1260
1375
  * // In component
1261
1376
  * constructor() {
1262
- * const adapter = inject(AngularGridAdapter); // or create new instance
1377
+ * const adapter = inject(GridAdapter); // or create new instance
1263
1378
  * this.processedConfig = adapter.processGridConfig(config);
1264
1379
  * }
1265
1380
  * ```
@@ -1268,14 +1383,39 @@ class AngularGridAdapter {
1268
1383
  * @returns Processed GridConfig with actual renderer/editor functions
1269
1384
  */
1270
1385
  processGridConfig(config) {
1271
- if (!config.columns) {
1272
- return config;
1386
+ const result = { ...config };
1387
+ // Process columns
1388
+ if (config.columns) {
1389
+ result.columns = config.columns.map((col) => this.processColumn(col));
1273
1390
  }
1274
- const processedColumns = config.columns.map((col) => this.processColumn(col));
1275
- return {
1276
- ...config,
1277
- columns: processedColumns,
1278
- };
1391
+ // Process typeDefaults - convert Angular component classes to renderer/editor functions
1392
+ if (config.typeDefaults) {
1393
+ result.typeDefaults = this.processTypeDefaults(config.typeDefaults);
1394
+ }
1395
+ return result;
1396
+ }
1397
+ /**
1398
+ * Processes typeDefaults configuration, converting component class references
1399
+ * to actual renderer/editor functions.
1400
+ *
1401
+ * @param typeDefaults - Angular type defaults with possible component class references
1402
+ * @returns Processed TypeDefault record
1403
+ */
1404
+ processTypeDefaults(typeDefaults) {
1405
+ const processed = {};
1406
+ for (const [type, config] of Object.entries(typeDefaults)) {
1407
+ const processedConfig = { ...config };
1408
+ // Convert renderer component class to function
1409
+ if (config.renderer && isComponentClass(config.renderer)) {
1410
+ processedConfig.renderer = this.createComponentRenderer(config.renderer);
1411
+ }
1412
+ // Convert editor component class to function
1413
+ if (config.editor && isComponentClass(config.editor)) {
1414
+ processedConfig.editor = this.createComponentEditor(config.editor);
1415
+ }
1416
+ processed[type] = processedConfig;
1417
+ }
1418
+ return processed;
1279
1419
  }
1280
1420
  /**
1281
1421
  * Processes a single column configuration, converting component class references
@@ -1318,6 +1458,11 @@ class AngularGridAdapter {
1318
1458
  return undefined;
1319
1459
  }
1320
1460
  return (ctx) => {
1461
+ // Skip rendering if the cell is in editing mode
1462
+ // This prevents the renderer from overwriting the editor when the grid re-renders
1463
+ if (ctx.cellEl?.classList.contains('editing')) {
1464
+ return null;
1465
+ }
1321
1466
  // Create the context for the template
1322
1467
  const context = {
1323
1468
  $implicit: ctx.value,
@@ -1358,10 +1503,10 @@ class AngularGridAdapter {
1358
1503
  // Find the parent grid element for FormArray context access
1359
1504
  const gridElement = element.closest('tbw-grid');
1360
1505
  if (!template) {
1361
- // No warning - this can happen during early initialization before Angular
1362
- // registers structural directive templates. The grid will re-parse columns
1363
- // after templates are registered.
1364
- return () => document.createElement('div');
1506
+ // No template registered - return undefined to let the grid use its default editor.
1507
+ // This allows columns with only *tbwRenderer (no *tbwEditor) to still be editable
1508
+ // using the built-in text/number/boolean editors.
1509
+ return undefined;
1365
1510
  }
1366
1511
  return (ctx) => {
1367
1512
  // Create simple callback functions (preferred)
@@ -1591,31 +1736,58 @@ class AngularGridAdapter {
1591
1736
  }
1592
1737
  return typeDefault;
1593
1738
  }
1739
+ /**
1740
+ * Creates and mounts an Angular component dynamically.
1741
+ * Shared logic between renderer and editor component creation.
1742
+ * @internal
1743
+ */
1744
+ mountComponent(componentClass, inputs) {
1745
+ // Create a host element for the component
1746
+ const hostElement = document.createElement('span');
1747
+ hostElement.style.display = 'contents';
1748
+ // Create the component dynamically
1749
+ const componentRef = createComponent(componentClass, {
1750
+ environmentInjector: this.injector,
1751
+ hostElement,
1752
+ });
1753
+ // Set inputs - components should have value, row, column inputs
1754
+ this.setComponentInputs(componentRef, inputs);
1755
+ // Attach to app for change detection
1756
+ this.appRef.attachView(componentRef.hostView);
1757
+ this.componentRefs.push(componentRef);
1758
+ // Trigger change detection
1759
+ componentRef.changeDetectorRef.detectChanges();
1760
+ return { hostElement, componentRef };
1761
+ }
1762
+ /**
1763
+ * Wires up commit/cancel handlers for an editor component.
1764
+ * Supports both Angular outputs and DOM CustomEvents.
1765
+ * @internal
1766
+ */
1767
+ wireEditorCallbacks(hostElement, componentRef, commit, cancel) {
1768
+ // Subscribe to Angular outputs (commit/cancel) on the component instance.
1769
+ // This works with Angular's output() signal API.
1770
+ const instance = componentRef.instance;
1771
+ this.subscribeToOutput(instance, 'commit', commit);
1772
+ this.subscribeToOutput(instance, 'cancel', cancel);
1773
+ // Also listen for DOM events as fallback (for components that dispatch CustomEvents)
1774
+ hostElement.addEventListener('commit', (e) => {
1775
+ const customEvent = e;
1776
+ commit(customEvent.detail);
1777
+ });
1778
+ hostElement.addEventListener('cancel', () => cancel());
1779
+ }
1594
1780
  /**
1595
1781
  * Creates a renderer function from an Angular component class.
1596
1782
  * @internal
1597
1783
  */
1598
1784
  createComponentRenderer(componentClass) {
1599
1785
  return (ctx) => {
1600
- // Create a host element for the component
1601
- const hostElement = document.createElement('span');
1602
- hostElement.style.display = 'contents';
1603
- // Create the component dynamically
1604
- const componentRef = createComponent(componentClass, {
1605
- environmentInjector: this.injector,
1606
- hostElement,
1607
- });
1608
- // Set inputs - components should have value, row, column inputs
1609
- this.setComponentInputs(componentRef, {
1786
+ const { hostElement } = this.mountComponent(componentClass, {
1610
1787
  value: ctx.value,
1611
1788
  row: ctx.row,
1612
1789
  column: ctx.column,
1613
1790
  });
1614
- // Attach to app for change detection
1615
- this.appRef.attachView(componentRef.hostView);
1616
- this.componentRefs.push(componentRef);
1617
- // Trigger change detection
1618
- componentRef.changeDetectorRef.detectChanges();
1619
1791
  return hostElement;
1620
1792
  };
1621
1793
  }
@@ -1625,38 +1797,12 @@ class AngularGridAdapter {
1625
1797
  */
1626
1798
  createComponentEditor(componentClass) {
1627
1799
  return (ctx) => {
1628
- // Create a host element for the component
1629
- const hostElement = document.createElement('span');
1630
- hostElement.style.display = 'contents';
1631
- // Create the component dynamically
1632
- const componentRef = createComponent(componentClass, {
1633
- environmentInjector: this.injector,
1634
- hostElement,
1635
- });
1636
- // Set inputs - components should have value, row, column inputs
1637
- this.setComponentInputs(componentRef, {
1800
+ const { hostElement, componentRef } = this.mountComponent(componentClass, {
1638
1801
  value: ctx.value,
1639
1802
  row: ctx.row,
1640
1803
  column: ctx.column,
1641
1804
  });
1642
- // Attach to app for change detection
1643
- this.appRef.attachView(componentRef.hostView);
1644
- this.componentRefs.push(componentRef);
1645
- // Trigger change detection
1646
- componentRef.changeDetectorRef.detectChanges();
1647
- // Subscribe to Angular outputs (commit/cancel) on the component instance.
1648
- // This works with Angular's output() signal API.
1649
- const instance = componentRef.instance;
1650
- this.subscribeToOutput(instance, 'commit', (value) => ctx.commit(value));
1651
- this.subscribeToOutput(instance, 'cancel', () => ctx.cancel());
1652
- // Also listen for DOM events as fallback (for components that dispatch CustomEvents)
1653
- hostElement.addEventListener('commit', (e) => {
1654
- const customEvent = e;
1655
- ctx.commit(customEvent.detail);
1656
- });
1657
- hostElement.addEventListener('cancel', () => {
1658
- ctx.cancel();
1659
- });
1805
+ this.wireEditorCallbacks(hostElement, componentRef, (value) => ctx.commit(value), () => ctx.cancel());
1660
1806
  return hostElement;
1661
1807
  };
1662
1808
  }
@@ -1699,6 +1845,11 @@ class AngularGridAdapter {
1699
1845
  this.componentRefs = [];
1700
1846
  }
1701
1847
  }
1848
+ /**
1849
+ * @deprecated Use `GridAdapter` instead. This alias will be removed in a future version.
1850
+ * @see {@link GridAdapter}
1851
+ */
1852
+ const AngularGridAdapter = GridAdapter;
1702
1853
 
1703
1854
  /**
1704
1855
  * Icon configuration registry for Angular applications.
@@ -2377,6 +2528,490 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImpor
2377
2528
  type: Directive
2378
2529
  }], 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"] }] } });
2379
2530
 
2531
+ // Symbol for storing form context on the grid element (shared with GridFormArray)
2532
+ const FORM_ARRAY_CONTEXT = Symbol('formArrayContext');
2533
+ /**
2534
+ * Gets the FormArrayContext from a grid element, if present.
2535
+ * @internal
2536
+ */
2537
+ function getLazyFormContext(gridElement) {
2538
+ return gridElement[FORM_ARRAY_CONTEXT];
2539
+ }
2540
+ /**
2541
+ * Directive that provides lazy FormGroup creation for grid editing.
2542
+ *
2543
+ * Unlike `GridFormArray` which creates all FormGroups upfront, this directive
2544
+ * creates FormGroups on-demand only when a row enters edit mode. This provides
2545
+ * much better performance for large datasets while still enabling full
2546
+ * Angular Reactive Forms integration.
2547
+ *
2548
+ * ## Key Benefits
2549
+ *
2550
+ * - **Performance**: Only creates FormGroups for rows being edited (20-100x fewer controls)
2551
+ * - **Same DX**: Editors still receive `control` in their context for validation
2552
+ * - **Memory efficient**: FormGroups are cleaned up when rows exit edit mode
2553
+ *
2554
+ * ## Usage
2555
+ *
2556
+ * ```typescript
2557
+ * import { Component, inject, signal } from '@angular/core';
2558
+ * import { FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
2559
+ * import { Grid, GridLazyForm, TbwEditor } from '@toolbox-web/grid-angular';
2560
+ *
2561
+ * @Component({
2562
+ * imports: [Grid, GridLazyForm, TbwEditor, ReactiveFormsModule],
2563
+ * template: \`
2564
+ * <tbw-grid
2565
+ * [rows]="employees()"
2566
+ * [lazyForm]="createRowForm"
2567
+ * [gridConfig]="config">
2568
+ *
2569
+ * <tbw-grid-column field="firstName">
2570
+ * <input *tbwEditor="let _; control as ctrl"
2571
+ * [formControl]="ctrl"
2572
+ * [class.is-invalid]="ctrl?.invalid && ctrl?.touched" />
2573
+ * </tbw-grid-column>
2574
+ * </tbw-grid>
2575
+ * \`
2576
+ * })
2577
+ * export class MyComponent {
2578
+ * private fb = inject(FormBuilder);
2579
+ * employees = signal(generateEmployees(1000));
2580
+ *
2581
+ * // Factory called when editing starts - only include editable fields!
2582
+ * createRowForm = (row: Employee): FormGroup => this.fb.group({
2583
+ * firstName: [row.firstName, Validators.required],
2584
+ * lastName: [row.lastName, Validators.minLength(2)],
2585
+ * salary: [row.salary, [Validators.required, Validators.min(0)]],
2586
+ * });
2587
+ *
2588
+ * gridConfig = { columns: [...] };
2589
+ * }
2590
+ * ```
2591
+ *
2592
+ * ## How It Works
2593
+ *
2594
+ * 1. Rows come from `[rows]` input (plain data array)
2595
+ * 2. When a cell enters edit mode, the FormGroup is created lazily
2596
+ * 3. Editors receive the FormControl in their template context
2597
+ * 4. On commit, FormGroup values are synced back to the row
2598
+ * 5. FormGroup is cleaned up when the row exits edit mode (configurable)
2599
+ *
2600
+ * ## Performance Comparison
2601
+ *
2602
+ * | Rows | GridFormArray (20 fields) | GridLazyForm |
2603
+ * |------|---------------------------|--------------|
2604
+ * | 100 | 2,000 controls | ~20 controls |
2605
+ * | 500 | 10,000 controls | ~20 controls |
2606
+ * | 1000 | 20,000 controls | ~20 controls |
2607
+ *
2608
+ * @see GridFormArray For small datasets with full upfront validation
2609
+ */
2610
+ class GridLazyForm {
2611
+ elementRef = inject((ElementRef));
2612
+ // Cache of FormGroups by row reference
2613
+ formGroupCache = new Map();
2614
+ // Map from row reference to rowIndex (needed for getControl)
2615
+ rowIndexMap = new Map();
2616
+ // Track which row is currently being edited
2617
+ editingRowIndex = null;
2618
+ cellCommitListener = null;
2619
+ rowCommitListener = null;
2620
+ rowsChangeListener = null;
2621
+ /**
2622
+ * Factory function to create a FormGroup for a row.
2623
+ * Called lazily when the row first enters edit mode.
2624
+ *
2625
+ * @example
2626
+ * ```typescript
2627
+ * createRowForm = (row: Employee): FormGroup => this.fb.group({
2628
+ * firstName: [row.firstName, Validators.required],
2629
+ * lastName: [row.lastName],
2630
+ * salary: [row.salary, [Validators.min(0)]],
2631
+ * });
2632
+ * ```
2633
+ */
2634
+ lazyForm = input.required(...(ngDevMode ? [{ debugName: "lazyForm" }] : []));
2635
+ /**
2636
+ * Whether to automatically sync Angular validation state to grid's visual invalid styling.
2637
+ *
2638
+ * When enabled:
2639
+ * - After a cell commit, if the FormControl is invalid, the cell is marked with `setInvalid()`
2640
+ * - When a FormControl becomes valid, `clearInvalid()` is called
2641
+ * - On `row-commit`, if the row's FormGroup has invalid controls, the commit is prevented
2642
+ *
2643
+ * @default true
2644
+ */
2645
+ syncValidation = input(true, ...(ngDevMode ? [{ debugName: "syncValidation" }] : []));
2646
+ /**
2647
+ * Whether to keep FormGroups cached after a row exits edit mode.
2648
+ *
2649
+ * - `true`: FormGroups are kept, preserving dirty/touched state across edit sessions
2650
+ * - `false`: FormGroups are disposed when the row exits edit mode (default)
2651
+ *
2652
+ * @default false
2653
+ */
2654
+ keepFormGroups = input(false, ...(ngDevMode ? [{ debugName: "keepFormGroups" }] : []));
2655
+ /**
2656
+ * Emitted when a row's form values change.
2657
+ * Useful for auto-save, validation display, or syncing to external state.
2658
+ */
2659
+ rowFormChange = output();
2660
+ ngOnInit() {
2661
+ const grid = this.elementRef.nativeElement;
2662
+ if (!grid)
2663
+ return;
2664
+ // Store the form context for AngularGridAdapter to access
2665
+ this.#storeFormContext(grid);
2666
+ // Listen for cell-commit to update FormControl and sync validation
2667
+ this.cellCommitListener = (e) => {
2668
+ const detail = e.detail;
2669
+ this.#handleCellCommit(detail);
2670
+ };
2671
+ grid.addEventListener('cell-commit', this.cellCommitListener);
2672
+ // Listen for row-commit to sync FormGroup values back to row and cleanup
2673
+ this.rowCommitListener = (e) => {
2674
+ const detail = e.detail;
2675
+ this.#handleRowCommit(e, detail);
2676
+ };
2677
+ grid.addEventListener('row-commit', this.rowCommitListener);
2678
+ // Listen for rows-change to update row index mappings
2679
+ this.rowsChangeListener = () => {
2680
+ this.#updateRowIndexMap();
2681
+ };
2682
+ grid.addEventListener('rows-change', this.rowsChangeListener);
2683
+ // Initial row index mapping
2684
+ this.#updateRowIndexMap();
2685
+ }
2686
+ ngOnDestroy() {
2687
+ const grid = this.elementRef.nativeElement;
2688
+ if (!grid)
2689
+ return;
2690
+ if (this.cellCommitListener) {
2691
+ grid.removeEventListener('cell-commit', this.cellCommitListener);
2692
+ }
2693
+ if (this.rowCommitListener) {
2694
+ grid.removeEventListener('row-commit', this.rowCommitListener);
2695
+ }
2696
+ if (this.rowsChangeListener) {
2697
+ grid.removeEventListener('rows-change', this.rowsChangeListener);
2698
+ }
2699
+ this.#clearFormContext(grid);
2700
+ this.formGroupCache.clear();
2701
+ this.rowIndexMap.clear();
2702
+ }
2703
+ // #region FormArrayContext Implementation
2704
+ /**
2705
+ * Gets or creates the FormGroup for a row.
2706
+ * This is the core lazy initialization logic.
2707
+ */
2708
+ #getOrCreateFormGroup(row, rowIndex) {
2709
+ let formGroup = this.formGroupCache.get(row);
2710
+ if (!formGroup) {
2711
+ const factory = this.lazyForm();
2712
+ formGroup = factory(row, rowIndex);
2713
+ this.formGroupCache.set(row, formGroup);
2714
+ this.rowIndexMap.set(row, rowIndex);
2715
+ }
2716
+ return formGroup;
2717
+ }
2718
+ /**
2719
+ * Gets the FormGroup for a row if it exists (without creating).
2720
+ */
2721
+ #getRowFormGroup(rowIndex) {
2722
+ const grid = this.elementRef.nativeElement;
2723
+ const rows = grid?.rows;
2724
+ if (!rows || rowIndex < 0 || rowIndex >= rows.length)
2725
+ return undefined;
2726
+ const row = rows[rowIndex];
2727
+ return this.formGroupCache.get(row);
2728
+ }
2729
+ /**
2730
+ * Stores the FormArrayContext on the grid element.
2731
+ * This uses the same interface as GridFormArray for compatibility.
2732
+ */
2733
+ #storeFormContext(grid) {
2734
+ const context = {
2735
+ getRow: (rowIndex) => {
2736
+ const rows = grid.rows;
2737
+ if (!rows || rowIndex < 0 || rowIndex >= rows.length)
2738
+ return null;
2739
+ return rows[rowIndex];
2740
+ },
2741
+ updateField: (rowIndex, field, value) => {
2742
+ const formGroup = this.#getRowFormGroup(rowIndex);
2743
+ if (formGroup) {
2744
+ const control = formGroup.get(field);
2745
+ if (control) {
2746
+ control.setValue(value);
2747
+ control.markAsDirty();
2748
+ }
2749
+ }
2750
+ },
2751
+ getValue: () => {
2752
+ return (grid.rows ?? []);
2753
+ },
2754
+ // Always true for lazy forms - we create FormGroups on demand
2755
+ hasFormGroups: true,
2756
+ getControl: (rowIndex, field) => {
2757
+ const rows = grid.rows;
2758
+ if (!rows || rowIndex < 0 || rowIndex >= rows.length)
2759
+ return undefined;
2760
+ const row = rows[rowIndex];
2761
+ // LAZY: Create the FormGroup when first needed
2762
+ const formGroup = this.#getOrCreateFormGroup(row, rowIndex);
2763
+ return formGroup.get(field) ?? undefined;
2764
+ },
2765
+ getRowFormGroup: (rowIndex) => {
2766
+ const rows = grid.rows;
2767
+ if (!rows || rowIndex < 0 || rowIndex >= rows.length)
2768
+ return undefined;
2769
+ const row = rows[rowIndex];
2770
+ // LAZY: Create the FormGroup when first needed
2771
+ return this.#getOrCreateFormGroup(row, rowIndex);
2772
+ },
2773
+ isRowValid: (rowIndex) => {
2774
+ const formGroup = this.#getRowFormGroup(rowIndex);
2775
+ // If no FormGroup exists yet, consider it valid (not edited)
2776
+ if (!formGroup)
2777
+ return true;
2778
+ return formGroup.valid;
2779
+ },
2780
+ isRowTouched: (rowIndex) => {
2781
+ const formGroup = this.#getRowFormGroup(rowIndex);
2782
+ if (!formGroup)
2783
+ return false;
2784
+ return formGroup.touched;
2785
+ },
2786
+ isRowDirty: (rowIndex) => {
2787
+ const formGroup = this.#getRowFormGroup(rowIndex);
2788
+ if (!formGroup)
2789
+ return false;
2790
+ return formGroup.dirty;
2791
+ },
2792
+ getRowErrors: (rowIndex) => {
2793
+ const formGroup = this.#getRowFormGroup(rowIndex);
2794
+ if (!formGroup)
2795
+ return null;
2796
+ const errors = {};
2797
+ let hasErrors = false;
2798
+ Object.keys(formGroup.controls).forEach((field) => {
2799
+ const control = formGroup.get(field);
2800
+ if (control?.errors) {
2801
+ errors[field] = control.errors;
2802
+ hasErrors = true;
2803
+ }
2804
+ });
2805
+ if (formGroup.errors) {
2806
+ errors['_group'] = formGroup.errors;
2807
+ hasErrors = true;
2808
+ }
2809
+ return hasErrors ? errors : null;
2810
+ },
2811
+ };
2812
+ grid[FORM_ARRAY_CONTEXT] = context;
2813
+ }
2814
+ /**
2815
+ * Clears the FormArrayContext from the grid element.
2816
+ */
2817
+ #clearFormContext(grid) {
2818
+ delete grid[FORM_ARRAY_CONTEXT];
2819
+ }
2820
+ // #endregion
2821
+ // #region Event Handlers
2822
+ /**
2823
+ * Updates the row index map when rows change.
2824
+ * This ensures we can find FormGroups by row reference after sorting/filtering.
2825
+ */
2826
+ #updateRowIndexMap() {
2827
+ const grid = this.elementRef.nativeElement;
2828
+ const rows = grid?.rows;
2829
+ if (!rows)
2830
+ return;
2831
+ this.rowIndexMap.clear();
2832
+ rows.forEach((row, index) => {
2833
+ if (this.formGroupCache.has(row)) {
2834
+ this.rowIndexMap.set(row, index);
2835
+ }
2836
+ });
2837
+ }
2838
+ /**
2839
+ * Handles cell-commit events by updating the FormControl in the FormGroup.
2840
+ */
2841
+ #handleCellCommit(detail) {
2842
+ const { rowIndex, field, value, rowId } = detail;
2843
+ const formGroup = this.#getRowFormGroup(rowIndex);
2844
+ if (formGroup) {
2845
+ const control = formGroup.get(field);
2846
+ if (control) {
2847
+ control.setValue(value);
2848
+ control.markAsDirty();
2849
+ control.markAsTouched();
2850
+ // Sync Angular validation state to grid's visual invalid styling
2851
+ if (this.syncValidation() && rowId) {
2852
+ this.#syncControlValidationToGrid(rowId, field, control);
2853
+ }
2854
+ // Emit change event
2855
+ const grid = this.elementRef.nativeElement;
2856
+ const row = grid?.rows?.[rowIndex];
2857
+ if (row) {
2858
+ this.rowFormChange.emit({
2859
+ rowIndex,
2860
+ rowId,
2861
+ row,
2862
+ formGroup,
2863
+ values: formGroup.value,
2864
+ valid: formGroup.valid,
2865
+ dirty: formGroup.dirty,
2866
+ });
2867
+ }
2868
+ }
2869
+ }
2870
+ }
2871
+ /**
2872
+ * Handles row-commit events.
2873
+ * - Prevents commit if FormGroup is invalid (when syncValidation is true)
2874
+ * - Syncs FormGroup values back to the row
2875
+ * - Cleans up FormGroup if keepFormGroups is false
2876
+ */
2877
+ #handleRowCommit(event, detail) {
2878
+ const { rowIndex, rowId } = detail;
2879
+ const grid = this.elementRef.nativeElement;
2880
+ const rows = grid?.rows;
2881
+ if (!rows || rowIndex < 0 || rowIndex >= rows.length)
2882
+ return;
2883
+ const row = rows[rowIndex];
2884
+ const formGroup = this.formGroupCache.get(row);
2885
+ if (!formGroup)
2886
+ return;
2887
+ // Prevent commit if invalid (when syncValidation is enabled)
2888
+ if (this.syncValidation() && formGroup.invalid) {
2889
+ // Mark all controls as touched to show validation errors
2890
+ formGroup.markAllAsTouched();
2891
+ event.preventDefault();
2892
+ return;
2893
+ }
2894
+ // Sync FormGroup values back to the row object
2895
+ if (formGroup.dirty) {
2896
+ const formValue = formGroup.value;
2897
+ Object.keys(formValue).forEach((field) => {
2898
+ if (field in row) {
2899
+ row[field] = formValue[field];
2900
+ }
2901
+ });
2902
+ }
2903
+ // Clean up FormGroup if not keeping them
2904
+ if (!this.keepFormGroups()) {
2905
+ this.formGroupCache.delete(row);
2906
+ this.rowIndexMap.delete(row);
2907
+ // Clear any validation state in the grid
2908
+ if (rowId) {
2909
+ const editingPlugin = grid.getPluginByName?.('editing');
2910
+ editingPlugin?.clearRowInvalid(rowId);
2911
+ }
2912
+ }
2913
+ }
2914
+ // #endregion
2915
+ // #region Validation Sync
2916
+ /**
2917
+ * Syncs a FormControl's validation state to the grid's visual invalid styling.
2918
+ */
2919
+ #syncControlValidationToGrid(rowId, field, control) {
2920
+ const grid = this.elementRef.nativeElement;
2921
+ if (!grid)
2922
+ return;
2923
+ const editingPlugin = grid.getPluginByName?.('editing');
2924
+ if (!editingPlugin)
2925
+ return;
2926
+ if (control.invalid) {
2927
+ const errorMessage = this.#getFirstErrorMessage(control);
2928
+ editingPlugin.setInvalid(rowId, field, errorMessage);
2929
+ }
2930
+ else {
2931
+ editingPlugin.clearInvalid(rowId, field);
2932
+ }
2933
+ }
2934
+ /**
2935
+ * Gets a human-readable error message from the first validation error.
2936
+ */
2937
+ #getFirstErrorMessage(control) {
2938
+ const errors = control.errors;
2939
+ if (!errors)
2940
+ return '';
2941
+ const firstKey = Object.keys(errors)[0];
2942
+ const error = errors[firstKey];
2943
+ switch (firstKey) {
2944
+ case 'required':
2945
+ return 'This field is required';
2946
+ case 'minlength':
2947
+ return `Minimum length is ${error.requiredLength}`;
2948
+ case 'maxlength':
2949
+ return `Maximum length is ${error.requiredLength}`;
2950
+ case 'min':
2951
+ return `Minimum value is ${error.min}`;
2952
+ case 'max':
2953
+ return `Maximum value is ${error.max}`;
2954
+ case 'email':
2955
+ return 'Invalid email address';
2956
+ case 'pattern':
2957
+ return 'Invalid format';
2958
+ default:
2959
+ return typeof error === 'string' ? error : (error?.message ?? `Validation error: ${firstKey}`);
2960
+ }
2961
+ }
2962
+ // #endregion
2963
+ // #region Public API
2964
+ /**
2965
+ * Gets the FormGroup for a row, if it exists.
2966
+ * Unlike the context methods, this does NOT create a FormGroup lazily.
2967
+ *
2968
+ * @param rowIndex The row index
2969
+ * @returns The FormGroup or undefined
2970
+ */
2971
+ getFormGroup(rowIndex) {
2972
+ return this.#getRowFormGroup(rowIndex);
2973
+ }
2974
+ /**
2975
+ * Gets all cached FormGroups.
2976
+ * Useful for bulk validation or inspection.
2977
+ *
2978
+ * @returns Map of row objects to their FormGroups
2979
+ */
2980
+ getAllFormGroups() {
2981
+ return this.formGroupCache;
2982
+ }
2983
+ /**
2984
+ * Clears all cached FormGroups.
2985
+ * Useful when the underlying data changes significantly.
2986
+ */
2987
+ clearAllFormGroups() {
2988
+ this.formGroupCache.clear();
2989
+ this.rowIndexMap.clear();
2990
+ }
2991
+ /**
2992
+ * Validates all currently cached FormGroups.
2993
+ *
2994
+ * @returns true if all FormGroups are valid, false otherwise
2995
+ */
2996
+ validateAll() {
2997
+ for (const formGroup of this.formGroupCache.values()) {
2998
+ if (formGroup.invalid) {
2999
+ formGroup.markAllAsTouched();
3000
+ return false;
3001
+ }
3002
+ }
3003
+ return true;
3004
+ }
3005
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: GridLazyForm, deps: [], target: i0.ɵɵFactoryTarget.Directive });
3006
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.1", type: GridLazyForm, isStandalone: true, selector: "tbw-grid[lazyForm]", inputs: { lazyForm: { classPropertyName: "lazyForm", publicName: "lazyForm", isSignal: true, isRequired: true, transformFunction: null }, syncValidation: { classPropertyName: "syncValidation", publicName: "syncValidation", isSignal: true, isRequired: false, transformFunction: null }, keepFormGroups: { classPropertyName: "keepFormGroups", publicName: "keepFormGroups", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { rowFormChange: "rowFormChange" }, ngImport: i0 });
3007
+ }
3008
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: GridLazyForm, decorators: [{
3009
+ type: Directive,
3010
+ args: [{
3011
+ selector: 'tbw-grid[lazyForm]',
3012
+ }]
3013
+ }], propDecorators: { lazyForm: [{ type: i0.Input, args: [{ isSignal: true, alias: "lazyForm", required: true }] }], syncValidation: [{ type: i0.Input, args: [{ isSignal: true, alias: "syncValidation", required: false }] }], keepFormGroups: [{ type: i0.Input, args: [{ isSignal: true, alias: "keepFormGroups", required: false }] }], rowFormChange: [{ type: i0.Output, args: ["rowFormChange"] }] } });
3014
+
2380
3015
  /**
2381
3016
  * Directive that automatically registers the Angular adapter with tbw-grid elements.
2382
3017
  *
@@ -2407,7 +3042,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImpor
2407
3042
  * ```
2408
3043
  *
2409
3044
  * The directive automatically:
2410
- * - Creates an AngularGridAdapter instance
3045
+ * - Creates a GridAdapter instance
2411
3046
  * - Registers it with the GridElement
2412
3047
  * - Injects custom styles into the grid
2413
3048
  * - Handles cleanup on destruction
@@ -2420,11 +3055,18 @@ class Grid {
2420
3055
  iconRegistry = inject(GridIconRegistry, { optional: true });
2421
3056
  adapter = null;
2422
3057
  constructor() {
2423
- // Effect to process angularConfig and apply to grid
3058
+ // Effect to process gridConfig and apply to grid
2424
3059
  // This merges feature input plugins with the user's config plugins
2425
3060
  effect(() => {
2426
- const angularCfg = this.angularConfig();
3061
+ const deprecatedAngularConfig = this.angularConfig();
2427
3062
  const userGridConfig = this.gridConfig();
3063
+ // Emit deprecation warning if angularConfig is used
3064
+ if (deprecatedAngularConfig && !userGridConfig) {
3065
+ console.warn('[tbw-grid] The [angularConfig] input is deprecated. Use [gridConfig] instead. ' +
3066
+ 'The gridConfig input now accepts GridConfig directly.');
3067
+ }
3068
+ // Use gridConfig preferentially, fall back to deprecated angularConfig
3069
+ const angularCfg = userGridConfig ?? deprecatedAngularConfig;
2428
3070
  if (!this.adapter)
2429
3071
  return;
2430
3072
  // Create plugins from feature inputs
@@ -2448,25 +3090,24 @@ class Grid {
2448
3090
  // Registry icons are base, config.icons override them
2449
3091
  const registryIcons = this.iconRegistry?.getAll();
2450
3092
  if (registryIcons && Object.keys(registryIcons).length > 0) {
2451
- const existingIcons = angularCfg?.icons || userGridConfig?.icons || {};
3093
+ const existingIcons = angularCfg?.icons || {};
2452
3094
  coreConfigOverrides['icons'] = { ...registryIcons, ...existingIcons };
2453
3095
  }
2454
- // If angularConfig is provided, process it (converts component classes to renderer functions)
3096
+ // If gridConfig is provided, process it (converts component classes to renderer functions)
2455
3097
  const processedConfig = angularCfg ? this.adapter.processGridConfig(angularCfg) : null;
2456
- // IMPORTANT: If user is NOT using angularConfig input, and there are no feature plugins
3098
+ // IMPORTANT: If user is NOT using gridConfig input, and there are no feature plugins
2457
3099
  // or config overrides to merge, do NOT overwrite grid.gridConfig.
2458
3100
  // This allows [gridConfig]="myConfig" binding to work correctly without the directive
2459
3101
  // creating a new object that strips properties like typeDefaults.
2460
3102
  const hasFeaturePlugins = featurePlugins.length > 0;
2461
3103
  const hasConfigOverrides = Object.keys(coreConfigOverrides).length > 0;
2462
- // Use the gridConfig input signal (preferred) or fallback to what's on the grid element
2463
3104
  // The input signal gives us reactive tracking of the user's config
2464
- const existingConfig = userGridConfig || {};
2465
- if (!processedConfig && !hasFeaturePlugins && !hasConfigOverrides && !userGridConfig) {
3105
+ const existingConfig = angularCfg || {};
3106
+ if (!processedConfig && !hasFeaturePlugins && !hasConfigOverrides && !angularCfg) {
2466
3107
  // Nothing to merge and no config input - let the user's DOM binding work directly
2467
3108
  return;
2468
3109
  }
2469
- // Merge: existing config (from [gridConfig] input) < processed angularConfig < feature plugins
3110
+ // Merge: processed config < feature plugins
2470
3111
  const configPlugins = processedConfig?.plugins || existingConfig.plugins || [];
2471
3112
  const mergedPlugins = [...featurePlugins, ...configPlugins];
2472
3113
  // Build the final config, preserving ALL existing properties (including typeDefaults)
@@ -2595,65 +3236,55 @@ class Grid {
2595
3236
  */
2596
3237
  loading = input(...(ngDevMode ? [undefined, { debugName: "loading" }] : []));
2597
3238
  /**
2598
- * Core grid configuration object.
3239
+ * Grid configuration object with optional Angular-specific extensions.
2599
3240
  *
2600
- * Use this input for the base GridConfig (typeDefaults, getRowId, etc.).
2601
- * This is the same as binding directly to the web component's gridConfig property,
2602
- * but allows the directive to properly merge feature plugins and config overrides.
3241
+ * Accepts Angular-augmented `GridConfig` from `@toolbox-web/grid-angular`.
3242
+ * You can specify Angular component classes directly for renderers and editors.
2603
3243
  *
2604
- * For Angular-specific features (component class renderers/editors), use `angularConfig` instead.
3244
+ * Component classes must implement the appropriate interfaces:
3245
+ * - Renderers: `CellRenderer<TRow, TValue>` - requires `value()` and `row()` signal inputs
3246
+ * - Editors: `CellEditor<TRow, TValue>` - adds `commit` and `cancel` outputs
2605
3247
  *
2606
3248
  * @example
2607
3249
  * ```typescript
3250
+ * // Simple config with plain renderers
2608
3251
  * config: GridConfig = {
3252
+ * columns: [
3253
+ * { field: 'name', header: 'Name' },
3254
+ * { field: 'active', type: 'boolean' }
3255
+ * ],
2609
3256
  * typeDefaults: {
2610
3257
  * boolean: { renderer: (ctx) => ctx.value ? '✓' : '✗' }
2611
3258
  * }
2612
3259
  * };
2613
- * columns = [
2614
- * { field: 'active', type: 'boolean' }
2615
- * ];
3260
+ *
3261
+ * // Config with component classes
3262
+ * config: GridConfig<Employee> = {
3263
+ * columns: [
3264
+ * { field: 'name', header: 'Name' },
3265
+ * { field: 'bonus', header: 'Bonus', editable: true, editor: BonusEditorComponent }
3266
+ * ]
3267
+ * };
2616
3268
  * ```
2617
3269
  *
2618
3270
  * ```html
2619
- * <tbw-grid [gridConfig]="config" [columns]="columns" />
3271
+ * <tbw-grid [gridConfig]="config" [rows]="employees"></tbw-grid>
2620
3272
  * ```
2621
3273
  */
2622
3274
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
2623
3275
  gridConfig = input(...(ngDevMode ? [undefined, { debugName: "gridConfig" }] : []));
2624
3276
  /**
2625
- * Angular-specific grid configuration that supports component classes for renderers/editors.
3277
+ * @deprecated Use `gridConfig` instead. This input will be removed in a future version.
2626
3278
  *
2627
- * Use this input when you want to specify Angular component classes directly in column configs.
2628
- * Components must implement the appropriate interfaces:
2629
- * - Renderers: `AngularCellRenderer<TRow, TValue>` - requires `value()` and `row()` signal inputs
2630
- * - Editors: `AngularCellEditor<TRow, TValue>` - adds `commit` and `cancel` outputs
2631
- *
2632
- * The directive automatically processes component classes and converts them to grid-compatible
2633
- * renderer/editor functions before applying to the grid.
2634
- *
2635
- * @example
2636
- * ```typescript
2637
- * // Component that implements AngularCellEditor
2638
- * @Component({...})
2639
- * export class BonusEditorComponent implements AngularCellEditor<Employee, number> {
2640
- * value = input.required<number>();
2641
- * row = input.required<Employee>();
2642
- * commit = output<number>();
2643
- * cancel = output<void>();
2644
- * }
2645
- *
2646
- * // In your grid config
2647
- * config: AngularGridConfig<Employee> = {
2648
- * columns: [
2649
- * { field: 'name', header: 'Name' },
2650
- * { field: 'bonus', header: 'Bonus', editable: true, editor: BonusEditorComponent }
2651
- * ]
2652
- * };
2653
- * ```
3279
+ * The `angularConfig` name was inconsistent with React and Vue adapters, which both use `gridConfig`.
3280
+ * The `gridConfig` input now accepts `GridConfig` directly.
2654
3281
  *
2655
3282
  * ```html
2656
- * <tbw-grid [angularConfig]="config" [rows]="employees"></tbw-grid>
3283
+ * <!-- Before -->
3284
+ * <tbw-grid [angularConfig]="config" />
3285
+ *
3286
+ * <!-- After -->
3287
+ * <tbw-grid [gridConfig]="config" />
2657
3288
  * ```
2658
3289
  */
2659
3290
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -2697,6 +3328,9 @@ class Grid {
2697
3328
  * <tbw-grid [editing]="'click'" />
2698
3329
  * <tbw-grid [editing]="'dblclick'" />
2699
3330
  * <tbw-grid [editing]="'manual'" />
3331
+ *
3332
+ * <!-- Full config with callbacks -->
3333
+ * <tbw-grid [editing]="{ editOn: 'dblclick', onBeforeEditClose: myCallback }" />
2700
3334
  * ```
2701
3335
  */
2702
3336
  editing = input(...(ngDevMode ? [undefined, { debugName: "editing" }] : []));
@@ -2966,7 +3600,7 @@ class Grid {
2966
3600
  * <tbw-grid [export]="{ filename: 'data.csv' }" />
2967
3601
  * ```
2968
3602
  */
2969
- exportFeature = input(...(ngDevMode ? [undefined, { debugName: "exportFeature" }] : []));
3603
+ exportFeature = input(undefined, { ...(ngDevMode ? { debugName: "exportFeature" } : {}), alias: 'export' });
2970
3604
  /**
2971
3605
  * Enable print functionality.
2972
3606
  *
@@ -3284,7 +3918,7 @@ class Grid {
3284
3918
  eventListeners = new Map();
3285
3919
  ngOnInit() {
3286
3920
  // Create and register the adapter
3287
- this.adapter = new AngularGridAdapter(this.injector, this.appRef, this.viewContainerRef);
3921
+ this.adapter = new GridAdapter(this.injector, this.appRef, this.viewContainerRef);
3288
3922
  DataGridElement.registerAdapter(this.adapter);
3289
3923
  const grid = this.elementRef.nativeElement;
3290
3924
  // Wire up all event listeners based on eventOutputMap
@@ -3485,12 +4119,12 @@ class Grid {
3485
4119
  }
3486
4120
  }
3487
4121
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: Grid, deps: [], target: i0.ɵɵFactoryTarget.Directive });
3488
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.1", type: Grid, isStandalone: true, selector: "tbw-grid", inputs: { customStyles: { classPropertyName: "customStyles", publicName: "customStyles", isSignal: true, isRequired: false, transformFunction: null }, sortable: { classPropertyName: "sortable", publicName: "sortable", isSignal: true, isRequired: false, transformFunction: null }, filterable: { classPropertyName: "filterable", publicName: "filterable", isSignal: true, isRequired: false, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, gridConfig: { classPropertyName: "gridConfig", publicName: "gridConfig", 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 });
4122
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.1", type: Grid, isStandalone: true, selector: "tbw-grid", inputs: { customStyles: { classPropertyName: "customStyles", publicName: "customStyles", isSignal: true, isRequired: false, transformFunction: null }, sortable: { classPropertyName: "sortable", publicName: "sortable", isSignal: true, isRequired: false, transformFunction: null }, filterable: { classPropertyName: "filterable", publicName: "filterable", isSignal: true, isRequired: false, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, gridConfig: { classPropertyName: "gridConfig", publicName: "gridConfig", 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: "export", isSignal: true, isRequired: false, transformFunction: null }, print: { classPropertyName: "print", publicName: "print", isSignal: true, isRequired: false, transformFunction: null }, pivot: { classPropertyName: "pivot", publicName: "pivot", isSignal: true, isRequired: false, transformFunction: null }, serverSide: { classPropertyName: "serverSide", publicName: "serverSide", isSignal: true, isRequired: false, transformFunction: null } }, 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 });
3489
4123
  }
3490
4124
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: Grid, decorators: [{
3491
4125
  type: Directive,
3492
4126
  args: [{ selector: 'tbw-grid' }]
3493
- }], ctorParameters: () => [], propDecorators: { customStyles: [{ type: i0.Input, args: [{ isSignal: true, alias: "customStyles", required: false }] }], sortable: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortable", required: false }] }], filterable: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterable", required: false }] }], selectable: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectable", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], gridConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "gridConfig", 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"] }] } });
4127
+ }], ctorParameters: () => [], propDecorators: { customStyles: [{ type: i0.Input, args: [{ isSignal: true, alias: "customStyles", required: false }] }], sortable: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortable", required: false }] }], filterable: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterable", required: false }] }], selectable: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectable", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], gridConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "gridConfig", 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: "export", required: false }] }], print: [{ type: i0.Input, args: [{ isSignal: true, alias: "print", required: false }] }], pivot: [{ type: i0.Input, args: [{ isSignal: true, alias: "pivot", required: false }] }], serverSide: [{ type: i0.Input, args: [{ isSignal: true, alias: "serverSide", required: false }] }], 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"] }] } });
3494
4128
 
3495
4129
  /**
3496
4130
  * @packageDocumentation
@@ -3498,10 +4132,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImpor
3498
4132
  *
3499
4133
  * Provides directives for seamless Angular integration with the grid component.
3500
4134
  */
4135
+ // Primary export - use this
3501
4136
 
3502
4137
  /**
3503
4138
  * Generated bundle index. Do not edit.
3504
4139
  */
3505
4140
 
3506
- export { AngularGridAdapter, BaseGridEditor, GRID_ICONS, GRID_TYPE_DEFAULTS, Grid, GridColumnEditor, GridColumnView, GridDetailView, GridFormArray, GridIconRegistry, GridResponsiveCard, GridToolPanel, GridTypeRegistry, TbwEditor as TbwCellEditor, TbwRenderer as TbwCellView, TbwEditor, TbwRenderer, clearFeatureRegistry, createPluginFromFeature, getFeatureFactory, getFormArrayContext, getRegisteredFeatures, injectGrid, isComponentClass, isFeatureRegistered, provideGridIcons, provideGridTypeDefaults, registerFeature };
4141
+ export { AngularGridAdapter, BaseGridEditor, GRID_ICONS, GRID_TYPE_DEFAULTS, Grid, GridAdapter, GridColumnEditor, GridColumnView, GridDetailView, GridFormArray, GridIconRegistry, GridLazyForm, GridResponsiveCard, GridToolPanel, GridTypeRegistry, TbwEditor as TbwCellEditor, TbwRenderer as TbwCellView, TbwEditor, TbwRenderer, clearFeatureRegistry, createPluginFromFeature, getFeatureFactory, getFormArrayContext, getLazyFormContext, getRegisteredFeatures, injectGrid, isComponentClass, isFeatureRegistered, provideGridIcons, provideGridTypeDefaults, registerFeature };
3507
4142
  //# sourceMappingURL=toolbox-web-grid-angular.mjs.map