@toolbox-web/grid-angular 0.10.0 → 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 +649 -64
  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 +383 -115
  26. package/types/toolbox-web-grid-angular.d.ts.map +1 -1
@@ -2,9 +2,11 @@ import * as i0 from '@angular/core';
2
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
3
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
4
4
  import { FormGroup } from '@angular/forms';
5
- import { startWith } from 'rxjs/operators';
5
+ import { startWith, debounceTime } from 'rxjs/operators';
6
6
  import { DataGridElement } from '@toolbox-web/grid';
7
7
 
8
+ // #endregion
9
+ // #region Utilities
8
10
  /**
9
11
  * Type guard to check if a value is an Angular component class.
10
12
  *
@@ -28,6 +30,7 @@ function isComponentClass(value) {
28
30
  const fnString = Function.prototype.toString.call(value);
29
31
  return fnString.startsWith('class ') || fnString.startsWith('class{');
30
32
  }
33
+ // #endregion
31
34
 
32
35
  // Global registry mapping DOM elements to their templates
33
36
  const editorTemplateRegistry = new Map();
@@ -281,13 +284,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImpor
281
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 }] }] } });
282
285
 
283
286
  // Symbol for storing form context on the grid element
284
- const FORM_ARRAY_CONTEXT = Symbol('formArrayContext');
287
+ const FORM_ARRAY_CONTEXT$1 = Symbol('formArrayContext');
285
288
  /**
286
289
  * Gets the FormArrayContext from a grid element, if present.
287
290
  * @internal
288
291
  */
289
292
  function getFormArrayContext(gridElement) {
290
- return gridElement[FORM_ARRAY_CONTEXT];
293
+ return gridElement[FORM_ARRAY_CONTEXT$1];
291
294
  }
292
295
  /**
293
296
  * Directive that binds a FormArray directly to the grid.
@@ -347,6 +350,7 @@ class GridFormArray {
347
350
  rowCommitListener = null;
348
351
  touchListener = null;
349
352
  valueChangesSubscription = null;
353
+ statusChangesSubscriptions = [];
350
354
  /**
351
355
  * The FormArray to bind to the grid.
352
356
  */
@@ -358,6 +362,7 @@ class GridFormArray {
358
362
  * - After a cell commit, if the FormControl is invalid, the cell is marked with `setInvalid()`
359
363
  * - When a FormControl becomes valid, `clearInvalid()` is called
360
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
361
366
  *
362
367
  * @default true
363
368
  */
@@ -412,6 +417,15 @@ class GridFormArray {
412
417
  }
413
418
  };
414
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
+ });
415
429
  }
416
430
  ngOnDestroy() {
417
431
  const grid = this.elementRef.nativeElement;
@@ -429,8 +443,93 @@ class GridFormArray {
429
443
  if (this.valueChangesSubscription) {
430
444
  this.valueChangesSubscription.unsubscribe();
431
445
  }
446
+ // Clean up status change subscriptions (grid mode)
447
+ this.statusChangesSubscriptions.forEach((sub) => sub.unsubscribe());
448
+ this.statusChangesSubscriptions = [];
432
449
  this.#clearFormContext(grid);
433
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
+ }
434
533
  /**
435
534
  * Checks if the FormArray contains FormGroups.
436
535
  */
@@ -518,13 +617,13 @@ class GridFormArray {
518
617
  return hasErrors ? errors : null;
519
618
  },
520
619
  };
521
- grid[FORM_ARRAY_CONTEXT] = context;
620
+ grid[FORM_ARRAY_CONTEXT$1] = context;
522
621
  }
523
622
  /**
524
623
  * Clears the FormArrayContext from the grid element.
525
624
  */
526
625
  #clearFormContext(grid) {
527
- delete grid[FORM_ARRAY_CONTEXT];
626
+ delete grid[FORM_ARRAY_CONTEXT$1];
528
627
  }
529
628
  /**
530
629
  * Handles cell-commit events by updating the FormControl in the FormGroup.
@@ -1185,7 +1284,7 @@ function getAnyEditorTemplate(element) {
1185
1284
  * ```typescript
1186
1285
  * import { Component, inject, EnvironmentInjector, ApplicationRef, ViewContainerRef } from '@angular/core';
1187
1286
  * import { GridElement } from '@toolbox-web/grid';
1188
- * import { AngularGridAdapter } from '@toolbox-web/grid-angular';
1287
+ * import { GridAdapter } from '@toolbox-web/grid-angular';
1189
1288
  *
1190
1289
  * @Component({
1191
1290
  * selector: 'app-root',
@@ -1196,7 +1295,7 @@ function getAnyEditorTemplate(element) {
1196
1295
  * const injector = inject(EnvironmentInjector);
1197
1296
  * const appRef = inject(ApplicationRef);
1198
1297
  * const viewContainerRef = inject(ViewContainerRef);
1199
- * GridElement.registerAdapter(new AngularGridAdapter(injector, appRef, viewContainerRef));
1298
+ * GridElement.registerAdapter(new GridAdapter(injector, appRef, viewContainerRef));
1200
1299
  * }
1201
1300
  * }
1202
1301
  * ```
@@ -1235,7 +1334,7 @@ function getAnyEditorTemplate(element) {
1235
1334
  * - Handles editor callbacks (onCommit/onCancel)
1236
1335
  * - Manages view lifecycle and change detection
1237
1336
  */
1238
- class AngularGridAdapter {
1337
+ class GridAdapter {
1239
1338
  injector;
1240
1339
  appRef;
1241
1340
  viewContainerRef;
@@ -1265,9 +1364,9 @@ class AngularGridAdapter {
1265
1364
  *
1266
1365
  * @example
1267
1366
  * ```typescript
1268
- * import { AngularGridAdapter, type AngularGridConfig } from '@toolbox-web/grid-angular';
1367
+ * import { GridAdapter, type GridConfig } from '@toolbox-web/grid-angular';
1269
1368
  *
1270
- * const config: AngularGridConfig<Employee> = {
1369
+ * const config: GridConfig<Employee> = {
1271
1370
  * columns: [
1272
1371
  * { field: 'status', renderer: StatusBadgeComponent, editor: StatusEditorComponent },
1273
1372
  * ],
@@ -1275,7 +1374,7 @@ class AngularGridAdapter {
1275
1374
  *
1276
1375
  * // In component
1277
1376
  * constructor() {
1278
- * const adapter = inject(AngularGridAdapter); // or create new instance
1377
+ * const adapter = inject(GridAdapter); // or create new instance
1279
1378
  * this.processedConfig = adapter.processGridConfig(config);
1280
1379
  * }
1281
1380
  * ```
@@ -1746,6 +1845,11 @@ class AngularGridAdapter {
1746
1845
  this.componentRefs = [];
1747
1846
  }
1748
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;
1749
1853
 
1750
1854
  /**
1751
1855
  * Icon configuration registry for Angular applications.
@@ -2424,6 +2528,490 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImpor
2424
2528
  type: Directive
2425
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"] }] } });
2426
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
+
2427
3015
  /**
2428
3016
  * Directive that automatically registers the Angular adapter with tbw-grid elements.
2429
3017
  *
@@ -2454,7 +3042,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImpor
2454
3042
  * ```
2455
3043
  *
2456
3044
  * The directive automatically:
2457
- * - Creates an AngularGridAdapter instance
3045
+ * - Creates a GridAdapter instance
2458
3046
  * - Registers it with the GridElement
2459
3047
  * - Injects custom styles into the grid
2460
3048
  * - Handles cleanup on destruction
@@ -2467,11 +3055,18 @@ class Grid {
2467
3055
  iconRegistry = inject(GridIconRegistry, { optional: true });
2468
3056
  adapter = null;
2469
3057
  constructor() {
2470
- // Effect to process angularConfig and apply to grid
3058
+ // Effect to process gridConfig and apply to grid
2471
3059
  // This merges feature input plugins with the user's config plugins
2472
3060
  effect(() => {
2473
- const angularCfg = this.angularConfig();
3061
+ const deprecatedAngularConfig = this.angularConfig();
2474
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;
2475
3070
  if (!this.adapter)
2476
3071
  return;
2477
3072
  // Create plugins from feature inputs
@@ -2495,25 +3090,24 @@ class Grid {
2495
3090
  // Registry icons are base, config.icons override them
2496
3091
  const registryIcons = this.iconRegistry?.getAll();
2497
3092
  if (registryIcons && Object.keys(registryIcons).length > 0) {
2498
- const existingIcons = angularCfg?.icons || userGridConfig?.icons || {};
3093
+ const existingIcons = angularCfg?.icons || {};
2499
3094
  coreConfigOverrides['icons'] = { ...registryIcons, ...existingIcons };
2500
3095
  }
2501
- // 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)
2502
3097
  const processedConfig = angularCfg ? this.adapter.processGridConfig(angularCfg) : null;
2503
- // 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
2504
3099
  // or config overrides to merge, do NOT overwrite grid.gridConfig.
2505
3100
  // This allows [gridConfig]="myConfig" binding to work correctly without the directive
2506
3101
  // creating a new object that strips properties like typeDefaults.
2507
3102
  const hasFeaturePlugins = featurePlugins.length > 0;
2508
3103
  const hasConfigOverrides = Object.keys(coreConfigOverrides).length > 0;
2509
- // Use the gridConfig input signal (preferred) or fallback to what's on the grid element
2510
3104
  // The input signal gives us reactive tracking of the user's config
2511
- const existingConfig = userGridConfig || {};
2512
- if (!processedConfig && !hasFeaturePlugins && !hasConfigOverrides && !userGridConfig) {
3105
+ const existingConfig = angularCfg || {};
3106
+ if (!processedConfig && !hasFeaturePlugins && !hasConfigOverrides && !angularCfg) {
2513
3107
  // Nothing to merge and no config input - let the user's DOM binding work directly
2514
3108
  return;
2515
3109
  }
2516
- // Merge: existing config (from [gridConfig] input) < processed angularConfig < feature plugins
3110
+ // Merge: processed config < feature plugins
2517
3111
  const configPlugins = processedConfig?.plugins || existingConfig.plugins || [];
2518
3112
  const mergedPlugins = [...featurePlugins, ...configPlugins];
2519
3113
  // Build the final config, preserving ALL existing properties (including typeDefaults)
@@ -2642,65 +3236,55 @@ class Grid {
2642
3236
  */
2643
3237
  loading = input(...(ngDevMode ? [undefined, { debugName: "loading" }] : []));
2644
3238
  /**
2645
- * Core grid configuration object.
3239
+ * Grid configuration object with optional Angular-specific extensions.
2646
3240
  *
2647
- * Use this input for the base GridConfig (typeDefaults, getRowId, etc.).
2648
- * This is the same as binding directly to the web component's gridConfig property,
2649
- * 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.
2650
3243
  *
2651
- * 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
2652
3247
  *
2653
3248
  * @example
2654
3249
  * ```typescript
3250
+ * // Simple config with plain renderers
2655
3251
  * config: GridConfig = {
3252
+ * columns: [
3253
+ * { field: 'name', header: 'Name' },
3254
+ * { field: 'active', type: 'boolean' }
3255
+ * ],
2656
3256
  * typeDefaults: {
2657
3257
  * boolean: { renderer: (ctx) => ctx.value ? '✓' : '✗' }
2658
3258
  * }
2659
3259
  * };
2660
- * columns = [
2661
- * { field: 'active', type: 'boolean' }
2662
- * ];
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
+ * };
2663
3268
  * ```
2664
3269
  *
2665
3270
  * ```html
2666
- * <tbw-grid [gridConfig]="config" [columns]="columns" />
3271
+ * <tbw-grid [gridConfig]="config" [rows]="employees"></tbw-grid>
2667
3272
  * ```
2668
3273
  */
2669
3274
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
2670
3275
  gridConfig = input(...(ngDevMode ? [undefined, { debugName: "gridConfig" }] : []));
2671
3276
  /**
2672
- * Angular-specific grid configuration that supports component classes for renderers/editors.
2673
- *
2674
- * Use this input when you want to specify Angular component classes directly in column configs.
2675
- * Components must implement the appropriate interfaces:
2676
- * - Renderers: `AngularCellRenderer<TRow, TValue>` - requires `value()` and `row()` signal inputs
2677
- * - Editors: `AngularCellEditor<TRow, TValue>` - adds `commit` and `cancel` outputs
2678
- *
2679
- * The directive automatically processes component classes and converts them to grid-compatible
2680
- * renderer/editor functions before applying to the grid.
2681
- *
2682
- * @example
2683
- * ```typescript
2684
- * // Component that implements AngularCellEditor
2685
- * @Component({...})
2686
- * export class BonusEditorComponent implements AngularCellEditor<Employee, number> {
2687
- * value = input.required<number>();
2688
- * row = input.required<Employee>();
2689
- * commit = output<number>();
2690
- * cancel = output<void>();
2691
- * }
3277
+ * @deprecated Use `gridConfig` instead. This input will be removed in a future version.
2692
3278
  *
2693
- * // In your grid config
2694
- * config: AngularGridConfig<Employee> = {
2695
- * columns: [
2696
- * { field: 'name', header: 'Name' },
2697
- * { field: 'bonus', header: 'Bonus', editable: true, editor: BonusEditorComponent }
2698
- * ]
2699
- * };
2700
- * ```
3279
+ * The `angularConfig` name was inconsistent with React and Vue adapters, which both use `gridConfig`.
3280
+ * The `gridConfig` input now accepts `GridConfig` directly.
2701
3281
  *
2702
3282
  * ```html
2703
- * <tbw-grid [angularConfig]="config" [rows]="employees"></tbw-grid>
3283
+ * <!-- Before -->
3284
+ * <tbw-grid [angularConfig]="config" />
3285
+ *
3286
+ * <!-- After -->
3287
+ * <tbw-grid [gridConfig]="config" />
2704
3288
  * ```
2705
3289
  */
2706
3290
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -3016,7 +3600,7 @@ class Grid {
3016
3600
  * <tbw-grid [export]="{ filename: 'data.csv' }" />
3017
3601
  * ```
3018
3602
  */
3019
- exportFeature = input(...(ngDevMode ? [undefined, { debugName: "exportFeature" }] : []));
3603
+ exportFeature = input(undefined, { ...(ngDevMode ? { debugName: "exportFeature" } : {}), alias: 'export' });
3020
3604
  /**
3021
3605
  * Enable print functionality.
3022
3606
  *
@@ -3334,7 +3918,7 @@ class Grid {
3334
3918
  eventListeners = new Map();
3335
3919
  ngOnInit() {
3336
3920
  // Create and register the adapter
3337
- this.adapter = new AngularGridAdapter(this.injector, this.appRef, this.viewContainerRef);
3921
+ this.adapter = new GridAdapter(this.injector, this.appRef, this.viewContainerRef);
3338
3922
  DataGridElement.registerAdapter(this.adapter);
3339
3923
  const grid = this.elementRef.nativeElement;
3340
3924
  // Wire up all event listeners based on eventOutputMap
@@ -3535,12 +4119,12 @@ class Grid {
3535
4119
  }
3536
4120
  }
3537
4121
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: Grid, deps: [], target: i0.ɵɵFactoryTarget.Directive });
3538
- 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 });
3539
4123
  }
3540
4124
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: Grid, decorators: [{
3541
4125
  type: Directive,
3542
4126
  args: [{ selector: 'tbw-grid' }]
3543
- }], 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"] }] } });
3544
4128
 
3545
4129
  /**
3546
4130
  * @packageDocumentation
@@ -3548,10 +4132,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImpor
3548
4132
  *
3549
4133
  * Provides directives for seamless Angular integration with the grid component.
3550
4134
  */
4135
+ // Primary export - use this
3551
4136
 
3552
4137
  /**
3553
4138
  * Generated bundle index. Do not edit.
3554
4139
  */
3555
4140
 
3556
- 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 };
3557
4142
  //# sourceMappingURL=toolbox-web-grid-angular.mjs.map